@blzsky/variant 0.2.0 → 0.2.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 (29) hide show
  1. package/README.md +197 -146
  2. package/dist/{build-2LO7GD2S.js → build-3DSBMMI3.js} +9 -9
  3. package/dist/{check-ZTKGGF4M.js → check-SIQJU6Z2.js} +8 -8
  4. package/dist/{chunk-RGD422OM.js → chunk-26DHEXGS.js} +1 -1
  5. package/dist/{chunk-LXKLCLL6.js → chunk-43VLELTL.js} +1 -1
  6. package/dist/{chunk-XUBWCTJZ.js → chunk-6BXSZPHC.js} +18 -0
  7. package/dist/{chunk-MZZOE7L4.js → chunk-7OPTWU6E.js} +1 -1
  8. package/dist/{chunk-V22IPJ7A.js → chunk-EISTWXDW.js} +5 -1
  9. package/dist/{chunk-4LLK2S4R.js → chunk-FA64KWJ2.js} +8 -8
  10. package/dist/{chunk-LJDDS4RR.js → chunk-FZEXZBXB.js} +16 -2
  11. package/dist/{chunk-NSNZOH5P.js → chunk-Q7RLUZSN.js} +2 -2
  12. package/dist/{chunk-ISRF5F3Q.js → chunk-QRPI6TJE.js} +1 -1
  13. package/dist/{chunk-O4LZKXNZ.js → chunk-RC23JQKI.js} +50 -1
  14. package/dist/{chunk-VTJ67TT5.js → chunk-TCISS2AS.js} +4 -4
  15. package/dist/{chunk-RVXUCJYA.js → chunk-VYWPQKMS.js} +7 -0
  16. package/dist/{chunk-SWPVR2DF.js → chunk-ZH2PQUNL.js} +7 -7
  17. package/dist/cli.js +30 -26
  18. package/dist/{diff-GFI6RAEA.js → diff-HSJBZFMC.js} +9 -5
  19. package/dist/{doctor-GH4RZVEH.js → doctor-B7MHFIYJ.js} +3 -3
  20. package/dist/{env-QORDD6FQ.js → env-I7JZRWDN.js} +7 -7
  21. package/dist/{impact-AF453PLK.js → impact-PRZ6HEP5.js} +54 -21
  22. package/dist/index.js +2 -2
  23. package/dist/{init-KJIUNGAN.js → init-O2BY3FOY.js} +3 -3
  24. package/dist/{insight-DZ5NQY6D.js → insight-JQLTGFYB.js} +8 -8
  25. package/dist/{pr-UFZRZX6S.js → pr-Y724QAHE.js} +19 -6
  26. package/dist/{run-6PNH4QRA.js → run-C6VAQOVM.js} +9 -9
  27. package/dist/{symbol-graph-YYUEMZGR.js → symbol-graph-E5GXKDFZ.js} +3 -3
  28. package/dist/{workspace-X5FUBTY3.js → workspace-HRTFGWKN.js} +4 -4
  29. package/package.json +3 -3
@@ -276,6 +276,7 @@ function inferTypeText(get) {
276
276
  }
277
277
  function significantText(src, tsm) {
278
278
  if (src.trim() === "") return "";
279
+ const { SyntaxKind } = tsm.ts;
279
280
  const scanner = tsm.ts.createScanner(
280
281
  tsm.ts.ScriptTarget.Latest,
281
282
  /* skipTrivia */
@@ -284,13 +285,61 @@ function significantText(src, tsm) {
284
285
  src
285
286
  );
286
287
  const parts = [];
288
+ const substitutionBraces = [];
289
+ let previous = SyntaxKind.Unknown;
287
290
  let kind = scanner.scan();
288
- while (kind !== tsm.ts.SyntaxKind.EndOfFileToken) {
291
+ while (kind !== SyntaxKind.EndOfFileToken) {
292
+ if (kind === SyntaxKind.CloseBraceToken && substitutionBraces.at(-1) === 0) {
293
+ substitutionBraces.pop();
294
+ kind = scanner.reScanTemplateToken(
295
+ /* isTaggedTemplate */
296
+ false
297
+ );
298
+ } else if (kind === SyntaxKind.OpenBraceToken || kind === SyntaxKind.CloseBraceToken) {
299
+ const depth = substitutionBraces.pop();
300
+ if (depth !== void 0) {
301
+ substitutionBraces.push(
302
+ kind === SyntaxKind.OpenBraceToken ? depth + 1 : depth - 1
303
+ );
304
+ }
305
+ } else if ((kind === SyntaxKind.SlashToken || kind === SyntaxKind.SlashEqualsToken) && !endsOperand(previous, tsm)) {
306
+ kind = scanner.reScanSlashToken();
307
+ }
308
+ if (kind === SyntaxKind.TemplateHead || kind === SyntaxKind.TemplateMiddle) {
309
+ substitutionBraces.push(0);
310
+ }
289
311
  parts.push(scanner.getTokenText());
312
+ previous = kind;
290
313
  kind = scanner.scan();
291
314
  }
292
315
  return parts.join(" ");
293
316
  }
317
+ function endsOperand(kind, tsm) {
318
+ const { SyntaxKind } = tsm.ts;
319
+ switch (kind) {
320
+ case SyntaxKind.Identifier:
321
+ case SyntaxKind.PrivateIdentifier:
322
+ case SyntaxKind.NumericLiteral:
323
+ case SyntaxKind.BigIntLiteral:
324
+ case SyntaxKind.StringLiteral:
325
+ case SyntaxKind.NoSubstitutionTemplateLiteral:
326
+ case SyntaxKind.TemplateTail:
327
+ case SyntaxKind.RegularExpressionLiteral:
328
+ case SyntaxKind.CloseParenToken:
329
+ case SyntaxKind.CloseBracketToken:
330
+ case SyntaxKind.CloseBraceToken:
331
+ case SyntaxKind.PlusPlusToken:
332
+ case SyntaxKind.MinusMinusToken:
333
+ case SyntaxKind.ThisKeyword:
334
+ case SyntaxKind.SuperKeyword:
335
+ case SyntaxKind.TrueKeyword:
336
+ case SyntaxKind.FalseKeyword:
337
+ case SyntaxKind.NullKeyword:
338
+ return true;
339
+ default:
340
+ return false;
341
+ }
342
+ }
294
343
  function normalize(text) {
295
344
  return text.replace(/\s+/g, " ").trim();
296
345
  }
@@ -1,9 +1,9 @@
1
- import {
2
- GraphError
3
- } from "./chunk-RVXUCJYA.js";
4
1
  import {
5
2
  collectExportedSurface
6
- } from "./chunk-O4LZKXNZ.js";
3
+ } from "./chunk-RC23JQKI.js";
4
+ import {
5
+ GraphError
6
+ } from "./chunk-VYWPQKMS.js";
7
7
 
8
8
  // src/core/semantic/symbol-graph.ts
9
9
  import { createHash } from "crypto";
@@ -56,6 +56,12 @@ var GraphError = class extends VariantError {
56
56
  this.hint = "Delete `.variant/graph/` and retry.";
57
57
  }
58
58
  };
59
+ var GitRefError = class extends VariantError {
60
+ constructor(message) {
61
+ super("GIT_REF_ERROR", message);
62
+ this.hint = "Check the ref with `git rev-parse --verify <ref>`. In CI, check out with `fetch-depth: 0` and pass a remote-tracking ref such as `origin/main`.";
63
+ }
64
+ };
59
65
  var ImpactReportError = class extends VariantError {
60
66
  constructor(message) {
61
67
  super("IMPACT_REPORT_ERROR", `Could not read the test report: ${message}`);
@@ -76,6 +82,7 @@ export {
76
82
  TaskExecutionError,
77
83
  CacheError,
78
84
  GraphError,
85
+ GitRefError,
79
86
  ImpactReportError,
80
87
  CliUsageError
81
88
  };
@@ -7,29 +7,29 @@ import {
7
7
  } from "./chunk-V5CTGXMM.js";
8
8
  import {
9
9
  loadConfig
10
- } from "./chunk-RGD422OM.js";
10
+ } from "./chunk-26DHEXGS.js";
11
11
  import {
12
12
  getColors
13
13
  } from "./chunk-YYGCSKIQ.js";
14
14
  import {
15
15
  changedFilesToPackages,
16
16
  getChangedFiles
17
- } from "./chunk-MZZOE7L4.js";
17
+ } from "./chunk-7OPTWU6E.js";
18
18
  import {
19
19
  computeAffectedPackages,
20
20
  loadPackageGraph,
21
21
  tasksFromPackageGraph
22
22
  } from "./chunk-3E5LBU3O.js";
23
- import {
24
- ConfigError,
25
- CycleError
26
- } from "./chunk-RVXUCJYA.js";
27
23
  import {
28
24
  errorLines
29
25
  } from "./chunk-6J4XDG44.js";
30
26
  import {
31
27
  getPrinter
32
- } from "./chunk-V22IPJ7A.js";
28
+ } from "./chunk-EISTWXDW.js";
29
+ import {
30
+ ConfigError,
31
+ CycleError
32
+ } from "./chunk-VYWPQKMS.js";
33
33
 
34
34
  // src/adapters/frameworks/plugin.ts
35
35
  var wrapFrameworkAsPlugin = (a) => ({
package/dist/cli.js CHANGED
@@ -4,14 +4,14 @@ import {
4
4
  resolveColorChoice,
5
5
  writeGlobalColorChoice
6
6
  } from "./chunk-YYGCSKIQ.js";
7
- import {
8
- CliUsageError,
9
- VariantError
10
- } from "./chunk-RVXUCJYA.js";
11
7
  import {
12
8
  Printer,
13
9
  setGlobalPrinter
14
- } from "./chunk-V22IPJ7A.js";
10
+ } from "./chunk-EISTWXDW.js";
11
+ import {
12
+ CliUsageError,
13
+ VariantError
14
+ } from "./chunk-VYWPQKMS.js";
15
15
 
16
16
  // src/cli/index.ts
17
17
  import { readFileSync } from "fs";
@@ -121,7 +121,7 @@ var program = new Command().name("variant").description(
121
121
  var impactCmd = program.command("impact").description(
122
122
  "Predict which tests a change requires \u2014 report-only; the full suite should still run"
123
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-AF453PLK.js");
124
+ const { registerImpactAction } = await import("./impact-PRZ6HEP5.js");
125
125
  await registerImpactAction({
126
126
  base: opts.base,
127
127
  ...opts.json === true && { json: true }
@@ -129,26 +129,25 @@ var impactCmd = program.command("impact").description(
129
129
  });
130
130
  impactCmd.command("verify <report>").description(
131
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(
133
- async (report, opts) => {
134
- const { registerImpactVerifyAction } = await import("./impact-AF453PLK.js");
135
- await registerImpactVerifyAction(report, {
136
- ...opts.headSha !== void 0 && { headSha: opts.headSha },
137
- ...opts.json === true && { json: true }
138
- });
139
- }
140
- );
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");
134
+ const opts = command.optsWithGlobals();
135
+ await registerImpactVerifyAction(report, {
136
+ ...opts.headSha !== void 0 && { headSha: opts.headSha },
137
+ ...opts.json === true && { json: true }
138
+ });
139
+ });
141
140
  program.command("diff <file>").description(
142
141
  "Classify a TypeScript file change as non-impacting / internal / breaking"
143
142
  ).option("--base <ref>", "git ref to compare against", "HEAD~1").action(async (file, opts) => {
144
- const { registerDiffAction } = await import("./diff-GFI6RAEA.js");
143
+ const { registerDiffAction } = await import("./diff-HSJBZFMC.js");
145
144
  await registerDiffAction(file, { base: opts.base });
146
145
  });
147
146
  var workspaceCmd = program.command("workspace").description("Workspace-level analysis commands");
148
147
  workspaceCmd.command("check").description(
149
148
  "Detect packages importing dependencies they do not declare (CI gate: exits 1 on violations)"
150
149
  ).option("--json", "output the result as JSON").action(async (opts) => {
151
- const { registerWorkspaceCheckAction } = await import("./workspace-X5FUBTY3.js");
150
+ const { registerWorkspaceCheckAction } = await import("./workspace-HRTFGWKN.js");
152
151
  await registerWorkspaceCheckAction({
153
152
  ...opts.json === true && { json: true }
154
153
  });
@@ -157,12 +156,12 @@ var prCmd = program.command("pr").description("PR-aware analysis commands");
157
156
  prCmd.command("check").description(
158
157
  "Classify all TypeScript changes in this PR and report a build verdict"
159
158
  ).option("--base <ref>", "base branch or ref for the PR diff", "main").action(async (opts) => {
160
- const { registerPrCheckAction } = await import("./pr-UFZRZX6S.js");
159
+ const { registerPrCheckAction } = await import("./pr-Y724QAHE.js");
161
160
  await registerPrCheckAction({ base: opts.base });
162
161
  });
163
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(
164
163
  async (opts) => {
165
- const { registerPrReportAction } = await import("./pr-UFZRZX6S.js");
164
+ const { registerPrReportAction } = await import("./pr-Y724QAHE.js");
166
165
  await registerPrReportAction(opts);
167
166
  }
168
167
  );
@@ -171,7 +170,7 @@ program.command("build").description("Run the build task").option("-c, --concurr
171
170
  "run only packages affected by changes since baseRef (includes cascade dependents)"
172
171
  ).option("--dry-run", "print the task plan without executing").action(
173
172
  async (opts) => {
174
- const { registerBuildAction } = await import("./build-2LO7GD2S.js");
173
+ const { registerBuildAction } = await import("./build-3DSBMMI3.js");
175
174
  const concurrency = parseConcurrency(opts);
176
175
  await registerBuildAction({
177
176
  ...concurrency !== void 0 && { concurrency },
@@ -185,7 +184,7 @@ program.command("run <task>").description("Run a named task").option(
185
184
  "max tasks to run concurrently per DAG level"
186
185
  ).option("--dry-run", "print the task plan without executing").action(
187
186
  async (taskName, opts) => {
188
- const { registerRunAction } = await import("./run-6PNH4QRA.js");
187
+ const { registerRunAction } = await import("./run-C6VAQOVM.js");
189
188
  const concurrency = parseConcurrency(opts);
190
189
  await registerRunAction(taskName, {
191
190
  ...concurrency !== void 0 && { concurrency },
@@ -194,27 +193,32 @@ program.command("run <task>").description("Run a named task").option(
194
193
  }
195
194
  );
196
195
  program.command("init").description("Scaffold variant.config.ts in the current directory").action(async () => {
197
- const { registerInitAction } = await import("./init-KJIUNGAN.js");
196
+ const { registerInitAction } = await import("./init-O2BY3FOY.js");
198
197
  await registerInitAction();
199
198
  });
200
199
  program.command("doctor").description("Validate config and diagnose common issues").action(async () => {
201
- const { registerDoctorAction } = await import("./doctor-GH4RZVEH.js");
200
+ const { registerDoctorAction } = await import("./doctor-B7MHFIYJ.js");
202
201
  await registerDoctorAction();
203
202
  });
204
203
  program.command("insight").description("Show task timing and cache hit stats").action(async () => {
205
- const { registerInsightAction } = await import("./insight-DZ5NQY6D.js");
204
+ const { registerInsightAction } = await import("./insight-JQLTGFYB.js");
206
205
  await registerInsightAction();
207
206
  });
208
207
  program.command("env").description(
209
208
  "Show detected environment (runtime, package manager, framework)"
210
209
  ).action(async () => {
211
- const { registerEnvAction } = await import("./env-QORDD6FQ.js");
210
+ const { registerEnvAction } = await import("./env-I7JZRWDN.js");
212
211
  await registerEnvAction();
213
212
  });
214
213
  program.command("check").description("Validate config and task graph (no execution)").action(async () => {
215
- const { registerCheckAction } = await import("./check-ZTKGGF4M.js");
214
+ const { registerCheckAction } = await import("./check-SIQJU6Z2.js");
216
215
  await registerCheckAction();
217
216
  });
217
+ for (const stream of [process.stdout, process.stderr]) {
218
+ stream.on("error", (error) => {
219
+ if (error.code !== "EPIPE" && error.code !== "EOF") throw error;
220
+ });
221
+ }
218
222
  program.parseAsync(process.argv).catch((err) => {
219
223
  if (err instanceof VariantError) {
220
224
  renderError(err);
@@ -2,16 +2,19 @@ import {
2
2
  DEFAULT_DIFF_BASE_REF,
3
3
  classifyFileAgainstRef,
4
4
  toWorkspaceRelative
5
- } from "./chunk-NSNZOH5P.js";
6
- import "./chunk-LJDDS4RR.js";
7
- import "./chunk-XUBWCTJZ.js";
5
+ } from "./chunk-Q7RLUZSN.js";
6
+ import {
7
+ assertBaseRef
8
+ } from "./chunk-FZEXZBXB.js";
9
+ import "./chunk-6BXSZPHC.js";
8
10
  import {
9
11
  lines
10
12
  } from "./chunk-6J4XDG44.js";
11
13
  import {
12
14
  getPrinter
13
- } from "./chunk-V22IPJ7A.js";
14
- import "./chunk-O4LZKXNZ.js";
15
+ } from "./chunk-EISTWXDW.js";
16
+ import "./chunk-RC23JQKI.js";
17
+ import "./chunk-VYWPQKMS.js";
15
18
 
16
19
  // src/cli/render/diff.ts
17
20
  var CLASS_MEANING = {
@@ -50,6 +53,7 @@ async function registerDiffAction(filePath, opts = {}) {
50
53
  const cwd = process.cwd();
51
54
  const baseRef = opts.base ?? DEFAULT_DIFF_BASE_REF;
52
55
  const relPath = toWorkspaceRelative(cwd, filePath);
56
+ await assertBaseRef(cwd, baseRef);
53
57
  renderClassification(
54
58
  await classifyFileAgainstRef(cwd, relPath, baseRef),
55
59
  baseRef
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  findVariantConfigPath,
3
3
  loadConfig
4
- } from "./chunk-RGD422OM.js";
4
+ } from "./chunk-26DHEXGS.js";
5
5
  import {
6
6
  getColors
7
7
  } from "./chunk-YYGCSKIQ.js";
8
- import "./chunk-RVXUCJYA.js";
9
8
  import {
10
9
  lines
11
10
  } from "./chunk-6J4XDG44.js";
12
11
  import {
13
12
  getPrinter
14
- } from "./chunk-V22IPJ7A.js";
13
+ } from "./chunk-EISTWXDW.js";
14
+ import "./chunk-VYWPQKMS.js";
15
15
 
16
16
  // src/core/doctor/diagnostics.ts
17
17
  import { readdir, stat } from "fs/promises";
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  renderEnv
3
- } from "./chunk-ISRF5F3Q.js";
3
+ } from "./chunk-QRPI6TJE.js";
4
4
  import {
5
5
  createContext
6
- } from "./chunk-SWPVR2DF.js";
6
+ } from "./chunk-ZH2PQUNL.js";
7
7
  import "./chunk-V5CTGXMM.js";
8
- import "./chunk-RGD422OM.js";
8
+ import "./chunk-26DHEXGS.js";
9
9
  import "./chunk-YYGCSKIQ.js";
10
- import "./chunk-MZZOE7L4.js";
10
+ import "./chunk-7OPTWU6E.js";
11
11
  import "./chunk-3E5LBU3O.js";
12
- import "./chunk-RVXUCJYA.js";
13
- import "./chunk-XUBWCTJZ.js";
12
+ import "./chunk-6BXSZPHC.js";
14
13
  import "./chunk-6J4XDG44.js";
15
- import "./chunk-V22IPJ7A.js";
14
+ import "./chunk-EISTWXDW.js";
15
+ import "./chunk-VYWPQKMS.js";
16
16
 
17
17
  // src/cli/commands/env.ts
18
18
  async function registerEnvAction() {
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-GDSZFIHM.js";
4
4
  import {
5
5
  getChangedFiles
6
- } from "./chunk-MZZOE7L4.js";
6
+ } from "./chunk-7OPTWU6E.js";
7
7
  import {
8
8
  buildImportGraph,
9
9
  computeAffectedFiles
@@ -16,22 +16,20 @@ import {
16
16
  } from "./chunk-KSHWC2XN.js";
17
17
  import {
18
18
  updateSymbolGraph
19
- } from "./chunk-VTJ67TT5.js";
20
- import {
21
- ImpactReportError
22
- } from "./chunk-RVXUCJYA.js";
19
+ } from "./chunk-TCISS2AS.js";
23
20
  import {
24
21
  deriveVerdict,
25
22
  verdictText
26
23
  } from "./chunk-DRMASVYN.js";
27
24
  import {
25
+ assertBaseRef,
28
26
  createClassifier
29
- } from "./chunk-LJDDS4RR.js";
27
+ } from "./chunk-FZEXZBXB.js";
30
28
  import {
31
29
  readFileAtRef,
32
30
  readFilesAtRef,
33
31
  readHeadSha
34
- } from "./chunk-XUBWCTJZ.js";
32
+ } from "./chunk-6BXSZPHC.js";
35
33
  import {
36
34
  classificationLabel,
37
35
  plural
@@ -41,8 +39,12 @@ import {
41
39
  } from "./chunk-6J4XDG44.js";
42
40
  import {
43
41
  getPrinter
44
- } from "./chunk-V22IPJ7A.js";
45
- import "./chunk-O4LZKXNZ.js";
42
+ } from "./chunk-EISTWXDW.js";
43
+ import "./chunk-RC23JQKI.js";
44
+ import {
45
+ GitRefError,
46
+ ImpactReportError
47
+ } from "./chunk-VYWPQKMS.js";
46
48
 
47
49
  // src/core/history/impact-log.ts
48
50
  import { mkdir, readFile, writeFile } from "fs/promises";
@@ -293,6 +295,10 @@ function defaultIsTestFile(file) {
293
295
  return /(^|\/)__tests__\//.test(file) || /\.(test|spec)\.(ts|tsx|mts|cts)$/.test(file);
294
296
  }
295
297
  var TEST_CONFIG_FILE = /(^|\/)(package\.json|pnpm-lock\.yaml|package-lock\.json|yarn\.lock|tsconfig[^/]*\.json|(vitest|jest|playwright|vite)\.config\.[^/.]+(\.[^/]+)?)$/;
298
+ var TEST_SETUP_FILE = /(^|\/)((vitest|jest)\.setup|setup-?tests|global-?(setup|teardown)|tests?\/setup)\.[cm]?[jt]sx?$/i;
299
+ function invalidatesAllTests(filePath) {
300
+ return TEST_CONFIG_FILE.test(filePath) || TEST_SETUP_FILE.test(filePath);
301
+ }
296
302
  function buildTestTrace(graph, options = {}) {
297
303
  const isTest = options.isTestFile ?? defaultIsTestFile;
298
304
  const closures = /* @__PURE__ */ new Map();
@@ -322,7 +328,7 @@ function computeTestImpact(radius, graph, options = {}) {
322
328
  const notes = /* @__PURE__ */ new Set();
323
329
  let selectAll = false;
324
330
  for (const impact of radius.changed) {
325
- if (impact.classification === "unanalyzed" && TEST_CONFIG_FILE.test(impact.filePath)) {
331
+ if (invalidatesAllTests(impact.filePath)) {
326
332
  selectAll = true;
327
333
  notes.add(
328
334
  `${impact.filePath}: build/test configuration changed \u2014 running all tests`
@@ -418,6 +424,7 @@ function closureOf(start, graph) {
418
424
  var DEFAULT_IMPACT_BASE_REF = "HEAD~1";
419
425
  async function predictImpact(cwd, options = {}) {
420
426
  const baseRef = options.base ?? DEFAULT_IMPACT_BASE_REF;
427
+ if (options.changedFiles === void 0) await assertBaseRef(cwd, baseRef);
421
428
  const packageGraph = await loadPackageGraph(cwd).catch(() => void 0);
422
429
  const result = await traceTestImpact(cwd, {
423
430
  baseRef,
@@ -427,7 +434,11 @@ async function predictImpact(cwd, options = {}) {
427
434
  },
428
435
  ...options.readBefore !== void 0 && { readBefore: options.readBefore }
429
436
  });
430
- if (result === null) return null;
437
+ if (result === null) {
438
+ throw new GitRefError(
439
+ `Could not list the files changed since "${baseRef}"`
440
+ );
441
+ }
431
442
  const verdict = deriveVerdict(
432
443
  result.radius.changed.map((change) => change.classification),
433
444
  { forceBuild: result.tests.selectAll }
@@ -451,13 +462,30 @@ async function predictImpact(cwd, options = {}) {
451
462
  }
452
463
 
453
464
  // src/core/impact/verify.ts
465
+ import { realpathSync } from "fs";
454
466
  import { readFile as readFile3 } from "fs/promises";
455
467
  import path3 from "path";
456
- function toRelativePosix(cwd, testPath) {
457
- const relative = path3.isAbsolute(testPath) ? path3.relative(cwd, testPath) : testPath;
468
+ function realpathOrSelf(target) {
469
+ try {
470
+ return realpathSync.native(target);
471
+ } catch {
472
+ return target;
473
+ }
474
+ }
475
+ function escapes(relative) {
476
+ return relative === ".." || relative.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative);
477
+ }
478
+ function toRelativePosix(cwd, testPath, realpath) {
479
+ let relative = testPath;
480
+ if (path3.isAbsolute(testPath)) {
481
+ relative = path3.relative(cwd, testPath);
482
+ if (escapes(relative)) {
483
+ relative = path3.relative(realpath(cwd), realpath(testPath));
484
+ }
485
+ }
458
486
  return relative.replace(/\\/g, "/");
459
487
  }
460
- function parseFailedTests(cwd, raw) {
488
+ function parseFailedTests(cwd, raw, realpath = realpathOrSelf) {
461
489
  let parsed;
462
490
  try {
463
491
  parsed = JSON.parse(raw);
@@ -476,7 +504,7 @@ function parseFailedTests(cwd, raw) {
476
504
  if (typeof entry !== "object" || entry === null) continue;
477
505
  const { name, status } = entry;
478
506
  if (status !== "failed" || typeof name !== "string") continue;
479
- failed.push(toRelativePosix(cwd, name));
507
+ failed.push(toRelativePosix(cwd, name, realpath));
480
508
  }
481
509
  return [...new Set(failed)].sort();
482
510
  }
@@ -507,12 +535,22 @@ function selectPrediction(predictions, headSha) {
507
535
  const latest = predictions[predictions.length - 1];
508
536
  return latest === void 0 ? null : { prediction: latest, matchedBy: "most-recent" };
509
537
  }
538
+ async function readReport(cwd, reportPath) {
539
+ try {
540
+ return await readFile3(path3.resolve(cwd, reportPath), "utf8");
541
+ } catch (error) {
542
+ const code = error instanceof Error && "code" in error ? String(error.code) : "";
543
+ throw new ImpactReportError(
544
+ code === "ENOENT" ? `no file at ${reportPath}` : `${reportPath} could not be read${code === "" ? "" : ` (${code})`}`
545
+ );
546
+ }
547
+ }
510
548
  async function verifyImpact(cwd, reportPath, options = {}) {
511
549
  const historyDir = defaultHistoryDir(cwd);
512
550
  const predictions = options.predictions ?? await readImpactPredictions(historyDir);
513
551
  const match = selectPrediction(predictions, options.headSha);
514
552
  if (match === null) return null;
515
- const raw = await readFile3(path3.resolve(cwd, reportPath), "utf8");
553
+ const raw = await readReport(cwd, reportPath);
516
554
  const failedTests = parseFailedTests(cwd, raw);
517
555
  const { caught, falseSkips, falseSkipRate } = reconcile(
518
556
  match.prediction,
@@ -543,7 +581,6 @@ async function verifyImpact(cwd, reportPath, options = {}) {
543
581
  var MAX_LISTED_FILES = 20;
544
582
  var count = (value) => value.toLocaleString("en-US");
545
583
  var NO_PREDICTION_MESSAGE = "impact verify: no logged prediction to reconcile against. Run `variant impact` before the test run, or pass --head-sha for a specific commit.";
546
- var NO_CHANGED_FILES_MESSAGE = "impact: could not determine changed files (is this a git repository with at least one prior commit?). Pass --base <ref> against a valid ref.";
547
584
  function renderImpactJson(report, printer = getPrinter()) {
548
585
  const { result, ...rest } = report;
549
586
  lines(
@@ -672,10 +709,6 @@ function renderImpactVerify(result, printer = getPrinter()) {
672
709
  // src/cli/commands/impact.ts
673
710
  async function registerImpactAction(opts = {}) {
674
711
  const report = await predictImpact(process.cwd(), opts);
675
- if (report === null) {
676
- lines(getPrinter(), NO_CHANGED_FILES_MESSAGE);
677
- return;
678
- }
679
712
  if (opts.json === true) {
680
713
  renderImpactJson(report);
681
714
  return;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  defineConfig
3
- } from "./chunk-RGD422OM.js";
4
- import "./chunk-RVXUCJYA.js";
3
+ } from "./chunk-26DHEXGS.js";
4
+ import "./chunk-VYWPQKMS.js";
5
5
  export {
6
6
  defineConfig
7
7
  };
@@ -3,14 +3,14 @@ import {
3
3
  } from "./chunk-V5CTGXMM.js";
4
4
  import {
5
5
  findVariantConfigPath
6
- } from "./chunk-RGD422OM.js";
7
- import "./chunk-RVXUCJYA.js";
6
+ } from "./chunk-26DHEXGS.js";
8
7
  import {
9
8
  lines
10
9
  } from "./chunk-6J4XDG44.js";
11
10
  import {
12
11
  getPrinter
13
- } from "./chunk-V22IPJ7A.js";
12
+ } from "./chunk-EISTWXDW.js";
13
+ import "./chunk-VYWPQKMS.js";
14
14
 
15
15
  // src/cli/commands/init.ts
16
16
  import { writeFile } from "fs/promises";
@@ -1,22 +1,22 @@
1
1
  import {
2
2
  computeInsights,
3
3
  readCache
4
- } from "./chunk-LXKLCLL6.js";
4
+ } from "./chunk-43VLELTL.js";
5
5
  import {
6
6
  renderInsights
7
- } from "./chunk-ISRF5F3Q.js";
7
+ } from "./chunk-QRPI6TJE.js";
8
8
  import {
9
9
  createContext
10
- } from "./chunk-SWPVR2DF.js";
10
+ } from "./chunk-ZH2PQUNL.js";
11
11
  import "./chunk-V5CTGXMM.js";
12
- import "./chunk-RGD422OM.js";
12
+ import "./chunk-26DHEXGS.js";
13
13
  import "./chunk-YYGCSKIQ.js";
14
- import "./chunk-MZZOE7L4.js";
14
+ import "./chunk-7OPTWU6E.js";
15
15
  import "./chunk-3E5LBU3O.js";
16
- import "./chunk-RVXUCJYA.js";
17
- import "./chunk-XUBWCTJZ.js";
16
+ import "./chunk-6BXSZPHC.js";
18
17
  import "./chunk-6J4XDG44.js";
19
- import "./chunk-V22IPJ7A.js";
18
+ import "./chunk-EISTWXDW.js";
19
+ import "./chunk-VYWPQKMS.js";
20
20
 
21
21
  // src/cli/commands/insight.ts
22
22
  async function registerInsightAction() {
@@ -4,11 +4,13 @@ import {
4
4
  } from "./chunk-DRMASVYN.js";
5
5
  import {
6
6
  classifyFileAgainstRef
7
- } from "./chunk-NSNZOH5P.js";
8
- import "./chunk-LJDDS4RR.js";
7
+ } from "./chunk-Q7RLUZSN.js";
8
+ import {
9
+ assertBaseRef
10
+ } from "./chunk-FZEXZBXB.js";
9
11
  import {
10
12
  listChangedFilesSinceMergeBase
11
- } from "./chunk-XUBWCTJZ.js";
13
+ } from "./chunk-6BXSZPHC.js";
12
14
  import {
13
15
  classificationLabel
14
16
  } from "./chunk-CXNXZLDG.js";
@@ -17,8 +19,11 @@ import {
17
19
  } from "./chunk-6J4XDG44.js";
18
20
  import {
19
21
  getPrinter
20
- } from "./chunk-V22IPJ7A.js";
21
- import "./chunk-O4LZKXNZ.js";
22
+ } from "./chunk-EISTWXDW.js";
23
+ import "./chunk-RC23JQKI.js";
24
+ import {
25
+ GitRefError
26
+ } from "./chunk-VYWPQKMS.js";
22
27
 
23
28
  // src/cli/commands/pr.ts
24
29
  import { writeFile } from "fs/promises";
@@ -27,9 +32,17 @@ import path from "path";
27
32
  // src/core/pr/check.ts
28
33
  var DEFAULT_PR_BASE_REF = "main";
29
34
  var TS_FILE = /\.tsx?$/;
35
+ async function listBranchChanges(cwd, baseRef) {
36
+ await assertBaseRef(cwd, baseRef);
37
+ const changed = await listChangedFilesSinceMergeBase(cwd, baseRef);
38
+ if (changed === null) {
39
+ throw new GitRefError(`"${baseRef}" has no merge base with HEAD`);
40
+ }
41
+ return changed;
42
+ }
30
43
  async function runPrCheck(cwd, options = {}) {
31
44
  const baseRef = options.base ?? DEFAULT_PR_BASE_REF;
32
- const changed = options.changedFiles ?? await listChangedFilesSinceMergeBase(cwd, baseRef) ?? [];
45
+ const changed = options.changedFiles ?? await listBranchChanges(cwd, baseRef);
33
46
  const tsFiles = changed.filter((file) => TS_FILE.test(file));
34
47
  const files = [];
35
48
  for (const relPath of tsFiles) {