@rightkit/git 0.2.5 → 0.2.7
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 +2 -2
- package/manifest.mjs +193 -1
- package/manifest.schema.json +3 -1
- package/manifest.test.mjs +9 -0
- package/package.json +1 -1
- package/render.test.mjs +56 -1
- package/templates/profiles/rust-hybrid/release-candidate.yml +9 -4
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Sync records exact prior bytes, refuses unmanaged same-name workflows, & preflig
|
|
|
23
23
|
|
|
24
24
|
## Credential posture (read before enabling package-manager publishing)
|
|
25
25
|
|
|
26
|
-
- **
|
|
26
|
+
- **Signing credentials stay environment-scoped.** Opt-in Windows candidates use GitHub OIDC with Azure Artifact Signing. Opt-in macOS candidates use a temporary runner keychain plus Apple API-key notarization. npm publishing uses short-lived OIDC.
|
|
27
27
|
- **Exception, disclosed:** the optional, opt-in `publish-package-managers` lane *does* require two owner-provisioned, long-lived tokens in GitHub Actions `secrets.*` (`HOMEBREW_TAP_TOKEN`, `WINGET_CREATE_GITHUB_TOKEN`) to open tap/manifest PRs. These are real bearer tokens, public-repo-scoped by the visibility gate but not zero-exposure. The "no signing credential in CI" invariant is specifically about code-signing material; it does not mean "no credential of any kind."
|
|
28
28
|
|
|
29
29
|
Manifest input is treated as untrusted: every field flowing into rendered YAML is charset-allowlisted, so a manifest cannot inject `${{ secrets.* }}` or shell/expression syntax.
|
|
@@ -38,4 +38,4 @@ Node builtins only; no runtime dependencies.
|
|
|
38
38
|
|
|
39
39
|
## Profiles
|
|
40
40
|
|
|
41
|
-
`profile` defaults to `node`. `rust-hybrid` requires `ci`, accepts optional `release-candidate`, installs pinned Node plus Rust, then runs product-owned package scripts. Candidate configuration is `{ buildScript, checkScript, artifactRoot, os }`; `os` selects supported release hosts from main matrix. RightGit
|
|
41
|
+
`profile` defaults to `node`. `rust-hybrid` requires `ci`, accepts optional `release-candidate`, installs pinned Node plus Rust, then runs product-owned package scripts. Candidate configuration is `{ buildScript, checkScript, artifactRoot, os, signWindows?, signMacos? }`; `os` selects supported release hosts from main matrix. RightGit always uploads an unsigned handoff. `signWindows: true` adds Azure OIDC signing. `signMacos: true` adds Developer ID signing plus notarization from protected environment secrets. Both use product-owned RightRelease finalization & never publish releases.
|
package/manifest.mjs
CHANGED
|
@@ -141,7 +141,7 @@ export function validateManifest(manifest) {
|
|
|
141
141
|
errors.push("candidate must be an object");
|
|
142
142
|
} else {
|
|
143
143
|
for (const key of Object.keys(candidate)) {
|
|
144
|
-
if (!["buildScript", "checkScript", "artifactRoot", "os"].includes(key)) errors.push(`unknown candidate field: ${key}`);
|
|
144
|
+
if (!["buildScript", "checkScript", "artifactRoot", "os", "signWindows", "signMacos"].includes(key)) errors.push(`unknown candidate field: ${key}`);
|
|
145
145
|
}
|
|
146
146
|
for (const key of ["buildScript", "checkScript"]) {
|
|
147
147
|
if (typeof candidate[key] !== "string" || !PACKAGE_SCRIPT_RE.test(candidate[key])) {
|
|
@@ -156,6 +156,21 @@ export function validateManifest(manifest) {
|
|
|
156
156
|
} else if (candidate.os.some((value) => !manifest.matrix?.os?.includes(value))) {
|
|
157
157
|
errors.push("candidate.os entries must also exist in matrix.os");
|
|
158
158
|
}
|
|
159
|
+
if (candidate.signWindows !== undefined && typeof candidate.signWindows !== "boolean") {
|
|
160
|
+
errors.push("candidate.signWindows must be a boolean");
|
|
161
|
+
}
|
|
162
|
+
if (candidate.signWindows === true && candidate.os.filter((value) => /^windows-/i.test(value)).length !== 1) {
|
|
163
|
+
errors.push("candidate.signWindows requires exactly one Windows runner in candidate.os");
|
|
164
|
+
}
|
|
165
|
+
if (candidate.signMacos !== undefined && typeof candidate.signMacos !== "boolean") {
|
|
166
|
+
errors.push("candidate.signMacos must be a boolean");
|
|
167
|
+
}
|
|
168
|
+
if (candidate.signMacos === true && candidate.os.filter((value) => /^macos-/i.test(value)).length !== 1) {
|
|
169
|
+
errors.push("candidate.signMacos requires exactly one macOS runner in candidate.os");
|
|
170
|
+
}
|
|
171
|
+
if ((candidate.signWindows === true || candidate.signMacos === true) && manifest.matrix?.node?.length !== 1) {
|
|
172
|
+
errors.push("candidate signing requires exactly one matrix.node version");
|
|
173
|
+
}
|
|
159
174
|
}
|
|
160
175
|
}
|
|
161
176
|
if (profile === "rust-hybrid" && lanes.includes("release-candidate") && candidate === undefined) {
|
|
@@ -278,6 +293,180 @@ function yamlFlowStringList(values) {
|
|
|
278
293
|
return `[${values.map((v) => JSON.stringify(String(v))).join(", ")}]`;
|
|
279
294
|
}
|
|
280
295
|
|
|
296
|
+
function candidateMatrix(manifest) {
|
|
297
|
+
const entries = [];
|
|
298
|
+
for (const os of manifest.candidate?.os ?? manifest.matrix.os) {
|
|
299
|
+
const platform = /^windows-/i.test(os) ? "windows" : /^macos-/i.test(os) ? "macos" : "linux";
|
|
300
|
+
const architecture = platform === "macos" ? "arm64" : "x86_64";
|
|
301
|
+
for (const node of manifest.matrix.node) entries.push({ os, node, platform, architecture });
|
|
302
|
+
}
|
|
303
|
+
return [
|
|
304
|
+
" include:",
|
|
305
|
+
...entries.flatMap((entry) => [
|
|
306
|
+
` - os: ${JSON.stringify(entry.os)}`,
|
|
307
|
+
` node: ${JSON.stringify(entry.node)}`,
|
|
308
|
+
` platform: ${JSON.stringify(entry.platform)}`,
|
|
309
|
+
` architecture: ${JSON.stringify(entry.architecture)}`,
|
|
310
|
+
]),
|
|
311
|
+
].join("\n");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const AZURE_LOGIN_SHA = "7ddb5af1ef8758cf1353cf3b42f940aee27ba21c"; // v3
|
|
315
|
+
const DOWNLOAD_ARTIFACT_SHA = "d3f86a106a0bac45b974a628896c90dbdf5c8093"; // v4.3.0
|
|
316
|
+
const UPLOAD_ARTIFACT_SHA = "ea165f8d65b6e75b540449e92b4886f43607fa02"; // v4
|
|
317
|
+
const ARTIFACT_SIGNING_CLIENT_VERSION = "1.0.128";
|
|
318
|
+
const ARTIFACT_SIGNING_CLIENT_SHA256 = "74BD7D27E6CE1051409C38D9B46BC8DF0400ECD643D51FFBF2AC00869061E40B";
|
|
319
|
+
const WINDOWS_SDK_BUILD_TOOLS_VERSION = "10.0.26100.4188";
|
|
320
|
+
const WINDOWS_SDK_BUILD_TOOLS_SHA256 = "180DEB372659029864C10A0C04787833234D64AACD1D2C0661D2C00295D8E022";
|
|
321
|
+
|
|
322
|
+
function windowsSigningJob(manifest, pm, artifactPrefix) {
|
|
323
|
+
if (manifest.candidate?.signWindows !== true) return "";
|
|
324
|
+
const windowsRunner = manifest.candidate.os.find((value) => /^windows-/i.test(value));
|
|
325
|
+
const nodeVersion = String(manifest.matrix.node[0]);
|
|
326
|
+
return `
|
|
327
|
+
windows-sign:
|
|
328
|
+
needs: [candidate]
|
|
329
|
+
environment: release
|
|
330
|
+
runs-on: ${windowsRunner}
|
|
331
|
+
permissions:
|
|
332
|
+
contents: read
|
|
333
|
+
id-token: write
|
|
334
|
+
steps:
|
|
335
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
336
|
+
- name: Resolve checked-out source revision
|
|
337
|
+
shell: pwsh
|
|
338
|
+
run: '"RIGHT_GIT_SOURCE_REVISION=$(git rev-parse HEAD)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append'
|
|
339
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
340
|
+
with:
|
|
341
|
+
node-version: ${nodeVersion}
|
|
342
|
+
package-manager-cache: false
|
|
343
|
+
${pm.setup}
|
|
344
|
+
- uses: dtolnay/rust-toolchain@f8be11a05b1d4f3fcebe6410cc16743212b999b0
|
|
345
|
+
with:
|
|
346
|
+
toolchain: 1.98.0
|
|
347
|
+
- run: ${pm.installCommand}
|
|
348
|
+
- name: Download exact unsigned Windows candidate
|
|
349
|
+
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
350
|
+
with:
|
|
351
|
+
name: ${artifactPrefix}-unsigned-${windowsRunner}-${nodeVersion}
|
|
352
|
+
path: \${{ runner.temp }}/unsigned-windows-candidate
|
|
353
|
+
- name: Authenticate to Azure with GitHub OIDC
|
|
354
|
+
uses: azure/login@${AZURE_LOGIN_SHA} # v3
|
|
355
|
+
with:
|
|
356
|
+
client-id: \${{ vars.AZURE_CLIENT_ID }}
|
|
357
|
+
tenant-id: \${{ vars.AZURE_TENANT_ID }}
|
|
358
|
+
subscription-id: \${{ vars.AZURE_SUBSCRIPTION_ID }}
|
|
359
|
+
- name: Install pinned Artifact Signing client
|
|
360
|
+
shell: pwsh
|
|
361
|
+
run: |
|
|
362
|
+
$ErrorActionPreference = 'Stop'
|
|
363
|
+
$tools = Join-Path $env:RUNNER_TEMP 'artifact-signing-tools'
|
|
364
|
+
New-Item -ItemType Directory -Force -Path $tools | Out-Null
|
|
365
|
+
$client = Join-Path $tools 'client.zip'
|
|
366
|
+
$sdk = Join-Path $tools 'sdk.zip'
|
|
367
|
+
Invoke-WebRequest 'https://api.nuget.org/v3-flatcontainer/microsoft.artifactsigning.client/${ARTIFACT_SIGNING_CLIENT_VERSION}/microsoft.artifactsigning.client.${ARTIFACT_SIGNING_CLIENT_VERSION}.nupkg' -OutFile $client
|
|
368
|
+
Invoke-WebRequest 'https://api.nuget.org/v3-flatcontainer/microsoft.windows.sdk.buildtools/${WINDOWS_SDK_BUILD_TOOLS_VERSION}/microsoft.windows.sdk.buildtools.${WINDOWS_SDK_BUILD_TOOLS_VERSION}.nupkg' -OutFile $sdk
|
|
369
|
+
if ((Get-FileHash $client -Algorithm SHA256).Hash -ne '${ARTIFACT_SIGNING_CLIENT_SHA256}') { throw 'Artifact Signing client hash mismatch' }
|
|
370
|
+
if ((Get-FileHash $sdk -Algorithm SHA256).Hash -ne '${WINDOWS_SDK_BUILD_TOOLS_SHA256}') { throw 'Windows SDK Build Tools hash mismatch' }
|
|
371
|
+
Expand-Archive $client (Join-Path $tools 'client')
|
|
372
|
+
Expand-Archive $sdk (Join-Path $tools 'sdk')
|
|
373
|
+
"AZURE_ARTIFACT_SIGNING_DLIB_PATH=$tools\\client\\bin\\x64\\Azure.CodeSigning.Dlib.dll" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
|
374
|
+
"AZURE_SIGNTOOL_PATH=$tools\\sdk\\bin\\10.0.26100.0\\x64\\signtool.exe" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
|
375
|
+
- name: Sign & finalize Windows candidate
|
|
376
|
+
run: pnpm exec right-release build --platform win --skip-checks
|
|
377
|
+
env:
|
|
378
|
+
LEGION_UNSIGNED_CANDIDATE_ROOT: \${{ runner.temp }}/unsigned-windows-candidate
|
|
379
|
+
LEGION_SOURCE_REVISION: \${{ env.RIGHT_GIT_SOURCE_REVISION }}
|
|
380
|
+
AZURE_ARTIFACT_SIGNING_ENDPOINT: \${{ vars.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
|
|
381
|
+
AZURE_ARTIFACT_SIGNING_ACCOUNT: \${{ vars.AZURE_ARTIFACT_SIGNING_ACCOUNT }}
|
|
382
|
+
AZURE_ARTIFACT_SIGNING_PROFILE: \${{ vars.AZURE_ARTIFACT_SIGNING_PROFILE }}
|
|
383
|
+
- name: Upload signed Windows candidate
|
|
384
|
+
uses: actions/upload-artifact@${UPLOAD_ARTIFACT_SHA} # v4
|
|
385
|
+
with:
|
|
386
|
+
name: ${artifactPrefix}-signed-windows-${nodeVersion}
|
|
387
|
+
path: |
|
|
388
|
+
dist/releases/windows/**
|
|
389
|
+
.right-release/receipts/windows-*.json
|
|
390
|
+
if-no-files-found: error
|
|
391
|
+
`.trimEnd();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function macosSigningJob(manifest, pm, artifactPrefix) {
|
|
395
|
+
if (manifest.candidate?.signMacos !== true) return "";
|
|
396
|
+
const macosRunner = manifest.candidate.os.find((value) => /^macos-/i.test(value));
|
|
397
|
+
const nodeVersion = String(manifest.matrix.node[0]);
|
|
398
|
+
return `
|
|
399
|
+
macos-sign:
|
|
400
|
+
needs: [candidate]
|
|
401
|
+
environment: release
|
|
402
|
+
runs-on: ${macosRunner}
|
|
403
|
+
permissions:
|
|
404
|
+
contents: read
|
|
405
|
+
steps:
|
|
406
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
407
|
+
- name: Resolve checked-out source revision
|
|
408
|
+
shell: bash
|
|
409
|
+
run: printf 'RIGHT_GIT_SOURCE_REVISION=%s\\n' "$(git rev-parse HEAD)" >> "$GITHUB_ENV"
|
|
410
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
411
|
+
with:
|
|
412
|
+
node-version: ${nodeVersion}
|
|
413
|
+
package-manager-cache: false
|
|
414
|
+
${pm.setup}
|
|
415
|
+
- uses: dtolnay/rust-toolchain@f8be11a05b1d4f3fcebe6410cc16743212b999b0
|
|
416
|
+
with:
|
|
417
|
+
toolchain: 1.98.0
|
|
418
|
+
- run: ${pm.installCommand}
|
|
419
|
+
- name: Download exact unsigned macOS candidate
|
|
420
|
+
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
421
|
+
with:
|
|
422
|
+
name: ${artifactPrefix}-unsigned-${macosRunner}-${nodeVersion}
|
|
423
|
+
path: \${{ runner.temp }}/unsigned-macos-candidate
|
|
424
|
+
- name: Install Apple signing identity in temporary keychain
|
|
425
|
+
shell: bash
|
|
426
|
+
env:
|
|
427
|
+
APPLE_CERTIFICATE_BASE64: \${{ secrets.APPLE_CERTIFICATE_BASE64 }}
|
|
428
|
+
APPLE_CERTIFICATE_PASSWORD: \${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
429
|
+
APPLE_KEYCHAIN_PASSWORD: \${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
|
|
430
|
+
APPLE_API_KEY_BASE64: \${{ secrets.APPLE_API_KEY_BASE64 }}
|
|
431
|
+
run: |
|
|
432
|
+
set -euo pipefail
|
|
433
|
+
certificate_path="$RUNNER_TEMP/apple-signing.p12"
|
|
434
|
+
key_path="$RUNNER_TEMP/AuthKey.p8"
|
|
435
|
+
keychain_path="$RUNNER_TEMP/apple-signing.keychain-db"
|
|
436
|
+
printf '%s' "$APPLE_CERTIFICATE_BASE64" | base64 --decode > "$certificate_path"
|
|
437
|
+
printf '%s' "$APPLE_API_KEY_BASE64" | base64 --decode > "$key_path"
|
|
438
|
+
security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$keychain_path"
|
|
439
|
+
security set-keychain-settings -lut 21600 "$keychain_path"
|
|
440
|
+
security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$keychain_path"
|
|
441
|
+
security import "$certificate_path" -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$keychain_path"
|
|
442
|
+
security set-key-partition-list -S apple-tool:,apple: -k "$APPLE_KEYCHAIN_PASSWORD" "$keychain_path"
|
|
443
|
+
security list-keychain -d user -s "$keychain_path"
|
|
444
|
+
printf 'APPLE_API_KEY_PATH=%s\\n' "$key_path" >> "$GITHUB_ENV"
|
|
445
|
+
- name: Sign, notarize & finalize macOS candidate
|
|
446
|
+
run: pnpm exec right-release build --platform mac --skip-checks
|
|
447
|
+
env:
|
|
448
|
+
LEGION_UNSIGNED_CANDIDATE_ROOT: \${{ runner.temp }}/unsigned-macos-candidate
|
|
449
|
+
LEGION_SOURCE_REVISION: \${{ env.RIGHT_GIT_SOURCE_REVISION }}
|
|
450
|
+
APPLE_DEVELOPER_ID: \${{ vars.APPLE_DEVELOPER_ID }}
|
|
451
|
+
APPLE_API_KEY: \${{ secrets.APPLE_API_KEY }}
|
|
452
|
+
APPLE_API_ISSUER: \${{ secrets.APPLE_API_ISSUER }}
|
|
453
|
+
- name: Upload signed & notarized macOS candidate
|
|
454
|
+
uses: actions/upload-artifact@${UPLOAD_ARTIFACT_SHA} # v4
|
|
455
|
+
with:
|
|
456
|
+
name: ${artifactPrefix}-signed-macos-${nodeVersion}
|
|
457
|
+
path: |
|
|
458
|
+
dist/releases/mac/**
|
|
459
|
+
.right-release/receipts/macos-*.json
|
|
460
|
+
if-no-files-found: error
|
|
461
|
+
- name: Remove Apple signing material
|
|
462
|
+
if: \${{ always() }}
|
|
463
|
+
shell: bash
|
|
464
|
+
run: |
|
|
465
|
+
security delete-keychain "$RUNNER_TEMP/apple-signing.keychain-db" 2>/dev/null || true
|
|
466
|
+
rm -f "$RUNNER_TEMP/apple-signing.p12" "$RUNNER_TEMP/AuthKey.p8"
|
|
467
|
+
`.trimEnd();
|
|
468
|
+
}
|
|
469
|
+
|
|
281
470
|
/**
|
|
282
471
|
* Derive one project name from the target repo's package.json, falling back
|
|
283
472
|
* to the repo directory's basename. Never throws.
|
|
@@ -321,6 +510,9 @@ export function buildRenderContext(manifest, repoPath) {
|
|
|
321
510
|
CANDIDATE_CHECK_SCRIPT: manifest.candidate?.checkScript,
|
|
322
511
|
CANDIDATE_ARTIFACT_ROOT: manifest.candidate?.artifactRoot,
|
|
323
512
|
CANDIDATE_OS_MATRIX: yamlFlowStringList(manifest.candidate?.os ?? manifest.matrix.os),
|
|
513
|
+
CANDIDATE_MATRIX: candidateMatrix(manifest),
|
|
514
|
+
WINDOWS_SIGNING_JOB: windowsSigningJob(manifest, pm, artifactPrefix),
|
|
515
|
+
MACOS_SIGNING_JOB: macosSigningJob(manifest, pm, artifactPrefix),
|
|
324
516
|
};
|
|
325
517
|
|
|
326
518
|
if (manifest.publish?.npm) {
|
package/manifest.schema.json
CHANGED
|
@@ -94,7 +94,9 @@
|
|
|
94
94
|
"buildScript": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9:_-]*$" },
|
|
95
95
|
"checkScript": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9:_-]*$" },
|
|
96
96
|
"artifactRoot": { "type": "string", "pattern": "^(?!.*\\.\\.)(?!.*\\s)[A-Za-z0-9][A-Za-z0-9._/-]*$" },
|
|
97
|
-
"os": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }
|
|
97
|
+
"os": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } },
|
|
98
|
+
"signWindows": { "type": "boolean" },
|
|
99
|
+
"signMacos": { "type": "boolean" }
|
|
98
100
|
}
|
|
99
101
|
}
|
|
100
102
|
}
|
package/manifest.test.mjs
CHANGED
|
@@ -29,6 +29,15 @@ test("rust-hybrid supports ci plus a configured unsigned release-candidate lane"
|
|
|
29
29
|
candidate: { buildScript: "ci:candidate", checkScript: "ci:candidate-check", artifactRoot: "legion-unsigned", os: ["ubuntu-latest"] },
|
|
30
30
|
};
|
|
31
31
|
assert.equal(validateManifest(candidate).valid, true);
|
|
32
|
+
assert.equal(validateManifest({ ...candidate, candidate: { ...candidate.candidate, signWindows: true } }).valid, false);
|
|
33
|
+
assert.equal(validateManifest({ ...candidate, candidate: { ...candidate.candidate, signMacos: true } }).valid, false);
|
|
34
|
+
const windowsCandidate = { ...candidate, matrix: { ...candidate.matrix, os: ["ubuntu-latest", "windows-2025"] }, candidate: { ...candidate.candidate, os: ["windows-2025"], signWindows: true } };
|
|
35
|
+
assert.equal(validateManifest(windowsCandidate).valid, true);
|
|
36
|
+
assert.equal(validateManifest({ ...windowsCandidate, candidate: { ...windowsCandidate.candidate, signWindows: "yes" } }).valid, false);
|
|
37
|
+
const macCandidate = { ...candidate, matrix: { ...candidate.matrix, os: ["ubuntu-latest", "macos-15"] }, candidate: { ...candidate.candidate, os: ["macos-15"], signMacos: true } };
|
|
38
|
+
assert.equal(validateManifest(macCandidate).valid, true);
|
|
39
|
+
assert.equal(validateManifest({ ...macCandidate, candidate: { ...macCandidate.candidate, signMacos: "yes" } }).valid, false);
|
|
40
|
+
assert.equal(validateManifest({ ...macCandidate, matrix: { ...macCandidate.matrix, node: ["22", "24"] } }).valid, false);
|
|
32
41
|
assert.equal(validateManifest({ ...candidate, candidate: { ...candidate.candidate, buildScript: "echo unsafe" } }).valid, false);
|
|
33
42
|
assert.equal(validateManifest({ ...candidate, candidate: { ...candidate.candidate, artifactRoot: "../escape" } }).valid, false);
|
|
34
43
|
assert.equal(validateManifest({ ...candidate, candidate: { ...candidate.candidate, os: ["windows-latest"] } }).valid, false);
|
package/package.json
CHANGED
package/render.test.mjs
CHANGED
|
@@ -68,11 +68,66 @@ test("rust-hybrid release candidate renders product scripts, artifact handoff, a
|
|
|
68
68
|
assert.match(content, /pnpm run ci:candidate/);
|
|
69
69
|
assert.match(content, /pnpm run ci:candidate-check/);
|
|
70
70
|
assert.match(content, /RIGHT_GIT_ARTIFACT_ROOT/);
|
|
71
|
-
assert.match(content, /os:
|
|
71
|
+
assert.match(content, /os: "macos-latest"/);
|
|
72
|
+
assert.match(content, /platform: "windows"/);
|
|
73
|
+
assert.match(content, /RIGHT_GIT_RELEASE_ARCHITECTURE: \$\{\{ matrix\.architecture \}\}/);
|
|
72
74
|
assert.match(content, /actions\/upload-artifact@[a-f0-9]{40}/);
|
|
73
75
|
assert.doesNotMatch(content, /secrets\.|AZURE_|APPLE_|certificate|private.key/i);
|
|
74
76
|
});
|
|
75
77
|
|
|
78
|
+
test("rust-hybrid release candidate can opt into OIDC Windows signing without long-lived secrets", () => {
|
|
79
|
+
const manifest = {
|
|
80
|
+
...FULL_MANIFEST,
|
|
81
|
+
lanes: ["ci", "release-candidate"],
|
|
82
|
+
profile: "rust-hybrid",
|
|
83
|
+
gate: { path: "scripts/gate.sh" },
|
|
84
|
+
candidate: { buildScript: "ci:candidate", checkScript: "ci:candidate-check", artifactRoot: "widgets-unsigned", os: ["windows-2025"], signWindows: true },
|
|
85
|
+
matrix: { os: ["ubuntu-24.04", "windows-2025"], node: ["22.23.2"] },
|
|
86
|
+
templateVersion: "1.3.0",
|
|
87
|
+
};
|
|
88
|
+
const content = renderTemplate(
|
|
89
|
+
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
90
|
+
buildRenderContext(manifest, "/repos/widgets"),
|
|
91
|
+
);
|
|
92
|
+
assert.match(content, /environment: release/);
|
|
93
|
+
assert.match(content, /id-token: write/);
|
|
94
|
+
assert.match(content, /azure\/login@[a-f0-9]{40}/);
|
|
95
|
+
assert.match(content, /vars\.AZURE_CLIENT_ID/);
|
|
96
|
+
assert.match(content, /right-release build --platform win --skip-checks/);
|
|
97
|
+
assert.match(content, /Artifact Signing client hash mismatch/);
|
|
98
|
+
assert.match(content, /RIGHT_GIT_SOURCE_REVISION=\$\(git rev-parse HEAD\)/);
|
|
99
|
+
assert.ok(content.includes("LEGION_SOURCE_REVISION: ${{ env.RIGHT_GIT_SOURCE_REVISION }}"));
|
|
100
|
+
assert.doesNotMatch(content, /LEGION_SOURCE_REVISION: \$\{\{ github\.sha \}\}/);
|
|
101
|
+
assert.doesNotMatch(content, /secrets\.|client-secret|private.key/i);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("rust-hybrid release candidate can opt into protected macOS signing & notarization", () => {
|
|
105
|
+
const manifest = {
|
|
106
|
+
...FULL_MANIFEST,
|
|
107
|
+
lanes: ["ci", "release-candidate"],
|
|
108
|
+
profile: "rust-hybrid",
|
|
109
|
+
gate: { path: "scripts/gate.sh" },
|
|
110
|
+
candidate: { buildScript: "ci:candidate", checkScript: "ci:candidate-check", artifactRoot: "widgets-unsigned", os: ["macos-15"], signMacos: true },
|
|
111
|
+
matrix: { os: ["ubuntu-24.04", "macos-15"], node: ["22.23.2"] },
|
|
112
|
+
templateVersion: "1.3.0",
|
|
113
|
+
};
|
|
114
|
+
const content = renderTemplate(
|
|
115
|
+
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
116
|
+
buildRenderContext(manifest, "/repos/widgets"),
|
|
117
|
+
);
|
|
118
|
+
assert.match(content, /macos-sign:/);
|
|
119
|
+
assert.match(content, /environment: release/);
|
|
120
|
+
assert.match(content, /secrets\.APPLE_CERTIFICATE_BASE64/);
|
|
121
|
+
assert.match(content, /temporary keychain/i);
|
|
122
|
+
assert.match(content, /right-release build --platform mac --skip-checks/);
|
|
123
|
+
assert.match(content, /secrets\.APPLE_API_KEY_BASE64/);
|
|
124
|
+
assert.match(content, /RIGHT_GIT_SOURCE_REVISION=%s\\n.*git rev-parse HEAD/);
|
|
125
|
+
assert.ok(content.includes("LEGION_SOURCE_REVISION: ${{ env.RIGHT_GIT_SOURCE_REVISION }}"));
|
|
126
|
+
assert.doesNotMatch(content, /LEGION_SOURCE_REVISION: \$\{\{ github\.sha \}\}/);
|
|
127
|
+
assert.match(content, /security delete-keychain/);
|
|
128
|
+
assert.doesNotMatch(content, /pull_request_target/);
|
|
129
|
+
});
|
|
130
|
+
|
|
76
131
|
test("render determinism: same manifest renders byte-identical output every time", () => {
|
|
77
132
|
const first = renderAllLanes(FULL_MANIFEST, "/repos/widgets");
|
|
78
133
|
const second = renderAllLanes(FULL_MANIFEST, "/repos/widgets");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Managed by right-git — do not hand-edit. Run `right-git sync` to update.
|
|
2
2
|
# Template: profiles/rust-hybrid/release-candidate.yml | Template version: ${TEMPLATE_VERSION}
|
|
3
|
-
# Public CI produces unsigned candidates
|
|
4
|
-
#
|
|
3
|
+
# Public CI always produces unsigned candidates. Opt-in finalization signs Windows
|
|
4
|
+
# through Azure OIDC & macOS through environment-scoped Apple credentials.
|
|
5
5
|
name: release-candidate
|
|
6
6
|
on:
|
|
7
7
|
workflow_dispatch:
|
|
@@ -21,8 +21,7 @@ jobs:
|
|
|
21
21
|
strategy:
|
|
22
22
|
fail-fast: false
|
|
23
23
|
matrix:
|
|
24
|
-
|
|
25
|
-
node: ${NODE_MATRIX}
|
|
24
|
+
${CANDIDATE_MATRIX}
|
|
26
25
|
runs-on: ${{ matrix.os }}
|
|
27
26
|
steps:
|
|
28
27
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
@@ -43,13 +42,19 @@ ${PACKAGE_MANAGER_SETUP}
|
|
|
43
42
|
run: ${PM_RUN} ${CANDIDATE_BUILD_SCRIPT}
|
|
44
43
|
env:
|
|
45
44
|
RIGHT_GIT_ARTIFACT_ROOT: ${{ runner.temp }}/${CANDIDATE_ARTIFACT_ROOT}
|
|
45
|
+
RIGHT_GIT_RELEASE_PLATFORM: ${{ matrix.platform }}
|
|
46
|
+
RIGHT_GIT_RELEASE_ARCHITECTURE: ${{ matrix.architecture }}
|
|
46
47
|
- name: Check unsigned release candidate
|
|
47
48
|
run: ${PM_RUN} ${CANDIDATE_CHECK_SCRIPT}
|
|
48
49
|
env:
|
|
49
50
|
RIGHT_GIT_ARTIFACT_ROOT: ${{ runner.temp }}/${CANDIDATE_ARTIFACT_ROOT}
|
|
51
|
+
RIGHT_GIT_RELEASE_PLATFORM: ${{ matrix.platform }}
|
|
52
|
+
RIGHT_GIT_RELEASE_ARCHITECTURE: ${{ matrix.architecture }}
|
|
50
53
|
- name: Upload unsigned candidate handoff
|
|
51
54
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
52
55
|
with:
|
|
53
56
|
name: ${ARTIFACT_PREFIX}-unsigned-${{ matrix.os }}-${{ matrix.node }}
|
|
54
57
|
path: ${{ runner.temp }}/${CANDIDATE_ARTIFACT_ROOT}/**
|
|
55
58
|
if-no-files-found: error
|
|
59
|
+
${WINDOWS_SIGNING_JOB}
|
|
60
|
+
${MACOS_SIGNING_JOB}
|