diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index bbf8afc..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: ci - -on: - pull_request: - # Release validation is owned by the release workflows rather than this CI - # workflow: `release-stable` has a verify job before publishing, and - # `release-beta` builds from its selected release commit. Keep this trigger - # focused on PRs, main, and manual reruns instead of duplicating tag/release - # events that would run after those release workflows have already selected - # or validated their commit. - push: - branches: - - main - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ci-${{ github.event.pull_request.number || github.ref }} - # Prefer current-head signal over preserving superseded logs: PR authors often - # push fixups while this workflow is still running, and stale runs can report - # failures for commits reviewers no longer need to evaluate. Release workflows - # use cancel-in-progress: false where preserving build evidence matters more. - cancel-in-progress: true - -jobs: - validate: - name: Validate workspace - runs-on: ubuntu-latest - timeout-minutes: 30 - - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - - - name: Setup pnpm - uses: pnpm/action-setup@v5 - with: - version: 10.33.2 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - # `scripts/postinstall.mjs` only prebuilds package/tool entrypoints that - # are needed immediately after install for linked bins and shared - # sidecar/platform imports. It intentionally skips app outputs because - # building all apps would make every install run a Next/Electron-adjacent - # app build, even when a developer only needs packages/tools. - # - # Fresh CI typecheck/test still need these specific generated declarations: - # - `apps/daemon/dist/*.d.ts` for e2e runtime-adapter tests that import - # daemon runtime modules - # - `apps/desktop/dist/main/index.d.ts` for `apps/packaged` imports of - # `@open-design/desktop/main` - # - `apps/web/dist/sidecar/index.d.ts` for `apps/packaged` imports of - # `@open-design/web/sidecar` - # If postinstall grows a targeted app type-generation phase covering these - # three exports without broad app builds, this CI prebuild can be removed. - - name: Prebuild workspace type declarations - run: | - pnpm --filter @open-design/daemon build - pnpm --filter @open-design/desktop build - pnpm --filter @open-design/web build:sidecar - - - name: Typecheck workspaces - run: pnpm -r --workspace-concurrency=1 --if-present run typecheck - - - name: Check residual JS in TypeScript packages - run: pnpm check:residual-js - - - name: Test - run: pnpm test - - # Keep workspace builds serialized so generated dist output and local - # runtime artifacts are produced in a deterministic order. Parallel - # recursive builds would surface late-package failures sooner, but the - # current workspace is small enough that safer logs and fewer shared-FS - # races outweigh the lost parallelism; revisit if the package count grows. - - name: Build workspaces - run: pnpm -r --workspace-concurrency=1 --if-present run build diff --git a/.github/workflows/landing-page-ci.yml b/.github/workflows/landing-page-ci.yml deleted file mode 100644 index 12ee1a7..0000000 --- a/.github/workflows/landing-page-ci.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: landing-page-ci - -on: - pull_request: - paths: - - .github/workflows/landing-page-ci.yml - - .github/workflows/landing-page.yml - - apps/landing-page/** - - package.json - - pnpm-lock.yaml - - pnpm-workspace.yaml - push: - branches: - - main - paths: - - .github/workflows/landing-page-ci.yml - - .github/workflows/landing-page.yml - - apps/landing-page/** - - package.json - - pnpm-lock.yaml - - pnpm-workspace.yaml - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: landing-page-ci-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - validate: - name: Validate landing page - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - - - name: Setup pnpm - uses: pnpm/action-setup@v5 - with: - version: 10.33.2 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Typecheck landing page - run: pnpm --filter @open-design/landing-page typecheck - - - name: Build landing page - run: pnpm --filter @open-design/landing-page build - - - name: Verify zero external JavaScript - run: | - node <<'NODE' - const { readFileSync } = require('node:fs'); - const html = readFileSync('apps/landing-page/out/index.html', 'utf8'); - const forbidden = [ - /]*\bsrc=/i, - /type=["']module["']/i, - /\/_astro\/[^"'<>\s]+\.js/i, - ]; - for (const pattern of forbidden) { - if (pattern.test(html)) { - console.error(`Unexpected client JavaScript matched ${pattern}`); - process.exit(1); - } - } - NODE - - - name: Verify Cloudflare image resizing URLs - run: | - node <<'NODE' - const { readFileSync } = require('node:fs'); - const html = readFileSync('apps/landing-page/out/index.html', 'utf8'); - const resizedUrls = html.match(/https:\/\/static\.open-design\.ai\/cdn-cgi\/image\//g) ?? []; - if (resizedUrls.length < 16) { - console.error(`Expected at least 16 Cloudflare resized image URLs, found ${resizedUrls.length}`); - process.exit(1); - } - if (/(?:src|content)=["']\/assets\/[A-Za-z0-9_.-]+\.png/.test(html)) { - console.error('Found local /assets/*.png image reference in generated landing HTML.'); - process.exit(1); - } - NODE diff --git a/.github/workflows/landing-page-deploy.yml b/.github/workflows/landing-page-deploy.yml deleted file mode 100644 index d402335..0000000 --- a/.github/workflows/landing-page-deploy.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: landing-page-deploy - -on: - push: - branches: - - main - paths: - - .github/workflows/landing-page-deploy.yml - - .github/workflows/landing-page-ci.yml - - apps/landing-page/** - - package.json - - pnpm-lock.yaml - - pnpm-workspace.yaml - workflow_dispatch: - -permissions: - contents: read - deployments: write - -concurrency: - group: landing-page-deploy-${{ github.ref }} - cancel-in-progress: true - -jobs: - deploy: - name: Deploy landing page - runs-on: ubuntu-latest - timeout-minutes: 15 - - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - - - name: Setup pnpm - uses: pnpm/action-setup@v5 - with: - version: 10.33.2 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Typecheck landing page - run: pnpm --filter @open-design/landing-page typecheck - - - name: Build landing page - run: pnpm --filter @open-design/landing-page build - - - name: Verify zero external JavaScript - run: | - node <<'NODE' - const { readFileSync } = require('node:fs'); - const html = readFileSync('apps/landing-page/out/index.html', 'utf8'); - const forbidden = [ - /]*\bsrc=/i, - /type=["']module["']/i, - /\/_astro\/[^"'<>\s]+\.js/i, - ]; - for (const pattern of forbidden) { - if (pattern.test(html)) { - console.error(`Unexpected client JavaScript matched ${pattern}`); - process.exit(1); - } - } - NODE - - - name: Verify Cloudflare image resizing URLs - run: | - node <<'NODE' - const { readFileSync } = require('node:fs'); - const html = readFileSync('apps/landing-page/out/index.html', 'utf8'); - const resizedUrls = html.match(/https:\/\/static\.open-design\.ai\/cdn-cgi\/image\//g) ?? []; - if (resizedUrls.length < 16) { - console.error(`Expected at least 16 Cloudflare resized image URLs, found ${resizedUrls.length}`); - process.exit(1); - } - if (/(?:src|content)=["']\/assets\/[A-Za-z0-9_.-]+\.png/.test(html)) { - console.error('Found local /assets/*.png image reference in generated landing HTML.'); - process.exit(1); - } - NODE - - - name: Deploy to Cloudflare Pages - uses: cloudflare/wrangler-action@v3 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - workingDirectory: apps/landing-page - packageManager: npm - command: > - pages deploy out - --project-name=open-design-landing - --branch=${{ github.ref_name }} diff --git a/.github/workflows/metrics.yml b/.github/workflows/metrics.yml deleted file mode 100644 index ccc7c8f..0000000 --- a/.github/workflows/metrics.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: github-metrics - -on: - schedule: - # Runs daily at 00:15 UTC; output is committed to docs/assets/github-metrics.svg. - - cron: '15 0 * * *' - workflow_dispatch: - push: - branches: - - main - paths: - - .github/workflows/metrics.yml - -permissions: - contents: write - pull-requests: write - -jobs: - metrics: - name: Generate repository metrics SVG - runs-on: ubuntu-latest - steps: - - name: Generate GitHub repository metrics - uses: lowlighter/metrics@latest - with: - # Output path; the action opens/updates a PR when this file changes. - # Requires manual review to merge. If metrics unchanged, no PR is created. - filename: docs/assets/github-metrics.svg - - # Auth: METRICS_TOKEN must be a fine-grained PAT or GitHub App token that - # can create pull requests in this repository. GITHUB_TOKEN is kept only - # as a read/render fallback because many orgs disallow PR creation from it. - token: ${{ secrets.METRICS_TOKEN || secrets.GITHUB_TOKEN }} - committer_token: ${{ secrets.METRICS_TOKEN }} - output_action: pull-request - output_condition: data-changed - - # Use the repository template (per-repo metrics, not user metrics). - # Organization-owned repositories must be targeted explicitly, otherwise - # lowlighter/metrics infers the token owner and treats the target as an org. - template: repository - base: '' - user: nexu-io - repo: open-design - - # Plugins. Anything that requires a personal token will silently no-op - # without METRICS_TOKEN — the rest still produce a useful SVG. - plugin_contributors: yes - plugin_contributors_categories: | - { - "Skills": "skills/**", - "Design systems": "design-systems/**", - "Web": "apps/web/**", - "Daemon": "apps/daemon/**", - "Docs": "docs/**" - } - plugin_followup: yes - plugin_followup_sections: pr, issue - plugin_languages: yes - plugin_languages_details: lines, percentage - plugin_languages_limit: 8 - plugin_lines: yes - plugin_traffic: yes - plugin_stargazers: yes - plugin_stargazers_charts_type: chartist - - config_timezone: Asia/Shanghai - config_display: large diff --git a/.github/workflows/refresh-contributors-wall.yml b/.github/workflows/refresh-contributors-wall.yml deleted file mode 100644 index 0a77156..0000000 --- a/.github/workflows/refresh-contributors-wall.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: refresh-contributors-wall - -on: - # Daily refresh keeps the contributors wall CDN cache moving even when - # contributor data changes outside pull request merges. - schedule: - - cron: '0 1 * * *' - # Manual trigger: Use when you need to force-refresh the contributors wall - # outside the daily schedule (e.g., after a bulk contributor update or - # after fixing the cache_bust pattern in README files). - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -concurrency: - group: refresh-contributors-wall - cancel-in-progress: true - -jobs: - refresh: - name: Refresh contributors wall cache bust - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - - - name: Refresh cache bust date - run: | - DATE="$(date -u +%F)" - MATCHES="$(perl -0ne '$count += () = /cache_bust=\d{4}-\d{2}-\d{2}/g; END { print $count + 0 }' README*.md)" - if [ "$MATCHES" -eq 0 ]; then - echo "Warning: No cache_bust patterns found. README format may have changed." - exit 1 - fi - perl -0pi -e "s/cache_bust=\d{4}-\d{2}-\d{2}/cache_bust=$DATE/g" README*.md - - - name: Create refresh pull request - uses: peter-evans/create-pull-request@v8 - with: - # Auth mirrors the metrics workflow: prefer a repository token that can - # create pull requests, with GITHUB_TOKEN as a fallback for repos where - # Actions-created PRs are allowed. - token: ${{ secrets.METRICS_TOKEN || secrets.GITHUB_TOKEN }} - add-paths: 'README*.md' - branch: automation/refresh-contributors-wall - delete-branch: true - commit-message: 'docs(readme): refresh contributors wall' - title: 'docs(readme): refresh contributors wall' - body: | - Refreshes the contributors wall cache bust date in README files. - - Generated by the scheduled `refresh-contributors-wall` workflow. diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml deleted file mode 100644 index ec3b8ec..0000000 --- a/.github/workflows/release-beta.yml +++ /dev/null @@ -1,467 +0,0 @@ -name: release-beta - -on: - workflow_dispatch: - inputs: - signed: - description: "Build signed/notarized mac artifacts. Disable only for explicit unsigned validation releases." - required: true - type: boolean - default: true - -permissions: - contents: write - -concurrency: - group: open-design-release-beta - cancel-in-progress: false - -jobs: - metadata: - name: Prepare beta metadata - runs-on: ubuntu-latest - env: - GH_TOKEN: ${{ github.token }} - GITHUB_REPOSITORY: ${{ github.repository }} - OPEN_DESIGN_RELEASE_SIGNED: ${{ inputs.signed }} - outputs: - asset_version_suffix: ${{ steps.beta.outputs.asset_version_suffix }} - base_version: ${{ steps.beta.outputs.base_version }} - beta_tag: ${{ steps.beta.outputs.beta_tag }} - beta_version: ${{ steps.beta.outputs.beta_version }} - branch: ${{ steps.beta.outputs.branch }} - commit: ${{ steps.beta.outputs.commit }} - release_name: ${{ steps.beta.outputs.release_name }} - signed: ${{ steps.beta.outputs.signed }} - version_tag: ${{ steps.beta.outputs.version_tag }} - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - with: - fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - - - name: Prepare beta release metadata - id: beta - run: node --experimental-strip-types ./scripts/release-beta.ts - - build_mac: - name: Build beta mac arm64 - needs: metadata - runs-on: macos-14 - env: - GH_TOKEN: ${{ github.token }} - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v5 - with: - version: 10.33.2 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Apply beta package version - run: npm pkg set "version=${{ needs.metadata.outputs.beta_version }}" --prefix apps/packaged - - - name: Prepare Apple signing certificate - if: ${{ inputs.signed }} - env: - APPLE_SIGNING_CERTIFICATE_BASE64: ${{ secrets.APPLE_SIGNING_CERTIFICATE_BASE64 }} - APPLE_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }} - run: | - set -euo pipefail - cert_path="$RUNNER_TEMP/open-design-signing.p12" - if ! printf '%s' "$APPLE_SIGNING_CERTIFICATE_BASE64" | base64 --decode > "$cert_path" 2>/dev/null; then - printf '%s' "$APPLE_SIGNING_CERTIFICATE_BASE64" | base64 -D > "$cert_path" - fi - { - echo "CSC_LINK=$cert_path" - echo "CSC_KEY_PASSWORD=$APPLE_SIGNING_CERTIFICATE_PASSWORD" - } >> "$GITHUB_ENV" - - - name: Build beta mac artifacts - env: - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - run: | - set -euo pipefail - signed_flag="" - if [ "${{ inputs.signed }}" = "true" ]; then - signed_flag="--signed" - fi - pnpm exec tools-pack mac build \ - --dir "$RUNNER_TEMP/tools-pack" \ - --namespace release-beta \ - --portable \ - --to all \ - --json \ - $signed_flag - - - name: Prepare beta assets - id: assets - run: | - set -euo pipefail - release_dir="$RUNNER_TEMP/release-assets" - mkdir -p "$release_dir" - - source_dmg="$RUNNER_TEMP/tools-pack/out/mac/namespaces/release-beta/dmg/Open Design-release-beta.dmg" - source_zip="$RUNNER_TEMP/tools-pack/out/mac/namespaces/release-beta/zip/Open Design-release-beta.zip" - if [ ! -f "$source_dmg" ]; then - echo "expected dmg not found at $source_dmg" >&2 - exit 1 - fi - if [ ! -f "$source_zip" ]; then - echo "expected zip not found at $source_zip" >&2 - exit 1 - fi - - asset_suffix="${{ needs.metadata.outputs.asset_version_suffix }}" - versioned_dmg="open-design-${{ needs.metadata.outputs.beta_version }}${asset_suffix}-mac-arm64.dmg" - versioned_zip="open-design-${{ needs.metadata.outputs.beta_version }}${asset_suffix}-mac-arm64.zip" - dmg_checksum_file="$versioned_dmg.sha256" - zip_checksum_file="$versioned_zip.sha256" - - cp "$source_dmg" "$release_dir/$versioned_dmg" - cp "$source_zip" "$release_dir/$versioned_zip" - ( - cd "$release_dir" - shasum -a 256 "$versioned_dmg" > "$dmg_checksum_file" - shasum -a 256 "$versioned_zip" > "$zip_checksum_file" - ) - - zip_sha512="$(openssl dgst -sha512 -binary "$release_dir/$versioned_zip" | openssl base64 -A)" - zip_size="$(stat -f%z "$release_dir/$versioned_zip")" - zip_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${{ needs.metadata.outputs.version_tag }}/$versioned_zip" - release_date="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - cat > "$release_dir/latest-mac.yml" <- - pnpm exec tools-pack win build - --dir "${{ runner.temp }}/tools-pack" - --namespace release-beta-win - --portable - --to nsis - --json - - - name: Prepare windows beta assets - shell: pwsh - run: | - $releaseDir = Join-Path $env:RUNNER_TEMP "release-assets" - New-Item -ItemType Directory -Force -Path $releaseDir | Out-Null - - $sourceInstaller = Join-Path $env:RUNNER_TEMP "tools-pack/out/win/namespaces/release-beta-win/builder/Open Design-release-beta-win-setup.exe" - $sourceBlockmap = Join-Path $env:RUNNER_TEMP "tools-pack/out/win/namespaces/release-beta-win/builder/Open Design-release-beta-win-setup.exe.blockmap" - if (!(Test-Path $sourceInstaller)) { - throw "expected installer not found at $sourceInstaller" - } - if (!(Test-Path $sourceBlockmap)) { - throw "expected blockmap not found at $sourceBlockmap" - } - - $windowsAssetSuffix = ".unsigned" - $versionedInstaller = "open-design-${{ needs.metadata.outputs.beta_version }}$windowsAssetSuffix-win-x64-setup.exe" - $versionedBlockmap = "open-design-${{ needs.metadata.outputs.beta_version }}$windowsAssetSuffix-win-x64-setup.exe.blockmap" - $checksumFile = "$versionedInstaller.sha256" - Copy-Item $sourceInstaller (Join-Path $releaseDir $versionedInstaller) - Copy-Item $sourceBlockmap (Join-Path $releaseDir $versionedBlockmap) - - $installerPath = Join-Path $releaseDir $versionedInstaller - $hash = (Get-FileHash -Path $installerPath -Algorithm SHA256).Hash.ToLowerInvariant() - "$hash $versionedInstaller" | Set-Content -Path (Join-Path $releaseDir $checksumFile) - $installerBytes = [System.IO.File]::ReadAllBytes($installerPath) - $installerSha512 = [System.Convert]::ToBase64String([System.Security.Cryptography.SHA512]::Create().ComputeHash($installerBytes)) - $installerSize = (Get-Item $installerPath).Length - $installerUrl = "https://github.com/$env:GITHUB_REPOSITORY/releases/download/${{ needs.metadata.outputs.version_tag }}/$versionedInstaller" - $releaseDate = [DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ") - @( - 'version: "${{ needs.metadata.outputs.beta_version }}"' - 'files:' - " - url: `"$installerUrl`"" - " sha512: `"$installerSha512`"" - " size: $installerSize" - "path: `"$installerUrl`"" - "sha512: `"$installerSha512`"" - "releaseDate: `"$releaseDate`"" - "releaseNotes: `"Open Design beta ${{ needs.metadata.outputs.beta_version }}$windowsAssetSuffix`"" - ) | Set-Content -Path (Join-Path $releaseDir "latest.yml") - - - name: Upload windows release bundle - uses: actions/upload-artifact@v7 - with: - name: open-design-beta-win-release-assets - path: ${{ runner.temp }}/release-assets - - build_linux: - name: Build beta linux x64 - needs: metadata - runs-on: ubuntu-latest - env: - GH_TOKEN: ${{ github.token }} - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v5 - with: - version: 10.33.2 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Apply beta package version - env: - BETA_VERSION: ${{ needs.metadata.outputs.beta_version }} - run: npm pkg set "version=$BETA_VERSION" --prefix apps/packaged - - # `--containerized` builds the AppImage inside the electronuserland/builder - # Docker image (glibc 2.27 baseline) so the resulting binary runs on older - # distros than ubuntu-latest's glibc 2.39. Docker is preinstalled on the - # GitHub-hosted ubuntu-latest runner, so no extra setup is required. - - name: Build beta linux artifacts - run: | - set -euo pipefail - pnpm exec tools-pack linux build \ - --dir "$RUNNER_TEMP/tools-pack" \ - --namespace release-beta-linux \ - --portable \ - --to appimage \ - --containerized \ - --json - - - name: Prepare linux beta assets - env: - BETA_VERSION: ${{ needs.metadata.outputs.beta_version }} - run: | - set -euo pipefail - release_dir="$RUNNER_TEMP/release-assets" - mkdir -p "$release_dir" - - source_appimage="$RUNNER_TEMP/tools-pack/out/linux/namespaces/release-beta-linux/builder/Open Design-release-beta-linux.AppImage" - if [ ! -f "$source_appimage" ]; then - echo "expected AppImage not found at $source_appimage" >&2 - exit 1 - fi - - # Linux currently has no signing path in tools-pack, so the suffix is - # hardcoded to .unsigned (matching the windows convention above). - linux_asset_suffix=".unsigned" - versioned_appimage="open-design-${BETA_VERSION}${linux_asset_suffix}-linux-x64.AppImage" - checksum_file="$versioned_appimage.sha256" - - cp "$source_appimage" "$release_dir/$versioned_appimage" - ( - cd "$release_dir" - sha256sum "$versioned_appimage" > "$checksum_file" - ) - - - name: Upload linux release bundle - uses: actions/upload-artifact@v7 - with: - name: open-design-beta-linux-release-assets - path: ${{ runner.temp }}/release-assets - - publish: - name: Publish beta release - needs: - - metadata - - build_mac - - build_win - - build_linux - runs-on: ubuntu-latest - env: - GH_TOKEN: ${{ github.token }} - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - with: - fetch-depth: 0 - - - name: Download mac release bundle - uses: actions/download-artifact@v8 - with: - name: open-design-beta-mac-release-assets - path: ${{ runner.temp }}/release-assets/mac - - - name: Download windows release bundle - uses: actions/download-artifact@v8 - with: - name: open-design-beta-win-release-assets - path: ${{ runner.temp }}/release-assets/win - - - name: Download linux release bundle - uses: actions/download-artifact@v8 - with: - name: open-design-beta-linux-release-assets - path: ${{ runner.temp }}/release-assets/linux - - - name: Move beta tags to current commit - run: | - set -euo pipefail - git tag -f "${{ needs.metadata.outputs.version_tag }}" "$GITHUB_SHA" - git push origin "refs/tags/${{ needs.metadata.outputs.version_tag }}" --force - git tag -f "${{ needs.metadata.outputs.beta_tag }}" "$GITHUB_SHA" - git push origin "refs/tags/${{ needs.metadata.outputs.beta_tag }}" --force - - - name: Write release notes - id: notes - run: | - set -euo pipefail - version_notes_file="$RUNNER_TEMP/open-design-beta-version-notes.md" - latest_notes_file="$RUNNER_TEMP/open-design-beta-latest-notes.md" - cat > "$version_notes_file" < "$latest_notes_file" <> "$GITHUB_OUTPUT" - - - name: Create or update immutable beta prerelease - run: | - set -euo pipefail - release_dir="$RUNNER_TEMP/release-assets/mac" - all_release_dir="$RUNNER_TEMP/release-assets/all" - mkdir -p "$all_release_dir" - cp "$RUNNER_TEMP/release-assets/mac"/* "$all_release_dir/" - cp "$RUNNER_TEMP/release-assets/win"/* "$all_release_dir/" - cp "$RUNNER_TEMP/release-assets/linux"/* "$all_release_dir/" - if gh release view "${{ needs.metadata.outputs.version_tag }}" >/dev/null 2>&1; then - gh release edit "${{ needs.metadata.outputs.version_tag }}" \ - --title "${{ needs.metadata.outputs.release_name }}" \ - --notes-file "${{ steps.notes.outputs.version_notes_file }}" \ - --prerelease - else - gh release create "${{ needs.metadata.outputs.version_tag }}" \ - --target "$GITHUB_SHA" \ - --title "${{ needs.metadata.outputs.release_name }}" \ - --notes-file "${{ steps.notes.outputs.version_notes_file }}" \ - --prerelease - fi - gh release upload "${{ needs.metadata.outputs.version_tag }}" "$all_release_dir"/* --clobber - - - name: Create or update beta channel feed - run: | - set -euo pipefail - latest_mac_path="$RUNNER_TEMP/release-assets/mac/latest-mac.yml" - latest_win_path="$RUNNER_TEMP/release-assets/win/latest.yml" - if gh release view "${{ needs.metadata.outputs.beta_tag }}" >/dev/null 2>&1; then - while IFS= read -r asset_name; do - if [ -n "$asset_name" ]; then - gh release delete-asset "${{ needs.metadata.outputs.beta_tag }}" "$asset_name" --yes - fi - done < <(gh release view "${{ needs.metadata.outputs.beta_tag }}" --json assets --jq '.assets[].name') - gh release edit "${{ needs.metadata.outputs.beta_tag }}" \ - --title "Open Design Beta Latest" \ - --notes-file "${{ steps.notes.outputs.latest_notes_file }}" \ - --prerelease - gh release upload "${{ needs.metadata.outputs.beta_tag }}" "$latest_mac_path" "$latest_win_path" --clobber - else - gh release create "${{ needs.metadata.outputs.beta_tag }}" \ - "$latest_mac_path" "$latest_win_path" \ - --target "$GITHUB_SHA" \ - --title "Open Design Beta Latest" \ - --notes-file "${{ steps.notes.outputs.latest_notes_file }}" \ - --prerelease - fi - - - name: Publish summary - run: | - { - echo "## Beta release" - echo "- Channel: beta" - echo "- Version: ${{ needs.metadata.outputs.beta_version }}" - echo "- Version tag: ${{ needs.metadata.outputs.version_tag }}" - echo "- Channel feed tag: ${{ needs.metadata.outputs.beta_tag }}" - echo "- mac signed/notarized: ${{ needs.metadata.outputs.signed }}" - echo "- windows signed: false" - echo "- mac assets: open-design-${{ needs.metadata.outputs.beta_version }}${{ needs.metadata.outputs.asset_version_suffix }}-mac-arm64.dmg, open-design-${{ needs.metadata.outputs.beta_version }}${{ needs.metadata.outputs.asset_version_suffix }}-mac-arm64.zip" - echo "- win assets: open-design-${{ needs.metadata.outputs.beta_version }}.unsigned-win-x64-setup.exe, open-design-${{ needs.metadata.outputs.beta_version }}.unsigned-win-x64-setup.exe.blockmap" - echo "- linux assets: open-design-${{ needs.metadata.outputs.beta_version }}.unsigned-linux-x64.AppImage" - echo "- Feeds: latest-mac.yml, latest.yml (no latest-linux.yml; AppImage updater not yet wired)" - } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release-stable.yml b/.github/workflows/release-stable.yml deleted file mode 100644 index 87f1f53..0000000 --- a/.github/workflows/release-stable.yml +++ /dev/null @@ -1,486 +0,0 @@ -name: release-stable - -on: - workflow_dispatch: - inputs: - mac_signed: - description: "Build signed/notarized mac artifacts. Disable only for explicit unsigned validation releases." - required: true - type: boolean - default: true - -permissions: - contents: write - -concurrency: - group: open-design-release-stable - cancel-in-progress: false - -jobs: - metadata: - name: Prepare stable metadata - runs-on: ubuntu-latest - env: - GH_TOKEN: ${{ github.token }} - GITHUB_REPOSITORY: ${{ github.repository }} - outputs: - base_version: ${{ steps.stable.outputs.base_version }} - branch: ${{ steps.stable.outputs.branch }} - commit: ${{ steps.stable.outputs.commit }} - mac_signed: ${{ inputs.mac_signed }} - previous_stable: ${{ steps.stable.outputs.previous_stable }} - release_name: ${{ steps.stable.outputs.release_name }} - stable_version: ${{ steps.stable.outputs.stable_version }} - version_tag: ${{ steps.stable.outputs.version_tag }} - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - with: - fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - - - name: Prepare stable release metadata - id: stable - run: node --experimental-strip-types ./scripts/release-stable.ts - - verify: - name: Verify build (typecheck + tests) - needs: metadata - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v5 - with: - version: 10.33.2 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - # `scripts/postinstall.mjs` auto-builds `packages/*` and `tools/*`, but - # `apps/daemon` and `apps/desktop` are not in that list. On a fresh clone - # (every CI run), workspace typecheck fails because: - # - `e2e/scripts/runtime-adapter.e2e.live.test.ts` imports types from - # `apps/daemon/dist/*.js` - # - `apps/packaged/src/index.ts` dynamic-imports `@open-design/desktop/main` - # which resolves to `apps/desktop/dist/main/index.d.ts` - # Build them explicitly here. Keeps the root `typecheck` script untouched. - - name: Build daemon and desktop (typecheck dependencies) - run: | - pnpm --filter @open-design/daemon build - pnpm --filter @open-design/desktop build - - - name: Typecheck workspaces - run: pnpm -r --workspace-concurrency=1 --if-present run typecheck - - - name: Check residual JS in TypeScript packages - run: pnpm check:residual-js - - # Workspace tests are intentionally not gated here. apps/web's - # i18n content-coverage tests assert that every locale carries - # display metadata for every prompt template / skill / design - # system. Those tests fail on `main` as of this writing because - # PR #187 added two new prompt templates without translating - # their metadata into the 9 ship-ready locales — an i18n drift - # that's out of scope for the release infrastructure. Tracked as - # a follow-up; revisit once locale metadata is back in sync. - - build_mac: - name: Build stable mac arm64 - needs: [metadata, verify] - runs-on: macos-14 - env: - GH_TOKEN: ${{ github.token }} - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v5 - with: - version: 10.33.2 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Prepare Apple signing certificate - if: ${{ inputs.mac_signed }} - env: - APPLE_SIGNING_CERTIFICATE_BASE64: ${{ secrets.APPLE_SIGNING_CERTIFICATE_BASE64 }} - APPLE_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }} - run: | - set -euo pipefail - cert_path="$RUNNER_TEMP/open-design-signing.p12" - if ! printf '%s' "$APPLE_SIGNING_CERTIFICATE_BASE64" | base64 --decode > "$cert_path" 2>/dev/null; then - printf '%s' "$APPLE_SIGNING_CERTIFICATE_BASE64" | base64 -D > "$cert_path" - fi - { - echo "CSC_LINK=$cert_path" - echo "CSC_KEY_PASSWORD=$APPLE_SIGNING_CERTIFICATE_PASSWORD" - } >> "$GITHUB_ENV" - - - name: Build stable mac artifacts - env: - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - run: | - set -euo pipefail - signed_flag="" - if [ "${{ inputs.mac_signed }}" = "true" ]; then - signed_flag="--signed" - fi - pnpm exec tools-pack mac build \ - --dir "$RUNNER_TEMP/tools-pack" \ - --namespace release-stable \ - --portable \ - --to all \ - --json \ - $signed_flag - - - name: Prepare stable mac assets - id: assets - run: | - set -euo pipefail - release_dir="$RUNNER_TEMP/release-assets" - mkdir -p "$release_dir" - - source_dmg="$RUNNER_TEMP/tools-pack/out/mac/namespaces/release-stable/dmg/Open Design-release-stable.dmg" - source_zip="$RUNNER_TEMP/tools-pack/out/mac/namespaces/release-stable/zip/Open Design-release-stable.zip" - if [ ! -f "$source_dmg" ]; then - echo "expected dmg not found at $source_dmg" >&2 - exit 1 - fi - if [ ! -f "$source_zip" ]; then - echo "expected zip not found at $source_zip" >&2 - exit 1 - fi - - versioned_dmg="open-design-${{ needs.metadata.outputs.stable_version }}-mac-arm64.dmg" - versioned_zip="open-design-${{ needs.metadata.outputs.stable_version }}-mac-arm64.zip" - dmg_checksum_file="$versioned_dmg.sha256" - zip_checksum_file="$versioned_zip.sha256" - - cp "$source_dmg" "$release_dir/$versioned_dmg" - cp "$source_zip" "$release_dir/$versioned_zip" - ( - cd "$release_dir" - shasum -a 256 "$versioned_dmg" > "$dmg_checksum_file" - shasum -a 256 "$versioned_zip" > "$zip_checksum_file" - ) - - zip_sha512="$(openssl dgst -sha512 -binary "$release_dir/$versioned_zip" | openssl base64 -A)" - zip_size="$(stat -f%z "$release_dir/$versioned_zip")" - zip_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${{ needs.metadata.outputs.version_tag }}/$versioned_zip" - release_date="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - cat > "$release_dir/latest-mac.yml" <- - pnpm exec tools-pack win build - --dir "${{ runner.temp }}/tools-pack" - --namespace release-stable-win - --portable - --to nsis - --json - - - name: Prepare windows stable assets - shell: pwsh - run: | - $releaseDir = Join-Path $env:RUNNER_TEMP "release-assets" - New-Item -ItemType Directory -Force -Path $releaseDir | Out-Null - - $sourceInstaller = Join-Path $env:RUNNER_TEMP "tools-pack/out/win/namespaces/release-stable-win/builder/Open Design-release-stable-win-setup.exe" - $sourceBlockmap = Join-Path $env:RUNNER_TEMP "tools-pack/out/win/namespaces/release-stable-win/builder/Open Design-release-stable-win-setup.exe.blockmap" - if (!(Test-Path $sourceInstaller)) { - throw "expected installer not found at $sourceInstaller" - } - if (!(Test-Path $sourceBlockmap)) { - throw "expected blockmap not found at $sourceBlockmap" - } - - $versionedInstaller = "open-design-${{ needs.metadata.outputs.stable_version }}-win-x64-setup.exe" - $versionedBlockmap = "open-design-${{ needs.metadata.outputs.stable_version }}-win-x64-setup.exe.blockmap" - $checksumFile = "$versionedInstaller.sha256" - Copy-Item $sourceInstaller (Join-Path $releaseDir $versionedInstaller) - Copy-Item $sourceBlockmap (Join-Path $releaseDir $versionedBlockmap) - - $installerPath = Join-Path $releaseDir $versionedInstaller - $hash = (Get-FileHash -Path $installerPath -Algorithm SHA256).Hash.ToLowerInvariant() - "$hash $versionedInstaller" | Set-Content -Path (Join-Path $releaseDir $checksumFile) - $installerBytes = [System.IO.File]::ReadAllBytes($installerPath) - $installerSha512 = [System.Convert]::ToBase64String([System.Security.Cryptography.SHA512]::Create().ComputeHash($installerBytes)) - $installerSize = (Get-Item $installerPath).Length - $installerUrl = "https://github.com/$env:GITHUB_REPOSITORY/releases/download/${{ needs.metadata.outputs.version_tag }}/$versionedInstaller" - $releaseDate = [DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ") - @( - 'version: "${{ needs.metadata.outputs.stable_version }}"' - 'files:' - " - url: `"$installerUrl`"" - " sha512: `"$installerSha512`"" - " size: $installerSize" - "path: `"$installerUrl`"" - "sha512: `"$installerSha512`"" - "releaseDate: `"$releaseDate`"" - "releaseNotes: `"Open Design ${{ needs.metadata.outputs.stable_version }}`"" - ) | Set-Content -Path (Join-Path $releaseDir "latest.yml") - - - name: Upload windows release bundle - uses: actions/upload-artifact@v7 - with: - name: open-design-stable-win-release-assets - path: ${{ runner.temp }}/release-assets - - build_linux: - name: Build stable linux x64 - needs: [metadata, verify] - runs-on: ubuntu-latest - env: - GH_TOKEN: ${{ github.token }} - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - with: - fetch-depth: 0 - - - name: Setup pnpm - uses: pnpm/action-setup@v5 - with: - version: 10.33.2 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: 24 - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - # `--containerized` builds the AppImage inside the electronuserland/builder - # Docker image (glibc 2.27 baseline) so the resulting binary runs on older - # distros than ubuntu-latest's glibc 2.39. Docker is preinstalled on the - # GitHub-hosted ubuntu-latest runner, so no extra setup is required. - - name: Build stable linux artifacts - run: | - set -euo pipefail - pnpm exec tools-pack linux build \ - --dir "$RUNNER_TEMP/tools-pack" \ - --namespace release-stable-linux \ - --portable \ - --to appimage \ - --containerized \ - --json - - - name: Prepare linux stable assets - env: - STABLE_VERSION: ${{ needs.metadata.outputs.stable_version }} - run: | - set -euo pipefail - release_dir="$RUNNER_TEMP/release-assets" - mkdir -p "$release_dir" - - source_appimage="$RUNNER_TEMP/tools-pack/out/linux/namespaces/release-stable-linux/builder/Open Design-release-stable-linux.AppImage" - if [ ! -f "$source_appimage" ]; then - echo "expected AppImage not found at $source_appimage" >&2 - exit 1 - fi - - # Linux currently has no signing path in tools-pack; the asset has no - # signing-related suffix (matches the windows convention above). - versioned_appimage="open-design-${STABLE_VERSION}-linux-x64.AppImage" - checksum_file="$versioned_appimage.sha256" - - cp "$source_appimage" "$release_dir/$versioned_appimage" - ( - cd "$release_dir" - sha256sum "$versioned_appimage" > "$checksum_file" - ) - - - name: Upload linux release bundle - uses: actions/upload-artifact@v7 - with: - name: open-design-stable-linux-release-assets - path: ${{ runner.temp }}/release-assets - - publish: - name: Publish stable release - needs: - - metadata - - verify - - build_mac - - build_win - - build_linux - runs-on: ubuntu-latest - env: - GH_TOKEN: ${{ github.token }} - steps: - - name: Checkout - uses: actions/checkout@v6.0.2 - with: - fetch-depth: 0 - - - name: Pre-flight tag/release check - run: | - set -euo pipefail - if git ls-remote --exit-code --tags origin "refs/tags/${{ needs.metadata.outputs.version_tag }}" >/dev/null 2>&1; then - echo "tag ${{ needs.metadata.outputs.version_tag }} already exists on origin; aborting" >&2 - exit 1 - fi - if gh release view "${{ needs.metadata.outputs.version_tag }}" >/dev/null 2>&1; then - echo "release ${{ needs.metadata.outputs.version_tag }} already exists; aborting" >&2 - exit 1 - fi - - - name: Download mac release bundle - uses: actions/download-artifact@v8 - with: - name: open-design-stable-mac-release-assets - path: ${{ runner.temp }}/release-assets/mac - - - name: Download windows release bundle - uses: actions/download-artifact@v8 - with: - name: open-design-stable-win-release-assets - path: ${{ runner.temp }}/release-assets/win - - - name: Download linux release bundle - uses: actions/download-artifact@v8 - with: - name: open-design-stable-linux-release-assets - path: ${{ runner.temp }}/release-assets/linux - - - name: Write release notes shell - id: notes - run: | - set -euo pipefail - notes_file="$RUNNER_TEMP/open-design-stable-notes.md" - cat > "$notes_file" <> "$GITHUB_OUTPUT" - - - name: Create draft release with tag - id: create_release - run: | - set -euo pipefail - # gh release create creates the tag at $GITHUB_SHA atomically with the release. - # Using --draft keeps the release invisible until all assets upload successfully; - # the cleanup step rolls back the release + tag together if any subsequent step fails. - gh release create "${{ needs.metadata.outputs.version_tag }}" \ - --target "$GITHUB_SHA" \ - --title "${{ needs.metadata.outputs.release_name }}" \ - --notes-file "${{ steps.notes.outputs.notes_file }}" \ - --draft - - - name: Upload assets to draft release - run: | - set -euo pipefail - all_release_dir="$RUNNER_TEMP/release-assets/all" - mkdir -p "$all_release_dir" - cp "$RUNNER_TEMP/release-assets/mac"/* "$all_release_dir/" - cp "$RUNNER_TEMP/release-assets/win"/* "$all_release_dir/" - cp "$RUNNER_TEMP/release-assets/linux"/* "$all_release_dir/" - gh release upload "${{ needs.metadata.outputs.version_tag }}" "$all_release_dir"/* - - - name: Promote draft to published latest - run: | - set -euo pipefail - gh release edit "${{ needs.metadata.outputs.version_tag }}" \ - --draft=false \ - --latest - - - name: Cleanup release + tag on failure - if: failure() && steps.create_release.outcome == 'success' - run: | - set +e - echo "publish failed after release was created; rolling back release and tag" - gh release delete "${{ needs.metadata.outputs.version_tag }}" --cleanup-tag --yes - # belt-and-suspenders: ensure remote tag is gone even if --cleanup-tag missed - git push origin --delete "refs/tags/${{ needs.metadata.outputs.version_tag }}" || true - - - name: Publish summary - run: | - { - echo "## Stable release" - echo "- Channel: stable" - echo "- Version: ${{ needs.metadata.outputs.stable_version }}" - echo "- Version tag: ${{ needs.metadata.outputs.version_tag }}" - echo "- mac signed/notarized: ${{ inputs.mac_signed }}" - echo "- windows signed: false" - echo "- mac assets: open-design-${{ needs.metadata.outputs.stable_version }}-mac-arm64.dmg, open-design-${{ needs.metadata.outputs.stable_version }}-mac-arm64.zip" - echo "- win assets: open-design-${{ needs.metadata.outputs.stable_version }}-win-x64-setup.exe, open-design-${{ needs.metadata.outputs.stable_version }}-win-x64-setup.exe.blockmap" - echo "- linux assets: open-design-${{ needs.metadata.outputs.stable_version }}-linux-x64.AppImage" - echo "- Feeds: latest-mac.yml, latest.yml (no latest-linux.yml; AppImage updater not yet wired)" - } >> "$GITHUB_STEP_SUMMARY"