mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-17 03:38:32 +02:00
* feat(desktop): support macOS cross-platform packaging * fix(desktop): use releaseType instead of publishingType in electron-builder publish config publishingType is not a valid electron-builder key; the correct GitHub provider option is releaseType. The previous value was silently ignored, causing uploads to be skipped and breaking auto-update. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(release): standardize artifact naming across desktop and CLI Unified scheme: `multica-<kind>-<version>-<platform>-<arch>.<ext>` so a filename alone reveals kind, version, platform, and CPU arch. Desktop (apps/desktop/electron-builder.yml): mac → multica-desktop-<v>-mac-<arch>.{dmg,zip} linux → multica-desktop-<v>-linux-<arch>.{deb,AppImage} (fixes `\${name}` expanding the scoped `@multica/desktop` into a broken `@multica/desktop-*` filename path) windows → multica-desktop-<v>-windows-<arch>.exe CLI (.goreleaser.yml): multica_<os>_<arch>.tar.gz → multica-cli-<v>-<os>-<arch>.tar.gz (adds `-cli` marker + version; switches `_` to `-` for consistency) Matrix update in apps/desktop/scripts/package.mjs `--all-platforms`: - drop mac x64 (Intel not a target yet) - add linux arm64 Final: mac arm64, win x64/arm64, linux x64/arm64. Downstream updates so install paths match the new CLI names: - scripts/install.sh - scripts/install.ps1 (URL + checksum regex) - CLI_INSTALL.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(release): use multica_{os}_{arch} CLI archive naming Standardize on the GoReleaser default 'multica_{os}_{arch}.{tar.gz|zip}' asset names. Install scripts and the desktop CLI bootstrap now resolve assets via checksums.txt so they work without hardcoding versions. The Go self-update path queries the GitHub release API and accepts either the new or legacy 'multica-cli-<version>-...' names so existing releases keep updating cleanly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(release): ship both legacy and versioned CLI archive names GoReleaser now produces both 'multica_{os}_{arch}.{ext}' (legacy) and 'multica-cli-{version}-{os}-{arch}.{ext}' (versioned) archives in every release. The legacy name keeps already-released CLIs self-updating; the versioned name is what new clients should use going forward. Self-update / install paths flipped to prefer the versioned name and fall back to legacy: - server/internal/cli/update.go (multica update) - apps/desktop/src/main/cli-release-asset.ts (desktop CLI bootstrap) - scripts/install.sh, scripts/install.ps1 (fresh install) Homebrew formula is pinned to the versioned archive via 'ids: [versioned]'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(desktop): also build Linux .rpm packages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(release): build Linux/Windows Desktop installers in CI; detect Windows ARM64 in install.ps1 Address review feedback on PR #1262: - .github/workflows/release.yml: add a 'desktop' job that runs after the CLI 'release' job and packages the Desktop installers for Linux (AppImage/deb/rpm) and Windows (NSIS) on x64 and arm64, then publishes them to the same GitHub Release via electron-builder. macOS Desktop continues to ship through the manual release-desktop skill so it can be signed and notarized with Apple Developer credentials. - scripts/install.ps1: detect Windows ARM64 hosts via RuntimeInformation::OSArchitecture so the new windows-arm64 CLI archive is downloaded on ARM64 machines instead of always falling back to amd64. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(release): split Windows arm64 auto-update channel to avoid latest.yml collision electron-builder's update metadata file is hardcoded to `latest.yml` for Windows regardless of arch (only Linux gets an arch-suffixed name; see app-builder-lib's getArchPrefixForUpdateFile). With two separate electron-builder invocations for Windows x64 and arm64, both publish `latest.yml` to the same GitHub Release and the second upload silently overwrites the first — leaving one of the two architectures with auto- update metadata pointing at the other arch's installer. Route Windows arm64 to its own `latest-arm64` channel: * scripts/package.mjs appends `-c.publish.channel=latest-arm64` only for the Windows arm64 invocation, so x64 keeps producing `latest.yml` and arm64 produces `latest-arm64.yml` alongside it. * updater.ts pins `autoUpdater.channel = 'latest-arm64'` on Windows arm64 clients so they fetch the matching metadata file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Devv <devv@Devvs-Mac-mini.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
169 lines
4.9 KiB
JavaScript
169 lines
4.9 KiB
JavaScript
#!/usr/bin/env node
|
|
// Builds the `multica` CLI from server/cmd/multica and copies the binary
|
|
// into apps/desktop/resources/bin/ so electron-vite (dev) and electron-
|
|
// builder (prod) pick it up. Running this on every dev/build/package
|
|
// invocation guarantees the bundled CLI always matches the current Go
|
|
// source — no more stale binary surprises. Go's build cache makes the
|
|
// no-op case (nothing changed) effectively free.
|
|
//
|
|
// ldflags mirror `make build` so `multica --version` reports a meaningful
|
|
// version / commit / date.
|
|
//
|
|
// Graceful: if `go` is not installed (e.g. frontend-only contributor), we
|
|
// skip the build and fall through to auto-install at runtime. A genuine
|
|
// Go compile error is fatal — you want that to block dev, not hide.
|
|
|
|
import { access, chmod, copyFile, mkdir, rm } from "node:fs/promises";
|
|
import { constants } from "node:fs";
|
|
import { execFileSync, execSync } from "node:child_process";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const repoRoot = resolve(here, "..", "..", "..");
|
|
const serverDir = join(repoRoot, "server");
|
|
|
|
const PLATFORM_TO_GOOS = {
|
|
darwin: "darwin",
|
|
linux: "linux",
|
|
win32: "windows",
|
|
};
|
|
|
|
const SUPPORTED_ARCHS = new Set(["x64", "arm64"]);
|
|
|
|
function runtimePlatformFromArgs(argv) {
|
|
const flagIndex = argv.indexOf("--target-platform");
|
|
if (flagIndex === -1) return process.platform;
|
|
return argv[flagIndex + 1] ?? "";
|
|
}
|
|
|
|
function runtimeArchFromArgs(argv) {
|
|
const flagIndex = argv.indexOf("--target-arch");
|
|
if (flagIndex === -1) return process.arch;
|
|
return argv[flagIndex + 1] ?? "";
|
|
}
|
|
|
|
function normalizeRuntimePlatform(platform) {
|
|
if (platform in PLATFORM_TO_GOOS) return platform;
|
|
throw new Error(
|
|
`[bundle-cli] unsupported target platform: ${platform}. ` +
|
|
"Use darwin, linux, or win32.",
|
|
);
|
|
}
|
|
|
|
function normalizeRuntimeArch(arch) {
|
|
if (SUPPORTED_ARCHS.has(arch)) return arch;
|
|
throw new Error(
|
|
`[bundle-cli] unsupported target architecture: ${arch}. ` +
|
|
"Use x64 or arm64.",
|
|
);
|
|
}
|
|
|
|
function binaryNameForPlatform(platform) {
|
|
return platform === "win32" ? "multica.exe" : "multica";
|
|
}
|
|
|
|
const targetPlatform = normalizeRuntimePlatform(
|
|
runtimePlatformFromArgs(process.argv.slice(2)),
|
|
);
|
|
const targetArch = normalizeRuntimeArch(runtimeArchFromArgs(process.argv.slice(2)));
|
|
const goos = PLATFORM_TO_GOOS[targetPlatform];
|
|
const goarch = targetArch === "x64" ? "amd64" : targetArch;
|
|
const binName = binaryNameForPlatform(targetPlatform);
|
|
const srcBinary = join(serverDir, "bin", `${goos}-${goarch}`, binName);
|
|
const destDir = join(repoRoot, "apps", "desktop", "resources", "bin");
|
|
const destBinary = join(destDir, binName);
|
|
|
|
function sh(cmd) {
|
|
try {
|
|
return execSync(cmd, { encoding: "utf-8" }).trim();
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function hasGo() {
|
|
try {
|
|
execSync("go version", { stdio: "pipe" });
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function exists(p) {
|
|
try {
|
|
await access(p, constants.F_OK);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (hasGo()) {
|
|
const version = sh("git describe --tags --always --dirty") || "dev";
|
|
const commit = sh("git rev-parse --short HEAD") || "unknown";
|
|
const date = new Date().toISOString().replace(/\.\d+Z$/, "Z");
|
|
const ldflags = `-X main.version=${version} -X main.commit=${commit} -X main.date=${date}`;
|
|
|
|
console.log(
|
|
`[bundle-cli] go build → ${srcBinary} (${goos}/${goarch}, version=${version} commit=${commit})`,
|
|
);
|
|
await mkdir(join(serverDir, "bin", `${goos}-${goarch}`), { recursive: true });
|
|
execFileSync(
|
|
"go",
|
|
[
|
|
"build",
|
|
"-ldflags",
|
|
ldflags,
|
|
"-o",
|
|
srcBinary,
|
|
"./cmd/multica",
|
|
],
|
|
{
|
|
cwd: serverDir,
|
|
stdio: "inherit",
|
|
env: {
|
|
...process.env,
|
|
CGO_ENABLED: "0",
|
|
GOOS: goos,
|
|
GOARCH: goarch,
|
|
},
|
|
},
|
|
);
|
|
} else {
|
|
console.warn(
|
|
"[bundle-cli] `go` not found in PATH — skipping CLI build. " +
|
|
"Desktop will use whatever is already in resources/bin/, or fall back " +
|
|
"to auto-installing the latest release at runtime.",
|
|
);
|
|
}
|
|
|
|
if (!(await exists(srcBinary))) {
|
|
console.warn(
|
|
`[bundle-cli] ${srcBinary} not present — Desktop will fall back to ` +
|
|
`auto-installing the latest release at runtime.`,
|
|
);
|
|
await rm(destDir, { recursive: true, force: true });
|
|
process.exit(0);
|
|
}
|
|
|
|
await rm(destDir, { recursive: true, force: true });
|
|
await mkdir(destDir, { recursive: true });
|
|
await copyFile(srcBinary, destBinary);
|
|
await chmod(destBinary, 0o755);
|
|
|
|
// macOS: ad-hoc sign so Gatekeeper doesn't complain when the parent app
|
|
// (which itself may be unsigned in dev) spawns the child.
|
|
if (process.platform === "darwin") {
|
|
try {
|
|
execSync(`codesign -s - --force ${JSON.stringify(destBinary)}`, {
|
|
stdio: "pipe",
|
|
});
|
|
} catch {
|
|
// Non-fatal. Unsigned binaries still run when the parent app is trusted.
|
|
}
|
|
}
|
|
|
|
console.log(`[bundle-cli] bundled ${srcBinary} → ${destBinary}`);
|