@mirinjs/cli 0.0.1-alpha.12 → 0.0.1-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mirinjs/cli",
3
- "version": "0.0.1-alpha.12",
3
+ "version": "0.0.1-alpha.13",
4
4
  "description": "CLI for mirin apps: dev, build, init.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,11 +27,11 @@
27
27
  "bun": ">=1.2.0"
28
28
  },
29
29
  "dependencies": {
30
- "create-mirinjs": "0.0.1-alpha.12",
31
- "mirinjs": "0.0.1-alpha.12"
30
+ "create-mirinjs": "0.0.1-alpha.13",
31
+ "mirinjs": "0.0.1-alpha.13"
32
32
  },
33
33
  "optionalDependencies": {
34
- "@mirinjs/darwin-arm64": "0.0.1-alpha.12"
34
+ "@mirinjs/darwin-arm64": "0.0.1-alpha.13"
35
35
  },
36
36
  "publishConfig": {
37
37
  "access": "public"
package/src/bundle.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  import { $ } from "bun";
15
- import { cpSync, mkdirSync, rmSync, writeFileSync, existsSync } from "node:fs";
15
+ import { cpSync, mkdirSync, rmSync, writeFileSync, existsSync, readdirSync } from "node:fs";
16
16
  import { join } from "node:path";
17
17
 
18
18
  const FRAMEWORK = "Chromium Embedded Framework.framework";
@@ -208,14 +208,66 @@ export async function buildAppBundle(opts: BundleOptions): Promise<{ app: string
208
208
  );
209
209
  }
210
210
 
211
- // Sign inside-out: framework, helpers, then the outer app. Ad-hoc ("-") by
212
- // default; pass a Developer ID to produce a distributable, notarizable app.
211
+ // Sign inside-out. Ad-hoc ("-") by default for local builds; pass a Developer
212
+ // ID to produce a distributable, notarizable app. Notarization requires the
213
+ // hardened runtime (--options runtime), a secure timestamp (--timestamp), and
214
+ // entitlements that let CEF + Bun JIT and load unsigned executable memory —
215
+ // without all three the Apple notary service returns "Invalid".
213
216
  const identity = opts.signIdentity ?? "-";
214
- await $`codesign --force --sign ${identity} ${join(frameworks, FRAMEWORK)}`.quiet();
215
- for (const { suffix } of HELPER_TYPES) {
216
- await $`codesign --force --sign ${identity} ${join(frameworks, `${appName} Helper${suffix}.app`)}`.quiet();
217
+ const notarizable = identity !== "-";
218
+ const cef = join(frameworks, FRAMEWORK);
219
+
220
+ if (notarizable) {
221
+ // Mirrors the entitlement set Electrobun ships for Bun + CEF under hardened
222
+ // runtime (electrobun-reference/package/src/cli/index.ts).
223
+ const entitlements = join(opts.outDir, "_entitlements.plist");
224
+ writeFileSync(
225
+ entitlements,
226
+ `<?xml version="1.0" encoding="UTF-8"?>
227
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
228
+ <plist version="1.0">
229
+ <dict>
230
+ <key>com.apple.security.cs.allow-jit</key><true/>
231
+ <key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>
232
+ <key>com.apple.security.cs.disable-library-validation</key><true/>
233
+ </dict>
234
+ </plist>
235
+ `,
236
+ );
237
+ const sign = (path: string, ents = false) =>
238
+ ents
239
+ ? $`codesign --force --timestamp --options runtime --entitlements ${entitlements} --sign ${identity} ${path}`.quiet()
240
+ : $`codesign --force --timestamp --options runtime --sign ${identity} ${path}`.quiet();
241
+
242
+ // 1. CEF's nested libraries, then 2. the framework bundle itself.
243
+ const cefLibs = join(cef, "Libraries");
244
+ if (existsSync(cefLibs)) {
245
+ for (const lib of readdirSync(cefLibs)) {
246
+ if (lib.endsWith(".dylib")) await sign(join(cefLibs, lib));
247
+ }
248
+ }
249
+ await sign(cef);
250
+ // 3. our FFI core dylib.
251
+ await sign(join(macos, "libmirin_core.dylib"));
252
+ // 4. each helper: the inner executable, then the .app wrapper (entitlements
253
+ // on both — the renderer/GPU helpers are what actually JIT).
254
+ for (const { suffix } of HELPER_TYPES) {
255
+ const name = `${appName} Helper${suffix}`;
256
+ const helperApp = join(frameworks, `${name}.app`);
257
+ await sign(join(helperApp, "Contents", "MacOS", name), true);
258
+ await sign(helperApp, true);
259
+ }
260
+ // 5. finally the outer app.
261
+ await sign(app, true);
262
+ rmSync(entitlements, { force: true });
263
+ } else {
264
+ // Ad-hoc: enough to launch locally; not distributable or notarizable.
265
+ await $`codesign --force --sign ${identity} ${cef}`.quiet();
266
+ for (const { suffix } of HELPER_TYPES) {
267
+ await $`codesign --force --sign ${identity} ${join(frameworks, `${appName} Helper${suffix}.app`)}`.quiet();
268
+ }
269
+ await $`codesign --force --sign ${identity} ${app}`.quiet();
217
270
  }
218
- await $`codesign --force --sign ${identity} ${app}`.quiet();
219
271
 
220
272
  return { app, exe: join(macos, appName) };
221
273
  }
package/src/release.ts CHANGED
@@ -52,9 +52,30 @@ export async function release(projectDir = process.cwd()): Promise<number> {
52
52
  console.log("[mirin release] notarizing (this can take a few minutes)…");
53
53
  const zip = join(buildDir, "_notarize.zip");
54
54
  await $`ditto -c -k --keepParent ${result.app} ${zip}`;
55
- await $`xcrun notarytool submit ${zip} --apple-id ${apple} --password ${pw} --team-id ${team} --wait`;
56
- await $`xcrun stapler staple ${result.app}`;
55
+ // `notarytool submit --wait` exits 0 even when the result is "Invalid", so
56
+ // parse the JSON status ourselves and surface the notary log on rejection —
57
+ // otherwise the only symptom is a confusing `stapler` failure downstream.
58
+ const out =
59
+ await $`xcrun notarytool submit ${zip} --apple-id ${apple} --password ${pw} --team-id ${team} --wait --output-format json`.text();
57
60
  rmSync(zip, { force: true });
61
+ let sub: { id?: string; status?: string } = {};
62
+ try {
63
+ sub = JSON.parse(out);
64
+ } catch {
65
+ console.error(out);
66
+ }
67
+ if (sub.status !== "Accepted") {
68
+ console.error(`[mirin release] notarization ${sub.status ?? "failed"} (id: ${sub.id ?? "?"})`);
69
+ if (sub.id) {
70
+ const log =
71
+ await $`xcrun notarytool log ${sub.id} --apple-id ${apple} --password ${pw} --team-id ${team}`
72
+ .text()
73
+ .catch(() => "");
74
+ if (log) console.error(log);
75
+ }
76
+ throw new Error(`notarization not accepted: ${sub.status ?? "unknown"}`);
77
+ }
78
+ await $`xcrun stapler staple ${result.app}`;
58
79
  }
59
80
 
60
81
  const codec = loadCodec(result.coreDylib);