diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a534e9b..745ba63 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,6 @@ concurrency: cancel-in-progress: false permissions: - actions: read contents: write jobs: @@ -220,27 +219,6 @@ jobs: restore-keys: | ${{ runner.os }}-bun- - - name: Validate Windows signing secrets - shell: bash - run: | - missing=0 - for name in SIGNPATH_API_TOKEN SIGNPATH_ORGANIZATION_ID SIGNPATH_PROJECT_SLUG SIGNPATH_SIGNING_POLICY_SLUG SIGNPATH_ARTIFACT_CONFIGURATION_SLUG; do - if [ -z "${!name}" ]; then - echo "Missing required secret: $name" - missing=1 - fi - done - if [ "$missing" -ne 0 ]; then - echo "Set the SignPath Windows signing secrets and rerun." - exit 1 - fi - env: - SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }} - SIGNPATH_ORGANIZATION_ID: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} - SIGNPATH_PROJECT_SLUG: ${{ secrets.SIGNPATH_PROJECT_SLUG }} - SIGNPATH_SIGNING_POLICY_SLUG: ${{ secrets.SIGNPATH_SIGNING_POLICY_SLUG }} - SIGNPATH_ARTIFACT_CONFIGURATION_SLUG: ${{ secrets.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }} - - name: Install dependencies run: bun install --frozen-lockfile @@ -252,99 +230,17 @@ jobs: bun run build - name: Build unsigned Windows artifacts - run: bun run build:win + run: bun run build:win:unsigned - - name: Upload unsigned Windows artifact for SignPath - id: upload-unsigned-windows-artifact + - name: Upload Windows artifacts uses: actions/upload-artifact@v4 with: - name: unsigned-windows + name: windows path: | release/*.exe release/*.zip if-no-files-found: error - - name: Submit Windows signing request (attempt 1) - id: signpath-sign-attempt-1 - continue-on-error: true - uses: signpath/github-action-submit-signing-request@v2 - with: - api-token: ${{ secrets.SIGNPATH_API_TOKEN }} - organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} - project-slug: ${{ secrets.SIGNPATH_PROJECT_SLUG }} - signing-policy-slug: ${{ secrets.SIGNPATH_SIGNING_POLICY_SLUG }} - artifact-configuration-slug: ${{ secrets.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }} - github-artifact-id: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }} - wait-for-completion: true - output-artifact-directory: signed-windows-attempt-1 - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Submit Windows signing request (attempt 2) - id: signpath-sign-attempt-2 - if: steps.signpath-sign-attempt-1.outcome == 'failure' - continue-on-error: true - uses: signpath/github-action-submit-signing-request@v2 - with: - api-token: ${{ secrets.SIGNPATH_API_TOKEN }} - organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} - project-slug: ${{ secrets.SIGNPATH_PROJECT_SLUG }} - signing-policy-slug: ${{ secrets.SIGNPATH_SIGNING_POLICY_SLUG }} - artifact-configuration-slug: ${{ secrets.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }} - github-artifact-id: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }} - wait-for-completion: true - output-artifact-directory: signed-windows-attempt-2 - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Submit Windows signing request (attempt 3) - id: signpath-sign-attempt-3 - if: steps.signpath-sign-attempt-1.outcome == 'failure' && steps.signpath-sign-attempt-2.outcome == 'failure' - continue-on-error: true - uses: signpath/github-action-submit-signing-request@v2 - with: - api-token: ${{ secrets.SIGNPATH_API_TOKEN }} - organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }} - project-slug: ${{ secrets.SIGNPATH_PROJECT_SLUG }} - signing-policy-slug: ${{ secrets.SIGNPATH_SIGNING_POLICY_SLUG }} - artifact-configuration-slug: ${{ secrets.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }} - github-artifact-id: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }} - wait-for-completion: true - output-artifact-directory: signed-windows-attempt-3 - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Fail when all SignPath signing attempts fail - if: steps.signpath-sign-attempt-1.outcome == 'failure' && steps.signpath-sign-attempt-2.outcome == 'failure' && steps.signpath-sign-attempt-3.outcome == 'failure' - shell: bash - run: | - echo "All SignPath signing attempts failed; rerun the workflow when SignPath is healthy." - exit 1 - - - name: Upload signed Windows artifacts (attempt 1) - if: steps.signpath-sign-attempt-1.outcome == 'success' - uses: actions/upload-artifact@v4 - with: - name: windows - path: | - signed-windows-attempt-1/*.exe - signed-windows-attempt-1/*.zip - - - name: Upload signed Windows artifacts (attempt 2) - if: steps.signpath-sign-attempt-2.outcome == 'success' - uses: actions/upload-artifact@v4 - with: - name: windows - path: | - signed-windows-attempt-2/*.exe - signed-windows-attempt-2/*.zip - - - name: Upload signed Windows artifacts (attempt 3) - if: steps.signpath-sign-attempt-3.outcome == 'success' - uses: actions/upload-artifact@v4 - with: - name: windows - path: | - signed-windows-attempt-3/*.exe - signed-windows-attempt-3/*.zip - release: needs: [build-linux, build-macos, build-windows] runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index d693e2f..547f4d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v0.5.3 (2026-03-09) + +### Changed +- Release: Publish unsigned Windows `.exe` and `.zip` artifacts directly from release CI instead of routing them through SignPath. +- Release: Added `bun run build:win:unsigned` for explicit local unsigned Windows packaging. + ## v0.5.2 (2026-03-09) ### Internal diff --git a/backlog/tasks/task-138 - Publish-unsigned-Windows-release-artifacts-and-add-local-unsigned-build-script.md b/backlog/tasks/task-138 - Publish-unsigned-Windows-release-artifacts-and-add-local-unsigned-build-script.md new file mode 100644 index 0000000..86cdb2d --- /dev/null +++ b/backlog/tasks/task-138 - Publish-unsigned-Windows-release-artifacts-and-add-local-unsigned-build-script.md @@ -0,0 +1,62 @@ +--- +id: TASK-138 +title: Publish unsigned Windows release artifacts and add local unsigned build script +status: Done +assignee: + - codex +created_date: '2026-03-09 00:00' +updated_date: '2026-03-09 00:00' +labels: + - release + - windows +dependencies: [] +references: + - .github/workflows/release.yml + - package.json + - src/release-workflow.test.ts +priority: high +--- + +## Description + + +Stop the tag-driven release workflow from depending on SignPath and publish unsigned Windows `.exe` and `.zip` artifacts directly. Add an explicit local `build:win:unsigned` script without changing the existing `build:win` command. + + +## Acceptance Criteria + +- [x] #1 Windows release CI builds unsigned artifacts without requiring SignPath secrets. +- [x] #2 The Windows release job uploads `release/*.exe` and `release/*.zip` directly as the `windows` artifact. +- [x] #3 The repo exposes a local `build:win:unsigned` script for explicit unsigned Windows packaging. +- [x] #4 Regression coverage fails if the workflow reintroduces SignPath submission or drops the unsigned script. + + +## Implementation Plan + + +1. Update workflow regression tests to assert unsigned Windows release behavior and the new local script. +2. Patch `package.json` to add `build:win:unsigned`. +3. Patch `.github/workflows/release.yml` to build unsigned Windows artifacts and upload them directly. +4. Add the release changelog fragment and run focused verification. + + +## Implementation Notes + + +Removed the Windows SignPath secret validation and submission steps from `.github/workflows/release.yml`. The Windows release job now runs `bun run build:win:unsigned` and uploads `release/*.exe` and `release/*.zip` directly as the `windows` artifact consumed by the release job. + +Added `scripts/build-win-unsigned.mjs` plus the `build:win:unsigned` package script. The wrapper clears Windows code-signing environment variables and disables identity auto-discovery before invoking `electron-builder`, so release CI stays unsigned even if signing credentials are configured elsewhere. + +Updated `src/release-workflow.test.ts` to assert the unsigned workflow contract and added the release changelog fragment in `changes/unsigned-windows-release-builds.md`. + + +## Final Summary + + +Windows release CI now publishes unsigned artifacts directly and no longer depends on SignPath. Local developers also have an explicit `bun run build:win:unsigned` path for unsigned packaging without changing the existing `build:win` command. + +Verification: +- `bun test src/release-workflow.test.ts` +- `bun run typecheck` +- `node --check scripts/build-win-unsigned.mjs` + diff --git a/backlog/tasks/task-139 - Cut-patch-release-v0.5.3-for-unsigned-Windows-release-builds.md b/backlog/tasks/task-139 - Cut-patch-release-v0.5.3-for-unsigned-Windows-release-builds.md new file mode 100644 index 0000000..117d308 --- /dev/null +++ b/backlog/tasks/task-139 - Cut-patch-release-v0.5.3-for-unsigned-Windows-release-builds.md @@ -0,0 +1,57 @@ +--- +id: TASK-139 +title: Cut patch release v0.5.3 for unsigned Windows release builds +status: Done +assignee: + - codex +created_date: '2026-03-09 00:00' +updated_date: '2026-03-09 00:00' +labels: + - release + - patch +dependencies: + - TASK-138 +references: + - package.json + - CHANGELOG.md + - release/release-notes.md +priority: high +--- + +## Description + + +Publish a patch release from the unsigned Windows release-build change by bumping the app version, generating committed changelog artifacts for `v0.5.3`, and pushing the release-prep commit. + + +## Acceptance Criteria + +- [x] #1 Repository version metadata is updated to `0.5.3`. +- [x] #2 `CHANGELOG.md` and `release/release-notes.md` contain the committed `v0.5.3` section and consumed fragments are removed. +- [x] #3 New `v0.5.3` release-prep commit is pushed to `origin/main`. + + +## Implementation Plan + + +1. Bump `package.json` from `0.5.2` to `0.5.3`. +2. Run `bun run changelog:build` so committed changelog artifacts match the new patch version. +3. Run changelog/typecheck/test verification. +4. Commit the release-prep change set and push `main`. + + +## Implementation Notes + + +Bumped `package.json` from `0.5.2` to `0.5.3`, ran `bun run changelog:build`, and committed the generated release artifacts. That prepended the `v0.5.3` section to `CHANGELOG.md`, regenerated `release/release-notes.md`, and removed the consumed `changes/unsigned-windows-release-builds.md` fragment. + +Verification before push: `bun run changelog:lint`, `bun run changelog:check --version 0.5.3`, `bun run typecheck`, and `bun run test:fast`. + + +## Final Summary + + +Prepared patch release `v0.5.3` so the unsigned Windows release-build change is captured in committed release metadata on `main`. Version metadata, changelog output, and release notes are aligned with the new patch version. + +Validation: `bun run changelog:lint`, `bun run changelog:check --version 0.5.3`, `bun run typecheck`, `bun run test:fast`. + diff --git a/docs/plans/2026-03-09-unsigned-windows-release-builds.md b/docs/plans/2026-03-09-unsigned-windows-release-builds.md new file mode 100644 index 0000000..6ca4338 --- /dev/null +++ b/docs/plans/2026-03-09-unsigned-windows-release-builds.md @@ -0,0 +1,77 @@ +# Unsigned Windows Release Builds Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Publish unsigned Windows release artifacts in GitHub Actions while adding an explicit local `build:win:unsigned` script. + +**Architecture:** Keep Windows packaging on `electron-builder`, but stop the release workflow from routing artifacts through SignPath. The Windows release job will build unsigned artifacts and upload them directly under the existing `windows` artifact name so the downstream release job stays stable. Local developer behavior remains unchanged except for a new explicit unsigned build script. + +**Tech Stack:** GitHub Actions, Bun, Electron Builder, Node test runner + +--- + +### Task 1: Track the workflow contract change + +**Files:** +- Create: `backlog/tasks/task-138 - Publish-unsigned-Windows-release-artifacts-and-add-local-unsigned-build-script.md` +- Create: `changes/unsigned-windows-release-builds.md` + +**Step 1: Write the backlog task + changelog fragment** + +Document the scope: unsigned Windows release CI, new local unsigned script, no SignPath dependency. + +**Step 2: Review file formatting** + +Run: `sed -n '1,220p' backlog/tasks/task-138\ -\ Publish-unsigned-Windows-release-artifacts-and-add-local-unsigned-build-script.md && sed -n '1,80p' changes/unsigned-windows-release-builds.md` +Expected: task metadata matches existing backlog files; changelog fragment matches `changes/README.md` format. + +### Task 2: Write failing workflow regression tests + +**Files:** +- Modify: `src/release-workflow.test.ts` + +**Step 1: Write the failing test** + +Replace SignPath-specific workflow assertions with assertions for: +- unsigned Windows artifacts built via `bun run build:win:unsigned` +- direct `windows` artifact upload from `release/*.exe` and `release/*.zip` +- no SignPath action references +- package scripts include `build:win:unsigned` + +**Step 2: Run test to verify it fails** + +Run: `bun test src/release-workflow.test.ts` +Expected: FAIL because the current workflow still validates SignPath secrets and submits signing requests. + +### Task 3: Patch package scripts and release workflow + +**Files:** +- Modify: `package.json` +- Modify: `.github/workflows/release.yml` + +**Step 1: Write minimal implementation** + +- add `build:win:unsigned` that clears Windows signing env and disables auto discovery before invoking `electron-builder --win nsis zip --publish never` +- change the Windows release job to remove SignPath secret validation/submission +- build Windows artifacts with `bun run build:win:unsigned` +- upload `release/*.exe` and `release/*.zip` directly as `windows` + +**Step 2: Run tests to verify they pass** + +Run: `bun test src/release-workflow.test.ts` +Expected: PASS + +### Task 4: Run focused verification + +**Files:** +- Modify: none + +**Step 1: Run focused checks** + +Run: `bun test src/release-workflow.test.ts && bun run typecheck` +Expected: all green + +**Step 2: Spot-check diff** + +Run: `git --no-pager diff -- .github/workflows/release.yml package.json src/release-workflow.test.ts changes/unsigned-windows-release-builds.md backlog/tasks/task-138\ -\ Publish-unsigned-Windows-release-artifacts-and-add-local-unsigned-build-script.md docs/plans/2026-03-09-unsigned-windows-release-builds.md` +Expected: only scoped unsigned-Windows workflow/script/docs changes diff --git a/package.json b/package.json index 7b670b1..c876864 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "subminer", - "version": "0.5.2", + "version": "0.5.3", "description": "All-in-one sentence mining overlay with AnkiConnect and dictionary integration", "packageManager": "bun@1.3.5", "main": "dist/main-entry.js", @@ -60,7 +60,8 @@ "build:mac": "bun run build && electron-builder --mac dmg zip --publish never", "build:mac:unsigned": "bun run build && env -u APPLE_ID -u APPLE_APP_SPECIFIC_PASSWORD -u APPLE_TEAM_ID -u CSC_LINK -u CSC_KEY_PASSWORD CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --mac dmg zip --publish never", "build:mac:zip": "bun run build && electron-builder --mac zip --publish never", - "build:win": "bun run build && electron-builder --win nsis zip --publish never" + "build:win": "bun run build && electron-builder --win nsis zip --publish never", + "build:win:unsigned": "bun run build && node scripts/build-win-unsigned.mjs" }, "keywords": [ "anki", diff --git a/release/release-notes.md b/release/release-notes.md index 12a0952..66cad4c 100644 --- a/release/release-notes.md +++ b/release/release-notes.md @@ -1,6 +1,7 @@ ## Highlights -### Internal -- Release: Pinned the Windows SignPath submission workflow to an explicit artifact-configuration slug instead of relying on the SignPath project's default configuration. +### Changed +- Release: Publish unsigned Windows `.exe` and `.zip` artifacts directly from release CI instead of routing them through SignPath. +- Release: Added `bun run build:win:unsigned` for explicit local unsigned Windows packaging. ## Installation diff --git a/scripts/build-win-unsigned.mjs b/scripts/build-win-unsigned.mjs new file mode 100644 index 0000000..2697358 --- /dev/null +++ b/scripts/build-win-unsigned.mjs @@ -0,0 +1,30 @@ +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; + +const env = { ...process.env }; + +for (const name of [ + 'CSC_LINK', + 'CSC_KEY_PASSWORD', + 'WIN_CSC_LINK', + 'WIN_CSC_KEY_PASSWORD', + 'CSC_NAME', + 'WIN_CSC_NAME', +]) { + delete env[name]; +} + +env.CSC_IDENTITY_AUTO_DISCOVERY = 'false'; + +const electronBuilderCli = fileURLToPath(new URL('../node_modules/electron-builder/out/cli/cli.js', import.meta.url)); + +const result = spawnSync(process.execPath, [electronBuilderCli, '--win', 'nsis', 'zip', '--publish', 'never'], { + stdio: 'inherit', + env, +}); + +if (result.error) { + throw result.error; +} + +process.exit(result.status ?? 1); diff --git a/src/release-workflow.test.ts b/src/release-workflow.test.ts index 97ee380..aa1afc2 100644 --- a/src/release-workflow.test.ts +++ b/src/release-workflow.test.ts @@ -38,18 +38,16 @@ test('release package scripts disable implicit electron-builder publishing', () assert.match(packageJson.scripts['build:appimage'] ?? '', /--publish never/); assert.match(packageJson.scripts['build:mac'] ?? '', /--publish never/); assert.match(packageJson.scripts['build:win'] ?? '', /--publish never/); + assert.match(packageJson.scripts['build:win:unsigned'] ?? '', /build-win-unsigned\.mjs/); }); -test('windows release workflow retries SignPath submission and fails only after exhausting attempts', () => { - assert.match(releaseWorkflow, /Submit Windows signing request \(attempt 1\)/); - assert.match(releaseWorkflow, /Submit Windows signing request \(attempt 2\)/); - assert.match(releaseWorkflow, /Submit Windows signing request \(attempt 3\)/); - assert.match(releaseWorkflow, /All SignPath signing attempts failed; rerun the workflow when SignPath is healthy\./); -}); - -test('windows release workflow pins the SignPath artifact configuration slug explicitly', () => { - assert.match(releaseWorkflow, /SIGNPATH_ARTIFACT_CONFIGURATION_SLUG/); - assert.match(releaseWorkflow, /artifact-configuration-slug: \$\{\{ secrets\.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG \}\}/); +test('windows release workflow publishes unsigned artifacts directly without SignPath', () => { + assert.match(releaseWorkflow, /Build unsigned Windows artifacts/); + assert.match(releaseWorkflow, /run: bun run build:win:unsigned/); + assert.match(releaseWorkflow, /name: windows/); + assert.match(releaseWorkflow, /path: \|\n\s+release\/\*\.exe\n\s+release\/\*\.zip/); + assert.ok(!releaseWorkflow.includes('signpath/github-action-submit-signing-request')); + assert.ok(!releaseWorkflow.includes('SIGNPATH_')); }); test('Makefile routes Windows install-plugin setup through bun and documents Windows builds', () => {