Multi-arch release script + Gitea workflow; retire legacy BunSCP name
bun run release -- --target mac-arm64|mac-x64|win-x64: gen version/icons, build UI, electrobun build --env=stable, collect artifacts as release/<target>/miniSCP-Build<n>-<target>.*. mac-x64 builds on the arm64 Mac by running electrobun's x64 CLI under Rosetta 2 (verified: x86_64 launcher in the produced app). Windows also zips a portable build next to the Setup installer. Tag-triggered Gitea Actions matrix included, inert until runners exist. legacyNames emptied — the BunSCP migration has served its purpose. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
# Release matrix — INERT until act_runner instances with these labels are
|
||||||
|
# registered on git.teknes.es. Each OS builds its own artifacts (electrobun
|
||||||
|
# cannot cross-compile: packaging needs codesign/hdiutil on macOS and
|
||||||
|
# rcedit/PowerShell on Windows).
|
||||||
|
#
|
||||||
|
# Runner prerequisites:
|
||||||
|
# macos — arm64 Mac: bun, Xcode CLT, Rosetta 2 (softwareupdate
|
||||||
|
# --install-rosetta), network to github.com (electrobun downloads
|
||||||
|
# its per-arch core binaries on first build)
|
||||||
|
# windows — Windows 11 x64: bun, network to github.com; WebView2 runtime is
|
||||||
|
# preinstalled on Win 11
|
||||||
|
#
|
||||||
|
# Trigger: push a tag like build-62 (git tag build-$(git rev-list --count HEAD)).
|
||||||
|
name: release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "build-*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mac:
|
||||||
|
runs-on: macos
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # full history — the build number is the commit count
|
||||||
|
- run: bun install --frozen-lockfile
|
||||||
|
- run: bun scripts/release.ts --target mac-arm64
|
||||||
|
- run: bun scripts/release.ts --target mac-x64
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: mac
|
||||||
|
path: release/**
|
||||||
|
|
||||||
|
windows:
|
||||||
|
runs-on: windows
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- run: bun install --frozen-lockfile
|
||||||
|
- run: bun scripts/release.ts --target win-x64
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: windows
|
||||||
|
path: release/**
|
||||||
@@ -87,6 +87,29 @@ This starts the Angular dev server (hot reload for UI code) and launches the
|
|||||||
Electrobun app pointed at it. Edits under `ui/` reload instantly; edits to the
|
Electrobun app pointed at it. Edits under `ui/` reload instantly; edits to the
|
||||||
Bun main process (`src/bun/**`, RPC schema) trigger an app relaunch.
|
Bun main process (`src/bun/**`, RPC schema) trigger an app relaunch.
|
||||||
|
|
||||||
|
## Releases
|
||||||
|
|
||||||
|
Versioning is Sublime-style: **Build n**, where n is the commit count
|
||||||
|
(`git rev-list --count HEAD`), regenerated into the gitignored
|
||||||
|
`src/shared/version.gen.ts` by every build script (and `postinstall`).
|
||||||
|
|
||||||
|
One artifact per invocation, on the matching host — electrobun cannot
|
||||||
|
cross-compile between OSes:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bun run release -- --target mac-arm64 # on the Mac (native)
|
||||||
|
bun run release -- --target mac-x64 # on the same arm64 Mac, via Rosetta 2
|
||||||
|
bun run release -- --target win-x64 # on a Windows 11 x64 machine
|
||||||
|
```
|
||||||
|
|
||||||
|
Artifacts land in `release/<target>/miniSCP-Build<n>-<target>.*` — a `.dmg`
|
||||||
|
per mac arch; on Windows a `-Setup.zip` installer **and** a `-portable.zip`
|
||||||
|
(unzip anywhere and run). The mac-x64 build fetches electrobun's x64 CLI once
|
||||||
|
into `~/.cache/miniscp/` and runs it under Rosetta so the whole pipeline
|
||||||
|
believes it is on an Intel Mac. CI: `.gitea/workflows/release.yml` runs the
|
||||||
|
same script on a macos + windows runner matrix once act_runners are
|
||||||
|
registered; trigger by pushing a `build-<n>` tag.
|
||||||
|
|
||||||
## Build a packaged app
|
## Build a packaged app
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -109,7 +132,7 @@ Everything user-facing reads from **[`src/shared/branding.ts`](src/shared/brandi
|
|||||||
export const BRANDING = {
|
export const BRANDING = {
|
||||||
appName: "miniSCP", // window title, bundle name, app-data folder
|
appName: "miniSCP", // window title, bundle name, app-data folder
|
||||||
identifier: "es.teknes.miniscp",
|
identifier: "es.teknes.miniscp",
|
||||||
legacyNames: ["BunSCP"], // previous names; their data dirs migrate on boot
|
legacyNames: [], // previous names; their data dirs migrate on boot
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
icon: "assets/icon.iconset",
|
icon: "assets/icon.iconset",
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
// Build one release artifact for one target, on the matching host:
|
||||||
|
// bun scripts/release.ts [--target mac-arm64|mac-x64|win-x64]
|
||||||
|
// Default target = the host. electrobun cannot cross-compile OSes (its
|
||||||
|
// packaging needs host tools: codesign/hdiutil on mac, rcedit/PowerShell on
|
||||||
|
// Windows), so win-x64 must run on a Windows machine. mac-x64 IS buildable on
|
||||||
|
// an arm64 Mac: we fetch electrobun's x64 CLI binary and run it under
|
||||||
|
// Rosetta 2 — it sees an x64 host, pulls the x64 core, and packages mac-x64.
|
||||||
|
// Artifacts land in release/<target>/miniSCP-Build<N>-<target>.<ext>.
|
||||||
|
import { cpSync, existsSync, mkdirSync, readdirSync } from "node:fs";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
const ROOT = path.join(import.meta.dir, "..");
|
||||||
|
const TARGETS = ["mac-arm64", "mac-x64", "win-x64"] as const;
|
||||||
|
type Target = (typeof TARGETS)[number];
|
||||||
|
|
||||||
|
function hostTarget(): Target {
|
||||||
|
if (process.platform === "win32") return "win-x64";
|
||||||
|
return process.arch === "arm64" ? "mac-arm64" : "mac-x64";
|
||||||
|
}
|
||||||
|
|
||||||
|
function fail(message: string): never {
|
||||||
|
console.error(`\n[release] ERROR: ${message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(cmd: string[], opts: { cwd?: string } = {}): void {
|
||||||
|
console.log(`[release] $ ${cmd.join(" ")}`);
|
||||||
|
const proc = Bun.spawnSync(cmd, {
|
||||||
|
cwd: opts.cwd ?? ROOT,
|
||||||
|
stdout: "inherit",
|
||||||
|
stderr: "inherit",
|
||||||
|
});
|
||||||
|
if (!proc.success) fail(`command failed: ${cmd.join(" ")}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- target selection + host guards ----
|
||||||
|
|
||||||
|
const argIndex = process.argv.indexOf("--target");
|
||||||
|
const requested = (argIndex >= 0 ? process.argv[argIndex + 1] : hostTarget()) as Target;
|
||||||
|
if (!TARGETS.includes(requested)) fail(`unknown target "${requested}" (${TARGETS.join(", ")})`);
|
||||||
|
|
||||||
|
if (requested === "win-x64" && process.platform !== "win32") {
|
||||||
|
fail("win-x64 must be built on a Windows machine/runner — electrobun cannot cross-compile.");
|
||||||
|
}
|
||||||
|
if (requested.startsWith("mac") && process.platform !== "darwin") {
|
||||||
|
fail(`${requested} must be built on macOS.`);
|
||||||
|
}
|
||||||
|
const viaRosetta = requested === "mac-x64" && process.arch === "arm64";
|
||||||
|
|
||||||
|
// ---- prepare: version, icons, UI bundle ----
|
||||||
|
|
||||||
|
run(["bun", "scripts/gen-version.ts"]);
|
||||||
|
run(["bun", "scripts/gen-icons.ts"]);
|
||||||
|
run(["bun", "run", "--cwd", "ui", "build"]);
|
||||||
|
|
||||||
|
const { BUILD } = await import("../src/shared/version.gen");
|
||||||
|
const { BRANDING } = await import("../src/shared/branding");
|
||||||
|
const buildName = `Build${BUILD.number}`;
|
||||||
|
|
||||||
|
// ---- electrobun build (native, or x64 CLI under Rosetta) ----
|
||||||
|
|
||||||
|
async function x64Cli(): Promise<string> {
|
||||||
|
const pkg = await Bun.file(path.join(ROOT, "node_modules", "electrobun", "package.json")).json();
|
||||||
|
const dir = path.join(os.homedir(), ".cache", "miniscp", `electrobun-cli-x64-${pkg.version}`);
|
||||||
|
const bin = path.join(dir, "electrobun");
|
||||||
|
if (existsSync(bin)) return bin;
|
||||||
|
console.log(`[release] downloading electrobun x64 CLI ${pkg.version} (one-time)…`);
|
||||||
|
mkdirSync(dir, { recursive: true });
|
||||||
|
const url = `https://github.com/blackboardsh/electrobun/releases/download/v${pkg.version}/electrobun-cli-darwin-x64.tar.gz`;
|
||||||
|
const tarball = path.join(dir, "cli.tar.gz");
|
||||||
|
run(["curl", "-fsSL", "-o", tarball, url]);
|
||||||
|
run(["tar", "-xzf", tarball, "-C", dir]);
|
||||||
|
if (!existsSync(bin)) fail(`x64 CLI tarball did not contain "electrobun" (${dir})`);
|
||||||
|
run(["chmod", "+x", bin]);
|
||||||
|
return bin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viaRosetta) {
|
||||||
|
// x64 Mach-O runs under Rosetta transparently; it sees process.arch === x64
|
||||||
|
const rosetta = Bun.spawnSync(["arch", "-x86_64", "true"]);
|
||||||
|
if (!rosetta.success) {
|
||||||
|
fail("Rosetta 2 is not installed — run: softwareupdate --install-rosetta --agree-to-license");
|
||||||
|
}
|
||||||
|
run([await x64Cli(), "build", "--env=stable"]);
|
||||||
|
} else {
|
||||||
|
run(["bunx", "electrobun", "build", "--env=stable"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- collect artifacts ----
|
||||||
|
|
||||||
|
const [osName, archName] = requested === "win-x64" ? ["win", "x64"] : ["macos", requested.slice(4)];
|
||||||
|
const prefix = `stable-${osName}-${archName}-`;
|
||||||
|
const artifactsDir = path.join(ROOT, "artifacts");
|
||||||
|
const outDir = path.join(ROOT, "release", requested);
|
||||||
|
mkdirSync(outDir, { recursive: true });
|
||||||
|
|
||||||
|
const produced = readdirSync(artifactsDir).filter(
|
||||||
|
(f) => f.startsWith(prefix) && f.includes(BRANDING.appName),
|
||||||
|
);
|
||||||
|
if (!produced.length) fail(`no ${prefix}* artifacts found in artifacts/ — did the build run?`);
|
||||||
|
|
||||||
|
const out: string[] = [];
|
||||||
|
for (const file of produced) {
|
||||||
|
const ext = file.endsWith(".dmg") ? ".dmg" : file.endsWith("-Setup.zip") ? "-Setup.zip" : null;
|
||||||
|
if (!ext) continue; // skip the raw .app.tar.zst + update.json
|
||||||
|
const name = `${BRANDING.appName}-${buildName}-${requested}${ext}`;
|
||||||
|
cpSync(path.join(artifactsDir, file), path.join(outDir, name));
|
||||||
|
out.push(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows: also produce a portable zip from the assembled app folder (the
|
||||||
|
// Setup.exe is an installer; portable = unzip anywhere and run). Best-effort:
|
||||||
|
// the folder layout is confirmed by the first real Windows build.
|
||||||
|
if (requested === "win-x64") {
|
||||||
|
const appDir = path.join(ROOT, "build", `stable-${osName}-${archName}`, BRANDING.appName);
|
||||||
|
if (existsSync(appDir)) {
|
||||||
|
const portable = path.join(outDir, `${BRANDING.appName}-${buildName}-win-x64-portable.zip`);
|
||||||
|
run(["powershell", "-Command", `Compress-Archive -Force -Path '${appDir}\\*' -DestinationPath '${portable}'`]);
|
||||||
|
out.push(path.basename(portable));
|
||||||
|
} else {
|
||||||
|
console.warn(`[release] WARN: portable skipped — app folder not found at ${appDir}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!out.length) fail("artifacts were found but none matched a known package extension.");
|
||||||
|
console.log(`\n[release] ${BRANDING.appName} ${BRANDING.buildLabel} (${BUILD.commit}) — ${requested}`);
|
||||||
|
for (const name of out) console.log(`[release] release/${requested}/${name}`);
|
||||||
@@ -5,9 +5,9 @@ import { BUILD } from "./version.gen";
|
|||||||
export const BRANDING = {
|
export const BRANDING = {
|
||||||
appName: "miniSCP",
|
appName: "miniSCP",
|
||||||
identifier: "es.teknes.miniscp",
|
identifier: "es.teknes.miniscp",
|
||||||
// Previous app names: their app-data folders are migrated on boot
|
// Previous app names whose app-data folders migrate on boot after a rename
|
||||||
// (see migrateLegacyDataDir in src/bun/store/paths.ts).
|
// (see migrateLegacyDataDir in src/bun/store/paths.ts).
|
||||||
legacyNames: ["BunSCP"],
|
legacyNames: [] as string[],
|
||||||
// Sublime-style versioning: n = commit count (scripts/gen-version.ts).
|
// Sublime-style versioning: n = commit count (scripts/gen-version.ts).
|
||||||
// `version` stays semver-shaped for electrobun's config/updater.
|
// `version` stays semver-shaped for electrobun's config/updater.
|
||||||
version: `0.1.${BUILD.number}`,
|
version: `0.1.${BUILD.number}`,
|
||||||
|
|||||||
Reference in New Issue
Block a user