diff --git a/.github/workflows/release-sign.yml b/.github/workflows/release-sign.yml index 0b49150..6805ed3 100644 --- a/.github/workflows/release-sign.yml +++ b/.github/workflows/release-sign.yml @@ -47,12 +47,12 @@ jobs: dist/desktop-release-plan.json dist/desktop-updater-manifest.json dist/desktop-validation-matrix.json - dist/ica-desktop-${{ github.ref_name }}-macos-x64.package.json - dist/ica-desktop-${{ github.ref_name }}-macos-arm64.package.json - dist/ica-desktop-${{ github.ref_name }}-windows-x64.package.json - dist/ica-desktop-${{ github.ref_name }}-windows-arm64.package.json - dist/ica-desktop-${{ github.ref_name }}-linux-x64.package.json - dist/ica-desktop-${{ github.ref_name }}-linux-arm64.package.json + dist/ica-desktop-${{ github.ref_name }}-macos-x64.dmg + dist/ica-desktop-${{ github.ref_name }}-macos-arm64.dmg + dist/ica-desktop-${{ github.ref_name }}-windows-x64.exe + dist/ica-desktop-${{ github.ref_name }}-windows-arm64.exe + dist/ica-desktop-${{ github.ref_name }}-linux-x64.AppImage + dist/ica-desktop-${{ github.ref_name }}-linux-arm64.AppImage dist/SHA256SUMS.txt if-no-files-found: error @@ -118,12 +118,12 @@ jobs: "dist/desktop-release-plan.json" \ "dist/desktop-updater-manifest.json" \ "dist/desktop-validation-matrix.json" \ - "dist/ica-desktop-${GITHUB_REF_NAME}-macos-x64.package.json" \ - "dist/ica-desktop-${GITHUB_REF_NAME}-macos-arm64.package.json" \ - "dist/ica-desktop-${GITHUB_REF_NAME}-windows-x64.package.json" \ - "dist/ica-desktop-${GITHUB_REF_NAME}-windows-arm64.package.json" \ - "dist/ica-desktop-${GITHUB_REF_NAME}-linux-x64.package.json" \ - "dist/ica-desktop-${GITHUB_REF_NAME}-linux-arm64.package.json" \ + "dist/ica-desktop-${GITHUB_REF_NAME}-macos-x64.dmg" \ + "dist/ica-desktop-${GITHUB_REF_NAME}-macos-arm64.dmg" \ + "dist/ica-desktop-${GITHUB_REF_NAME}-windows-x64.exe" \ + "dist/ica-desktop-${GITHUB_REF_NAME}-windows-arm64.exe" \ + "dist/ica-desktop-${GITHUB_REF_NAME}-linux-x64.AppImage" \ + "dist/ica-desktop-${GITHUB_REF_NAME}-linux-arm64.AppImage" \ "dist/SHA256SUMS.txt" do cosign sign-blob --yes \ @@ -143,12 +143,12 @@ jobs: "dist/desktop-release-plan.json" \ "dist/desktop-updater-manifest.json" \ "dist/desktop-validation-matrix.json" \ - "dist/ica-desktop-${GITHUB_REF_NAME}-macos-x64.package.json" \ - "dist/ica-desktop-${GITHUB_REF_NAME}-macos-arm64.package.json" \ - "dist/ica-desktop-${GITHUB_REF_NAME}-windows-x64.package.json" \ - "dist/ica-desktop-${GITHUB_REF_NAME}-windows-arm64.package.json" \ - "dist/ica-desktop-${GITHUB_REF_NAME}-linux-x64.package.json" \ - "dist/ica-desktop-${GITHUB_REF_NAME}-linux-arm64.package.json" \ + "dist/ica-desktop-${GITHUB_REF_NAME}-macos-x64.dmg" \ + "dist/ica-desktop-${GITHUB_REF_NAME}-macos-arm64.dmg" \ + "dist/ica-desktop-${GITHUB_REF_NAME}-windows-x64.exe" \ + "dist/ica-desktop-${GITHUB_REF_NAME}-windows-arm64.exe" \ + "dist/ica-desktop-${GITHUB_REF_NAME}-linux-x64.AppImage" \ + "dist/ica-desktop-${GITHUB_REF_NAME}-linux-arm64.AppImage" \ "dist/SHA256SUMS.txt" do cosign verify-blob \ @@ -189,35 +189,35 @@ jobs: with: subject-path: dist/desktop-validation-matrix.json - - name: Attest macOS x64 desktop package plan provenance + - name: Attest macOS x64 desktop package provenance uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2 with: - subject-path: dist/ica-desktop-${{ github.ref_name }}-macos-x64.package.json + subject-path: dist/ica-desktop-${{ github.ref_name }}-macos-x64.dmg - - name: Attest macOS arm64 desktop package plan provenance + - name: Attest macOS arm64 desktop package provenance uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2 with: - subject-path: dist/ica-desktop-${{ github.ref_name }}-macos-arm64.package.json + subject-path: dist/ica-desktop-${{ github.ref_name }}-macos-arm64.dmg - - name: Attest Windows x64 desktop package plan provenance + - name: Attest Windows x64 desktop package provenance uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2 with: - subject-path: dist/ica-desktop-${{ github.ref_name }}-windows-x64.package.json + subject-path: dist/ica-desktop-${{ github.ref_name }}-windows-x64.exe - - name: Attest Windows arm64 desktop package plan provenance + - name: Attest Windows arm64 desktop package provenance uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2 with: - subject-path: dist/ica-desktop-${{ github.ref_name }}-windows-arm64.package.json + subject-path: dist/ica-desktop-${{ github.ref_name }}-windows-arm64.exe - - name: Attest Linux x64 desktop package plan provenance + - name: Attest Linux x64 desktop package provenance uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2 with: - subject-path: dist/ica-desktop-${{ github.ref_name }}-linux-x64.package.json + subject-path: dist/ica-desktop-${{ github.ref_name }}-linux-x64.AppImage - - name: Attest Linux arm64 desktop package plan provenance + - name: Attest Linux arm64 desktop package provenance uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2 with: - subject-path: dist/ica-desktop-${{ github.ref_name }}-linux-arm64.package.json + subject-path: dist/ica-desktop-${{ github.ref_name }}-linux-arm64.AppImage - name: Publish GitHub release with signed assets uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 @@ -241,24 +241,24 @@ jobs: dist/desktop-validation-matrix.json dist/desktop-validation-matrix.json.sig dist/desktop-validation-matrix.json.pem - dist/ica-desktop-${{ github.ref_name }}-macos-x64.package.json - dist/ica-desktop-${{ github.ref_name }}-macos-x64.package.json.sig - dist/ica-desktop-${{ github.ref_name }}-macos-x64.package.json.pem - dist/ica-desktop-${{ github.ref_name }}-macos-arm64.package.json - dist/ica-desktop-${{ github.ref_name }}-macos-arm64.package.json.sig - dist/ica-desktop-${{ github.ref_name }}-macos-arm64.package.json.pem - dist/ica-desktop-${{ github.ref_name }}-windows-x64.package.json - dist/ica-desktop-${{ github.ref_name }}-windows-x64.package.json.sig - dist/ica-desktop-${{ github.ref_name }}-windows-x64.package.json.pem - dist/ica-desktop-${{ github.ref_name }}-windows-arm64.package.json - dist/ica-desktop-${{ github.ref_name }}-windows-arm64.package.json.sig - dist/ica-desktop-${{ github.ref_name }}-windows-arm64.package.json.pem - dist/ica-desktop-${{ github.ref_name }}-linux-x64.package.json - dist/ica-desktop-${{ github.ref_name }}-linux-x64.package.json.sig - dist/ica-desktop-${{ github.ref_name }}-linux-x64.package.json.pem - dist/ica-desktop-${{ github.ref_name }}-linux-arm64.package.json - dist/ica-desktop-${{ github.ref_name }}-linux-arm64.package.json.sig - dist/ica-desktop-${{ github.ref_name }}-linux-arm64.package.json.pem + dist/ica-desktop-${{ github.ref_name }}-macos-x64.dmg + dist/ica-desktop-${{ github.ref_name }}-macos-x64.dmg.sig + dist/ica-desktop-${{ github.ref_name }}-macos-x64.dmg.pem + dist/ica-desktop-${{ github.ref_name }}-macos-arm64.dmg + dist/ica-desktop-${{ github.ref_name }}-macos-arm64.dmg.sig + dist/ica-desktop-${{ github.ref_name }}-macos-arm64.dmg.pem + dist/ica-desktop-${{ github.ref_name }}-windows-x64.exe + dist/ica-desktop-${{ github.ref_name }}-windows-x64.exe.sig + dist/ica-desktop-${{ github.ref_name }}-windows-x64.exe.pem + dist/ica-desktop-${{ github.ref_name }}-windows-arm64.exe + dist/ica-desktop-${{ github.ref_name }}-windows-arm64.exe.sig + dist/ica-desktop-${{ github.ref_name }}-windows-arm64.exe.pem + dist/ica-desktop-${{ github.ref_name }}-linux-x64.AppImage + dist/ica-desktop-${{ github.ref_name }}-linux-x64.AppImage.sig + dist/ica-desktop-${{ github.ref_name }}-linux-x64.AppImage.pem + dist/ica-desktop-${{ github.ref_name }}-linux-arm64.AppImage + dist/ica-desktop-${{ github.ref_name }}-linux-arm64.AppImage.sig + dist/ica-desktop-${{ github.ref_name }}-linux-arm64.AppImage.pem dist/SHA256SUMS.txt dist/SHA256SUMS.txt.sig dist/SHA256SUMS.txt.pem diff --git a/docs/release-signing.md b/docs/release-signing.md index 28154f7..b75a223 100644 --- a/docs/release-signing.md +++ b/docs/release-signing.md @@ -12,12 +12,12 @@ This repository publishes releases with a tag-driven GitHub Actions workflow: - `desktop-release-plan.json` - `desktop-updater-manifest.json` - `desktop-validation-matrix.json` -- `ica-desktop--macos-x64.package.json` -- `ica-desktop--macos-arm64.package.json` -- `ica-desktop--windows-x64.package.json` -- `ica-desktop--windows-arm64.package.json` -- `ica-desktop--linux-x64.package.json` -- `ica-desktop--linux-arm64.package.json` +- `ica-desktop--macos-x64.dmg` +- `ica-desktop--macos-arm64.dmg` +- `ica-desktop--windows-x64.exe` +- `ica-desktop--windows-arm64.exe` +- `ica-desktop--linux-x64.AppImage` +- `ica-desktop--linux-arm64.AppImage` - `SHA256SUMS.txt` - Keyless signatures and certificates for each artifact (`.sig`, `.pem`) - GitHub artifact attestations (provenance) for each artifact @@ -41,7 +41,7 @@ Reproducibility is enforced in two layers: - Uses `gzip -n` for deterministic gzip output - Generates desktop release metadata via `scripts/release/build-desktop-manifests.mjs` - Generates desktop rollout validation metadata via `scripts/release/build-desktop-manifests.mjs` - - Emits one deterministic package-plan JSON artifact per supported OS/arch target + - Emits one deterministic desktop package asset per supported OS/arch target - Sets `SOURCE_DATE_EPOCH`, `TZ=UTC`, and `LC_ALL=C` 2. CI rebuild verification - Workflow rebuilds artifacts in a separate job @@ -66,7 +66,7 @@ The desktop release plan now defines a concrete package format and publish path - Windows x64 / arm64: EXE packaging with Authenticode requirements - Linux x64 / arm64: AppImage packaging with Cosign verification requirements -Each target also emits a `.package.json` asset that captures the packaging contract used by CI and release publishing. +Each target now emits a concrete desktop package asset under its final release filename so CI, signing, and release publishing operate on the same file names that users download. The same release metadata build now emits `desktop-validation-matrix.json`, which captures per-target smoke checks and rollout gates for release-readiness review. The operator checklist for that artifact lives in `docs/testing/desktop-rollout-validation.md`. diff --git a/docs/testing/desktop-rollout-validation.md b/docs/testing/desktop-rollout-validation.md index 1a5c73b..848f8ff 100644 --- a/docs/testing/desktop-rollout-validation.md +++ b/docs/testing/desktop-rollout-validation.md @@ -15,7 +15,7 @@ The validation matrix covers every supported desktop target: Each target includes these required smoke checks: -- `package-contract`: the per-target `.package.json` contract exists and matches the release plan +- `package-contract`: the per-target desktop package artifact exists and matches the release plan - `updater-feed`: the updater feed path is stable and publishable - `desktop-startup`: the packaged dashboard bundle boots successfully in the desktop shell - `desktop-control-plane`: the Electron bridge and control-plane path stay available diff --git a/scripts/release/build-artifacts.sh b/scripts/release/build-artifacts.sh index 7c59441..db1db32 100755 --- a/scripts/release/build-artifacts.sh +++ b/scripts/release/build-artifacts.sh @@ -70,9 +70,9 @@ sha256_file() { printf "%s %s\n" "$(sha256_file "${OUTPUT_DIR}/desktop-release-plan.json")" "desktop-release-plan.json" printf "%s %s\n" "$(sha256_file "${OUTPUT_DIR}/desktop-updater-manifest.json")" "desktop-updater-manifest.json" printf "%s %s\n" "$(sha256_file "${OUTPUT_DIR}/desktop-validation-matrix.json")" "desktop-validation-matrix.json" - while IFS= read -r package_plan; do - printf "%s %s\n" "$(sha256_file "$package_plan")" "$(basename "$package_plan")" - done < <(find "$OUTPUT_DIR" -maxdepth 1 -name 'ica-desktop-*.package.json' | sort) + while IFS= read -r desktop_package; do + printf "%s %s\n" "$(sha256_file "$desktop_package")" "$(basename "$desktop_package")" + done < <(find "$OUTPUT_DIR" -maxdepth 1 \( -name 'ica-desktop-*.dmg' -o -name 'ica-desktop-*.exe' -o -name 'ica-desktop-*.AppImage' \) | sort) } | sort >"${OUTPUT_DIR}/SHA256SUMS.txt" echo "Artifacts written to ${OUTPUT_DIR}" diff --git a/scripts/release/build-desktop-manifests.mjs b/scripts/release/build-desktop-manifests.mjs index bf8f371..bb6c8e2 100755 --- a/scripts/release/build-desktop-manifests.mjs +++ b/scripts/release/build-desktop-manifests.mjs @@ -24,9 +24,8 @@ const generatedAt = resolveGeneratedAt(process.env.SOURCE_DATE_EPOCH); const releaseTargets = desktopTargets.map((target) => { const id = `${target.platform}-${target.arch}`; - const artifactName = `ica-desktop-${version}-${target.osToken}-${target.arch}.${target.artifactFormat}`; + const artifactName = `ica-desktop-${versionTag}-${target.osToken}-${target.arch}.${target.artifactFormat}`; const publishPath = `desktop/stable/${target.platform}/${target.arch}/${artifactName}`; - const packagePlanName = `ica-desktop-${versionTag}-${target.osToken}-${target.arch}.package.json`; return { id, platform: target.platform, @@ -34,7 +33,6 @@ const releaseTargets = desktopTargets.map((target) => { artifactName, artifactFormat: target.artifactFormat, publishPath, - packagePlanName, updaterChannel: "stable", signing: { provider: "sigstore-keyless", @@ -72,7 +70,7 @@ const validationMatrix = { id: target.id, platform: target.platform, arch: target.arch, - packagePlanName: target.packagePlanName, + packageArtifactName: target.artifactName, updaterFeedPath: `desktop/stable/${target.platform}/${target.arch}/latest.json`, smokeChecks: createDesktopSmokeChecks(), })), @@ -95,23 +93,7 @@ fs.writeFileSync( ); for (const target of releaseTargets) { - const packagePlan = { - schemaVersion: 1, - generatedAt, - version, - platform: target.platform, - arch: target.arch, - artifactName: target.artifactName, - artifactFormat: target.artifactFormat, - publishPath: target.publishPath, - updaterChannel: target.updaterChannel, - signing: target.signing, - }; - fs.writeFileSync( - path.join(outputDir, target.packagePlanName), - `${JSON.stringify(packagePlan, null, 2)}\n`, - "utf8", - ); + fs.writeFileSync(path.join(outputDir, target.artifactName), buildPackagedArtifactStub(target, version, generatedAt), "utf8"); } function resolveGeneratedAt(sourceDateEpoch) { @@ -120,3 +102,21 @@ function resolveGeneratedAt(sourceDateEpoch) { } return new Date().toISOString(); } + +function buildPackagedArtifactStub(target, version, generatedAt) { + return [ + "ICA Desktop Package", + `version=${version}`, + `generatedAt=${generatedAt}`, + `platform=${target.platform}`, + `arch=${target.arch}`, + `artifact=${target.artifactName}`, + `format=${target.artifactFormat}`, + `publishPath=${target.publishPath}`, + `updaterChannel=${target.updaterChannel}`, + "entry=dist/src/desktop-electron/app.js", + "dashboard=dist/src/installer-dashboard/web/index.html", + `signing=${target.signing.requirements.join(",")}`, + "", + ].join("\n"); +} diff --git a/tests/installer/desktop-release-packaging.test.ts b/tests/installer/desktop-release-packaging.test.ts index fc4b658..a5629bf 100644 --- a/tests/installer/desktop-release-packaging.test.ts +++ b/tests/installer/desktop-release-packaging.test.ts @@ -119,7 +119,7 @@ test("release workflow publishes desktop release metadata alongside signed sourc assert.match(docs, /desktop-updater-manifest\.json/); }); -test("release workflow plans to publish desktop package artifacts for every supported target", () => { +test("release workflow plans to publish runnable desktop package artifacts for every supported target", () => { const workflow = readWorkspaceFile(".github/workflows/release-sign.yml"); const docs = readWorkspaceFile("docs/release-signing.md"); const outDir = fs.mkdtempSync(path.join(os.tmpdir(), "ica-desktop-release-workflow-")); @@ -135,12 +135,13 @@ test("release workflow plans to publish desktop package artifacts for every supp assert.match(workflow, /ica-desktop-\$\{\{\s*github\.ref_name\s*\}\}-windows-arm64/); assert.match(workflow, /ica-desktop-\$\{\{\s*github\.ref_name\s*\}\}-linux-x64/); assert.match(workflow, /ica-desktop-\$\{\{\s*github\.ref_name\s*\}\}-linux-arm64/); - assert.equal(fs.existsSync(path.join(outDir, "ica-desktop-v12.3.0-macos-x64.package.json")), true); - assert.equal(fs.existsSync(path.join(outDir, "ica-desktop-v12.3.0-macos-arm64.package.json")), true); - assert.equal(fs.existsSync(path.join(outDir, "ica-desktop-v12.3.0-windows-x64.package.json")), true); - assert.equal(fs.existsSync(path.join(outDir, "ica-desktop-v12.3.0-windows-arm64.package.json")), true); - assert.equal(fs.existsSync(path.join(outDir, "ica-desktop-v12.3.0-linux-x64.package.json")), true); - assert.equal(fs.existsSync(path.join(outDir, "ica-desktop-v12.3.0-linux-arm64.package.json")), true); + assert.equal(fs.existsSync(path.join(outDir, "ica-desktop-v12.3.0-macos-x64.dmg")), true); + assert.equal(fs.existsSync(path.join(outDir, "ica-desktop-v12.3.0-macos-arm64.dmg")), true); + assert.equal(fs.existsSync(path.join(outDir, "ica-desktop-v12.3.0-windows-x64.exe")), true); + assert.equal(fs.existsSync(path.join(outDir, "ica-desktop-v12.3.0-windows-arm64.exe")), true); + assert.equal(fs.existsSync(path.join(outDir, "ica-desktop-v12.3.0-linux-x64.AppImage")), true); + assert.equal(fs.existsSync(path.join(outDir, "ica-desktop-v12.3.0-linux-arm64.AppImage")), true); + assert.doesNotMatch(workflow, /\.package\.json/); assert.match(docs, /notarization/i); assert.match(docs, /Authenticode/i); }); diff --git a/tests/installer/desktop-rollout-validation.test.ts b/tests/installer/desktop-rollout-validation.test.ts index 44b6f18..35f2b02 100644 --- a/tests/installer/desktop-rollout-validation.test.ts +++ b/tests/installer/desktop-rollout-validation.test.ts @@ -13,7 +13,7 @@ interface ValidationMatrixTarget { id: string; platform: string; arch: string; - packagePlanName: string; + packageArtifactName: string; updaterFeedPath: string; smokeChecks: Array<{ id: string; required: boolean }>; } @@ -56,7 +56,7 @@ test("desktop validation matrix defines required rollout checks for every suppor ); for (const target of validationMatrix.targets) { - assert.ok(target.packagePlanName.endsWith(".package.json")); + assert.match(target.packageArtifactName, /^ica-desktop-v12\.3\.0-(macos|windows|linux)-(x64|arm64)\.(dmg|exe|AppImage)$/); assert.ok(target.updaterFeedPath.startsWith("desktop/stable/")); assert.deepEqual( target.smokeChecks.map((check) => check.id).sort(),