@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
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadPackageGraph
|
|
3
|
+
} from "./chunk-3E5LBU3O.js";
|
|
4
|
+
import {
|
|
5
|
+
defaultBranchRef
|
|
6
|
+
} from "./chunk-RSIOYQOQ.js";
|
|
7
|
+
|
|
8
|
+
// src/core/setup/discover.ts
|
|
9
|
+
import { access, readFile } from "fs/promises";
|
|
10
|
+
import path from "path";
|
|
11
|
+
|
|
12
|
+
// src/core/setup/workflow.ts
|
|
13
|
+
var TEST_COMMAND = /\b(?:vitest|jest)\b|\b(?:npm|pnpm|yarn|bun)(?:\s+run)?\s+test\b|\bturbo(?:\s+run)?\s+test\b|\bnx\s+(?:run-many|affected|test)\b/;
|
|
14
|
+
function runsTests(workflow) {
|
|
15
|
+
return workflow.split("\n").some((line) => !line.trim().startsWith("#") && TEST_COMMAND.test(line));
|
|
16
|
+
}
|
|
17
|
+
var CHECKOUT = /^(\s*)(-\s+)?uses:\s*actions\/checkout@\S+\s*$/;
|
|
18
|
+
function indentOf(line) {
|
|
19
|
+
return line.length - line.trimStart().length;
|
|
20
|
+
}
|
|
21
|
+
function withFullHistory(workflow) {
|
|
22
|
+
const lines = workflow.split("\n");
|
|
23
|
+
const out = [];
|
|
24
|
+
let changed = false;
|
|
25
|
+
for (let i = 0; i < lines.length; i++) {
|
|
26
|
+
const line = lines[i] ?? "";
|
|
27
|
+
out.push(line);
|
|
28
|
+
const match = CHECKOUT.exec(line);
|
|
29
|
+
if (match === null) continue;
|
|
30
|
+
const keyIndent = (match[1]?.length ?? 0) + (match[2]?.length ?? 0);
|
|
31
|
+
const pad = " ".repeat(keyIndent);
|
|
32
|
+
let end = i + 1;
|
|
33
|
+
while (end < lines.length) {
|
|
34
|
+
const next = lines[end] ?? "";
|
|
35
|
+
if (next.trim() !== "" && indentOf(next) < keyIndent) break;
|
|
36
|
+
if (next.trim().startsWith("- ") && indentOf(next) < keyIndent) break;
|
|
37
|
+
end++;
|
|
38
|
+
}
|
|
39
|
+
const step = lines.slice(i + 1, end);
|
|
40
|
+
const withAt = step.findIndex(
|
|
41
|
+
(l) => indentOf(l) === keyIndent && /^with:\s*$/.test(l.trim())
|
|
42
|
+
);
|
|
43
|
+
if (withAt === -1) {
|
|
44
|
+
out.push(`${pad}with:`, `${pad} fetch-depth: 0`);
|
|
45
|
+
changed = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const depthAt = step.findIndex(
|
|
49
|
+
(l, index) => index > withAt && indentOf(l) > keyIndent && /^fetch-depth:/.test(l.trim())
|
|
50
|
+
);
|
|
51
|
+
for (let j = 0; j < step.length; j++) {
|
|
52
|
+
const stepLine = step[j] ?? "";
|
|
53
|
+
if (j === depthAt) {
|
|
54
|
+
const fixed = stepLine.replace(/fetch-depth:.*$/, "fetch-depth: 0");
|
|
55
|
+
if (fixed !== stepLine) changed = true;
|
|
56
|
+
out.push(fixed);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
out.push(stepLine);
|
|
60
|
+
if (j === withAt && depthAt === -1) {
|
|
61
|
+
out.push(`${pad} fetch-depth: 0`);
|
|
62
|
+
changed = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
i = end - 1;
|
|
66
|
+
}
|
|
67
|
+
return changed ? out.join("\n") : null;
|
|
68
|
+
}
|
|
69
|
+
var ACTIONS = [
|
|
70
|
+
"actions/checkout",
|
|
71
|
+
"actions/setup-node",
|
|
72
|
+
"actions/github-script",
|
|
73
|
+
"pnpm/action-setup",
|
|
74
|
+
"oven-sh/setup-bun"
|
|
75
|
+
];
|
|
76
|
+
function workflowStyle(workflow) {
|
|
77
|
+
const actions = {};
|
|
78
|
+
let node;
|
|
79
|
+
let bun;
|
|
80
|
+
for (const raw of workflow.split("\n")) {
|
|
81
|
+
const line = raw.trim();
|
|
82
|
+
if (line.startsWith("#")) continue;
|
|
83
|
+
const uses = /^(?:-\s+)?uses:\s*(\S+)@(\S+)/.exec(line);
|
|
84
|
+
if (uses !== null) {
|
|
85
|
+
const [, action, version] = uses;
|
|
86
|
+
if (action !== void 0 && ACTIONS.includes(action)) {
|
|
87
|
+
actions[action] ??= `${action}@${version}`;
|
|
88
|
+
}
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (line.includes("${{")) continue;
|
|
92
|
+
if (/^node-version(?:-file)?:\s*\S/.test(line)) node ??= line;
|
|
93
|
+
if (/^bun-version(?:-file)?:\s*\S/.test(line)) bun ??= line;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
actions,
|
|
97
|
+
...node !== void 0 && { node },
|
|
98
|
+
...bun !== void 0 && { bun }
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
var DEFAULT_ACTIONS = {
|
|
102
|
+
"actions/checkout": "actions/checkout@v7",
|
|
103
|
+
"actions/setup-node": "actions/setup-node@v7",
|
|
104
|
+
"actions/github-script": "actions/github-script@v9",
|
|
105
|
+
"pnpm/action-setup": "pnpm/action-setup@v4",
|
|
106
|
+
"oven-sh/setup-bun": "oven-sh/setup-bun@v2"
|
|
107
|
+
};
|
|
108
|
+
var INSTALL = {
|
|
109
|
+
npm: "npm ci",
|
|
110
|
+
pnpm: "pnpm install --frozen-lockfile",
|
|
111
|
+
yarn: "yarn install --frozen-lockfile",
|
|
112
|
+
bun: "bun install --frozen-lockfile"
|
|
113
|
+
};
|
|
114
|
+
var EXEC = {
|
|
115
|
+
npm: "npx --no -- variant",
|
|
116
|
+
pnpm: "pnpm exec variant",
|
|
117
|
+
yarn: "yarn variant",
|
|
118
|
+
bun: "bun run variant"
|
|
119
|
+
};
|
|
120
|
+
var NODE_CACHE = {
|
|
121
|
+
npm: "npm",
|
|
122
|
+
pnpm: "pnpm",
|
|
123
|
+
yarn: "yarn"
|
|
124
|
+
};
|
|
125
|
+
function pullRequestWorkflow(packageManager, style = { actions: {} }) {
|
|
126
|
+
const cache = NODE_CACHE[packageManager];
|
|
127
|
+
const action = (name) => style.actions[name] ?? DEFAULT_ACTIONS[name] ?? name;
|
|
128
|
+
const setup = packageManager === "pnpm" ? [` - uses: ${action("pnpm/action-setup")}`, ""] : packageManager === "bun" ? [
|
|
129
|
+
` - uses: ${action("oven-sh/setup-bun")}`,
|
|
130
|
+
...style.bun === void 0 ? [] : [" with:", ` ${style.bun}`],
|
|
131
|
+
""
|
|
132
|
+
] : [];
|
|
133
|
+
return [
|
|
134
|
+
"# Written by `variant init`. It keeps one comment on each pull request",
|
|
135
|
+
"# saying what the change affects. It is yours to edit.",
|
|
136
|
+
"",
|
|
137
|
+
"name: variant",
|
|
138
|
+
"",
|
|
139
|
+
"on:",
|
|
140
|
+
" pull_request:",
|
|
141
|
+
"",
|
|
142
|
+
"permissions:",
|
|
143
|
+
" contents: read",
|
|
144
|
+
" pull-requests: write",
|
|
145
|
+
"",
|
|
146
|
+
"# A newer push makes the report for an older one pointless.",
|
|
147
|
+
"concurrency:",
|
|
148
|
+
// Split so the linter does not take a workflow expression for a
|
|
149
|
+
// template placeholder someone forgot the backticks on.
|
|
150
|
+
` group: variant-$${"{{ github.event.pull_request.number }}"}`,
|
|
151
|
+
" cancel-in-progress: true",
|
|
152
|
+
"",
|
|
153
|
+
"jobs:",
|
|
154
|
+
" report:",
|
|
155
|
+
" runs-on: ubuntu-latest",
|
|
156
|
+
" timeout-minutes: 10",
|
|
157
|
+
" steps:",
|
|
158
|
+
` - uses: ${action("actions/checkout")}`,
|
|
159
|
+
" with:",
|
|
160
|
+
" # variant compares against the commit the branch started from.",
|
|
161
|
+
" fetch-depth: 0",
|
|
162
|
+
"",
|
|
163
|
+
...setup,
|
|
164
|
+
` - uses: ${action("actions/setup-node")}`,
|
|
165
|
+
" with:",
|
|
166
|
+
` ${style.node ?? "node-version: 22"}`,
|
|
167
|
+
...cache === void 0 ? [] : [` cache: ${cache}`],
|
|
168
|
+
"",
|
|
169
|
+
` - run: ${INSTALL[packageManager]}`,
|
|
170
|
+
"",
|
|
171
|
+
" - name: Describe the pull request",
|
|
172
|
+
` run: ${EXEC[packageManager]} pr report --markdown --output pr-report.md`,
|
|
173
|
+
"",
|
|
174
|
+
" - name: Post or update the comment",
|
|
175
|
+
" # Pull requests from forks get a read-only token and cannot comment.",
|
|
176
|
+
" if: github.event.pull_request.head.repo.full_name == github.repository",
|
|
177
|
+
` uses: ${action("actions/github-script")}`,
|
|
178
|
+
" with:",
|
|
179
|
+
" script: |",
|
|
180
|
+
' const fs = require("node:fs");',
|
|
181
|
+
' const body = fs.readFileSync("pr-report.md", "utf8");',
|
|
182
|
+
" // The first line of every report; it marks the comment to update.",
|
|
183
|
+
' const marker = "## Variant PR Report";',
|
|
184
|
+
" const { owner, repo } = context.repo;",
|
|
185
|
+
" const issue_number = context.issue.number;",
|
|
186
|
+
" const comments = await github.paginate(github.rest.issues.listComments, {",
|
|
187
|
+
" owner,",
|
|
188
|
+
" repo,",
|
|
189
|
+
" issue_number,",
|
|
190
|
+
" });",
|
|
191
|
+
" const existing = comments.find(",
|
|
192
|
+
" (comment) =>",
|
|
193
|
+
' comment.user?.login === "github-actions[bot]" &&',
|
|
194
|
+
" comment.body?.startsWith(marker),",
|
|
195
|
+
" );",
|
|
196
|
+
" if (existing) {",
|
|
197
|
+
" await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });",
|
|
198
|
+
" } else {",
|
|
199
|
+
" await github.rest.issues.createComment({ owner, repo, issue_number, body });",
|
|
200
|
+
" }",
|
|
201
|
+
""
|
|
202
|
+
].join("\n");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/core/setup/discover.ts
|
|
206
|
+
var LOCKFILES = [
|
|
207
|
+
["bun.lock", "bun"],
|
|
208
|
+
["bun.lockb", "bun"],
|
|
209
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
210
|
+
["yarn.lock", "yarn"],
|
|
211
|
+
["package-lock.json", "npm"],
|
|
212
|
+
["npm-shrinkwrap.json", "npm"]
|
|
213
|
+
];
|
|
214
|
+
var IGNORE = ["**/node_modules/**", "**/dist/**", ".git/**", ".variant/**"];
|
|
215
|
+
async function readText(file) {
|
|
216
|
+
try {
|
|
217
|
+
return await readFile(file, "utf8");
|
|
218
|
+
} catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function readManifest(cwd) {
|
|
223
|
+
const raw = await readText(path.join(cwd, "package.json"));
|
|
224
|
+
if (raw === null) return null;
|
|
225
|
+
try {
|
|
226
|
+
const parsed = JSON.parse(raw);
|
|
227
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
228
|
+
} catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
var MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
233
|
+
async function managerIn(dir) {
|
|
234
|
+
for (const [lockfile, name2] of LOCKFILES) {
|
|
235
|
+
if (await readText(path.join(dir, lockfile)) !== null) return name2;
|
|
236
|
+
}
|
|
237
|
+
const declared = (await readManifest(dir))?.packageManager;
|
|
238
|
+
const name = typeof declared === "string" ? declared.split("@")[0] : void 0;
|
|
239
|
+
return name !== void 0 && MANAGERS.has(name) ? name : null;
|
|
240
|
+
}
|
|
241
|
+
async function detectPackageManager(cwd) {
|
|
242
|
+
let dir = path.resolve(cwd);
|
|
243
|
+
for (; ; ) {
|
|
244
|
+
const found = await managerIn(dir);
|
|
245
|
+
if (found !== null) return found;
|
|
246
|
+
const parent = path.dirname(dir);
|
|
247
|
+
const atRoot = await access(path.join(dir, ".git")).then(
|
|
248
|
+
() => true,
|
|
249
|
+
() => false
|
|
250
|
+
);
|
|
251
|
+
if (parent === dir || atRoot) return null;
|
|
252
|
+
dir = parent;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async function packageManagerOf(cwd) {
|
|
256
|
+
return detectPackageManager(cwd);
|
|
257
|
+
}
|
|
258
|
+
var CONFIG_FLAG = /(?:--config|-c)(?:=|\s+)(\S+)/;
|
|
259
|
+
function vitestRuns(manifest) {
|
|
260
|
+
const configs = [];
|
|
261
|
+
let plain = false;
|
|
262
|
+
for (const script of Object.values(manifest?.scripts ?? {})) {
|
|
263
|
+
if (typeof script !== "string") continue;
|
|
264
|
+
for (const command of script.split(/&&|\|\||;/)) {
|
|
265
|
+
if (!/\bvitest\b/.test(command)) continue;
|
|
266
|
+
const config = CONFIG_FLAG.exec(command)?.[1];
|
|
267
|
+
if (config === void 0) plain = true;
|
|
268
|
+
else configs.push(config.replace(/^["']|["']$/g, ""));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return { plain, configs };
|
|
272
|
+
}
|
|
273
|
+
function adapterTargets(configs, packageDirs) {
|
|
274
|
+
const root = configs.filter((file) => !file.includes("/"));
|
|
275
|
+
if (root.length > 0) return root;
|
|
276
|
+
const dirs = new Set(packageDirs);
|
|
277
|
+
return configs.filter((file) => dirs.has(path.posix.dirname(file)));
|
|
278
|
+
}
|
|
279
|
+
async function discoverRepository(cwd) {
|
|
280
|
+
const fg = (await import("fast-glob")).default;
|
|
281
|
+
const [manifest, packageGraph, found, gitignore, branch] = await Promise.all([
|
|
282
|
+
readManifest(cwd),
|
|
283
|
+
loadPackageGraph(cwd).catch(() => null),
|
|
284
|
+
fg(
|
|
285
|
+
[
|
|
286
|
+
"**/vitest.config.{ts,mts,cts,js,mjs,cjs}",
|
|
287
|
+
"**/vite.config.{ts,mts,cts,js,mjs,cjs}",
|
|
288
|
+
"**/jest.config.{ts,mts,cts,js,mjs,cjs,json}",
|
|
289
|
+
"**/playwright.config.{ts,mts,cts,js,mjs,cjs}",
|
|
290
|
+
".github/workflows/*.{yml,yaml}",
|
|
291
|
+
"turbo.json",
|
|
292
|
+
"nx.json"
|
|
293
|
+
],
|
|
294
|
+
{ cwd, onlyFiles: true, dot: true, ignore: IGNORE }
|
|
295
|
+
),
|
|
296
|
+
readText(path.join(cwd, ".gitignore")),
|
|
297
|
+
defaultBranchRef(cwd)
|
|
298
|
+
]);
|
|
299
|
+
const files = found.map((file) => file.replace(/\\/g, "/")).sort();
|
|
300
|
+
const named = (prefix) => files.filter((file) => path.posix.basename(file).startsWith(prefix));
|
|
301
|
+
const packageDirs = (packageGraph?.packages ?? []).map(
|
|
302
|
+
(pkg) => path.relative(cwd, pkg.dir).replace(/\\/g, "/")
|
|
303
|
+
);
|
|
304
|
+
const workflows = await Promise.all(
|
|
305
|
+
files.filter((file) => file.startsWith(".github/workflows/")).map(async (file) => {
|
|
306
|
+
const content = await readText(path.join(cwd, file)) ?? "";
|
|
307
|
+
return {
|
|
308
|
+
file,
|
|
309
|
+
content,
|
|
310
|
+
runsTests: runsTests(content),
|
|
311
|
+
shallow: withFullHistory(content) !== null
|
|
312
|
+
};
|
|
313
|
+
})
|
|
314
|
+
);
|
|
315
|
+
const vitestConfigs = adapterTargets(named("vitest.config."), packageDirs);
|
|
316
|
+
const hasRootConfig = vitestConfigs.some((file) => !file.includes("/"));
|
|
317
|
+
const configDirs = (prefix) => new Map(
|
|
318
|
+
named(prefix).map((file) => {
|
|
319
|
+
const dir = path.posix.dirname(file);
|
|
320
|
+
return [dir === "." ? "" : dir, file];
|
|
321
|
+
})
|
|
322
|
+
);
|
|
323
|
+
const vitestDirs = configDirs("vitest.config.");
|
|
324
|
+
const viteDirs = configDirs("vite.config.");
|
|
325
|
+
const members = (packageGraph?.packages ?? []).map((pkg) => ({
|
|
326
|
+
dir: path.relative(cwd, pkg.dir).replace(/\\/g, "/"),
|
|
327
|
+
manifest: pkg.manifest
|
|
328
|
+
})).filter((member) => member.dir !== "");
|
|
329
|
+
const usesVitest = (candidate) => "vitest" in (candidate?.devDependencies ?? {}) || "vitest" in (candidate?.dependencies ?? {});
|
|
330
|
+
const everyone = [{ dir: "", manifest }, ...members];
|
|
331
|
+
const runs = new Map(
|
|
332
|
+
everyone.map((member) => [member.dir, vitestRuns(member.manifest)])
|
|
333
|
+
);
|
|
334
|
+
const scripted = everyone.some((member) => {
|
|
335
|
+
const run = runs.get(member.dir);
|
|
336
|
+
return run !== void 0 && (run.plain || run.configs.length > 0);
|
|
337
|
+
});
|
|
338
|
+
const users = members.filter((member) => usesVitest(member.manifest));
|
|
339
|
+
const runners = scripted ? everyone.filter((member) => runs.get(member.dir)?.plain === true) : users.length > 0 ? users : usesVitest(manifest) ? [{ dir: "", manifest }] : [];
|
|
340
|
+
const unconfigured = hasRootConfig ? [] : runners.filter((runner) => !vitestDirs.has(runner.dir));
|
|
341
|
+
const scriptConfigs = everyone.flatMap(
|
|
342
|
+
(member) => (runs.get(member.dir)?.configs ?? []).map(
|
|
343
|
+
(config) => path.posix.normalize(path.posix.join(member.dir, config))
|
|
344
|
+
)
|
|
345
|
+
);
|
|
346
|
+
const allConfigs = [.../* @__PURE__ */ new Set([...vitestConfigs, ...scriptConfigs])].sort();
|
|
347
|
+
return {
|
|
348
|
+
packageManager: await detectPackageManager(cwd),
|
|
349
|
+
workspacePackages: packageDirs.filter((dir) => dir !== "").length,
|
|
350
|
+
taskRunner: files.includes("turbo.json") ? "turborepo" : files.includes("nx.json") ? "nx" : null,
|
|
351
|
+
vitestConfigs: allConfigs,
|
|
352
|
+
vitestWithoutConfig: unconfigured.filter((runner) => !viteDirs.has(runner.dir)).map((runner) => runner.dir).sort(),
|
|
353
|
+
viteConfigs: unconfigured.flatMap((runner) => viteDirs.get(runner.dir) ?? []).sort(),
|
|
354
|
+
reporterFlags: [{ dir: "", manifest }, ...members].filter(
|
|
355
|
+
(member) => Object.values(member.manifest?.scripts ?? {}).some(
|
|
356
|
+
(script) => typeof script === "string" && /\bvitest\b.*--reporter\b/.test(script)
|
|
357
|
+
)
|
|
358
|
+
).map(
|
|
359
|
+
(member) => member.dir === "" ? "package.json" : `${member.dir}/package.json`
|
|
360
|
+
).sort(),
|
|
361
|
+
jestConfigs: named("jest.config."),
|
|
362
|
+
playwrightConfigs: named("playwright.config."),
|
|
363
|
+
workflows,
|
|
364
|
+
defaultBranch: branch?.replace(/^origin\//, "") ?? null,
|
|
365
|
+
installed: "@blzsky/variant" in (manifest?.devDependencies ?? {}) || "@blzsky/variant" in (manifest?.dependencies ?? {}),
|
|
366
|
+
ignoresVariantDir: (gitignore ?? "").split("\n").some((line) => /^\/?\.variant\/?$/.test(line.trim())),
|
|
367
|
+
hasManifest: manifest !== null
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export {
|
|
372
|
+
withFullHistory,
|
|
373
|
+
workflowStyle,
|
|
374
|
+
pullRequestWorkflow,
|
|
375
|
+
packageManagerOf,
|
|
376
|
+
discoverRepository
|
|
377
|
+
};
|
|
@@ -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,16 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
classifyChange
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-MAK3HG46.js";
|
|
4
4
|
import {
|
|
5
5
|
readFileAtRef
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-RSIOYQOQ.js";
|
|
7
|
+
import {
|
|
8
|
+
CliUsageError
|
|
9
|
+
} from "./chunk-ACTY42RE.js";
|
|
7
10
|
|
|
8
11
|
// src/core/semantic/file-change.ts
|
|
9
12
|
import { readFile } from "fs/promises";
|
|
10
13
|
import path from "path";
|
|
11
14
|
var DEFAULT_DIFF_BASE_REF = "HEAD~1";
|
|
12
15
|
function toWorkspaceRelative(cwd, filePath) {
|
|
13
|
-
|
|
16
|
+
const relative = path.relative(cwd, path.resolve(cwd, filePath));
|
|
17
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
18
|
+
throw new CliUsageError(`${filePath} is outside the repository at ${cwd}`);
|
|
19
|
+
}
|
|
20
|
+
return relative.replace(/\\/g, "/");
|
|
14
21
|
}
|
|
15
22
|
async function classifyFileAgainstRef(cwd, relPath, baseRef) {
|
|
16
23
|
const [before, after] = await Promise.all([
|