diff --git a/.github/workflows/fork-check.yml b/.github/workflows/fork-check.yml new file mode 100644 index 0000000000000..c37fe9100b265 --- /dev/null +++ b/.github/workflows/fork-check.yml @@ -0,0 +1,55 @@ +# Fork check: typechecks pull requests into collin/main and every push to it. +# +# Upstream's ci.yml runs on Blacksmith runners a fork does not have, so its checks stay queued +# forever and broken code reached collin/main unnoticed. This runs what fork-release.yml needs: +# a frozen install and the typecheck of the apps the desktop build bundles. +name: Fork check + +on: + pull_request: + branches: [collin/main] + push: + branches: [collin/main] + +permissions: + contents: read + +concurrency: + group: fork-check-${{ github.ref }} + cancel-in-progress: true + +jobs: + typecheck: + if: github.repository != 'pingdotgg/t3code' + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + # Plain git, as in release-desktop.yml: checkout's cleanup trips over the vendored .repos gitlinks. + - name: Check out + env: + GIT_TERMINAL_PROMPT: "0" + run: | + set -euo pipefail + git init -q . + git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" + git fetch -q --no-tags origin "$GITHUB_SHA" + git sparse-checkout set --no-cone '/*' '!/.repos/' + git -c checkout.workers=0 checkout -q --detach "$GITHUB_SHA" + + - name: Setup Vite+ + uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # v1 + with: + node-version-file: package.json + cache: false + run-install: false + + - name: Install dependencies + timeout-minutes: 15 + run: VP_GIT_HOOKS=0 vp install --frozen-lockfile + + - name: Typecheck server, desktop and web + run: | + set -euo pipefail + for app in server desktop web; do + (cd "apps/$app" && vp run typecheck) + done diff --git a/.github/workflows/fork-release.yml b/.github/workflows/fork-release.yml index 1485635ed18bb..4ea8319ea2d5d 100644 --- a/.github/workflows/fork-release.yml +++ b/.github/workflows/fork-release.yml @@ -13,10 +13,11 @@ # fork/upstream-nightly-watcher, a Cloudflare Worker, checks every minute and sends # repository_dispatch once per new upstream nightly; the cron is only a fallback. Every trigger # builds only when that nightly or collin/main is new. -# Nothing is pushed to a branch. Claude Code resolves a merge conflict for the build only -# (fork/resolve-conflicts.sh, with the ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN secret); -# collin/main itself is still synced by fork/sync.sh. When Claude cannot resolve it, the run fails -# (and GitHub mails the owner): then sync collin/main by hand. +# Nothing is pushed to a branch. vp pack does not typecheck, so prepare does: Claude Code resolves +# merge conflicts and fixes type errors for the build only (fork/resolve-merge.sh, with the +# ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN secret); collin/main itself is still synced by +# fork/sync.sh. When Claude cannot make it typecheck, the run fails (and GitHub mails the owner): +# then sync collin/main by hand. name: Fork release # The watcher reads this title to dispatch each upstream nightly only once. run-name: ${{ github.event.client_payload.upstream_tag && format('Fork release of {0}', github.event.client_payload.upstream_tag) || 'Fork release' }} @@ -98,8 +99,7 @@ jobs: fi # Plain git, as in release-desktop.yml: checkout's cleanup trips over the vendored .repos gitlinks. - - name: Merge upstream into the fork - id: merge + - name: Check out the fork if: steps.due.outputs.due == 'true' env: GIT_TERMINAL_PROMPT: "0" @@ -116,76 +116,66 @@ jobs: git fetch -q --no-tags upstream "$UPSTREAM_SHA" git sparse-checkout set --no-cone '/*' '!/.repos/' git -c checkout.workers=0 checkout -q --detach "$FORK_SHA" - if git merge -q --no-edit -m "Merge upstream ${UPSTREAM_SHA:0:12}" "$UPSTREAM_SHA"; then - echo "conflict=false" >> "$GITHUB_OUTPUT" - else - git diff --name-only --diff-filter=U | sed 's/^/::warning::conflict: /' - echo "conflict=true" >> "$GITHUB_OUTPUT" - fi - # One resolution per fork/upstream pair: a failed build leaves the pair due again on every run, - # and a retry must neither pay for nor re-roll the resolution. force asks Claude again. - - name: Restore an earlier resolution of these commits + # Before the merge: a conflicted package.json would break reading the Node version. The + # resolve script installs and typechecks with it. + - name: Setup Vite+ + if: steps.due.outputs.due == 'true' + uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # v1 + with: + node-version-file: package.json + cache: false + run-install: false + + - name: Merge upstream into the fork + if: steps.due.outputs.due == 'true' + env: + UPSTREAM_SHA: ${{ steps.refs.outputs.upstream_sha }} + run: | + set -euo pipefail + git merge -q --no-commit --no-ff -m "Merge upstream ${UPSTREAM_SHA:0:12}" "$UPSTREAM_SHA" || + [[ -n "$(git diff --name-only --diff-filter=U)" ]] + git diff --name-only --diff-filter=U | sed 's/^/::warning::conflict: /' + + # One result per fork/upstream pair: a failed build leaves the pair due again on every run, + # and a retry must neither pay for nor re-roll Claude's work. force asks Claude again. + - name: Restore an earlier result for these commits id: earlier - if: steps.merge.outputs.conflict == 'true' && !inputs.force + if: steps.due.outputs.due == 'true' && !inputs.force uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: - path: ${{ runner.temp }}/resolution - key: fork-merge-${{ steps.refs.outputs.fork_sha }}-${{ steps.refs.outputs.upstream_sha }} + path: ${{ runner.temp }}/merge + key: fork-build-${{ steps.refs.outputs.fork_sha }}-${{ steps.refs.outputs.upstream_sha }} - - name: Resolve conflicts with Claude - if: steps.merge.outputs.conflict == 'true' && steps.earlier.outputs.cache-hit != 'true' + # Resolves conflicts and fixes type errors (also of a clean merge, or of collin/main itself) + # with Claude, so a build only fails when Claude cannot make it typecheck. + - name: Resolve and typecheck + if: steps.due.outputs.due == 'true' && steps.earlier.outputs.cache-hit != 'true' env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} DISABLE_AUTOUPDATER: "1" CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" - FORK_SHA: ${{ steps.refs.outputs.fork_sha }} - UPSTREAM_SHA: ${{ steps.refs.outputs.upstream_sha }} run: | set -euo pipefail [[ -n "$ANTHROPIC_API_KEY" ]] || unset ANTHROPIC_API_KEY [[ -n "$CLAUDE_CODE_OAUTH_TOKEN" ]] || unset CLAUDE_CODE_OAUTH_TOKEN - if [[ -z "${ANTHROPIC_API_KEY:-}${CLAUDE_CODE_OAUTH_TOKEN:-}" ]]; then - echo "::error::upstream no longer merges cleanly into $FORK_BRANCH and neither ANTHROPIC_API_KEY nor CLAUDE_CODE_OAUTH_TOKEN is set; sync it by hand" - exit 1 - fi - resolution="$RUNNER_TEMP/resolution" - mkdir -p "$resolution" npm install -g --silent @anthropic-ai/claude-code@2.1.306 # The script of this workflow's commit, which differs from collin/main's on a dispatch from a branch. git fetch -q --no-tags origin "$GITHUB_SHA" - git show "$GITHUB_SHA:fork/resolve-conflicts.sh" > "$RUNNER_TEMP/resolve-conflicts.sh" - if bash "$RUNNER_TEMP/resolve-conflicts.sh"; then - git bundle create -q "$resolution/merge.bundle" HEAD "^$FORK_SHA" "^$UPSTREAM_SHA" - else - touch "$resolution/failed" - fi - - - name: Remember the resolution - if: steps.merge.outputs.conflict == 'true' && steps.earlier.outputs.cache-hit != 'true' - uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 - with: - path: ${{ runner.temp }}/resolution - key: fork-merge-${{ steps.refs.outputs.fork_sha }}-${{ steps.refs.outputs.upstream_sha }} + git show "$GITHUB_SHA:fork/resolve-merge.sh" > "$RUNNER_TEMP/resolve-merge.sh" + bash "$RUNNER_TEMP/resolve-merge.sh" - - name: Apply the resolution - if: steps.merge.outputs.conflict == 'true' - env: - CACHE_HIT: ${{ steps.earlier.outputs.cache-hit }} + - name: Apply the earlier result + if: steps.earlier.outputs.cache-hit == 'true' run: | set -euo pipefail - resolution="$RUNNER_TEMP/resolution" - if [[ -f "$resolution/failed" ]]; then - echo "::error::Claude could not resolve the conflicts with upstream (see the first run of these commits); sync $FORK_BRANCH by hand, or rerun with force" - exit 1 - fi - if [[ "$CACHE_HIT" == "true" ]]; then - git merge --abort - git fetch -q --no-tags "$resolution/merge.bundle" - git -c checkout.workers=0 checkout -q --detach FETCH_HEAD + merge="$RUNNER_TEMP/merge" + git merge --abort 2>/dev/null || true + if [[ -f "$merge/merge.bundle" ]]; then + git fetch -q --no-tags "$merge/merge.bundle" fi - git log -1 --format='Merge %H' + git -c checkout.workers=0 checkout -q --detach "$(cat "$merge/sha")" # The merge commit differs per job (timestamps) and a resolution must not be re-rolled, so the # build jobs check out this exact commit. The sha file keeps the artifact non-empty when @@ -204,6 +194,13 @@ jobs: git bundle create -q "$RUNNER_TEMP/merge/merge.bundle" HEAD "^$FORK_SHA" "^$UPSTREAM_SHA" fi + - name: Remember the result + if: steps.due.outputs.due == 'true' && steps.earlier.outputs.cache-hit != 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: ${{ runner.temp }}/merge + key: fork-build-${{ steps.refs.outputs.fork_sha }}-${{ steps.refs.outputs.upstream_sha }} + - if: steps.due.outputs.due == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: @@ -267,56 +264,6 @@ jobs: echo "::error::The Cloudflare watcher missed upstream $UPSTREAM_TAG (published $published). Check it with: npx wrangler tail (in fork/upstream-nightly-watcher)" exit 1 - # vp pack does not typecheck, so a type error would otherwise ship. Only the apps the desktop - # build bundles; publish waits for this. - typecheck: - needs: prepare - if: needs.prepare.outputs.due == 'true' - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - name: merge - path: ${{ runner.temp }}/merge - - - name: Check out the merge - env: - GIT_TERMINAL_PROMPT: "0" - FORK_SHA: ${{ needs.prepare.outputs.fork_sha }} - UPSTREAM_SHA: ${{ needs.prepare.outputs.upstream_sha }} - run: | - set -euo pipefail - merge="$RUNNER_TEMP/merge" - git init -q . - git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" - git remote add upstream "$UPSTREAM_URL" - git fetch -q --no-tags origin "$FORK_SHA" - git fetch -q --no-tags upstream "$UPSTREAM_SHA" - if [[ -f "$merge/merge.bundle" ]]; then - git fetch -q --no-tags "$merge/merge.bundle" - fi - git sparse-checkout set --no-cone '/*' '!/.repos/' - git -c checkout.workers=0 checkout -q --detach "$(cat "$merge/sha")" - - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # v1 - with: - node-version-file: package.json - cache: false - run-install: false - - - name: Install dependencies - timeout-minutes: 15 - run: vp install - - - name: Typecheck server, desktop and web - run: | - set -euo pipefail - for app in server desktop web; do - (cd "apps/$app" && vp run typecheck) - done - build: needs: prepare if: needs.prepare.outputs.due == 'true' @@ -439,7 +386,7 @@ jobs: # and GitHub refuses the same for a tag on an older fork commit whose workflows differ from the # head's (a fork commit pushed during the build). The release notes record what was built. publish: - needs: [prepare, typecheck, build] + needs: [prepare, build] runs-on: ubuntu-latest timeout-minutes: 15 steps: diff --git a/FORK.md b/FORK.md index a03f4b11c4d0a..629da124ef6e3 100644 --- a/FORK.md +++ b/FORK.md @@ -61,12 +61,19 @@ upstream's (the offset sorts a fork build above an upstream nightly of the same nightly channel takes it, and a fork client and fork server on the other OS stay on the same version. `-f force=true` builds without new commits. -A merge conflict is resolved by Claude Code (`fork/resolve-conflicts.sh`, Opus, at most $5 per -run) for the build only; the typecheck job still gates the release. It needs the repository secret -`CLAUDE_CODE_OAUTH_TOKEN` (from `claude setup-token`; bills the subscription's monthly Agent SDK -credit) or `ANTHROPIC_API_KEY` (Console API credits; wins when both are set). Each fork/upstream pair is resolved once and cached, so retries cost -nothing. When Claude cannot resolve it, every run of that pair fails: sync `collin/main` by hand, -then `gh workflow run fork-release.yml` (`-f force=true` asks Claude again instead). +`vp pack` does not typecheck, so every build typechecks the merge first +(`fork/resolve-merge.sh`): Claude Code (Opus, at most $5 per call) resolves merge conflicts, then +gets up to two calls to fix whatever the typecheck reports, also in files git merged cleanly (one +side calls what the other renamed) or broken on `collin/main` itself. Fixes go into the build +only. It needs the repository secret `CLAUDE_CODE_OAUTH_TOKEN` (from `claude setup-token`; bills +the subscription's monthly Agent SDK credit) or `ANTHROPIC_API_KEY` (Console API credits; wins +when both are set). Each fork/upstream pair is checked once and cached, so retries cost nothing. +When Claude cannot make it typecheck, the run fails: sync `collin/main` by hand, then +`gh workflow run fork-release.yml` (`-f force=true` asks Claude again instead). + +Pull requests into `collin/main` and pushes to it run `.github/workflows/fork-check.yml` (frozen +install and the same typecheck). Upstream's CI needs Blacksmith runners a fork does not have; its +workflows are disabled in this repository (Actions settings), otherwise their checks stay queued. Watcher setup (once, or after the token expires), from `fork/upstream-nightly-watcher/`: `npx wrangler deploy`, then copy a fine-grained token for `collissimo/t3code` (Contents: read and diff --git a/fork/resolve-conflicts.sh b/fork/resolve-conflicts.sh deleted file mode 100755 index 2742ad8f4538e..0000000000000 --- a/fork/resolve-conflicts.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash -# Resolves the conflicts of an in-progress `git merge` with Claude Code, then commits the merge. -# fork-release.yml runs it when upstream no longer merges cleanly into collin/main; the result only -# feeds that build, collin/main itself is still synced by hand or by the daily T3 task. -# Needs the claude CLI and ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN. -# Exit 1 = not resolved, nothing committed. -set -euo pipefail - -mapfile -t files < <(git diff --name-only --diff-filter=U) -if [[ ${#files[@]} -eq 0 ]]; then - echo "no merge conflicts to resolve" >&2 - exit 1 -fi - -# Everything outside the conflicted files must stay exactly as the merge left it. -excludes=() -for file in "${files[@]}"; do excludes+=(":(exclude,literal)$file"); done -outside() { git status --porcelain --untracked-files=all -- . "${excludes[@]}"; } -before=$(outside) - -list=$(printf -- '- %s\n' "${files[@]}") -prompt="This repository is a personal fork of pingdotgg/t3code (see FORK.md): the fork's own features -on top of upstream. A \`git merge\` of a new upstream commit into the fork stopped with conflicts in: - -$list -Resolve every conflict so the result keeps upstream's new behavior AND the fork's features. Read -both sides, and the commits that touched the lines (git log -p / git show, :2: is the fork, -:3: upstream), so you understand what each side meant. Adapt the fork's code to upstream's -renames and API changes rather than reverting them. - -Only edit the files listed above, and remove every conflict marker in them. For a modify/delete -conflict, keep the file if the fork still needs it, otherwise delete it with git rm. Do not stage, -commit, check out, or touch any other file. If you cannot tell what one side intended, leave that -conflict's markers in place and say why; an unresolved file is better than a wrong guess. - -End with one line per file: the file, and how you resolved it." - -claude -p "$prompt" \ - --model "${CLAUDE_MODEL:-opus}" \ - --max-budget-usd "${CLAUDE_MAX_BUDGET_USD:-5}" \ - --allowedTools Read Grep Glob Edit \ - "Bash(git diff:*)" "Bash(git log:*)" "Bash(git show:*)" "Bash(git status:*)" "Bash(git rm:*)" - -if [[ "$(outside)" != "$before" ]]; then - echo "::error::Claude changed files outside the conflicts:" - diff <(echo "$before") <(outside) || true - exit 1 -fi -unresolved=() -for file in "${files[@]}"; do - if [[ -f "$file" ]] && grep -qE '^(<<<<<<<|\|\|\|\|\|\|\||>>>>>>>)( |$)' "$file"; then - unresolved+=("$file") - fi -done -if [[ ${#unresolved[@]} -gt 0 ]]; then - printf '::error::still conflicted: %s\n' "${unresolved[@]}" - exit 1 -fi - -git add -A -- "${files[@]}" -git commit -q --no-edit -echo "Resolved ${#files[@]} conflicted file(s): $(git rev-parse HEAD)" diff --git a/fork/resolve-merge.sh b/fork/resolve-merge.sh new file mode 100755 index 0000000000000..c7af190c6df50 --- /dev/null +++ b/fork/resolve-merge.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# Makes the checked-out fork build typecheck: resolves the conflicts of an in-progress `git merge` +# with Claude Code, typechecks, lets Claude fix what the typecheck reports, then commits. +# fork-release.yml runs it on every build (merge started with --no-commit, or nothing to merge); +# the result only feeds that build, collin/main itself is still synced by hand or by the daily T3 +# task. Needs vp, and for Claude the claude CLI and ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN. +# Exit 1 = not buildable, nothing committed. +set -euo pipefail + +mapfile -t files < <(git diff --name-only --diff-filter=U) +# `vp install` rewrites the lockfile: upstream's when it conflicts, plus what the fork's +# package.json files add. +fixable=(pnpm-lock.yaml) + +# Everything outside the files Claude may edit must stay exactly as the merge left it. +excludes=() +for file in "${files[@]}" "${fixable[@]}"; do excludes+=(":(exclude,literal)$file"); done +outside() { git status --porcelain --untracked-files=all -- . "${excludes[@]}"; } +before=$(outside) + +# Files Claude may edit later drop out of `outside`, so they drop out of the snapshot as well. +check_outside() { + local expected + expected=$(awk 'NR == FNR { allowed[$0]; next } !(substr($0, 4) in allowed)' \ + <(printf '%s\n' "${files[@]}" "${fixable[@]}") <(echo "$before")) + if [[ "$(outside)" != "$expected" ]]; then + echo "::error::Claude changed files it was not given:" + diff <(echo "$expected") <(outside) || true + exit 1 + fi +} + +claude_run() { + if [[ -n "${GITHUB_ACTIONS:-}" && -z "${ANTHROPIC_API_KEY:-}${CLAUDE_CODE_OAUTH_TOKEN:-}" ]]; then + echo "::error::this build needs Claude, but neither ANTHROPIC_API_KEY nor CLAUDE_CODE_OAUTH_TOKEN is set; sync collin/main by hand" + exit 1 + fi + claude -p "$1" \ + --model "${CLAUDE_MODEL:-opus}" \ + --max-budget-usd "${CLAUDE_MAX_BUDGET_USD:-5}" \ + --allowedTools Read Grep Glob Edit \ + "Bash(git diff:*)" "Bash(git log:*)" "Bash(git show:*)" "Bash(git status:*)" "Bash(git rm:*)" +} + +claude_files=() +for file in "${files[@]}"; do + if [[ "$file" == pnpm-lock.yaml ]]; then + git checkout -q --theirs -- pnpm-lock.yaml + else + claude_files+=("$file") + fi +done + +if [[ ${#claude_files[@]} -gt 0 ]]; then + list=$(printf -- '- %s\n' "${claude_files[@]}") + claude_run "This repository is a personal fork of pingdotgg/t3code (see FORK.md): the fork's own features +on top of upstream. A \`git merge\` of a new upstream commit into the fork stopped with conflicts in: + +$list +Resolve every conflict so the result keeps upstream's new behavior AND the fork's features. Read +both sides, and the commits that touched the lines (git log -p / git show, :2: is the fork, +:3: upstream), so you understand what each side meant. Adapt the fork's code to upstream's +renames and API changes rather than reverting them. + +Only edit the files listed above, and remove every conflict marker in them. For a modify/delete +conflict, keep the file if the fork still needs it, otherwise delete it with git rm. Do not stage, +commit, check out, or touch any other file. If you cannot tell what one side intended, leave that +conflict's markers in place and say why; an unresolved file is better than a wrong guess. + +End with one line per file: the file, and how you resolved it." + check_outside +fi + +unresolved=() +for file in "${files[@]}"; do + if [[ -f "$file" ]] && grep -qE '^(<<<<<<<|\|\|\|\|\|\|\||>>>>>>>)( |$)' "$file"; then + unresolved+=("$file") + fi +done +if [[ ${#unresolved[@]} -gt 0 ]]; then + printf '::error::still conflicted: %s\n' "${unresolved[@]}" + exit 1 +fi + +# Without the repository's commit hooks: they would lint the whole merge on the commit below. +VP_GIT_HOOKS=0 vp install + +# A clean textual merge can still break the build: one side calls what the other renamed or +# stopped importing, in a file git merged without a conflict. And collin/main itself may not +# typecheck. Claude fixes what the typecheck reports, in the files it names. Same apps as the +# desktop build bundles. +broken=() +typecheck() { + local app status=0 + broken=() + : > "$log" + for app in server desktop web; do + if ! (cd "apps/$app" && vp run typecheck) > "$log.$app" 2>&1; then + status=1 + # tsc prints paths relative to the app: src/x.ts:12:5 - error TS... + mapfile -t -O "${#broken[@]}" broken < <(sed 's/\x1b\[[0-9;]*m//g' "$log.$app" | + grep -oE '^[^ :(]+\.[cm]?tsx?:[0-9]+:[0-9]+ - error' | + cut -d: -f1 | sort -u | sed "s|^|apps/$app/|") + fi + cat "$log.$app" >> "$log" + done + return $status +} + +if git rev-parse -q --verify MERGE_HEAD > /dev/null; then + sides="A \`git merge\` of a new upstream commit into the fork is in progress (HEAD is the fork, +MERGE_HEAD upstream); its conflicts are resolved," +else + sides="The fork's branch (HEAD)" +fi +log="${RUNNER_TEMP:-/tmp}/resolve-typecheck.log" +for round in 1 2; do + if typecheck; then + break + fi + if [[ ${#broken[@]} -eq 0 ]]; then + echo "::error::typecheck failed without naming a file" + cat "$log" + exit 1 + fi + fixable+=("${broken[@]}") + for file in "${broken[@]}"; do excludes+=(":(exclude,literal)$file"); done + echo "Typecheck round $round failed in: ${broken[*]}" + claude_run "This repository is a personal fork of pingdotgg/t3code (see FORK.md): the fork's own features +on top of upstream. $sides but the result does not typecheck. +Typical cause: one side renamed, moved, or stopped importing something the other side's new code +still uses, in a file git merged without a conflict. Read both sides and git log -p to see why each +changed, and fix each error so upstream's new behavior and the fork's features both stay. Where +the fork deliberately replaced a helper, new upstream code should use the fork's replacement. Do +not silence errors with casts, any, or ts-ignore, and do not delete a feature to make it compile. + +Only edit these files: $(printf '%s ' "${broken[@]}") +Do not stage, commit, or check out anything. Do not run the typecheck; this script runs it again +after you finish. + +The typecheck output: + +$(tail -c 60000 "$log")" + check_outside +done +if ! typecheck; then + echo "::error::the build still does not typecheck" + cat "$log" + exit 1 +fi + +git add -A -- "${files[@]}" "${fixable[@]}" +if git rev-parse -q --verify MERGE_HEAD > /dev/null; then + git commit -q --no-edit +elif ! git diff --cached --quiet; then + git commit -q -m "Fix type errors for the build" +fi +echo "Build commit: $(git rev-parse HEAD)"