@storyshelf/affected 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,6 +24,27 @@ const result = await computeAffected({ cwd, buildDir, baseSha, headSha });
24
24
 
25
25
  See the [StoryShelf documentation](https://github.com/GuptaSiddhant/StoryShelf#readme) for configuration details.
26
26
 
27
+ ## API
28
+
29
+ ```ts
30
+ import {
31
+ computeAffected, // full pipeline: git + index + stats + trace (never throws)
32
+ traceAffected, // graph + changed files + story files -> affected, or null for full
33
+ selectAffectedStories, // stories + affected paths -> { render, inherit }
34
+ changedFiles, // git diff baseline..HEAD plus staged/untracked
35
+ gitHeadSha, // local git HEAD, or null without git
36
+ gitBranchName, // local git branch, or null without git
37
+ gitRepoStatus, // "ok" | "shallow" | "no-git"
38
+ localSha, // synthetic unique sha (LOCAL_SHA_PREFIX) for non-git checkouts
39
+ loadDepGraph, // preview-stats.json -> bidirectional DepGraph (null when unusable)
40
+ loadStoryImportPaths, // index.json/stories.json -> story import paths
41
+ } from "@storyshelf/affected";
42
+ ```
43
+
44
+ `computeAffected({ cwd, buildDir, baseSha, headSha, untraced?, statsFile? })` returns `{ affectedImportPaths: string[] | null, changedFiles, baselineSha, fullReason }`. A `null` affected set means "render everything" — the contract is fail-open by design, which is what lets affected capture stay enabled by default.
45
+
46
+ Accepted stats shapes: Vite `preview-stats.json` module arrays (`id`/`importedIds`), record-shaped module maps, and Webpack-style `reasons` edges. Absolute stats paths match repo-relative changes by suffix.
47
+
27
48
  ## License
28
49
 
29
50
  MIT
package/dist/git.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
- //#region src/git.d.ts
4
3
  /** Upper bound on changed files before falling back to a full render. */
5
4
  declare const MAX_CHANGED_FILES = 10000;
6
5
  /** Prefix marking CLI-synthesized identities for checkouts without git. */
@@ -42,6 +41,5 @@ declare function gitHeadSha(cwd: string): string | null;
42
41
  declare function gitBranchName(cwd: string): string | null;
43
42
  /** Synthesize a unique local sha for checkouts without git. */
44
43
  declare function localSha(): string;
45
- //#endregion
46
44
  export { GitRepoStatus, LOCAL_BRANCH, LOCAL_SHA_PREFIX, MAX_CHANGED_FILES, changedFiles, gitBranchName, gitHeadSha, gitRepoStatus, isShallowRepo, localSha };
47
45
  //# sourceMappingURL=git.d.mts.map
package/dist/git.mjs CHANGED
@@ -3,7 +3,7 @@ import "node:url";
3
3
  import.meta.url;
4
4
  import { execFileSync } from "node:child_process";
5
5
  import { randomUUID } from "node:crypto";
6
- //#region src/git.ts
6
+ // src/git.ts
7
7
  /** Upper bound on changed files before falling back to a full render. */
8
8
  const MAX_CHANGED_FILES = 1e4;
9
9
  /** Prefix marking CLI-synthesized identities for checkouts without git. */
@@ -115,7 +115,6 @@ function gitBranchName(cwd) {
115
115
  function localSha() {
116
116
  return `${LOCAL_SHA_PREFIX}${randomUUID().replaceAll("-", "").slice(0, 12)}`;
117
117
  }
118
- //#endregion
119
118
  export { LOCAL_BRANCH, LOCAL_SHA_PREFIX, MAX_CHANGED_FILES, changedFiles, gitBranchName, gitHeadSha, gitRepoStatus, isShallowRepo, localSha };
120
119
 
121
120
  //# sourceMappingURL=git.mjs.map
package/dist/index.d.mts CHANGED
@@ -5,7 +5,7 @@ import { GitRepoStatus, LOCAL_BRANCH, LOCAL_SHA_PREFIX, MAX_CHANGED_FILES, chang
5
5
  import { STATS_FILENAME, buildGraph, graphModulesFor, loadDepGraph, loadStoryImportPaths, normalizePath, statsPathFor } from "./stats.mjs";
6
6
  import { matchesAnyGlob, traceAffected } from "./trace.mjs";
7
7
  import { StoryPartition, selectAffectedStories } from "./select.mjs";
8
- //#region src/index.d.ts
8
+ import { PackageUsage, TrackedPackage, extractPackageUsage } from "./usage.mjs";
9
9
  /**
10
10
  * Compute the affected story set for an upload. Never throws: every failure
11
11
  * degrades to a full render with a machine-readable `fullReason`, so
@@ -14,6 +14,5 @@ import { StoryPartition, selectAffectedStories } from "./select.mjs";
14
14
  * @param input - Repository root, build dir, revisions, and trace options.
15
15
  */
16
16
  declare function computeAffected(input: AffectedInput): Promise<AffectedResult>;
17
- //#endregion
18
- export { type AffectedInput, type AffectedResult, type DepGraph, type GitRepoStatus, LOCAL_BRANCH, LOCAL_SHA_PREFIX, MAX_CHANGED_FILES, STATS_FILENAME, type StoryLike, type StoryPartition, type TraceOptions, buildGraph, changedFiles, computeAffected, gitBranchName, gitHeadSha, gitRepoStatus, graphModulesFor, isShallowRepo, loadDepGraph, loadStoryImportPaths, localSha, matchesAnyGlob, normalizePath, selectAffectedStories, statsPathFor, traceAffected };
17
+ export { type AffectedInput, type AffectedResult, type DepGraph, type GitRepoStatus, LOCAL_BRANCH, LOCAL_SHA_PREFIX, MAX_CHANGED_FILES, type PackageUsage, STATS_FILENAME, type StoryLike, type StoryPartition, type TraceOptions, type TrackedPackage, buildGraph, changedFiles, computeAffected, extractPackageUsage, gitBranchName, gitHeadSha, gitRepoStatus, graphModulesFor, isShallowRepo, loadDepGraph, loadStoryImportPaths, localSha, matchesAnyGlob, normalizePath, selectAffectedStories, statsPathFor, traceAffected };
19
18
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -5,7 +5,8 @@ import { LOCAL_BRANCH, LOCAL_SHA_PREFIX, MAX_CHANGED_FILES, changedFiles, gitBra
5
5
  import { STATS_FILENAME, buildGraph, graphModulesFor, loadDepGraph, loadStoryImportPaths, normalizePath, statsPathFor } from "./stats.mjs";
6
6
  import { matchesAnyGlob, traceAffected } from "./trace.mjs";
7
7
  import { selectAffectedStories } from "./select.mjs";
8
- //#region src/index.ts
8
+ import { extractPackageUsage } from "./usage.mjs";
9
+ // src/index.ts
9
10
  /**
10
11
  * Compute the affected story set for an upload. Never throws: every failure
11
12
  * degrades to a full render with a machine-readable `fullReason`, so
@@ -69,7 +70,6 @@ async function computeSelective(input) {
69
70
  return fullResult(changed, input.baseSha, reason);
70
71
  }
71
72
  }
72
- //#endregion
73
- export { LOCAL_BRANCH, LOCAL_SHA_PREFIX, MAX_CHANGED_FILES, STATS_FILENAME, buildGraph, changedFiles, computeAffected, gitBranchName, gitHeadSha, gitRepoStatus, graphModulesFor, isShallowRepo, loadDepGraph, loadStoryImportPaths, localSha, matchesAnyGlob, normalizePath, selectAffectedStories, statsPathFor, traceAffected };
73
+ export { LOCAL_BRANCH, LOCAL_SHA_PREFIX, MAX_CHANGED_FILES, STATS_FILENAME, buildGraph, changedFiles, computeAffected, extractPackageUsage, gitBranchName, gitHeadSha, gitRepoStatus, graphModulesFor, isShallowRepo, loadDepGraph, loadStoryImportPaths, localSha, matchesAnyGlob, normalizePath, selectAffectedStories, statsPathFor, traceAffected };
74
74
 
75
75
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { changedFiles, gitRepoStatus, MAX_CHANGED_FILES } from \"./git.ts\";\nimport { loadDepGraph, loadStoryImportPaths, statsPathFor } from \"./stats.ts\";\nimport { traceAffected } from \"./trace.ts\";\nimport type { AffectedInput, AffectedResult } from \"./types.ts\";\n\n/**\n * Compute the affected story set for an upload. Never throws: every failure\n * degrades to a full render with a machine-readable `fullReason`, so\n * affected capture can stay enabled by default without new failure modes.\n *\n * @param input - Repository root, build dir, revisions, and trace options.\n */\nexport async function computeAffected(input: AffectedInput): Promise<AffectedResult> {\n try {\n return await computeSelective(input);\n } catch {\n return fullResult([], input.baseSha, \"affected-computation-failed\");\n }\n}\n\n/** Full-render result with a reason. */\nfunction fullResult(changed: string[], baselineSha: string | null, reason: string): AffectedResult {\n return { affectedImportPaths: null, changedFiles: changed, baselineSha, fullReason: reason };\n}\n\n/** Selective result for an affected import-path set. */\nfunction selectiveResult(changed: string[], baselineSha: string, paths: string[]): AffectedResult {\n return { affectedImportPaths: paths, changedFiles: changed, baselineSha, fullReason: null };\n}\n\n/** Guarded changed-file list, throwing a full-render reason when unusable. */\nfunction guardedChanged(input: AffectedInput): string[] {\n const repo = gitRepoStatus(input.cwd);\n if (repo !== \"ok\") {\n throw new Error(repo === \"shallow\" ? \"shallow-clone\" : \"not-a-git-repo\");\n }\n const changed = changedFiles(input.cwd, input.baseSha, input.headSha);\n if (changed.length > MAX_CHANGED_FILES) {\n throw new Error(\"too-many-changed-files\");\n }\n return changed;\n}\n\n/** Trace the affected set once inputs are validated. Throws reason strings. */\nasync function traceSelective(input: AffectedInput, changed: string[]): Promise<AffectedResult> {\n if (changed.length === 0) {\n return selectiveResult(changed, input.baseSha, []);\n }\n const stories = await loadStoryImportPaths(input.cwd, input.buildDir).catch(() => {\n throw new Error(\"story-index-unreadable\");\n });\n const graph = await loadDepGraph(statsPathFor(input.cwd, input.buildDir, input.statsFile));\n if (!graph) {\n throw new Error(\"dependency-graph-unavailable\");\n }\n const affected = traceAffected(graph, changed, stories, { untraced: input.untraced });\n if (affected === null) {\n throw new Error(\"global-file-changed\");\n }\n return selectiveResult(changed, input.baseSha, affected);\n}\n\n/** Selective computation with reason-string failures mapped to full renders. */\nasync function computeSelective(input: AffectedInput): Promise<AffectedResult> {\n let changed: string[] = [];\n try {\n changed = guardedChanged(input);\n return await traceSelective(input, changed);\n } catch (error) {\n const reason = error instanceof Error ? error.message : \"affected-computation-failed\";\n return fullResult(changed, input.baseSha, reason);\n }\n}\n\nexport type { AffectedInput, AffectedResult, DepGraph, StoryLike, TraceOptions } from \"./types.ts\";\nexport {\n changedFiles,\n gitBranchName,\n gitHeadSha,\n gitRepoStatus,\n isShallowRepo,\n LOCAL_BRANCH,\n LOCAL_SHA_PREFIX,\n localSha,\n MAX_CHANGED_FILES,\n} from \"./git.ts\";\nexport type { GitRepoStatus } from \"./git.ts\";\nexport {\n buildGraph,\n graphModulesFor,\n loadDepGraph,\n loadStoryImportPaths,\n normalizePath,\n STATS_FILENAME,\n statsPathFor,\n} from \"./stats.ts\";\nexport { matchesAnyGlob, traceAffected } from \"./trace.ts\";\nexport { selectAffectedStories, type StoryPartition } from \"./select.ts\";\n"],"mappings":";;;;;;;;;;;;;;;AAYA,eAAsB,gBAAgB,OAA+C;CACnF,IAAI;EACF,OAAO,MAAM,iBAAiB,KAAK;CACrC,QAAQ;EACN,OAAO,WAAW,CAAC,GAAG,MAAM,SAAS,6BAA6B;CACpE;AACF;;AAGA,SAAS,WAAW,SAAmB,aAA4B,QAAgC;CACjG,OAAO;EAAE,qBAAqB;EAAM,cAAc;EAAS;EAAa,YAAY;CAAO;AAC7F;;AAGA,SAAS,gBAAgB,SAAmB,aAAqB,OAAiC;CAChG,OAAO;EAAE,qBAAqB;EAAO,cAAc;EAAS;EAAa,YAAY;CAAK;AAC5F;;AAGA,SAAS,eAAe,OAAgC;CACtD,MAAM,OAAO,cAAc,MAAM,GAAG;CACpC,IAAI,SAAS,MACX,MAAM,IAAI,MAAM,SAAS,YAAY,kBAAkB,gBAAgB;CAEzE,MAAM,UAAU,aAAa,MAAM,KAAK,MAAM,SAAS,MAAM,OAAO;CACpE,IAAI,QAAQ,SAAA,KACV,MAAM,IAAI,MAAM,wBAAwB;CAE1C,OAAO;AACT;;AAGA,eAAe,eAAe,OAAsB,SAA4C;CAC9F,IAAI,QAAQ,WAAW,GACrB,OAAO,gBAAgB,SAAS,MAAM,SAAS,CAAC,CAAC;CAEnD,MAAM,UAAU,MAAM,qBAAqB,MAAM,KAAK,MAAM,QAAQ,CAAC,CAAC,YAAY;EAChF,MAAM,IAAI,MAAM,wBAAwB;CAC1C,CAAC;CACD,MAAM,QAAQ,MAAM,aAAa,aAAa,MAAM,KAAK,MAAM,UAAU,MAAM,SAAS,CAAC;CACzF,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,8BAA8B;CAEhD,MAAM,WAAW,cAAc,OAAO,SAAS,SAAS,EAAE,UAAU,MAAM,SAAS,CAAC;CACpF,IAAI,aAAa,MACf,MAAM,IAAI,MAAM,qBAAqB;CAEvC,OAAO,gBAAgB,SAAS,MAAM,SAAS,QAAQ;AACzD;;AAGA,eAAe,iBAAiB,OAA+C;CAC7E,IAAI,UAAoB,CAAC;CACzB,IAAI;EACF,UAAU,eAAe,KAAK;EAC9B,OAAO,MAAM,eAAe,OAAO,OAAO;CAC5C,SAAS,OAAO;EACd,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU;EACxD,OAAO,WAAW,SAAS,MAAM,SAAS,MAAM;CAClD;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { changedFiles, gitRepoStatus, MAX_CHANGED_FILES } from \"./git.ts\";\nimport { loadDepGraph, loadStoryImportPaths, statsPathFor } from \"./stats.ts\";\nimport { traceAffected } from \"./trace.ts\";\nimport type { AffectedInput, AffectedResult } from \"./types.ts\";\n\n/**\n * Compute the affected story set for an upload. Never throws: every failure\n * degrades to a full render with a machine-readable `fullReason`, so\n * affected capture can stay enabled by default without new failure modes.\n *\n * @param input - Repository root, build dir, revisions, and trace options.\n */\nexport async function computeAffected(input: AffectedInput): Promise<AffectedResult> {\n try {\n return await computeSelective(input);\n } catch {\n return fullResult([], input.baseSha, \"affected-computation-failed\");\n }\n}\n\n/** Full-render result with a reason. */\nfunction fullResult(changed: string[], baselineSha: string | null, reason: string): AffectedResult {\n return { affectedImportPaths: null, changedFiles: changed, baselineSha, fullReason: reason };\n}\n\n/** Selective result for an affected import-path set. */\nfunction selectiveResult(changed: string[], baselineSha: string, paths: string[]): AffectedResult {\n return { affectedImportPaths: paths, changedFiles: changed, baselineSha, fullReason: null };\n}\n\n/** Guarded changed-file list, throwing a full-render reason when unusable. */\nfunction guardedChanged(input: AffectedInput): string[] {\n const repo = gitRepoStatus(input.cwd);\n if (repo !== \"ok\") {\n throw new Error(repo === \"shallow\" ? \"shallow-clone\" : \"not-a-git-repo\");\n }\n const changed = changedFiles(input.cwd, input.baseSha, input.headSha);\n if (changed.length > MAX_CHANGED_FILES) {\n throw new Error(\"too-many-changed-files\");\n }\n return changed;\n}\n\n/** Trace the affected set once inputs are validated. Throws reason strings. */\nasync function traceSelective(input: AffectedInput, changed: string[]): Promise<AffectedResult> {\n if (changed.length === 0) {\n return selectiveResult(changed, input.baseSha, []);\n }\n const stories = await loadStoryImportPaths(input.cwd, input.buildDir).catch(() => {\n throw new Error(\"story-index-unreadable\");\n });\n const graph = await loadDepGraph(statsPathFor(input.cwd, input.buildDir, input.statsFile));\n if (!graph) {\n throw new Error(\"dependency-graph-unavailable\");\n }\n const affected = traceAffected(graph, changed, stories, { untraced: input.untraced });\n if (affected === null) {\n throw new Error(\"global-file-changed\");\n }\n return selectiveResult(changed, input.baseSha, affected);\n}\n\n/** Selective computation with reason-string failures mapped to full renders. */\nasync function computeSelective(input: AffectedInput): Promise<AffectedResult> {\n let changed: string[] = [];\n try {\n changed = guardedChanged(input);\n return await traceSelective(input, changed);\n } catch (error) {\n const reason = error instanceof Error ? error.message : \"affected-computation-failed\";\n return fullResult(changed, input.baseSha, reason);\n }\n}\n\nexport type { AffectedInput, AffectedResult, DepGraph, StoryLike, TraceOptions } from \"./types.ts\";\nexport {\n changedFiles,\n gitBranchName,\n gitHeadSha,\n gitRepoStatus,\n isShallowRepo,\n LOCAL_BRANCH,\n LOCAL_SHA_PREFIX,\n localSha,\n MAX_CHANGED_FILES,\n} from \"./git.ts\";\nexport type { GitRepoStatus } from \"./git.ts\";\nexport {\n buildGraph,\n graphModulesFor,\n loadDepGraph,\n loadStoryImportPaths,\n normalizePath,\n STATS_FILENAME,\n statsPathFor,\n} from \"./stats.ts\";\nexport { matchesAnyGlob, traceAffected } from \"./trace.ts\";\nexport { selectAffectedStories, type StoryPartition } from \"./select.ts\";\nexport { extractPackageUsage, type PackageUsage, type TrackedPackage } from \"./usage.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;AAYA,eAAsB,gBAAgB,OAA+C;CACnF,IAAI;EACF,OAAO,MAAM,iBAAiB,KAAK;CACrC,QAAQ;EACN,OAAO,WAAW,CAAC,GAAG,MAAM,SAAS,6BAA6B;CACpE;AACF;;AAGA,SAAS,WAAW,SAAmB,aAA4B,QAAgC;CACjG,OAAO;EAAE,qBAAqB;EAAM,cAAc;EAAS;EAAa,YAAY;CAAO;AAC7F;;AAGA,SAAS,gBAAgB,SAAmB,aAAqB,OAAiC;CAChG,OAAO;EAAE,qBAAqB;EAAO,cAAc;EAAS;EAAa,YAAY;CAAK;AAC5F;;AAGA,SAAS,eAAe,OAAgC;CACtD,MAAM,OAAO,cAAc,MAAM,GAAG;CACpC,IAAI,SAAS,MACX,MAAM,IAAI,MAAM,SAAS,YAAY,kBAAkB,gBAAgB;CAEzE,MAAM,UAAU,aAAa,MAAM,KAAK,MAAM,SAAS,MAAM,OAAO;CACpE,IAAI,QAAQ,SAAA,KACV,MAAM,IAAI,MAAM,wBAAwB;CAE1C,OAAO;AACT;;AAGA,eAAe,eAAe,OAAsB,SAA4C;CAC9F,IAAI,QAAQ,WAAW,GACrB,OAAO,gBAAgB,SAAS,MAAM,SAAS,CAAC,CAAC;CAEnD,MAAM,UAAU,MAAM,qBAAqB,MAAM,KAAK,MAAM,QAAQ,CAAC,CAAC,YAAY;EAChF,MAAM,IAAI,MAAM,wBAAwB;CAC1C,CAAC;CACD,MAAM,QAAQ,MAAM,aAAa,aAAa,MAAM,KAAK,MAAM,UAAU,MAAM,SAAS,CAAC;CACzF,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,8BAA8B;CAEhD,MAAM,WAAW,cAAc,OAAO,SAAS,SAAS,EAAE,UAAU,MAAM,SAAS,CAAC;CACpF,IAAI,aAAa,MACf,MAAM,IAAI,MAAM,qBAAqB;CAEvC,OAAO,gBAAgB,SAAS,MAAM,SAAS,QAAQ;AACzD;;AAGA,eAAe,iBAAiB,OAA+C;CAC7E,IAAI,UAAoB,CAAC;CACzB,IAAI;EACF,UAAU,eAAe,KAAK;EAC9B,OAAO,MAAM,eAAe,OAAO,OAAO;CAC5C,SAAS,OAAO;EACd,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU;EACxD,OAAO,WAAW,SAAS,MAAM,SAAS,MAAM;CAClD;AACF"}
package/dist/select.d.mts CHANGED
@@ -1,7 +1,6 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
3
  import { StoryLike } from "./types.mjs";
4
- //#region src/select.d.ts
5
4
  /** Stories split into render-now versus inherit-from-baseline sets. */
6
5
  interface StoryPartition<T> {
7
6
  /** Stories to capture in this build. */
@@ -19,6 +18,5 @@ interface StoryPartition<T> {
19
18
  * @param affectedImportPaths - Affected import paths, or `null` for full.
20
19
  */
21
20
  declare function selectAffectedStories<T extends StoryLike>(stories: readonly T[], affectedImportPaths: readonly string[] | null): StoryPartition<T>;
22
- //#endregion
23
21
  export { StoryPartition, selectAffectedStories };
24
22
  //# sourceMappingURL=select.d.mts.map
package/dist/select.mjs CHANGED
@@ -2,7 +2,7 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { normalizePath } from "./stats.mjs";
5
- //#region src/select.ts
5
+ // src/select.ts
6
6
  /** Sort stories into render versus inherit buckets for an affected set. */
7
7
  function partitionByPath(stories, affected, render, inherit) {
8
8
  for (const story of stories) {
@@ -38,7 +38,6 @@ function selectAffectedStories(stories, affectedImportPaths) {
38
38
  inherit
39
39
  };
40
40
  }
41
- //#endregion
42
41
  export { selectAffectedStories };
43
42
 
44
43
  //# sourceMappingURL=select.mjs.map
package/dist/stats.d.mts CHANGED
@@ -1,7 +1,6 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
3
  import { DepGraph } from "./types.mjs";
4
- //#region src/stats.d.ts
5
4
  /** Basename of the Vite stats file emitted next to a built Storybook. */
6
5
  declare const STATS_FILENAME = "preview-stats.json";
7
6
  /** A single module entry in a stats file (Vite or Webpack shape). */
@@ -42,6 +41,5 @@ declare function statsPathFor(cwd: string, buildDir: string, override?: string):
42
41
  declare function loadStoryImportPaths(cwd: string, buildDir: string): Promise<string[]>;
43
42
  /** Find graph modules referring to repo-relative file `target`. */
44
43
  declare function graphModulesFor(graph: DepGraph, target: string): string[];
45
- //#endregion
46
44
  export { STATS_FILENAME, buildGraph, graphModulesFor, loadDepGraph, loadStoryImportPaths, normalizePath, statsPathFor };
47
45
  //# sourceMappingURL=stats.d.mts.map
package/dist/stats.mjs CHANGED
@@ -1,16 +1,26 @@
1
- import "node:path";
1
+ import { join, resolve } from "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { readFile } from "node:fs/promises";
5
- import { join, resolve } from "node:path";
6
- //#region src/stats.ts
5
+ // src/stats.ts
7
6
  /** Basename of the Vite stats file emitted next to a built Storybook. */
8
7
  const STATS_FILENAME = "preview-stats.json";
9
8
  /** Candidate index files listing a built Storybook's stories. */
10
9
  const INDEX_CANDIDATES = ["stories.json", "index.json"];
10
+ /**
11
+ * Strip bundler module-id decoration so sub-modules fold into their file:
12
+ * webpack loader chains (`a-loader!b-loader??opts!./Button.vue`) keep only
13
+ * the resource after the last `!`, and `?query` suffixes
14
+ * (`Button.vue?vue&type=script`, `Card.svelte?svelte&type=style`) are dropped.
15
+ */
16
+ function stripQueryAndLoader(value) {
17
+ const resource = value.slice(value.lastIndexOf("!") + 1);
18
+ const queryStart = resource.indexOf("?");
19
+ return queryStart === -1 ? resource : resource.slice(0, queryStart);
20
+ }
11
21
  /** Normalize a stats path to a repo-relative posix form for comparison. */
12
22
  function normalizePath(value) {
13
- return value.replaceAll("\\", "/").replaceAll(/^\.\//gu, "").replaceAll(/^\//gu, "");
23
+ return stripQueryAndLoader(value).replaceAll("\\", "/").replaceAll(/^\.\//gu, "").replaceAll(/^\//gu, "");
14
24
  }
15
25
  /** True when stats module `candidate` refers to repo-relative file `target`. */
16
26
  function matchesFile(candidate, target) {
@@ -155,7 +165,6 @@ function graphModulesFor(graph, target) {
155
165
  for (const file of Object.keys(graph.importedBy)) if (matchesFile(file, target) && !matched.includes(file)) matched.push(file);
156
166
  return matched;
157
167
  }
158
- //#endregion
159
- export { STATS_FILENAME, buildGraph, graphModulesFor, loadDepGraph, loadStoryImportPaths, normalizePath, statsPathFor };
168
+ export { STATS_FILENAME, buildGraph, graphModulesFor, loadDepGraph, loadStoryImportPaths, normalizePath, statsPathFor, stripQueryAndLoader };
160
169
 
161
170
  //# sourceMappingURL=stats.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"stats.mjs","names":["record"],"sources":["../src/stats.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport type { DepGraph } from \"./types.ts\";\n\n/** Basename of the Vite stats file emitted next to a built Storybook. */\nexport const STATS_FILENAME = \"preview-stats.json\";\n\n/** Candidate index files listing a built Storybook's stories. */\nconst INDEX_CANDIDATES = [\"stories.json\", \"index.json\"];\n\n/** A single module entry in a stats file (Vite or Webpack shape). */\ninterface StatsModule {\n id?: unknown;\n name?: unknown;\n identifier?: unknown;\n imported?: unknown;\n importedIds?: unknown;\n dependencies?: unknown;\n reasons?: unknown;\n}\n\ninterface StoryIndex {\n entries?: Record<string, { importPath?: unknown }>;\n}\n\n/** Normalize a stats path to a repo-relative posix form for comparison. */\nexport function normalizePath(value: string): string {\n return value.replaceAll(\"\\\\\", \"/\").replaceAll(/^\\.\\//gu, \"\").replaceAll(/^\\//gu, \"\");\n}\n\n/** True when stats module `candidate` refers to repo-relative file `target`. */\nfunction matchesFile(candidate: string, target: string): boolean {\n if (candidate === target) {\n return true;\n }\n return candidate.endsWith(`/${target}`);\n}\n\n/** Coerce an unknown stats value to a list of normalized paths. */\nfunction asPathList(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n const paths: string[] = [];\n for (const entry of value) {\n if (typeof entry === \"string\" && entry.length > 0) {\n paths.push(normalizePath(entry));\n }\n }\n return paths;\n}\n\n/** Read a non-empty string field from a stats module entry. */\nfunction stringField(entry: StatsModule, key: \"id\" | \"name\" | \"identifier\"): string | null {\n const value = entry[key];\n return typeof value === \"string\" && value.length > 0 ? value : null;\n}\n\n/** Read the file a stats module entry refers to, if any. */\nfunction moduleFile(entry: StatsModule): string | null {\n const raw =\n stringField(entry, \"id\") ?? stringField(entry, \"name\") ?? stringField(entry, \"identifier\");\n return raw === null ? null : normalizePath(raw);\n}\n\n/** Forward imports declared by a stats module entry, if any. */\nfunction moduleImports(entry: StatsModule): string[] {\n return asPathList(entry.imported ?? entry.importedIds ?? entry.dependencies);\n}\n\n/** Dependent names declared via Webpack-style `reasons`, if any. */\nfunction moduleReasonNames(entry: StatsModule): string[] {\n if (!Array.isArray(entry.reasons)) {\n return [];\n }\n const names: string[] = [];\n for (const reason of entry.reasons) {\n if (typeof reason === \"object\" && reason !== null) {\n const record = reason as Record<string, unknown>;\n const name = record[\"moduleName\"] ?? record[\"module\"];\n if (typeof name === \"string\" && name.length > 0) {\n names.push(normalizePath(name));\n }\n }\n }\n return names;\n}\n\n/** Attach a record-shaped module entry to its name. */\nfunction entryWithName(name: string, value: unknown): StatsModule {\n if (typeof value === \"object\" && value !== null) {\n return { name, ...(value as StatsModule) };\n }\n return { name };\n}\n\n/** Extract module entries from a parsed stats document of unknown shape. */\nfunction moduleEntries(document: unknown): StatsModule[] {\n if (typeof document !== \"object\" || document === null) {\n return [];\n }\n const record = document as Record<string, unknown>;\n const raw = record[\"modules\"];\n if (Array.isArray(raw)) {\n return raw.filter((entry): entry is StatsModule => typeof entry === \"object\");\n }\n if (typeof raw === \"object\" && raw !== null) {\n return Object.entries(raw as Record<string, unknown>).map(([name, value]) =>\n entryWithName(name, value),\n );\n }\n return [];\n}\n\n/** Record one directed edge in the graph under construction. */\nfunction addEdge(\n imports: Map<string, Set<string>>,\n importedBy: Map<string, Set<string>>,\n from: string,\n to: string,\n): void {\n if (from === to) {\n return;\n }\n const forward = imports.get(from) ?? new Set<string>();\n forward.add(to);\n imports.set(from, forward);\n const reverse = importedBy.get(to) ?? new Set<string>();\n reverse.add(from);\n importedBy.set(to, reverse);\n}\n\n/** Add one stats entry's edges to the graph under construction. */\nfunction addEntryEdges(\n imports: Map<string, Set<string>>,\n importedBy: Map<string, Set<string>>,\n entry: StatsModule,\n): void {\n const file = moduleFile(entry);\n if (!file) {\n return;\n }\n for (const imported of moduleImports(entry)) {\n addEdge(imports, importedBy, file, imported);\n }\n for (const dependent of moduleReasonNames(entry)) {\n addEdge(imports, importedBy, dependent, file);\n }\n}\nfunction toGraph(\n imports: Map<string, Set<string>>,\n importedBy: Map<string, Set<string>>,\n): DepGraph {\n return {\n imports: Object.fromEntries([...imports].map(([file, set]) => [file, [...set].toSorted()])),\n importedBy: Object.fromEntries(\n [...importedBy].map(([file, set]) => [file, [...set].toSorted()]),\n ),\n };\n}\n\n/**\n * Build a bidirectional dependency graph from parsed stats modules.\n *\n * @param entries - Module entries from a Vite or Webpack stats document.\n * @returns Bidirectional graph, or `null` when no usable edges exist.\n */\nexport function buildGraph(entries: StatsModule[]): DepGraph | null {\n const imports = new Map<string, Set<string>>();\n const importedBy = new Map<string, Set<string>>();\n for (const entry of entries) {\n addEntryEdges(imports, importedBy, entry);\n }\n if (imports.size === 0) {\n return null;\n }\n return toGraph(imports, importedBy);\n}\n\n/**\n * Load and parse a stats file into a dependency graph.\n *\n * @param statsPath - Absolute or cwd-relative path to the stats JSON file.\n * @returns Graph, or `null` when the file is missing or unrecognized.\n */\nexport async function loadDepGraph(statsPath: string): Promise<DepGraph | null> {\n let raw: string;\n try {\n raw = await readFile(statsPath, \"utf8\");\n } catch {\n return null;\n }\n let document: unknown;\n try {\n document = JSON.parse(raw) as unknown;\n } catch {\n return null;\n }\n return buildGraph(moduleEntries(document));\n}\n\n/** Resolve the stats file path for a built Storybook directory. */\nexport function statsPathFor(cwd: string, buildDir: string, override?: string): string {\n if (override) {\n return resolve(cwd, override);\n }\n return join(resolve(cwd, buildDir), STATS_FILENAME);\n}\n\n/**\n * Load the deduplicated story import paths from a built Storybook's index.\n *\n * @param cwd - Repository root.\n * @param buildDir - Built Storybook directory.\n * @returns Normalized import paths; throws when no index is readable.\n */\nexport async function loadStoryImportPaths(cwd: string, buildDir: string): Promise<string[]> {\n const root = resolve(cwd, buildDir);\n const documents = await Promise.all(\n INDEX_CANDIDATES.map(async (name) => {\n try {\n return JSON.parse(await readFile(join(root, name), \"utf8\")) as StoryIndex;\n } catch {\n return null;\n }\n }),\n );\n const found = documents.find((document) => document !== null);\n if (!found?.entries) {\n throw new Error(`No Storybook index found in ${root}`);\n }\n const paths = new Set<string>();\n for (const entry of Object.values(found.entries)) {\n if (typeof entry.importPath === \"string\" && entry.importPath.length > 0) {\n paths.add(normalizePath(entry.importPath));\n }\n }\n return [...paths].toSorted();\n}\n\n/** Find graph modules referring to repo-relative file `target`. */\nexport function graphModulesFor(graph: DepGraph, target: string): string[] {\n const matched: string[] = [];\n for (const file of Object.keys(graph.imports)) {\n if (matchesFile(file, target)) {\n matched.push(file);\n }\n }\n for (const file of Object.keys(graph.importedBy)) {\n if (matchesFile(file, target) && !matched.includes(file)) {\n matched.push(file);\n }\n }\n return matched;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"stats.mjs","names":["record"],"sources":["../src/stats.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport type { DepGraph } from \"./types.ts\";\n\n/** Basename of the Vite stats file emitted next to a built Storybook. */\nexport const STATS_FILENAME = \"preview-stats.json\";\n\n/** Candidate index files listing a built Storybook's stories. */\nconst INDEX_CANDIDATES = [\"stories.json\", \"index.json\"];\n\n/** A single module entry in a stats file (Vite or Webpack shape). */\ninterface StatsModule {\n id?: unknown;\n name?: unknown;\n identifier?: unknown;\n imported?: unknown;\n importedIds?: unknown;\n dependencies?: unknown;\n reasons?: unknown;\n}\n\ninterface StoryIndex {\n entries?: Record<string, { importPath?: unknown }>;\n}\n\n/**\n * Strip bundler module-id decoration so sub-modules fold into their file:\n * webpack loader chains (`a-loader!b-loader??opts!./Button.vue`) keep only\n * the resource after the last `!`, and `?query` suffixes\n * (`Button.vue?vue&type=script`, `Card.svelte?svelte&type=style`) are dropped.\n */\nexport function stripQueryAndLoader(value: string): string {\n const resource = value.slice(value.lastIndexOf(\"!\") + 1);\n const queryStart = resource.indexOf(\"?\");\n return queryStart === -1 ? resource : resource.slice(0, queryStart);\n}\n\n/** Normalize a stats path to a repo-relative posix form for comparison. */\nexport function normalizePath(value: string): string {\n return stripQueryAndLoader(value)\n .replaceAll(\"\\\\\", \"/\")\n .replaceAll(/^\\.\\//gu, \"\")\n .replaceAll(/^\\//gu, \"\");\n}\n\n/** True when stats module `candidate` refers to repo-relative file `target`. */\nfunction matchesFile(candidate: string, target: string): boolean {\n if (candidate === target) {\n return true;\n }\n return candidate.endsWith(`/${target}`);\n}\n\n/** Coerce an unknown stats value to a list of normalized paths. */\nfunction asPathList(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n const paths: string[] = [];\n for (const entry of value) {\n if (typeof entry === \"string\" && entry.length > 0) {\n paths.push(normalizePath(entry));\n }\n }\n return paths;\n}\n\n/** Read a non-empty string field from a stats module entry. */\nfunction stringField(entry: StatsModule, key: \"id\" | \"name\" | \"identifier\"): string | null {\n const value = entry[key];\n return typeof value === \"string\" && value.length > 0 ? value : null;\n}\n\n/** Read the file a stats module entry refers to, if any. */\nfunction moduleFile(entry: StatsModule): string | null {\n const raw =\n stringField(entry, \"id\") ?? stringField(entry, \"name\") ?? stringField(entry, \"identifier\");\n return raw === null ? null : normalizePath(raw);\n}\n\n/** Forward imports declared by a stats module entry, if any. */\nfunction moduleImports(entry: StatsModule): string[] {\n return asPathList(entry.imported ?? entry.importedIds ?? entry.dependencies);\n}\n\n/** Dependent names declared via Webpack-style `reasons`, if any. */\nfunction moduleReasonNames(entry: StatsModule): string[] {\n if (!Array.isArray(entry.reasons)) {\n return [];\n }\n const names: string[] = [];\n for (const reason of entry.reasons) {\n if (typeof reason === \"object\" && reason !== null) {\n const record = reason as Record<string, unknown>;\n const name = record[\"moduleName\"] ?? record[\"module\"];\n if (typeof name === \"string\" && name.length > 0) {\n names.push(normalizePath(name));\n }\n }\n }\n return names;\n}\n\n/** Attach a record-shaped module entry to its name. */\nfunction entryWithName(name: string, value: unknown): StatsModule {\n if (typeof value === \"object\" && value !== null) {\n return { name, ...(value as StatsModule) };\n }\n return { name };\n}\n\n/** Extract module entries from a parsed stats document of unknown shape. */\nfunction moduleEntries(document: unknown): StatsModule[] {\n if (typeof document !== \"object\" || document === null) {\n return [];\n }\n const record = document as Record<string, unknown>;\n const raw = record[\"modules\"];\n if (Array.isArray(raw)) {\n return raw.filter((entry): entry is StatsModule => typeof entry === \"object\");\n }\n if (typeof raw === \"object\" && raw !== null) {\n return Object.entries(raw as Record<string, unknown>).map(([name, value]) =>\n entryWithName(name, value),\n );\n }\n return [];\n}\n\n/** Record one directed edge in the graph under construction. */\nfunction addEdge(\n imports: Map<string, Set<string>>,\n importedBy: Map<string, Set<string>>,\n from: string,\n to: string,\n): void {\n if (from === to) {\n return;\n }\n const forward = imports.get(from) ?? new Set<string>();\n forward.add(to);\n imports.set(from, forward);\n const reverse = importedBy.get(to) ?? new Set<string>();\n reverse.add(from);\n importedBy.set(to, reverse);\n}\n\n/** Add one stats entry's edges to the graph under construction. */\nfunction addEntryEdges(\n imports: Map<string, Set<string>>,\n importedBy: Map<string, Set<string>>,\n entry: StatsModule,\n): void {\n const file = moduleFile(entry);\n if (!file) {\n return;\n }\n for (const imported of moduleImports(entry)) {\n addEdge(imports, importedBy, file, imported);\n }\n for (const dependent of moduleReasonNames(entry)) {\n addEdge(imports, importedBy, dependent, file);\n }\n}\nfunction toGraph(\n imports: Map<string, Set<string>>,\n importedBy: Map<string, Set<string>>,\n): DepGraph {\n return {\n imports: Object.fromEntries([...imports].map(([file, set]) => [file, [...set].toSorted()])),\n importedBy: Object.fromEntries(\n [...importedBy].map(([file, set]) => [file, [...set].toSorted()]),\n ),\n };\n}\n\n/**\n * Build a bidirectional dependency graph from parsed stats modules.\n *\n * @param entries - Module entries from a Vite or Webpack stats document.\n * @returns Bidirectional graph, or `null` when no usable edges exist.\n */\nexport function buildGraph(entries: StatsModule[]): DepGraph | null {\n const imports = new Map<string, Set<string>>();\n const importedBy = new Map<string, Set<string>>();\n for (const entry of entries) {\n addEntryEdges(imports, importedBy, entry);\n }\n if (imports.size === 0) {\n return null;\n }\n return toGraph(imports, importedBy);\n}\n\n/**\n * Load and parse a stats file into a dependency graph.\n *\n * @param statsPath - Absolute or cwd-relative path to the stats JSON file.\n * @returns Graph, or `null` when the file is missing or unrecognized.\n */\nexport async function loadDepGraph(statsPath: string): Promise<DepGraph | null> {\n let raw: string;\n try {\n raw = await readFile(statsPath, \"utf8\");\n } catch {\n return null;\n }\n let document: unknown;\n try {\n document = JSON.parse(raw) as unknown;\n } catch {\n return null;\n }\n return buildGraph(moduleEntries(document));\n}\n\n/** Resolve the stats file path for a built Storybook directory. */\nexport function statsPathFor(cwd: string, buildDir: string, override?: string): string {\n if (override) {\n return resolve(cwd, override);\n }\n return join(resolve(cwd, buildDir), STATS_FILENAME);\n}\n\n/**\n * Load the deduplicated story import paths from a built Storybook's index.\n *\n * @param cwd - Repository root.\n * @param buildDir - Built Storybook directory.\n * @returns Normalized import paths; throws when no index is readable.\n */\nexport async function loadStoryImportPaths(cwd: string, buildDir: string): Promise<string[]> {\n const root = resolve(cwd, buildDir);\n const documents = await Promise.all(\n INDEX_CANDIDATES.map(async (name) => {\n try {\n return JSON.parse(await readFile(join(root, name), \"utf8\")) as StoryIndex;\n } catch {\n return null;\n }\n }),\n );\n const found = documents.find((document) => document !== null);\n if (!found?.entries) {\n throw new Error(`No Storybook index found in ${root}`);\n }\n const paths = new Set<string>();\n for (const entry of Object.values(found.entries)) {\n if (typeof entry.importPath === \"string\" && entry.importPath.length > 0) {\n paths.add(normalizePath(entry.importPath));\n }\n }\n return [...paths].toSorted();\n}\n\n/** Find graph modules referring to repo-relative file `target`. */\nexport function graphModulesFor(graph: DepGraph, target: string): string[] {\n const matched: string[] = [];\n for (const file of Object.keys(graph.imports)) {\n if (matchesFile(file, target)) {\n matched.push(file);\n }\n }\n for (const file of Object.keys(graph.importedBy)) {\n if (matchesFile(file, target) && !matched.includes(file)) {\n matched.push(file);\n }\n }\n return matched;\n}\n"],"mappings":"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"}
package/dist/trace.d.mts CHANGED
@@ -1,7 +1,6 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
3
  import { DepGraph, TraceOptions } from "./types.mjs";
4
- //#region src/trace.d.ts
5
4
  /** True when `path` matches any of the given glob patterns. */
6
5
  declare function matchesAnyGlob(path: string, patterns: readonly string[]): boolean;
7
6
  /**
@@ -14,6 +13,5 @@ declare function matchesAnyGlob(path: string, patterns: readonly string[]): bool
14
13
  * @returns Affected story files, or `null` when everything must render.
15
14
  */
16
15
  declare function traceAffected(graph: DepGraph, changedFiles: string[], storyFiles: readonly string[], options?: TraceOptions): string[] | null;
17
- //#endregion
18
16
  export { matchesAnyGlob, traceAffected };
19
17
  //# sourceMappingURL=trace.d.mts.map
package/dist/trace.mjs CHANGED
@@ -2,7 +2,7 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { graphModulesFor } from "./stats.mjs";
5
- //#region src/trace.ts
5
+ // src/trace.ts
6
6
  /** Basenames whose change invalidates every story (global render setup). */
7
7
  const GLOBAL_BASENAMES = /* @__PURE__ */ new Set([
8
8
  "preview.js",
@@ -160,7 +160,6 @@ function traceAffected(graph, changedFiles, storyFiles, options = {}) {
160
160
  const storyOf = storyNodeMap(graph, storyFiles);
161
161
  return [...reachableStories(graph, traceRoots(graph, relevant, storyOf), storyOf)].toSorted();
162
162
  }
163
- //#endregion
164
163
  export { matchesAnyGlob, traceAffected };
165
164
 
166
165
  //# sourceMappingURL=trace.mjs.map
package/dist/types.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
- //#region src/types.d.ts
4
3
  /**
5
4
  * Shared types for affected capture: dependency graphs, trace inputs, and
6
5
  * the affected-set result. Zero runtime dependencies by design — both the
@@ -52,6 +51,5 @@ interface AffectedResult {
52
51
  interface StoryLike {
53
52
  importPath?: string | null;
54
53
  }
55
- //#endregion
56
54
  export { AffectedInput, AffectedResult, DepGraph, StoryLike, TraceOptions };
57
55
  //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1,29 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { DepGraph } from "./types.mjs";
4
+ /** A dependency package a story file reaches, and the first module that reaches it. */
5
+ interface PackageUsage {
6
+ /** Story file (repo-relative, as given by the Storybook index). */
7
+ storyFile: string;
8
+ packageName: string;
9
+ /** Graph module inside the package (a `node_modules/<pkg>/…` path or a workspace source file). */
10
+ modulePath: string;
11
+ }
12
+ /** Where a tracked package lives: always `node_modules`, plus a workspace dir when linked. */
13
+ interface TrackedPackage {
14
+ /** Repo-relative directory of a workspace-linked package (outside `node_modules`). */
15
+ root?: string;
16
+ }
17
+ /**
18
+ * Find which tracked dependency packages each story reaches through its
19
+ * import graph. Traversal stops at a package boundary (its internals are not
20
+ * walked) and records only the shallowest module per package, so a story
21
+ * yields at most one row per package.
22
+ *
23
+ * @param graph - Bidirectional module graph from the build's stats file.
24
+ * @param storyFiles - Story import paths from the Storybook index.
25
+ * @param packages - Packages to track (e.g. the project's declared dependencies).
26
+ */
27
+ declare function extractPackageUsage(graph: DepGraph, storyFiles: readonly string[], packages: Record<string, TrackedPackage>): PackageUsage[];
28
+ export { PackageUsage, TrackedPackage, extractPackageUsage };
29
+ //# sourceMappingURL=usage.d.mts.map
package/dist/usage.mjs ADDED
@@ -0,0 +1,65 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { graphModulesFor } from "./stats.mjs";
5
+ // src/usage.ts
6
+ const NODE_MODULES = "node_modules/";
7
+ /** Package name for a `…/node_modules/<pkg>/…` module path, handling scopes. */
8
+ function nodeModulesPackage(module) {
9
+ const start = module.lastIndexOf(NODE_MODULES);
10
+ if (start === -1) return null;
11
+ const [first, second] = module.slice(start + 13).split("/");
12
+ if (!first) return null;
13
+ return first.startsWith("@") && second ? `${first}/${second}` : first;
14
+ }
15
+ /** Tracked package that owns `module`, via `node_modules` or a workspace root. */
16
+ function owningPackage(module, tracked) {
17
+ const installed = nodeModulesPackage(module);
18
+ if (installed !== null) return tracked.has(installed) ? installed : null;
19
+ for (const [name, { root }] of tracked) if (root && module.startsWith(`${root}/`)) return name;
20
+ return null;
21
+ }
22
+ /** Classify one imported module: record a package hit, or queue it for expansion. */
23
+ function visitDep(walk, dep, storyFile, tracked) {
24
+ if (walk.seen.has(dep)) return;
25
+ walk.seen.add(dep);
26
+ const owner = owningPackage(dep, tracked);
27
+ if (owner === null) walk.next.push(dep);
28
+ else if (!walk.found.has(owner)) walk.found.set(owner, {
29
+ storyFile,
30
+ packageName: owner,
31
+ modulePath: dep
32
+ });
33
+ }
34
+ /** Breadth-first walk of one story's imports, recording the shallowest hit per package. */
35
+ function usageForStory(graph, storyFile, start, tracked) {
36
+ const walk = {
37
+ seen: new Set(start),
38
+ found: /* @__PURE__ */ new Map(),
39
+ next: []
40
+ };
41
+ let frontier = start;
42
+ while (frontier.length > 0) {
43
+ walk.next = [];
44
+ for (const dep of frontier.flatMap((module) => graph.imports[module] ?? [])) visitDep(walk, dep, storyFile, tracked);
45
+ frontier = walk.next;
46
+ }
47
+ return [...walk.found.values()];
48
+ }
49
+ /**
50
+ * Find which tracked dependency packages each story reaches through its
51
+ * import graph. Traversal stops at a package boundary (its internals are not
52
+ * walked) and records only the shallowest module per package, so a story
53
+ * yields at most one row per package.
54
+ *
55
+ * @param graph - Bidirectional module graph from the build's stats file.
56
+ * @param storyFiles - Story import paths from the Storybook index.
57
+ * @param packages - Packages to track (e.g. the project's declared dependencies).
58
+ */
59
+ function extractPackageUsage(graph, storyFiles, packages) {
60
+ const tracked = new Map(Object.entries(packages));
61
+ return storyFiles.flatMap((storyFile) => usageForStory(graph, storyFile, graphModulesFor(graph, storyFile), tracked));
62
+ }
63
+ export { extractPackageUsage };
64
+
65
+ //# sourceMappingURL=usage.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.mjs","names":[],"sources":["../src/usage.ts"],"sourcesContent":["import { graphModulesFor } from \"./stats.ts\";\nimport type { DepGraph } from \"./types.ts\";\n\n/** A dependency package a story file reaches, and the first module that reaches it. */\nexport interface PackageUsage {\n /** Story file (repo-relative, as given by the Storybook index). */\n storyFile: string;\n packageName: string;\n /** Graph module inside the package (a `node_modules/<pkg>/…` path or a workspace source file). */\n modulePath: string;\n}\n\n/** Where a tracked package lives: always `node_modules`, plus a workspace dir when linked. */\nexport interface TrackedPackage {\n /** Repo-relative directory of a workspace-linked package (outside `node_modules`). */\n root?: string;\n}\n\nconst NODE_MODULES = \"node_modules/\";\n\n/** Package name for a `…/node_modules/<pkg>/…` module path, handling scopes. */\nfunction nodeModulesPackage(module: string): string | null {\n const start = module.lastIndexOf(NODE_MODULES);\n if (start === -1) {\n return null;\n }\n const [first, second] = module.slice(start + NODE_MODULES.length).split(\"/\");\n if (!first) {\n return null;\n }\n return first.startsWith(\"@\") && second ? `${first}/${second}` : first;\n}\n\n/** Tracked package that owns `module`, via `node_modules` or a workspace root. */\nfunction owningPackage(module: string, tracked: Map<string, TrackedPackage>): string | null {\n const installed = nodeModulesPackage(module);\n if (installed !== null) {\n return tracked.has(installed) ? installed : null;\n }\n for (const [name, { root }] of tracked) {\n if (root && module.startsWith(`${root}/`)) {\n return name;\n }\n }\n return null;\n}\n\n/** Mutable state of one story's breadth-first walk. */\ninterface Walk {\n seen: Set<string>;\n found: Map<string, PackageUsage>;\n next: string[];\n}\n\n/** Classify one imported module: record a package hit, or queue it for expansion. */\nfunction visitDep(\n walk: Walk,\n dep: string,\n storyFile: string,\n tracked: Map<string, TrackedPackage>,\n): void {\n if (walk.seen.has(dep)) {\n return;\n }\n walk.seen.add(dep);\n const owner = owningPackage(dep, tracked);\n if (owner === null) {\n walk.next.push(dep);\n } else if (!walk.found.has(owner)) {\n walk.found.set(owner, { storyFile, packageName: owner, modulePath: dep });\n }\n}\n\n/** Breadth-first walk of one story's imports, recording the shallowest hit per package. */\nfunction usageForStory(\n graph: DepGraph,\n storyFile: string,\n start: string[],\n tracked: Map<string, TrackedPackage>,\n): PackageUsage[] {\n const walk: Walk = { seen: new Set(start), found: new Map(), next: [] };\n let frontier = start;\n while (frontier.length > 0) {\n walk.next = [];\n for (const dep of frontier.flatMap((module) => graph.imports[module] ?? [])) {\n visitDep(walk, dep, storyFile, tracked);\n }\n frontier = walk.next;\n }\n return [...walk.found.values()];\n}\n\n/**\n * Find which tracked dependency packages each story reaches through its\n * import graph. Traversal stops at a package boundary (its internals are not\n * walked) and records only the shallowest module per package, so a story\n * yields at most one row per package.\n *\n * @param graph - Bidirectional module graph from the build's stats file.\n * @param storyFiles - Story import paths from the Storybook index.\n * @param packages - Packages to track (e.g. the project's declared dependencies).\n */\nexport function extractPackageUsage(\n graph: DepGraph,\n storyFiles: readonly string[],\n packages: Record<string, TrackedPackage>,\n): PackageUsage[] {\n const tracked = new Map(Object.entries(packages));\n return storyFiles.flatMap((storyFile) =>\n usageForStory(graph, storyFile, graphModulesFor(graph, storyFile), tracked),\n );\n}\n"],"mappings":";;;;;AAkBA,MAAM,eAAe;;AAGrB,SAAS,mBAAmB,QAA+B;CACzD,MAAM,QAAQ,OAAO,YAAY,YAAY;CAC7C,IAAI,UAAU,IACZ,OAAO;CAET,MAAM,CAAC,OAAO,UAAU,OAAO,MAAM,QAAQ,EAAmB,CAAC,CAAC,MAAM,GAAG;CAC3E,IAAI,CAAC,OACH,OAAO;CAET,OAAO,MAAM,WAAW,GAAG,KAAK,SAAS,GAAG,MAAM,GAAG,WAAW;AAClE;;AAGA,SAAS,cAAc,QAAgB,SAAqD;CAC1F,MAAM,YAAY,mBAAmB,MAAM;CAC3C,IAAI,cAAc,MAChB,OAAO,QAAQ,IAAI,SAAS,IAAI,YAAY;CAE9C,KAAK,MAAM,CAAC,MAAM,EAAE,WAAW,SAC7B,IAAI,QAAQ,OAAO,WAAW,GAAG,KAAK,EAAE,GACtC,OAAO;CAGX,OAAO;AACT;;AAUA,SAAS,SACP,MACA,KACA,WACA,SACM;CACN,IAAI,KAAK,KAAK,IAAI,GAAG,GACnB;CAEF,KAAK,KAAK,IAAI,GAAG;CACjB,MAAM,QAAQ,cAAc,KAAK,OAAO;CACxC,IAAI,UAAU,MACZ,KAAK,KAAK,KAAK,GAAG;MACb,IAAI,CAAC,KAAK,MAAM,IAAI,KAAK,GAC9B,KAAK,MAAM,IAAI,OAAO;EAAE;EAAW,aAAa;EAAO,YAAY;CAAI,CAAC;AAE5E;;AAGA,SAAS,cACP,OACA,WACA,OACA,SACgB;CAChB,MAAM,OAAa;EAAE,MAAM,IAAI,IAAI,KAAK;EAAG,uBAAO,IAAI,IAAI;EAAG,MAAM,CAAC;CAAE;CACtE,IAAI,WAAW;CACf,OAAO,SAAS,SAAS,GAAG;EAC1B,KAAK,OAAO,CAAC;EACb,KAAK,MAAM,OAAO,SAAS,SAAS,WAAW,MAAM,QAAQ,WAAW,CAAC,CAAC,GACxE,SAAS,MAAM,KAAK,WAAW,OAAO;EAExC,WAAW,KAAK;CAClB;CACA,OAAO,CAAC,GAAG,KAAK,MAAM,OAAO,CAAC;AAChC;;;;;;;;;;;AAYA,SAAgB,oBACd,OACA,YACA,UACgB;CAChB,MAAM,UAAU,IAAI,IAAI,OAAO,QAAQ,QAAQ,CAAC;CAChD,OAAO,WAAW,SAAS,cACzB,cAAc,OAAO,WAAW,gBAAgB,OAAO,SAAS,GAAG,OAAO,CAC5E;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storyshelf/affected",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "description": "Affected capture for StoryShelf: dependency-graph tracing that selects the stories impacted by a change.",
5
5
  "keywords": [
6
6
  "affected",
@@ -27,9 +27,6 @@
27
27
  "dist"
28
28
  ],
29
29
  "type": "module",
30
- "engines": {
31
- "node": ">=24"
32
- },
33
30
  "sideEffects": false,
34
31
  "types": "./dist/index.d.mts",
35
32
  "exports": {
@@ -63,7 +60,10 @@
63
60
  "oxlint-tsgolint": "^7.0.2001",
64
61
  "tsdown": "^0.22.14",
65
62
  "typescript": "^7.0.2",
66
- "vitest": "^5.0.0"
63
+ "vitest": "^5.0.5"
64
+ },
65
+ "engines": {
66
+ "node": ">=24"
67
67
  },
68
68
  "jsr": {
69
69
  "runtimeCompat": {