From 4cb60f14cb95b2210e78aa12909337ad5c8ef15f Mon Sep 17 00:00:00 2001 From: Daniel Reis Date: Mon, 28 Sep 2026 21:21:10 -0300 Subject: [PATCH] chore(release): publish Linux packages only The maintainer now requests Linux-only releases. Keep x64 and ARM64 AppImage, Debian and CLI builds, remove macOS and Windows jobs, and reject non-Linux assets during release assembly. Record the policy in the retained local release guide so future requests preserve it. --- .github/t2code/README.md | 6 ++- .github/t2code/release.mjs | 68 ++++++++------------------ .github/t2code/release.test.mjs | 85 +++++++++++++++++++++++++-------- .github/workflows/release.yml | 84 ++------------------------------ 4 files changed, 92 insertions(+), 151 deletions(-) diff --git a/.github/t2code/README.md b/.github/t2code/README.md index f58f2bfb7e1b..4a8880e52d2a 100644 --- a/.github/t2code/README.md +++ b/.github/t2code/README.md @@ -3,8 +3,10 @@ Release requests are handled in the local Codex task on the maintainer's machine. The task reviews and adapts the analytics overlay, opens the source update PR, waits for CI, merges it, and dispatches `release.yml`. GitHub-hosted runners build -and publish the cross-platform packages. No scheduled sync or homelab runner is -required. +and publish Linux packages only, for x64 and ARM64. Keep this release policy +unless the maintainer explicitly changes it. Releases include AppImage and Debian +installers, CLI archives, and Linux updater feeds. No scheduled sync or homelab +runner is required. ## Prepare a source update diff --git a/.github/t2code/release.mjs b/.github/t2code/release.mjs index ceb4ddd6f73d..fc38232962d9 100644 --- a/.github/t2code/release.mjs +++ b/.github/t2code/release.mjs @@ -8,11 +8,7 @@ import * as NodePath from "node:path"; const { resolve } = NodePath; import { nightlyVersion, verifyNightly, forkRelease, forkRepository } from "./nightly.mjs"; import { assertSha } from "./overlay.mjs"; -import { - mergeUpdateManifests, - parseUpdateManifest, - serializeUpdateManifest, -} from "../../scripts/lib/update-manifest.ts"; +import { parseUpdateManifest } from "../../scripts/lib/update-manifest.ts"; const repo = forkRepository; @@ -48,24 +44,6 @@ function sourcePin(sha, tag) { return pin; } -function mergeManifests(directory, first, second, output, platform) { - const primary = resolve(directory, first); - const secondary = resolve(directory, second); - if (!existsSync(primary) || !existsSync(secondary)) - throw new Error(`Missing ${platform} update manifest.`); - const merged = mergeUpdateManifests( - parseUpdateManifest(readFileSync(primary, "utf8"), first, platform), - parseUpdateManifest(readFileSync(secondary, "utf8"), second, platform), - platform, - ); - rmSync(primary); - rmSync(secondary); - writeFileSync( - resolve(directory, output), - serializeUpdateManifest(merged, { platformLabel: platform }), - ); -} - function assemble() { const directory = resolve(process.argv[3]); const version = process.env.T2_RELEASE_VERSION; @@ -75,21 +53,21 @@ function assemble() { const sha = assertSha(process.env.T2_RELEASE_SHA); const pin = sourcePin(sha, tag); const upstream = pin.commit; - mergeManifests(directory, "nightly-mac.yml", "nightly-mac-x64.yml", "nightly-mac.yml", "macOS"); - mergeManifests( - directory, - "nightly-win-x64.yml", - "nightly-win-arm64.yml", - "nightly.yml", - "Windows", - ); - for (const arch of ["x64", "arm64"]) { - for (const ext of ["dmg", "AppImage", "exe"]) { - const artifactArch = ext === "AppImage" && arch === "x64" ? "x86_64" : arch; - if (!existsSync(resolve(directory, `T2-Code-${version}-${artifactArch}.${ext}`))) - throw new Error(`Missing ${arch} ${ext} installer.`); - } + const requiredAssets = [ + `T2-Code-${version}-x86_64.AppImage`, + `T2-Code-${version}-arm64.AppImage`, + `T2-Code-${version}-amd64.deb`, + `T2-Code-${version}-arm64.deb`, + `t3-${version}-linux-x64.tar.gz`, + `t3-${version}-linux-arm64.tar.gz`, + "nightly-linux.yml", + "nightly-linux-arm64.yml", + ]; + for (const file of requiredAssets) { + if (!existsSync(resolve(directory, file))) + throw new Error(`Missing Linux release asset: ${file}`); } + const allowedAssets = new Set([...requiredAssets, "provenance.json", "SHA256SUMS"]); const provenance = { repository: repo, commit: sha, @@ -102,22 +80,16 @@ function assemble() { }; writeFileSync(resolve(directory, "provenance.json"), JSON.stringify(provenance, null, 2) + "\n"); for (const file of readdirSync(directory)) { - if (file === "builder-debug.yml" || file.endsWith("-win-arm64.yml")) - rmSync(resolve(directory, file)); + if (file === "builder-debug.yml") rmSync(resolve(directory, file)); else if (/^latest.*\.yml$/.test(file)) throw new Error(`Stable updater feed in nightly release: ${file}`); - else if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(file)) - throw new Error(`Unsafe release filename: ${file}`); - } - for (const name of ["nightly-linux.yml", "nightly-linux-arm64.yml"]) { - if (!existsSync(resolve(directory, name))) - throw new Error(`Missing Linux update manifest: ${name}`); + else if (!allowedAssets.has(file)) throw new Error(`Unexpected Linux release asset: ${file}`); } - for (const file of readdirSync(directory).filter((name) => /^nightly.*\.yml$/.test(name))) { + for (const file of ["nightly-linux.yml", "nightly-linux-arm64.yml"]) { const manifest = parseUpdateManifest( readFileSync(resolve(directory, file), "utf8"), file, - "desktop", + "Linux", ); if (manifest.version !== version) throw new Error(`Wrong updater version in ${file}.`); for (const asset of manifest.files) { @@ -144,7 +116,7 @@ function assemble() { writeFileSync(resolve(directory, "SHA256SUMS"), hashes + "\n"); writeFileSync( "release-notes.md", - `T2 Code Nightly ${version}\n\nDownload the installer for your operating system and processor. These builds are unsigned; macOS and Windows may require an installation override.\n\nThis distribution runs locally without a product account. Analytics and external diagnostic exporters are disabled in code. Local diagnostics are retained. Provider harnesses use their own credentials. Upstream Git, update, and optional remote-connection features are retained.\n\nUpstream nightly: https://github.com/pingdotgg/t3code/releases/tag/${tag}\nSource: https://github.com/${repo}/commit/${sha}\n${upstream ? `Upstream: https://github.com/pingdotgg/t3code/commit/${upstream}\n` : ""}\nInstall this nightly separately if you use an older 0.1.x T2 release. Its stable update channel does not switch to nightly automatically.\n\nSHA256SUMS covers the attached artifacts. provenance.json records the build source.\n`, + `T2 Code Nightly ${version}\n\nDownload the AppImage or Debian package for your Linux processor, x64 or ARM64. Linux CLI archives are also attached.\n\nThis distribution runs locally without a product account. Analytics and external diagnostic exporters are disabled in code. Local diagnostics are retained. Provider harnesses use their own credentials. Upstream Git, update, and optional remote-connection features are retained.\n\nUpstream nightly: https://github.com/pingdotgg/t3code/releases/tag/${tag}\nSource: https://github.com/${repo}/commit/${sha}\n${upstream ? `Upstream: https://github.com/pingdotgg/t3code/commit/${upstream}\n` : ""}\nInstall this nightly separately if you use an older 0.1.x T2 release. Its stable update channel does not switch to nightly automatically.\n\nSHA256SUMS covers the attached artifacts. provenance.json records the build source.\n`, ); } diff --git a/.github/t2code/release.test.mjs b/.github/t2code/release.test.mjs index c6987098b412..03a54bcfc0b7 100644 --- a/.github/t2code/release.test.mjs +++ b/.github/t2code/release.test.mjs @@ -61,26 +61,39 @@ function fixture() { const bytes = Buffer.from("installer fixture"); const hash = NodeCrypto.createHash("sha512").update(bytes).digest("base64"); for (const [arch, ext] of [ - ["x64", "dmg"], - ["arm64", "dmg"], ["x86_64", "AppImage"], ["arm64", "AppImage"], - ["x64", "exe"], - ["arm64", "exe"], + ["amd64", "deb"], + ["arm64", "deb"], ]) { NodeFS.writeFileSync(NodePath.join(assets, `T2-Code-${version}-${arch}.${ext}`), bytes); } - for (const [name, arch, ext] of [ - ["nightly-mac.yml", "arm64", "dmg"], - ["nightly-mac-x64.yml", "x64", "dmg"], - ["nightly-win-x64.yml", "x64", "exe"], - ["nightly-win-arm64.yml", "arm64", "exe"], - ["nightly-linux.yml", "x86_64", "AppImage"], - ["nightly-linux-arm64.yml", "arm64", "AppImage"], + for (const arch of ["x64", "arm64"]) { + NodeFS.writeFileSync(NodePath.join(assets, `t3-${version}-linux-${arch}.tar.gz`), bytes); + } + for (const [name, appImageArch, debArch] of [ + ["nightly-linux.yml", "x86_64", "amd64"], + ["nightly-linux-arm64.yml", "arm64", "arm64"], ]) { NodeFS.writeFileSync( NodePath.join(assets, name), - `version: ${version}\nfiles:\n - url: T2-Code-${version}-${arch}.${ext}\n sha512: ${hash}\n size: ${bytes.length}\n${ext === "AppImage" ? " blockMapSize: 4\n" : ""}releaseDate: '2026-09-16T00:00:00.000Z'\n`, + serializeUpdateManifest( + { + version, + releaseDate: "2026-09-16T00:00:00.000Z", + files: [ + { + url: `T2-Code-${version}-${appImageArch}.AppImage`, + sha512: hash, + size: bytes.length, + blockMapSize: 4, + }, + { url: `T2-Code-${version}-${debArch}.deb`, sha512: hash, size: bytes.length }, + ], + extras: {}, + }, + { platformLabel: "Linux" }, + ), ); } return { @@ -106,7 +119,7 @@ function fixture() { } NodeTest.test( - "release assembly records the pinned upstream and hashes each published artifact", + "Linux-only release assembly records the pinned upstream and hashes each artifact", () => { const { assets, sha, upstream, assemble } = fixture(); const result = assemble(); @@ -132,11 +145,6 @@ NodeTest.test( .digest("hex"), ); } - NodeAssert.match( - NodeFS.readFileSync(NodePath.join(assets, "nightly.yml"), "utf8"), - /arm64\.exe/, - ); - NodeAssert.match(NodeFS.readFileSync(NodePath.join(assets, "nightly.yml"), "utf8"), /x64\.exe/); }, ); @@ -171,14 +179,14 @@ NodeTest.test("the actual AppImage manifest shape retains its embedded block map NodeTest.test("release assembly rejects modified installer bytes and missing architectures", () => { for (const remove of [false, true]) { const { assets, assemble } = fixture(); - const path = NodePath.join(assets, `T2-Code-${version}-arm64.exe`); + const path = NodePath.join(assets, `T2-Code-${version}-arm64.AppImage`); if (remove) NodeFS.rmSync(path); else NodeFS.appendFileSync(path, "modified"); const result = assemble(); NodeAssert.notEqual(result.status, 0); NodeAssert.match( result.stderr, - remove ? /Missing arm64 exe installer/ : /Updater checksum mismatch/, + remove ? /Missing Linux release asset: .*arm64.AppImage/ : /Updater checksum mismatch/, ); } }); @@ -200,3 +208,40 @@ NodeTest.test("release assembly rejects stable feeds and mismatched nightly iden NodeAssert.notEqual(stable.status, 0); NodeAssert.match(stable.stderr, /Stable updater feed/); }); + +NodeTest.test( + "release assembly requires Debian packages, CLI archives and feeds for both Linux architectures", + () => { + for (const name of [ + `T2-Code-${version}-amd64.deb`, + `T2-Code-${version}-arm64.deb`, + `t3-${version}-linux-x64.tar.gz`, + `t3-${version}-linux-arm64.tar.gz`, + "nightly-linux.yml", + "nightly-linux-arm64.yml", + ]) { + const { assets, assemble } = fixture(); + NodeFS.rmSync(NodePath.join(assets, name)); + const result = assemble(); + NodeAssert.notEqual(result.status, 0); + NodeAssert.ok(result.stderr.includes(`Missing Linux release asset: ${name}`), result.stderr); + } + }, +); + +NodeTest.test("release assembly rejects non-Linux installers, archives and updater feeds", () => { + for (const name of [ + `T2-Code-${version}-x64.dmg`, + `T2-Code-${version}-arm64.exe`, + `t3-${version}-darwin-arm64.tar.gz`, + `t3-${version}-win32-x64.zip`, + "nightly-mac.yml", + "nightly.yml", + ]) { + const { assets, assemble } = fixture(); + NodeFS.writeFileSync(NodePath.join(assets, name), "non-Linux artifact"); + const result = assemble(); + NodeAssert.notEqual(result.status, 0); + NodeAssert.ok(result.stderr.includes(`Unexpected Linux release asset: ${name}`), result.stderr); + } +}); diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 68c632ebd63d..765e425ed071 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -104,40 +104,6 @@ jobs: if-no-files-found: error retention-days: 3 - desktop_mac_arm64: - name: macOS arm64 - needs: [metadata, build_bundle] - uses: ./.github/workflows/release-desktop.yml - with: - label: macOS arm64 - runner: macos-15 - platform: mac - target: dmg - arch: arm64 - rust_target: aarch64-apple-darwin - resource_key: darwin-arm64 - cli_archive: true - version: ${{ needs.metadata.outputs.version }} - ref: ${{ needs.metadata.outputs.sha }} - release_channel: nightly - - desktop_mac_x64: - name: macOS x64 - needs: [metadata, build_bundle] - uses: ./.github/workflows/release-desktop.yml - with: - label: macOS x64 - runner: macos-15-intel - platform: mac - target: dmg - arch: x64 - rust_target: x86_64-apple-darwin - resource_key: darwin-x64 - cli_archive: false - version: ${{ needs.metadata.outputs.version }} - ref: ${{ needs.metadata.outputs.sha }} - release_channel: nightly - desktop_linux_x64: name: Linux x64 needs: [metadata, build_bundle] @@ -172,52 +138,8 @@ jobs: ref: ${{ needs.metadata.outputs.sha }} release_channel: nightly - desktop_win_x64: - name: Windows x64 - needs: [metadata, build_bundle, desktop_linux_x64] - uses: ./.github/workflows/release-desktop.yml - with: - label: Windows x64 - runner: windows-2025 - platform: win - target: nsis - arch: x64 - rust_target: x86_64-pc-windows-msvc - resource_key: win32-x64 - cli_archive: true - version: ${{ needs.metadata.outputs.version }} - ref: ${{ needs.metadata.outputs.sha }} - release_channel: nightly - - desktop_win_arm64: - name: Windows arm64 - needs: [metadata, build_bundle, desktop_linux_arm64] - uses: ./.github/workflows/release-desktop.yml - with: - label: Windows arm64 - runner: windows-11-arm - platform: win - target: nsis - arch: arm64 - rust_target: aarch64-pc-windows-msvc - resource_key: win32-arm64 - cli_archive: true - version: ${{ needs.metadata.outputs.version }} - ref: ${{ needs.metadata.outputs.sha }} - release_channel: nightly - publish: - needs: - [ - metadata, - quality, - desktop_mac_arm64, - desktop_mac_x64, - desktop_linux_x64, - desktop_linux_arm64, - desktop_win_x64, - desktop_win_arm64, - ] + needs: [metadata, quality, desktop_linux_x64, desktop_linux_arm64] runs-on: ubuntu-24.04 timeout-minutes: 15 permissions: @@ -240,12 +162,12 @@ jobs: node-version: 24 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: - pattern: desktop-* + pattern: desktop-linux-* merge-multiple: true path: release-assets - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: - pattern: cli-* + pattern: cli-linux-* merge-multiple: true path: release-assets - name: Assemble feeds, provenance and checksums