@mirinjs/cli 0.1.0-alpha.25 → 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 +10 -3
- package/src/bundle/linux/app.ts +5 -0
- package/src/bundle/macos/app.ts +16 -0
- package/src/bundle/macos/macho.ts +42 -0
- package/src/bundle/windows/app.ts +5 -0
- package/src/dev.ts +5 -1
- 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
|
*/
|
|
@@ -21,7 +23,7 @@ import { buildLinuxBundle } from "./bundle/linux/index.ts";
|
|
|
21
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,
|
|
@@ -256,9 +260,12 @@ export async function build(
|
|
|
256
260
|
singleInstance: config.singleInstance,
|
|
257
261
|
// Lets a diagnostic build opt in via `devtools.production`; otherwise fully off.
|
|
258
262
|
devtools: config.devtools,
|
|
263
|
+
// Production: names resolve under the bundle resources directory.
|
|
264
|
+
resources: Object.fromEntries(extraResources.map((r) => [r.name, r.name])),
|
|
259
265
|
}),
|
|
260
266
|
versionJson,
|
|
261
267
|
workers: extraWorkers,
|
|
268
|
+
extra: extraResources,
|
|
262
269
|
};
|
|
263
270
|
const { app } = IS_WINDOWS
|
|
264
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
|
+
}
|
|
@@ -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
|
@@ -29,7 +29,7 @@ import { buildWindowsBundle } from "./bundle/windows/index.ts";
|
|
|
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";
|
|
@@ -247,6 +247,10 @@ export async function dev(projectDir = process.cwd(), options?: DevOptions): Pro
|
|
|
247
247
|
id: bundleId,
|
|
248
248
|
windows: config.windows,
|
|
249
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
|
+
),
|
|
250
254
|
}),
|
|
251
255
|
// dev: true → enables inspect-element AND gives this run its own `-dev`
|
|
252
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,
|