Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,37 @@ jobs:

vp run dist:desktop:artifact "${args[@]}"

- name: Build Linux deb package
if: matrix.platform == 'linux'
shell: bash
run: |
set -euo pipefail
shopt -s nullglob

# electron-builder writes update metadata for every fpm target, so the
# deb build would overwrite the AppImage updater manifest. Linux
# auto-update only supports the AppImage, so stash the manifest and put
# it back once the deb is packaged.
manifest_backup="$RUNNER_TEMP/linux-update-manifest"
mkdir -p "$manifest_backup"
for manifest in release/*-linux*.yml; do
cp "$manifest" "$manifest_backup/"
done

# --skip-build reuses the app compiled by the previous step; this only
# reruns packaging.
vp run dist:desktop:artifact \
--platform linux \
--target deb \
--arch "${{ matrix.arch }}" \
--build-version "${{ needs.preflight.outputs.version }}" \
--skip-build \
--verbose

for manifest in "$manifest_backup"/*.yml; do
cp "$manifest" release/
done

- name: Collect release assets
shell: bash
run: |
Expand All @@ -584,6 +615,7 @@ jobs:
"release/*.dmg" \
"release/*.zip" \
"release/*.AppImage" \
"release/*.deb" \
"release/*.exe" \
"release/*.blockmap" \
"release/*.yml"; do
Expand Down Expand Up @@ -804,6 +836,7 @@ jobs:
release-assets/*.dmg
release-assets/*.zip
release-assets/*.AppImage
release-assets/*.deb
release-assets/*.exe
release-assets/*.blockmap
release-assets/*.yml
Expand All @@ -824,6 +857,7 @@ jobs:
release-assets/*.dmg
release-assets/*.zip
release-assets/*.AppImage
release-assets/*.deb
release-assets/*.exe
release-assets/*.blockmap
release-assets/*.yml
Expand Down
10 changes: 8 additions & 2 deletions docs/operations/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ This document covers the unified release workflow for stable and nightly desktop
- manual `workflow_dispatch` for either channel
- Runs quality gates first: lint, typecheck, test.
- Reads the shared production T3 Connect relay URL and Clerk client configuration before packaging clients.
- Builds four artifacts in parallel for both channels:
- Builds four platform bundles in parallel for both channels:
- macOS `arm64` DMG
- macOS `x64` DMG
- Linux `x64` AppImage
- Linux `x64` AppImage, plus a `.deb` repackaged from the same build
- Windows `x64` NSIS installer
- Publishes one GitHub Release with all produced files.
- Stable tags with a suffix after `X.Y.Z` (for example `1.2.3-alpha.1`) are published as GitHub prereleases.
Expand Down Expand Up @@ -194,6 +194,12 @@ guidance when those environments are available.
- platform installers (`.exe`, `.dmg`, `.AppImage`, plus macOS `.zip` for Squirrel.Mac update payloads)
- channel metadata: `latest*.yml` for stable releases, `nightly*.yml` for nightly releases
- `*.blockmap` files (used for differential downloads)
- Linux packaging note:
- The `.deb` is published for installation only; `DesktopUpdates` disables in-app
updates unless the app is running from the AppImage.
- `electron-builder` rewrites `latest-linux.yml` for every fpm target, so the
workflow restores the AppImage manifest after building the `.deb`. Without
that, the updater would advertise a `.deb` the app refuses to install.
- macOS metadata note:
- `electron-updater` reads `latest-mac.yml` on stable and `nightly-mac.yml` on nightly, for both Intel and Apple Silicon.
- The workflow merges the per-arch mac manifests into one channel-specific mac manifest before publishing the GitHub Release.
Expand Down
1 change: 1 addition & 0 deletions docs/reference/scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- `vp run dist:desktop:dmg` — Builds a shareable macOS `.dmg` into `./release`.
- `vp run dist:desktop:dmg:x64` — Builds an Intel macOS `.dmg`.
- `vp run dist:desktop:linux` — Builds a Linux AppImage into `./release`.
- `vp run dist:desktop:linux:deb` — Builds a Linux `.deb` into `./release`.
- `vp run dist:desktop:win` — Builds a Windows NSIS installer into `./release`.

## Desktop `.dmg` packaging notes
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"dist:desktop:dmg:arm64": "node scripts/build-desktop-artifact.ts --platform mac --target dmg --arch arm64",
"dist:desktop:dmg:x64": "node scripts/build-desktop-artifact.ts --platform mac --target dmg --arch x64",
"dist:desktop:linux": "node scripts/build-desktop-artifact.ts --platform linux --target AppImage --arch x64",
"dist:desktop:linux:deb": "node scripts/build-desktop-artifact.ts --platform linux --target deb --arch x64",
"dist:desktop:win": "node scripts/build-desktop-artifact.ts --platform win --target nsis",
"dist:desktop:win:arm64": "node scripts/build-desktop-artifact.ts --platform win --target nsis --arch arm64",
"dist:desktop:win:x64": "node scripts/build-desktop-artifact.ts --platform win --target nsis --arch x64",
Expand Down
23 changes: 23 additions & 0 deletions scripts/build-desktop-artifact.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,29 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
}).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })))),
);

it.effect("supplies the package maintainer that fpm-backed Linux targets require", () =>
Effect.gen(function* () {
const linux = yield* createBuildConfig(
"linux",
"deb",
"1.2.3",
false,
false,
undefined,
undefined,
);
const linuxConfig = linux.linux as {
readonly target: ReadonlyArray<string>;
readonly maintainer?: string;
};

assert.deepStrictEqual(linuxConfig.target, ["deb"]);
// electron-builder's FpmTarget aborts the build unless it can resolve a
// `Name <email>` maintainer, so this is what keeps deb/rpm buildable.
assert.match(linuxConfig.maintainer ?? "", /^.+ <[^@\s]+@[^@\s]+>$/u);
}).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })))),
);

it.effect("preserves both Linux icon resize failures with structural context", () => {
const commands: Array<{ readonly command: string; readonly args: ReadonlyArray<string> }> = [];

Expand Down
8 changes: 8 additions & 0 deletions scripts/build-desktop-artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";

const LINUX_ICON_SIZES = [16, 22, 24, 32, 48, 64, 128, 256, 512] as const;
const DESKTOP_APP_ID = "com.t3tools.t3code";
// fpm-backed Linux targets (deb, rpm) refuse to build without a project URL and
// a `Name <email>` maintainer; AppImage never asked for either. The maintainer
// string becomes both the deb Maintainer and Vendor fields.
const DESKTOP_HOMEPAGE = "https://t3.codes";
const DESKTOP_LINUX_MAINTAINER = "pingdotgg <support@t3.codes>";
const APPLE_TEAM_ID_PATTERN = /^[A-Z0-9]{10}$/u;

const BuildPlatform = Schema.Literals(["mac", "linux", "win"]);
Expand Down Expand Up @@ -617,6 +622,7 @@ interface StagePackageJson {
readonly packageManager: string;
readonly description: string;
readonly author: string;
readonly homepage: string;
readonly main: string;
readonly build: Record<string, unknown>;
readonly dependencies: Record<string, unknown>;
Expand Down Expand Up @@ -1589,6 +1595,7 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* (
executableName: "t3code",
icon: "icons",
category: "Development",
maintainer: DESKTOP_LINUX_MAINTAINER,
desktop: {
entry: {
StartupWMClass: "t3code",
Expand Down Expand Up @@ -1911,6 +1918,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
packageManager: rootPackageJson.packageManager,
description: "T3 Code desktop build",
author: "T3 Tools",
homepage: DESKTOP_HOMEPAGE,
main: "apps/desktop/dist-electron/main.cjs",
build: yield* createBuildConfig(
options.platform,
Expand Down
Loading