@blzsky/variant 0.2.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.
Files changed (42) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +177 -0
  3. package/dist/build-2LO7GD2S.js +63 -0
  4. package/dist/check-ZTKGGF4M.js +44 -0
  5. package/dist/chunk-3E5LBU3O.js +180 -0
  6. package/dist/chunk-4LLK2S4R.js +630 -0
  7. package/dist/chunk-6J4XDG44.js +14 -0
  8. package/dist/chunk-CXNXZLDG.js +13 -0
  9. package/dist/chunk-DRMASVYN.js +25 -0
  10. package/dist/chunk-GDSZFIHM.js +29 -0
  11. package/dist/chunk-ISRF5F3Q.js +68 -0
  12. package/dist/chunk-KSHWC2XN.js +78 -0
  13. package/dist/chunk-LJDDS4RR.js +91 -0
  14. package/dist/chunk-LXKLCLL6.js +91 -0
  15. package/dist/chunk-MZZOE7L4.js +26 -0
  16. package/dist/chunk-NSNZOH5P.js +31 -0
  17. package/dist/chunk-O4LZKXNZ.js +301 -0
  18. package/dist/chunk-RGD422OM.js +108 -0
  19. package/dist/chunk-RVXUCJYA.js +81 -0
  20. package/dist/chunk-SWPVR2DF.js +468 -0
  21. package/dist/chunk-V22IPJ7A.js +64 -0
  22. package/dist/chunk-V5CTGXMM.js +256 -0
  23. package/dist/chunk-VTJ67TT5.js +238 -0
  24. package/dist/chunk-XTYVVUWW.js +279 -0
  25. package/dist/chunk-XUBWCTJZ.js +100 -0
  26. package/dist/chunk-YYGCSKIQ.js +29 -0
  27. package/dist/cli.d.ts +1 -0
  28. package/dist/cli.js +225 -0
  29. package/dist/diff-GFI6RAEA.js +60 -0
  30. package/dist/doctor-GH4RZVEH.js +127 -0
  31. package/dist/env-QORDD6FQ.js +24 -0
  32. package/dist/impact-AF453PLK.js +702 -0
  33. package/dist/index.d.ts +56 -0
  34. package/dist/index.js +7 -0
  35. package/dist/init-KJIUNGAN.js +158 -0
  36. package/dist/insight-DZ5NQY6D.js +29 -0
  37. package/dist/pr-UFZRZX6S.js +152 -0
  38. package/dist/run-6PNH4QRA.js +40 -0
  39. package/dist/symbol-graph-YYUEMZGR.js +22 -0
  40. package/dist/tsconfig-paths-E3UEDGXZ.js +10 -0
  41. package/dist/workspace-X5FUBTY3.js +186 -0
  42. package/package.json +86 -0
@@ -0,0 +1,56 @@
1
+ import * as z from 'zod';
2
+
3
+ declare const taskConfigSchema: z.ZodObject<{
4
+ inputs: z.ZodOptional<z.ZodArray<z.ZodString>>;
5
+ dependsOn: z.ZodOptional<z.ZodArray<z.ZodString>>;
6
+ command: z.ZodOptional<z.ZodString>;
7
+ cpuHeavy: z.ZodOptional<z.ZodBoolean>;
8
+ }, z.core.$strip>;
9
+ declare const variantConfigSchema: z.ZodObject<{
10
+ strategy: z.ZodDefault<z.ZodEnum<{
11
+ adaptive: "adaptive";
12
+ strict: "strict";
13
+ }>>;
14
+ cache: z.ZodDefault<z.ZodObject<{
15
+ mode: z.ZodDefault<z.ZodLiteral<"content">>;
16
+ directory: z.ZodDefault<z.ZodString>;
17
+ ttlDays: z.ZodOptional<z.ZodNumber>;
18
+ }, z.core.$strip>>;
19
+ tasks: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
20
+ inputs: z.ZodOptional<z.ZodArray<z.ZodString>>;
21
+ dependsOn: z.ZodOptional<z.ZodArray<z.ZodString>>;
22
+ command: z.ZodOptional<z.ZodString>;
23
+ cpuHeavy: z.ZodOptional<z.ZodBoolean>;
24
+ }, z.core.$strip>>>;
25
+ workspace: z.ZodOptional<z.ZodObject<{
26
+ enabled: z.ZodDefault<z.ZodBoolean>;
27
+ scripts: z.ZodDefault<z.ZodArray<z.ZodString>>;
28
+ }, z.core.$strip>>;
29
+ git: z.ZodOptional<z.ZodObject<{
30
+ baseRef: z.ZodDefault<z.ZodString>;
31
+ enabled: z.ZodDefault<z.ZodBoolean>;
32
+ }, z.core.$strip>>;
33
+ semanticDiff: z.ZodOptional<z.ZodObject<{
34
+ enabled: z.ZodDefault<z.ZodBoolean>;
35
+ }, z.core.$strip>>;
36
+ scheduler: z.ZodOptional<z.ZodObject<{
37
+ policy: z.ZodDefault<z.ZodEnum<{
38
+ auto: "auto";
39
+ "light-first": "light-first";
40
+ "pack-heavy": "pack-heavy";
41
+ "critical-path": "critical-path";
42
+ }>>;
43
+ }, z.core.$strip>>;
44
+ }, z.core.$strip>;
45
+
46
+ /** The object a user passes to `defineConfig`: every field optional. */
47
+ type VariantConfig = z.input<typeof variantConfigSchema>;
48
+ /** The same config after validation, with every default filled in. */
49
+ type ResolvedVariantConfig = z.output<typeof variantConfigSchema>;
50
+ type Strategy = ResolvedVariantConfig["strategy"];
51
+ type TaskConfig = z.infer<typeof taskConfigSchema>;
52
+ type CacheConfig = ResolvedVariantConfig["cache"];
53
+
54
+ declare function defineConfig(config: VariantConfig): VariantConfig;
55
+
56
+ export { type CacheConfig, type ResolvedVariantConfig, type Strategy, type TaskConfig, type VariantConfig, defineConfig };
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ defineConfig
3
+ } from "./chunk-RGD422OM.js";
4
+ import "./chunk-RVXUCJYA.js";
5
+ export {
6
+ defineConfig
7
+ };
@@ -0,0 +1,158 @@
1
+ import {
2
+ detectProject
3
+ } from "./chunk-V5CTGXMM.js";
4
+ import {
5
+ findVariantConfigPath
6
+ } from "./chunk-RGD422OM.js";
7
+ import "./chunk-RVXUCJYA.js";
8
+ import {
9
+ lines
10
+ } from "./chunk-6J4XDG44.js";
11
+ import {
12
+ getPrinter
13
+ } from "./chunk-V22IPJ7A.js";
14
+
15
+ // src/cli/commands/init.ts
16
+ import { writeFile } from "fs/promises";
17
+ import path2 from "path";
18
+ import { createInterface } from "readline/promises";
19
+
20
+ // src/core/scaffold/config-template.ts
21
+ import { readFile } from "fs/promises";
22
+ import path from "path";
23
+ var DEFAULT_TASK_INPUTS = {
24
+ build: ["src/**/*", "package.json"],
25
+ lint: ["src/**/*", "*.config.*"],
26
+ test: ["src/**/*", "**/__tests__/**/*"],
27
+ typecheck: ["src/**/*", "tsconfig*.json"],
28
+ format: ["src/**/*"]
29
+ };
30
+ var TASK_ORDER = ["build", "lint", "test", "typecheck", "format"];
31
+ var FALLBACK_TASKS = ["build", "lint", "test"];
32
+ function inputsFor(task) {
33
+ return DEFAULT_TASK_INPUTS[task] ?? DEFAULT_TASK_INPUTS["build"] ?? [];
34
+ }
35
+ function runPrefix(pm) {
36
+ return pm === "pnpm" || pm === "yarn" ? pm : "npm run";
37
+ }
38
+ function binaryRunner(pm) {
39
+ if (pm === "pnpm") return "pnpm";
40
+ if (pm === "yarn") return "yarn";
41
+ return "npx";
42
+ }
43
+ function defaultCommandFor(pm, task, framework) {
44
+ if (task === "build" && framework === "next") {
45
+ return `${binaryRunner(pm)} next build`;
46
+ }
47
+ if (task === "build" && framework === "vite") {
48
+ return `${binaryRunner(pm)} vite build`;
49
+ }
50
+ return `${runPrefix(pm)} ${task}`;
51
+ }
52
+ async function detectScriptTasks(cwd) {
53
+ try {
54
+ const manifest = JSON.parse(
55
+ await readFile(path.join(cwd, "package.json"), "utf8")
56
+ );
57
+ const present = new Set(Object.keys(manifest.scripts ?? {}));
58
+ const ordered = TASK_ORDER.filter((task) => present.has(task));
59
+ return ordered.length > 0 ? ordered : [...FALLBACK_TASKS];
60
+ } catch {
61
+ return [...FALLBACK_TASKS];
62
+ }
63
+ }
64
+ function renderConfigTemplate(tasks) {
65
+ const taskLines = tasks.map(
66
+ ({ name, command, inputs }) => ` ${name}: {
67
+ command: "${command}",
68
+ inputs: [${inputs.map((i) => `"${i}"`).join(", ")}],
69
+ }`
70
+ ).join(",\n");
71
+ return `import { defineConfig } from "@blzsky/variant";
72
+
73
+ export default defineConfig({
74
+ strategy: "adaptive",
75
+ tasks: {
76
+ ${taskLines},
77
+ },
78
+ });
79
+ `;
80
+ }
81
+
82
+ // src/cli/commands/init.ts
83
+ var CONFIG_FILENAME = "variant.config.ts";
84
+ function splitList(answer) {
85
+ return answer.split(",").map((entry) => entry.trim()).filter(Boolean);
86
+ }
87
+ async function registerInitAction() {
88
+ const cwd = process.cwd();
89
+ const printer = getPrinter();
90
+ const existing = findVariantConfigPath(cwd);
91
+ if (existing) {
92
+ lines(
93
+ printer,
94
+ `${path2.basename(existing)} already exists \u2014 nothing written.`
95
+ );
96
+ return;
97
+ }
98
+ const [{ pm, framework }, detectedScripts] = await Promise.all([
99
+ detectProject(cwd),
100
+ detectScriptTasks(cwd)
101
+ ]);
102
+ const pmName = pm.name;
103
+ const frameworkName = framework?.name ?? null;
104
+ const destination = path2.join(cwd, CONFIG_FILENAME);
105
+ if (!process.stdin.isTTY) {
106
+ const build = {
107
+ name: "build",
108
+ command: defaultCommandFor(pmName, "build", frameworkName),
109
+ inputs: inputsFor("build")
110
+ };
111
+ await writeFile(destination, renderConfigTemplate([build]));
112
+ lines(
113
+ printer,
114
+ `Created ${CONFIG_FILENAME} (non-interactive, detected ${pmName}${frameworkName ? `/${frameworkName}` : ""})`
115
+ );
116
+ return;
117
+ }
118
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
119
+ try {
120
+ lines(
121
+ printer,
122
+ "",
123
+ `Detected: ${pmName}${frameworkName ? ` \xB7 ${frameworkName}` : ""} \xB7 scripts: ${detectedScripts.join(", ")}`,
124
+ ""
125
+ );
126
+ const answeredTasks = await rl.question(
127
+ `Tasks to configure [${detectedScripts.join(",")}]: `
128
+ );
129
+ const taskNames = answeredTasks.trim() ? splitList(answeredTasks) : detectedScripts;
130
+ const tasks = [];
131
+ for (const name of taskNames) {
132
+ const suggestedCommand = defaultCommandFor(pmName, name, frameworkName);
133
+ const answeredCommand = await rl.question(
134
+ ` Command for "${name}" [${suggestedCommand}]: `
135
+ );
136
+ const suggestedInputs = inputsFor(name);
137
+ const answeredInputs = await rl.question(
138
+ ` Inputs for "${name}" [${suggestedInputs.join(", ")}]: `
139
+ );
140
+ tasks.push({
141
+ name,
142
+ command: answeredCommand.trim() || suggestedCommand,
143
+ inputs: answeredInputs.trim() ? splitList(answeredInputs) : suggestedInputs
144
+ });
145
+ }
146
+ await writeFile(destination, renderConfigTemplate(tasks));
147
+ lines(
148
+ printer,
149
+ "",
150
+ `Created ${CONFIG_FILENAME} \u2014 run \`variant doctor\` to verify.`
151
+ );
152
+ } finally {
153
+ rl.close();
154
+ }
155
+ }
156
+ export {
157
+ registerInitAction
158
+ };
@@ -0,0 +1,29 @@
1
+ import {
2
+ computeInsights,
3
+ readCache
4
+ } from "./chunk-LXKLCLL6.js";
5
+ import {
6
+ renderInsights
7
+ } from "./chunk-ISRF5F3Q.js";
8
+ import {
9
+ createContext
10
+ } from "./chunk-SWPVR2DF.js";
11
+ import "./chunk-V5CTGXMM.js";
12
+ import "./chunk-RGD422OM.js";
13
+ import "./chunk-YYGCSKIQ.js";
14
+ import "./chunk-MZZOE7L4.js";
15
+ import "./chunk-3E5LBU3O.js";
16
+ import "./chunk-RVXUCJYA.js";
17
+ import "./chunk-XUBWCTJZ.js";
18
+ import "./chunk-6J4XDG44.js";
19
+ import "./chunk-V22IPJ7A.js";
20
+
21
+ // src/cli/commands/insight.ts
22
+ async function registerInsightAction() {
23
+ const ctx = await createContext(process.cwd(), { scope: false });
24
+ const cache = await readCache(ctx.cacheDir);
25
+ renderInsights(computeInsights([], cache));
26
+ }
27
+ export {
28
+ registerInsightAction
29
+ };
@@ -0,0 +1,152 @@
1
+ import {
2
+ deriveVerdict,
3
+ verdictText
4
+ } from "./chunk-DRMASVYN.js";
5
+ import {
6
+ classifyFileAgainstRef
7
+ } from "./chunk-NSNZOH5P.js";
8
+ import "./chunk-LJDDS4RR.js";
9
+ import {
10
+ listChangedFilesSinceMergeBase
11
+ } from "./chunk-XUBWCTJZ.js";
12
+ import {
13
+ classificationLabel
14
+ } from "./chunk-CXNXZLDG.js";
15
+ import {
16
+ lines
17
+ } from "./chunk-6J4XDG44.js";
18
+ import {
19
+ getPrinter
20
+ } from "./chunk-V22IPJ7A.js";
21
+ import "./chunk-O4LZKXNZ.js";
22
+
23
+ // src/cli/commands/pr.ts
24
+ import { writeFile } from "fs/promises";
25
+ import path from "path";
26
+
27
+ // src/core/pr/check.ts
28
+ var DEFAULT_PR_BASE_REF = "main";
29
+ var TS_FILE = /\.tsx?$/;
30
+ async function runPrCheck(cwd, options = {}) {
31
+ const baseRef = options.base ?? DEFAULT_PR_BASE_REF;
32
+ const changed = options.changedFiles ?? await listChangedFilesSinceMergeBase(cwd, baseRef) ?? [];
33
+ const tsFiles = changed.filter((file) => TS_FILE.test(file));
34
+ const files = [];
35
+ for (const relPath of tsFiles) {
36
+ files.push(await classifyFileAgainstRef(cwd, relPath, baseRef));
37
+ }
38
+ return {
39
+ baseRef,
40
+ tsFilesChanged: tsFiles.length,
41
+ files,
42
+ verdict: deriveVerdict(files.map((file) => file.classification))
43
+ };
44
+ }
45
+
46
+ // src/core/pr/report.ts
47
+ async function buildPrReport(cwd, options = {}) {
48
+ const check = await runPrCheck(cwd, options);
49
+ return { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), check };
50
+ }
51
+ var VERDICT_EMOJI = {
52
+ "safe-to-skip": "\u2705",
53
+ "build-recommended": "\u26A0\uFE0F",
54
+ "build-required": "\u{1F534}"
55
+ };
56
+ var VERDICT_HEADING = {
57
+ "safe-to-skip": "Safe to skip build",
58
+ "build-recommended": "Build recommended",
59
+ "build-required": "Build required"
60
+ };
61
+ function symbolDelta(counts) {
62
+ const delta = [];
63
+ if (counts.added.length > 0) delta.push(`+${counts.added.length}`);
64
+ if (counts.removed.length > 0) delta.push(`-${counts.removed.length}`);
65
+ if (counts.changed.length > 0) delta.push(`~${counts.changed.length}`);
66
+ return delta;
67
+ }
68
+ function formatPrReportJson(report) {
69
+ return JSON.stringify(report, null, 2);
70
+ }
71
+ function formatPrReportMarkdown(report) {
72
+ const { check } = report;
73
+ const lines2 = [
74
+ "## Variant PR Report",
75
+ "",
76
+ `**Generated:** ${report.generatedAt} `,
77
+ `**Base ref:** \`${check.baseRef}\` `,
78
+ "",
79
+ "### Semantic Diff",
80
+ "",
81
+ `${VERDICT_EMOJI[check.verdict]} **${VERDICT_HEADING[check.verdict]}**`,
82
+ ""
83
+ ];
84
+ if (check.files.length > 0) {
85
+ lines2.push(
86
+ "| File | Classification | API Changes |",
87
+ "|------|----------------|-------------|"
88
+ );
89
+ for (const file of check.files) {
90
+ const delta = symbolDelta(file.exportedSymbols);
91
+ lines2.push(
92
+ `| \`${file.filePath}\` | ${file.classification} | ${delta.join(" ") || "\u2014"} |`
93
+ );
94
+ }
95
+ lines2.push("");
96
+ } else {
97
+ lines2.push("_No TypeScript files changed._", "");
98
+ }
99
+ return lines2.join("\n");
100
+ }
101
+
102
+ // src/cli/render/pr.ts
103
+ function symbolSummary(symbols) {
104
+ const changes = [];
105
+ if (symbols.added.length > 0) changes.push(`+${symbols.added.length} added`);
106
+ if (symbols.removed.length > 0) {
107
+ changes.push(`-${symbols.removed.length} removed`);
108
+ }
109
+ if (symbols.changed.length > 0) {
110
+ changes.push(`~${symbols.changed.length} changed`);
111
+ }
112
+ return changes.length > 0 ? ` (${changes.join(", ")})` : "";
113
+ }
114
+ function renderPrCheck(result, printer = getPrinter()) {
115
+ lines(
116
+ printer,
117
+ "",
118
+ `Base ref: ${result.baseRef}`,
119
+ `Changed .ts files: ${result.tsFilesChanged}`
120
+ );
121
+ if (result.files.length > 0) {
122
+ lines(printer, "", "File classifications:");
123
+ for (const file of result.files) {
124
+ lines(
125
+ printer,
126
+ ` ${classificationLabel(file.classification)} ${file.filePath}${symbolSummary(file.exportedSymbols)}`
127
+ );
128
+ }
129
+ }
130
+ lines(printer, "", `Verdict: ${verdictText(result.verdict)}`);
131
+ }
132
+
133
+ // src/cli/commands/pr.ts
134
+ async function registerPrCheckAction(opts) {
135
+ renderPrCheck(await runPrCheck(process.cwd(), opts));
136
+ }
137
+ async function registerPrReportAction(opts) {
138
+ const cwd = process.cwd();
139
+ const report = await buildPrReport(cwd, opts);
140
+ const content = opts.markdown ? formatPrReportMarkdown(report) : formatPrReportJson(report);
141
+ if (opts.output === void 0) {
142
+ lines(getPrinter(), content);
143
+ return;
144
+ }
145
+ await writeFile(path.resolve(cwd, opts.output), content, "utf8");
146
+ const kind = opts.markdown ? "Markdown report" : "Report";
147
+ lines(getPrinter(), `${kind} written to ${opts.output}`);
148
+ }
149
+ export {
150
+ registerPrCheckAction,
151
+ registerPrReportAction
152
+ };
@@ -0,0 +1,40 @@
1
+ import {
2
+ executeTarget,
3
+ renderDryRunPlan,
4
+ reportRunInsights
5
+ } from "./chunk-4LLK2S4R.js";
6
+ import "./chunk-LXKLCLL6.js";
7
+ import "./chunk-ISRF5F3Q.js";
8
+ import {
9
+ createContext,
10
+ toRunOptions
11
+ } from "./chunk-SWPVR2DF.js";
12
+ import "./chunk-V5CTGXMM.js";
13
+ import "./chunk-RGD422OM.js";
14
+ import "./chunk-YYGCSKIQ.js";
15
+ import "./chunk-GDSZFIHM.js";
16
+ import "./chunk-MZZOE7L4.js";
17
+ import "./chunk-3E5LBU3O.js";
18
+ import "./chunk-RVXUCJYA.js";
19
+ import "./chunk-XUBWCTJZ.js";
20
+ import "./chunk-6J4XDG44.js";
21
+ import "./chunk-V22IPJ7A.js";
22
+
23
+ // src/cli/commands/run.ts
24
+ async function registerRunAction(taskName, opts = {}) {
25
+ const ctx = await createContext();
26
+ if (opts.dryRun) {
27
+ renderDryRunPlan(taskName, ctx.graph.toLevels(taskName));
28
+ return;
29
+ }
30
+ const results = await executeTarget(
31
+ taskName,
32
+ ctx,
33
+ toRunOptions(ctx, opts),
34
+ `Running ${taskName} tasks...`
35
+ );
36
+ await reportRunInsights(ctx, results);
37
+ }
38
+ export {
39
+ registerRunAction
40
+ };
@@ -0,0 +1,22 @@
1
+ import {
2
+ SYMBOL_GRAPH_VERSION,
3
+ buildSymbolGraph,
4
+ defaultGraphDir,
5
+ findSymbol,
6
+ getFileIndex,
7
+ loadSymbolGraph,
8
+ saveSymbolGraph,
9
+ updateSymbolGraph
10
+ } from "./chunk-VTJ67TT5.js";
11
+ import "./chunk-RVXUCJYA.js";
12
+ import "./chunk-O4LZKXNZ.js";
13
+ export {
14
+ SYMBOL_GRAPH_VERSION,
15
+ buildSymbolGraph,
16
+ defaultGraphDir,
17
+ findSymbol,
18
+ getFileIndex,
19
+ loadSymbolGraph,
20
+ saveSymbolGraph,
21
+ updateSymbolGraph
22
+ };
@@ -0,0 +1,10 @@
1
+ import {
2
+ matchPathAlias,
3
+ readPathAliases,
4
+ toPathAliases
5
+ } from "./chunk-KSHWC2XN.js";
6
+ export {
7
+ matchPathAlias,
8
+ readPathAliases,
9
+ toPathAliases
10
+ };
@@ -0,0 +1,186 @@
1
+ import {
2
+ resolveRelativeImport
3
+ } from "./chunk-XTYVVUWW.js";
4
+ import {
5
+ loadPackageGraph
6
+ } from "./chunk-3E5LBU3O.js";
7
+ import "./chunk-KSHWC2XN.js";
8
+ import {
9
+ updateSymbolGraph
10
+ } from "./chunk-VTJ67TT5.js";
11
+ import "./chunk-RVXUCJYA.js";
12
+ import {
13
+ plural
14
+ } from "./chunk-CXNXZLDG.js";
15
+ import {
16
+ lines
17
+ } from "./chunk-6J4XDG44.js";
18
+ import {
19
+ getPrinter
20
+ } from "./chunk-V22IPJ7A.js";
21
+ import "./chunk-O4LZKXNZ.js";
22
+
23
+ // src/core/graph/workspace-audit.ts
24
+ import { readFile } from "fs/promises";
25
+ import path from "path";
26
+
27
+ // src/core/graph/workspace-check.ts
28
+ import { builtinModules } from "module";
29
+ var BUILTINS = new Set(builtinModules);
30
+ function checkWorkspace(input) {
31
+ const { symbolGraph, packages } = input;
32
+ const rootDeclared = input.rootDeclared ?? /* @__PURE__ */ new Set();
33
+ const byName = new Map(packages.map((p) => [p.name, p]));
34
+ const files = new Set(Object.keys(symbolGraph.files));
35
+ const grouped = /* @__PURE__ */ new Map();
36
+ const addViolation = (kind, pkg, target, file) => {
37
+ const key = `${kind}|${pkg}|${target}`;
38
+ let violation = grouped.get(key);
39
+ if (violation === void 0) {
40
+ violation = { kind, package: pkg, target, files: [] };
41
+ grouped.set(key, violation);
42
+ }
43
+ if (!violation.files.includes(file)) violation.files.push(file);
44
+ };
45
+ for (const file of [...files].sort()) {
46
+ const owner = fileToPackage(file, packages);
47
+ if (owner === void 0) continue;
48
+ for (const imp of symbolGraph.files[file]?.imports ?? []) {
49
+ const spec = imp.module;
50
+ if (spec.startsWith("./") || spec.startsWith("../")) {
51
+ const target = resolveRelativeImport(file, spec, files);
52
+ if (target === void 0) continue;
53
+ const targetOwner = fileToPackage(target, packages);
54
+ if (targetOwner !== void 0 && targetOwner.name !== owner.name) {
55
+ addViolation(
56
+ "cross-package-relative-import",
57
+ owner.name,
58
+ targetOwner.name,
59
+ file
60
+ );
61
+ }
62
+ continue;
63
+ }
64
+ const name = packageNameOf(stripNodePrefix(spec));
65
+ if (name === void 0 || BUILTINS.has(name)) continue;
66
+ if (spec.startsWith("node:") || name === owner.name) continue;
67
+ if (byName.has(name)) {
68
+ if (!owner.declared.has(name)) {
69
+ addViolation("undeclared-workspace-dep", owner.name, name, file);
70
+ }
71
+ continue;
72
+ }
73
+ if (!owner.declared.has(name) && !rootDeclared.has(name)) {
74
+ addViolation("undeclared-external-dep", owner.name, name, file);
75
+ }
76
+ }
77
+ }
78
+ const violations = [...grouped.values()].map((v) => ({ ...v, files: [...v.files].sort() })).sort(
79
+ (a, b) => a.package.localeCompare(b.package) || a.target.localeCompare(b.target) || a.kind.localeCompare(b.kind)
80
+ );
81
+ return { packagesChecked: packages.length, violations };
82
+ }
83
+ function fileToPackage(file, packages) {
84
+ let best;
85
+ let bestLength = -1;
86
+ for (const pkg of packages) {
87
+ const prefix = `${pkg.dir}/`;
88
+ if (file.startsWith(prefix) && prefix.length > bestLength) {
89
+ best = pkg;
90
+ bestLength = prefix.length;
91
+ }
92
+ }
93
+ return best;
94
+ }
95
+ function packageNameOf(spec) {
96
+ const segments = spec.split("/");
97
+ const nameLength = spec.startsWith("@") ? 2 : 1;
98
+ if (segments.length < nameLength) return void 0;
99
+ const name = segments.slice(0, nameLength).join("/");
100
+ return name === "" ? void 0 : name;
101
+ }
102
+ function stripNodePrefix(spec) {
103
+ return spec.startsWith("node:") ? spec.slice(5) : spec;
104
+ }
105
+
106
+ // src/core/graph/workspace-audit.ts
107
+ function declaredNames(manifest) {
108
+ return /* @__PURE__ */ new Set([
109
+ ...Object.keys(manifest.dependencies ?? {}),
110
+ ...Object.keys(manifest.devDependencies ?? {}),
111
+ ...Object.keys(manifest.peerDependencies ?? {})
112
+ ]);
113
+ }
114
+ async function readRootDeclared(cwd) {
115
+ try {
116
+ const manifest = JSON.parse(
117
+ await readFile(path.join(cwd, "package.json"), "utf8")
118
+ );
119
+ return declaredNames(manifest);
120
+ } catch {
121
+ return /* @__PURE__ */ new Set();
122
+ }
123
+ }
124
+ async function auditWorkspace(cwd) {
125
+ const packageGraph = await loadPackageGraph(cwd).catch(() => null);
126
+ if (packageGraph === null || packageGraph.packages.length === 0) return null;
127
+ const packages = packageGraph.packages.map((pkg) => ({
128
+ name: pkg.manifest.name,
129
+ dir: path.relative(cwd, pkg.dir).replace(/\\/g, "/"),
130
+ declared: declaredNames(pkg.manifest)
131
+ }));
132
+ const { graph: symbolGraph } = await updateSymbolGraph(cwd);
133
+ return checkWorkspace({
134
+ symbolGraph,
135
+ packages,
136
+ rootDeclared: await readRootDeclared(cwd)
137
+ });
138
+ }
139
+
140
+ // src/cli/render/workspace.ts
141
+ var MAX_FILES_SHOWN = 5;
142
+ var NO_WORKSPACE_MESSAGE = "workspace check: no workspace packages found (pnpm-workspace.yaml or package.json `workspaces` required).";
143
+ var DESCRIBE = {
144
+ "undeclared-workspace-dep": (v) => `${v.package} imports ${v.target} but does not declare it`,
145
+ "undeclared-external-dep": (v) => `${v.package} imports ${v.target} but neither it nor the workspace root declares it`,
146
+ "cross-package-relative-import": (v) => `${v.package} reaches into ${v.target} via a relative import (bypasses its public entry)`
147
+ };
148
+ function renderWorkspaceCheck(result, printer = getPrinter()) {
149
+ lines(printer, "", `Checked ${plural(result.packagesChecked, "package")}.`);
150
+ if (result.violations.length === 0) {
151
+ lines(printer, "", "No dependency violations found.");
152
+ return;
153
+ }
154
+ lines(printer, "");
155
+ for (const violation of result.violations) {
156
+ lines(printer, ` \u2717 ${DESCRIBE[violation.kind](violation)}`);
157
+ for (const file of violation.files.slice(0, MAX_FILES_SHOWN)) {
158
+ lines(printer, ` ${file}`);
159
+ }
160
+ if (violation.files.length > MAX_FILES_SHOWN) {
161
+ lines(
162
+ printer,
163
+ ` \u2026 and ${violation.files.length - MAX_FILES_SHOWN} more file(s)`
164
+ );
165
+ }
166
+ }
167
+ lines(printer, "", `${plural(result.violations.length, "violation")} found.`);
168
+ }
169
+
170
+ // src/cli/commands/workspace.ts
171
+ async function registerWorkspaceCheckAction(opts = {}) {
172
+ const result = await auditWorkspace(process.cwd());
173
+ if (result === null) {
174
+ lines(getPrinter(), NO_WORKSPACE_MESSAGE);
175
+ return;
176
+ }
177
+ if (opts.json === true) {
178
+ lines(getPrinter(), JSON.stringify(result, null, 2));
179
+ } else {
180
+ renderWorkspaceCheck(result);
181
+ }
182
+ if (result.violations.length > 0) process.exitCode = 1;
183
+ }
184
+ export {
185
+ registerWorkspaceCheckAction
186
+ };