fix: align chat timeline estimator with browser render #125
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*" | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Release version (for example 1.2.3 or v1.2.3)" | |
| required: true | |
| type: string | |
| permissions: | |
| contents: write | |
| jobs: | |
| preflight: | |
| name: Preflight | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| version: ${{ steps.release_meta.outputs.version }} | |
| tag: ${{ steps.release_meta.outputs.tag }} | |
| is_prerelease: ${{ steps.release_meta.outputs.is_prerelease }} | |
| make_latest: ${{ steps.release_meta.outputs.make_latest }} | |
| release_channel: ${{ steps.release_meta.outputs.release_channel }} | |
| npm_tag: ${{ steps.release_meta.outputs.npm_tag }} | |
| build_timestamp: ${{ steps.release_meta.outputs.build_timestamp }} | |
| ref: ${{ github.sha }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - id: release_meta | |
| name: Resolve release version | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then | |
| raw="${{ github.event.inputs.version }}" | |
| else | |
| raw="${GITHUB_REF_NAME}" | |
| fi | |
| version="${raw#v}" | |
| if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then | |
| echo "Invalid release version: $raw" >&2 | |
| exit 1 | |
| fi | |
| build_timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| echo "tag=v$version" >> "$GITHUB_OUTPUT" | |
| echo "build_timestamp=$build_timestamp" >> "$GITHUB_OUTPUT" | |
| if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "is_prerelease=false" >> "$GITHUB_OUTPUT" | |
| echo "make_latest=true" >> "$GITHUB_OUTPUT" | |
| echo "release_channel=stable" >> "$GITHUB_OUTPUT" | |
| echo "npm_tag=latest" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "is_prerelease=true" >> "$GITHUB_OUTPUT" | |
| echo "make_latest=false" >> "$GITHUB_OUTPUT" | |
| echo "release_channel=prerelease" >> "$GITHUB_OUTPUT" | |
| echo "npm_tag=next" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version-file: package.json | |
| - name: Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: package.json | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Install browser dependencies | |
| run: bun run --cwd apps/web test:browser:install | |
| - name: Format check | |
| run: bun run fmt:check | |
| - name: Lint | |
| run: bun run lint | |
| - name: Typecheck | |
| run: bun run typecheck | |
| - name: Test | |
| run: bun run test | |
| - name: Browser test | |
| run: bun run --cwd apps/web test:browser | |
| - name: Desktop smoke | |
| run: bun run test:desktop-smoke | |
| - name: Release smoke | |
| run: bun run release:smoke | |
| desktop_build: | |
| name: Desktop ${{ matrix.label }} | |
| needs: [preflight] | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - label: macOS arm64 | |
| runner: macos-14 | |
| platform: mac | |
| target: dmg | |
| arch: arm64 | |
| - label: Linux x64 | |
| runner: ubuntu-24.04 | |
| platform: linux | |
| target: AppImage | |
| arch: x64 | |
| - label: Windows x64 | |
| runner: windows-2022 | |
| platform: win | |
| target: nsis | |
| arch: x64 | |
| env: | |
| OKCODE_COMMIT_HASH: ${{ github.sha }} | |
| OKCODE_BUILD_TIMESTAMP: ${{ needs.preflight.outputs.build_timestamp }} | |
| OKCODE_RELEASE_CHANNEL: ${{ needs.preflight.outputs.release_channel }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| fetch-depth: 0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version-file: package.json | |
| - name: Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: package.json | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Align package versions to release version | |
| run: node scripts/update-release-package-versions.ts "${{ needs.preflight.outputs.version }}" | |
| - name: Build desktop artifact | |
| shell: bash | |
| env: | |
| CSC_LINK: ${{ secrets.CSC_LINK }} | |
| CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} | |
| APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} | |
| APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} | |
| APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} | |
| AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }} | |
| AZURE_TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }} | |
| AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME }} | |
| AZURE_TRUSTED_SIGNING_PUBLISHER_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_PUBLISHER_NAME }} | |
| run: | | |
| set -euo pipefail | |
| args=( | |
| --platform "${{ matrix.platform }}" | |
| --target "${{ matrix.target }}" | |
| --arch "${{ matrix.arch }}" | |
| --build-version "${{ needs.preflight.outputs.version }}" | |
| --verbose | |
| ) | |
| require_values() { | |
| for value in "$@"; do | |
| if [[ -z "$value" ]]; then | |
| return 1 | |
| fi | |
| done | |
| return 0 | |
| } | |
| if [[ "${{ matrix.platform }}" == "mac" ]]; then | |
| required_mac_signing_secrets=( | |
| CSC_LINK | |
| CSC_KEY_PASSWORD | |
| APPLE_API_KEY | |
| APPLE_API_KEY_ID | |
| APPLE_API_ISSUER | |
| ) | |
| missing_mac_signing_secrets=() | |
| for secret_name in "${required_mac_signing_secrets[@]}"; do | |
| if [[ -z "${!secret_name}" ]]; then | |
| missing_mac_signing_secrets+=("$secret_name") | |
| fi | |
| done | |
| if (( ${#missing_mac_signing_secrets[@]} > 0 )); then | |
| echo "Missing required macOS signing/notarization secrets: ${missing_mac_signing_secrets[*]}" >&2 | |
| exit 1 | |
| fi | |
| key_path="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8" | |
| printf '%s' "$APPLE_API_KEY" > "$key_path" | |
| export APPLE_API_KEY="$key_path" | |
| args+=(--signed --require-signed) | |
| elif [[ "${{ matrix.platform }}" == "win" ]]; then | |
| if require_values \ | |
| "$AZURE_TENANT_ID" \ | |
| "$AZURE_CLIENT_ID" \ | |
| "$AZURE_CLIENT_SECRET" \ | |
| "$AZURE_TRUSTED_SIGNING_ENDPOINT" \ | |
| "$AZURE_TRUSTED_SIGNING_ACCOUNT_NAME" \ | |
| "$AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME" \ | |
| "$AZURE_TRUSTED_SIGNING_PUBLISHER_NAME"; then | |
| args+=(--signed --require-signed) | |
| else | |
| echo "Azure Trusted Signing secrets not configured; building unsigned Windows artifact." >&2 | |
| fi | |
| fi | |
| bun run dist:desktop:artifact -- "${args[@]}" | |
| - name: Validate packaged artifact outputs | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| case "${{ matrix.platform }}" in | |
| mac) | |
| dmg=(release/*.dmg) | |
| manifest=(release/latest-mac*.yml) | |
| [[ ${#dmg[@]} -gt 0 ]] || { echo "Missing macOS DMG artifact" >&2; exit 1; } | |
| [[ ${#manifest[@]} -gt 0 ]] || { echo "Missing macOS updater manifest" >&2; exit 1; } | |
| ;; | |
| linux) | |
| appimage=(release/*.AppImage) | |
| [[ ${#appimage[@]} -gt 0 ]] || { echo "Missing Linux AppImage artifact" >&2; exit 1; } | |
| ;; | |
| win) | |
| installer=(release/*.exe) | |
| [[ ${#installer[@]} -gt 0 ]] || { echo "Missing Windows installer artifact" >&2; exit 1; } | |
| ;; | |
| esac | |
| - name: Collect release assets | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p release-publish | |
| shopt -s nullglob | |
| for pattern in \ | |
| "release/*.dmg" \ | |
| "release/*.zip" \ | |
| "release/*.AppImage" \ | |
| "release/*.exe" \ | |
| "release/*.blockmap" \ | |
| "release/latest*.yml"; do | |
| for file in $pattern; do | |
| cp "$file" release-publish/ | |
| done | |
| done | |
| - name: Upload desktop artifact bundle | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: desktop-${{ matrix.platform }}-${{ matrix.arch }} | |
| path: release-publish/* | |
| if-no-files-found: error | |
| publish_cli: | |
| name: Publish CLI | |
| needs: [preflight, desktop_build] | |
| if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.desktop_build.result == 'success' }} | |
| runs-on: ubuntu-24.04 | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} | |
| OKCODE_COMMIT_HASH: ${{ github.sha }} | |
| OKCODE_BUILD_TIMESTAMP: ${{ needs.preflight.outputs.build_timestamp }} | |
| OKCODE_RELEASE_CHANNEL: ${{ needs.preflight.outputs.release_channel }} | |
| steps: | |
| - name: Checkout release commit | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| fetch-depth: 0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version-file: package.json | |
| - name: Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: package.json | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Validate npm publish token | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| [[ -n "$NODE_AUTH_TOKEN" ]] || { echo "Missing secret NODE_AUTH_TOKEN" >&2; exit 1; } | |
| - name: Align package versions to release version | |
| run: node scripts/update-release-package-versions.ts "${{ needs.preflight.outputs.version }}" | |
| - name: Build CLI package | |
| run: bun run build --filter=@okcode/web --filter=okcodes | |
| - name: Verify npm pack | |
| working-directory: apps/server | |
| run: npm pack | |
| - name: Verify local CLI entrypoints | |
| working-directory: apps/server | |
| run: | | |
| node dist/index.mjs --version | |
| node dist/index.mjs --help >/dev/null | |
| node dist/index.mjs doctor --help >/dev/null | |
| - name: Publish okcodes | |
| run: > | |
| node apps/server/scripts/cli.ts publish | |
| --tag "${{ needs.preflight.outputs.npm_tag }}" | |
| --app-version "${{ needs.preflight.outputs.version }}" | |
| --verbose | |
| - name: Verify published CLI | |
| run: | | |
| npm exec --yes --package okcodes@${{ needs.preflight.outputs.version }} -- okcode --version | |
| npm exec --yes --package okcodes@${{ needs.preflight.outputs.version }} -- okcode --help >/dev/null | |
| release: | |
| name: Publish GitHub Release | |
| needs: [preflight, desktop_build, publish_cli] | |
| if: ${{ always() && !cancelled() && needs.preflight.result == 'success' && needs.desktop_build.result == 'success' && needs.publish_cli.result == 'success' }} | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| - name: Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: package.json | |
| - name: Download desktop artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: desktop-* | |
| merge-multiple: true | |
| path: release-assets | |
| - name: Stage release documentation | |
| env: | |
| RELEASE_VERSION: ${{ needs.preflight.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| notes="docs/releases/v${RELEASE_VERSION}.md" | |
| manifest="docs/releases/v${RELEASE_VERSION}/assets.md" | |
| [[ -f "$notes" ]] || { echo "Missing release notes: $notes" >&2; exit 1; } | |
| [[ -f "$manifest" ]] || { echo "Missing asset manifest: $manifest" >&2; exit 1; } | |
| cp CHANGELOG.md release-assets/okcode-CHANGELOG.md | |
| cp "$notes" release-assets/okcode-RELEASE-NOTES.md | |
| cp "$manifest" release-assets/okcode-ASSETS-MANIFEST.md | |
| - name: Validate release asset completeness | |
| run: node scripts/validate-release-assets.ts release-assets | |
| - name: Publish release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.preflight.outputs.tag }} | |
| target_commitish: ${{ needs.preflight.outputs.ref }} | |
| name: OK Code v${{ needs.preflight.outputs.version }} | |
| body_path: docs/releases/v${{ needs.preflight.outputs.version }}.md | |
| prerelease: ${{ needs.preflight.outputs.is_prerelease }} | |
| make_latest: ${{ needs.preflight.outputs.make_latest }} | |
| files: release-assets/* | |
| fail_on_unmatched_files: true | |
| finalize: | |
| name: Finalize release | |
| needs: [preflight, release] | |
| if: ${{ !cancelled() && needs.release.result == 'success' }} | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Validate release app secrets | |
| shell: bash | |
| env: | |
| RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }} | |
| RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} | |
| run: | | |
| set -euo pipefail | |
| [[ -n "$RELEASE_APP_ID" ]] || { echo "Missing secret RELEASE_APP_ID"; exit 1; } | |
| [[ "$RELEASE_APP_ID" =~ ^[0-9]+$ ]] || { echo "RELEASE_APP_ID must be a numeric GitHub App ID"; exit 1; } | |
| [[ -n "$RELEASE_APP_PRIVATE_KEY" ]] || { echo "Missing secret RELEASE_APP_PRIVATE_KEY"; exit 1; } | |
| - id: app_token | |
| name: Mint release app token | |
| uses: actions/create-github-app-token@v2 | |
| with: | |
| app-id: ${{ secrets.RELEASE_APP_ID }} | |
| private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} | |
| owner: ${{ github.repository_owner }} | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| token: ${{ steps.app_token.outputs.token }} | |
| persist-credentials: true | |
| - id: app_bot | |
| name: Resolve GitHub App bot identity | |
| env: | |
| GH_TOKEN: ${{ steps.app_token.outputs.token }} | |
| APP_SLUG: ${{ steps.app_token.outputs.app-slug }} | |
| run: | | |
| user_id="$(gh api "/users/${APP_SLUG}[bot]" --jq .id)" | |
| echo "name=${APP_SLUG}[bot]" >> "$GITHUB_OUTPUT" | |
| echo "email=${user_id}+${APP_SLUG}[bot]@users.noreply.github.com" >> "$GITHUB_OUTPUT" | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version-file: package.json | |
| - name: Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: package.json | |
| - id: update_versions | |
| name: Update version strings | |
| env: | |
| RELEASE_VERSION: ${{ needs.preflight.outputs.version }} | |
| run: node scripts/update-release-package-versions.ts "$RELEASE_VERSION" --github-output | |
| - name: Format package.json files | |
| if: steps.update_versions.outputs.changed == 'true' | |
| run: bunx oxfmt apps/server/package.json apps/desktop/package.json apps/web/package.json apps/mobile/package.json packages/contracts/package.json | |
| - name: Refresh lockfile | |
| if: steps.update_versions.outputs.changed == 'true' | |
| run: bun install --lockfile-only --ignore-scripts | |
| - name: Commit and push version bump | |
| if: steps.update_versions.outputs.changed == 'true' | |
| shell: bash | |
| env: | |
| RELEASE_TAG: ${{ needs.preflight.outputs.tag }} | |
| run: | | |
| if git diff --quiet -- apps/server/package.json apps/desktop/package.json apps/web/package.json apps/mobile/package.json packages/contracts/package.json bun.lock; then | |
| echo "No version changes to commit." | |
| exit 0 | |
| fi | |
| git config user.name "${{ steps.app_bot.outputs.name }}" | |
| git config user.email "${{ steps.app_bot.outputs.email }}" | |
| git add apps/server/package.json apps/desktop/package.json apps/web/package.json apps/mobile/package.json packages/contracts/package.json bun.lock | |
| git commit -m "chore(release): prepare $RELEASE_TAG" | |
| git push origin HEAD:main |