@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.
Files changed (47) hide show
  1. package/README.md +110 -56
  2. package/dist/{build-3DSBMMI3.js → build-GKWNLU4Q.js} +9 -8
  3. package/dist/{check-SIQJU6Z2.js → check-KO5VY67Z.js} +6 -6
  4. package/dist/{chunk-FA64KWJ2.js → chunk-7NC7AO75.js} +15 -28
  5. package/dist/{chunk-VYWPQKMS.js → chunk-ACTY42RE.js} +20 -0
  6. package/dist/{chunk-ZH2PQUNL.js → chunk-CN25SRKD.js} +6 -6
  7. package/dist/{chunk-43VLELTL.js → chunk-CWF6QXEW.js} +1 -1
  8. package/dist/{chunk-7OPTWU6E.js → chunk-DMDAJPFJ.js} +1 -1
  9. package/dist/{chunk-RC23JQKI.js → chunk-DSQDMFQ6.js} +9 -1
  10. package/dist/chunk-H5C4SE73.js +184 -0
  11. package/dist/chunk-IFFZ46YS.js +575 -0
  12. package/dist/chunk-IHXJ72XA.js +31 -0
  13. package/dist/chunk-KCEGA56S.js +670 -0
  14. package/dist/{chunk-FZEXZBXB.js → chunk-MAK3HG46.js} +41 -9
  15. package/dist/chunk-MT4BOAAV.js +167 -0
  16. package/dist/{chunk-TCISS2AS.js → chunk-NFFXYYM6.js} +120 -25
  17. package/dist/{chunk-26DHEXGS.js → chunk-RJBFCURK.js} +1 -1
  18. package/dist/{chunk-6BXSZPHC.js → chunk-RSIOYQOQ.js} +67 -8
  19. package/dist/chunk-SGP5JX6I.js +377 -0
  20. package/dist/chunk-X3VVGTEV.js +268 -0
  21. package/dist/{chunk-Q7RLUZSN.js → chunk-YII4TAKS.js} +10 -3
  22. package/dist/cli.js +62 -27
  23. package/dist/{diff-HSJBZFMC.js → diff-A3TQWWUJ.js} +5 -5
  24. package/dist/{doctor-B7MHFIYJ.js → doctor-6ONS6LKY.js} +4 -4
  25. package/dist/{env-I7JZRWDN.js → env-XF2US3CB.js} +6 -6
  26. package/dist/git-W6N3WMYQ.js +24 -0
  27. package/dist/impact-DPZIAJR4.js +263 -0
  28. package/dist/index.js +2 -2
  29. package/dist/init-VNK5E4H3.js +777 -0
  30. package/dist/{insight-JQLTGFYB.js → insight-LNYVMCAE.js} +7 -7
  31. package/dist/pr-6CYMM2XU.js +707 -0
  32. package/dist/{run-C6VAQOVM.js → run-44PKMOV5.js} +9 -8
  33. package/dist/{symbol-graph-E5GXKDFZ.js → symbol-graph-OB4AJCLW.js} +3 -3
  34. package/dist/{tsconfig-paths-E3UEDGXZ.js → tsconfig-paths-SV3PULCW.js} +3 -1
  35. package/dist/vitest.cjs +629 -0
  36. package/dist/vitest.d.cts +181 -0
  37. package/dist/vitest.d.ts +181 -0
  38. package/dist/vitest.js +356 -0
  39. package/dist/workspace-PIQXG4ZD.js +65 -0
  40. package/package.json +13 -2
  41. package/dist/chunk-DRMASVYN.js +0 -25
  42. package/dist/chunk-KSHWC2XN.js +0 -78
  43. package/dist/chunk-XTYVVUWW.js +0 -279
  44. package/dist/impact-PRZ6HEP5.js +0 -735
  45. package/dist/init-O2BY3FOY.js +0 -158
  46. package/dist/pr-Y724QAHE.js +0 -165
  47. package/dist/workspace-HRTFGWKN.js +0 -186
package/dist/vitest.js ADDED
@@ -0,0 +1,356 @@
1
+ import {
2
+ testRunFrom,
3
+ verifyRun
4
+ } from "./chunk-MT4BOAAV.js";
5
+ import {
6
+ changeBaseDeps,
7
+ defaultHistoryDir,
8
+ detectHeadSha
9
+ } from "./chunk-H5C4SE73.js";
10
+ import {
11
+ repositoryRoot,
12
+ resolveCommit
13
+ } from "./chunk-RSIOYQOQ.js";
14
+ import "./chunk-ACTY42RE.js";
15
+
16
+ // src/reporters/shadow.ts
17
+ import { spawn } from "child_process";
18
+ import {
19
+ appendFile,
20
+ mkdir,
21
+ open,
22
+ readdir,
23
+ readFile,
24
+ stat,
25
+ unlink,
26
+ writeFile
27
+ } from "fs/promises";
28
+ import path from "path";
29
+ import { fileURLToPath } from "url";
30
+ var DEFAULT_TIMEOUT_MS = 3e4;
31
+ var STALE_LOCK_MS = 10 * 6e4;
32
+ var POLL_MS = 200;
33
+ var KEEP_OUTCOME_MS = 60 * 6e4;
34
+ var DISABLED = { finish: async () => {
35
+ }, cancel: () => {
36
+ } };
37
+ function inCi(env) {
38
+ const ci = env["CI"];
39
+ return ci !== void 0 && ci !== "" && ci !== "false" && ci !== "0";
40
+ }
41
+ function isEnabled(options, env) {
42
+ if (env["VARIANT_SHADOW"] === "0") return false;
43
+ if (options.local === true || env["VARIANT_SHADOW"] === "1") return true;
44
+ return inCi(env);
45
+ }
46
+ function count(n, noun) {
47
+ return `${n} ${noun}${n === 1 ? "" : "s"}`;
48
+ }
49
+ function summaryLines(summary, result) {
50
+ const why = summary.unreached > 0 ? `: ${count(summary.unreached, "changed file")} reach${summary.unreached === 1 ? "es" : ""} no test` : summary.unselectedTests > 0 ? `: ${count(summary.unselectedTests, "changed JavaScript test")} could not be selected` : "";
51
+ const predicted = summary.selectAll ? `variant: selected all ${count(result.ranTests, "test file")}, because configuration changed.` : `variant: predicted ${result.predictedRan} of ${count(result.ranTests, "test file")} (${summary.resolution}${why}).`;
52
+ const failed = result.failedTests.length;
53
+ if (failed === 0) {
54
+ return [`${predicted} No failures, so nothing to check.`];
55
+ }
56
+ const missed = result.falseSkips.length;
57
+ if (missed === 0) {
58
+ return [
59
+ `${predicted} ${failed} failed, ${failed === 1 ? "and it was" : "all"} predicted.`
60
+ ];
61
+ }
62
+ return [
63
+ `${predicted} ${failed} failed, ${missed} NOT predicted:`,
64
+ ...result.falseSkips.map((file) => ` ${file}`),
65
+ " Nothing was skipped. This is a false skip; please report it."
66
+ ];
67
+ }
68
+ function pendingPaths(root, headSha) {
69
+ const dir = path.join(defaultHistoryDir(root), "pending");
70
+ return {
71
+ dir,
72
+ lock: path.join(dir, `${headSha}.lock`),
73
+ done: path.join(dir, `${headSha}.json`)
74
+ };
75
+ }
76
+ async function acquire(lock, dir, now) {
77
+ await mkdir(dir, { recursive: true });
78
+ let takenAt = now();
79
+ for (let attempt = 0; attempt < 2; attempt++) {
80
+ try {
81
+ const handle = await open(lock, "wx");
82
+ await handle.writeFile(String(now()));
83
+ await handle.close();
84
+ return true;
85
+ } catch (error) {
86
+ if (error.code !== "EEXIST") throw error;
87
+ takenAt = await stat(lock).then(
88
+ (stats) => stats.mtimeMs,
89
+ () => now()
90
+ );
91
+ if (now() - takenAt < STALE_LOCK_MS) return takenAt;
92
+ await unlink(lock).catch(() => {
93
+ });
94
+ }
95
+ }
96
+ return takenAt;
97
+ }
98
+ async function prune(dir, now) {
99
+ const names = await readdir(dir).catch(() => []);
100
+ await Promise.all(
101
+ names.map(async (name) => {
102
+ const file = path.join(dir, name);
103
+ const age = await stat(file).then(
104
+ (stats) => now() - stats.mtimeMs,
105
+ () => 0
106
+ );
107
+ if (age > KEEP_OUTCOME_MS) await unlink(file).catch(() => {
108
+ });
109
+ })
110
+ );
111
+ }
112
+ async function readDone(done, since) {
113
+ try {
114
+ const parsed = JSON.parse(await readFile(done, "utf8"));
115
+ return parsed.at >= since ? parsed : null;
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+ function startShadow(cwd, options = {}, deps = shadowDeps()) {
121
+ if (!isEnabled(options, deps.env)) return DISABLED;
122
+ const startedAt = deps.now();
123
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
124
+ let cancelled = false;
125
+ let handle;
126
+ let held;
127
+ const say = (lines) => {
128
+ if (options.silent !== true) deps.write(`${lines.join("\n")}
129
+ `);
130
+ };
131
+ const release = async () => {
132
+ if (held === void 0) return;
133
+ const lock = held;
134
+ held = void 0;
135
+ await unlink(lock).catch(() => {
136
+ });
137
+ };
138
+ const prediction = (async () => {
139
+ const root = await deps.repositoryRoot(cwd);
140
+ const headSha = await deps.headSha(root);
141
+ if (headSha === null || cancelled) return null;
142
+ const paths = pendingPaths(root, headSha);
143
+ if (inCi(deps.env)) {
144
+ const earlier = await readDone(paths.done, startedAt - STALE_LOCK_MS);
145
+ if (earlier !== null) return { root, headSha, outcome: earlier.outcome };
146
+ }
147
+ const lock = await acquire(paths.lock, paths.dir, deps.now);
148
+ if (lock === true) {
149
+ held = paths.lock;
150
+ if (cancelled) {
151
+ await release();
152
+ return null;
153
+ }
154
+ void prune(paths.dir, deps.now);
155
+ handle = deps.predict(root, headSha);
156
+ const outcome = await handle.result;
157
+ const done = { at: deps.now(), outcome };
158
+ await writeFile(paths.done, JSON.stringify(done)).catch(() => {
159
+ });
160
+ await release();
161
+ return { root, headSha, outcome };
162
+ }
163
+ while (!cancelled) {
164
+ const lockGone = await stat(paths.lock).then(
165
+ () => false,
166
+ () => true
167
+ );
168
+ if (lockGone) {
169
+ const done = await readDone(paths.done, lock - 1e3);
170
+ return {
171
+ root,
172
+ headSha,
173
+ outcome: done?.outcome ?? {
174
+ kind: "failed",
175
+ reason: "another test process was predicting and left no result"
176
+ }
177
+ };
178
+ }
179
+ await new Promise((resolve) => setTimeout(resolve, POLL_MS));
180
+ }
181
+ return null;
182
+ })();
183
+ prediction.catch(() => {
184
+ });
185
+ const stop = () => {
186
+ cancelled = true;
187
+ handle?.cancel();
188
+ void release();
189
+ };
190
+ return {
191
+ cancel: stop,
192
+ finish: async (results) => {
193
+ try {
194
+ let timer;
195
+ const timedOut = new Promise((resolve) => {
196
+ timer = setTimeout(() => resolve("timeout"), timeoutMs);
197
+ });
198
+ const settled = await Promise.race([prediction, timedOut]);
199
+ clearTimeout(timer);
200
+ if (settled === "timeout") {
201
+ stop();
202
+ say([
203
+ `variant: skipped (the prediction did not finish within ${Math.round(timeoutMs / 1e3)} s of the tests)`
204
+ ]);
205
+ return;
206
+ }
207
+ if (settled === null) return;
208
+ const { root, headSha, outcome } = settled;
209
+ if (outcome.kind === "none") {
210
+ say([`variant: no prediction (${outcome.reason})`]);
211
+ return;
212
+ }
213
+ if (outcome.kind === "failed") {
214
+ say([`variant: skipped (${outcome.reason})`]);
215
+ return;
216
+ }
217
+ const result = await deps.reconcile(root, results, headSha);
218
+ if (result === null) {
219
+ say(["variant: skipped (the prediction was not recorded)"]);
220
+ return;
221
+ }
222
+ const lines = summaryLines(outcome.summary, result);
223
+ say(lines);
224
+ const summaryFile = deps.env["GITHUB_STEP_SUMMARY"];
225
+ if (summaryFile !== void 0 && summaryFile !== "") {
226
+ await deps.appendSummary(summaryFile, `${lines.join("\n")}
227
+ `).catch(() => {
228
+ });
229
+ }
230
+ } catch (error) {
231
+ stop();
232
+ say([
233
+ `variant: skipped (${error instanceof Error ? error.message : String(error)})`
234
+ ]);
235
+ }
236
+ }
237
+ };
238
+ }
239
+ function firstLine(text) {
240
+ return text.split("\n").map((line) => line.trim()).find((line) => line !== "") ?? "variant impact failed";
241
+ }
242
+ function parsePrediction(exitCode, stdout, stderr) {
243
+ if (exitCode !== 0) return { kind: "failed", reason: firstLine(stderr) };
244
+ let parsed;
245
+ try {
246
+ parsed = JSON.parse(stdout);
247
+ } catch {
248
+ return { kind: "failed", reason: "variant impact printed no result" };
249
+ }
250
+ if (parsed.prediction === null) {
251
+ return { kind: "none", reason: parsed.reason ?? "nothing to compare" };
252
+ }
253
+ if (parsed.tests === void 0) {
254
+ return { kind: "failed", reason: "variant impact printed no result" };
255
+ }
256
+ return {
257
+ kind: "predicted",
258
+ summary: {
259
+ resolution: parsed.tests.resolution,
260
+ selectAll: parsed.tests.selectAll,
261
+ unreached: parsed.tests.unreached.length,
262
+ unselectedTests: parsed.tests.unselectedTests.length
263
+ }
264
+ };
265
+ }
266
+ function defaultCliPath() {
267
+ return fileURLToPath(new URL("./cli.js", import.meta.url));
268
+ }
269
+ function shadowDeps(cliPath = defaultCliPath()) {
270
+ return {
271
+ env: process.env,
272
+ now: () => Date.now(),
273
+ repositoryRoot: async (cwd) => await repositoryRoot(cwd) ?? cwd,
274
+ headSha: async (root) => await detectHeadSha(changeBaseDeps(root)) ?? await resolveCommit(root, "HEAD"),
275
+ predict: (root, headSha) => {
276
+ const child = spawn(
277
+ process.execPath,
278
+ [cliPath, "impact", "--json", "--head-sha", headSha],
279
+ { cwd: root, stdio: ["ignore", "pipe", "pipe"] }
280
+ );
281
+ let stdout = "";
282
+ let stderr = "";
283
+ child.stdout.on("data", (chunk) => {
284
+ stdout += new TextDecoder().decode(chunk);
285
+ });
286
+ child.stderr.on("data", (chunk) => {
287
+ stderr += new TextDecoder().decode(chunk);
288
+ });
289
+ const result = new Promise((resolve) => {
290
+ child.on(
291
+ "error",
292
+ (error) => resolve({ kind: "failed", reason: error.message })
293
+ );
294
+ child.on(
295
+ "close",
296
+ (code) => resolve(parsePrediction(code, stdout, stderr))
297
+ );
298
+ });
299
+ return { result, cancel: () => void child.kill("SIGKILL") };
300
+ },
301
+ reconcile: (root, results, headSha) => verifyRun(root, testRunFrom(root, results), { headSha }),
302
+ write: (line) => void process.stdout.write(line),
303
+ appendSummary: (file, text) => appendFile(file, text)
304
+ };
305
+ }
306
+
307
+ // src/reporters/vitest.ts
308
+ function variantReporter(options = {}, deps) {
309
+ let root = process.cwd();
310
+ let watching = false;
311
+ let log;
312
+ let run;
313
+ return {
314
+ onInit(vitest) {
315
+ root = vitest.config.root;
316
+ watching = vitest.config.watch === true;
317
+ log = (line) => vitest.logger.log(line.replace(/\n$/, ""));
318
+ },
319
+ onTestRunStart() {
320
+ if (watching) return;
321
+ const effects = deps ?? shadowDeps();
322
+ run = startShadow(root, options, {
323
+ ...effects,
324
+ ...log !== void 0 && deps === void 0 && { write: log }
325
+ });
326
+ },
327
+ async onTestRunEnd(testModules, _unhandledErrors, reason) {
328
+ const current = run;
329
+ run = void 0;
330
+ if (current === void 0) return;
331
+ if (reason === "interrupted") {
332
+ current.cancel();
333
+ return;
334
+ }
335
+ await current.finish(
336
+ testModules.map((testModule) => ({
337
+ file: testModule.moduleId,
338
+ failed: testModule.state() === "failed"
339
+ }))
340
+ );
341
+ }
342
+ };
343
+ }
344
+ function variantReporters(options = {}) {
345
+ return [
346
+ "default",
347
+ ...process.env["GITHUB_ACTIONS"] === "true" ? ["github-actions"] : [],
348
+ variantReporter(options)
349
+ ];
350
+ }
351
+ var vitest_default = variantReporter;
352
+ export {
353
+ vitest_default as default,
354
+ variantReporter,
355
+ variantReporters
356
+ };
@@ -0,0 +1,65 @@
1
+ import {
2
+ auditWorkspace
3
+ } from "./chunk-IFFZ46YS.js";
4
+ import {
5
+ plural
6
+ } from "./chunk-CXNXZLDG.js";
7
+ import "./chunk-3E5LBU3O.js";
8
+ import "./chunk-X3VVGTEV.js";
9
+ import "./chunk-NFFXYYM6.js";
10
+ import {
11
+ lines
12
+ } from "./chunk-6J4XDG44.js";
13
+ import {
14
+ getPrinter
15
+ } from "./chunk-EISTWXDW.js";
16
+ import "./chunk-DSQDMFQ6.js";
17
+ import "./chunk-ACTY42RE.js";
18
+
19
+ // src/cli/render/workspace.ts
20
+ var MAX_FILES_SHOWN = 5;
21
+ var NO_WORKSPACE_MESSAGE = "workspace check: no workspace packages found (pnpm-workspace.yaml or package.json `workspaces` required).";
22
+ var DESCRIBE = {
23
+ "undeclared-workspace-dep": (v) => `${v.package} imports ${v.target} but does not declare it`,
24
+ "undeclared-external-dep": (v) => `${v.package} imports ${v.target} but neither it nor the workspace root declares it`,
25
+ "cross-package-relative-import": (v) => `${v.package} reaches into ${v.target} via a relative import (bypasses its public entry)`
26
+ };
27
+ function renderWorkspaceCheck(result, printer = getPrinter()) {
28
+ lines(printer, "", `Checked ${plural(result.packagesChecked, "package")}.`);
29
+ if (result.violations.length === 0) {
30
+ lines(printer, "", "No dependency violations found.");
31
+ return;
32
+ }
33
+ lines(printer, "");
34
+ for (const violation of result.violations) {
35
+ lines(printer, ` \u2717 ${DESCRIBE[violation.kind](violation)}`);
36
+ for (const file of violation.files.slice(0, MAX_FILES_SHOWN)) {
37
+ lines(printer, ` ${file}`);
38
+ }
39
+ if (violation.files.length > MAX_FILES_SHOWN) {
40
+ lines(
41
+ printer,
42
+ ` \u2026 and ${violation.files.length - MAX_FILES_SHOWN} more file(s)`
43
+ );
44
+ }
45
+ }
46
+ lines(printer, "", `${plural(result.violations.length, "violation")} found.`);
47
+ }
48
+
49
+ // src/cli/commands/workspace.ts
50
+ async function registerWorkspaceCheckAction(opts = {}) {
51
+ const result = await auditWorkspace(process.cwd());
52
+ if (result === null) {
53
+ lines(getPrinter(), NO_WORKSPACE_MESSAGE);
54
+ return;
55
+ }
56
+ if (opts.json === true) {
57
+ lines(getPrinter(), JSON.stringify(result, null, 2));
58
+ } else {
59
+ renderWorkspaceCheck(result);
60
+ }
61
+ if (result.violations.length > 0) process.exitCode = 1;
62
+ }
63
+ export {
64
+ registerWorkspaceCheckAction
65
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blzsky/variant",
3
- "version": "0.2.1",
3
+ "version": "0.3.0-canary.0",
4
4
  "description": "Change intelligence for TypeScript monorepos — AST-level blast radius, test impact, and a cached task DAG",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,6 +9,16 @@
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
11
  "import": "./dist/index.js"
12
+ },
13
+ "./vitest": {
14
+ "import": {
15
+ "types": "./dist/vitest.d.ts",
16
+ "default": "./dist/vitest.js"
17
+ },
18
+ "require": {
19
+ "types": "./dist/vitest.d.cts",
20
+ "default": "./dist/vitest.cjs"
21
+ }
12
22
  }
13
23
  },
14
24
  "bin": {
@@ -69,7 +79,7 @@
69
79
  },
70
80
  "scripts": {
71
81
  "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
72
- "build": "tsup",
82
+ "build": "pnpm clean && tsup",
73
83
  "test": "vitest",
74
84
  "test:run": "vitest run",
75
85
  "test:integration": "vitest run --project integration",
@@ -77,6 +87,7 @@
77
87
  "test:all": "vitest run --coverage",
78
88
  "bench": "node benchmarks/bench.mjs",
79
89
  "bench:quick": "node benchmarks/bench.mjs --quick",
90
+ "accuracy": "node accuracy/replay.mjs",
80
91
  "format": "biome format --write .",
81
92
  "format:check": "biome format .",
82
93
  "lint": "biome check .",
@@ -1,25 +0,0 @@
1
- // src/core/semantic/verdict.ts
2
- function deriveVerdict(classifications, options = {}) {
3
- if (options.forceBuild === true) return "build-required";
4
- let verdict = "safe-to-skip";
5
- for (const classification of classifications) {
6
- if (classification === "breaking") return "build-required";
7
- if (classification === "internal" || classification === "unanalyzed") {
8
- verdict = "build-recommended";
9
- }
10
- }
11
- return verdict;
12
- }
13
- var VERDICT_TEXT = {
14
- "safe-to-skip": "safe to skip build",
15
- "build-recommended": "build recommended",
16
- "build-required": "build required"
17
- };
18
- function verdictText(verdict) {
19
- return VERDICT_TEXT[verdict];
20
- }
21
-
22
- export {
23
- deriveVerdict,
24
- verdictText
25
- };
@@ -1,78 +0,0 @@
1
- // src/core/graph/tsconfig-paths.ts
2
- import { access } from "fs/promises";
3
- import path from "path";
4
- var MAX_WILDCARDS = 1;
5
- function toPathAliases(cwd, paths, baseUrl) {
6
- if (paths === void 0) return [];
7
- const base = baseUrl ?? cwd;
8
- const aliases = [];
9
- for (const [pattern, rawTargets] of Object.entries(paths)) {
10
- const stars = pattern.split("*").length - 1;
11
- if (stars > MAX_WILDCARDS) continue;
12
- const wildcard = stars === 1;
13
- const [prefix = "", suffix = ""] = wildcard ? pattern.split("*") : [pattern, ""];
14
- const targets = [];
15
- for (const target of rawTargets) {
16
- if (target.split("*").length - 1 > MAX_WILDCARDS) continue;
17
- const absolute = path.resolve(base, target);
18
- const relative = path.relative(cwd, absolute).replace(/\\/g, "/");
19
- if (relative === "" || relative.startsWith("../")) continue;
20
- targets.push(relative);
21
- }
22
- if (targets.length > 0) {
23
- aliases.push({ prefix, suffix, wildcard, targets });
24
- }
25
- }
26
- aliases.sort((a, b) => b.prefix.length - a.prefix.length);
27
- return aliases;
28
- }
29
- function matchPathAlias(spec, aliases) {
30
- const bases = [];
31
- for (const alias of aliases) {
32
- if (!alias.wildcard) {
33
- if (spec === alias.prefix) bases.push(...alias.targets);
34
- continue;
35
- }
36
- if (spec.length < alias.prefix.length + alias.suffix.length || !spec.startsWith(alias.prefix) || !spec.endsWith(alias.suffix)) {
37
- continue;
38
- }
39
- const captured = spec.slice(
40
- alias.prefix.length,
41
- spec.length - alias.suffix.length
42
- );
43
- for (const target of alias.targets) {
44
- bases.push(target.replace("*", captured));
45
- }
46
- }
47
- return bases;
48
- }
49
- async function readPathAliases(cwd, tsconfigName = "tsconfig.json") {
50
- const configPath = path.join(cwd, tsconfigName);
51
- try {
52
- await access(configPath);
53
- } catch {
54
- return [];
55
- }
56
- const { ts } = await import("ts-morph");
57
- const read = ts.readConfigFile(configPath, (file) => ts.sys.readFile(file));
58
- if (read.error !== void 0 || read.config === void 0) return [];
59
- const parsed = ts.parseJsonConfigFileContent(
60
- read.config,
61
- {
62
- ...ts.sys,
63
- // Enumerating every file in the workspace is pure waste: only
64
- // `compilerOptions` is wanted, and a large repo makes this the most
65
- // expensive call in the command.
66
- readDirectory: () => [],
67
- useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames
68
- },
69
- cwd
70
- );
71
- return toPathAliases(cwd, parsed.options.paths, parsed.options.baseUrl);
72
- }
73
-
74
- export {
75
- toPathAliases,
76
- matchPathAlias,
77
- readPathAliases
78
- };