@blzsky/variant 0.2.1 → 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 +110 -56
- package/dist/{build-3DSBMMI3.js → build-GKWNLU4Q.js} +9 -8
- package/dist/{check-SIQJU6Z2.js → check-KO5VY67Z.js} +6 -6
- package/dist/{chunk-FA64KWJ2.js → chunk-7NC7AO75.js} +15 -28
- package/dist/{chunk-VYWPQKMS.js → chunk-ACTY42RE.js} +20 -0
- package/dist/{chunk-ZH2PQUNL.js → chunk-CN25SRKD.js} +6 -6
- package/dist/{chunk-43VLELTL.js → chunk-CWF6QXEW.js} +1 -1
- package/dist/{chunk-7OPTWU6E.js → chunk-DMDAJPFJ.js} +1 -1
- package/dist/{chunk-RC23JQKI.js → chunk-DSQDMFQ6.js} +9 -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-FZEXZBXB.js → chunk-MAK3HG46.js} +41 -9
- package/dist/chunk-MT4BOAAV.js +167 -0
- package/dist/{chunk-TCISS2AS.js → chunk-NFFXYYM6.js} +120 -25
- package/dist/{chunk-26DHEXGS.js → chunk-RJBFCURK.js} +1 -1
- package/dist/{chunk-6BXSZPHC.js → chunk-RSIOYQOQ.js} +67 -8
- package/dist/chunk-SGP5JX6I.js +377 -0
- package/dist/chunk-X3VVGTEV.js +268 -0
- package/dist/{chunk-Q7RLUZSN.js → chunk-YII4TAKS.js} +10 -3
- package/dist/cli.js +62 -27
- package/dist/{diff-HSJBZFMC.js → diff-A3TQWWUJ.js} +5 -5
- package/dist/{doctor-B7MHFIYJ.js → doctor-6ONS6LKY.js} +4 -4
- package/dist/{env-I7JZRWDN.js → env-XF2US3CB.js} +6 -6
- 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-JQLTGFYB.js → insight-LNYVMCAE.js} +7 -7
- package/dist/pr-6CYMM2XU.js +707 -0
- package/dist/{run-C6VAQOVM.js → run-44PKMOV5.js} +9 -8
- package/dist/{symbol-graph-E5GXKDFZ.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 +13 -2
- package/dist/chunk-DRMASVYN.js +0 -25
- package/dist/chunk-KSHWC2XN.js +0 -78
- package/dist/chunk-XTYVVUWW.js +0 -279
- package/dist/impact-PRZ6HEP5.js +0 -735
- package/dist/init-O2BY3FOY.js +0 -158
- package/dist/pr-Y724QAHE.js +0 -165
- package/dist/workspace-HRTFGWKN.js +0 -186
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
CliUsageError,
|
|
13
13
|
VariantError
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-ACTY42RE.js";
|
|
15
15
|
|
|
16
16
|
// src/cli/index.ts
|
|
17
17
|
import { readFileSync } from "fs";
|
|
@@ -120,19 +120,26 @@ 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 },
|
|
127
134
|
...opts.json === true && { json: true }
|
|
128
135
|
});
|
|
129
136
|
});
|
|
130
|
-
impactCmd.command("verify <report
|
|
131
|
-
"Reconcile a logged prediction against
|
|
132
|
-
).option("--head-sha <sha>", "reconcile the prediction made at this commit").option("--json", "output the reconciliation as JSON").action(async (
|
|
133
|
-
const { registerImpactVerifyAction } = await import("./impact-
|
|
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");
|
|
134
141
|
const opts = command.optsWithGlobals();
|
|
135
|
-
await registerImpactVerifyAction(
|
|
142
|
+
await registerImpactVerifyAction(reports, {
|
|
136
143
|
...opts.headSha !== void 0 && { headSha: opts.headSha },
|
|
137
144
|
...opts.json === true && { json: true }
|
|
138
145
|
});
|
|
@@ -140,14 +147,14 @@ impactCmd.command("verify <report>").description(
|
|
|
140
147
|
program.command("diff <file>").description(
|
|
141
148
|
"Classify a TypeScript file change as non-impacting / internal / breaking"
|
|
142
149
|
).option("--base <ref>", "git ref to compare against", "HEAD~1").action(async (file, opts) => {
|
|
143
|
-
const { registerDiffAction } = await import("./diff-
|
|
150
|
+
const { registerDiffAction } = await import("./diff-A3TQWWUJ.js");
|
|
144
151
|
await registerDiffAction(file, { base: opts.base });
|
|
145
152
|
});
|
|
146
153
|
var workspaceCmd = program.command("workspace").description("Workspace-level analysis commands");
|
|
147
154
|
workspaceCmd.command("check").description(
|
|
148
155
|
"Detect packages importing dependencies they do not declare (CI gate: exits 1 on violations)"
|
|
149
156
|
).option("--json", "output the result as JSON").action(async (opts) => {
|
|
150
|
-
const { registerWorkspaceCheckAction } = await import("./workspace-
|
|
157
|
+
const { registerWorkspaceCheckAction } = await import("./workspace-PIQXG4ZD.js");
|
|
151
158
|
await registerWorkspaceCheckAction({
|
|
152
159
|
...opts.json === true && { json: true }
|
|
153
160
|
});
|
|
@@ -155,14 +162,28 @@ workspaceCmd.command("check").description(
|
|
|
155
162
|
var prCmd = program.command("pr").description("PR-aware analysis commands");
|
|
156
163
|
prCmd.command("check").description(
|
|
157
164
|
"Classify all TypeScript changes in this PR and report a build verdict"
|
|
158
|
-
).option(
|
|
159
|
-
|
|
160
|
-
|
|
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
|
+
});
|
|
161
173
|
});
|
|
162
|
-
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(
|
|
163
180
|
async (opts) => {
|
|
164
|
-
const { registerPrReportAction } = await import("./pr-
|
|
165
|
-
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
|
+
});
|
|
166
187
|
}
|
|
167
188
|
);
|
|
168
189
|
program.command("build").description("Run the build task").option("-c, --concurrency <n>", "max tasks per DAG level").option(
|
|
@@ -170,7 +191,7 @@ program.command("build").description("Run the build task").option("-c, --concurr
|
|
|
170
191
|
"run only packages affected by changes since baseRef (includes cascade dependents)"
|
|
171
192
|
).option("--dry-run", "print the task plan without executing").action(
|
|
172
193
|
async (opts) => {
|
|
173
|
-
const { registerBuildAction } = await import("./build-
|
|
194
|
+
const { registerBuildAction } = await import("./build-GKWNLU4Q.js");
|
|
174
195
|
const concurrency = parseConcurrency(opts);
|
|
175
196
|
await registerBuildAction({
|
|
176
197
|
...concurrency !== void 0 && { concurrency },
|
|
@@ -184,7 +205,7 @@ program.command("run <task>").description("Run a named task").option(
|
|
|
184
205
|
"max tasks to run concurrently per DAG level"
|
|
185
206
|
).option("--dry-run", "print the task plan without executing").action(
|
|
186
207
|
async (taskName, opts) => {
|
|
187
|
-
const { registerRunAction } = await import("./run-
|
|
208
|
+
const { registerRunAction } = await import("./run-44PKMOV5.js");
|
|
188
209
|
const concurrency = parseConcurrency(opts);
|
|
189
210
|
await registerRunAction(taskName, {
|
|
190
211
|
...concurrency !== void 0 && { concurrency },
|
|
@@ -192,26 +213,40 @@ program.command("run <task>").description("Run a named task").option(
|
|
|
192
213
|
});
|
|
193
214
|
}
|
|
194
215
|
);
|
|
195
|
-
program.command("init").description(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
+
);
|
|
199
234
|
program.command("doctor").description("Validate config and diagnose common issues").action(async () => {
|
|
200
|
-
const { registerDoctorAction } = await import("./doctor-
|
|
235
|
+
const { registerDoctorAction } = await import("./doctor-6ONS6LKY.js");
|
|
201
236
|
await registerDoctorAction();
|
|
202
237
|
});
|
|
203
238
|
program.command("insight").description("Show task timing and cache hit stats").action(async () => {
|
|
204
|
-
const { registerInsightAction } = await import("./insight-
|
|
239
|
+
const { registerInsightAction } = await import("./insight-LNYVMCAE.js");
|
|
205
240
|
await registerInsightAction();
|
|
206
241
|
});
|
|
207
242
|
program.command("env").description(
|
|
208
243
|
"Show detected environment (runtime, package manager, framework)"
|
|
209
244
|
).action(async () => {
|
|
210
|
-
const { registerEnvAction } = await import("./env-
|
|
245
|
+
const { registerEnvAction } = await import("./env-XF2US3CB.js");
|
|
211
246
|
await registerEnvAction();
|
|
212
247
|
});
|
|
213
248
|
program.command("check").description("Validate config and task graph (no execution)").action(async () => {
|
|
214
|
-
const { registerCheckAction } = await import("./check-
|
|
249
|
+
const { registerCheckAction } = await import("./check-KO5VY67Z.js");
|
|
215
250
|
await registerCheckAction();
|
|
216
251
|
});
|
|
217
252
|
for (const stream of [process.stdout, process.stderr]) {
|
|
@@ -2,19 +2,19 @@ import {
|
|
|
2
2
|
DEFAULT_DIFF_BASE_REF,
|
|
3
3
|
classifyFileAgainstRef,
|
|
4
4
|
toWorkspaceRelative
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-YII4TAKS.js";
|
|
6
6
|
import {
|
|
7
7
|
assertBaseRef
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-6BXSZPHC.js";
|
|
8
|
+
} from "./chunk-MAK3HG46.js";
|
|
10
9
|
import {
|
|
11
10
|
lines
|
|
12
11
|
} from "./chunk-6J4XDG44.js";
|
|
13
12
|
import {
|
|
14
13
|
getPrinter
|
|
15
14
|
} from "./chunk-EISTWXDW.js";
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-RSIOYQOQ.js";
|
|
16
|
+
import "./chunk-DSQDMFQ6.js";
|
|
17
|
+
import "./chunk-ACTY42RE.js";
|
|
18
18
|
|
|
19
19
|
// src/cli/render/diff.ts
|
|
20
20
|
var CLASS_MEANING = {
|
|
@@ -1,7 +1,7 @@
|
|
|
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";
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
getPrinter
|
|
13
13
|
} from "./chunk-EISTWXDW.js";
|
|
14
|
-
import "./chunk-
|
|
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
|
}
|
|
@@ -3,16 +3,16 @@ import {
|
|
|
3
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-7OPTWU6E.js";
|
|
8
|
+
import "./chunk-RJBFCURK.js";
|
|
9
|
+
import "./chunk-DMDAJPFJ.js";
|
|
11
10
|
import "./chunk-3E5LBU3O.js";
|
|
12
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-YYGCSKIQ.js";
|
|
13
12
|
import "./chunk-6J4XDG44.js";
|
|
14
13
|
import "./chunk-EISTWXDW.js";
|
|
15
|
-
import "./chunk-
|
|
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
|
+
};
|