@mirinjs/cli 0.1.0-alpha.24 → 0.1.0-alpha.26
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 +8 -8
- package/src/build.ts +12 -4
- package/src/bundle/linux/app.ts +5 -0
- package/src/bundle/macos/app.ts +16 -0
- package/src/bundle/macos/index.ts +1 -0
- package/src/bundle/macos/macho.ts +42 -0
- package/src/bundle/macos/sdk-stamp.ts +38 -0
- package/src/bundle/windows/app.ts +5 -0
- package/src/dev.ts +7 -2
- package/src/extras.ts +27 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mirinjs/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.26",
|
|
4
4
|
"description": "CLI for mirin apps: dev, build, init.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,15 +27,15 @@
|
|
|
27
27
|
"bun": ">=1.2.0"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"create-mirinjs": "0.1.0-alpha.
|
|
31
|
-
"mirinjs": "0.1.0-alpha.
|
|
30
|
+
"create-mirinjs": "0.1.0-alpha.26",
|
|
31
|
+
"mirinjs": "0.1.0-alpha.26"
|
|
32
32
|
},
|
|
33
33
|
"optionalDependencies": {
|
|
34
|
-
"@mirinjs/darwin-arm64": "0.1.0-alpha.
|
|
35
|
-
"@mirinjs/win32-x64": "0.1.0-alpha.
|
|
36
|
-
"@mirinjs/win32-arm64": "0.1.0-alpha.
|
|
37
|
-
"@mirinjs/linux-arm64": "0.1.0-alpha.
|
|
38
|
-
"@mirinjs/linux-x64": "0.1.0-alpha.
|
|
34
|
+
"@mirinjs/darwin-arm64": "0.1.0-alpha.26",
|
|
35
|
+
"@mirinjs/win32-x64": "0.1.0-alpha.26",
|
|
36
|
+
"@mirinjs/win32-arm64": "0.1.0-alpha.26",
|
|
37
|
+
"@mirinjs/linux-arm64": "0.1.0-alpha.26",
|
|
38
|
+
"@mirinjs/linux-x64": "0.1.0-alpha.26"
|
|
39
39
|
},
|
|
40
40
|
"publishConfig": {
|
|
41
41
|
"access": "public"
|
package/src/build.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* from inside the bundle, and webviews load their `app://` URLs served from
|
|
10
10
|
* Contents/Resources by the native scheme handler.
|
|
11
11
|
*
|
|
12
|
+
* Release channel / feed: MIRIN_RELEASE_CHANNEL and MIRIN_RELEASE_BASE_URL override
|
|
13
|
+
* config.release so one config can produce stable and nightly builds.
|
|
12
14
|
* Codesign identity: ad-hoc by default; set MIRIN_SIGN_IDENTITY to a Developer
|
|
13
15
|
* ID to produce a distributable, notarizable app.
|
|
14
16
|
*/
|
|
@@ -18,10 +20,10 @@ import { join } from "node:path";
|
|
|
18
20
|
import { $ } from "bun";
|
|
19
21
|
import { resolveArtifacts } from "./artifacts.ts";
|
|
20
22
|
import { buildLinuxBundle } from "./bundle/linux/index.ts";
|
|
21
|
-
import { buildAppBundle } from "./bundle/macos/index.ts";
|
|
23
|
+
import { buildAppBundle, stampMacSdk } from "./bundle/macos/index.ts";
|
|
22
24
|
import { normalizeCefLocales } from "./bundle/shared/cef-locales.ts";
|
|
23
25
|
import { buildWindowsBundle } from "./bundle/windows/index.ts";
|
|
24
|
-
import { compileWorkers, normalizeSidecars } from "./extras.ts";
|
|
26
|
+
import { compileWorkers, normalizeResources, normalizeSidecars } from "./extras.ts";
|
|
25
27
|
import { resolveIconFallback, resolveIconSource } from "./icons/sources.ts";
|
|
26
28
|
import { makeWindowsIcon } from "./icons/windows/index.ts";
|
|
27
29
|
import { buildLinuxPackages, resolveLinuxFormats } from "./package/linux/index.ts";
|
|
@@ -186,8 +188,9 @@ export async function build(
|
|
|
186
188
|
const bundleId: string = config.id ?? "dev.mirin.app";
|
|
187
189
|
const mainEntry = join(projectDir, config.main ?? "main/main.ts");
|
|
188
190
|
const version = opts.version ?? appVersion(projectDir);
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
+
// A release pipeline builds several channels from one config: the env wins.
|
|
192
|
+
const channel: string = process.env.MIRIN_RELEASE_CHANNEL || config.release?.channel || "stable";
|
|
193
|
+
const baseUrl: string | undefined = process.env.MIRIN_RELEASE_BASE_URL || config.release?.baseUrl;
|
|
191
194
|
const releaseNotes: string | undefined = config.release?.notes;
|
|
192
195
|
const dmg: boolean | import("mirinjs").DmgConfig = config.dmg ?? true;
|
|
193
196
|
const nsis: boolean | import("mirinjs").NsisConfig = config.nsis ?? true;
|
|
@@ -213,6 +216,7 @@ export async function build(
|
|
|
213
216
|
const hostExe = join(work, IS_WINDOWS ? "host-release.exe" : "host-release");
|
|
214
217
|
const workerJs = join(work, "worker.release.js");
|
|
215
218
|
const sidecars = normalizeSidecars(projectDir, config.sidecars);
|
|
219
|
+
const extraResources = normalizeResources(projectDir, config.resources);
|
|
216
220
|
const hostBuild = (async (): Promise<void> => {
|
|
217
221
|
// Bun's Windows arm64 build currently has no bun:ffi/TinyCC runtime. The
|
|
218
222
|
// win32-arm64 Mirin package therefore carries an x64 compatibility payload,
|
|
@@ -221,6 +225,7 @@ export async function build(
|
|
|
221
225
|
await $`bun build --compile --minify ${target} ${artifacts.hostEntry} --outfile ${hostExe}`.cwd(
|
|
222
226
|
projectDir,
|
|
223
227
|
);
|
|
228
|
+
await stampMacSdk(hostExe);
|
|
224
229
|
if (IS_WINDOWS) {
|
|
225
230
|
patchPeToGuiSubsystem(hostExe);
|
|
226
231
|
await brandWindowsExe(hostExe, {
|
|
@@ -255,9 +260,12 @@ export async function build(
|
|
|
255
260
|
singleInstance: config.singleInstance,
|
|
256
261
|
// Lets a diagnostic build opt in via `devtools.production`; otherwise fully off.
|
|
257
262
|
devtools: config.devtools,
|
|
263
|
+
// Production: names resolve under the bundle resources directory.
|
|
264
|
+
resources: Object.fromEntries(extraResources.map((r) => [r.name, r.name])),
|
|
258
265
|
}),
|
|
259
266
|
versionJson,
|
|
260
267
|
workers: extraWorkers,
|
|
268
|
+
extra: extraResources,
|
|
261
269
|
};
|
|
262
270
|
const { app } = IS_WINDOWS
|
|
263
271
|
? await buildWindowsBundle({
|
package/src/bundle/linux/app.ts
CHANGED
|
@@ -61,6 +61,8 @@ export interface LinuxBundleOptions {
|
|
|
61
61
|
versionJson?: string; // serialized version.json → resources/version.json (updater)
|
|
62
62
|
sidecars?: { name: string; src: string }[]; // → resources/sidecars/<name>
|
|
63
63
|
workers?: Record<string, string>; // name → abs path → resources/workers/<name>.js
|
|
64
|
+
/** Extra resource directories (name -> abs dir) copied verbatim -> resources/<name>. */
|
|
65
|
+
extra?: { name: string; src: string }[];
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -234,6 +236,9 @@ export async function buildLinuxBundle(
|
|
|
234
236
|
cpSync(src, join(workersDir, `${name}.js`));
|
|
235
237
|
}
|
|
236
238
|
}
|
|
239
|
+
for (const extra of opts.resources?.extra ?? []) {
|
|
240
|
+
cpSync(extra.src, join(res, extra.name), { recursive: true });
|
|
241
|
+
}
|
|
237
242
|
if (opts.resources.sidecars?.length) {
|
|
238
243
|
const sidecarsDir = join(res, "sidecars");
|
|
239
244
|
mkdirSync(sidecarsDir, { recursive: true });
|
package/src/bundle/macos/app.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { join } from "node:path";
|
|
|
24
24
|
import { $ } from "bun";
|
|
25
25
|
import { isIconComposerDoc, writeAppearanceCatalog } from "../../icons/macos/index.ts";
|
|
26
26
|
import { pruneMacCefLocales } from "../shared/cef-locales.ts";
|
|
27
|
+
import { findMachOFiles } from "./macho.ts";
|
|
27
28
|
|
|
28
29
|
const FRAMEWORK = "Chromium Embedded Framework.framework";
|
|
29
30
|
|
|
@@ -68,6 +69,8 @@ export interface BundleOptions {
|
|
|
68
69
|
sidecars?: SidecarBundle[];
|
|
69
70
|
/** Compiled extra-worker JS (name -> abs path) -> Resources/workers/<name>.js. */
|
|
70
71
|
workers?: Record<string, string>;
|
|
72
|
+
/** Extra resource directories (name -> abs dir) copied verbatim -> Resources/<name>. */
|
|
73
|
+
extra?: { name: string; src: string }[];
|
|
71
74
|
};
|
|
72
75
|
}
|
|
73
76
|
|
|
@@ -292,6 +295,15 @@ export async function buildAppBundle(opts: BundleOptions): Promise<{ app: string
|
|
|
292
295
|
cpSync(src, join(workersDir, `${name}.js`));
|
|
293
296
|
}
|
|
294
297
|
}
|
|
298
|
+
// Extra resource dirs -> Resources/<name>. Any Mach-O inside (a bundled server's
|
|
299
|
+
// native addons, helper executables) is signed below: notarization rejects
|
|
300
|
+
// unsigned code anywhere in the bundle.
|
|
301
|
+
const extraDirs: string[] = [];
|
|
302
|
+
for (const extra of opts.resources?.extra ?? []) {
|
|
303
|
+
const dest = join(resources, extra.name);
|
|
304
|
+
cpSync(extra.src, dest, { recursive: true });
|
|
305
|
+
extraDirs.push(dest);
|
|
306
|
+
}
|
|
295
307
|
// Sidecar binaries -> Resources/sidecars/<name> (chmod +x; signed below). Paths
|
|
296
308
|
// collected here so the codesign loop can sign them inside-out with the app.
|
|
297
309
|
const sidecarDests: SidecarBundle[] = [];
|
|
@@ -385,6 +397,10 @@ export async function buildAppBundle(opts: BundleOptions): Promise<{ app: string
|
|
|
385
397
|
await sign(sc.src);
|
|
386
398
|
}
|
|
387
399
|
}
|
|
400
|
+
// 3c. Mach-O files shipped inside resource dirs (.node addons, dylibs, CLIs).
|
|
401
|
+
for (const dir of extraDirs) {
|
|
402
|
+
for (const binary of findMachOFiles(dir)) await sign(binary);
|
|
403
|
+
}
|
|
388
404
|
// 4. each helper: the inner executable, then the .app wrapper (entitlements
|
|
389
405
|
// on both — the renderer/GPU helpers are what actually JIT).
|
|
390
406
|
for (const { suffix } of HELPER_TYPES) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { closeSync, lstatSync, openSync, readdirSync, readSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
// Thin (LE/BE, 32/64-bit) and fat Mach-O magics. A fat header shares 0xCAFEBABE
|
|
5
|
+
// with Java class files; `nfat_arch` separates them (a class file has a version there).
|
|
6
|
+
const THIN_MAGICS = new Set([0xfeedface, 0xfeedfacf, 0xcefaedfe, 0xcffaedfe]);
|
|
7
|
+
const FAT_MAGICS = new Set([0xcafebabe, 0xcafebabf]);
|
|
8
|
+
const MAX_FAT_ARCHS = 32;
|
|
9
|
+
|
|
10
|
+
/** True when the file starts with a Mach-O header (thin or fat), regardless of extension. */
|
|
11
|
+
export function isMachO(path: string): boolean {
|
|
12
|
+
const fd = openSync(path, "r");
|
|
13
|
+
try {
|
|
14
|
+
const header = Buffer.alloc(8);
|
|
15
|
+
if (readSync(fd, header, 0, 8, 0) < 8) return false;
|
|
16
|
+
const magic = header.readUInt32BE(0);
|
|
17
|
+
if (THIN_MAGICS.has(magic)) return true;
|
|
18
|
+
if (!FAT_MAGICS.has(magic)) return false;
|
|
19
|
+
const archCount = header.readUInt32BE(4);
|
|
20
|
+
return archCount > 0 && archCount <= MAX_FAT_ARCHS;
|
|
21
|
+
} finally {
|
|
22
|
+
closeSync(fd);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Every regular Mach-O file under `root` (symlinks skipped), sorted for stable signing. */
|
|
27
|
+
export function findMachOFiles(root: string): string[] {
|
|
28
|
+
const found: string[] = [];
|
|
29
|
+
const walk = (dir: string) => {
|
|
30
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
31
|
+
const path = join(dir, entry.name);
|
|
32
|
+
if (entry.isSymbolicLink()) continue;
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
walk(path);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (entry.isFile() && lstatSync(path).size >= 8 && isMachO(path)) found.push(path);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
walk(root);
|
|
41
|
+
return found.sort();
|
|
42
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stamp the compiled host with the macOS 26 SDK version.
|
|
3
|
+
*
|
|
4
|
+
* `bun build --compile` clones the Bun runtime binary, so the host inherits
|
|
5
|
+
* Bun's LC_BUILD_VERSION (SDK 15.x). AppKit gates the macOS 26 window design
|
|
6
|
+
* ("linked-on-or-after") on the MAIN executable's SDK, which leaves mirin apps
|
|
7
|
+
* with the pre-Tahoe compatibility chrome — flat traffic lights, old window
|
|
8
|
+
* metrics. Rewriting the load command opts the app into the current design.
|
|
9
|
+
* Must run before codesigning; vtool invalidates the signature.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, renameSync } from "node:fs";
|
|
13
|
+
import { $ } from "bun";
|
|
14
|
+
|
|
15
|
+
/** Matches the LSMinimumSystemVersion mirin writes into Info.plist. */
|
|
16
|
+
const MIN_OS = "13.0";
|
|
17
|
+
/** The design-gate threshold; the number is declarative, no SDK needs to exist locally. */
|
|
18
|
+
const SDK = "26.0";
|
|
19
|
+
|
|
20
|
+
/** Best-effort: a missing vtool (bare machines) keeps Bun's original header. */
|
|
21
|
+
export async function stampMacSdk(hostExe: string): Promise<void> {
|
|
22
|
+
if (process.platform !== "darwin") return;
|
|
23
|
+
if (!Bun.which("vtool")) {
|
|
24
|
+
console.warn("[mirin] vtool not found — the app keeps the compatibility window design.");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const out = `${hostExe}.sdkstamp`;
|
|
28
|
+
const res =
|
|
29
|
+
await $`vtool -set-build-version macos ${MIN_OS} ${SDK} -replace -output ${out} ${hostExe}`
|
|
30
|
+
.nothrow()
|
|
31
|
+
.quiet();
|
|
32
|
+
if (res.exitCode !== 0 || !existsSync(out)) {
|
|
33
|
+
console.warn(`[mirin] vtool failed (exit ${res.exitCode}) — keeping Bun's SDK header.`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
renameSync(out, hostExe);
|
|
37
|
+
await $`chmod +x ${hostExe}`.quiet();
|
|
38
|
+
}
|
|
@@ -48,6 +48,8 @@ export interface WinBundleOptions {
|
|
|
48
48
|
versionJson?: string; // serialized version.json → resources/version.json (updater)
|
|
49
49
|
sidecars?: { name: string; src: string }[]; // → resources/sidecars/<name>
|
|
50
50
|
workers?: Record<string, string>; // name → abs path → resources/workers/<name>.js
|
|
51
|
+
/** Extra resource directories (name -> abs dir) copied verbatim -> resources/<name>. */
|
|
52
|
+
extra?: { name: string; src: string }[];
|
|
51
53
|
};
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -94,6 +96,9 @@ export async function buildWindowsBundle(
|
|
|
94
96
|
cpSync(src, join(workersDir, `${name}.js`));
|
|
95
97
|
}
|
|
96
98
|
}
|
|
99
|
+
for (const extra of opts.resources?.extra ?? []) {
|
|
100
|
+
cpSync(extra.src, join(res, extra.name), { recursive: true });
|
|
101
|
+
}
|
|
97
102
|
if (opts.resources.sidecars?.length) {
|
|
98
103
|
const sidecarsDir = join(res, "sidecars");
|
|
99
104
|
mkdirSync(sidecarsDir, { recursive: true });
|
package/src/dev.ts
CHANGED
|
@@ -23,13 +23,13 @@ import {
|
|
|
23
23
|
resolveLinuxDesktopIconPng,
|
|
24
24
|
resolveLinuxIconPng,
|
|
25
25
|
} from "./bundle/linux/index.ts";
|
|
26
|
-
import { buildAppBundle } from "./bundle/macos/index.ts";
|
|
26
|
+
import { buildAppBundle, stampMacSdk } from "./bundle/macos/index.ts";
|
|
27
27
|
import { buildWindowsBundle } from "./bundle/windows/index.ts";
|
|
28
28
|
|
|
29
29
|
const IS_WINDOWS = process.platform === "win32";
|
|
30
30
|
const IS_LINUX = process.platform === "linux";
|
|
31
31
|
|
|
32
|
-
import { compileWorkers, normalizeSidecars } from "./extras.ts";
|
|
32
|
+
import { compileWorkers, normalizeResources, normalizeSidecars } from "./extras.ts";
|
|
33
33
|
import { resolveIconFallback, resolveIconSource } from "./icons/sources.ts";
|
|
34
34
|
import { createReporter } from "./shared/report.ts";
|
|
35
35
|
import { DevSession } from "./shared/session.ts";
|
|
@@ -104,6 +104,7 @@ export async function dev(projectDir = process.cwd(), options?: DevOptions): Pro
|
|
|
104
104
|
projectDir,
|
|
105
105
|
),
|
|
106
106
|
);
|
|
107
|
+
await stampMacSdk(hostExe);
|
|
107
108
|
await reporter.build(
|
|
108
109
|
$`bun build ${mainEntry} --target=bun --outfile ${workerJs}`.cwd(projectDir),
|
|
109
110
|
);
|
|
@@ -246,6 +247,10 @@ export async function dev(projectDir = process.cwd(), options?: DevOptions): Pro
|
|
|
246
247
|
id: bundleId,
|
|
247
248
|
windows: config.windows,
|
|
248
249
|
devtools: config.devtools,
|
|
250
|
+
// Dev: resources resolve straight to their source directories.
|
|
251
|
+
resources: Object.fromEntries(
|
|
252
|
+
normalizeResources(projectDir, config.resources).map((r) => [r.name, r.src]),
|
|
253
|
+
),
|
|
249
254
|
}),
|
|
250
255
|
// dev: true → enables inspect-element AND gives this run its own `-dev`
|
|
251
256
|
// CEF cache dir, so `mirin dev` can run alongside the installed app.
|
package/src/extras.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* place so the two CLI paths stay in sync.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { mkdirSync } from "node:fs";
|
|
8
|
+
import { mkdirSync, statSync } from "node:fs";
|
|
9
9
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
10
10
|
import { $ } from "bun";
|
|
11
11
|
import { createReporter, type Reporter } from "./shared/report.ts";
|
|
@@ -13,6 +13,13 @@ import { createReporter, type Reporter } from "./shared/report.ts";
|
|
|
13
13
|
/** Config shapes (mirrors packages/mirin/src/config/index.ts; CLI can't import the runtime). */
|
|
14
14
|
export type SidecarConfig = Record<string, string | { bin: string; entitlements?: string[] }>;
|
|
15
15
|
export type WorkersConfig = Record<string, string>;
|
|
16
|
+
export type ResourcesConfig = Record<string, string>;
|
|
17
|
+
|
|
18
|
+
/** A resource directory resolved to an absolute source path. */
|
|
19
|
+
export interface NormalizedResource {
|
|
20
|
+
name: string;
|
|
21
|
+
src: string;
|
|
22
|
+
}
|
|
16
23
|
|
|
17
24
|
/** A sidecar resolved to an absolute source path + its requested entitlements. */
|
|
18
25
|
export interface NormalizedSidecar {
|
|
@@ -53,6 +60,25 @@ function projectRelativePath(projectDir: string, file: string, label: string): s
|
|
|
53
60
|
return full;
|
|
54
61
|
}
|
|
55
62
|
|
|
63
|
+
/** Resolve `config.resources` to absolute source directories. */
|
|
64
|
+
export function normalizeResources(
|
|
65
|
+
projectDir: string,
|
|
66
|
+
resources: ResourcesConfig | undefined,
|
|
67
|
+
): NormalizedResource[] {
|
|
68
|
+
return Object.entries(resources ?? {}).map(([name, dir]) => {
|
|
69
|
+
const safeName = safeExtraAssetName(name, "resource name");
|
|
70
|
+
const src = projectRelativePath(projectDir, dir, `resource "${safeName}" path`);
|
|
71
|
+
let isDirectory = false;
|
|
72
|
+
try {
|
|
73
|
+
isDirectory = statSync(src).isDirectory();
|
|
74
|
+
} catch {
|
|
75
|
+
isDirectory = false;
|
|
76
|
+
}
|
|
77
|
+
if (!isDirectory) throw new Error(`[mirin] resource "${safeName}" is not a directory: ${dir}`);
|
|
78
|
+
return { name: safeName, src };
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
56
82
|
/** Resolve `config.sidecars` to absolute source paths. */
|
|
57
83
|
export function normalizeSidecars(
|
|
58
84
|
projectDir: string,
|