Repository navigation
fix(core): permission prompts and the agent indicator (#148) (#150) #48
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
| # THE CORE RELEASES ITSELF, AND TELLS PRISM (#23). | |
| # | |
| # core/ is the terminal of TWO apps. Prism builds against a pinned tag of the | |
| # `core-dist` branch, so a core change reaches Prism only when (1) core-dist is | |
| # re-split, (2) a core-v<version> tag is pushed, and (3) Prism's pin is bumped. | |
| # All three were done by hand, which is how an app falls silently behind. | |
| # Owner, 2026-09-19: "that compiled copy needs to be auto bumped when a new | |
| # Prism Terminal release or merge to main happens." | |
| # | |
| # So, on every push to main that touches core/: | |
| # 1. split core/ into core-dist and tag it core-v<core/package.json version>; | |
| # 2. open a PR in Maxaubert/Prism that bumps the pin to that tag. | |
| # | |
| # THE BUMP PROVES ITSELF (#23, and Prism #164). Prism's terminal gate used to | |
| # run only on the owner's machine, so a bump could not merge without a person. | |
| # It now runs on a GitHub runner (`terminal-gate.yml` in Prism; MEASURED green | |
| # 3 runs of 3, real dictation included), beside Prism's typecheck, lint and unit | |
| # tests. So the bump PR carries its own version bump, this job WAITS for every | |
| # check on it, and then: | |
| # - repo variable PRISM_AUTO_MERGE = 'true': it squash-merges the PR, Prism's | |
| # release.yml publishes the new version, and Prism's users see the update | |
| # chip. No person in the loop. This is a STANDING EXCEPTION to the owner's | |
| # "never merge without my word" rule, and only the owner switches it on. | |
| # - anything else (the default): the PR stays open, green, for a person to merge. | |
| # A red check never merges, in either mode: the PR stays open and says why. | |
| # | |
| # Step 2 needs a token that can write to the Prism repo, since this repo's own | |
| # GITHUB_TOKEN cannot: a fine-grained token on Maxaubert/Prism with Contents and | |
| # Pull requests read/write, stored here as the secret PRISM_BUMP_TOKEN. Without | |
| # it step 2 says so and is skipped; step 1 still happens. | |
| name: core-release | |
| on: | |
| push: | |
| branches: [main] | |
| paths: ['core/**'] | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| issues: write | |
| concurrency: | |
| group: core-release | |
| cancel-in-progress: false | |
| jobs: | |
| release: | |
| # MAIN ONLY (code review 2026-09-24, #34). A run by hand may pick any | |
| # branch, and this job force-pushes core-dist and pushes a tag that is never | |
| # moved: from a feature branch it released unreviewed core to Prism, and | |
| # with auto-merge on, Prism shipped it. By hand is for resuming on main. | |
| if: github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| tag: ${{ steps.tag.outputs.tag }} | |
| fresh: ${{ steps.tag.outputs.fresh }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Split core/ and tag it | |
| id: tag | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| version=$(node -p "require('./core/package.json').version") | |
| tag="core-v$version" | |
| echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| git subtree split --prefix=core -b core-dist-new | |
| tree=$(git rev-parse 'core-dist-new^{tree}') | |
| if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then | |
| if [ "$(git rev-parse "$tag^{tree}")" = "$tree" ]; then | |
| echo "::notice::$tag already holds exactly this core. Nothing to release." | |
| echo "fresh=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # A tag is a promise to everyone who pinned it. It is never moved. | |
| msg="core/ changed but core/package.json still says $version, and $tag already exists with different contents. Bump the version in core/package.json (ci.yml checks this on the PR)." | |
| echo "::error::$msg" | |
| # Not only a red run nobody opens (code review 2026-09-24, #39): two | |
| # PRs that bumped core to the same version both passed, and the | |
| # second one's core never reached Prism. An issue says so. | |
| gh issue create --title "Core change on main was not released: $tag already exists" \ | |
| --body "$msg Commit: $GITHUB_SHA. Open a PR that bumps core/package.json; its merge releases the core." || true | |
| exit 1 | |
| fi | |
| git push -f origin core-dist-new:core-dist | |
| git tag "$tag" core-dist-new | |
| git push origin "$tag" | |
| echo "fresh=true" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Released $tag" | |
| bump-prism: | |
| needs: release | |
| # On a fresh tag, or when run by hand: that is how a bump HELD by a red check | |
| # is resumed once the cause is fixed (it finds the open PR and carries on). | |
| if: needs.release.outputs.fresh == 'true' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| BUMP_TOKEN: ${{ secrets.PRISM_BUMP_TOKEN }} | |
| AUTO_MERGE: ${{ vars.PRISM_AUTO_MERGE }} | |
| TAG: ${{ needs.release.outputs.tag }} | |
| steps: | |
| - name: No token, no bump | |
| if: env.BUMP_TOKEN == '' | |
| run: | | |
| echo "::warning::$TAG is released, but the secret PRISM_BUMP_TOKEN is not set, so no PR was opened in Maxaubert/Prism. Bump Prism's pin by hand, or add the secret (see the top of core-release.yml) and re-run this workflow." | |
| - uses: actions/checkout@v4 | |
| if: env.BUMP_TOKEN != '' | |
| with: | |
| repository: Maxaubert/Prism | |
| token: ${{ secrets.PRISM_BUMP_TOKEN }} | |
| - uses: actions/setup-node@v4 | |
| if: env.BUMP_TOKEN != '' | |
| with: | |
| node-version: 22 | |
| - name: Bump the pin and the version, open the PR | |
| id: pr | |
| if: env.BUMP_TOKEN != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.PRISM_BUMP_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| branch="chore/$TAG" | |
| if [ -n "$(git ls-remote --heads origin "$branch")" ]; then | |
| # Already opened, by an earlier run: never rebuilt (a person may have | |
| # pushed a fix onto it). Carry on from its open PR, if it still has one. | |
| url=$(gh pr list --repo Maxaubert/Prism --head "$branch" --state open --json url --jq '.[0].url // ""') | |
| if [ -n "$url" ]; then | |
| echo "::notice::$branch is already open as $url. Resuming from its checks." | |
| echo "url=$url" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "::notice::$branch exists in Prism with no open PR (merged or closed). Nothing to do." | |
| fi | |
| exit 0 | |
| fi | |
| current=$(node -p "require('./package.json').devDependencies['prism-term-core']") | |
| want="github:Maxaubert/PrismTerminal#$TAG" | |
| if [ "$current" = "$want" ]; then | |
| echo "::notice::Prism already pins $TAG." | |
| exit 0 | |
| fi | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git checkout -b "$branch" | |
| npm pkg set "devDependencies.prism-term-core=$want" | |
| # A NEW Prism version, or release.yml refuses to publish and nobody's | |
| # update chip fires. A core FEATURE (its major.minor moved) is a minor | |
| # bump of Prism, anything else a patch: the owner's own rule. | |
| kind=$(node -e " | |
| const v = (s) => (String(s).match(/core-v(\d+)\.(\d+)\./) || []).slice(1, 3).join('.') | |
| process.stdout.write(v(process.argv[1]) && v(process.argv[1]) !== v(process.argv[2]) ? 'minor' : 'patch') | |
| " "$current" "$TAG") | |
| npm version "$kind" --no-git-tag-version >/dev/null | |
| version=$(node -p "require('./package.json').version") | |
| # THE LOCKFILE IS EDITED, NOT REGENERATED. `npm install --package-lock-only` | |
| # on this Linux runner rewrote about 300 unrelated lines of Prism's lock | |
| # (MEASURED on the first live bump, Prism #165: a different npm drops every | |
| # "libc" field, and lock-only mode invents bundled entries even under the | |
| # owner's own npm). A pin bump is FOUR lines: the two version fields npm | |
| # version has just set, the pin, and the commit it resolves to. The rest | |
| # of the entry (the core's own version and peer ranges) is read from the | |
| # released core itself, so the lock says what `npm ci` will find. | |
| sha=$(git ls-remote https://github.com/Maxaubert/PrismTerminal.git "refs/tags/$TAG" | cut -f1) | |
| if [ -z "$sha" ]; then echo "::error::$TAG was not found on PrismTerminal."; exit 1; fi | |
| curl -fsSL "https://raw.githubusercontent.com/Maxaubert/PrismTerminal/$sha/package.json" -o "$RUNNER_TEMP/core.json" | |
| WANT="$want" SHA="$sha" CORE="$RUNNER_TEMP/core.json" node -e " | |
| const fs = require('fs') | |
| const lock = JSON.parse(fs.readFileSync('package-lock.json', 'utf8')) | |
| const core = JSON.parse(fs.readFileSync(process.env.CORE, 'utf8')) | |
| const entry = lock.packages['node_modules/prism-term-core'] | |
| if (!entry) throw new Error('package-lock.json has no prism-term-core entry') | |
| lock.packages[''].devDependencies['prism-term-core'] = process.env.WANT | |
| entry.version = core.version | |
| entry.resolved = 'git+ssh://git@github.com/Maxaubert/PrismTerminal.git#' + process.env.SHA | |
| if (core.peerDependencies) entry.peerDependencies = core.peerDependencies | |
| fs.writeFileSync('package-lock.json', JSON.stringify(lock, null, 2) + '\n') | |
| " | |
| changed=$(git diff --numstat package-lock.json | cut -f1) | |
| if [ "${changed:-0}" -gt 12 ]; then | |
| echo "::error::The lockfile edit touched $changed lines; a pin bump is about four. Not pushing it." | |
| exit 1 | |
| fi | |
| git add package.json package-lock.json | |
| git commit -m "build(terminal): pin prism-term-core to $TAG, $version" | |
| git push origin "$branch" | |
| url=$(gh pr create --repo Maxaubert/Prism --base main --head "$branch" \ | |
| --title "Terminal core: prism-term-core $TAG (Prism $version)" \ | |
| --body "Opened automatically by PrismTerminal's \`core-release\` workflow: the shared terminal core released **$TAG**, and Prism pinned \`$current\`. This bumps the pin and makes Prism **$version** ($kind). | |
| What changed in the core: https://github.com/Maxaubert/PrismTerminal/commits/main/core | |
| The checks on this PR are the proof: typecheck, lint, the unit suite, and **terminal-gate**, which builds the app on a Windows runner and drives every scenario the terminal can break, real dictation included. | |
| If the repo variable \`PRISM_AUTO_MERGE\` in PrismTerminal is \`true\`, this merges itself once every check is green and Prism releases $version. Otherwise it waits here for a person. A red check never merges.") | |
| echo "url=$url" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Opened $url" | |
| - name: Wait for Prism's checks, then merge if the owner switched that on | |
| if: env.BUMP_TOKEN != '' && steps.pr.outputs.url != '' | |
| env: | |
| GH_TOKEN: ${{ secrets.PRISM_BUMP_TOKEN }} | |
| URL: ${{ steps.pr.outputs.url }} | |
| run: | | |
| set -uo pipefail | |
| # Checks take a moment to be registered; "no checks" is not "green". | |
| # THE GATE BY NAME (code review 2026-09-24, #38): a count of three | |
| # checks meant the gate only while Prism had exactly three; one more | |
| # check anywhere and --watch could finish green before terminal-gate | |
| # was even registered, and a bump merged without the ratchet. | |
| gate() { | |
| gh pr checks "$URL" --json workflow,state \ | |
| --jq "[.[] | select(.workflow == \"terminal-gate\"$1)] | length" 2>/dev/null || echo 0 | |
| } | |
| for i in $(seq 1 40); do | |
| [ "$(gate '')" -ge 1 ] && break | |
| sleep 15 | |
| done | |
| if [ "$(gate '')" -lt 1 ]; then | |
| echo "::error::Prism's terminal-gate never reported on $URL. Not merging." | |
| exit 1 | |
| fi | |
| if ! gh pr checks "$URL" --watch --interval 30 --fail-fast; then | |
| echo "::error::A check failed on $URL. The bump stays open; it is NOT merged." | |
| exit 1 | |
| fi | |
| if [ "$(gate ' and .state == "SUCCESS"')" -lt 1 ]; then | |
| echo "::error::terminal-gate did not pass on $URL (skipped or cancelled). Not merging." | |
| exit 1 | |
| fi | |
| if [ "${AUTO_MERGE:-}" = "true" ]; then | |
| gh pr merge "$URL" --squash --delete-branch | |
| echo "::notice::Merged $URL. Prism's release workflow publishes it now." | |
| else | |
| echo "::notice::$URL is green and waiting for a person (PRISM_AUTO_MERGE is not 'true')." | |
| fi |