diff --git a/.github/actions/setup-git-committer/action.yml b/.github/actions/setup-git-committer/action.yml deleted file mode 100644 index 65c974c6ab48..000000000000 --- a/.github/actions/setup-git-committer/action.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: "Setup Git Committer" -description: "Create app token and configure git user" -inputs: - opencode-app-id: - description: "OpenCode GitHub App ID" - required: true - opencode-app-secret: - description: "OpenCode GitHub App private key" - required: true -outputs: - token: - description: "GitHub App token" - value: ${{ steps.apptoken.outputs.token }} - app-slug: - description: "GitHub App slug" - value: ${{ steps.apptoken.outputs.app-slug }} -runs: - using: "composite" - steps: - - name: Create app token - id: apptoken - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2 - with: - app-id: ${{ inputs.opencode-app-id }} - private-key: ${{ inputs.opencode-app-secret }} - owner: ${{ github.repository_owner }} - - - name: Configure git user - run: | - slug="${{ steps.apptoken.outputs.app-slug }}" - git config --global user.name "${slug}[bot]" - git config --global user.email "${slug}[bot]@users.noreply.github.com" - shell: bash - - - name: Clear checkout auth - run: | - git config --local --unset-all http.https://github.com/.extraheader || true - shell: bash - - - name: Configure git remote - run: | - git remote set-url origin https://x-access-token:${{ steps.apptoken.outputs.token }}@github.com/${{ github.repository }} - shell: bash diff --git a/.github/nix-hashes/publish.sh b/.github/nix-hashes/publish.sh new file mode 100644 index 000000000000..9cea85c26312 --- /dev/null +++ b/.github/nix-hashes/publish.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# publish.sh : run from a checkout of v1. Puts on bot/v1-nix-hashes as one commit on top of v1, +# opens or updates the pull request into v1, dispatches its required checks and enables auto-merge. Closes the pull +# request instead when v1 already has these hashes. Uses git, gh and jq only; never runs repository code. +set -euo pipefail + +here=$(cd "$(dirname "$0")" && pwd) +new=$(realpath "$1") +branch=bot/v1-nix-hashes +file=nix/hashes.json +title="chore: update nix node_modules hashes" +: "${GH_REPO:?}" "${GITHUB_SHA:?}" +git=(git -c core.hooksPath=/dev/null -c user.name='github-actions[bot]' -c user.email='41898282+github-actions[bot]@users.noreply.github.com') + +bash "$here/validate.sh" "$new" + +"${git[@]}" fetch --no-tags --depth=1 origin "+refs/heads/v1:refs/remotes/origin/v1" "$GITHUB_SHA" + +# A newer push that changed the inputs has its own run, which publishes hashes for it. Mirrors on.push.paths. +if ! git diff --quiet "$GITHUB_SHA" origin/v1 -- bun.lock package.json ':(glob)packages/*/package.json' \ + flake.lock nix/node_modules.nix nix/scripts patches; then + echo "::notice::v1 changed the hash inputs since $GITHUB_SHA; leaving it to the newer run" + exit 0 +fi + +# Only a same-repository pull request is the bot's; a fork can use the same branch name. +find_pr() { + gh pr list --repo "$GH_REPO" --base v1 --head "$branch" --state open --json number,isCrossRepository \ + --jq 'map(select(.isCrossRepository | not)) | .[0].number // empty' +} +pr=$(find_pr) + +"${git[@]}" checkout -B "$branch" origin/v1 +if cmp -s "$new" "$file"; then + echo "v1 already has these hashes" + if [ -n "$pr" ]; then + gh pr close "$pr" --repo "$GH_REPO" --delete-branch --comment "v1 already has these hashes." + fi + exit 0 +fi + +cp "$new" "$file" +bash "$here/validate.sh" "$file" +"${git[@]}" add -- "$file" +"${git[@]}" commit --no-verify -m "$title" +[ "$(git diff --name-only origin/v1 HEAD)" = "$file" ] || { + echo "::error::the commit changes more than $file" >&2 + exit 1 +} +"${git[@]}" push --no-verify --force origin "HEAD:refs/heads/$branch" + +if [ -z "$pr" ]; then + gh pr create --repo "$GH_REPO" --base v1 --head "$branch" --title "$title" \ + --body "Updates \`$file\` to the node_modules hashes computed for $GITHUB_SHA by bot-nix-hashes. check-bot-scope keeps this branch to that file." + pr=$(find_pr) +fi + +# Pushes and pull requests made with GITHUB_TOKEN start no workflows; dispatches do. These produce v1's pull request +# checks on the new head. +for workflow in typecheck.yml test.yml nix-eval.yml check-bot-scope.yml; do + gh workflow run "$workflow" --repo "$GH_REPO" --ref "$branch" +done + +gh pr merge "$pr" --repo "$GH_REPO" --auto --squash +echo "### Nix hashes" >> "${GITHUB_STEP_SUMMARY:-/dev/null}" +echo "Updated pull request #$pr" >> "${GITHUB_STEP_SUMMARY:-/dev/null}" diff --git a/.github/nix-hashes/validate.sh b/.github/nix-hashes/validate.sh new file mode 100644 index 000000000000..59a7abf1dd0a --- /dev/null +++ b/.github/nix-hashes/validate.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# validate.sh : fails unless has exactly the shape of nix/hashes.json, so the hashes bot can only ever publish +# three SRI hashes. Used by bot-nix-hashes.yml before it commits and by check-bot-scope.yml on the bot's pull request. +# Adding a system to the flake means adding it to SYSTEMS here. +set -euo pipefail + +file=${1:?usage: validate.sh } +SYSTEMS='["aarch64-darwin","aarch64-linux","x86_64-linux"]' + +fail() { + echo "::error::$file: $*" >&2 + exit 1 +} + +if [ ! -f "$file" ] || [ -L "$file" ]; then fail "not a regular file"; fi +[ "$(wc -c < "$file")" -lt 4096 ] || fail "4096 bytes or larger" +jq -se 'length == 1' "$file" > /dev/null 2>&1 || fail "not a single JSON value" +jq -e --argjson systems "$SYSTEMS" ' + type == "object" + and keys == ["nodeModules"] + and (.nodeModules | type == "object") + and (.nodeModules | keys == $systems) + and (.nodeModules | all(.[]; type == "string" and test("\\Asha256-[A-Za-z0-9+/]{43}=\\z"))) +' "$file" > /dev/null || fail "expected {\"nodeModules\": {<$SYSTEMS>: \"sha256-<44 base64 chars>\"}}" +# The canonical form rules out duplicate keys and anything else jq would drop on parse. +jq . "$file" | cmp -s - "$file" || fail "not formatted as jq . prints it" +echo "$file: valid" diff --git a/.github/workflows/bot-nix-hashes.yml b/.github/workflows/bot-nix-hashes.yml new file mode 100644 index 000000000000..13337383b6d9 --- /dev/null +++ b/.github/workflows/bot-nix-hashes.yml @@ -0,0 +1,202 @@ +name: bot-nix-hashes + +# Keeps nix/hashes.json in step with the lockfile through a pull request, using only GITHUB_TOKEN. +# - hash and compute run repository code (nix build) with a read-only token and no secrets. compute uploads only the +# new nix/hashes.json. +# - publish holds the write token. It runs only .github/nix-hashes/ from this commit, treats the artifact as data, +# validates its exact shape and commits only that file to bot/v1-nix-hashes. It never runs repository code. +# - check-bot-scope.yml, a required check, fails the bot's pull request unless it changes only a valid nix/hashes.json, +# so a tampered branch cannot auto-merge once v1 requires that check. +# This file, check-bot-scope.yml and .github/nix-hashes/ are the security boundary. + +permissions: + contents: read + +on: + workflow_dispatch: + push: + branches: [v1] + # Mirrored in .github/nix-hashes/publish.sh. + paths: + - "bun.lock" + - "package.json" + - "packages/*/package.json" + - "flake.lock" + - "nix/node_modules.nix" + - "nix/scripts/**" + - "patches/**" + - ".github/workflows/bot-nix-hashes.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # Native runners required: bun install cross-compilation flags (--os/--cpu) + # do not produce byte-identical node_modules as native installs. + hash: + strategy: + fail-fast: false + matrix: + include: + - system: x86_64-linux + runner: blacksmith-4vcpu-ubuntu-2404 + - system: aarch64-linux + runner: blacksmith-4vcpu-ubuntu-2404-arm + - system: aarch64-darwin + runner: macos-latest + runs-on: ${{ matrix.runner }} + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Nix + uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 + + - name: Compute node_modules hash + id: hash + env: + SYSTEM: ${{ matrix.system }} + run: | + set -euo pipefail + + BUILD_LOG=$(mktemp) + trap 'rm -f "$BUILD_LOG"' EXIT + + HASH="" + MAX_ATTEMPTS=3 + for ((ATTEMPT = 1; ATTEMPT <= MAX_ATTEMPTS; ATTEMPT++)); do + # Build with fakeHash to trigger hash mismatch and reveal correct hash + nix build ".#packages.${SYSTEM}.node_modules_updater" --no-link 2>&1 | tee "$BUILD_LOG" || true + + HASH="$(nix run --inputs-from . nixpkgs#gnugrep -- -oP 'got:\s*\Ksha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | tail -n1 || true)" + + [ -n "$HASH" ] && break + + if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then + echo "::warning::Attempt ${ATTEMPT}/${MAX_ATTEMPTS} produced no hash for ${SYSTEM}; retrying in $((ATTEMPT * 10))s" + sleep $((ATTEMPT * 10)) + fi + done + + if [ -z "$HASH" ]; then + echo "::error::Failed to compute hash for ${SYSTEM} after ${MAX_ATTEMPTS} attempts" + cat "$BUILD_LOG" + exit 1 + fi + + echo "$HASH" > hash.txt + echo "Computed hash for ${SYSTEM}: $HASH" + + - name: Upload hash + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: hash-${{ matrix.system }} + path: hash.txt + retention-days: 1 + + # Assembles nix/hashes.json from the per-system hashes and uploads it as the only input publish accepts. + compute: + needs: hash + runs-on: ubuntu-24.04 + outputs: + changed: ${{ steps.update.outputs.changed }} + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false + + - name: Download hash artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + path: ${{ runner.temp }}/hashes + pattern: hash-* + + - name: Update hashes.json + id: update + env: + HASHES: ${{ runner.temp }}/hashes + run: | + set -euo pipefail + + HASH_FILE="nix/hashes.json" + + [ -f "$HASH_FILE" ] || echo '{"nodeModules":{}}' > "$HASH_FILE" + + for SYSTEM in x86_64-linux aarch64-linux aarch64-darwin; do + HASH="$(tr -d '[:space:]' < "$HASHES/hash-${SYSTEM}/hash.txt")" + echo "${SYSTEM}: ${HASH}" + jq --arg sys "$SYSTEM" --arg h "$HASH" '.nodeModules[$sys] = $h' "$HASH_FILE" > tmp.json + mv tmp.json "$HASH_FILE" + done + + cat "$HASH_FILE" + bash .github/nix-hashes/validate.sh "$HASH_FILE" + + if git diff --quiet -- "$HASH_FILE"; then + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY" + echo "Status: no changes" >> "$GITHUB_STEP_SUMMARY" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY" + echo "Status: changed" >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload hashes.json + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: nix-hashes + path: nix/hashes.json + if-no-files-found: error + retention-days: 1 + + # Runs even when compute found no change, so an open bot pull request with stale hashes is closed, not merged. + publish: + needs: compute + if: github.repository == 'anomalyco/opencode' && github.ref == 'refs/heads/v1' + runs-on: ubuntu-24.04 + concurrency: + group: bot-nix-hashes-publish-v1 + cancel-in-progress: false + permissions: + contents: write + pull-requests: write + actions: write + + steps: + - name: Check out publish scripts + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + ref: ${{ github.sha }} + path: trusted + sparse-checkout: /.github/nix-hashes/ + sparse-checkout-cone-mode: false + persist-credentials: false + + # Holds the push credential; only nix/hashes.json is checked out and nothing in it runs. + - name: Check out v1 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + ref: v1 + path: repo + sparse-checkout: /nix/hashes.json + sparse-checkout-cone-mode: false + + - name: Download hashes.json + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: nix-hashes + path: ${{ runner.temp }}/nix-hashes + + - name: Publish + working-directory: repo + run: bash ../trusted/.github/nix-hashes/publish.sh "$RUNNER_TEMP/nix-hashes/hashes.json" + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} diff --git a/.github/workflows/check-bot-scope.yml b/.github/workflows/check-bot-scope.yml new file mode 100644 index 000000000000..d80289a8653e --- /dev/null +++ b/.github/workflows/check-bot-scope.yml @@ -0,0 +1,63 @@ +name: check-bot-scope + +# Meant to be required on v1. bot-nix-hashes.yml pushes bot/v1-nix-hashes and auto-merges its pull request without review, so this +# fails that branch unless it changes nothing but a valid nix/hashes.json. Every other branch passes. The bot's own +# pushes start no workflows, so bot-nix-hashes.yml dispatches this; pull_request covers anyone else pushing the branch. + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + bot-scope: + name: bot-scope + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Check branch + id: branch + env: + HEAD_REF: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }} + run: | + if [ "$HEAD_REF" = bot/v1-nix-hashes ]; then + echo "bot=true" >> "$GITHUB_OUTPUT" + else + echo "$HEAD_REF is not a bot branch; nothing to check" + fi + + - name: Checkout repository + if: steps.branch.outputs.bot == 'true' + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + fetch-depth: 0 + filter: blob:none + persist-credentials: false + + - name: Check changed files + if: steps.branch.outputs.bot == 'true' + env: + BASE: ${{ github.base_ref || 'v1' }} + run: | + set -euo pipefail + git fetch --no-tags --filter=blob:none origin "+refs/heads/$BASE:refs/remotes/origin/$BASE" + changed=$(git diff --name-only "origin/$BASE...HEAD") + echo "Changed files:" + echo "$changed" + if [ "$changed" != nix/hashes.json ]; then + echo "::error::bot/v1-nix-hashes may change only nix/hashes.json" + exit 1 + fi + + # Runs v1's validator: the step above proved this branch leaves .github/ unchanged. + - name: Validate nix/hashes.json + if: steps.branch.outputs.bot == 'true' + run: bash .github/nix-hashes/validate.sh nix/hashes.json diff --git a/.github/workflows/docs-locale-sync.yml b/.github/workflows/docs-locale-sync.yml deleted file mode 100644 index 5f921e8bb717..000000000000 --- a/.github/workflows/docs-locale-sync.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: docs-locale-sync - -on: - push: - branches: - - dev - paths: - - packages/web/src/content/docs/*.mdx - -jobs: - sync-locales: - if: false - #if: github.actor != 'opencode-agent[bot]' - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: write - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - with: - persist-credentials: false - fetch-depth: 0 - ref: ${{ github.ref_name }} - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Setup git committer - id: committer - uses: ./.github/actions/setup-git-committer - with: - opencode-app-id: ${{ vars.OPENCODE_APP_ID }} - opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - - - name: Compute changed English docs - id: changes - run: | - FILES=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- ':(glob)packages/web/src/content/docs/*.mdx' || true) - if [ -z "$FILES" ]; then - echo "has_changes=false" >> "$GITHUB_OUTPUT" - echo "No English docs changed in push range" - exit 0 - fi - echo "has_changes=true" >> "$GITHUB_OUTPUT" - { - echo "files<> "$GITHUB_OUTPUT" - - - name: Install OpenCode - if: steps.changes.outputs.has_changes == 'true' - run: curl -fsSL https://opencode.ai/install | bash - - - name: Sync locale docs with OpenCode - if: steps.changes.outputs.has_changes == 'true' - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - OPENCODE_CONFIG_CONTENT: | - { - "permission": { - "*": "deny", - "read": "allow", - "edit": "allow", - "glob": "allow", - "task": "allow" - } - } - run: | - opencode run --agent docs --model opencode/gpt-5.3-codex <<'EOF' - Update localized docs to match the latest English docs changes. - - Changed English doc files: - - ${{ steps.changes.outputs.files }} - - - Requirements: - 1. Update all relevant locale docs under packages/web/src/content/docs// so they reflect these English page changes. - 2. You MUST use the Task tool for translation work and launch subagents with subagent_type `translator` (defined in .opencode/agent/translator.md). - 3. Do not translate directly in the primary agent. Use translator subagent output as the source for locale text updates. - 4. Run translator subagent Task calls in parallel whenever file/locale translation work is independent. - 5. Use only the minimum tools needed for this task (read/glob, file edits, and translator Task). Do not use shell, web, search, or GitHub tools for translation work. - 6. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update. - 7. Keep locale docs structure aligned with their corresponding English pages. - 8. Do not modify English source docs in packages/web/src/content/docs/*.mdx. - 9. If no locale updates are needed, make no changes. - EOF - - - name: Commit and push locale docs updates - if: steps.changes.outputs.has_changes == 'true' - run: | - if [ -z "$(git status --porcelain)" ]; then - echo "No locale docs changes to commit" - exit 0 - fi - git add -A - git commit -m "docs(i18n): sync locale docs from english changes" - git pull --rebase --autostash origin "$GITHUB_REF_NAME" - git push origin HEAD:"$GITHUB_REF_NAME" diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml deleted file mode 100644 index ff36239fed5e..000000000000 --- a/.github/workflows/generate.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: generate - -on: - push: - branches: - - v1 - -jobs: - generate: - runs-on: blacksmith-4vcpu-ubuntu-2404 - permissions: - contents: write - pull-requests: write - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - - name: Setup Bun - uses: ./.github/actions/setup-bun - - - name: Setup git committer - id: committer - uses: ./.github/actions/setup-git-committer - with: - opencode-app-id: ${{ vars.OPENCODE_APP_ID }} - opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - - - name: Generate - run: ./script/generate.ts - - - name: Commit and push - run: | - if [ -z "$(git status --porcelain)" ]; then - echo "No changes to commit" - exit 0 - fi - git add -A - git commit -m "chore: generate" --allow-empty - git push origin HEAD:${{ github.ref_name }} --no-verify - # if ! git push origin HEAD:${{ github.event.pull_request.head.ref || github.ref_name }} --no-verify; then - # echo "" - # echo "============================================" - # echo "Failed to push generated code." - # echo "Please run locally and push:" - # echo "" - # echo " ./script/generate.ts" - # echo " git add -A && git commit -m \"chore: generate\" && git push" - # echo "" - # echo "============================================" - # exit 1 - # fi diff --git a/.github/workflows/models-snapshot.yml b/.github/workflows/models-snapshot.yml deleted file mode 100644 index 8b731908213c..000000000000 --- a/.github/workflows/models-snapshot.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: models-snapshot - -permissions: - contents: write - -on: - workflow_dispatch: - schedule: - - cron: "0 12 * * *" - -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: false - -jobs: - refresh: - if: github.repository == 'anomalyco/opencode' - runs-on: blacksmith-4vcpu-ubuntu-2404 - - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - with: - persist-credentials: false - ref: v2 - - # The refresh script only needs `fetch`; skip the monorepo install. - - name: Setup Bun - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 - with: - bun-version-file: package.json - - - name: Setup git committer - uses: ./.github/actions/setup-git-committer - with: - opencode-app-id: ${{ vars.OPENCODE_APP_ID }} - opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - - - name: Refresh snapshot - run: bun packages/core/script/update-models-snapshot.ts - - - name: Commit changes - run: | - set -euo pipefail - - FILE="packages/core/src/models-dev/snapshot.txt" - - if [ -z "$(git status --short -- "$FILE")" ]; then - echo "No changes to commit" - echo "### models.dev snapshot" >> "$GITHUB_STEP_SUMMARY" - echo "Status: no changes" >> "$GITHUB_STEP_SUMMARY" - exit 0 - fi - - git add "$FILE" - git commit -m "chore(core): refresh bundled models.dev snapshot" - - git pull --rebase --autostash origin v2 - git push origin HEAD:v2 --no-verify - - echo "### models.dev snapshot" >> "$GITHUB_STEP_SUMMARY" - echo "Status: committed $(git rev-parse --short HEAD)" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/nix-hashes.yml b/.github/workflows/nix-hashes.yml deleted file mode 100644 index 966c4c25246f..000000000000 --- a/.github/workflows/nix-hashes.yml +++ /dev/null @@ -1,160 +0,0 @@ -name: nix-hashes - -permissions: - contents: write - -on: - workflow_dispatch: - push: - branches: [v1] - paths: - - "bun.lock" - - "package.json" - - "packages/*/package.json" - - "flake.lock" - - "nix/node_modules.nix" - - "nix/scripts/**" - - "patches/**" - - ".github/workflows/nix-hashes.yml" - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - # Native runners required: bun install cross-compilation flags (--os/--cpu) - # do not produce byte-identical node_modules as native installs. - compute-hash: - strategy: - fail-fast: false - matrix: - include: - - system: x86_64-linux - runner: blacksmith-4vcpu-ubuntu-2404 - - system: aarch64-linux - runner: blacksmith-4vcpu-ubuntu-2404-arm - - system: aarch64-darwin - runner: macos-latest - runs-on: ${{ matrix.runner }} - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Setup Nix - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 - - - name: Compute node_modules hash - id: hash - env: - SYSTEM: ${{ matrix.system }} - run: | - set -euo pipefail - - BUILD_LOG=$(mktemp) - trap 'rm -f "$BUILD_LOG"' EXIT - - HASH="" - MAX_ATTEMPTS=3 - for ((ATTEMPT = 1; ATTEMPT <= MAX_ATTEMPTS; ATTEMPT++)); do - # Build with fakeHash to trigger hash mismatch and reveal correct hash - nix build ".#packages.${SYSTEM}.node_modules_updater" --no-link 2>&1 | tee "$BUILD_LOG" || true - - HASH="$(nix run --inputs-from . nixpkgs#gnugrep -- -oP 'got:\s*\Ksha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | tail -n1 || true)" - - [ -n "$HASH" ] && break - - if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then - echo "::warning::Attempt ${ATTEMPT}/${MAX_ATTEMPTS} produced no hash for ${SYSTEM}; retrying in $((ATTEMPT * 10))s" - sleep $((ATTEMPT * 10)) - fi - done - - if [ -z "$HASH" ]; then - echo "::error::Failed to compute hash for ${SYSTEM} after ${MAX_ATTEMPTS} attempts" - cat "$BUILD_LOG" - exit 1 - fi - - echo "$HASH" > hash.txt - echo "Computed hash for ${SYSTEM}: $HASH" - - - name: Upload hash - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: hash-${{ matrix.system }} - path: hash.txt - retention-days: 1 - - update-hashes: - needs: compute-hash - if: github.ref == 'refs/heads/v1' - runs-on: blacksmith-4vcpu-ubuntu-2404 - - steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - with: - persist-credentials: false - fetch-depth: 0 - ref: ${{ github.ref_name }} - - - name: Setup git committer - uses: ./.github/actions/setup-git-committer - with: - opencode-app-id: ${{ vars.OPENCODE_APP_ID }} - opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }} - - - name: Pull latest changes - run: | - git pull --rebase --autostash origin "$GITHUB_REF_NAME" - - - name: Download hash artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - with: - path: hashes - pattern: hash-* - - - name: Update hashes.json - run: | - set -euo pipefail - - HASH_FILE="nix/hashes.json" - - [ -f "$HASH_FILE" ] || echo '{"nodeModules":{}}' > "$HASH_FILE" - - for SYSTEM in x86_64-linux aarch64-linux aarch64-darwin; do - FILE="hashes/hash-${SYSTEM}/hash.txt" - if [ -f "$FILE" ]; then - HASH="$(tr -d '[:space:]' < "$FILE")" - echo "${SYSTEM}: ${HASH}" - jq --arg sys "$SYSTEM" --arg h "$HASH" '.nodeModules[$sys] = $h' "$HASH_FILE" > tmp.json - mv tmp.json "$HASH_FILE" - else - echo "::warning::Missing hash for ${SYSTEM}" - fi - done - - cat "$HASH_FILE" - - - name: Commit changes - run: | - set -euo pipefail - - HASH_FILE="nix/hashes.json" - - if [ -z "$(git status --short -- "$HASH_FILE")" ]; then - echo "No changes to commit" - echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY" - echo "Status: no changes" >> "$GITHUB_STEP_SUMMARY" - exit 0 - fi - - git add "$HASH_FILE" - git commit -m "chore: update nix node_modules hashes" - - git pull --rebase --autostash origin "$GITHUB_REF_NAME" - git push origin HEAD:"$GITHUB_REF_NAME" - - echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY" - echo "Status: committed $(git rev-parse --short HEAD)" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 7aa20a35f560..07c0e043f025 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -19,3 +19,25 @@ jobs: - name: Run typecheck run: bun typecheck + + # Replaces the generate bot: pull requests must commit the regenerated SDK, OpenAPI document and formatting. + generated: + runs-on: blacksmith-4vcpu-ubuntu-2404 + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Setup Bun + uses: ./.github/actions/setup-bun + + - name: Generate + run: ./script/generate.ts + + - name: Check generated files + run: | + if [ -n "$(git status --porcelain)" ]; then + git status --short + git diff + echo "::error::Generated files are out of date. Run ./script/generate.ts and commit the result." + exit 1 + fi