@rightkit/git 0.2.22 → 0.2.24
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/cli/right-git.mjs +0 -0
- package/install.mjs +1 -1
- package/manifest.mjs +286 -52
- package/manifest.schema.json +19 -3
- package/manifest.test.mjs +108 -1
- package/package.json +6 -5
- package/render.test.mjs +258 -25
- package/sync.test.mjs +3 -0
- package/templates/profiles/rust-hybrid/ci.yml +10 -4
- package/templates/profiles/rust-hybrid/release-candidate.yml +105 -7
- package/uninstall.test.mjs +2 -2
package/manifest.test.mjs
CHANGED
|
@@ -32,7 +32,14 @@ test("rust-hybrid supports ci plus a configured unsigned release-candidate lane"
|
|
|
32
32
|
const candidate = {
|
|
33
33
|
...valid,
|
|
34
34
|
lanes: ["ci", "release-candidate"],
|
|
35
|
-
candidate: {
|
|
35
|
+
candidate: {
|
|
36
|
+
buildScript: "ci:candidate",
|
|
37
|
+
checkScript: "ci:candidate-check",
|
|
38
|
+
admissionScript: "release:admit",
|
|
39
|
+
stageSummaryScript: "release:stage-summary",
|
|
40
|
+
artifactRoot: "legion-unsigned",
|
|
41
|
+
os: ["ubuntu-latest"],
|
|
42
|
+
},
|
|
36
43
|
};
|
|
37
44
|
assert.equal(validateManifest(candidate).valid, true);
|
|
38
45
|
assert.equal(validateManifest({ ...candidate, candidate: { ...candidate.candidate, signWindows: true } }).valid, false);
|
|
@@ -53,6 +60,7 @@ test("rust-hybrid supports ci plus a configured unsigned release-candidate lane"
|
|
|
53
60
|
candidate: {
|
|
54
61
|
...windowsCandidate.candidate,
|
|
55
62
|
releaseChain: {
|
|
63
|
+
evidenceVerificationScript: "release:verify-evidence",
|
|
56
64
|
windowsFinalizeScript: "release:finalize:win",
|
|
57
65
|
installedQualificationScript: "release:qualify:installed",
|
|
58
66
|
publishScript: "release:publish:qualified",
|
|
@@ -68,6 +76,103 @@ test("rust-hybrid supports ci plus a configured unsigned release-candidate lane"
|
|
|
68
76
|
assert.equal(validateManifest({ ...releaseChain, candidate: { ...releaseChain.candidate, releaseChain: { ...releaseChain.candidate.releaseChain, publishSecrets: ["TOKEN", "TOKEN"] } } }).valid, false);
|
|
69
77
|
});
|
|
70
78
|
|
|
79
|
+
// Part B fix: admissionScript & stageSummaryScript were moved OUT of the
|
|
80
|
+
// optional candidate.releaseChain object and onto candidate directly,
|
|
81
|
+
// because the admission job and the candidate job's stage-summary
|
|
82
|
+
// init/finalize steps are unconditional parts of the rendered
|
|
83
|
+
// release-candidate template — present for every release-candidate lane,
|
|
84
|
+
// signed or not. This test proves each is independently required with a
|
|
85
|
+
// specific, actionable error, and that the full releaseChain contract
|
|
86
|
+
// (now minus those two promoted fields) is validated key-by-key.
|
|
87
|
+
test("rust-hybrid release-candidate requires admissionScript & stageSummaryScript independent of signing", () => {
|
|
88
|
+
const base = {
|
|
89
|
+
...VALID_MINIMAL,
|
|
90
|
+
profile: "rust-hybrid",
|
|
91
|
+
gate: { path: "scripts/gate.sh" },
|
|
92
|
+
lanes: ["ci", "release-candidate"],
|
|
93
|
+
candidate: {
|
|
94
|
+
buildScript: "ci:candidate",
|
|
95
|
+
checkScript: "ci:candidate-check",
|
|
96
|
+
admissionScript: "release:admit",
|
|
97
|
+
stageSummaryScript: "release:stage-summary",
|
|
98
|
+
artifactRoot: "widgets-unsigned",
|
|
99
|
+
os: ["ubuntu-latest"],
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
assert.equal(validateManifest(base).valid, true, JSON.stringify(validateManifest(base).errors));
|
|
103
|
+
|
|
104
|
+
const { admissionScript, ...withoutAdmission } = base.candidate;
|
|
105
|
+
const missingAdmission = validateManifest({ ...base, candidate: withoutAdmission });
|
|
106
|
+
assert.equal(missingAdmission.valid, false);
|
|
107
|
+
assert.ok(missingAdmission.errors.includes("candidate.admissionScript must be one safe package script name"));
|
|
108
|
+
|
|
109
|
+
const { stageSummaryScript, ...withoutStageSummary } = base.candidate;
|
|
110
|
+
const missingStageSummary = validateManifest({ ...base, candidate: withoutStageSummary });
|
|
111
|
+
assert.equal(missingStageSummary.valid, false);
|
|
112
|
+
assert.ok(missingStageSummary.errors.includes("candidate.stageSummaryScript must be one safe package script name"));
|
|
113
|
+
|
|
114
|
+
// Neither field is accepted inside releaseChain any more.
|
|
115
|
+
const windows = {
|
|
116
|
+
...base,
|
|
117
|
+
matrix: { ...base.matrix, os: ["ubuntu-latest", "windows-2025"] },
|
|
118
|
+
candidate: { ...base.candidate, os: ["windows-2025"], signWindows: true },
|
|
119
|
+
};
|
|
120
|
+
const misplaced = validateManifest({
|
|
121
|
+
...windows,
|
|
122
|
+
candidate: {
|
|
123
|
+
...windows.candidate,
|
|
124
|
+
releaseChain: {
|
|
125
|
+
admissionScript: "release:admit",
|
|
126
|
+
evidenceVerificationScript: "release:verify-evidence",
|
|
127
|
+
windowsFinalizeScript: "release:finalize:win",
|
|
128
|
+
installedQualificationScript: "release:qualify:installed",
|
|
129
|
+
publishScript: "release:publish:qualified",
|
|
130
|
+
qualificationRunnerVariable: "QUALIFICATION_RUNNER_LABEL",
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
assert.equal(misplaced.valid, false);
|
|
135
|
+
assert.ok(misplaced.errors.includes("unknown candidate.releaseChain field: admissionScript"));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Part B fix: evidenceVerificationScript is the one new script that DOES
|
|
139
|
+
// stay inside releaseChain (it runs in the `publish` job, verifying
|
|
140
|
+
// aggregated same-run evidence before the publish step) — required
|
|
141
|
+
// whenever releaseChain is configured, with its own specific error.
|
|
142
|
+
test("candidate.releaseChain requires evidenceVerificationScript with a specific error", () => {
|
|
143
|
+
const windows = {
|
|
144
|
+
...VALID_MINIMAL,
|
|
145
|
+
profile: "rust-hybrid",
|
|
146
|
+
gate: { path: "scripts/gate.sh" },
|
|
147
|
+
lanes: ["ci", "release-candidate"],
|
|
148
|
+
matrix: { ...VALID_MINIMAL.matrix, os: ["ubuntu-latest", "windows-2025"] },
|
|
149
|
+
candidate: {
|
|
150
|
+
buildScript: "ci:candidate",
|
|
151
|
+
checkScript: "ci:candidate-check",
|
|
152
|
+
admissionScript: "release:admit",
|
|
153
|
+
stageSummaryScript: "release:stage-summary",
|
|
154
|
+
artifactRoot: "widgets-unsigned",
|
|
155
|
+
os: ["windows-2025"],
|
|
156
|
+
signWindows: true,
|
|
157
|
+
releaseChain: {
|
|
158
|
+
windowsFinalizeScript: "release:finalize:win",
|
|
159
|
+
installedQualificationScript: "release:qualify:installed",
|
|
160
|
+
publishScript: "release:publish:qualified",
|
|
161
|
+
qualificationRunnerVariable: "QUALIFICATION_RUNNER_LABEL",
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
const result = validateManifest(windows);
|
|
166
|
+
assert.equal(result.valid, false);
|
|
167
|
+
assert.ok(result.errors.includes("candidate.releaseChain.evidenceVerificationScript must be one safe package script name"));
|
|
168
|
+
|
|
169
|
+
const fixed = {
|
|
170
|
+
...windows,
|
|
171
|
+
candidate: { ...windows.candidate, releaseChain: { ...windows.candidate.releaseChain, evidenceVerificationScript: "release:verify-evidence" } },
|
|
172
|
+
};
|
|
173
|
+
assert.equal(validateManifest(fixed).valid, true, JSON.stringify(validateManifest(fixed).errors));
|
|
174
|
+
});
|
|
175
|
+
|
|
71
176
|
test("candidate targets permit native multi-architecture matrices independent of normal CI runners", () => {
|
|
72
177
|
const multiArchitecture = {
|
|
73
178
|
...VALID_MINIMAL,
|
|
@@ -77,6 +182,8 @@ test("candidate targets permit native multi-architecture matrices independent of
|
|
|
77
182
|
candidate: {
|
|
78
183
|
buildScript: "ci:candidate",
|
|
79
184
|
checkScript: "ci:candidate-check",
|
|
185
|
+
admissionScript: "release:admit",
|
|
186
|
+
stageSummaryScript: "release:stage-summary",
|
|
80
187
|
artifactRoot: "widgets-unsigned",
|
|
81
188
|
os: ["windows-2025", "windows-11-arm", "macos-15", "macos-15-intel"],
|
|
82
189
|
targets: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/git",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.24",
|
|
4
4
|
"description": "Public-repo-only GitHub Actions workflow layer: templates, visibility gate, sync/drift/adopt/uninstall for right-git.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
"*.json"
|
|
16
16
|
],
|
|
17
17
|
"sideEffects": false,
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "node --test *.test.mjs"
|
|
20
|
+
},
|
|
18
21
|
"publishConfig": {
|
|
19
22
|
"registry": "https://registry.npmjs.org/",
|
|
20
23
|
"access": "public"
|
|
@@ -24,7 +27,5 @@
|
|
|
24
27
|
"url": "git+https://github.com/bogusyogi/claude.git",
|
|
25
28
|
"directory": "tools/rightkit/packages/git"
|
|
26
29
|
},
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
}
|
|
30
|
+
"packageManager": "pnpm@11.24.0"
|
|
31
|
+
}
|
package/render.test.mjs
CHANGED
|
@@ -61,9 +61,10 @@ test("rust-hybrid ci renders its pinned package manager and gate", () => {
|
|
|
61
61
|
assert.match(content, /--diff-filter=ACDMRTUXB/, "deleted Rust inputs must trigger Rust setup");
|
|
62
62
|
assert.match(content, /\*\.rs\|Cargo\.toml[\s\S]*scripts\/gate\.sh/);
|
|
63
63
|
assert.match(content, /\.rightgit\.json\|\.github\/workflows\/\*\.yml\|\.github\/workflows\/\*\.yaml/);
|
|
64
|
-
assert.equal((content.match(/if: steps\.rust-changes\.outputs\.
|
|
64
|
+
assert.equal((content.match(/if: steps\.rust-changes\.outputs\.required == 'true'/g) ?? []).length, 3);
|
|
65
65
|
assert.match(content, /RIGHT_GIT_RUST_CHANGED: \$\{\{ steps\.rust-changes\.outputs\.changed \}\}/);
|
|
66
|
-
assert.match(content, /
|
|
66
|
+
assert.match(content, /RIGHT_GIT_RUST_REQUIRED: \$\{\{ steps\.rust-changes\.outputs\.required \}\}/);
|
|
67
|
+
assert.match(content, /RUSTC_WRAPPER: \$\{\{ steps\.rust-changes\.outputs\.required == 'true' && 'sccache' \|\| '' \}\}/);
|
|
67
68
|
assert.match(content, /CARGO_INCREMENTAL: "0"/);
|
|
68
69
|
assert.match(content, /CARGO_PROFILE_DEV_DEBUG: "0"/);
|
|
69
70
|
assert.match(content, /CARGO_PROFILE_TEST_DEBUG: "0"/);
|
|
@@ -100,7 +101,14 @@ test("rust-hybrid release candidate renders product scripts, artifact handoff, a
|
|
|
100
101
|
lanes: ["ci", "release-candidate"],
|
|
101
102
|
profile: "rust-hybrid",
|
|
102
103
|
gate: { path: "scripts/gate.sh" },
|
|
103
|
-
candidate: {
|
|
104
|
+
candidate: {
|
|
105
|
+
buildScript: "ci:candidate",
|
|
106
|
+
checkScript: "ci:candidate-check",
|
|
107
|
+
admissionScript: "release:admit",
|
|
108
|
+
stageSummaryScript: "release:stage-summary",
|
|
109
|
+
artifactRoot: "widgets-unsigned",
|
|
110
|
+
os: ["macos-latest", "windows-latest"],
|
|
111
|
+
},
|
|
104
112
|
templateVersion: "1.2.0",
|
|
105
113
|
};
|
|
106
114
|
const content = renderTemplate(
|
|
@@ -109,6 +117,8 @@ test("rust-hybrid release candidate renders product scripts, artifact handoff, a
|
|
|
109
117
|
);
|
|
110
118
|
assert.match(content, /pnpm run ci:candidate/);
|
|
111
119
|
assert.match(content, /pnpm run ci:candidate-check/);
|
|
120
|
+
assert.match(content, /pnpm run release:admit/);
|
|
121
|
+
assert.match(content, /pnpm run release:stage-summary/);
|
|
112
122
|
assert.match(content, /Mozilla-Actions\/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba/);
|
|
113
123
|
assert.match(content, /shared-key: widgets-rust/);
|
|
114
124
|
assert.doesNotMatch(content, /bash \.\/scripts\/gate\.sh/);
|
|
@@ -121,6 +131,12 @@ test("rust-hybrid release candidate renders product scripts, artifact handoff, a
|
|
|
121
131
|
assert.doesNotMatch(content, /rust-changes|RIGHT_GIT_RUST_CHANGED/);
|
|
122
132
|
assert.doesNotMatch(content, /if: steps\.rust-changes/);
|
|
123
133
|
assert.match(content, /uses: dtolnay\/rust-toolchain@[a-f0-9]{40}\n with:/);
|
|
134
|
+
// workflow_dispatch tag-last envelope: no windows-sign/macos-sign/release
|
|
135
|
+
// chain jobs render at all when signing is not configured.
|
|
136
|
+
assert.match(content, /^on:\n workflow_dispatch:/m);
|
|
137
|
+
assert.doesNotMatch(content, /^ {2}(?:windows-sign|macos-sign|installed-qualification|publish):\s*$/m);
|
|
138
|
+
assert.doesNotMatch(content, /run: (?:pnpm|npm|yarn) run undefined\b/);
|
|
139
|
+
assert.doesNotMatch(content, /\$\{\{\s*github\.output\s*\}\}/);
|
|
124
140
|
});
|
|
125
141
|
|
|
126
142
|
test("rust-hybrid cache contract renders every declared Cargo workspace", () => {
|
|
@@ -129,7 +145,7 @@ test("rust-hybrid cache contract renders every declared Cargo workspace", () =>
|
|
|
129
145
|
lanes: ["ci"],
|
|
130
146
|
profile: "rust-hybrid",
|
|
131
147
|
gate: { path: "scripts/gate.sh" },
|
|
132
|
-
rustCache: { workspaces: [".", "apps/studio/src-tauri"] },
|
|
148
|
+
rustCache: { workspaces: [".", "apps/studio/src-tauri"], requiredForGate: true },
|
|
133
149
|
matrix: { os: ["windows-2025"], node: ["22.23.2"] },
|
|
134
150
|
};
|
|
135
151
|
const content = renderTemplate(
|
|
@@ -140,7 +156,8 @@ test("rust-hybrid cache contract renders every declared Cargo workspace", () =>
|
|
|
140
156
|
assert.match(content, /add-job-id-key: false/);
|
|
141
157
|
assert.match(content, /cache-workspace-crates: false/);
|
|
142
158
|
assert.match(content, /cache-on-failure: true/);
|
|
143
|
-
assert.match(content, /
|
|
159
|
+
assert.match(content, /if \[\[ "true" == "true" \]\]; then[\s\S]*required=true/);
|
|
160
|
+
assert.match(content, /RUSTC_WRAPPER: \$\{\{ steps\.rust-changes\.outputs\.required == 'true' && 'sccache' \|\| '' \}\}/);
|
|
144
161
|
});
|
|
145
162
|
|
|
146
163
|
test("rust-hybrid release candidate can opt into OIDC Windows signing without long-lived secrets", () => {
|
|
@@ -149,7 +166,15 @@ test("rust-hybrid release candidate can opt into OIDC Windows signing without lo
|
|
|
149
166
|
lanes: ["ci", "release-candidate"],
|
|
150
167
|
profile: "rust-hybrid",
|
|
151
168
|
gate: { path: "scripts/gate.sh" },
|
|
152
|
-
candidate: {
|
|
169
|
+
candidate: {
|
|
170
|
+
buildScript: "ci:candidate",
|
|
171
|
+
checkScript: "ci:candidate-check",
|
|
172
|
+
admissionScript: "release:admit",
|
|
173
|
+
stageSummaryScript: "release:stage-summary",
|
|
174
|
+
artifactRoot: "widgets-unsigned",
|
|
175
|
+
os: ["windows-2025"],
|
|
176
|
+
signWindows: true,
|
|
177
|
+
},
|
|
153
178
|
matrix: { os: ["ubuntu-24.04", "windows-2025"], node: ["22.23.2"] },
|
|
154
179
|
templateVersion: "1.3.0",
|
|
155
180
|
};
|
|
@@ -158,16 +183,26 @@ test("rust-hybrid release candidate can opt into OIDC Windows signing without lo
|
|
|
158
183
|
buildRenderContext(manifest, "/repos/widgets"),
|
|
159
184
|
);
|
|
160
185
|
assert.match(content, /environment: release/);
|
|
161
|
-
|
|
186
|
+
// Tag-last contract: signing is gated on the admitted, same-run
|
|
187
|
+
// signed_qualification OUTPUT of the `admission` job — never on a ref/tag
|
|
188
|
+
// and never on the raw workflow_dispatch input directly.
|
|
189
|
+
assert.match(content, /windows-sign:\n needs: \[admission, candidate\]\n if: \$\{\{ needs\.admission\.outputs\.signed_qualification == 'true' \}\}/);
|
|
190
|
+
assert.doesNotMatch(content, /startsWith\(github\.ref, 'refs\/tags\/v'\)/);
|
|
162
191
|
assert.match(content, /id-token: write/);
|
|
163
192
|
assert.match(content, /azure\/login@[a-f0-9]{40}/);
|
|
164
193
|
assert.match(content, /vars\.AZURE_CLIENT_ID/);
|
|
165
194
|
assert.match(content, /right-release build --platform win --skip-checks/);
|
|
166
195
|
assert.match(content, /Artifact Signing client hash mismatch/);
|
|
167
|
-
|
|
196
|
+
// Every downstream job (admission, candidate, and windows-sign here) checks
|
|
197
|
+
// out the exact admitted revision by SHA, never by resolving HEAD after a
|
|
198
|
+
// ref-triggered checkout.
|
|
199
|
+
assert.match(content, /windows-sign:[\s\S]*?ref: \$\{\{ needs\.admission\.outputs\.source_revision \}\}\n\s+persist-credentials: false/);
|
|
200
|
+
assert.doesNotMatch(content, /git rev-parse HEAD/);
|
|
168
201
|
assert.ok(content.includes("LEGION_SOURCE_REVISION: ${{ env.RIGHT_GIT_SOURCE_REVISION }}"));
|
|
169
202
|
assert.doesNotMatch(content, /LEGION_SOURCE_REVISION: \$\{\{ github\.sha \}\}/);
|
|
170
203
|
assert.doesNotMatch(content, /secrets\.|client-secret|private.key/i);
|
|
204
|
+
assert.doesNotMatch(content, /run: (?:pnpm|npm|yarn) run undefined\b/);
|
|
205
|
+
assert.doesNotMatch(content, /\$\{\{\s*github\.output\s*\}\}/);
|
|
171
206
|
});
|
|
172
207
|
|
|
173
208
|
test("rust-hybrid release candidate can opt into protected macOS signing & notarization", () => {
|
|
@@ -176,7 +211,15 @@ test("rust-hybrid release candidate can opt into protected macOS signing & notar
|
|
|
176
211
|
lanes: ["ci", "release-candidate"],
|
|
177
212
|
profile: "rust-hybrid",
|
|
178
213
|
gate: { path: "scripts/gate.sh" },
|
|
179
|
-
candidate: {
|
|
214
|
+
candidate: {
|
|
215
|
+
buildScript: "ci:candidate",
|
|
216
|
+
checkScript: "ci:candidate-check",
|
|
217
|
+
admissionScript: "release:admit",
|
|
218
|
+
stageSummaryScript: "release:stage-summary",
|
|
219
|
+
artifactRoot: "widgets-unsigned",
|
|
220
|
+
os: ["macos-15"],
|
|
221
|
+
signMacos: true,
|
|
222
|
+
},
|
|
180
223
|
matrix: { os: ["ubuntu-24.04", "macos-15"], node: ["22.23.2"] },
|
|
181
224
|
templateVersion: "1.3.0",
|
|
182
225
|
};
|
|
@@ -185,17 +228,21 @@ test("rust-hybrid release candidate can opt into protected macOS signing & notar
|
|
|
185
228
|
buildRenderContext(manifest, "/repos/widgets"),
|
|
186
229
|
);
|
|
187
230
|
assert.match(content, /macos-sign:/);
|
|
188
|
-
assert.match(content, /macos-sign:\n needs: \[candidate\]\n if: \$\{\{
|
|
231
|
+
assert.match(content, /macos-sign:\n needs: \[admission, candidate\]\n if: \$\{\{ needs\.admission\.outputs\.signed_qualification == 'true' \}\}/);
|
|
232
|
+
assert.doesNotMatch(content, /startsWith\(github\.ref, 'refs\/tags\/v'\)/);
|
|
189
233
|
assert.match(content, /environment: release/);
|
|
190
234
|
assert.match(content, /secrets\.APPLE_CERTIFICATE_BASE64/);
|
|
191
235
|
assert.match(content, /temporary keychain/i);
|
|
192
236
|
assert.match(content, /right-release build --platform mac --skip-checks/);
|
|
193
237
|
assert.match(content, /secrets\.APPLE_API_KEY_BASE64/);
|
|
194
|
-
assert.match(content, /
|
|
238
|
+
assert.match(content, /macos-sign:[\s\S]*?ref: \$\{\{ needs\.admission\.outputs\.source_revision \}\}\n\s+persist-credentials: false/);
|
|
239
|
+
assert.doesNotMatch(content, /git rev-parse HEAD/);
|
|
195
240
|
assert.ok(content.includes("LEGION_SOURCE_REVISION: ${{ env.RIGHT_GIT_SOURCE_REVISION }}"));
|
|
196
241
|
assert.doesNotMatch(content, /LEGION_SOURCE_REVISION: \$\{\{ github\.sha \}\}/);
|
|
197
242
|
assert.match(content, /security delete-keychain/);
|
|
198
243
|
assert.doesNotMatch(content, /pull_request_target/);
|
|
244
|
+
assert.doesNotMatch(content, /run: (?:pnpm|npm|yarn) run undefined\b/);
|
|
245
|
+
assert.doesNotMatch(content, /\$\{\{\s*github\.output\s*\}\}/);
|
|
199
246
|
});
|
|
200
247
|
|
|
201
248
|
test("rust-hybrid multi-architecture candidates bind every native target to exact artifacts and signing", () => {
|
|
@@ -208,6 +255,8 @@ test("rust-hybrid multi-architecture candidates bind every native target to exac
|
|
|
208
255
|
candidate: {
|
|
209
256
|
buildScript: "ci:candidate",
|
|
210
257
|
checkScript: "ci:candidate-check",
|
|
258
|
+
admissionScript: "release:admit",
|
|
259
|
+
stageSummaryScript: "release:stage-summary",
|
|
211
260
|
artifactRoot: "widgets-unsigned",
|
|
212
261
|
os: ["windows-2025", "windows-11-arm", "macos-15", "macos-15-intel"],
|
|
213
262
|
targets: [
|
|
@@ -233,10 +282,15 @@ test("rust-hybrid multi-architecture candidates bind every native target to exac
|
|
|
233
282
|
assert.doesNotMatch(content, /bash \.\/scripts\/gate\.sh/);
|
|
234
283
|
assert.match(content, /windows-sign:[\s\S]*matrix:[\s\S]*windows-11-arm/);
|
|
235
284
|
assert.match(content, /macos-sign:[\s\S]*matrix:[\s\S]*macos-15-intel/);
|
|
236
|
-
|
|
237
|
-
assert.match(content, /name: widgets-
|
|
238
|
-
assert.match(content, /name: widgets-signed-
|
|
239
|
-
assert.
|
|
285
|
+
// Every artifact name is run- and identity-bound: <prefix>-<run_id>-<run_attempt>-<kind>-...-<version>-<source_revision>.
|
|
286
|
+
assert.match(content, /name: widgets-\$\{\{ github\.run_id \}\}-\$\{\{ github\.run_attempt \}\}-candidate-\$\{\{ matrix\.platform \}\}-\$\{\{ matrix\.architecture \}\}-\$\{\{ needs\.admission\.outputs\.version \}\}-\$\{\{ needs\.admission\.outputs\.source_revision \}\}/);
|
|
287
|
+
assert.match(content, /name: widgets-\$\{\{ github\.run_id \}\}-\$\{\{ github\.run_attempt \}\}-signed-windows-\$\{\{ matrix\.architecture \}\}-\$\{\{ needs\.admission\.outputs\.version \}\}-\$\{\{ needs\.admission\.outputs\.source_revision \}\}/);
|
|
288
|
+
assert.match(content, /name: widgets-\$\{\{ github\.run_id \}\}-\$\{\{ github\.run_attempt \}\}-signed-macos-\$\{\{ matrix\.architecture \}\}-\$\{\{ needs\.admission\.outputs\.version \}\}-\$\{\{ needs\.admission\.outputs\.source_revision \}\}/);
|
|
289
|
+
// Exact-count regression guard across the candidate job's build/check/
|
|
290
|
+
// stage-summary steps and the windows-sign/macos-sign stage-summary steps.
|
|
291
|
+
assert.equal((content.match(/RIGHT_GIT_RELEASE_ARCHITECTURE: \$\{\{ matrix\.architecture \}\}/g) ?? []).length, 10);
|
|
292
|
+
assert.doesNotMatch(content, /run: (?:pnpm|npm|yarn) run undefined\b/);
|
|
293
|
+
assert.doesNotMatch(content, /\$\{\{\s*github\.output\s*\}\}/);
|
|
240
294
|
});
|
|
241
295
|
|
|
242
296
|
test("rust-hybrid release chain preserves signed candidate, installed qualification, then publish", () => {
|
|
@@ -248,11 +302,14 @@ test("rust-hybrid release chain preserves signed candidate, installed qualificat
|
|
|
248
302
|
candidate: {
|
|
249
303
|
buildScript: "ci:candidate",
|
|
250
304
|
checkScript: "ci:candidate-check",
|
|
305
|
+
admissionScript: "release:admit",
|
|
306
|
+
stageSummaryScript: "release:stage-summary",
|
|
251
307
|
artifactRoot: "widgets-unsigned",
|
|
252
308
|
os: ["windows-2025", "macos-15"],
|
|
253
309
|
signWindows: true,
|
|
254
310
|
signMacos: true,
|
|
255
311
|
releaseChain: {
|
|
312
|
+
evidenceVerificationScript: "release:verify-evidence",
|
|
256
313
|
windowsFinalizeScript: "release:finalize:win",
|
|
257
314
|
macosFinalizeScript: "release:finalize:mac",
|
|
258
315
|
installedQualificationScript: "release:qualify:installed",
|
|
@@ -270,26 +327,202 @@ test("rust-hybrid release chain preserves signed candidate, installed qualificat
|
|
|
270
327
|
);
|
|
271
328
|
assert.match(content, /windows-sign:[\s\S]*pnpm run release:finalize:win/);
|
|
272
329
|
assert.match(content, /macos-sign:[\s\S]*pnpm run release:finalize:mac/);
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
assert.match(content, /
|
|
277
|
-
assert.
|
|
330
|
+
// Tag-last contract: every gate reads the ADMITTED, same-run outputs of
|
|
331
|
+
// the `admission` job — never the raw workflow_dispatch input directly,
|
|
332
|
+
// and never a ref/tag.
|
|
333
|
+
assert.match(content, /signed_qualification:\n description: Run protected signing, installed qualification, & evidence verification/);
|
|
334
|
+
assert.doesNotMatch(content, /inputs\.signed_qualification == true/);
|
|
335
|
+
assert.doesNotMatch(content, /startsWith\(github\.ref, 'refs\/tags\/v'\)/);
|
|
336
|
+
assert.match(content, /windows-sign:\n needs: \[admission, candidate\]\n if: \$\{\{ needs\.admission\.outputs\.signed_qualification == 'true' \}\}/);
|
|
337
|
+
assert.match(content, /macos-sign:\n needs: \[admission, candidate\]\n if: \$\{\{ needs\.admission\.outputs\.signed_qualification == 'true' \}\}/);
|
|
338
|
+
assert.match(content, /installed-qualification:\n needs: \[admission, candidate, windows-sign\]/);
|
|
339
|
+
assert.match(content, /installed-qualification:[\s\S]*?if: \$\{\{ always\(\) && needs\.admission\.outputs\.signed_qualification == 'true' && needs\.candidate\.result == 'success' \}\}/);
|
|
278
340
|
assert.match(content, /installed_qualification_failed/);
|
|
279
341
|
assert.match(content, /shell: pwsh\n run: \|\n throw 'installed_qualification_failed \{ reason: prerequisites_absent, detail: "same-run Windows signing\/finalization did not succeed" \}'/);
|
|
280
342
|
assert.doesNotMatch(content, /^ run: throw 'installed_qualification_failed .*detail: /m);
|
|
281
343
|
assert.match(content, /pnpm run release:qualify:installed/);
|
|
282
344
|
assert.match(content, /installed-qualification:[\s\S]*GH_TOKEN: \$\{\{ github\.token \}\}/);
|
|
283
|
-
assert.match(content, /publish:\n needs: \[installed-qualification, macos-sign\]/);
|
|
284
|
-
assert.match(content, /publish:[\s\S]
|
|
285
|
-
|
|
286
|
-
assert.
|
|
345
|
+
assert.match(content, /publish:\n needs: \[admission, installed-qualification, macos-sign\]/);
|
|
346
|
+
assert.match(content, /publish:[\s\S]*?if: \$\{\{ needs\.admission\.outputs\.publish == 'true' && needs\.installed-qualification\.result == 'success' && needs\.macos-sign\.result == 'success' \}\}/);
|
|
347
|
+
const publishJobIndex = content.search(/^ {2}publish:\n {4}needs:/m);
|
|
348
|
+
assert.ok(publishJobIndex >= 0, "publish job must be present");
|
|
349
|
+
assert.doesNotMatch(content.slice(publishJobIndex), /inputs\.signed_qualification|inputs\.publish/);
|
|
287
350
|
assert.match(content, /contents: write/);
|
|
351
|
+
// Publish is the sole creator of the tag/release: it must not require a
|
|
352
|
+
// pre-existing tag anywhere in its gate.
|
|
353
|
+
assert.doesNotMatch(content.slice(publishJobIndex), /refs\/tags/);
|
|
354
|
+
assert.match(content, /pnpm run release:verify-evidence/);
|
|
355
|
+
assert.match(content, /Verify aggregated release evidence[\s\S]*?pnpm run release:verify-evidence/);
|
|
288
356
|
assert.match(content, /pnpm run release:publish:qualified/);
|
|
289
357
|
assert.match(content, /CLOUDFLARE_API_TOKEN: \$\{\{ secrets\.CLOUDFLARE_API_TOKEN \}\}/);
|
|
290
|
-
|
|
358
|
+
// The old ref-triggered "resolve HEAD after checkout" trick is gone
|
|
359
|
+
// entirely for the releaseChain path — every job instead checks out the
|
|
360
|
+
// exact admitted SHA from the admission job's own output.
|
|
361
|
+
assert.doesNotMatch(content, /git rev-parse HEAD/);
|
|
291
362
|
assert.doesNotMatch(content, /RIGHT_GIT_SOURCE_REVISION: \$\{\{ github\.sha \}\}/);
|
|
363
|
+
// windows-sign, macos-sign, installed-qualification, publish all check out
|
|
364
|
+
// the admitted revision directly followed by persist-credentials: false;
|
|
365
|
+
// candidate does too but with a fetch-depth: 0 line between them (matched
|
|
366
|
+
// separately below), and admission checks out ${{ inputs.source_revision }}
|
|
367
|
+
// (it IS the admission job — there is no needs.admission yet).
|
|
368
|
+
assert.equal((content.match(/ref: \$\{\{ needs\.admission\.outputs\.source_revision \}\}\n\s+persist-credentials: false/g) ?? []).length, 4, "windows-sign, macos-sign, installed-qualification, publish should all pin the admitted revision");
|
|
369
|
+
assert.match(content, /ref: \$\{\{ needs\.admission\.outputs\.source_revision \}\}\n\s+fetch-depth: 0\n\s+persist-credentials: false/, "candidate must also pin the admitted revision");
|
|
370
|
+
assert.match(content, /ref: \$\{\{ inputs\.source_revision \}\}\n\s+fetch-depth: 0\n\s+persist-credentials: false/, "admission must check out the exact dispatch-supplied revision");
|
|
292
371
|
assert.doesNotMatch(content.slice(0, content.indexOf(" windows-sign:")), /secrets\.|id-token: write|contents: write/);
|
|
372
|
+
// Downstream download-artifact names must match exactly what windows-sign/
|
|
373
|
+
// macos-sign uploaded (see the multi-architecture test) — this is the
|
|
374
|
+
// regression guard for the artifact-name mismatch bug.
|
|
375
|
+
const windowsSignedArtifact = "widgets-${{ github.run_id }}-${{ github.run_attempt }}-signed-windows-x86_64-${{ needs.admission.outputs.version }}-${{ needs.admission.outputs.source_revision }}";
|
|
376
|
+
const macosSignedArtifact = "widgets-${{ github.run_id }}-${{ github.run_attempt }}-signed-macos-arm64-${{ needs.admission.outputs.version }}-${{ needs.admission.outputs.source_revision }}";
|
|
377
|
+
assert.equal((content.match(new RegExp(windowsSignedArtifact.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g")) ?? []).length, 2, "windows-sign upload + installed-qualification/publish download must share one artifact name");
|
|
378
|
+
assert.ok(content.includes(macosSignedArtifact), "publish must download the exact macOS artifact macos-sign uploaded");
|
|
379
|
+
assert.doesNotMatch(content, /run: (?:pnpm|npm|yarn) run undefined\b/);
|
|
380
|
+
assert.doesNotMatch(content, /\$\{\{\s*github\.output\s*\}\}/);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// Part E regression guard: every `run: <pm> run <script>` line in a
|
|
384
|
+
// rendered full release-chain workflow must name a script the manifest
|
|
385
|
+
// actually declares. This is the general form of the `pnpm run undefined`
|
|
386
|
+
// class of bug — it would also catch a typo'd or stale script reference
|
|
387
|
+
// even when it resolves to a real (but wrong) string, which the fail-closed
|
|
388
|
+
// undefined/null check in buildRenderContext cannot.
|
|
389
|
+
test("every 'run <script>' in a full release-chain workflow names a script the manifest declares", () => {
|
|
390
|
+
const manifest = {
|
|
391
|
+
...FULL_MANIFEST,
|
|
392
|
+
lanes: ["ci", "release-candidate"],
|
|
393
|
+
profile: "rust-hybrid",
|
|
394
|
+
gate: { path: "scripts/gate.sh" },
|
|
395
|
+
candidate: {
|
|
396
|
+
buildScript: "ci:candidate",
|
|
397
|
+
checkScript: "ci:candidate-check",
|
|
398
|
+
admissionScript: "release:admit",
|
|
399
|
+
stageSummaryScript: "release:stage-summary",
|
|
400
|
+
artifactRoot: "widgets-unsigned",
|
|
401
|
+
os: ["windows-2025", "macos-15"],
|
|
402
|
+
signWindows: true,
|
|
403
|
+
signMacos: true,
|
|
404
|
+
releaseChain: {
|
|
405
|
+
evidenceVerificationScript: "release:verify-evidence",
|
|
406
|
+
windowsFinalizeScript: "release:finalize:win",
|
|
407
|
+
macosFinalizeScript: "release:finalize:mac",
|
|
408
|
+
installedQualificationScript: "release:qualify:installed",
|
|
409
|
+
publishScript: "release:publish:qualified",
|
|
410
|
+
qualificationRunnerVariable: "QUALIFICATION_RUNNER_LABEL",
|
|
411
|
+
publishSecrets: ["CLOUDFLARE_API_TOKEN"],
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
matrix: { os: ["ubuntu-24.04", "windows-2025", "macos-15"], node: ["22.23.2"] },
|
|
415
|
+
templateVersion: "1.4.0",
|
|
416
|
+
};
|
|
417
|
+
const content = renderTemplate(
|
|
418
|
+
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
419
|
+
buildRenderContext(manifest, "/repos/widgets"),
|
|
420
|
+
);
|
|
421
|
+
const declaredScripts = new Set([
|
|
422
|
+
manifest.candidate.buildScript,
|
|
423
|
+
manifest.candidate.checkScript,
|
|
424
|
+
manifest.candidate.admissionScript,
|
|
425
|
+
manifest.candidate.stageSummaryScript,
|
|
426
|
+
manifest.candidate.releaseChain.evidenceVerificationScript,
|
|
427
|
+
manifest.candidate.releaseChain.windowsFinalizeScript,
|
|
428
|
+
manifest.candidate.releaseChain.macosFinalizeScript,
|
|
429
|
+
manifest.candidate.releaseChain.installedQualificationScript,
|
|
430
|
+
manifest.candidate.releaseChain.publishScript,
|
|
431
|
+
]);
|
|
432
|
+
const runLines = content.match(/run: (?:pnpm|npm|yarn) run \S+/g) ?? [];
|
|
433
|
+
assert.ok(runLines.length > 0, "expected at least one package-script run line");
|
|
434
|
+
for (const line of runLines) {
|
|
435
|
+
const script = line.split(" run ").pop();
|
|
436
|
+
assert.ok(declaredScripts.has(script), `rendered step runs undeclared script: ${line}`);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Downstream-consumer gap fix: the evidence verifier can only prove a stage
|
|
441
|
+
// ran and succeeded if that stage produces a stage summary AND the publish
|
|
442
|
+
// job actually downloads it. This test proves all four authoritative
|
|
443
|
+
// stages (candidate, windows-sign, macos-sign, installed-qualification)
|
|
444
|
+
// produce a summary, that installed-qualification wraps itself in
|
|
445
|
+
// init/finalize like every other stage, that publish downloads the
|
|
446
|
+
// complete same-run set into distinct subdirectories (no collisions
|
|
447
|
+
// between candidate's two platform legs), and that evidence is bound to
|
|
448
|
+
// this exact run (not merely version+source_revision) via RIGHT_GIT_RUN_ID/
|
|
449
|
+
// RIGHT_GIT_RUN_ATTEMPT on both the verification and publish steps.
|
|
450
|
+
test("release chain: installed-qualification has a stage summary and publish downloads the complete same-run evidence set", () => {
|
|
451
|
+
const manifest = {
|
|
452
|
+
...FULL_MANIFEST,
|
|
453
|
+
lanes: ["ci", "release-candidate"],
|
|
454
|
+
profile: "rust-hybrid",
|
|
455
|
+
gate: { path: "scripts/gate.sh" },
|
|
456
|
+
candidate: {
|
|
457
|
+
buildScript: "ci:candidate",
|
|
458
|
+
checkScript: "ci:candidate-check",
|
|
459
|
+
admissionScript: "release:admit",
|
|
460
|
+
stageSummaryScript: "release:stage-summary",
|
|
461
|
+
artifactRoot: "widgets-unsigned",
|
|
462
|
+
os: ["windows-2025", "macos-15"],
|
|
463
|
+
signWindows: true,
|
|
464
|
+
signMacos: true,
|
|
465
|
+
releaseChain: {
|
|
466
|
+
evidenceVerificationScript: "release:verify-evidence",
|
|
467
|
+
windowsFinalizeScript: "release:finalize:win",
|
|
468
|
+
macosFinalizeScript: "release:finalize:mac",
|
|
469
|
+
installedQualificationScript: "release:qualify:installed",
|
|
470
|
+
publishScript: "release:publish:qualified",
|
|
471
|
+
qualificationRunnerVariable: "QUALIFICATION_RUNNER_LABEL",
|
|
472
|
+
publishSecrets: ["CLOUDFLARE_API_TOKEN"],
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
matrix: { os: ["ubuntu-24.04", "windows-2025", "macos-15"], node: ["22.23.2"] },
|
|
476
|
+
templateVersion: "1.4.0",
|
|
477
|
+
};
|
|
478
|
+
const content = renderTemplate(
|
|
479
|
+
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
480
|
+
buildRenderContext(manifest, "/repos/widgets"),
|
|
481
|
+
);
|
|
482
|
+
const installedQualificationJob = content.slice(
|
|
483
|
+
content.search(/^ {2}installed-qualification:\n {4}needs:/m),
|
|
484
|
+
content.search(/^ {2}publish:\n {4}needs:/m),
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
// Point 1: installed-qualification wraps itself in stage-summary
|
|
488
|
+
// init/finalize like candidate/windows-sign/macos-sign, with the correct
|
|
489
|
+
// authoritative stage identifier, and finalize runs unconditionally.
|
|
490
|
+
assert.match(installedQualificationJob, /name: Initialize installed-qualification stage summary\n\s+run: pnpm run release:stage-summary\n\s+env:\n\s+RIGHT_GIT_STAGE_ACTION: init/);
|
|
491
|
+
assert.match(installedQualificationJob, /RIGHT_GIT_STAGE: installed-qualification/);
|
|
492
|
+
assert.match(installedQualificationJob, /name: Finalize installed-qualification stage summary\n\s+if: \$\{\{ always\(\) \}\}\n\s+run: pnpm run release:stage-summary\n\s+env:\n\s+RIGHT_GIT_STAGE_ACTION: finalize/);
|
|
493
|
+
assert.equal((installedQualificationJob.match(/RIGHT_GIT_STAGE: installed-qualification/g) ?? []).length, 2, "both init and finalize must carry the installed-qualification stage id");
|
|
494
|
+
assert.match(installedQualificationJob, /name: Upload installed-qualification stage summary\n\s+if: \$\{\{ always\(\) \}\}/);
|
|
495
|
+
|
|
496
|
+
const publishJob = content.slice(content.search(/^ {2}publish:\n {4}needs:/m));
|
|
497
|
+
|
|
498
|
+
// Point 2: publish downloads the complete same-run evidence set — both
|
|
499
|
+
// candidate legs (windows + macos) in distinct subdirectories, plus
|
|
500
|
+
// windows-sign, macos-sign, and installed-qualification summaries.
|
|
501
|
+
assert.match(publishJob, /Download same-run candidate \(Windows\) stage summary[\s\S]*?path: \$\{\{ runner\.temp \}\}\/right-git-release\/stage-summaries\/candidate-windows/);
|
|
502
|
+
assert.match(publishJob, /Download same-run candidate \(macOS\) stage summary[\s\S]*?path: \$\{\{ runner\.temp \}\}\/right-git-release\/stage-summaries\/candidate-macos/);
|
|
503
|
+
assert.match(publishJob, /Download same-run Windows signing stage summary[\s\S]*?path: \$\{\{ runner\.temp \}\}\/right-git-release\/stage-summaries\/windows-sign/);
|
|
504
|
+
assert.match(publishJob, /Download same-run macOS signing stage summary[\s\S]*?path: \$\{\{ runner\.temp \}\}\/right-git-release\/stage-summaries\/macos-sign/);
|
|
505
|
+
assert.match(publishJob, /Download same-run installed-qualification stage summary[\s\S]*?path: \$\{\{ runner\.temp \}\}\/right-git-release\/stage-summaries\/installed-qualification/);
|
|
506
|
+
// Distinct subdirectories: no two downloads share a path.
|
|
507
|
+
const downloadPaths = [...publishJob.matchAll(/path: (\$\{\{ runner\.temp \}\}\/right-git-release\/stage-summaries\/\S+)/g)].map((m) => m[1]);
|
|
508
|
+
assert.equal(downloadPaths.length, 5);
|
|
509
|
+
assert.equal(new Set(downloadPaths).size, 5, "every stage-summary download must use a distinct subdirectory");
|
|
510
|
+
|
|
511
|
+
// Point 3: both the evidence-verification step and the publish step carry
|
|
512
|
+
// the full run-identity contract (RIGHT_GIT_STAGE_SUMMARY_ROOT, run id,
|
|
513
|
+
// run attempt) alongside version/source_revision — evidence is bound to
|
|
514
|
+
// THIS run, not merely reusable across runs of the same version+SHA.
|
|
515
|
+
const verifyStep = publishJob.slice(publishJob.indexOf("Verify aggregated release evidence"), publishJob.indexOf("Publish qualified signed release"));
|
|
516
|
+
assert.match(verifyStep, /RIGHT_GIT_STAGE_SUMMARY_ROOT: \$\{\{ runner\.temp \}\}\/right-git-release\/stage-summaries/);
|
|
517
|
+
assert.match(verifyStep, /RIGHT_GIT_RUN_ID: \$\{\{ github\.run_id \}\}/);
|
|
518
|
+
assert.match(verifyStep, /RIGHT_GIT_RUN_ATTEMPT: \$\{\{ github\.run_attempt \}\}/);
|
|
519
|
+
const publishStep = publishJob.slice(publishJob.indexOf("Publish qualified signed release"));
|
|
520
|
+
assert.match(publishStep, /RIGHT_GIT_STAGE_SUMMARY_ROOT: \$\{\{ runner\.temp \}\}\/right-git-release\/stage-summaries/);
|
|
521
|
+
assert.match(publishStep, /RIGHT_GIT_RUN_ID: \$\{\{ github\.run_id \}\}/);
|
|
522
|
+
assert.match(publishStep, /RIGHT_GIT_RUN_ATTEMPT: \$\{\{ github\.run_attempt \}\}/);
|
|
523
|
+
|
|
524
|
+
assert.doesNotMatch(content, /run: (?:pnpm|npm|yarn) run undefined\b/);
|
|
525
|
+
assert.doesNotMatch(content, /\$\{\{\s*github\.output\s*\}\}/);
|
|
293
526
|
});
|
|
294
527
|
|
|
295
528
|
test("render determinism: same manifest renders byte-identical output every time", () => {
|
package/sync.test.mjs
CHANGED
|
@@ -67,11 +67,14 @@ test("sync rejects a Membrane-like release chain without root @rightkit/release"
|
|
|
67
67
|
candidate: {
|
|
68
68
|
buildScript: "release:candidate:build",
|
|
69
69
|
checkScript: "release:candidate:check",
|
|
70
|
+
admissionScript: "release:admit",
|
|
71
|
+
stageSummaryScript: "release:stage-summary",
|
|
70
72
|
artifactRoot: "membrane-release-candidate",
|
|
71
73
|
os: ["windows-2025", "macos-15"],
|
|
72
74
|
signWindows: true,
|
|
73
75
|
signMacos: true,
|
|
74
76
|
releaseChain: {
|
|
77
|
+
evidenceVerificationScript: "release:verify-evidence",
|
|
75
78
|
windowsFinalizeScript: "release:finalize:windows",
|
|
76
79
|
macosFinalizeScript: "release:finalize:macos",
|
|
77
80
|
installedQualificationScript: "release:qualify:installed",
|
|
@@ -54,17 +54,22 @@ jobs:
|
|
|
54
54
|
fi
|
|
55
55
|
fi
|
|
56
56
|
printf 'changed=%s\n' "$changed" >> "$GITHUB_OUTPUT"
|
|
57
|
+
required="$changed"
|
|
58
|
+
if [[ "${RUST_REQUIRED_FOR_GATE}" == "true" ]]; then
|
|
59
|
+
required=true
|
|
60
|
+
fi
|
|
61
|
+
printf 'required=%s\n' "$required" >> "$GITHUB_OUTPUT"
|
|
57
62
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
58
63
|
with:
|
|
59
64
|
node-version: ${{ matrix.node }}
|
|
60
65
|
package-manager-cache: false
|
|
61
66
|
${PACKAGE_MANAGER_SETUP}
|
|
62
67
|
- uses: dtolnay/rust-toolchain@f8be11a05b1d4f3fcebe6410cc16743212b999b0
|
|
63
|
-
if: steps.rust-changes.outputs.
|
|
68
|
+
if: steps.rust-changes.outputs.required == 'true'
|
|
64
69
|
with:
|
|
65
70
|
components: rustfmt, clippy
|
|
66
71
|
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
|
67
|
-
if: steps.rust-changes.outputs.
|
|
72
|
+
if: steps.rust-changes.outputs.required == 'true'
|
|
68
73
|
with:
|
|
69
74
|
workspaces: |
|
|
70
75
|
${RUST_CACHE_WORKSPACES}
|
|
@@ -74,14 +79,15 @@ ${RUST_CACHE_WORKSPACES}
|
|
|
74
79
|
cache-on-failure: true
|
|
75
80
|
save-if: ${{ github.event_name != 'pull_request' }}
|
|
76
81
|
- uses: Mozilla-Actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
|
77
|
-
if: steps.rust-changes.outputs.
|
|
82
|
+
if: steps.rust-changes.outputs.required == 'true'
|
|
78
83
|
with:
|
|
79
84
|
version: "v0.16.0"
|
|
80
85
|
- run: bash ./${GATE_PATH}
|
|
81
86
|
env:
|
|
82
87
|
RIGHT_GIT_RUST_CHANGED: ${{ steps.rust-changes.outputs.changed }}
|
|
88
|
+
RIGHT_GIT_RUST_REQUIRED: ${{ steps.rust-changes.outputs.required }}
|
|
83
89
|
SCCACHE_GHA_ENABLED: "true"
|
|
84
|
-
RUSTC_WRAPPER: ${{ steps.rust-changes.outputs.
|
|
90
|
+
RUSTC_WRAPPER: ${{ steps.rust-changes.outputs.required == 'true' && 'sccache' || '' }}
|
|
85
91
|
CARGO_INCREMENTAL: "0"
|
|
86
92
|
CARGO_PROFILE_DEV_DEBUG: "0"
|
|
87
93
|
CARGO_PROFILE_TEST_DEBUG: "0"
|