@@ -4,9 +4,9 @@ import { URL } from "node:url";
44import { describe , expect , it } from "vitest" ;
55import { parseDocument } from "yaml" ;
66
7- function readWorkflow ( ) {
7+ function readWorkflow ( name = "ci.yml" ) {
88 const source = readFileSync (
9- new URL ( " ../.github/workflows/ci.yml" , import . meta. url ) ,
9+ new URL ( ` ../.github/workflows/${ name } ` , import . meta. url ) ,
1010 "utf8" ,
1111 ) ;
1212 const document = parseDocument ( source , { uniqueKeys : true } ) ;
@@ -15,6 +15,84 @@ function readWorkflow() {
1515}
1616
1717describe ( "blocking CI workflow" , ( ) => {
18+ it ( "installs dependencies before dependency-backed workflow gates" , ( ) => {
19+ const workflowLint = readWorkflow ( ) . jobs ?. [ "workflow-lint" ] ;
20+ expect ( workflowLint ) . toBeDefined ( ) ;
21+ const previewInstall = workflowLint . steps . findIndex (
22+ ( step ) => step . run === "npm ci" ,
23+ ) ;
24+ const previewGate = workflowLint . steps . findIndex (
25+ ( step ) => step . run === "npm run check:public-preview" ,
26+ ) ;
27+ expect ( workflowLint . steps [ previewInstall ] ) . toEqual ( {
28+ name : "Install locked dependencies" ,
29+ run : "npm ci" ,
30+ } ) ;
31+ expect ( workflowLint . steps [ previewGate ] ) . toEqual ( {
32+ name : "Validate Public Preview content and identity" ,
33+ run : "npm run check:public-preview" ,
34+ } ) ;
35+ expect ( previewGate ) . toBeGreaterThan ( previewInstall ) ;
36+
37+ const releaseVerify = readWorkflow ( "publish.yml" ) . jobs ?. verify ;
38+ expect ( releaseVerify ) . toBeDefined ( ) ;
39+ const trustGate = releaseVerify . steps . findIndex (
40+ ( step ) => step . id === "trust" ,
41+ ) ;
42+ const validationInstall = releaseVerify . steps . findIndex (
43+ ( step ) => step . run === "npm ci --ignore-scripts" ,
44+ ) ;
45+ const releaseGate = releaseVerify . steps . findIndex (
46+ ( step ) =>
47+ typeof step . run === "string" &&
48+ step . run . includes ( "node scripts/validate-release.mjs" ) ,
49+ ) ;
50+ const fullInstall = releaseVerify . steps . findIndex (
51+ ( step , index ) => index > releaseGate && step . run === "npm ci" ,
52+ ) ;
53+ expect ( Object . keys ( releaseVerify . steps [ trustGate ] ) . sort ( ) ) . toEqual ( [
54+ "env" ,
55+ "id" ,
56+ "name" ,
57+ "run" ,
58+ "shell" ,
59+ ] ) ;
60+ expect ( releaseVerify . steps [ trustGate ] ) . toMatchObject ( {
61+ id : "trust" ,
62+ name : "Reject an untrusted release target" ,
63+ shell : "bash" ,
64+ } ) ;
65+ expect ( releaseVerify . steps [ validationInstall ] ) . toEqual ( {
66+ name : "Install validation dependencies without lifecycle scripts" ,
67+ run : "npm ci --ignore-scripts" ,
68+ } ) ;
69+ expect ( validationInstall ) . toBeGreaterThan ( trustGate ) ;
70+ expect ( releaseVerify . steps [ releaseGate ] ) . toEqual ( {
71+ env : {
72+ RELEASE_IS_PRERELEASE : "${{ github.event.release.prerelease }}" ,
73+ RELEASE_TAG : "${{ github.event.release.tag_name }}" ,
74+ } ,
75+ id : "version" ,
76+ name : "Verify release metadata and derive the npm dist-tag" ,
77+ run : [
78+ "set -euo pipefail" ,
79+ "node scripts/validate-release.mjs \\" ,
80+ ' --tag "$RELEASE_TAG" \\' ,
81+ ' --release-prerelease "$RELEASE_IS_PRERELEASE" \\' ,
82+ " --require-final \\" ,
83+ ' --require-releasable-docs >> "$GITHUB_OUTPUT"' ,
84+ "" ,
85+ ] . join ( "\n" ) ,
86+ shell : "bash" ,
87+ } ) ;
88+ expect ( releaseGate ) . toBeGreaterThan ( validationInstall ) ;
89+ expect ( releaseVerify . steps [ fullInstall ] ) . toEqual ( {
90+ name : "Install locked dependencies" ,
91+ run : "npm ci" ,
92+ } ) ;
93+ expect ( fullInstall ) . toBeGreaterThan ( releaseGate ) ;
94+ } ) ;
95+
1896 it ( "runs the live-smoke contract in the locked Node.js 22 and 24 job" , ( ) => {
1997 const workflow = readWorkflow ( ) ;
2098 const locked = workflow . jobs ?. locked ;
0 commit comments