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
13 changes: 11 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
verify:
name: Lint, typecheck, test
runs-on: macos-latest
timeout-minutes: 30
timeout-minutes: 45

steps:
- name: Check out repository
Expand Down Expand Up @@ -59,5 +59,14 @@ jobs:
env:
NODE_OPTIONS: --max-old-space-size=8192

- name: Inspect packaged JavaScript graph
- name: Inspect Vite JavaScript graph
run: bun ./scripts/inspect-packaged-js.mjs apps/desktop/out

- name: Assert isolated main-process layout
run: bun ./scripts/assert-main-build-layout.mjs apps/desktop/out/main

- name: Pack unsigned macOS dir and inspect asar
run: bun run --filter @pi-desktop/desktop pack:mac:dir
env:
NODE_OPTIONS: --max-old-space-size=8192
CSC_IDENTITY_AUTO_DISCOVERY: "false"
5 changes: 3 additions & 2 deletions .github/workflows/release-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ jobs:
bun run typecheck
bun run test

- name: Build, sign, notarize, and verify
- name: Build, inspect, and optionally sign
run: bun run release:mac
env:
NODE_OPTIONS: --max-old-space-size=8192
CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
Expand All @@ -60,4 +61,4 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
run: gh release edit "${RELEASE_TAG}" --draft=false --latest
run: bun ./scripts/publish-verified-release.mjs "${RELEASE_TAG}"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
xattr -dr com.apple.quarantine "/Applications/Pi Desktop.app"
```

Release builds are unsigned (no paid Apple Developer ID). That command is what makes the download launch.
Release builds are unsigned unless Developer ID credentials are present in the release workflow. Unsigned downloads need that `xattr` command to launch.

**From source**

Expand Down
21 changes: 12 additions & 9 deletions apps/desktop/electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ import type { Plugin } from "vite";
const mainEntry = fileURLToPath(
new URL("./src/main/index.ts", import.meta.url),
);
const agentHostSessionServerEntry = fileURLToPath(
new URL("./src/main/agent-host-session-server-entry.ts", import.meta.url),
);

function rejectEmptyMainChunks(): Plugin {
return {
name: "reject-empty-main-chunks",
generateBundle(_options, bundle) {
const empty = Object.values(bundle).filter(
(item) => item.type === "chunk" && item.code.trim().length === 0,
(item) =>
(item.type === "chunk" && item.code.trim().length === 0) ||
(item.type === "asset" &&
typeof item.fileName === "string" &&
/\.(?:[cm]?js)$/.test(item.fileName) &&
(typeof item.source === "string"
? item.source.trim().length === 0
: Buffer.from(item.source).toString("utf8").trim().length === 0)),
);
if (empty.length === 0) {
return;
Expand Down Expand Up @@ -53,15 +57,14 @@ export default defineConfig({
external: ["electron", "node-pty"],
input: {
index: mainEntry,
agentHostSessionServer: agentHostSessionServerEntry,
},
treeshake: false,
plugins: [rejectEmptyMainChunks()],
output: {
// Fold empty/tiny shared chunks into their importers. A 0-byte
// shared chunk is the v0.9.3 launch crash (rolldown#6677). Infinity
// OOMs electron-vite on GitHub-hosted runners.
experimentalMinChunkSize: 512,
// Single main entry. The agent session server is bundled separately
// via `?modulePath`. Two Rollup inputs here created the shared
// empty chunk that crashed v0.9.3.
inlineDynamicImports: true,
},
},
},
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"build": "electron-vite build",
"dev": "electron-vite dev",
"dist:mac": "bun run build && bun ../../scripts/stage-desktop-release.mjs && electron-builder --projectDir . --config ../../electron-builder.yml --mac dmg zip --arm64",
"pack:mac:dir": "CSC_IDENTITY_AUTO_DISCOVERY=false bun ../../scripts/stage-desktop-release.mjs && CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --projectDir . --config ../../electron-builder.yml --mac dir --arm64",
"postinstall": "electron-rebuild -f -w node-pty",
"typecheck": "tsc --project tsconfig.json --noEmit"
},
Expand Down
79 changes: 79 additions & 0 deletions scripts/assert-main-build-layout.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env node
import { existsSync, readdirSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const SESSION_SERVER_PATTERN = /^agent-host-session-server-entry-[^./]+\.js$/;

export function assertMainProcessLayout(mainOutDir) {
const indexPath = path.join(mainOutDir, "index.js");
if (!existsSync(indexPath)) {
throw new Error(`Missing main entry: ${indexPath}`);
}
const indexSource = readFileSync(indexPath, "utf8");
if (indexSource.trim().length === 0) {
throw new Error(`Main entry is empty: ${indexPath}`);
}

const chunksDir = path.join(mainOutDir, "chunks");
if (existsSync(chunksDir)) {
throw new Error(
`Shared main-process chunks directory exists: ${chunksDir}`,
);
}

const sessionFiles = readdirSync(mainOutDir).filter((name) =>
SESSION_SERVER_PATTERN.test(name),
);
if (sessionFiles.length === 0) {
throw new Error(
`Missing ?modulePath session-server bundle under ${mainOutDir}`,
);
}

const referenced = sessionFiles.filter((fileName) => {
const relative = `./${fileName}`;
return (
indexSource.includes(JSON.stringify(relative)) ||
indexSource.includes(relative)
);
});
if (referenced.length === 0) {
throw new Error(
`Main entry does not reference a session-server bundle (${sessionFiles.join(", ")})`,
);
}

for (const fileName of referenced) {
const sessionPath = path.join(mainOutDir, fileName);
if (readFileSync(sessionPath, "utf8").trim().length === 0) {
throw new Error(`Session-server bundle is empty: ${sessionPath}`);
}
}

return {
indexPath,
sessionFiles: referenced,
};
}

function runCli(argv) {
const target = argv[0];
if (!target) {
throw new Error("Usage: assert-main-build-layout.mjs <main-out-dir>");
}
const resolved = path.resolve(target);
const result = assertMainProcessLayout(resolved);
console.log(
`Main process layout OK: ${result.indexPath} -> ${result.sessionFiles.join(", ")}`,
);
}

if (process.argv[1] === fileURLToPath(import.meta.url)) {
try {
runCli(process.argv.slice(2));
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
}
}
Loading
Loading