@blzsky/variant 0.2.1 → 0.3.0-canary.1

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 (58) hide show
  1. package/README.md +222 -85
  2. package/dist/{build-3DSBMMI3.js → build-GCUFPPIB.js} +11 -10
  3. package/dist/check-AKYABBIV.js +1459 -0
  4. package/dist/{chunk-43VLELTL.js → chunk-2UVLKBLV.js} +1 -1
  5. package/dist/chunk-34VGKPEM.js +59 -0
  6. package/dist/chunk-BNACT47L.js +590 -0
  7. package/dist/chunk-C3NZIFW6.js +174 -0
  8. package/dist/{chunk-ZH2PQUNL.js → chunk-EFCMBBBT.js} +72 -110
  9. package/dist/chunk-H55GYSQ6.js +102 -0
  10. package/dist/chunk-HL65IPEQ.js +167 -0
  11. package/dist/chunk-IHXJ72XA.js +31 -0
  12. package/dist/{chunk-FZEXZBXB.js → chunk-JEJRXPCU.js} +41 -9
  13. package/dist/chunk-LUTP2TVU.js +94 -0
  14. package/dist/{chunk-7OPTWU6E.js → chunk-MKUYZIXF.js} +1 -1
  15. package/dist/chunk-MNYKLWRC.js +377 -0
  16. package/dist/{chunk-Q7RLUZSN.js → chunk-NJSTAWDM.js} +10 -3
  17. package/dist/{chunk-26DHEXGS.js → chunk-NOC53Z5Q.js} +1 -1
  18. package/dist/chunk-O6FCFZDF.js +127 -0
  19. package/dist/{chunk-6BXSZPHC.js → chunk-OON6O4S5.js} +99 -6
  20. package/dist/{chunk-FA64KWJ2.js → chunk-TPMHY4L6.js} +16 -31
  21. package/dist/chunk-TRUK3TJF.js +575 -0
  22. package/dist/chunk-WTXWEEI2.js +21 -0
  23. package/dist/{chunk-RC23JQKI.js → chunk-WULBOSG5.js} +15 -2
  24. package/dist/chunk-X3VVGTEV.js +268 -0
  25. package/dist/{chunk-TCISS2AS.js → chunk-XXMD4NC5.js} +120 -25
  26. package/dist/cli.js +89 -30
  27. package/dist/{diff-HSJBZFMC.js → diff-ST5KHT7P.js} +5 -5
  28. package/dist/{doctor-B7MHFIYJ.js → doctor-OEIY4Y5X.js} +41 -8
  29. package/dist/{env-I7JZRWDN.js → env-EQAOZCFR.js} +8 -9
  30. package/dist/git-VGHUF2SY.js +30 -0
  31. package/dist/impact-AXLZTLVT.js +266 -0
  32. package/dist/index.js +2 -2
  33. package/dist/init-QCOZWLOO.js +793 -0
  34. package/dist/{insight-JQLTGFYB.js → insight-KRWT6Z47.js} +9 -10
  35. package/dist/pr-7JXUC5BQ.js +710 -0
  36. package/dist/{run-C6VAQOVM.js → run-UOPS5FRU.js} +11 -10
  37. package/dist/stryker-host.d.ts +2 -0
  38. package/dist/stryker-host.js +135 -0
  39. package/dist/stryker-plugins.d.ts +33 -0
  40. package/dist/stryker-plugins.js +142 -0
  41. package/dist/{symbol-graph-E5GXKDFZ.js → symbol-graph-GYOI4GU4.js} +3 -3
  42. package/dist/{tsconfig-paths-E3UEDGXZ.js → tsconfig-paths-SV3PULCW.js} +3 -1
  43. package/dist/vitest.cjs +629 -0
  44. package/dist/vitest.d.cts +181 -0
  45. package/dist/vitest.d.ts +181 -0
  46. package/dist/vitest.js +358 -0
  47. package/dist/workspace-IQFLR3RY.js +65 -0
  48. package/package.json +17 -3
  49. package/dist/check-SIQJU6Z2.js +0 -44
  50. package/dist/chunk-DRMASVYN.js +0 -25
  51. package/dist/chunk-KSHWC2XN.js +0 -78
  52. package/dist/chunk-QRPI6TJE.js +0 -68
  53. package/dist/chunk-VYWPQKMS.js +0 -88
  54. package/dist/chunk-XTYVVUWW.js +0 -279
  55. package/dist/impact-PRZ6HEP5.js +0 -735
  56. package/dist/init-O2BY3FOY.js +0 -158
  57. package/dist/pr-Y724QAHE.js +0 -165
  58. package/dist/workspace-HRTFGWKN.js +0 -186
@@ -0,0 +1,268 @@
1
+ // src/core/graph/tsconfig-paths.ts
2
+ import { readFile } from "fs/promises";
3
+ import path3 from "path";
4
+
5
+ // src/core/graph/package-imports.ts
6
+ import path2 from "path";
7
+
8
+ // src/core/graph/package-exports.ts
9
+ import path from "path";
10
+ var BUILD_DIRS = ["dist", "lib", "build", "out", "es", "esm", "cjs"];
11
+ var SOURCE_DIRS = ["src", "lib", "source", ""];
12
+ var CONDITIONS = [
13
+ "source",
14
+ "development",
15
+ "import",
16
+ "module",
17
+ "require",
18
+ "types",
19
+ "node",
20
+ "default"
21
+ ];
22
+ var MAX_CONDITION_DEPTH = 8;
23
+ function conditionTargets(value) {
24
+ return targetsOf(value);
25
+ }
26
+ function targetsOf(value, depth = 0) {
27
+ if (depth > MAX_CONDITION_DEPTH) return [];
28
+ if (typeof value === "string") return [value];
29
+ if (Array.isArray(value)) {
30
+ return value.flatMap((entry) => targetsOf(entry, depth + 1));
31
+ }
32
+ if (typeof value !== "object" || value === null) return [];
33
+ const record = value;
34
+ const out = [];
35
+ for (const condition of CONDITIONS) {
36
+ if (condition in record) {
37
+ out.push(...targetsOf(record[condition], depth + 1));
38
+ }
39
+ }
40
+ for (const [key, nested] of Object.entries(record)) {
41
+ if (!CONDITIONS.includes(key) && !key.startsWith(".")) {
42
+ out.push(...targetsOf(nested, depth + 1));
43
+ }
44
+ }
45
+ return out;
46
+ }
47
+ function clean(target) {
48
+ if (!target.startsWith("./")) return void 0;
49
+ return target.slice(2);
50
+ }
51
+ function resolveExportsSubpath(exportsField, subpath) {
52
+ if (exportsField === void 0 || exportsField === null) return [];
53
+ const request = subpath === "" ? "." : `./${subpath}`;
54
+ const isSubpathMap = typeof exportsField === "object" && !Array.isArray(exportsField) && Object.keys(exportsField).some(
55
+ (k) => k.startsWith(".")
56
+ );
57
+ if (!isSubpathMap) {
58
+ return request === "." ? targetsOf(exportsField).map(clean).filter((t) => t !== void 0) : [];
59
+ }
60
+ const map = exportsField;
61
+ if (request in map) {
62
+ return targetsOf(map[request]).map(clean).filter((t) => t !== void 0);
63
+ }
64
+ const patterns = Object.keys(map).filter((key) => key.startsWith("./") && key.includes("*")).sort((a, b) => b.length - a.length);
65
+ for (const pattern of patterns) {
66
+ const [prefix = "", suffix = ""] = pattern.split("*");
67
+ if (request.length < prefix.length + suffix.length || !request.startsWith(prefix) || !request.endsWith(suffix)) {
68
+ continue;
69
+ }
70
+ const captured = request.slice(
71
+ prefix.length,
72
+ request.length - suffix.length
73
+ );
74
+ const targets = targetsOf(map[pattern]).map((t) => clean(t.replace("*", captured))).filter((t) => t !== void 0);
75
+ if (targets.length > 0) return targets;
76
+ }
77
+ return [];
78
+ }
79
+ function toSourceCandidates(target) {
80
+ const withoutExtension = stripExtension(target);
81
+ const segments = withoutExtension.split("/");
82
+ const head = segments[0];
83
+ const bodies = head !== void 0 && BUILD_DIRS.includes(head) && segments.length > 1 ? [segments.slice(1).join("/")] : [withoutExtension];
84
+ const out = [];
85
+ for (const body of bodies) {
86
+ for (const dir of SOURCE_DIRS) {
87
+ out.push(dir === "" ? body : `${dir}/${body}`);
88
+ }
89
+ }
90
+ return [...new Set(out)];
91
+ }
92
+ function stripExtension(target) {
93
+ if (target.endsWith(".d.ts")) return target.slice(0, -".d.ts".length);
94
+ const ext = path.posix.extname(target);
95
+ return ext === "" ? target : target.slice(0, -ext.length);
96
+ }
97
+ function exportsCandidates(exportsField, subpath) {
98
+ const targets = resolveExportsSubpath(exportsField, subpath);
99
+ if (targets.length === 0) return [];
100
+ const out = [];
101
+ for (const target of targets) out.push(stripExtension(target));
102
+ for (const target of targets) out.push(...toSourceCandidates(target));
103
+ return [...new Set(out)];
104
+ }
105
+
106
+ // src/core/graph/package-imports.ts
107
+ function toImportAliases(dir, importsField) {
108
+ if (typeof importsField !== "object" || importsField === null || Array.isArray(importsField)) {
109
+ return [];
110
+ }
111
+ const aliases = [];
112
+ for (const [key, value] of Object.entries(importsField)) {
113
+ if (!key.startsWith("#")) continue;
114
+ const stars = key.split("*").length - 1;
115
+ if (stars > 1) continue;
116
+ const targets = conditionTargets(value).filter((target) => target.startsWith("./")).map((target) => path2.posix.join(dir, target));
117
+ if (targets.length === 0) continue;
118
+ const wildcard = stars === 1;
119
+ const [prefix = "", suffix = ""] = wildcard ? key.split("*") : [key, ""];
120
+ aliases.push({
121
+ prefix,
122
+ suffix,
123
+ wildcard,
124
+ targets,
125
+ ...dir !== "" && { scope: dir }
126
+ });
127
+ }
128
+ aliases.sort((a, b) => b.prefix.length - a.prefix.length);
129
+ return aliases;
130
+ }
131
+
132
+ // src/core/graph/tsconfig-paths.ts
133
+ var MAX_WILDCARDS = 1;
134
+ function toPathAliases(cwd, paths, baseUrl, scope) {
135
+ if (paths === void 0) return [];
136
+ const base = baseUrl ?? cwd;
137
+ const aliases = [];
138
+ for (const [pattern, rawTargets] of Object.entries(paths)) {
139
+ const stars = pattern.split("*").length - 1;
140
+ if (stars > MAX_WILDCARDS) continue;
141
+ const wildcard = stars === 1;
142
+ const [prefix = "", suffix = ""] = wildcard ? pattern.split("*") : [pattern, ""];
143
+ const targets = [];
144
+ for (const target of rawTargets) {
145
+ if (target.split("*").length - 1 > MAX_WILDCARDS) continue;
146
+ const absolute = path3.resolve(base, target);
147
+ const relative = path3.relative(cwd, absolute).replace(/\\/g, "/");
148
+ if (relative === "" || relative.startsWith("../")) continue;
149
+ targets.push(relative);
150
+ }
151
+ if (targets.length > 0) {
152
+ aliases.push({
153
+ prefix,
154
+ suffix,
155
+ wildcard,
156
+ targets,
157
+ ...scope !== void 0 && { scope }
158
+ });
159
+ }
160
+ }
161
+ aliases.sort((a, b) => b.prefix.length - a.prefix.length);
162
+ return aliases;
163
+ }
164
+ function matchPathAlias(spec, aliases) {
165
+ const bases = [];
166
+ for (const alias of aliases) {
167
+ if (!alias.wildcard) {
168
+ if (spec === alias.prefix) bases.push(...alias.targets);
169
+ continue;
170
+ }
171
+ if (spec.length < alias.prefix.length + alias.suffix.length || !spec.startsWith(alias.prefix) || !spec.endsWith(alias.suffix)) {
172
+ continue;
173
+ }
174
+ const captured = spec.slice(
175
+ alias.prefix.length,
176
+ spec.length - alias.suffix.length
177
+ );
178
+ for (const target of alias.targets) {
179
+ bases.push(target.replace("*", captured));
180
+ }
181
+ }
182
+ return bases;
183
+ }
184
+ function aliasesInScope(file, aliases) {
185
+ return aliases.filter(
186
+ (alias) => alias.scope === void 0 || file.startsWith(`${alias.scope}/`)
187
+ ).sort((a, b) => (b.scope?.length ?? -1) - (a.scope?.length ?? -1));
188
+ }
189
+ var CONFIG_IGNORE = [
190
+ "**/node_modules/**",
191
+ "**/dist/**",
192
+ ".git/**",
193
+ ".variant/**"
194
+ ];
195
+ async function readPathAliases(cwd) {
196
+ const fg = (await import("fast-glob")).default;
197
+ const found = (await fg(["**/tsconfig.json", "**/package.json"], {
198
+ cwd,
199
+ onlyFiles: true,
200
+ ignore: CONFIG_IGNORE
201
+ })).map((file) => file.replace(/\\/g, "/")).sort();
202
+ const named = (name) => found.filter((file) => path3.posix.basename(file) === name);
203
+ const [fromTsconfig, fromManifests] = await Promise.all([
204
+ tsconfigAliases(cwd, named("tsconfig.json")),
205
+ Promise.all(
206
+ named("package.json").map((file) => manifestImportAliases(cwd, file))
207
+ )
208
+ ]);
209
+ return [...fromTsconfig, ...fromManifests.flat()];
210
+ }
211
+ async function manifestImportAliases(cwd, manifestPath) {
212
+ try {
213
+ const manifest = JSON.parse(
214
+ await readFile(path3.join(cwd, manifestPath), "utf8")
215
+ );
216
+ if (typeof manifest !== "object" || manifest === null) return [];
217
+ const dir = path3.posix.dirname(manifestPath);
218
+ return toImportAliases(
219
+ dir === "." ? "" : dir,
220
+ manifest.imports
221
+ );
222
+ } catch {
223
+ return [];
224
+ }
225
+ }
226
+ async function tsconfigAliases(cwd, configs) {
227
+ if (configs.length === 0) return [];
228
+ const { ts } = await import("ts-morph");
229
+ const aliases = [];
230
+ for (const config of configs) {
231
+ const configPath = path3.join(cwd, config);
232
+ const configDir = path3.dirname(configPath);
233
+ const read = ts.readConfigFile(configPath, (file) => ts.sys.readFile(file));
234
+ if (read.error !== void 0 || read.config === void 0) continue;
235
+ const { options } = ts.parseJsonConfigFileContent(
236
+ read.config,
237
+ {
238
+ ...ts.sys,
239
+ // Enumerating every file in the workspace is pure waste: only
240
+ // `compilerOptions` is wanted, and a large repo makes this the
241
+ // most expensive call in the command.
242
+ readDirectory: () => [],
243
+ useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames
244
+ },
245
+ configDir
246
+ );
247
+ const pathsBase = options["pathsBasePath"];
248
+ const base = options.baseUrl ?? (typeof pathsBase === "string" ? pathsBase : configDir);
249
+ const scope = path3.posix.dirname(config);
250
+ aliases.push(
251
+ ...toPathAliases(
252
+ cwd,
253
+ options.paths,
254
+ base,
255
+ scope === "." ? void 0 : scope
256
+ )
257
+ );
258
+ }
259
+ return aliases;
260
+ }
261
+
262
+ export {
263
+ exportsCandidates,
264
+ toPathAliases,
265
+ matchPathAlias,
266
+ aliasesInScope,
267
+ readPathAliases
268
+ };
@@ -1,24 +1,52 @@
1
1
  import {
2
2
  collectExportedSurface
3
- } from "./chunk-RC23JQKI.js";
3
+ } from "./chunk-WULBOSG5.js";
4
4
  import {
5
5
  GraphError
6
- } from "./chunk-VYWPQKMS.js";
6
+ } from "./chunk-C3NZIFW6.js";
7
7
 
8
8
  // src/core/semantic/symbol-graph.ts
9
9
  import { createHash } from "crypto";
10
10
  import { mkdir, readFile, writeFile } from "fs/promises";
11
11
  import path from "path";
12
- var SYMBOL_GRAPH_VERSION = 1;
12
+ var SYMBOL_GRAPH_VERSION = 6;
13
13
  var GRAPH_FILENAME = "symbols.json";
14
14
  var DEFAULT_INCLUDE = ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"];
15
+ var JAVASCRIPT_INCLUDE = ["**/*.js", "**/*.jsx", "**/*.mjs", "**/*.cjs"];
16
+ var JAVASCRIPT_PATHSPECS = ["*.js", "*.jsx", "*.mjs", "*.cjs"];
17
+ var MAX_JAVASCRIPT_BYTES = 512 * 1024;
15
18
  var DEFAULT_IGNORE = [
16
- "node_modules/**",
19
+ "**/node_modules/**",
17
20
  ".git/**",
18
21
  ".variant/**",
19
- "dist/**",
20
- "**/*.d.ts"
22
+ "**/dist/**",
23
+ "**/*.d.ts",
24
+ "**/*.d.mts",
25
+ "**/*.d.cts"
21
26
  ];
27
+ var JAVASCRIPT_FILE = /\.(?:[cm]?js|jsx)$/;
28
+ function isJavaScript(file) {
29
+ return JAVASCRIPT_FILE.test(file);
30
+ }
31
+ async function listJavaScript(cwd, ignore, fg) {
32
+ const { listRepositoryFiles } = await import("./git-VGHUF2SY.js");
33
+ const tracked = await listRepositoryFiles(cwd, JAVASCRIPT_PATHSPECS);
34
+ if (tracked === null) {
35
+ return fg(JAVASCRIPT_INCLUDE, { cwd, onlyFiles: true, ignore });
36
+ }
37
+ const ignored = ignore.map(globToRegExp);
38
+ return tracked.filter(
39
+ (file) => !/\.min\.[cm]?js$/.test(file) && !ignored.some((pattern) => pattern.test(file))
40
+ );
41
+ }
42
+ function globToRegExp(pattern) {
43
+ const source = pattern.split("**/").map(
44
+ (part) => part.split("**").map(
45
+ (piece) => piece.split("*").map((text) => text.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")
46
+ ).join(".*")
47
+ ).join("(?:.*/)?");
48
+ return new RegExp(`^${source}$`);
49
+ }
22
50
  function defaultGraphDir(cwd) {
23
51
  return path.join(cwd, ".variant", "graph");
24
52
  }
@@ -26,11 +54,14 @@ async function buildSymbolGraph(cwd, options = {}) {
26
54
  const fg = (await import("fast-glob")).default;
27
55
  const include = options.include ?? DEFAULT_INCLUDE;
28
56
  for (const p of include) assertSafePattern(p);
29
- const files = (await fg(include, {
30
- cwd,
31
- onlyFiles: true,
32
- ignore: [...DEFAULT_IGNORE, ...options.ignore ?? []]
33
- })).map(toPosix).sort();
57
+ const ignore = [...DEFAULT_IGNORE, ...options.ignore ?? []];
58
+ const [typescript, javascript] = await Promise.all([
59
+ fg(include, { cwd, onlyFiles: true, ignore }),
60
+ options.include === void 0 ? listJavaScript(cwd, ignore, fg) : []
61
+ ]);
62
+ const files = [
63
+ ...new Set([...typescript, ...javascript].map(toPosix))
64
+ ].sort();
34
65
  const previous = options.previous?.version === SYMBOL_GRAPH_VERSION ? options.previous : null;
35
66
  const stats = {
36
67
  scanned: files.length,
@@ -40,6 +71,7 @@ async function buildSymbolGraph(cwd, options = {}) {
40
71
  };
41
72
  const contents = new Array(files.length);
42
73
  const limit = Math.max(1, options.parallel ?? 32);
74
+ const read = options.readFile ?? ((absolutePath) => readFile(absolutePath, "utf8"));
43
75
  let next = 0;
44
76
  const worker = async () => {
45
77
  while (true) {
@@ -47,7 +79,15 @@ async function buildSymbolGraph(cwd, options = {}) {
47
79
  if (i >= files.length) return;
48
80
  const relPath = files[i];
49
81
  if (relPath === void 0) return;
50
- contents[i] = await readFile(path.join(cwd, relPath), "utf8");
82
+ try {
83
+ const content = await read(path.join(cwd, relPath));
84
+ if (isJavaScript(relPath) && content.length > MAX_JAVASCRIPT_BYTES) {
85
+ continue;
86
+ }
87
+ contents[i] = content;
88
+ } catch (err) {
89
+ if (err.code !== "ENOENT") throw err;
90
+ }
51
91
  }
52
92
  };
53
93
  await Promise.all(
@@ -69,12 +109,19 @@ async function buildSymbolGraph(cwd, options = {}) {
69
109
  }
70
110
  if (tsm === void 0 || project === void 0) {
71
111
  tsm = await import("ts-morph");
72
- project = new tsm.Project({ useInMemoryFileSystem: true });
112
+ project = new tsm.Project({
113
+ useInMemoryFileSystem: true,
114
+ compilerOptions: { allowJs: true }
115
+ });
73
116
  }
74
117
  const sourceFile = project.createSourceFile(relPath, content, {
75
118
  overwrite: true
76
119
  });
77
- out[relPath] = indexSourceFile(sourceFile, contentHash, tsm);
120
+ try {
121
+ out[relPath] = indexSourceFile(sourceFile, contentHash, tsm);
122
+ } finally {
123
+ project.removeSourceFile(sourceFile);
124
+ }
78
125
  stats.parsed++;
79
126
  }
80
127
  if (previous !== null) {
@@ -188,24 +235,72 @@ function collectImports(file, tsm) {
188
235
  names: named.length > 0 ? named : ["*"]
189
236
  });
190
237
  }
191
- for (const call of file.getDescendantsOfKind(tsm.SyntaxKind.CallExpression)) {
192
- if (call.getExpression().getKind() !== tsm.SyntaxKind.ImportKeyword) {
238
+ for (const decl of file.getDescendantsOfKind(
239
+ tsm.SyntaxKind.ImportEqualsDeclaration
240
+ )) {
241
+ const reference = decl.getModuleReference();
242
+ if (!tsm.Node.isExternalModuleReference(reference)) continue;
243
+ const expression = reference.getExpression();
244
+ if (expression === void 0 || !tsm.Node.isStringLiteral(expression)) {
193
245
  continue;
194
246
  }
247
+ imports.push({
248
+ module: expression.getLiteralValue(),
249
+ kind: "static",
250
+ typeOnly: decl.isTypeOnly(),
251
+ names: ["*"]
252
+ });
253
+ }
254
+ for (const call of file.getDescendantsOfKind(tsm.SyntaxKind.CallExpression)) {
255
+ const callee = call.getExpression();
256
+ const isImport = callee.getKind() === tsm.SyntaxKind.ImportKeyword;
257
+ const isRequire = tsm.Node.isIdentifier(callee) && callee.getText() === "require";
258
+ if (!isImport && !isRequire) continue;
195
259
  const arg = call.getArguments()[0];
196
- if (arg !== void 0 && tsm.Node.isStringLiteral(arg)) {
197
- imports.push({
198
- module: arg.getLiteralValue(),
199
- kind: "dynamic",
200
- typeOnly: false,
201
- names: []
202
- });
203
- } else {
204
- notes.push("dynamic import() with a non-literal specifier");
260
+ if (arg !== void 0 && (tsm.Node.isStringLiteral(arg) || tsm.Node.isNoSubstitutionTemplateLiteral(arg))) {
261
+ imports.push(
262
+ isImport ? {
263
+ module: arg.getLiteralValue(),
264
+ kind: "dynamic",
265
+ typeOnly: false,
266
+ names: []
267
+ } : {
268
+ module: arg.getLiteralValue(),
269
+ kind: "static",
270
+ typeOnly: false,
271
+ names: ["*"]
272
+ }
273
+ );
274
+ continue;
275
+ }
276
+ const prefix = arg === void 0 ? "" : staticPrefix(arg, tsm);
277
+ imports.push({
278
+ module: prefix,
279
+ kind: "pattern",
280
+ typeOnly: false,
281
+ names: []
282
+ });
283
+ if (prefix === "") {
284
+ notes.push(
285
+ `${isImport ? "dynamic import()" : "require()"} with a non-literal specifier`
286
+ );
205
287
  }
206
288
  }
207
289
  return { imports, notes };
208
290
  }
291
+ function staticPrefix(arg, tsm) {
292
+ if (tsm.Node.isTemplateExpression(arg)) {
293
+ return arg.getHead().getLiteralText();
294
+ }
295
+ if (tsm.Node.isBinaryExpression(arg) && arg.getOperatorToken().getKind() === tsm.SyntaxKind.PlusToken) {
296
+ const left = arg.getLeft();
297
+ if (tsm.Node.isStringLiteral(left) || tsm.Node.isNoSubstitutionTemplateLiteral(left)) {
298
+ return left.getLiteralValue();
299
+ }
300
+ return staticPrefix(left, tsm);
301
+ }
302
+ return "";
303
+ }
209
304
  function sha256(text) {
210
305
  return createHash("sha256").update(text).digest("hex");
211
306
  }
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  import {
12
12
  CliUsageError,
13
13
  VariantError
14
- } from "./chunk-VYWPQKMS.js";
14
+ } from "./chunk-C3NZIFW6.js";
15
15
 
16
16
  // src/cli/index.ts
17
17
  import { readFileSync } from "fs";
@@ -71,6 +71,8 @@ var writeStderr = (text) => {
71
71
  function renderError(err, write = writeStderr) {
72
72
  const colors = getColors();
73
73
  write(`${colors.red(`[${err.code}]`)} ${err.message}
74
+ `);
75
+ for (const line of err.details ?? []) write(`${colors.dim(` ${line}`)}
74
76
  `);
75
77
  if (err.provenance) {
76
78
  for (const line of provenanceLines(err.provenance)) {
@@ -118,21 +120,54 @@ var program = new Command().name("variant").description(
118
120
  })
119
121
  );
120
122
  });
123
+ program.command("check").description(
124
+ "Mutate the lines you changed and report the mutations your tests did not notice"
125
+ ).option(
126
+ "--base <ref>",
127
+ "git ref to compare against (default: HEAD for uncommitted changes, else detected)"
128
+ ).option("--max-mutants <n>", "mutations to run at most (default: 50)").option(
129
+ "--budget <time>",
130
+ "stop after this long, baseline included: 300, 90s or 5m (default: 5m)"
131
+ ).option("--vitest-config <file>", "the Vitest config to run the tests with").option(
132
+ "--fail-on-survivors",
133
+ "exit 1 when a mutation went undetected or unreached, or the run is incomplete"
134
+ ).option("--json", "output the result as JSON").action(
135
+ async (opts) => {
136
+ const { registerCheckAction } = await import("./check-AKYABBIV.js");
137
+ await registerCheckAction({
138
+ ...opts.base !== void 0 && { base: opts.base },
139
+ ...opts.maxMutants !== void 0 && { maxMutants: opts.maxMutants },
140
+ ...opts.budget !== void 0 && { budget: opts.budget },
141
+ ...opts.vitestConfig !== void 0 && {
142
+ vitestConfig: opts.vitestConfig
143
+ },
144
+ ...opts.failOnSurvivors === true && { failOnSurvivors: true },
145
+ ...opts.json === true && { json: true }
146
+ });
147
+ }
148
+ );
121
149
  var impactCmd = program.command("impact").description(
122
150
  "Predict which tests a change requires \u2014 report-only; the full suite should still run"
123
- ).option("--base <ref>", "git ref to compare against", "HEAD~1").option("--json", "output the report as JSON").action(async (opts) => {
124
- const { registerImpactAction } = await import("./impact-PRZ6HEP5.js");
151
+ ).option(
152
+ "--base <ref>",
153
+ "git ref to compare against (default: detected from git and CI)"
154
+ ).option(
155
+ "--head-sha <sha>",
156
+ "record the prediction against this commit instead of HEAD"
157
+ ).option("--json", "output the report as JSON").action(async (opts) => {
158
+ const { registerImpactAction } = await import("./impact-AXLZTLVT.js");
125
159
  await registerImpactAction({
126
- base: opts.base,
160
+ ...opts.base !== void 0 && { base: opts.base },
161
+ ...opts.headSha !== void 0 && { headSha: opts.headSha },
127
162
  ...opts.json === true && { json: true }
128
163
  });
129
164
  });
130
- impactCmd.command("verify <report>").description(
131
- "Reconcile a logged prediction against a Vitest/Jest JSON report \u2014 reports false skips"
132
- ).option("--head-sha <sha>", "reconcile the prediction made at this commit").option("--json", "output the reconciliation as JSON").action(async (report, _opts, command) => {
133
- const { registerImpactVerifyAction } = await import("./impact-PRZ6HEP5.js");
165
+ impactCmd.command("verify <report...>").description(
166
+ "Reconcile a logged prediction against one or more Vitest/Jest JSON reports \u2014 reports false skips"
167
+ ).option("--head-sha <sha>", "reconcile the prediction made at this commit").option("--json", "output the reconciliation as JSON").action(async (reports, _opts, command) => {
168
+ const { registerImpactVerifyAction } = await import("./impact-AXLZTLVT.js");
134
169
  const opts = command.optsWithGlobals();
135
- await registerImpactVerifyAction(report, {
170
+ await registerImpactVerifyAction(reports, {
136
171
  ...opts.headSha !== void 0 && { headSha: opts.headSha },
137
172
  ...opts.json === true && { json: true }
138
173
  });
@@ -140,14 +175,14 @@ impactCmd.command("verify <report>").description(
140
175
  program.command("diff <file>").description(
141
176
  "Classify a TypeScript file change as non-impacting / internal / breaking"
142
177
  ).option("--base <ref>", "git ref to compare against", "HEAD~1").action(async (file, opts) => {
143
- const { registerDiffAction } = await import("./diff-HSJBZFMC.js");
178
+ const { registerDiffAction } = await import("./diff-ST5KHT7P.js");
144
179
  await registerDiffAction(file, { base: opts.base });
145
180
  });
146
181
  var workspaceCmd = program.command("workspace").description("Workspace-level analysis commands");
147
182
  workspaceCmd.command("check").description(
148
183
  "Detect packages importing dependencies they do not declare (CI gate: exits 1 on violations)"
149
184
  ).option("--json", "output the result as JSON").action(async (opts) => {
150
- const { registerWorkspaceCheckAction } = await import("./workspace-HRTFGWKN.js");
185
+ const { registerWorkspaceCheckAction } = await import("./workspace-IQFLR3RY.js");
151
186
  await registerWorkspaceCheckAction({
152
187
  ...opts.json === true && { json: true }
153
188
  });
@@ -155,14 +190,28 @@ workspaceCmd.command("check").description(
155
190
  var prCmd = program.command("pr").description("PR-aware analysis commands");
156
191
  prCmd.command("check").description(
157
192
  "Classify all TypeScript changes in this PR and report a build verdict"
158
- ).option("--base <ref>", "base branch or ref for the PR diff", "main").action(async (opts) => {
159
- const { registerPrCheckAction } = await import("./pr-Y724QAHE.js");
160
- await registerPrCheckAction({ base: opts.base });
193
+ ).option(
194
+ "--base <ref>",
195
+ "base branch or ref for the PR diff (default: the target or default branch)"
196
+ ).action(async (opts) => {
197
+ const { registerPrCheckAction } = await import("./pr-7JXUC5BQ.js");
198
+ await registerPrCheckAction({
199
+ ...opts.base !== void 0 && { base: opts.base }
200
+ });
161
201
  });
162
- prCmd.command("report").description("Render `pr check` as a structured JSON or markdown report").option("--base <ref>", "base branch or ref for the PR diff", "main").option("--markdown", "output a markdown summary instead of JSON").option("--output <file>", "write report to file instead of stdout").action(
202
+ prCmd.command("report").description(
203
+ "Describe what this PR changes and what must be verified before it merges"
204
+ ).option(
205
+ "--base <ref>",
206
+ "base branch or ref for the PR diff (default: the target or default branch)"
207
+ ).option("--markdown", "output a markdown summary instead of JSON").option("--output <file>", "write report to file instead of stdout").action(
163
208
  async (opts) => {
164
- const { registerPrReportAction } = await import("./pr-Y724QAHE.js");
165
- await registerPrReportAction(opts);
209
+ const { registerPrReportAction } = await import("./pr-7JXUC5BQ.js");
210
+ await registerPrReportAction({
211
+ ...opts.base !== void 0 && { base: opts.base },
212
+ ...opts.markdown === true && { markdown: true },
213
+ ...opts.output !== void 0 && { output: opts.output }
214
+ });
166
215
  }
167
216
  );
168
217
  program.command("build").description("Run the build task").option("-c, --concurrency <n>", "max tasks per DAG level").option(
@@ -170,7 +219,7 @@ program.command("build").description("Run the build task").option("-c, --concurr
170
219
  "run only packages affected by changes since baseRef (includes cascade dependents)"
171
220
  ).option("--dry-run", "print the task plan without executing").action(
172
221
  async (opts) => {
173
- const { registerBuildAction } = await import("./build-3DSBMMI3.js");
222
+ const { registerBuildAction } = await import("./build-GCUFPPIB.js");
174
223
  const concurrency = parseConcurrency(opts);
175
224
  await registerBuildAction({
176
225
  ...concurrency !== void 0 && { concurrency },
@@ -184,7 +233,7 @@ program.command("run <task>").description("Run a named task").option(
184
233
  "max tasks to run concurrently per DAG level"
185
234
  ).option("--dry-run", "print the task plan without executing").action(
186
235
  async (taskName, opts) => {
187
- const { registerRunAction } = await import("./run-C6VAQOVM.js");
236
+ const { registerRunAction } = await import("./run-UOPS5FRU.js");
188
237
  const concurrency = parseConcurrency(opts);
189
238
  await registerRunAction(taskName, {
190
239
  ...concurrency !== void 0 && { concurrency },
@@ -192,28 +241,38 @@ program.command("run <task>").description("Run a named task").option(
192
241
  });
193
242
  }
194
243
  );
195
- program.command("init").description("Scaffold variant.config.ts in the current directory").action(async () => {
196
- const { registerInitAction } = await import("./init-O2BY3FOY.js");
197
- await registerInitAction();
198
- });
244
+ program.command("init").description(
245
+ "Set variant up in this repository: detect how it is tested, show the changes, apply them"
246
+ ).option("--dry-run", "show what would change and stop").option("-y, --yes", "apply without asking").option("--no-install", "do not install @blzsky/variant; print the command").option(
247
+ "--package <spec>",
248
+ "install this in place of the running version (a tarball path or a spec)"
249
+ ).option("--tasks", "write variant.config.ts for the task runner instead").action(
250
+ async (opts) => {
251
+ const { registerInitAction } = await import("./init-QCOZWLOO.js");
252
+ await registerInitAction({
253
+ version: _pkg.version,
254
+ ...opts.dryRun === true && { dryRun: true },
255
+ ...opts.yes === true && { yes: true },
256
+ ...opts.install === false && { install: false },
257
+ ...opts.package !== void 0 && { package: opts.package },
258
+ ...opts.tasks === true && { tasks: true }
259
+ });
260
+ }
261
+ );
199
262
  program.command("doctor").description("Validate config and diagnose common issues").action(async () => {
200
- const { registerDoctorAction } = await import("./doctor-B7MHFIYJ.js");
263
+ const { registerDoctorAction } = await import("./doctor-OEIY4Y5X.js");
201
264
  await registerDoctorAction();
202
265
  });
203
266
  program.command("insight").description("Show task timing and cache hit stats").action(async () => {
204
- const { registerInsightAction } = await import("./insight-JQLTGFYB.js");
267
+ const { registerInsightAction } = await import("./insight-KRWT6Z47.js");
205
268
  await registerInsightAction();
206
269
  });
207
270
  program.command("env").description(
208
271
  "Show detected environment (runtime, package manager, framework)"
209
272
  ).action(async () => {
210
- const { registerEnvAction } = await import("./env-I7JZRWDN.js");
273
+ const { registerEnvAction } = await import("./env-EQAOZCFR.js");
211
274
  await registerEnvAction();
212
275
  });
213
- program.command("check").description("Validate config and task graph (no execution)").action(async () => {
214
- const { registerCheckAction } = await import("./check-SIQJU6Z2.js");
215
- await registerCheckAction();
216
- });
217
276
  for (const stream of [process.stdout, process.stderr]) {
218
277
  stream.on("error", (error) => {
219
278
  if (error.code !== "EPIPE" && error.code !== "EOF") throw error;