@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
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveCommit
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OON6O4S5.js";
|
|
4
4
|
import {
|
|
5
5
|
collectExportedSurface,
|
|
6
6
|
significantText
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-WULBOSG5.js";
|
|
8
8
|
import {
|
|
9
9
|
GitRefError
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-C3NZIFW6.js";
|
|
11
11
|
|
|
12
12
|
// src/core/vcs/base-ref.ts
|
|
13
13
|
async function assertBaseRef(cwd, ref) {
|
|
@@ -25,18 +25,22 @@ function createClassifier() {
|
|
|
25
25
|
const tsm2 = await import("ts-morph");
|
|
26
26
|
return {
|
|
27
27
|
tsm: tsm2,
|
|
28
|
-
project: new tsm2.Project({
|
|
28
|
+
project: new tsm2.Project({
|
|
29
|
+
useInMemoryFileSystem: true,
|
|
30
|
+
compilerOptions: { allowJs: true }
|
|
31
|
+
})
|
|
29
32
|
};
|
|
30
33
|
})();
|
|
31
34
|
const { tsm, project } = await host;
|
|
32
35
|
const id = nextId++;
|
|
36
|
+
const ext = sourceExtension(input.filePath);
|
|
33
37
|
const beforeFile = project.createSourceFile(
|
|
34
|
-
`__before_${id}
|
|
38
|
+
`__before_${id}${ext}`,
|
|
35
39
|
input.before,
|
|
36
40
|
{ overwrite: true }
|
|
37
41
|
);
|
|
38
42
|
const afterFile = project.createSourceFile(
|
|
39
|
-
`__after_${id}
|
|
43
|
+
`__after_${id}${ext}`,
|
|
40
44
|
input.after,
|
|
41
45
|
{ overwrite: true }
|
|
42
46
|
);
|
|
@@ -76,15 +80,23 @@ function compareSurfaces(input, beforeFile, afterFile, tsm) {
|
|
|
76
80
|
if (!afterApi.symbols.has(name)) removed.push(name);
|
|
77
81
|
}
|
|
78
82
|
const surfaceChanged = added.length > 0 || removed.length > 0 || changed.some((c) => c.kind !== "body");
|
|
83
|
+
const parseNotes = [
|
|
84
|
+
...hasParseErrors(beforeFile) ? ["base"] : [],
|
|
85
|
+
...hasParseErrors(afterFile) ? ["working-tree"] : []
|
|
86
|
+
].map(
|
|
87
|
+
(side) => `the ${side} version does not parse; classified as breaking without trusting the comparison`
|
|
88
|
+
);
|
|
79
89
|
let classification;
|
|
80
|
-
if (surfaceChanged) {
|
|
90
|
+
if (surfaceChanged || parseNotes.length > 0) {
|
|
81
91
|
classification = "breaking";
|
|
82
92
|
} else if (changed.length > 0 || significantText(input.before, tsm) !== significantText(input.after, tsm)) {
|
|
83
93
|
classification = "internal";
|
|
84
94
|
} else {
|
|
85
95
|
classification = "non-impacting";
|
|
86
96
|
}
|
|
87
|
-
const confidenceNotes = [
|
|
97
|
+
const confidenceNotes = [
|
|
98
|
+
.../* @__PURE__ */ new Set([...parseNotes, ...beforeApi.notes, ...afterApi.notes])
|
|
99
|
+
];
|
|
88
100
|
const confidence = Math.max(
|
|
89
101
|
0.3,
|
|
90
102
|
Math.round((1 - confidenceNotes.length * 0.15) * 100) / 100
|
|
@@ -94,9 +106,29 @@ function compareSurfaces(input, beforeFile, afterFile, tsm) {
|
|
|
94
106
|
classification,
|
|
95
107
|
exportedSymbols: { added, removed, changed },
|
|
96
108
|
confidence,
|
|
97
|
-
confidenceNotes
|
|
109
|
+
confidenceNotes,
|
|
110
|
+
syntaxErrors: parseNotes.length > 0
|
|
98
111
|
};
|
|
99
112
|
}
|
|
113
|
+
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
114
|
+
".ts",
|
|
115
|
+
".tsx",
|
|
116
|
+
".mts",
|
|
117
|
+
".cts",
|
|
118
|
+
".js",
|
|
119
|
+
".jsx",
|
|
120
|
+
".mjs",
|
|
121
|
+
".cjs"
|
|
122
|
+
]);
|
|
123
|
+
function sourceExtension(filePath) {
|
|
124
|
+
const dot = filePath.lastIndexOf(".");
|
|
125
|
+
const ext = dot === -1 ? "" : filePath.slice(dot);
|
|
126
|
+
return SOURCE_EXTENSIONS.has(ext) ? ext : ".ts";
|
|
127
|
+
}
|
|
128
|
+
function hasParseErrors(file) {
|
|
129
|
+
const { parseDiagnostics } = file.compilerNode;
|
|
130
|
+
return (parseDiagnostics?.length ?? 0) > 0;
|
|
131
|
+
}
|
|
100
132
|
|
|
101
133
|
export {
|
|
102
134
|
createClassifier,
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deriveVerdict,
|
|
3
|
+
traceTestImpact
|
|
4
|
+
} from "./chunk-BNACT47L.js";
|
|
5
|
+
import {
|
|
6
|
+
assertBaseRef
|
|
7
|
+
} from "./chunk-JEJRXPCU.js";
|
|
8
|
+
import {
|
|
9
|
+
loadPackageGraph
|
|
10
|
+
} from "./chunk-3E5LBU3O.js";
|
|
11
|
+
import {
|
|
12
|
+
appendImpactPrediction,
|
|
13
|
+
defaultHistoryDir
|
|
14
|
+
} from "./chunk-34VGKPEM.js";
|
|
15
|
+
import {
|
|
16
|
+
changeBaseDeps,
|
|
17
|
+
resolveChangeBase
|
|
18
|
+
} from "./chunk-O6FCFZDF.js";
|
|
19
|
+
import {
|
|
20
|
+
readHeadSha
|
|
21
|
+
} from "./chunk-OON6O4S5.js";
|
|
22
|
+
import {
|
|
23
|
+
GitRefError,
|
|
24
|
+
NoTestFilesError
|
|
25
|
+
} from "./chunk-C3NZIFW6.js";
|
|
26
|
+
|
|
27
|
+
// src/core/impact/predict.ts
|
|
28
|
+
var DEFAULT_IMPACT_BASE_REF = "HEAD~1";
|
|
29
|
+
async function analyzeImpact(cwd, options = {}) {
|
|
30
|
+
const base = options.changedFiles === void 0 ? await resolveChangeBase(
|
|
31
|
+
options.base,
|
|
32
|
+
options.changeBaseDeps ?? changeBaseDeps(cwd)
|
|
33
|
+
) : {
|
|
34
|
+
ref: options.base ?? DEFAULT_IMPACT_BASE_REF,
|
|
35
|
+
source: options.base === void 0 ? "previous-commit" : "flag",
|
|
36
|
+
label: options.base ?? "previous commit"
|
|
37
|
+
};
|
|
38
|
+
const baseRef = base.ref;
|
|
39
|
+
if (options.changedFiles === void 0) await assertBaseRef(cwd, baseRef);
|
|
40
|
+
const packageGraph = await loadPackageGraph(cwd).catch(() => void 0);
|
|
41
|
+
const result = await traceTestImpact(cwd, {
|
|
42
|
+
baseRef,
|
|
43
|
+
...packageGraph !== void 0 && { packageGraph },
|
|
44
|
+
...options.changedFiles !== void 0 && {
|
|
45
|
+
changedFiles: options.changedFiles
|
|
46
|
+
},
|
|
47
|
+
...options.readBefore !== void 0 && { readBefore: options.readBefore }
|
|
48
|
+
});
|
|
49
|
+
if (result === null) {
|
|
50
|
+
throw new GitRefError(
|
|
51
|
+
`Could not list the files changed since "${baseRef}"`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return { base, result, packageGraph };
|
|
55
|
+
}
|
|
56
|
+
async function predictImpact(cwd, options = {}) {
|
|
57
|
+
const { base, result, packageGraph } = await analyzeImpact(cwd, options);
|
|
58
|
+
const baseRef = base.ref;
|
|
59
|
+
if (result.tests.totalTests === 0) throw new NoTestFilesError();
|
|
60
|
+
const verdict = deriveVerdict(
|
|
61
|
+
result.radius.changed.map((change) => change.classification),
|
|
62
|
+
{ forceBuild: result.tests.selectAll }
|
|
63
|
+
);
|
|
64
|
+
const headSha = options.headSha !== void 0 ? options.headSha : base.headSha ?? await readHeadSha(cwd);
|
|
65
|
+
const historyLogged = await appendImpactPrediction(defaultHistoryDir(cwd), {
|
|
66
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
67
|
+
baseRef,
|
|
68
|
+
headSha,
|
|
69
|
+
changedFiles: result.radius.changed.map((change) => change.filePath),
|
|
70
|
+
affectedFiles: result.radius.affectedFiles.length,
|
|
71
|
+
affectedPackages: result.radius.affectedPackages,
|
|
72
|
+
affectedTests: result.tests.affectedTests,
|
|
73
|
+
totalTests: result.tests.totalTests,
|
|
74
|
+
selectAll: result.tests.selectAll,
|
|
75
|
+
verdict,
|
|
76
|
+
confidence: result.tests.confidence,
|
|
77
|
+
notes: [...result.radius.notes, ...result.tests.notes]
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
baseRef,
|
|
81
|
+
baseSource: base.source,
|
|
82
|
+
baseLabel: base.label,
|
|
83
|
+
headSha,
|
|
84
|
+
result,
|
|
85
|
+
verdict,
|
|
86
|
+
packagesFound: packageGraph?.packages.length ?? 0,
|
|
87
|
+
historyLogged
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
analyzeImpact,
|
|
93
|
+
predictImpact
|
|
94
|
+
};
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadPackageGraph
|
|
3
|
+
} from "./chunk-3E5LBU3O.js";
|
|
4
|
+
import {
|
|
5
|
+
defaultBranchRef
|
|
6
|
+
} from "./chunk-OON6O4S5.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
|
+
};
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
classifyChange
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JEJRXPCU.js";
|
|
4
4
|
import {
|
|
5
5
|
readFileAtRef
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-OON6O4S5.js";
|
|
7
|
+
import {
|
|
8
|
+
CliUsageError
|
|
9
|
+
} from "./chunk-C3NZIFW6.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([
|