@blzsky/variant 0.2.0 → 0.3.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +241 -136
- package/dist/{build-2LO7GD2S.js → build-GKWNLU4Q.js} +11 -10
- package/dist/{check-ZTKGGF4M.js → check-KO5VY67Z.js} +9 -9
- package/dist/{chunk-4LLK2S4R.js → chunk-7NC7AO75.js} +19 -32
- package/dist/{chunk-RVXUCJYA.js → chunk-ACTY42RE.js} +27 -0
- package/dist/{chunk-SWPVR2DF.js → chunk-CN25SRKD.js} +9 -9
- package/dist/{chunk-LXKLCLL6.js → chunk-CWF6QXEW.js} +1 -1
- package/dist/{chunk-MZZOE7L4.js → chunk-DMDAJPFJ.js} +1 -1
- package/dist/{chunk-O4LZKXNZ.js → chunk-DSQDMFQ6.js} +59 -2
- package/dist/{chunk-V22IPJ7A.js → chunk-EISTWXDW.js} +5 -1
- package/dist/chunk-H5C4SE73.js +184 -0
- package/dist/chunk-IFFZ46YS.js +575 -0
- package/dist/chunk-IHXJ72XA.js +31 -0
- package/dist/chunk-KCEGA56S.js +670 -0
- package/dist/{chunk-LJDDS4RR.js → chunk-MAK3HG46.js} +54 -8
- package/dist/chunk-MT4BOAAV.js +167 -0
- package/dist/{chunk-VTJ67TT5.js → chunk-NFFXYYM6.js} +122 -27
- package/dist/{chunk-ISRF5F3Q.js → chunk-QRPI6TJE.js} +1 -1
- package/dist/{chunk-RGD422OM.js → chunk-RJBFCURK.js} +1 -1
- package/dist/{chunk-XUBWCTJZ.js → chunk-RSIOYQOQ.js} +85 -8
- package/dist/chunk-SGP5JX6I.js +377 -0
- package/dist/chunk-X3VVGTEV.js +268 -0
- package/dist/{chunk-NSNZOH5P.js → chunk-YII4TAKS.js} +10 -3
- package/dist/cli.js +76 -37
- package/dist/{diff-GFI6RAEA.js → diff-A3TQWWUJ.js} +9 -5
- package/dist/{doctor-GH4RZVEH.js → doctor-6ONS6LKY.js} +5 -5
- package/dist/{env-QORDD6FQ.js → env-XF2US3CB.js} +8 -8
- package/dist/git-W6N3WMYQ.js +24 -0
- package/dist/impact-DPZIAJR4.js +263 -0
- package/dist/index.js +2 -2
- package/dist/init-VNK5E4H3.js +777 -0
- package/dist/{insight-DZ5NQY6D.js → insight-LNYVMCAE.js} +9 -9
- package/dist/pr-6CYMM2XU.js +707 -0
- package/dist/{run-6PNH4QRA.js → run-44PKMOV5.js} +11 -10
- package/dist/{symbol-graph-YYUEMZGR.js → symbol-graph-OB4AJCLW.js} +3 -3
- package/dist/{tsconfig-paths-E3UEDGXZ.js → tsconfig-paths-SV3PULCW.js} +3 -1
- package/dist/vitest.cjs +629 -0
- package/dist/vitest.d.cts +181 -0
- package/dist/vitest.d.ts +181 -0
- package/dist/vitest.js +356 -0
- package/dist/workspace-PIQXG4ZD.js +65 -0
- package/package.json +15 -4
- package/dist/chunk-DRMASVYN.js +0 -25
- package/dist/chunk-KSHWC2XN.js +0 -78
- package/dist/chunk-XTYVVUWW.js +0 -279
- package/dist/impact-AF453PLK.js +0 -702
- package/dist/init-KJIUNGAN.js +0 -158
- package/dist/pr-UFZRZX6S.js +0 -152
- package/dist/workspace-X5FUBTY3.js +0 -186
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-
|
|
10
|
+
} from "./chunk-EISTWXDW.js";
|
|
11
|
+
import {
|
|
12
|
+
CliUsageError,
|
|
13
|
+
VariantError
|
|
14
|
+
} from "./chunk-ACTY42RE.js";
|
|
15
15
|
|
|
16
16
|
// src/cli/index.ts
|
|
17
17
|
import { readFileSync } from "fs";
|
|
@@ -120,35 +120,41 @@ var program = new Command().name("variant").description(
|
|
|
120
120
|
});
|
|
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
|
-
).option(
|
|
124
|
-
|
|
123
|
+
).option(
|
|
124
|
+
"--base <ref>",
|
|
125
|
+
"git ref to compare against (default: detected from git and CI)"
|
|
126
|
+
).option(
|
|
127
|
+
"--head-sha <sha>",
|
|
128
|
+
"record the prediction against this commit instead of HEAD"
|
|
129
|
+
).option("--json", "output the report as JSON").action(async (opts) => {
|
|
130
|
+
const { registerImpactAction } = await import("./impact-DPZIAJR4.js");
|
|
125
131
|
await registerImpactAction({
|
|
126
|
-
base: opts.base,
|
|
132
|
+
...opts.base !== void 0 && { base: opts.base },
|
|
133
|
+
...opts.headSha !== void 0 && { headSha: opts.headSha },
|
|
134
|
+
...opts.json === true && { json: true }
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
impactCmd.command("verify <report...>").description(
|
|
138
|
+
"Reconcile a logged prediction against one or more Vitest/Jest JSON reports \u2014 reports false skips"
|
|
139
|
+
).option("--head-sha <sha>", "reconcile the prediction made at this commit").option("--json", "output the reconciliation as JSON").action(async (reports, _opts, command) => {
|
|
140
|
+
const { registerImpactVerifyAction } = await import("./impact-DPZIAJR4.js");
|
|
141
|
+
const opts = command.optsWithGlobals();
|
|
142
|
+
await registerImpactVerifyAction(reports, {
|
|
143
|
+
...opts.headSha !== void 0 && { headSha: opts.headSha },
|
|
127
144
|
...opts.json === true && { json: true }
|
|
128
145
|
});
|
|
129
146
|
});
|
|
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(
|
|
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
|
-
);
|
|
141
147
|
program.command("diff <file>").description(
|
|
142
148
|
"Classify a TypeScript file change as non-impacting / internal / breaking"
|
|
143
149
|
).option("--base <ref>", "git ref to compare against", "HEAD~1").action(async (file, opts) => {
|
|
144
|
-
const { registerDiffAction } = await import("./diff-
|
|
150
|
+
const { registerDiffAction } = await import("./diff-A3TQWWUJ.js");
|
|
145
151
|
await registerDiffAction(file, { base: opts.base });
|
|
146
152
|
});
|
|
147
153
|
var workspaceCmd = program.command("workspace").description("Workspace-level analysis commands");
|
|
148
154
|
workspaceCmd.command("check").description(
|
|
149
155
|
"Detect packages importing dependencies they do not declare (CI gate: exits 1 on violations)"
|
|
150
156
|
).option("--json", "output the result as JSON").action(async (opts) => {
|
|
151
|
-
const { registerWorkspaceCheckAction } = await import("./workspace-
|
|
157
|
+
const { registerWorkspaceCheckAction } = await import("./workspace-PIQXG4ZD.js");
|
|
152
158
|
await registerWorkspaceCheckAction({
|
|
153
159
|
...opts.json === true && { json: true }
|
|
154
160
|
});
|
|
@@ -156,14 +162,28 @@ workspaceCmd.command("check").description(
|
|
|
156
162
|
var prCmd = program.command("pr").description("PR-aware analysis commands");
|
|
157
163
|
prCmd.command("check").description(
|
|
158
164
|
"Classify all TypeScript changes in this PR and report a build verdict"
|
|
159
|
-
).option(
|
|
160
|
-
|
|
161
|
-
|
|
165
|
+
).option(
|
|
166
|
+
"--base <ref>",
|
|
167
|
+
"base branch or ref for the PR diff (default: the target or default branch)"
|
|
168
|
+
).action(async (opts) => {
|
|
169
|
+
const { registerPrCheckAction } = await import("./pr-6CYMM2XU.js");
|
|
170
|
+
await registerPrCheckAction({
|
|
171
|
+
...opts.base !== void 0 && { base: opts.base }
|
|
172
|
+
});
|
|
162
173
|
});
|
|
163
|
-
prCmd.command("report").description(
|
|
174
|
+
prCmd.command("report").description(
|
|
175
|
+
"Describe what this PR changes and what must be verified before it merges"
|
|
176
|
+
).option(
|
|
177
|
+
"--base <ref>",
|
|
178
|
+
"base branch or ref for the PR diff (default: the target or default branch)"
|
|
179
|
+
).option("--markdown", "output a markdown summary instead of JSON").option("--output <file>", "write report to file instead of stdout").action(
|
|
164
180
|
async (opts) => {
|
|
165
|
-
const { registerPrReportAction } = await import("./pr-
|
|
166
|
-
await registerPrReportAction(
|
|
181
|
+
const { registerPrReportAction } = await import("./pr-6CYMM2XU.js");
|
|
182
|
+
await registerPrReportAction({
|
|
183
|
+
...opts.base !== void 0 && { base: opts.base },
|
|
184
|
+
...opts.markdown === true && { markdown: true },
|
|
185
|
+
...opts.output !== void 0 && { output: opts.output }
|
|
186
|
+
});
|
|
167
187
|
}
|
|
168
188
|
);
|
|
169
189
|
program.command("build").description("Run the build task").option("-c, --concurrency <n>", "max tasks per DAG level").option(
|
|
@@ -171,7 +191,7 @@ program.command("build").description("Run the build task").option("-c, --concurr
|
|
|
171
191
|
"run only packages affected by changes since baseRef (includes cascade dependents)"
|
|
172
192
|
).option("--dry-run", "print the task plan without executing").action(
|
|
173
193
|
async (opts) => {
|
|
174
|
-
const { registerBuildAction } = await import("./build-
|
|
194
|
+
const { registerBuildAction } = await import("./build-GKWNLU4Q.js");
|
|
175
195
|
const concurrency = parseConcurrency(opts);
|
|
176
196
|
await registerBuildAction({
|
|
177
197
|
...concurrency !== void 0 && { concurrency },
|
|
@@ -185,7 +205,7 @@ program.command("run <task>").description("Run a named task").option(
|
|
|
185
205
|
"max tasks to run concurrently per DAG level"
|
|
186
206
|
).option("--dry-run", "print the task plan without executing").action(
|
|
187
207
|
async (taskName, opts) => {
|
|
188
|
-
const { registerRunAction } = await import("./run-
|
|
208
|
+
const { registerRunAction } = await import("./run-44PKMOV5.js");
|
|
189
209
|
const concurrency = parseConcurrency(opts);
|
|
190
210
|
await registerRunAction(taskName, {
|
|
191
211
|
...concurrency !== void 0 && { concurrency },
|
|
@@ -193,28 +213,47 @@ program.command("run <task>").description("Run a named task").option(
|
|
|
193
213
|
});
|
|
194
214
|
}
|
|
195
215
|
);
|
|
196
|
-
program.command("init").description(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
216
|
+
program.command("init").description(
|
|
217
|
+
"Set variant up in this repository: detect how it is tested, show the changes, apply them"
|
|
218
|
+
).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(
|
|
219
|
+
"--package <spec>",
|
|
220
|
+
"install this in place of the running version (a tarball path or a spec)"
|
|
221
|
+
).option("--tasks", "write variant.config.ts for the task runner instead").action(
|
|
222
|
+
async (opts) => {
|
|
223
|
+
const { registerInitAction } = await import("./init-VNK5E4H3.js");
|
|
224
|
+
await registerInitAction({
|
|
225
|
+
version: _pkg.version,
|
|
226
|
+
...opts.dryRun === true && { dryRun: true },
|
|
227
|
+
...opts.yes === true && { yes: true },
|
|
228
|
+
...opts.install === false && { install: false },
|
|
229
|
+
...opts.package !== void 0 && { package: opts.package },
|
|
230
|
+
...opts.tasks === true && { tasks: true }
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
);
|
|
200
234
|
program.command("doctor").description("Validate config and diagnose common issues").action(async () => {
|
|
201
|
-
const { registerDoctorAction } = await import("./doctor-
|
|
235
|
+
const { registerDoctorAction } = await import("./doctor-6ONS6LKY.js");
|
|
202
236
|
await registerDoctorAction();
|
|
203
237
|
});
|
|
204
238
|
program.command("insight").description("Show task timing and cache hit stats").action(async () => {
|
|
205
|
-
const { registerInsightAction } = await import("./insight-
|
|
239
|
+
const { registerInsightAction } = await import("./insight-LNYVMCAE.js");
|
|
206
240
|
await registerInsightAction();
|
|
207
241
|
});
|
|
208
242
|
program.command("env").description(
|
|
209
243
|
"Show detected environment (runtime, package manager, framework)"
|
|
210
244
|
).action(async () => {
|
|
211
|
-
const { registerEnvAction } = await import("./env-
|
|
245
|
+
const { registerEnvAction } = await import("./env-XF2US3CB.js");
|
|
212
246
|
await registerEnvAction();
|
|
213
247
|
});
|
|
214
248
|
program.command("check").description("Validate config and task graph (no execution)").action(async () => {
|
|
215
|
-
const { registerCheckAction } = await import("./check-
|
|
249
|
+
const { registerCheckAction } = await import("./check-KO5VY67Z.js");
|
|
216
250
|
await registerCheckAction();
|
|
217
251
|
});
|
|
252
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
253
|
+
stream.on("error", (error) => {
|
|
254
|
+
if (error.code !== "EPIPE" && error.code !== "EOF") throw error;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
218
257
|
program.parseAsync(process.argv).catch((err) => {
|
|
219
258
|
if (err instanceof VariantError) {
|
|
220
259
|
renderError(err);
|
|
@@ -2,16 +2,19 @@ import {
|
|
|
2
2
|
DEFAULT_DIFF_BASE_REF,
|
|
3
3
|
classifyFileAgainstRef,
|
|
4
4
|
toWorkspaceRelative
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import
|
|
7
|
-
|
|
5
|
+
} from "./chunk-YII4TAKS.js";
|
|
6
|
+
import {
|
|
7
|
+
assertBaseRef
|
|
8
|
+
} from "./chunk-MAK3HG46.js";
|
|
8
9
|
import {
|
|
9
10
|
lines
|
|
10
11
|
} from "./chunk-6J4XDG44.js";
|
|
11
12
|
import {
|
|
12
13
|
getPrinter
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-EISTWXDW.js";
|
|
15
|
+
import "./chunk-RSIOYQOQ.js";
|
|
16
|
+
import "./chunk-DSQDMFQ6.js";
|
|
17
|
+
import "./chunk-ACTY42RE.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-
|
|
4
|
+
} from "./chunk-RJBFCURK.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-
|
|
13
|
+
} from "./chunk-EISTWXDW.js";
|
|
14
|
+
import "./chunk-ACTY42RE.js";
|
|
15
15
|
|
|
16
16
|
// src/core/doctor/diagnostics.ts
|
|
17
17
|
import { readdir, stat } from "fs/promises";
|
|
@@ -71,9 +71,9 @@ async function checkConfig(cwd) {
|
|
|
71
71
|
if (!configPath) {
|
|
72
72
|
return [
|
|
73
73
|
{
|
|
74
|
-
level: "
|
|
74
|
+
level: "warn",
|
|
75
75
|
label: "No variant.config.ts found",
|
|
76
|
-
detail: "
|
|
76
|
+
detail: "`impact`, `diff`, `pr` and `workspace check` work without one. `build` and `run` need it: `variant init --tasks` creates one."
|
|
77
77
|
}
|
|
78
78
|
];
|
|
79
79
|
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
renderEnv
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QRPI6TJE.js";
|
|
4
4
|
import {
|
|
5
5
|
createContext
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-CN25SRKD.js";
|
|
7
7
|
import "./chunk-V5CTGXMM.js";
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-MZZOE7L4.js";
|
|
8
|
+
import "./chunk-RJBFCURK.js";
|
|
9
|
+
import "./chunk-DMDAJPFJ.js";
|
|
11
10
|
import "./chunk-3E5LBU3O.js";
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-XUBWCTJZ.js";
|
|
11
|
+
import "./chunk-YYGCSKIQ.js";
|
|
14
12
|
import "./chunk-6J4XDG44.js";
|
|
15
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-EISTWXDW.js";
|
|
14
|
+
import "./chunk-RSIOYQOQ.js";
|
|
15
|
+
import "./chunk-ACTY42RE.js";
|
|
16
16
|
|
|
17
17
|
// src/cli/commands/env.ts
|
|
18
18
|
async function registerEnvAction() {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defaultBranchRef,
|
|
3
|
+
listChangedFiles,
|
|
4
|
+
listChangedFilesSinceMergeBase,
|
|
5
|
+
listRepositoryFiles,
|
|
6
|
+
mergeBase,
|
|
7
|
+
readFileAtRef,
|
|
8
|
+
readFilesAtRef,
|
|
9
|
+
readHeadSha,
|
|
10
|
+
repositoryRoot,
|
|
11
|
+
resolveCommit
|
|
12
|
+
} from "./chunk-RSIOYQOQ.js";
|
|
13
|
+
export {
|
|
14
|
+
defaultBranchRef,
|
|
15
|
+
listChangedFiles,
|
|
16
|
+
listChangedFilesSinceMergeBase,
|
|
17
|
+
listRepositoryFiles,
|
|
18
|
+
mergeBase,
|
|
19
|
+
readFileAtRef,
|
|
20
|
+
readFilesAtRef,
|
|
21
|
+
readHeadSha,
|
|
22
|
+
repositoryRoot,
|
|
23
|
+
resolveCommit
|
|
24
|
+
};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import {
|
|
2
|
+
predictImpact,
|
|
3
|
+
verdictText
|
|
4
|
+
} from "./chunk-KCEGA56S.js";
|
|
5
|
+
import "./chunk-IFFZ46YS.js";
|
|
6
|
+
import "./chunk-GDSZFIHM.js";
|
|
7
|
+
import {
|
|
8
|
+
classificationLabel,
|
|
9
|
+
plural
|
|
10
|
+
} from "./chunk-CXNXZLDG.js";
|
|
11
|
+
import "./chunk-DMDAJPFJ.js";
|
|
12
|
+
import "./chunk-3E5LBU3O.js";
|
|
13
|
+
import {
|
|
14
|
+
getColors
|
|
15
|
+
} from "./chunk-YYGCSKIQ.js";
|
|
16
|
+
import {
|
|
17
|
+
verifyImpact
|
|
18
|
+
} from "./chunk-MT4BOAAV.js";
|
|
19
|
+
import {
|
|
20
|
+
changeBaseDeps,
|
|
21
|
+
detectHeadSha
|
|
22
|
+
} from "./chunk-H5C4SE73.js";
|
|
23
|
+
import "./chunk-X3VVGTEV.js";
|
|
24
|
+
import "./chunk-NFFXYYM6.js";
|
|
25
|
+
import "./chunk-MAK3HG46.js";
|
|
26
|
+
import {
|
|
27
|
+
lines
|
|
28
|
+
} from "./chunk-6J4XDG44.js";
|
|
29
|
+
import {
|
|
30
|
+
getPrinter
|
|
31
|
+
} from "./chunk-EISTWXDW.js";
|
|
32
|
+
import "./chunk-RSIOYQOQ.js";
|
|
33
|
+
import "./chunk-DSQDMFQ6.js";
|
|
34
|
+
import {
|
|
35
|
+
NoBaseCommitError
|
|
36
|
+
} from "./chunk-ACTY42RE.js";
|
|
37
|
+
|
|
38
|
+
// src/cli/render/impact.ts
|
|
39
|
+
var MAX_LISTED_FILES = 20;
|
|
40
|
+
var count = (value) => value.toLocaleString("en-US");
|
|
41
|
+
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.";
|
|
42
|
+
function shortRef(ref) {
|
|
43
|
+
return /^[0-9a-f]{40}$/.test(ref) ? ref.slice(0, 12) : ref;
|
|
44
|
+
}
|
|
45
|
+
function renderNoBaseCommit(reason, json, printer = getPrinter()) {
|
|
46
|
+
lines(
|
|
47
|
+
printer,
|
|
48
|
+
json ? JSON.stringify(
|
|
49
|
+
{ prediction: null, code: "NO_BASE_COMMIT", reason },
|
|
50
|
+
null,
|
|
51
|
+
2
|
|
52
|
+
) : `impact: ${reason}; no prediction was made.`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
function renderImpactJson(report, printer = getPrinter()) {
|
|
56
|
+
const { result, ...rest } = report;
|
|
57
|
+
lines(
|
|
58
|
+
printer,
|
|
59
|
+
JSON.stringify(
|
|
60
|
+
{ ...rest, radius: result.radius, tests: result.tests },
|
|
61
|
+
null,
|
|
62
|
+
2
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
function renderImpact(report, printer = getPrinter()) {
|
|
67
|
+
const { radius, tests } = report.result;
|
|
68
|
+
lines(
|
|
69
|
+
printer,
|
|
70
|
+
"",
|
|
71
|
+
`Base ref: ${shortRef(report.baseRef)}${report.baseLabel === report.baseRef ? "" : ` (${report.baseLabel})`}`,
|
|
72
|
+
`Workspace: ${report.packagesFound === 0 ? "no workspace packages found" : plural(report.packagesFound, "package")}`,
|
|
73
|
+
"",
|
|
74
|
+
`You changed ${plural(radius.changed.length, "file")}.`
|
|
75
|
+
);
|
|
76
|
+
for (const impact of radius.changed.slice(0, MAX_LISTED_FILES)) {
|
|
77
|
+
const symbols = impact.impactedSymbols.length > 0 ? ` (${impact.impactedSymbols.join(", ")})` : "";
|
|
78
|
+
lines(
|
|
79
|
+
printer,
|
|
80
|
+
` ${classificationLabel(impact.classification)} ${impact.filePath}${symbols}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (radius.changed.length > MAX_LISTED_FILES) {
|
|
84
|
+
lines(
|
|
85
|
+
printer,
|
|
86
|
+
` \u2026 and ${count(radius.changed.length - MAX_LISTED_FILES)} more`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const packages = radius.affectedPackages.length > 0 ? `, ${plural(radius.affectedPackages.length, "package")} (${radius.affectedPackages.join(", ")})` : "";
|
|
90
|
+
lines(
|
|
91
|
+
printer,
|
|
92
|
+
"",
|
|
93
|
+
`Impact: ${plural(radius.affectedFiles.length, "file")}${packages}`
|
|
94
|
+
);
|
|
95
|
+
const skipped = tests.totalTests - tests.affectedTests.length;
|
|
96
|
+
lines(
|
|
97
|
+
printer,
|
|
98
|
+
"",
|
|
99
|
+
`Run: ${plural(tests.affectedTests.length, "test file")}`,
|
|
100
|
+
`Skip: ${plural(skipped, "test file")} (of ${count(tests.totalTests)} total)`
|
|
101
|
+
);
|
|
102
|
+
if (tests.unreached.length > 0) {
|
|
103
|
+
const listed = tests.unreached.slice(0, MAX_LISTED_FILES).join(", ");
|
|
104
|
+
const more = tests.unreached.length > MAX_LISTED_FILES ? `, \u2026 ${count(tests.unreached.length - MAX_LISTED_FILES)} more` : "";
|
|
105
|
+
const one = tests.unreached.length === 1;
|
|
106
|
+
lines(
|
|
107
|
+
printer,
|
|
108
|
+
"",
|
|
109
|
+
`${getColors().yellow("Unreached:")} ${plural(tests.unreached.length, "changed file")} no test reaches (${listed}${more}). No test is selected for ${one ? "it: it is" : "them: they are"} untested, or used in a way variant cannot follow.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
if (tests.unselectedTests.length > 0) {
|
|
113
|
+
const listed = tests.unselectedTests.slice(0, MAX_LISTED_FILES).join(", ");
|
|
114
|
+
const more = tests.unselectedTests.length > MAX_LISTED_FILES ? `, \u2026 ${count(tests.unselectedTests.length - MAX_LISTED_FILES)} more` : "";
|
|
115
|
+
const one = tests.unselectedTests.length === 1;
|
|
116
|
+
lines(
|
|
117
|
+
printer,
|
|
118
|
+
"",
|
|
119
|
+
`${getColors().yellow("Not selected:")} ${plural(tests.unselectedTests.length, "changed test file")} ${one ? "is" : "are"} JavaScript (${listed}${more}). variant does not index JavaScript tests, so it cannot select them; run them yourself.`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const nothingAnalyzed = !tests.selectAll && radius.changed.length > 0 && radius.changed.every((impact) => impact.classification === "unanalyzed");
|
|
123
|
+
if (nothingAnalyzed) {
|
|
124
|
+
lines(
|
|
125
|
+
printer,
|
|
126
|
+
"",
|
|
127
|
+
"Nothing in this change was analyzed: none of the changed files is TypeScript that variant indexes."
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
lines(
|
|
131
|
+
printer,
|
|
132
|
+
"",
|
|
133
|
+
`Verdict: ${verdictText(report.verdict)}`,
|
|
134
|
+
`Confidence: ${tests.resolution} (${Math.round(tests.confidence * 100)}%) (report-only \u2014 run the full suite; skipping unlocks after shadow-mode validation)`
|
|
135
|
+
);
|
|
136
|
+
const notes = [...radius.notes, ...tests.notes];
|
|
137
|
+
if (notes.length > 0) {
|
|
138
|
+
lines(printer, "", "Notes:");
|
|
139
|
+
for (const note of notes) lines(printer, ` - ${note}`);
|
|
140
|
+
}
|
|
141
|
+
const standing = [...radius.repositoryNotes, ...tests.repositoryNotes];
|
|
142
|
+
if (standing.length > 0 && printer.mode === "verbose") {
|
|
143
|
+
lines(printer, "", "Repository (the same whatever changed):");
|
|
144
|
+
for (const note of standing) lines(printer, ` - ${note}`);
|
|
145
|
+
} else if (standing.length > 0) {
|
|
146
|
+
lines(
|
|
147
|
+
printer,
|
|
148
|
+
"",
|
|
149
|
+
`Repository: ${plural(standing.length, "standing note")}, the same whatever changed (-v lists them)`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (!report.historyLogged) {
|
|
153
|
+
lines(
|
|
154
|
+
printer,
|
|
155
|
+
"",
|
|
156
|
+
"(warning: could not write .variant/history/impact.jsonl \u2014 shadow-mode logging skipped)"
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function renderImpactVerifyJson(result, printer = getPrinter()) {
|
|
161
|
+
const { prediction, ...rest } = result;
|
|
162
|
+
lines(
|
|
163
|
+
printer,
|
|
164
|
+
JSON.stringify(
|
|
165
|
+
{ ...rest, predictedAt: prediction.at, baseRef: prediction.baseRef },
|
|
166
|
+
null,
|
|
167
|
+
2
|
|
168
|
+
)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
function renderImpactVerify(result, printer = getPrinter()) {
|
|
172
|
+
const { prediction } = result;
|
|
173
|
+
lines(
|
|
174
|
+
printer,
|
|
175
|
+
"",
|
|
176
|
+
`Prediction: ${prediction.at} (${prediction.headSha ?? "no commit recorded"})`,
|
|
177
|
+
`Matched by: ${result.matchedBy === "head-sha" ? "head SHA" : "most recent \u2014 verify this is the right run"}`,
|
|
178
|
+
"",
|
|
179
|
+
`Predicted: ${count(prediction.affectedTests.length)} of ${count(prediction.totalTests)} test files${prediction.selectAll ? " (selected all)" : ""}`,
|
|
180
|
+
`Ran: ${plural(result.ranTests, "test file")}, ${count(result.predictedRan)} of them predicted`,
|
|
181
|
+
`Failed: ${plural(result.failedTests.length, "test file")}`
|
|
182
|
+
);
|
|
183
|
+
if (result.failedTests.length === 0) {
|
|
184
|
+
lines(
|
|
185
|
+
printer,
|
|
186
|
+
"",
|
|
187
|
+
"No failures in this run, so it neither confirms nor refutes the prediction."
|
|
188
|
+
);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
lines(
|
|
192
|
+
printer,
|
|
193
|
+
"",
|
|
194
|
+
`Caught: ${count(result.caught.length)} (inside the predicted set)`,
|
|
195
|
+
`False skips: ${count(result.falseSkips.length)} (would have been missed)`
|
|
196
|
+
);
|
|
197
|
+
if (result.falseSkips.length > 0) {
|
|
198
|
+
lines(printer, "", "False skips:");
|
|
199
|
+
for (const test of result.falseSkips.slice(0, MAX_LISTED_FILES)) {
|
|
200
|
+
lines(printer, ` ${test}`);
|
|
201
|
+
}
|
|
202
|
+
if (result.falseSkips.length > MAX_LISTED_FILES) {
|
|
203
|
+
lines(
|
|
204
|
+
printer,
|
|
205
|
+
` \u2026 and ${count(result.falseSkips.length - MAX_LISTED_FILES)} more`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
lines(
|
|
210
|
+
printer,
|
|
211
|
+
"",
|
|
212
|
+
`False-skip rate this run: ${(result.falseSkipRate * 100).toFixed(1)}%`,
|
|
213
|
+
"One run is not a rate. Reconcile many before reading anything into it."
|
|
214
|
+
);
|
|
215
|
+
if (!result.historyLogged) {
|
|
216
|
+
lines(
|
|
217
|
+
printer,
|
|
218
|
+
"",
|
|
219
|
+
"(warning: could not write .variant/history/reconciliation.jsonl)"
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/cli/commands/impact.ts
|
|
225
|
+
async function registerImpactAction(opts = {}) {
|
|
226
|
+
let report;
|
|
227
|
+
try {
|
|
228
|
+
report = await predictImpact(process.cwd(), opts);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (!(error instanceof NoBaseCommitError)) throw error;
|
|
231
|
+
renderNoBaseCommit(error.message, opts.json === true);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (opts.json === true) {
|
|
235
|
+
renderImpactJson(report);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
renderImpact(report);
|
|
239
|
+
}
|
|
240
|
+
async function registerImpactVerifyAction(reportPaths, opts = {}) {
|
|
241
|
+
const cwd = process.cwd();
|
|
242
|
+
const detected = opts.headSha === void 0 ? await detectHeadSha(changeBaseDeps(cwd)) : void 0;
|
|
243
|
+
const headSha = opts.headSha ?? detected;
|
|
244
|
+
let result = await verifyImpact(cwd, reportPaths, {
|
|
245
|
+
...headSha !== void 0 && { headSha }
|
|
246
|
+
});
|
|
247
|
+
if (result === null && opts.headSha === void 0 && detected !== void 0) {
|
|
248
|
+
result = await verifyImpact(cwd, reportPaths);
|
|
249
|
+
}
|
|
250
|
+
if (result === null) {
|
|
251
|
+
lines(getPrinter(), NO_PREDICTION_MESSAGE);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (opts.json === true) {
|
|
255
|
+
renderImpactVerifyJson(result);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
renderImpactVerify(result);
|
|
259
|
+
}
|
|
260
|
+
export {
|
|
261
|
+
registerImpactAction,
|
|
262
|
+
registerImpactVerifyAction
|
|
263
|
+
};
|