-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathworkflow-policy.test.mjs
More file actions
440 lines (381 loc) · 18.2 KB
/
Copy pathworkflow-policy.test.mjs
File metadata and controls
440 lines (381 loc) · 18.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { access, readdir, readFile } from "node:fs/promises";
import { describe, it } from "node:test";
const ROOT_URL = new URL("../../", import.meta.url);
const WORKFLOWS_URL = new URL("workflows/", new URL("../", import.meta.url));
const BENCH_WORKFLOW_URL = new URL("bench.yml", WORKFLOWS_URL);
const CI_WORKFLOW_URL = new URL("ci.yml", WORKFLOWS_URL);
const COVERAGE_WORKFLOW_URL = new URL("coverage.yml", WORKFLOWS_URL);
const RELEASE_WORKFLOW_URL = new URL("release.yml", WORKFLOWS_URL);
const CONTRIBUTING_URL = new URL("CONTRIBUTING.md", ROOT_URL);
const DEPENDABOT_CONFIG_URL = new URL(".github/dependabot.yml", ROOT_URL);
const FALLOW_CONFIG_URL = new URL(".fallowrc.json", ROOT_URL);
const PACKAGE_JSON_URL = new URL("package.json", ROOT_URL);
const README_URL = new URL("README.md", ROOT_URL);
const RUST_TOOLCHAIN_ACTION =
"dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30";
const WASM_PACK_INSTALL_ACTION = "taiki-e/install-action@43aecc8d72668fbcfe75c31400bc4f890f1c5853";
const WASM_PACK_WORKFLOWS = new Map([
["bench.yml", " if: matrix.kind == 'node'\n"],
["ci.yml", ""],
["coverage.yml", ""],
["release.yml", ""],
]);
const trackedPackageLocks = async () => {
const tracked = execFileSync("git", ["ls-files", "--", ":(glob)**/package-lock.json"], {
cwd: ROOT_URL,
encoding: "utf8",
})
.trim()
.split("\n")
.filter(Boolean);
const existing = await Promise.all(
tracked.map(async (path) => {
try {
await access(new URL(path, ROOT_URL));
return path;
} catch {
return null;
}
}),
);
return existing.filter((path) => path !== null);
};
const workflowFiles = async () => {
const entries = await readdir(WORKFLOWS_URL, { withFileTypes: true });
return entries.filter((entry) => entry.isFile() && /\.ya?ml$/.test(entry.name));
};
const workflowSteps = (workflow, action) => {
const matches = [...workflow.matchAll(new RegExp(`^(\\s*)- uses: ${action}[^\\n]*$`, "gm"))];
return matches.map((match) => {
const start = match.index;
const remaining = workflow.slice(start + match[0].length + 1);
const nextStep = remaining.search(new RegExp(`^${match[1]}- `, "m"));
return nextStep === -1
? workflow.slice(start)
: workflow.slice(start, start + match[0].length + 1 + nextStep);
});
};
const workflowJob = (workflow, jobName) => {
const marker = ` ${jobName}:\n`;
const start = workflow.indexOf(marker);
assert.notEqual(start, -1, `missing ${jobName} job`);
const bodyStart = start + marker.length;
const remaining = workflow.slice(bodyStart);
const nextJob = remaining.search(/^ [\w-]+:/m);
return nextJob === -1 ? remaining : remaining.slice(0, nextJob);
};
const assertSafeNapiConsumer = (name, contents) => {
const unsafeBuilds = [
/\bnapi build\b/,
/\bpnpm\b[^\n]*--filter @srcmap\/(?:codec|sourcemap)(?=\s)[^\n]*\bbuild\b/,
/\b(?:cd|working-directory:)\s+packages\/(?:codec|sourcemap)(?=\s|$)/m,
/\bpnpm\b[^\n]*--dir\s+packages\/(?:codec|sourcemap)(?=\s)[^\n]*\bbuild\b/,
];
for (const unsafeBuild of unsafeBuilds) {
assert.doesNotMatch(contents, unsafeBuild, `${name}: use the no-js bootstrap`);
}
};
describe("JavaScript dependency policy", () => {
it("keeps pnpm-lock.yaml as the only tracked JavaScript lockfile", async () => {
assert.deepEqual(await trackedPackageLocks(), []);
});
it("uses frozen pnpm installs in every workflow", async () => {
for (const entry of await workflowFiles()) {
const workflow = await readFile(new URL(entry.name, WORKFLOWS_URL), "utf8");
assert.doesNotMatch(workflow, /--no-frozen-lockfile/, entry.name);
const installs = workflow.split("\n").filter((line) => /\bpnpm install\b/.test(line));
for (const install of installs) {
assert.match(install, /--ignore-scripts\b/, `${entry.name}: ${install.trim()}`);
assert.match(install, /--frozen-lockfile\b/, `${entry.name}: ${install.trim()}`);
}
}
});
});
describe("Generated artifact policy", () => {
it("ignores generated wasm-pack binaries during artifactless analysis", async () => {
const config = JSON.parse(await readFile(FALLOW_CONFIG_URL, "utf8"));
assert.ok(config.ignoreUnresolvedImports.includes("**/srcmap_*_wasm_bg.wasm"));
});
it("uses one safe bootstrap command for JavaScript test artifacts", async () => {
const packageJson = JSON.parse(await readFile(PACKAGE_JSON_URL, "utf8"));
assert.equal(
packageJson.scripts["build:test-artifacts"],
"pnpm run build:test-artifacts:napi && pnpm run build:test-artifacts:wasm",
);
assert.equal(
packageJson.scripts["build:test-artifacts:napi"],
"pnpm --filter @srcmap/codec exec napi build --release --platform --no-js --dts ../../target/napi-codec.d.ts && pnpm --filter @srcmap/sourcemap exec napi build --release --platform --no-js --dts ../../target/napi-sourcemap.d.ts",
);
assert.equal(
packageJson.scripts["build:test-artifacts:wasm"],
"pnpm --filter @srcmap/sourcemap-wasm build:all && pnpm --filter @srcmap/generator-wasm build:all && pnpm --filter @srcmap/remapping-wasm build:all && pnpm --filter @srcmap/symbolicate-wasm build:all",
);
const workflow = await readFile(CI_WORKFLOW_URL, "utf8");
const job = workflowJob(workflow, "js-runtime");
assert.match(
job,
/ - name: Build JavaScript test artifacts\n run: corepack pnpm run build:test-artifacts/,
);
assert.doesNotMatch(job, /corepack pnpm --filter @srcmap\/.+ build/);
});
it("keeps every non-release NAPI consumer on the no-js bootstrap", async () => {
const ci = await readFile(CI_WORKFLOW_URL, "utf8");
const coverage = await readFile(COVERAGE_WORKFLOW_URL, "utf8");
const bench = await readFile(BENCH_WORKFLOW_URL, "utf8");
const contributing = await readFile(CONTRIBUTING_URL, "utf8");
const readme = await readFile(README_URL, "utf8");
assert.match(workflowJob(ci, "js-runtime"), /corepack pnpm run build:test-artifacts/);
assert.match(workflowJob(coverage, "coverage"), /corepack pnpm run build:test-artifacts:napi/);
assert.match(bench, /corepack pnpm run build:test-artifacts:napi/);
assert.match(contributing, /corepack pnpm run build:test-artifacts:napi/);
assert.match(readme, /corepack pnpm run build:test-artifacts:napi/);
for (const [name, contents] of [
["ci.yml", ci],
["coverage.yml", coverage],
["bench.yml", bench],
["CONTRIBUTING.md", contributing],
["README.md", readme],
]) {
assertSafeNapiConsumer(name, contents);
}
});
it("rejects direct and package-local NAPI build variants", () => {
for (const unsafeBuild of [
"corepack pnpm exec napi build --release",
"run: corepack pnpm run build\nworking-directory: packages/codec",
"corepack pnpm --dir packages/sourcemap run build",
]) {
assert.throws(() => assertSafeNapiConsumer("fixture", unsafeBuild));
}
});
});
describe("Dependabot policy", () => {
it("holds only memchr 2.8.3 after its failed performance gate", async () => {
const config = await readFile(DEPENDABOT_CONFIG_URL, "utf8");
assert.match(
config,
/# memchr 2\.8\.3 regressed the performance gate; retry with the next release\.\n\s+- dependency-name: memchr\n\s+versions: \["2\.8\.3"\]/,
);
});
it("pins the manually managed stable Rust toolchain consistently", async () => {
let consumers = 0;
for (const entry of await workflowFiles()) {
const workflow = await readFile(new URL(entry.name, WORKFLOWS_URL), "utf8");
const uses = workflow
.split("\n")
.filter((line) => line.includes("dtolnay/rust-toolchain@"));
consumers += uses.length;
for (const use of uses) {
assert.ok(use.includes(RUST_TOOLCHAIN_ACTION), `${entry.name}: ${use.trim()}`);
}
}
assert.ok(consumers > 0, "expected at least one Rust toolchain consumer");
const config = await readFile(DEPENDABOT_CONFIG_URL, "utf8");
const githubActionsStart = config.indexOf(" - package-ecosystem: github-actions\n");
assert.notEqual(githubActionsStart, -1, "missing GitHub Actions ecosystem");
const githubActions = config.slice(githubActionsStart);
assert.match(
githubActions,
/# The stable branch has no current versioned release; synchronize its commit pin manually\.\n\s+- dependency-name: dtolnay\/rust-toolchain/,
);
});
});
describe("Checkout credential policy", () => {
it("does not persist GitHub credentials in workflow worktrees", async () => {
for (const entry of await workflowFiles()) {
const workflow = await readFile(new URL(entry.name, WORKFLOWS_URL), "utf8");
for (const step of workflowSteps(workflow, "actions/checkout@")) {
assert.match(
step,
/^\s+persist-credentials: false$/m,
`${entry.name}: checkout must disable persisted credentials`,
);
}
}
});
it("uses a step-scoped token and explicit authenticated remote for badge pushes", async () => {
const workflow = await readFile(COVERAGE_WORKFLOW_URL, "utf8");
const job = workflowJob(workflow, "coverage");
const badgeStep = job.slice(job.indexOf(" - name: Update coverage badges"));
assert.match(badgeStep, /^ env:\n GH_TOKEN: \$\{\{ github\.token \}\}$/m);
assert.doesNotMatch(job, /^ env:\n(?: .*\n)* GH_TOKEN:/m);
assert.doesNotMatch(badgeStep, /git push origin badges/);
const authenticatedPushes = badgeStep.match(
/git push "https:\/\/x-access-token:\$\{GH_TOKEN\}@github\.com\/\$\{GITHUB_REPOSITORY\}\.git" badges/g,
);
assert.equal(authenticatedPushes?.length, 2);
});
});
describe("Pinned installer policy", () => {
it("specifies a tool for every install-action step", async () => {
for (const entry of await workflowFiles()) {
const workflow = await readFile(new URL(entry.name, WORKFLOWS_URL), "utf8");
for (const step of workflowSteps(workflow, "taiki-e/install-action@")) {
assert.match(step, /^\s+tool: \S+$/m, `${entry.name}: install-action must specify a tool`);
}
}
});
});
describe("Pinned wasm-pack installation policy", () => {
it("uses only the pinned install action for wasm-pack in every workflow", async () => {
for (const entry of await workflowFiles()) {
const workflow = await readFile(new URL(entry.name, WORKFLOWS_URL), "utf8");
assert.doesNotMatch(workflow, /rustwasm\.github\.io\/wasm-pack/, entry.name);
assert.doesNotMatch(workflow, /curl[^\n|]*\|\s*sh/, entry.name);
const condition = WASM_PACK_WORKFLOWS.get(entry.name);
if (condition !== undefined) {
const install = [
" - name: Install wasm-pack",
condition.trimEnd(),
` uses: ${WASM_PACK_INSTALL_ACTION} # v2.83.2`,
" with:",
" tool: wasm-pack@0.13.1",
]
.filter(Boolean)
.join("\n");
assert.ok(workflow.includes(install), `${entry.name}: missing pinned wasm-pack installer`);
}
if (/\bwasm-pack (?:build|test)\b/.test(workflow)) {
assert.ok(
WASM_PACK_WORKFLOWS.has(entry.name),
`${entry.name}: wasm-pack use is missing from the pinned installer policy`,
);
}
}
});
});
describe("Rust feature coverage", () => {
it("keeps platform tests bounded and compiles every target", async () => {
const workflow = await readFile(CI_WORKFLOW_URL, "utf8");
const job = workflowJob(workflow, "check");
assert.match(job, /cargo test --workspace --lib --bins --tests --examples/);
assert.match(job, /cargo check --workspace --all-targets/);
assert.doesNotMatch(job, /cargo test --workspace --all-targets/);
});
it("tests both parallel encoders in Linux CI", async () => {
const workflow = await readFile(CI_WORKFLOW_URL, "utf8");
const job = workflowJob(workflow, "parallel-features");
assert.match(job, /^ runs-on: ubuntu-latest$/m);
assert.match(job, /^ run: cargo test -p srcmap-codec --features parallel$/m);
assert.match(job, /^ run: cargo test -p srcmap-generator --features parallel$/m);
});
it("checks Cargo advisories across all features", async () => {
const workflow = await readFile(CI_WORKFLOW_URL, "utf8");
const job = workflowJob(workflow, "deny");
const action = [
" - uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe # v2",
" with:",
" arguments: --all-features",
].join("\n");
assert.ok(job.includes(action), "Cargo Deny must check the all-features graph");
});
});
describe("Release supply-chain policy", () => {
it("contains no ad hoc npm installs", async () => {
const workflow = await readFile(RELEASE_WORKFLOW_URL, "utf8");
assert.doesNotMatch(workflow, /\bnpm install\b/);
});
it("requires provenance for every npm publish attempt", async () => {
const workflow = await readFile(RELEASE_WORKFLOW_URL, "utf8");
const publishes = workflow.split("\n").filter((line) => /\bnpm publish\b/.test(line));
assert.ok(publishes.length > 0, "release workflow must publish npm packages");
for (const publish of publishes) {
assert.match(publish, /--provenance\b/, publish.trim());
}
assert.doesNotMatch(workflow, /retrying without provenance/i);
});
it("uses ordered frozen tooling for both NAPI package builds", async () => {
const workflow = await readFile(RELEASE_WORKFLOW_URL, "utf8");
const buildJob = workflowJob(workflow, "build-napi");
const enable = " - name: Enable Corepack\n run: corepack enable";
const install = [
" - name: Install JS dependencies",
" run: corepack pnpm install --ignore-scripts --frozen-lockfile",
].join("\n");
const codecBuild = [
" - name: Build codec NAPI binary",
" run: |",
" cd packages/codec",
" corepack pnpm exec napi build --release --platform --target ${{ matrix.settings.target }}",
].join("\n");
const sourcemapBuild = [
" - name: Build sourcemap NAPI binary",
" run: |",
" cd packages/sourcemap",
" corepack pnpm exec napi build --release --platform --target ${{ matrix.settings.target }}",
].join("\n");
assert.ok(buildJob.includes(enable), "build-napi must enable Corepack");
assert.ok(buildJob.includes(install), "build-napi must install the frozen workspace");
assert.ok(buildJob.includes(codecBuild), "missing codec NAPI build step");
assert.ok(buildJob.includes(sourcemapBuild), "missing sourcemap NAPI build step");
assert.ok(buildJob.indexOf(enable) < buildJob.indexOf(install));
assert.ok(buildJob.indexOf(install) < buildJob.indexOf(codecBuild));
assert.ok(buildJob.indexOf(install) < buildJob.indexOf(sourcemapBuild));
});
it("uses ordered frozen tooling for both NAPI artifact moves", async () => {
const workflow = await readFile(RELEASE_WORKFLOW_URL, "utf8");
const publishJob = workflowJob(workflow, "publish-npm");
const enable = " - name: Enable Corepack\n run: corepack enable";
const install = [
" - name: Install JS dependencies",
" run: corepack pnpm install --ignore-scripts --frozen-lockfile",
].join("\n");
const codecArtifacts = [
" - name: Move codec artifacts",
" run: corepack pnpm exec napi artifacts -d artifacts",
" working-directory: packages/codec",
].join("\n");
const sourcemapArtifacts = [
" - name: Move sourcemap artifacts",
" run: corepack pnpm exec napi artifacts -d artifacts",
" working-directory: packages/sourcemap",
].join("\n");
assert.ok(publishJob.includes(enable), "publish-npm must enable Corepack");
assert.ok(publishJob.includes(install), "publish-npm must install the frozen workspace");
assert.ok(publishJob.includes(codecArtifacts), "missing codec artifact move step");
assert.ok(publishJob.includes(sourcemapArtifacts), "missing sourcemap artifact move step");
assert.ok(publishJob.indexOf(enable) < publishJob.indexOf(install));
assert.ok(publishJob.indexOf(install) < publishJob.indexOf(codecArtifacts));
assert.ok(publishJob.indexOf(install) < publishJob.indexOf(sourcemapArtifacts));
});
});
describe("NAPI declaration coverage", () => {
it("checks generated declarations after the NAPI build and before JavaScript tests", async () => {
const workflow = await readFile(CI_WORKFLOW_URL, "utf8");
const job = workflowJob(workflow, "js-runtime");
const build = "corepack pnpm run build:test-artifacts";
const declarationStep =
" - name: Check N-API declarations\n run: node .github/scripts/check-napi-declarations.mjs";
const check = "node .github/scripts/check-napi-declarations.mjs";
const test = "corepack pnpm run test:js";
assert.ok(job.includes(declarationStep), "missing explicit NAPI declaration check step");
assert.ok(job.indexOf(build) < job.indexOf(check), "NAPI declarations must build before check");
assert.ok(job.indexOf(check) < job.indexOf(test), "NAPI declarations must check before tests");
});
});
describe("WASM package coverage", () => {
it("builds symbolicate WASM targets before JavaScript tests", async () => {
const workflow = await readFile(CI_WORKFLOW_URL, "utf8");
const job = workflowJob(workflow, "js-runtime");
const build = "corepack pnpm run build:test-artifacts";
const test = "corepack pnpm run test:js";
assert.ok(job.includes(build), "missing symbolicate WASM build");
assert.ok(
job.indexOf(build) < job.indexOf(test),
"symbolicate WASM must build before JS tests",
);
});
it("builds symbolicate WASM targets before JavaScript coverage", async () => {
const workflow = await readFile(COVERAGE_WORKFLOW_URL, "utf8");
const job = workflowJob(workflow, "coverage");
const build = "corepack pnpm --filter @srcmap/symbolicate-wasm build:all";
const coverage = "corepack pnpm run coverage:js";
assert.ok(job.includes(build), "missing symbolicate WASM coverage build");
assert.ok(
job.indexOf(build) < job.indexOf(coverage),
"symbolicate WASM must build before JS coverage",
);
});
});