From 057efaea9aea6afe32e77f487e2c0b57bba3be44 Mon Sep 17 00:00:00 2001 From: hqhq1025 <1506751656@qq.com> Date: Sat, 18 Apr 2026 22:19:37 +0800 Subject: [PATCH 1/6] ci(release): add GitHub Releases pipeline for v0.1 (unsigned) - Replace changesets-only release.yml with a full tag-triggered build matrix (macos / windows / ubuntu) that packages and uploads installers to GitHub Releases via softprops/action-gh-release@v2 - Add gate job (lint + typecheck + test) that must pass before packaging - Add changelog job using mikepenz/release-changelog-builder-action@v5 (feat / fix / docs grouped from conventional commits) - workflow_dispatch includes a `draft` boolean for manual dry-runs - Add `release` script to apps/desktop/package.json (electron-builder --publish always) - Add releaseType comment to electron-builder.yml (no config change, provider was already wired) - Add ## Install section to README with per-platform unsigned-binary workaround instructions (Gatekeeper / SmartScreen / AppImage) Signed-off-by: hqhq1025 <1506751656@qq.com> --- .github/workflows/release.yml | 171 ++++++++++++++++++++++++++---- README.md | 36 +++++++ apps/desktop/electron-builder.yml | 3 + apps/desktop/package.json | 1 + 4 files changed, 193 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 69c59d1f..58110fa6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,15 +1,15 @@ name: Release on: - workflow_dispatch: push: - branches: [main] - paths: - - '.changeset/**' - - 'packages/**' - - 'apps/**' - - 'package.json' - - 'pnpm-lock.yaml' + tags: + - 'v*.*.*' + workflow_dispatch: + inputs: + draft: + description: 'Create as draft release' + type: boolean + default: true concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -20,10 +20,109 @@ permissions: id-token: write jobs: - release: - name: Version PR + # ------------------------------------------------------------------ + # Gate: lint / typecheck / test on ubuntu only (fast, cheap). + # Full matrix is already covered by ci.yml on PRs; we just need a + # quick sanity pass before we spend runner-minutes on packaging. + # ------------------------------------------------------------------ + gate: + name: Gate (lint · typecheck · test) + runs-on: ubuntu-latest + if: github.repository == 'OpenCoworkAI/open-codesign' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: pnpm + + - name: Install + run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm lint + + - name: Typecheck + run: pnpm typecheck + + - name: Test + run: pnpm -r test + + # ------------------------------------------------------------------ + # Changelog: build release notes from conventional commits. + # Runs in parallel with the gate; the build matrix waits for both. + # ------------------------------------------------------------------ + changelog: + name: Build changelog runs-on: ubuntu-latest if: github.repository == 'OpenCoworkAI/open-codesign' + outputs: + changelog: ${{ steps.build.outputs.changelog }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build changelog + id: build + uses: mikepenz/release-changelog-builder-action@v5 + with: + configurationJson: | + { + "categories": [ + { + "title": "### Features", + "labels": ["feat"], + "rules": [{ "pattern": "^feat", "on_property": "title" }] + }, + { + "title": "### Bug Fixes", + "labels": ["fix"], + "rules": [{ "pattern": "^fix", "on_property": "title" }] + }, + { + "title": "### Documentation", + "labels": ["docs"], + "rules": [{ "pattern": "^docs", "on_property": "title" }] + }, + { + "title": "### Other Changes", + "rules": [{ "pattern": "^(chore|refactor|perf|test|ci)", "on_property": "title" }] + } + ], + "ignore_labels": ["skip-changelog"], + "sort": { "order": "DESC", "on_property": "mergedAt" }, + "template": "#{{CHANGELOG}}\n\n**Full Changelog**: #{{RELEASE_DIFF}}" + } + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # ------------------------------------------------------------------ + # Build: native packaging per platform. + # electron-builder reads publish config from electron-builder.yml. + # We do NOT sign in v0.1 — notarization deferred to Stage 2. + # ------------------------------------------------------------------ + build: + name: Build (${{ matrix.os }}) + needs: [gate, changelog] + strategy: + fail-fast: false + matrix: + include: + - os: macos-latest + artifact_glob: 'apps/desktop/release/*.dmg' + - os: windows-latest + artifact_glob: 'apps/desktop/release/*.exe' + - os: ubuntu-latest + artifact_glob: 'apps/desktop/release/*.AppImage' + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 with: @@ -41,14 +140,50 @@ jobs: - name: Install run: pnpm install --frozen-lockfile - # Versioning only. Publishing (npm + GitHub release artifacts) is - # intentionally NOT wired up yet — packaging needs code-signing - # (Mac notarization, Windows Authenticode) which we do not have. - - name: Create Version Pull Request - uses: changesets/action@v1 + # Build all workspace packages (turborepo respects dep graph) + - name: Build workspace + run: pnpm -r build --filter '!@open-codesign/desktop' + + # Package the Electron app. + # CSC_IDENTITY_AUTO_DISCOVERY=false: skip ad-hoc Mac signing prompt. + # WIN_CSC_LINK / WIN_CSC_KEY_PASSWORD: intentionally unset (no cert in v0.1). + - name: Package desktop + env: + CSC_IDENTITY_AUTO_DISCOVERY: 'false' + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: pnpm --filter @open-codesign/desktop release + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: installer-${{ matrix.os }} + path: ${{ matrix.artifact_glob }} + if-no-files-found: error + retention-days: 7 + + # ------------------------------------------------------------------ + # Publish: create (or update draft) GitHub Release and attach files. + # ------------------------------------------------------------------ + publish: + name: Publish GitHub Release + needs: [build, changelog] + runs-on: ubuntu-latest + steps: + - name: Download all installers + uses: actions/download-artifact@v4 + with: + path: dist/ + merge-multiple: true + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 with: - version: pnpm version-packages - commit: 'chore(release): version packages' - title: 'chore(release): version packages' + tag_name: ${{ github.ref_name }} + name: ${{ github.ref_name }} + body: ${{ needs.changelog.outputs.changelog }} + draft: ${{ github.event_name == 'workflow_dispatch' && inputs.draft || false }} + prerelease: ${{ contains(github.ref_name, '-') }} + files: dist/** + fail_on_unmatched_files: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index a9e69ed5..5b43308b 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,42 @@ open-codesign is an open-source desktop app that turns natural-language prompts - **Lean**: Target install size ≤ 80 MB. No bundled runtimes, no telemetry by default. - **Ecosystem-friendly**: Designed to handoff to [open-cowork](https://github.com/OpenCoworkAI/open-cowork) for engineering, and to interoperate with Claude Artifacts. +## Install + +Download the latest installer from the [GitHub Releases](https://github.com/OpenCoworkAI/open-codesign/releases) page. + +| Platform | File | Notes | +|---|---|---| +| macOS (Apple Silicon) | `open-codesign-*-arm64.dmg` | See Gatekeeper note below | +| macOS (Intel) | `open-codesign-*-x64.dmg` | See Gatekeeper note below | +| Windows | `open-codesign-*-Setup.exe` | See SmartScreen note below | +| Linux | `open-codesign-*.AppImage` | See AppImage note below | + +**macOS — Gatekeeper warning (v0.1 is unsigned)** + +Because v0.1 installers are not notarized, macOS will block the double-click open. To run anyway: + +1. Right-click (or Control-click) the `.dmg` and choose **Open**. +2. In the dialog that appears, click **Open** again. + +You only need to do this once per install. + +**Windows — SmartScreen warning (v0.1 is unsigned)** + +Windows may show "Windows protected your PC". To proceed: + +1. Click **More info**. +2. Click **Run anyway**. + +**Linux — AppImage** + +```bash +chmod +x open-codesign-*.AppImage +./open-codesign-*.AppImage +``` + +> **Security note:** v0.1 binaries carry no code-signing certificate. Users who prefer a verified build can compile from source — see [CONTRIBUTING.md](./CONTRIBUTING.md). Code signing (Apple Developer ID + Windows Authenticode) is planned for Stage 2. + ## Status & Roadmap See [`docs/ROADMAP.md`](./docs/ROADMAP.md). MVP success criterion: replicate every public Claude Design demo. diff --git a/apps/desktop/electron-builder.yml b/apps/desktop/electron-builder.yml index 06ac316f..26b1b1b4 100644 --- a/apps/desktop/electron-builder.yml +++ b/apps/desktop/electron-builder.yml @@ -28,3 +28,6 @@ publish: provider: github owner: OpenCoworkAI repo: open-codesign + # releaseType: draft — softprops/action-gh-release controls draft/final; + # electron-builder is set to "release" so it uploads to an existing tag. + releaseType: release diff --git a/apps/desktop/package.json b/apps/desktop/package.json index ea1a3729..ade336cb 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -8,6 +8,7 @@ "dev": "electron-vite dev", "build": "electron-vite build && electron-builder", "build:dir": "electron-vite build && electron-builder --dir", + "release": "electron-vite build && electron-builder --publish always", "typecheck": "tsc --noEmit -p tsconfig.node.json && tsc --noEmit -p tsconfig.web.json", "test": "vitest run --passWithNoTests" }, From fab2e261c8088d6f7eff5ed7ccfe8d57cbb1883f Mon Sep 17 00:00:00 2001 From: hqhq1025 <1506751656@qq.com> Date: Sat, 18 Apr 2026 22:35:33 +0800 Subject: [PATCH 2/6] fix(release): require tag input on dispatch + single-stage publish - workflow_dispatch now requires an explicit `tag` input (must match v*.*.*), preventing accidental releases tagged "main" or any other branch name - publish job validates the tag format before downloading artifacts and derives tag_name / name / draft from event type (push vs dispatch) - release script changed to --publish never so electron-builder only builds; softprops/action-gh-release is the sole publish path - remove publish: block from electron-builder.yml to match --publish never Signed-off-by: hqhq1025 <1506751656@qq.com> --- .github/workflows/release.yml | 21 +++++++++++++++++---- apps/desktop/electron-builder.yml | 7 ------- apps/desktop/package.json | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 58110fa6..86d659a9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,8 +6,13 @@ on: - 'v*.*.*' workflow_dispatch: inputs: + tag: + description: 'Tag to release (e.g. v0.1.0). Must start with "v"' + required: true + type: string draft: description: 'Create as draft release' + required: false type: boolean default: true @@ -169,6 +174,14 @@ jobs: needs: [build, changelog] runs-on: ubuntu-latest steps: + - name: Validate tag input on dispatch + if: github.event_name == 'workflow_dispatch' + run: | + if [[ ! "${{ inputs.tag }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then + echo "::error::tag must match v*.*.* (got '${{ inputs.tag }}')" + exit 1 + fi + - name: Download all installers uses: actions/download-artifact@v4 with: @@ -178,11 +191,11 @@ jobs: - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - tag_name: ${{ github.ref_name }} - name: ${{ github.ref_name }} + tag_name: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }} + name: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }} body: ${{ needs.changelog.outputs.changelog }} - draft: ${{ github.event_name == 'workflow_dispatch' && inputs.draft || false }} - prerelease: ${{ contains(github.ref_name, '-') }} + draft: ${{ github.event_name == 'push' && false || inputs.draft }} + prerelease: ${{ contains(github.event_name == 'push' && github.ref_name || inputs.tag, '-') }} files: dist/** fail_on_unmatched_files: false env: diff --git a/apps/desktop/electron-builder.yml b/apps/desktop/electron-builder.yml index 26b1b1b4..6e148d24 100644 --- a/apps/desktop/electron-builder.yml +++ b/apps/desktop/electron-builder.yml @@ -24,10 +24,3 @@ linux: - target: AppImage arch: [x64] category: Graphics -publish: - provider: github - owner: OpenCoworkAI - repo: open-codesign - # releaseType: draft — softprops/action-gh-release controls draft/final; - # electron-builder is set to "release" so it uploads to an existing tag. - releaseType: release diff --git a/apps/desktop/package.json b/apps/desktop/package.json index ade336cb..865e8869 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -8,7 +8,7 @@ "dev": "electron-vite dev", "build": "electron-vite build && electron-builder", "build:dir": "electron-vite build && electron-builder --dir", - "release": "electron-vite build && electron-builder --publish always", + "release": "electron-vite build && electron-builder --publish never", "typecheck": "tsc --noEmit -p tsconfig.node.json && tsc --noEmit -p tsconfig.web.json", "test": "vitest run --passWithNoTests" }, From de3be65ce7b888cb6b1c790eef90dd32572ddab5 Mon Sep 17 00:00:00 2001 From: hqhq1025 <1506751656@qq.com> Date: Sat, 18 Apr 2026 22:41:40 +0800 Subject: [PATCH 3/6] fix(release): accept semver prerelease tags in dispatch validation Signed-off-by: hqhq1025 <1506751656@qq.com> --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 86d659a9..ecfc9c5f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -177,8 +177,8 @@ jobs: - name: Validate tag input on dispatch if: github.event_name == 'workflow_dispatch' run: | - if [[ ! "${{ inputs.tag }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then - echo "::error::tag must match v*.*.* (got '${{ inputs.tag }}')" + if [[ ! "${{ inputs.tag }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then + echo "::error::tag must be semver-like (e.g. v1.2.3 or v1.2.3-rc.1)" exit 1 fi From 3666297edd6b10aef54570ae6ee1f57be0b75684 Mon Sep 17 00:00:00 2001 From: hqhq1025 <1506751656@qq.com> Date: Sat, 18 Apr 2026 22:46:48 +0800 Subject: [PATCH 4/6] ci(release): commit-pin third-party actions to mitigate supply-chain risk Signed-off-by: hqhq1025 <1506751656@qq.com> --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ecfc9c5f..7abc98d7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,7 +40,7 @@ jobs: fetch-depth: 0 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - name: Setup Node uses: actions/setup-node@v4 @@ -77,7 +77,7 @@ jobs: - name: Build changelog id: build - uses: mikepenz/release-changelog-builder-action@v5 + uses: mikepenz/release-changelog-builder-action@c9dc8369bccbc41e0ac887f8fd674f5925d315f7 # v5 with: configurationJson: | { @@ -134,7 +134,7 @@ jobs: fetch-depth: 0 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - name: Setup Node uses: actions/setup-node@v4 @@ -189,7 +189,7 @@ jobs: merge-multiple: true - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: tag_name: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }} name: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }} From 7bfefe67a27b865af640e6348a59eb83b6a20e5b Mon Sep 17 00:00:00 2001 From: hqhq1025 <1506751656@qq.com> Date: Sat, 18 Apr 2026 23:01:56 +0800 Subject: [PATCH 5/6] ci(release): pin checkout ref to release tag for build/publish traceability Add RELEASE_REF env to all four jobs (gate, changelog, build, publish) that resolves to github.ref on push-tag triggers and to refs/tags/ on workflow_dispatch. Pass it as the `ref` input to every actions/checkout@v4 call so all jobs build from the exact tag commit rather than the default HEAD of the dispatching branch. Also strengthen the validate-tag step in the publish job: after the semver format check, verify that the tag exists on origin via git ls-remote before any artifacts are downloaded or released, providing early failure and a clear error message. Fixes the Major finding reported by Codex bot: source-to-binary traceability was broken on workflow_dispatch releases. Signed-off-by: hqhq1025 <1506751656@qq.com> --- .github/workflows/release.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7abc98d7..d3d55d01 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,9 +34,12 @@ jobs: name: Gate (lint · typecheck · test) runs-on: ubuntu-latest if: github.repository == 'OpenCoworkAI/open-codesign' + env: + RELEASE_REF: ${{ github.event_name == 'push' && github.ref || format('refs/tags/{0}', inputs.tag) }} steps: - uses: actions/checkout@v4 with: + ref: ${{ env.RELEASE_REF }} fetch-depth: 0 - name: Setup pnpm @@ -68,11 +71,14 @@ jobs: name: Build changelog runs-on: ubuntu-latest if: github.repository == 'OpenCoworkAI/open-codesign' + env: + RELEASE_REF: ${{ github.event_name == 'push' && github.ref || format('refs/tags/{0}', inputs.tag) }} outputs: changelog: ${{ steps.build.outputs.changelog }} steps: - uses: actions/checkout@v4 with: + ref: ${{ env.RELEASE_REF }} fetch-depth: 0 - name: Build changelog @@ -128,9 +134,12 @@ jobs: - os: ubuntu-latest artifact_glob: 'apps/desktop/release/*.AppImage' runs-on: ${{ matrix.os }} + env: + RELEASE_REF: ${{ github.event_name == 'push' && github.ref || format('refs/tags/{0}', inputs.tag) }} steps: - uses: actions/checkout@v4 with: + ref: ${{ env.RELEASE_REF }} fetch-depth: 0 - name: Setup pnpm @@ -173,6 +182,8 @@ jobs: name: Publish GitHub Release needs: [build, changelog] runs-on: ubuntu-latest + env: + RELEASE_REF: ${{ github.event_name == 'push' && github.ref || format('refs/tags/{0}', inputs.tag) }} steps: - name: Validate tag input on dispatch if: github.event_name == 'workflow_dispatch' @@ -181,6 +192,11 @@ jobs: echo "::error::tag must be semver-like (e.g. v1.2.3 or v1.2.3-rc.1)" exit 1 fi + # Verify the tag exists on the remote before proceeding + if ! git ls-remote --tags origin "refs/tags/${{ inputs.tag }}" | grep -q "${{ inputs.tag }}"; then + echo "::error::tag '${{ inputs.tag }}' does not exist on origin" + exit 1 + fi - name: Download all installers uses: actions/download-artifact@v4 From b2f9b2cf3f9382ebf0a24b99589c9a1a7cc2cb32 Mon Sep 17 00:00:00 2001 From: hqhq1025 <1506751656@qq.com> Date: Sat, 18 Apr 2026 23:08:49 +0800 Subject: [PATCH 6/6] ci(release): checkout before validating tag in publish job The validate-tag step called `git ls-remote --tags origin` but the publish job had no checkout, so the `origin` remote did not exist and the step always failed on workflow_dispatch. Fix: add `actions/checkout@v4` (ref=RELEASE_REF, fetch-depth=0) as the first step in the publish job. On push, RELEASE_REF is the pushed tag ref; on dispatch, it is `refs/tags/`, so a missing tag causes checkout to fail early with a clear git error before validate-tag even runs. The explicit ls-remote check is kept for a friendlier error message ("tag does not exist on origin" vs. git's generic reference not-found). Signed-off-by: hqhq1025 <1506751656@qq.com> --- .github/workflows/release.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3d55d01..6837f152 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -185,6 +185,11 @@ jobs: env: RELEASE_REF: ${{ github.event_name == 'push' && github.ref || format('refs/tags/{0}', inputs.tag) }} steps: + - uses: actions/checkout@v4 + with: + ref: ${{ env.RELEASE_REF }} + fetch-depth: 0 + - name: Validate tag input on dispatch if: github.event_name == 'workflow_dispatch' run: |