@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.
- package/README.md +222 -85
- package/dist/{build-3DSBMMI3.js → build-GCUFPPIB.js} +11 -10
- package/dist/check-AKYABBIV.js +1459 -0
- package/dist/{chunk-43VLELTL.js → chunk-2UVLKBLV.js} +1 -1
- package/dist/chunk-34VGKPEM.js +59 -0
- package/dist/chunk-BNACT47L.js +590 -0
- package/dist/chunk-C3NZIFW6.js +174 -0
- package/dist/{chunk-ZH2PQUNL.js → chunk-EFCMBBBT.js} +72 -110
- package/dist/chunk-H55GYSQ6.js +102 -0
- package/dist/chunk-HL65IPEQ.js +167 -0
- package/dist/chunk-IHXJ72XA.js +31 -0
- package/dist/{chunk-FZEXZBXB.js → chunk-JEJRXPCU.js} +41 -9
- package/dist/chunk-LUTP2TVU.js +94 -0
- package/dist/{chunk-7OPTWU6E.js → chunk-MKUYZIXF.js} +1 -1
- package/dist/chunk-MNYKLWRC.js +377 -0
- package/dist/{chunk-Q7RLUZSN.js → chunk-NJSTAWDM.js} +10 -3
- package/dist/{chunk-26DHEXGS.js → chunk-NOC53Z5Q.js} +1 -1
- package/dist/chunk-O6FCFZDF.js +127 -0
- package/dist/{chunk-6BXSZPHC.js → chunk-OON6O4S5.js} +99 -6
- package/dist/{chunk-FA64KWJ2.js → chunk-TPMHY4L6.js} +16 -31
- package/dist/chunk-TRUK3TJF.js +575 -0
- package/dist/chunk-WTXWEEI2.js +21 -0
- package/dist/{chunk-RC23JQKI.js → chunk-WULBOSG5.js} +15 -2
- package/dist/chunk-X3VVGTEV.js +268 -0
- package/dist/{chunk-TCISS2AS.js → chunk-XXMD4NC5.js} +120 -25
- package/dist/cli.js +89 -30
- package/dist/{diff-HSJBZFMC.js → diff-ST5KHT7P.js} +5 -5
- package/dist/{doctor-B7MHFIYJ.js → doctor-OEIY4Y5X.js} +41 -8
- package/dist/{env-I7JZRWDN.js → env-EQAOZCFR.js} +8 -9
- package/dist/git-VGHUF2SY.js +30 -0
- package/dist/impact-AXLZTLVT.js +266 -0
- package/dist/index.js +2 -2
- package/dist/init-QCOZWLOO.js +793 -0
- package/dist/{insight-JQLTGFYB.js → insight-KRWT6Z47.js} +9 -10
- package/dist/pr-7JXUC5BQ.js +710 -0
- package/dist/{run-C6VAQOVM.js → run-UOPS5FRU.js} +11 -10
- package/dist/stryker-host.d.ts +2 -0
- package/dist/stryker-host.js +135 -0
- package/dist/stryker-plugins.d.ts +33 -0
- package/dist/stryker-plugins.js +142 -0
- package/dist/{symbol-graph-E5GXKDFZ.js → symbol-graph-GYOI4GU4.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 +358 -0
- package/dist/workspace-IQFLR3RY.js +65 -0
- package/package.json +17 -3
- package/dist/check-SIQJU6Z2.js +0 -44
- package/dist/chunk-DRMASVYN.js +0 -25
- package/dist/chunk-KSHWC2XN.js +0 -78
- package/dist/chunk-QRPI6TJE.js +0 -68
- package/dist/chunk-VYWPQKMS.js +0 -88
- 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
|
@@ -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-
|
|
3
|
+
} from "./chunk-WULBOSG5.js";
|
|
4
4
|
import {
|
|
5
5
|
GraphError
|
|
6
|
-
} from "./chunk-
|
|
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 =
|
|
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
|
|
30
|
-
|
|
31
|
-
onlyFiles: true,
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
|
192
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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-
|
|
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(
|
|
124
|
-
|
|
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
|
|
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-
|
|
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(
|
|
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-
|
|
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-
|
|
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(
|
|
159
|
-
|
|
160
|
-
|
|
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(
|
|
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-
|
|
165
|
-
await registerPrReportAction(
|
|
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-
|
|
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-
|
|
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(
|
|
196
|
-
|
|
197
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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;
|