Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ jobs:
run: |
set -euo pipefail
shopt -s nullglob
assets=(release/*.exe release/*.blockmap release/*.yml)
assets=(release/*.exe release/*.zip release/*.blockmap release/*.yml)
if (( ${#assets[@]} == 0 )); then
echo "No Windows release assets found." >&2
exit 1
Expand Down
6 changes: 5 additions & 1 deletion apps/marketing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"test": "vp test run",
"typecheck": "astro check"
},
"dependencies": {
Expand All @@ -15,6 +16,9 @@
},
"devDependencies": {
"@astrojs/check": "^0.9.10",
"typescript": "~5.9.3"
"typescript": "~5.9.3",
"vite": "catalog:",
"vite-plus": "catalog:",
"vitest": "catalog:"
}
}
40 changes: 40 additions & 0 deletions apps/marketing/src/lib/releases.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, expect, it } from "vitest";

import { classifyAssets, type ReleaseAsset } from "./releases";

function asset(name: string): ReleaseAsset {
return { name, browser_download_url: `https://example.test/${name}`, size: 1 };
}

describe("classifyAssets", () => {
it("picks one installer per platform from the published release assets", () => {
const installers = classifyAssets([
asset("Threadlines-0.4.0-win-x64.exe"),
asset("Threadlines-0.4.0-win-x64.exe.blockmap"),
asset("Threadlines-0.4.0-win-x64.zip"),
asset("Threadlines-0.4.0-win-arm64.exe"),
asset("Threadlines-0.4.0-mac-arm64.dmg"),
asset("Threadlines-0.4.0-mac-arm64.zip"),
asset("Threadlines-0.4.0-mac-x64.dmg"),
asset("Threadlines-0.4.0-mac-x64.zip"),
asset("Threadlines-0.4.0-linux-x86_64.AppImage"),
asset("latest.yml"),
asset("latest-mac.yml"),
]);

expect(installers.winX64?.name).toBe("Threadlines-0.4.0-win-x64.exe");
expect(installers.winArm?.name).toBe("Threadlines-0.4.0-win-arm64.exe");
expect(installers.macArm?.name).toBe("Threadlines-0.4.0-mac-arm64.dmg");
expect(installers.macX64?.name).toBe("Threadlines-0.4.0-mac-x64.dmg");
expect(installers.linuxX64?.name).toBe("Threadlines-0.4.0-linux-x86_64.AppImage");
});

it("ignores updater and portable archives that share an installer's arch", () => {
const installers = classifyAssets([
asset("Threadlines-0.4.0-mac-arm64.zip"),
asset("Threadlines-0.4.0-win-x64.zip"),
]);

expect(installers).toEqual({});
});
});
27 changes: 16 additions & 11 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,14 @@ complete compact technical change list in a collapsed details section.

The release assets should include:

- `Threadlines-<version>-x64.exe`
- `Threadlines-<version>-x64.exe.blockmap`
- `Threadlines-<version>-arm64.dmg`
- `Threadlines-<version>-arm64.zip`
- `Threadlines-<version>-x64.dmg`
- `Threadlines-<version>-x64.zip`
- `Threadlines-<version>-x86_64.AppImage`
- `Threadlines-<version>-win-x64.exe`
- `Threadlines-<version>-win-x64.exe.blockmap`
- `Threadlines-<version>-win-x64.zip`
- `Threadlines-<version>-mac-arm64.dmg`
- `Threadlines-<version>-mac-arm64.zip`
- `Threadlines-<version>-mac-x64.dmg`
- `Threadlines-<version>-mac-x64.zip`
- `Threadlines-<version>-linux-x86_64.AppImage`
- `latest.yml`
- `latest-mac.yml`
- `latest-linux.yml`
Expand All @@ -214,10 +215,14 @@ prereleases so updater checks can read the prerelease manifest.

Download the matching asset from GitHub Releases:

- Windows: `Threadlines-<version>-x64.exe`
- Apple Silicon macOS: `Threadlines-<version>-arm64.dmg`
- Intel macOS: `Threadlines-<version>-x64.dmg`
- Linux x64: `Threadlines-<version>-x86_64.AppImage`
- Windows: `Threadlines-<version>-win-x64.exe` (or
`Threadlines-<version>-win-x64.zip` for a portable copy that does not install)
- Apple Silicon macOS: `Threadlines-<version>-mac-arm64.dmg`
- Intel macOS: `Threadlines-<version>-mac-x64.dmg`
- Linux x64: `Threadlines-<version>-linux-x86_64.AppImage`

Every asset name carries its operating system, so a macOS updater zip can no
longer be mistaken for a Windows download.

Windows and macOS public release artifacts are expected to be signed. Windows may
still show SmartScreen reputation prompts until the signing identity has enough
Expand Down
38 changes: 15 additions & 23 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions scripts/build-desktop-artifact.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,44 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
}),
);

it.effect("names artifacts with an operating system token on every platform", () =>
Effect.gen(function* () {
for (const [platform, target] of [
["mac", "dmg"],
["win", "nsis"],
["linux", "AppImage"],
] as const) {
const buildConfig = yield* createBuildConfig(
platform,
target,
"0.0.19",
false,
false,
undefined,
);

assert.equal(buildConfig.artifactName, "Threadlines-${version}-${os}-${arch}.${ext}");
}
}),
);

it.effect("pairs the default Windows installer with a portable zip", () =>
Effect.gen(function* () {
const nsisConfig = yield* createBuildConfig("win", "nsis", "0.0.2", false, false, undefined);
assert.deepStrictEqual((nsisConfig.win as Record<string, unknown>).target, ["nsis", "zip"]);

const portableConfig = yield* createBuildConfig(
"win",
"portable",
"0.0.2",
false,
false,
undefined,
);
assert.deepStrictEqual((portableConfig.win as Record<string, unknown>).target, ["portable"]);
}),
);

it.effect("keeps Windows executable resource editing enabled for unsigned builds", () =>
Effect.gen(function* () {
const buildConfig = yield* createBuildConfig("win", "nsis", "0.0.2", false, false, undefined);
Expand Down
9 changes: 7 additions & 2 deletions scripts/build-desktop-artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,10 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* (
const buildConfig: Record<string, unknown> = {
appId: DESKTOP_RELEASE_APP_ID,
productName: resolveDesktopProductName(version),
artifactName: "Threadlines-${version}-${arch}.${ext}",
// The ${os} token (mac/win/linux) keeps downloads self-describing on the
// GitHub releases page: without it a macOS updater zip reads as
// "Threadlines-0.3.1-x64.zip" and Windows users grab it by mistake.
artifactName: "Threadlines-${version}-${os}-${arch}.${ext}",
directories: {
buildResources: "apps/desktop/resources",
},
Expand Down Expand Up @@ -903,7 +906,9 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* (
},
];
const winConfig: Record<string, unknown> = {
target: [target],
// The zip is the portable, no-install Windows build. Pair it with the
// default nsis installer only; an explicitly requested target stays exact.
target: target === "nsis" ? [target, "zip"] : [target],
icon: "icon.ico",
};
if (signed) {
Expand Down
Loading
Loading