mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-28 05:46:58 +02:00
* fix(desktop): ship entitlements.mac.plist so electron-builder can codesign electron-builder.yml already references build/entitlements.mac.plist via entitlementsInherit, but the file was missing from the tree, so `pnpm package` failed at the codesign step with: build/entitlements.mac.plist: cannot read entitlement data Ship the file. It grants the hardened-runtime capabilities the app actually needs: JIT + unsigned executable memory for V8, disabled library validation so the Electron process can spawn the bundled `multica` Go binary as a child process, and network client/server for the daemon's API and /health endpoints. Also tweak the root .gitignore: the top-level `build` rule was shadowing apps/desktop/build/, hiding this config file from git. Add a scoped exception so apps/desktop/build/ (which holds electron-builder source resources, not output) is tracked. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(desktop): derive package version from git tag at build time The Desktop app version was hardcoded to "0.1.0" in package.json and never bumped, while the bundled CLI reports whatever `git describe` gives at build time. Result: packaging on main produced desktop-0.1.0.dmg containing multica v0.1.35-14-gf1415e96 — completely disconnected. Users see two unrelated version numbers for the same release. Sync them by using the same source GoReleaser uses for the CLI: the nearest git tag. A new scripts/package.mjs wrapper runs bundle-cli.mjs, derives the version via `git describe --tags --always --dirty` (strips the `v` prefix, falls back to `0.0.0-<hash>` when no tags are reachable), and invokes electron-builder with `-c.extraMetadata.version=<derived>` — which overrides package.json at build time without mutating the tracked file. On a clean tag commit → "0.1.36"; between tags → "0.1.35-14-gf1415e96" (valid semver prerelease); dirty tree → same with "-dirty" suffix. The `package` script in package.json now points to the wrapper. Passthrough args (--mac, --arm64, etc.) after `pnpm package --` are forwarded to electron-builder unchanged. Dev and build scripts are untouched — they continue to use bundle-cli.mjs directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(desktop): enable macOS notarization and clean artifact names Two electron-builder.yml tweaks that unblock a proper release: - `mac.notarize: false` → `true`. Notarization runs in-build via notarytool, reading APPLE_ID/APPLE_APP_SPECIFIC_PASSWORD/APPLE_TEAM_ID from env. electron-builder then staples the ticket before zipping, so `latest-mac.yml`'s SHA512s match the published artifacts (critical for electron-updater — post-hoc re-stapling would invalidate them). Non-mac/CI contributors are unaffected: `pnpm package` already requires the Developer ID signing cert, and notarization is a strict superset of signing. - `mac.artifactName` and `dmg.artifactName` now hardcode `multica-desktop-${version}-${arch}.${ext}` instead of using `${name}`, which expands to `@multica/desktop` for scoped package names and literally produced files at `dist/@multica/desktop-*.dmg`. The nested `@multica/` path is useless and makes the GitHub Release asset URL ugly. New layout is flat: `dist/multica-desktop-<ver>-arm64.dmg`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(desktop): keep local package builds working after notarize: true Three polish items from review of this PR. - Local dev regression: `mac.notarize: true` in electron-builder.yml made `pnpm package` hard-fail on macs without APPLE_* env vars, even for non-publishing local smoke tests. Detect the missing env in scripts/package.mjs and pass `-c.mac.notarize=false` for that run only. Real release builds (which source apps/desktop/macOS/.env via the release-desktop skill) are unaffected. Also logs a clear warning so the developer knows notarization was skipped. - spawnSync previously used `shell: true`, which reassembled argv into a shell command string. Zero real-world injection risk given our controlled inputs, but dropping it closes the vector at no cost — pnpm already puts node_modules/.bin on PATH for script runs so the binary is found without a shell wrapper. - On spawn failure (e.g. electron-builder not found), result.error was silently swallowed and the exit was just `1`. Log the underlying reason before exiting. Also refactor so normalizeGitVersion is exportable and guard the main entry behind an import.meta.url check, enabling unit coverage. New package.test.mjs covers the six branches: null/empty input, clean tag, between-tags prerelease, dirty suffix, v-prefixed prerelease tags (vX.Y.Z-alpha and vX.Y.Z-rc.2), and the 0.0.0-<hash> fallback for hash-only describe output. vitest.config.ts picks up scripts/**/*.test.mjs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(desktop): commit .env.production for release builds Bake production backend + app URLs into release packages so `pnpm package` produces a build that points at multica.ai out of the box. electron-vite (Vite) reads .env.production automatically in production mode — no script changes needed. Values: VITE_API_URL = https://api.multica.ai VITE_WS_URL = wss://api.multica.ai/ws VITE_APP_URL = https://multica.ai Also parameterize the two hardcoded `https://www.multica.ai` strings in platform/navigation.tsx's `getShareableUrl` on VITE_APP_URL. The previous hardcoded host pointed to `www.multica.ai`, which disagrees with the canonical `multica.ai` we're standardizing on. Shareable links from the desktop ("Copy link to issue") now match. The env file is public config, not a secret, so add a scoped exception to the root .gitignore's `.env*` rule. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
123 lines
4.3 KiB
JavaScript
123 lines
4.3 KiB
JavaScript
#!/usr/bin/env node
|
|
// Wrapper around `electron-builder` that keeps the Desktop version in
|
|
// lockstep with the CLI. Both are derived from `git describe --tags
|
|
// --always --dirty` — the same source GoReleaser reads for the CLI
|
|
// binary via the `main.version` ldflag — so a single `vX.Y.Z` tag push
|
|
// produces matching CLI and Desktop versions.
|
|
//
|
|
// Runs the existing bundle-cli.mjs first (so the Go binary is compiled
|
|
// and copied into resources/bin/), then invokes electron-builder with
|
|
// `-c.extraMetadata.version=<derived>` so the override applies at build
|
|
// time without mutating the tracked package.json.
|
|
//
|
|
// Extra CLI args after `pnpm package --` are forwarded to electron-builder
|
|
// unchanged (e.g. `--mac --arm64`).
|
|
//
|
|
// The `normalizeGitVersion` helper is exported so tests can cover the
|
|
// version-derivation logic without shelling out.
|
|
|
|
import { execFileSync, spawnSync, execSync } from "node:child_process";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const desktopRoot = resolve(here, "..");
|
|
|
|
function sh(cmd) {
|
|
try {
|
|
return execSync(cmd, { encoding: "utf-8" }).trim();
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pure transformation from the `git describe --tags --always --dirty`
|
|
* output to the value we feed into electron-builder's extraMetadata.version.
|
|
*
|
|
* - empty input → null (caller should fall back)
|
|
* - "v0.1.36" → "0.1.36"
|
|
* - "v0.1.35-14-gf1415e96" → "0.1.35-14-gf1415e96" (semver prerelease)
|
|
* - "v0.1.35-…-dirty" → same, dirty suffix preserved
|
|
* - "f1415e96" (no tag) → "0.0.0-f1415e96" (fallback)
|
|
*
|
|
* Leading `v` is stripped so the result is valid semver for package.json.
|
|
*/
|
|
export function normalizeGitVersion(raw) {
|
|
if (!raw) return null;
|
|
const stripped = raw.replace(/^v/, "");
|
|
if (!/^\d/.test(stripped)) {
|
|
// No reachable tag — `git describe` fell back to just the commit hash.
|
|
return `0.0.0-${stripped}`;
|
|
}
|
|
return stripped;
|
|
}
|
|
|
|
function deriveVersion() {
|
|
return normalizeGitVersion(sh("git describe --tags --always --dirty"));
|
|
}
|
|
|
|
function main() {
|
|
// Step 1: build + bundle the Go CLI via the existing script.
|
|
execFileSync("node", [resolve(here, "bundle-cli.mjs")], {
|
|
stdio: "inherit",
|
|
cwd: desktopRoot,
|
|
});
|
|
|
|
// Step 2: derive the version that should be written into the app.
|
|
const version = deriveVersion();
|
|
if (version) {
|
|
console.log(`[package] Desktop version → ${version} (from git describe)`);
|
|
} else {
|
|
console.warn(
|
|
"[package] could not derive version from git; falling back to package.json",
|
|
);
|
|
}
|
|
|
|
// Step 3: assemble electron-builder args.
|
|
const passthrough = process.argv.slice(2);
|
|
const builderArgs = [];
|
|
if (version) builderArgs.push(`-c.extraMetadata.version=${version}`);
|
|
|
|
// Step 4: gracefully degrade for local dev builds. electron-builder.yml
|
|
// sets `notarize: true` so real releases notarize in-build (keeping the
|
|
// stapled .app consistent with latest-mac.yml's SHA512). But a mac dev
|
|
// who just wants to smoke-test a local package doesn't have Apple
|
|
// credentials, and would otherwise hit a hard failure at the notarize
|
|
// step. Detect the missing env and flip notarize off for this run only.
|
|
if (!process.env.APPLE_TEAM_ID) {
|
|
console.warn(
|
|
"[package] APPLE_TEAM_ID not set — skipping notarization (local dev build). " +
|
|
"Set APPLE_ID + APPLE_APP_SPECIFIC_PASSWORD + APPLE_TEAM_ID for a release build.",
|
|
);
|
|
builderArgs.push("-c.mac.notarize=false");
|
|
}
|
|
|
|
builderArgs.push(...passthrough);
|
|
|
|
// Step 5: invoke electron-builder. pnpm puts node_modules/.bin on PATH
|
|
// for the script run, so spawnSync finds the binary without needing a
|
|
// shell wrapper (avoids any risk of argv interpolation).
|
|
const result = spawnSync("electron-builder", builderArgs, {
|
|
stdio: "inherit",
|
|
cwd: desktopRoot,
|
|
});
|
|
|
|
if (result.error) {
|
|
console.error(
|
|
"[package] failed to spawn electron-builder:",
|
|
result.error.message,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
process.exit(result.status ?? 1);
|
|
}
|
|
|
|
// Only run when invoked as a CLI, not when imported by a test file.
|
|
if (
|
|
process.argv[1] &&
|
|
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
) {
|
|
main();
|
|
}
|