@storyshelf/affected 0.7.0 → 0.9.0-next-d2cc082
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/dist/git.d.mts +0 -2
- package/dist/git.mjs +1 -2
- package/dist/index.d.mts +0 -2
- package/dist/index.mjs +1 -2
- package/dist/select.d.mts +0 -2
- package/dist/select.mjs +1 -2
- package/dist/stats.d.mts +0 -2
- package/dist/stats.mjs +2 -4
- package/dist/stats.mjs.map +1 -1
- package/dist/trace.d.mts +0 -2
- package/dist/trace.mjs +2 -4
- package/dist/trace.mjs.map +1 -1
- package/dist/types.d.mts +0 -2
- package/dist/usage.d.mts +0 -2
- package/dist/usage.mjs +1 -2
- package/package.json +2 -11
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
|
-
|
|
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
|
@@ -6,7 +6,6 @@ import { STATS_FILENAME, buildGraph, graphModulesFor, loadDepGraph, loadStoryImp
|
|
|
6
6
|
import { matchesAnyGlob, traceAffected } from "./trace.mjs";
|
|
7
7
|
import { StoryPartition, selectAffectedStories } from "./select.mjs";
|
|
8
8
|
import { PackageUsage, TrackedPackage, extractPackageUsage } from "./usage.mjs";
|
|
9
|
-
//#region src/index.d.ts
|
|
10
9
|
/**
|
|
11
10
|
* Compute the affected story set for an upload. Never throws: every failure
|
|
12
11
|
* degrades to a full render with a machine-readable `fullReason`, so
|
|
@@ -15,6 +14,5 @@ import { PackageUsage, TrackedPackage, extractPackageUsage } from "./usage.mjs";
|
|
|
15
14
|
* @param input - Repository root, build dir, revisions, and trace options.
|
|
16
15
|
*/
|
|
17
16
|
declare function computeAffected(input: AffectedInput): Promise<AffectedResult>;
|
|
18
|
-
//#endregion
|
|
19
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 };
|
|
20
18
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { STATS_FILENAME, buildGraph, graphModulesFor, loadDepGraph, loadStoryImp
|
|
|
6
6
|
import { matchesAnyGlob, traceAffected } from "./trace.mjs";
|
|
7
7
|
import { selectAffectedStories } from "./select.mjs";
|
|
8
8
|
import { extractPackageUsage } from "./usage.mjs";
|
|
9
|
-
|
|
9
|
+
// src/index.ts
|
|
10
10
|
/**
|
|
11
11
|
* Compute the affected story set for an upload. Never throws: every failure
|
|
12
12
|
* degrades to a full render with a machine-readable `fullReason`, so
|
|
@@ -70,7 +70,6 @@ async function computeSelective(input) {
|
|
|
70
70
|
return fullResult(changed, input.baseSha, reason);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
//#endregion
|
|
74
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 };
|
|
75
74
|
|
|
76
75
|
//# sourceMappingURL=index.mjs.map
|
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
|
-
|
|
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,9 +1,8 @@
|
|
|
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
|
-
|
|
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. */
|
|
@@ -166,7 +165,6 @@ function graphModulesFor(graph, target) {
|
|
|
166
165
|
for (const file of Object.keys(graph.importedBy)) if (matchesFile(file, target) && !matched.includes(file)) matched.push(file);
|
|
167
166
|
return matched;
|
|
168
167
|
}
|
|
169
|
-
//#endregion
|
|
170
168
|
export { STATS_FILENAME, buildGraph, graphModulesFor, loadDepGraph, loadStoryImportPaths, normalizePath, statsPathFor, stripQueryAndLoader };
|
|
171
169
|
|
|
172
170
|
//# sourceMappingURL=stats.mjs.map
|
package/dist/stats.mjs.map
CHANGED
|
@@ -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/**\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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"}
|
|
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
|
-
|
|
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",
|
|
@@ -33,8 +33,7 @@ const LOCKFILE_BASENAMES = /* @__PURE__ */ new Set([
|
|
|
33
33
|
"pnpm-lock.yaml",
|
|
34
34
|
"yarn.lock",
|
|
35
35
|
"bun.lock",
|
|
36
|
-
"bun.lockb"
|
|
37
|
-
"deno.lock"
|
|
36
|
+
"bun.lockb"
|
|
38
37
|
]);
|
|
39
38
|
/** Basename of a repo-relative path. */
|
|
40
39
|
function basename(path) {
|
|
@@ -160,7 +159,6 @@ function traceAffected(graph, changedFiles, storyFiles, options = {}) {
|
|
|
160
159
|
const storyOf = storyNodeMap(graph, storyFiles);
|
|
161
160
|
return [...reachableStories(graph, traceRoots(graph, relevant, storyOf), storyOf)].toSorted();
|
|
162
161
|
}
|
|
163
|
-
//#endregion
|
|
164
162
|
export { matchesAnyGlob, traceAffected };
|
|
165
163
|
|
|
166
164
|
//# sourceMappingURL=trace.mjs.map
|
package/dist/trace.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace.mjs","names":[],"sources":["../src/trace.ts"],"sourcesContent":["import { graphModulesFor } from \"./stats.ts\";\nimport type { DepGraph, TraceOptions } from \"./types.ts\";\n\n/** Basenames whose change invalidates every story (global render setup). */\nconst GLOBAL_BASENAMES = new Set([\n \"preview.js\",\n \"preview.jsx\",\n \"preview.ts\",\n \"preview.tsx\",\n \"preview.mjs\",\n \"preview.cjs\",\n \"preview.mts\",\n \"preview.cts\",\n \"manager.js\",\n \"manager.jsx\",\n \"manager.ts\",\n \"manager.tsx\",\n]);\n\n/** Config basenames whose change may add, remove, or re-glob stories. */\nconst CONFIG_BASENAMES = new Set([\"main.js\", \"main.ts\", \"main.mjs\", \"main.cjs\", \"main.mts\"]);\n\n/** Lockfiles whose change means dependency versions cannot be resolved. */\nconst LOCKFILE_BASENAMES = new Set([\n \"package.json\",\n \"package-lock.json\",\n \"pnpm-lock.yaml\",\n \"yarn.lock\",\n \"bun.lock\",\n \"bun.lockb\",\n
|
|
1
|
+
{"version":3,"file":"trace.mjs","names":[],"sources":["../src/trace.ts"],"sourcesContent":["import { graphModulesFor } from \"./stats.ts\";\nimport type { DepGraph, TraceOptions } from \"./types.ts\";\n\n/** Basenames whose change invalidates every story (global render setup). */\nconst GLOBAL_BASENAMES = new Set([\n \"preview.js\",\n \"preview.jsx\",\n \"preview.ts\",\n \"preview.tsx\",\n \"preview.mjs\",\n \"preview.cjs\",\n \"preview.mts\",\n \"preview.cts\",\n \"manager.js\",\n \"manager.jsx\",\n \"manager.ts\",\n \"manager.tsx\",\n]);\n\n/** Config basenames whose change may add, remove, or re-glob stories. */\nconst CONFIG_BASENAMES = new Set([\"main.js\", \"main.ts\", \"main.mjs\", \"main.cjs\", \"main.mts\"]);\n\n/** Lockfiles whose change means dependency versions cannot be resolved. */\nconst LOCKFILE_BASENAMES = new Set([\n \"package.json\",\n \"package-lock.json\",\n \"pnpm-lock.yaml\",\n \"yarn.lock\",\n \"bun.lock\",\n \"bun.lockb\",\n]);\n\n/** Basename of a repo-relative path. */\nfunction basename(path: string): string {\n const index = path.lastIndexOf(\"/\");\n return index === -1 ? path : path.slice(index + 1);\n}\n\n/** True when a changed file forces a full render of every story. */\nfunction isGlobalFile(path: string): boolean {\n const base = basename(path);\n if (GLOBAL_BASENAMES.has(base) || CONFIG_BASENAMES.has(base) || LOCKFILE_BASENAMES.has(base)) {\n return true;\n }\n return path.includes(\".storybook/\") && (CONFIG_BASENAMES.has(base) || GLOBAL_BASENAMES.has(base));\n}\n\n/** Regex-special chars escaped when translating globs (kept discrete: no `${`). */\nconst REGEX_SPECIALS = new Set([\"+\", \"^\", \"$\", \"{\", \"}\", \"(\", \")\", \"|\", \"[\", \"]\", \"\\\\\", \".\"]);\n\n/** Translate one glob token at `index` into regex text and advance width. */\nfunction translateGlobToken(pattern: string, index: number): { text: string; advance: number } {\n const char = pattern[index];\n if (char === \"*\") {\n return pattern[index + 1] === \"*\" ? { text: \".*\", advance: 2 } : { text: \"[^/]*\", advance: 1 };\n }\n if (char === \"?\") {\n return { text: \"[^/]\", advance: 1 };\n }\n if (char !== undefined && REGEX_SPECIALS.has(char)) {\n return { text: `\\\\${char}`, advance: 1 };\n }\n return { text: char ?? \"\", advance: 1 };\n}\n\n/** Escape regex syntax while translating `*`, `**`, and `?` globs. */\nfunction globToRegExp(pattern: string): RegExp {\n const parts: string[] = [];\n let index = 0;\n while (index < pattern.length) {\n const token = translateGlobToken(pattern, index);\n parts.push(token.text);\n index += token.advance;\n }\n return new RegExp(`^${parts.join(\"\")}$`, \"u\");\n}\n\n/** True when `path` matches any of the given glob patterns. */\nexport function matchesAnyGlob(path: string, patterns: readonly string[]): boolean {\n return patterns.some((pattern) => globToRegExp(pattern).test(path));\n}\n\n/** Queue reverse-edge dependents not yet visited. */\nfunction pushUnseen(seen: Set<string>, pending: string[], dependents: readonly string[]): void {\n for (const dependent of dependents) {\n if (!seen.has(dependent)) {\n seen.add(dependent);\n pending.push(dependent);\n }\n }\n}\n\n/** Fold one graph node into the affected set and queue its dependents. */\nfunction processNode(\n graph: DepGraph,\n storyOf: ReadonlyMap<string, string>,\n seen: Set<string>,\n pending: string[],\n affected: Set<string>,\n current: string,\n): void {\n const story = storyOf.get(current);\n if (story !== undefined) {\n affected.add(story);\n }\n pushUnseen(seen, pending, graph.importedBy[current] ?? []);\n}\n\n/** Collect every story file reachable from `roots` via reverse edges. */\nfunction reachableStories(\n graph: DepGraph,\n roots: string[],\n storyOf: ReadonlyMap<string, string>,\n): Set<string> {\n const seen = new Set<string>(roots);\n const pending = [...roots];\n const affected = new Set<string>();\n while (pending.length > 0) {\n const current = pending.pop();\n if (current === undefined) {\n break;\n }\n processNode(graph, storyOf, seen, pending, affected, current);\n }\n return affected;\n}\n\n/** Map every graph node to its story file (suffix match for absolute stats). */\nfunction storyNodeMap(graph: DepGraph, storyFiles: readonly string[]): Map<string, string> {\n const nodes = new Set([...Object.keys(graph.imports), ...Object.keys(graph.importedBy)]);\n const map = new Map<string, string>();\n for (const story of storyFiles) {\n for (const node of nodes) {\n if ((node === story || node.endsWith(`/${story}`)) && !map.has(node)) {\n map.set(node, story);\n }\n }\n }\n return map;\n}\n/** Root modules for tracing: changed modules plus directly changed stories. */\nfunction traceRoots(\n graph: DepGraph,\n relevant: string[],\n storyOf: ReadonlyMap<string, string>,\n): string[] {\n const roots = changedModules(graph, relevant);\n for (const file of relevant) {\n if (storyOf.has(file)) {\n roots.push(file);\n }\n }\n return [...new Set(roots)];\n}\n/** Map changed files to graph modules, dropping files outside the graph. */\nfunction changedModules(graph: DepGraph, changed: string[]): string[] {\n const modules = new Set<string>();\n for (const file of changed) {\n for (const module of graphModulesFor(graph, file)) {\n modules.add(module);\n }\n }\n return [...modules];\n}\n\n/**\n * Trace changed files through the dependency graph to story files.\n *\n * @param graph - Bidirectional module graph of the built Storybook.\n * @param changedFiles - Repo-relative changed files.\n * @param storyFiles - Normalized story import paths from the story index.\n * @param options - Untraced globs excluded from tracing.\n * @returns Affected story files, or `null` when everything must render.\n */\nexport function traceAffected(\n graph: DepGraph,\n changedFiles: string[],\n storyFiles: readonly string[],\n options: TraceOptions = {},\n): string[] | null {\n const untraced = options.untraced ?? [];\n const relevant = changedFiles.filter((file) => !matchesAnyGlob(file, untraced));\n const forcing = relevant.find((file) => isGlobalFile(file));\n if (forcing !== undefined) {\n return null;\n }\n const storyOf = storyNodeMap(graph, storyFiles);\n const roots = traceRoots(graph, relevant, storyOf);\n return [...reachableStories(graph, roots, storyOf)].toSorted();\n}\n"],"mappings":"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"}
|
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
|
package/dist/usage.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/usage.d.ts
|
|
5
4
|
/** A dependency package a story file reaches, and the first module that reaches it. */
|
|
6
5
|
interface PackageUsage {
|
|
7
6
|
/** Story file (repo-relative, as given by the Storybook index). */
|
|
@@ -26,6 +25,5 @@ interface TrackedPackage {
|
|
|
26
25
|
* @param packages - Packages to track (e.g. the project's declared dependencies).
|
|
27
26
|
*/
|
|
28
27
|
declare function extractPackageUsage(graph: DepGraph, storyFiles: readonly string[], packages: Record<string, TrackedPackage>): PackageUsage[];
|
|
29
|
-
//#endregion
|
|
30
28
|
export { PackageUsage, TrackedPackage, extractPackageUsage };
|
|
31
29
|
//# sourceMappingURL=usage.d.mts.map
|
package/dist/usage.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
|
-
|
|
5
|
+
// src/usage.ts
|
|
6
6
|
const NODE_MODULES = "node_modules/";
|
|
7
7
|
/** Package name for a `…/node_modules/<pkg>/…` module path, handling scopes. */
|
|
8
8
|
function nodeModulesPackage(module) {
|
|
@@ -60,7 +60,6 @@ function extractPackageUsage(graph, storyFiles, packages) {
|
|
|
60
60
|
const tracked = new Map(Object.entries(packages));
|
|
61
61
|
return storyFiles.flatMap((storyFile) => usageForStory(graph, storyFile, graphModulesFor(graph, storyFile), tracked));
|
|
62
62
|
}
|
|
63
|
-
//#endregion
|
|
64
63
|
export { extractPackageUsage };
|
|
65
64
|
|
|
66
65
|
//# sourceMappingURL=usage.mjs.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storyshelf/affected",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0-next-d2cc082",
|
|
4
4
|
"description": "Affected capture for StoryShelf: dependency-graph tracing that selects the stories impacted by a change.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"affected",
|
|
@@ -60,18 +60,9 @@
|
|
|
60
60
|
"oxlint-tsgolint": "^7.0.2001",
|
|
61
61
|
"tsdown": "^0.22.14",
|
|
62
62
|
"typescript": "^7.0.2",
|
|
63
|
-
"vitest": "^5.0.
|
|
63
|
+
"vitest": "^5.0.5"
|
|
64
64
|
},
|
|
65
65
|
"engines": {
|
|
66
66
|
"node": ">=24"
|
|
67
|
-
},
|
|
68
|
-
"jsr": {
|
|
69
|
-
"runtimeCompat": {
|
|
70
|
-
"node": true,
|
|
71
|
-
"deno": true,
|
|
72
|
-
"bun": true,
|
|
73
|
-
"workerd": false,
|
|
74
|
-
"browser": false
|
|
75
|
-
}
|
|
76
67
|
}
|
|
77
68
|
}
|