diff --git a/.github/workflows/fork-release.yml b/.github/workflows/fork-release.yml new file mode 100644 index 000000000000..6f98b12dfa5c --- /dev/null +++ b/.github/workflows/fork-release.yml @@ -0,0 +1,186 @@ +# Fork releases: Windows x64 nightlies of upstream main with this fork's features merged in. +# +# The desktop app looks for updates in the repository it was built in (GITHUB_REPOSITORY ends up +# in app-update.yml), so a build from here keeps updating from here and the update button never +# trades the fork for a plain upstream build. Upstream's release.yml needs runners and secrets a +# fork does not have; this is the one job a Windows install needs. +# +# collin/main is merged with upstream/main for the build only; nothing is pushed to a branch. +# A merge conflict fails the run (and GitHub mails the owner): then sync collin/main by hand. +name: Fork release + +on: + schedule: + # Every six hours, a minute off the hour; a run without new commits ends after the check. + - cron: "17 */6 * * *" + workflow_dispatch: + inputs: + force: + description: "Build even if nothing changed since the last release" + type: boolean + default: false + +permissions: + contents: write + +concurrency: + group: fork-release + cancel-in-progress: false + +jobs: + release: + if: github.repository != 'pingdotgg/t3code' + runs-on: windows-latest + timeout-minutes: 120 + defaults: + run: + shell: bash + env: + FORK_BRANCH: collin/main + UPSTREAM_URL: https://github.com/pingdotgg/t3code.git + GH_TOKEN: ${{ github.token }} + steps: + # Plain git, as in release-desktop.yml: checkout's cleanup trips over the vendored .repos gitlinks. + - name: Merge upstream into the fork + id: merge + env: + GIT_TERMINAL_PROMPT: "0" + run: | + set -euo pipefail + git init -q . + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" + git remote add upstream "$UPSTREAM_URL" + git fetch -q --no-tags origin "$FORK_BRANCH" + fork_sha=$(git rev-parse FETCH_HEAD) + git fetch -q --no-tags upstream main + upstream_sha=$(git rev-parse FETCH_HEAD) + 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 main ${upstream_sha:0:12}" "$upstream_sha"; then + git diff --name-only --diff-filter=U | sed 's/^/::error::conflict: /' + echo "::error::upstream main no longer merges cleanly into $FORK_BRANCH; sync it by hand" + exit 1 + fi + echo "fork_sha=$fork_sha" >> "$GITHUB_OUTPUT" + echo "upstream_sha=$upstream_sha" >> "$GITHUB_OUTPUT" + echo "build_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + - name: Skip when the last release has the same commits + id: due + env: + FORCE: ${{ inputs.force || false }} + FORK_SHA: ${{ steps.merge.outputs.fork_sha }} + UPSTREAM_SHA: ${{ steps.merge.outputs.upstream_sha }} + run: | + set -euo pipefail + last=$(gh release list --repo "$GITHUB_REPOSITORY" --limit 1 --json tagName --jq '.[0].tagName // ""') + body="" + if [[ -n "$last" ]]; then + body=$(gh release view "$last" --repo "$GITHUB_REPOSITORY" --json body --jq .body) + fi + if [[ "$FORCE" != "true" && "$body" == *"Fork: $FORK_SHA"* && "$body" == *"Upstream: $UPSTREAM_SHA"* ]]; then + echo "$last already has fork $FORK_SHA and upstream $UPSTREAM_SHA." + echo "due=false" >> "$GITHUB_OUTPUT" + else + echo "due=true" >> "$GITHUB_OUTPUT" + fi + + - 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: Install dependencies + if: steps.due.outputs.due == 'true' + timeout-minutes: 15 + run: vp install + + # Same scheme as upstream nightlies, so the app's nightly channel picks these up. The run + # number is offset so a fork build sorts above an upstream nightly of the same day. + - name: Resolve version + if: steps.due.outputs.due == 'true' + id: version + env: + BUILD_SHA: ${{ steps.merge.outputs.build_sha }} + RUN_STARTED_AT: ${{ github.run_started_at }} + RUN_NUMBER: ${{ github.run_number }} + run: | + set -euo pipefail + node scripts/resolve-nightly-release.ts \ + --date "$(date -u -d "$RUN_STARTED_AT" +%Y%m%d)" \ + --run-number "$((10000 + RUN_NUMBER))" \ + --sha "$BUILD_SHA" \ + --github-output + + - name: Setup Rust + if: steps.due.outputs.due == 'true' + uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable + with: + toolchain: stable + targets: x86_64-pc-windows-msvc + + # Upstream installs Spectre-mitigated MSVC libs first; only the build script's preflight + # asks for them, and a reused monitor skips that check. The VS installer took minutes there. + - name: Build resource monitor + if: steps.due.outputs.due == 'true' + timeout-minutes: 15 + run: cargo build --locked --release --manifest-path native/resource-monitor/Cargo.toml --target x86_64-pc-windows-msvc + + - name: Set package versions + if: steps.due.outputs.due == 'true' + run: node scripts/update-release-package-versions.ts "${{ steps.version.outputs.version }}" + + # Unsigned, and without the WSL runtime upstream bundles from its Linux CLI build. + # T3 Connect needs the public relay and Clerk values; they come from repository variables. + - name: Build desktop artifact + if: steps.due.outputs.due == 'true' + timeout-minutes: 45 + env: + T3CODE_DESKTOP_REUSE_RESOURCE_MONITOR: true + T3CODE_CLERK_PUBLISHABLE_KEY: ${{ vars.T3CODE_CLERK_PUBLISHABLE_KEY }} + T3CODE_CLERK_JWT_TEMPLATE: ${{ vars.T3CODE_CLERK_JWT_TEMPLATE }} + T3CODE_CLERK_CLI_OAUTH_CLIENT_ID: ${{ vars.T3CODE_CLERK_CLI_OAUTH_CLIENT_ID }} + T3CODE_RELAY_URL: ${{ vars.T3CODE_RELAY_URL }} + run: | + vp run dist:desktop:artifact \ + --platform win --target nsis --arch x64 \ + --build-version "${{ steps.version.outputs.version }}" \ + --verbose + + - name: Check update feed + if: steps.due.outputs.due == 'true' + run: | + set -euo pipefail + ls -la release + grep -q "version: ${{ steps.version.outputs.version }}" release/nightly.yml + cat release/nightly.yml + + # The tag goes on the fork branch: pushing the merge itself would need workflow-write rights, + # which GITHUB_TOKEN never has when upstream touched .github/workflows. + - name: Publish release + if: steps.due.outputs.due == 'true' + env: + TAG: ${{ steps.version.outputs.tag }} + VERSION: ${{ steps.version.outputs.version }} + FORK_SHA: ${{ steps.merge.outputs.fork_sha }} + UPSTREAM_SHA: ${{ steps.merge.outputs.upstream_sha }} + run: | + set -euo pipefail + shopt -s nullglob + assets=(release/*.exe release/*.blockmap release/nightly.yml) + notes=$(printf '%s\n' \ + "Windows x64 build of upstream main with the fork's features (see FORK.md). Unsigned." \ + "" \ + "Fork: $FORK_SHA" \ + "Upstream: $UPSTREAM_SHA") + gh release create "$TAG" "${assets[@]}" \ + --repo "$GITHUB_REPOSITORY" \ + --target "$FORK_SHA" \ + --title "T3 Code Nightly $VERSION (fork)" \ + --notes "$notes" \ + --prerelease diff --git a/FORK.md b/FORK.md index b0b2078be7b3..387eaddc5027 100644 --- a/FORK.md +++ b/FORK.md @@ -7,7 +7,8 @@ Personal fork of [pingdotgg/t3code](https://github.com/pingdotgg/t3code). Upstre - `main`: pristine mirror of `upstream/main`. Never commit here; fast-forward only. - `collin/main`: `main` + own changes. Work branches start here, PRs target here. -- Nightly builds are just `v*-nightly.*` tags on upstream `main`, not a separate repo. +- Upstream nightly builds are just `v*-nightly.*` tags on upstream `main`, not a separate repo. + The fork publishes its own (see Desktop builds). ## Remotes @@ -39,6 +40,27 @@ Merge, not rebase: history stays stable, no force-push needed. `fork/pre-push` blocks any push to `main` that is not exactly `upstream/main`. Hooks are not versioned; install per clone: `cp fork/pre-push "$(git rev-parse --git-common-dir)/hooks/pre-push"`. +## Desktop builds + +The installed app is a fork build, so the update button stays on the fork. The desktop app looks +for updates in the repository it was built in (`app-update.yml`, from `GITHUB_REPOSITORY`); an +official Nightly would update to upstream and lose the fork features. + +`.github/workflows/fork-release.yml` (`windows-latest`, every 6 hours and on dispatch) merges +`upstream/main` into `collin/main` for the build only, skips when the last release already has +both commits, and publishes a prerelease `v-nightly..<10000 + run>` with the NSIS installer, +blockmap and `nightly.yml`. The version scheme is upstream's (the offset sorts a fork build above an upstream nightly of the same day), so the app's nightly channel +takes it. A merge conflict fails the run; sync `collin/main` by hand, then +`gh workflow run fork-release.yml`. `-f force=true` builds without new commits. + +- Windows x64 only, unsigned (SmartScreen warns on the first install; updates do not verify a + publisher), without the WSL runtime upstream bundles. +- T3 Connect needs the public relay and Clerk values that upstream bakes in. They are repository + variables here: `T3CODE_RELAY_URL`, `T3CODE_CLERK_PUBLISHABLE_KEY`, `T3CODE_CLERK_JWT_TEMPLATE`, + `T3CODE_CLERK_CLI_OAUTH_CLIENT_ID`. Upstream's telemetry tokens are deliberately not copied. +- The tag sits on the `collin/main` commit; the release notes name the fork and upstream commits + the build merged. + ## Keeping changes mergeable - Prefer additive changes: new files, new modules, settings/flags. Avoid editing