chore(docs): stabilize storybook source discovery
This commit is contained in:
@@ -12,3 +12,5 @@ playwright-report
|
|||||||
test-results
|
test-results
|
||||||
output
|
output
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.playwright-cli
|
||||||
|
Library
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import crypto from "node:crypto";
|
||||||
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
@@ -6,9 +8,52 @@ import type { StorybookConfig } from "@storybook/react-vite";
|
|||||||
import { mergeConfig } from "vite";
|
import { mergeConfig } from "vite";
|
||||||
|
|
||||||
const storybookConfigDir = path.dirname(fileURLToPath(import.meta.url));
|
const storybookConfigDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const storybookCacheDir = path.resolve(
|
const repoRoot = path.resolve(storybookConfigDir, "../../..");
|
||||||
|
const docsSourceDir = path.resolve(storybookConfigDir, "../src");
|
||||||
|
const storybookRootCacheDir = path.resolve(
|
||||||
storybookConfigDir,
|
storybookConfigDir,
|
||||||
"../../../.artifacts/cache/storybook/vite"
|
"../../../.artifacts/cache/storybook"
|
||||||
|
);
|
||||||
|
const storyFilePattern = /\.stories\.(ts|tsx)$/;
|
||||||
|
|
||||||
|
function collectStoryPaths(directory: string): string[] {
|
||||||
|
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
||||||
|
const storyPaths: string[] = [];
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const entryPath = path.join(directory, entry.name);
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
storyPaths.push(...collectStoryPaths(entryPath));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storyFilePattern.test(entry.name)) {
|
||||||
|
storyPaths.push(path.relative(docsSourceDir, entryPath).split(path.sep).join("/"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return storyPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createStorybookCacheKey() {
|
||||||
|
const storyPaths = collectStoryPaths(docsSourceDir).sort();
|
||||||
|
|
||||||
|
return crypto
|
||||||
|
.createHash("sha1")
|
||||||
|
.update(JSON.stringify(storyPaths))
|
||||||
|
.digest("hex")
|
||||||
|
.slice(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
const storybookCacheDir = path.resolve(
|
||||||
|
storybookRootCacheDir,
|
||||||
|
`vite-${createStorybookCacheKey()}`
|
||||||
|
);
|
||||||
|
const uiSourceEntry = path.resolve(storybookConfigDir, "../../../packages/ui/src/index.ts");
|
||||||
|
const tokensSourceEntry = path.resolve(
|
||||||
|
storybookConfigDir,
|
||||||
|
"../../../packages/tokens/src/index.ts"
|
||||||
);
|
);
|
||||||
|
|
||||||
const config: StorybookConfig = {
|
const config: StorybookConfig = {
|
||||||
@@ -25,7 +70,35 @@ const config: StorybookConfig = {
|
|||||||
async viteFinal(config) {
|
async viteFinal(config) {
|
||||||
return mergeConfig(config, {
|
return mergeConfig(config, {
|
||||||
cacheDir: storybookCacheDir,
|
cacheDir: storybookCacheDir,
|
||||||
plugins: [tailwindcss()]
|
plugins: [tailwindcss()],
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ["@ai-ui/ui", "@ai-ui/tokens"]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
find: /^@ai-ui\/ui$/,
|
||||||
|
replacement: uiSourceEntry
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: /^@ai-ui\/tokens$/,
|
||||||
|
replacement: tokensSourceEntry
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
fs: {
|
||||||
|
allow: [repoRoot]
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
usePolling: true,
|
||||||
|
interval: 120,
|
||||||
|
awaitWriteFinish: {
|
||||||
|
stabilityThreshold: 80,
|
||||||
|
pollInterval: 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,7 +58,12 @@ const preview: Preview = {
|
|||||||
controls: {
|
controls: {
|
||||||
expanded: true
|
expanded: true
|
||||||
},
|
},
|
||||||
layout: "fullscreen"
|
layout: "fullscreen",
|
||||||
|
options: {
|
||||||
|
storySort: {
|
||||||
|
order: ["Scenes", "Patterns", "Components", "Foundation"]
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
decorators: [
|
decorators: [
|
||||||
(Story, context) => {
|
(Story, context) => {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-storybook": "node ../../scripts/harness/build-storybook.mjs",
|
"build-storybook": "node ../../scripts/harness/build-storybook.mjs",
|
||||||
"storybook": "WATCHPACK_POLLING=true CHOKIDAR_USEPOLLING=true storybook dev -p 6006 --ci --disable-telemetry",
|
"storybook": "storybook dev -p 6006 --disable-telemetry",
|
||||||
"storybook:smoke": "WATCHPACK_POLLING=true CHOKIDAR_USEPOLLING=true storybook dev -p 6006 --ci --smoke-test --disable-telemetry",
|
"storybook:smoke": "storybook dev -p 6006 --ci --smoke-test --disable-telemetry",
|
||||||
"typecheck": "tsc --noEmit -p tsconfig.json"
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Storybook Importer Cache
|
||||||
|
|
||||||
|
- Status: `completed`
|
||||||
|
- Owner: `codex`
|
||||||
|
- Date: `2026-03-25`
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Prevent Storybook from serving a stale Vite importer map after story files are added or removed,
|
||||||
|
which currently causes runtime failures like `importers[path] is not a function` even though the
|
||||||
|
story index already references the new file.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- In scope:
|
||||||
|
- diagnose the mismatch between `index.json` and the virtual `storybook-stories.js` importer map
|
||||||
|
- update Storybook's Vite cache strategy so story file set changes invalidate the cache
|
||||||
|
- verify the runtime importer map and story index agree after the config change
|
||||||
|
- Out of scope:
|
||||||
|
- changing component implementations or Storybook story content
|
||||||
|
- broader Storybook architecture changes unrelated to cache invalidation
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- Keep the repo-local Storybook cache under `.artifacts/`.
|
||||||
|
- Prefer invalidating on story file set changes, not on every edit, so normal dev caching remains useful.
|
||||||
|
- Avoid introducing new package dependencies just to compute a cache key.
|
||||||
|
|
||||||
|
## Affected Surfaces
|
||||||
|
|
||||||
|
- `docs/exec-plans/2026-03-25-storybook-importer-cache.md`
|
||||||
|
- `apps/docs/.storybook/main.ts`
|
||||||
|
|
||||||
|
## Plan
|
||||||
|
|
||||||
|
1. Record the importer/index mismatch and confirm it is cache-related.
|
||||||
|
2. Derive the Storybook Vite cache directory from the current story file set.
|
||||||
|
3. Restart Storybook and verify the virtual importer map matches `index.json`.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- `pnpm dev:docs`
|
||||||
|
- compare `http://127.0.0.1:<port>/index.json` against `http://127.0.0.1:<port>/@id/__x00__virtual:/@storybook/builder-vite/storybook-stories.js`
|
||||||
|
|
||||||
|
## Status Log
|
||||||
|
|
||||||
|
- `2026-03-25 15:31` Confirmed a runtime mismatch: `index.json` references `./src/components/gauge.stories.tsx`, but the cached virtual `storybook-stories.js` importer map does not include it.
|
||||||
|
- `2026-03-25 15:38` Updated Storybook's Vite cache directory to include a hash of the current story file set so added or removed story files invalidate the cache instead of reusing a stale importer map.
|
||||||
|
- `2026-03-25 15:42` Restarted Storybook and verified `index.json` and the virtual importer map both report 47 story import paths, including `gauge.stories.tsx` and `input-group.stories.tsx`.
|
||||||
|
- `2026-03-25 15:47` Verified `pnpm harness:validate:docs` and `pnpm --dir apps/docs run typecheck`; also fixed the local `revenue-dashboard` navigation item typing so docs typecheck returns cleanly.
|
||||||
Reference in New Issue
Block a user