@mirinjs/cli 0.0.1-alpha.0 → 0.0.1-alpha.10
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 +3 -3
- package/src/artifacts.ts +10 -6
- package/src/build.ts +3 -0
- package/src/bundle.ts +69 -19
- package/src/dev.ts +5 -0
- package/src/temps.ts +24 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mirinjs/cli",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.10",
|
|
4
4
|
"description": "CLI for mirin apps: dev, build, init.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"bun": ">=1.2.0"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"create-mirinjs": "0.0.1-alpha.
|
|
30
|
+
"create-mirinjs": "0.0.1-alpha.10"
|
|
31
31
|
},
|
|
32
32
|
"optionalDependencies": {
|
|
33
|
-
"@mirinjs/darwin-arm64": "0.0.1-alpha.
|
|
33
|
+
"@mirinjs/darwin-arm64": "0.0.1-alpha.10"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|
package/src/artifacts.ts
CHANGED
|
@@ -118,13 +118,17 @@ async function ensureCef(): Promise<string> {
|
|
|
118
118
|
const url = `https://github.com/${REPO_SLUG}/releases/download/v${version}/${asset}`;
|
|
119
119
|
console.log(`[mirin] downloading CEF for ${platformTag()} (one-time, ~hundreds of MB)…`);
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
121
|
+
// Download with curl, not `Bun.write(file, await fetch(url))`: the latter
|
|
122
|
+
// pins a core at 100% CPU and never completes on large gzip responses
|
|
123
|
+
// (the streaming write path). curl is fast and ubiquitous.
|
|
125
124
|
const archive = join(tmpdir(), asset);
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
try {
|
|
126
|
+
await $`curl -fSL --retry 3 -o ${archive} ${url}`;
|
|
127
|
+
await $`tar -xzf ${archive} -C ${cacheDir}`;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
rmSync(archive, { force: true });
|
|
130
|
+
throw new Error(`mirin: failed to download CEF from ${url} (${err}).`);
|
|
131
|
+
}
|
|
128
132
|
rmSync(archive, { force: true });
|
|
129
133
|
return cacheDir;
|
|
130
134
|
}
|
package/src/build.ts
CHANGED
|
@@ -20,12 +20,14 @@ import { mkdirSync, rmSync } from "node:fs";
|
|
|
20
20
|
import { join } from "node:path";
|
|
21
21
|
import { buildAppBundle } from "./bundle.ts";
|
|
22
22
|
import { resolveArtifacts } from "./artifacts.ts";
|
|
23
|
+
import { sweepBuildTemps } from "./temps.ts";
|
|
23
24
|
|
|
24
25
|
export async function build(projectDir = process.cwd()): Promise<number> {
|
|
25
26
|
const outDir = join(projectDir, "build");
|
|
26
27
|
const work = join(projectDir, ".mirin");
|
|
27
28
|
mkdirSync(outDir, { recursive: true });
|
|
28
29
|
mkdirSync(work, { recursive: true });
|
|
30
|
+
sweepBuildTemps(projectDir);
|
|
29
31
|
|
|
30
32
|
const config = (await import(join(projectDir, "mirin.config.ts"))).default;
|
|
31
33
|
const appName: string = config.name ?? "Mirin App";
|
|
@@ -59,6 +61,7 @@ export async function build(projectDir = process.cwd()): Promise<number> {
|
|
|
59
61
|
coreDylib: artifacts.coreDylib,
|
|
60
62
|
helperBin: artifacts.helperBin,
|
|
61
63
|
cefPath: artifacts.cefPath,
|
|
64
|
+
icon: config.icon ? join(projectDir, config.icon) : undefined,
|
|
62
65
|
signIdentity: process.env.MIRIN_SIGN_IDENTITY,
|
|
63
66
|
resources: {
|
|
64
67
|
uiDir: join(projectDir, "dist"),
|
package/src/bundle.ts
CHANGED
|
@@ -33,6 +33,8 @@ export interface BundleOptions {
|
|
|
33
33
|
coreDylib: string; // libmirin_core.dylib
|
|
34
34
|
helperBin: string; // compiled mirin-helper binary
|
|
35
35
|
cefPath: string; // dir containing the CEF framework (vendor/cef)
|
|
36
|
+
/** App icon source: a .icns, a .iconset dir, or a square .png. Optional. */
|
|
37
|
+
icon?: string;
|
|
36
38
|
/** Codesign identity; "-" (default) is ad-hoc. Set to a Developer ID to ship. */
|
|
37
39
|
signIdentity?: string;
|
|
38
40
|
/** Production-only resources placed under Contents/Resources. */
|
|
@@ -43,6 +45,50 @@ export interface BundleOptions {
|
|
|
43
45
|
};
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
/** The 10 standard iconset renditions (point size + @1x/@2x pixel size). */
|
|
49
|
+
const ICONSET_RENDITIONS = [
|
|
50
|
+
{ name: "icon_16x16.png", px: 16 },
|
|
51
|
+
{ name: "icon_16x16@2x.png", px: 32 },
|
|
52
|
+
{ name: "icon_32x32.png", px: 32 },
|
|
53
|
+
{ name: "icon_32x32@2x.png", px: 64 },
|
|
54
|
+
{ name: "icon_128x128.png", px: 128 },
|
|
55
|
+
{ name: "icon_128x128@2x.png", px: 256 },
|
|
56
|
+
{ name: "icon_256x256.png", px: 256 },
|
|
57
|
+
{ name: "icon_256x256@2x.png", px: 512 },
|
|
58
|
+
{ name: "icon_512x512.png", px: 512 },
|
|
59
|
+
{ name: "icon_512x512@2x.png", px: 1024 },
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Render the app icon to `Resources/icon.icns` and return the CFBundleIconFile
|
|
64
|
+
* stem ("icon"), or undefined if there's no usable source. Accepts a `.icns`
|
|
65
|
+
* (copied), a `.iconset` directory (iconutil), or a square `.png` (rendered to
|
|
66
|
+
* a full iconset via sips, then iconutil).
|
|
67
|
+
*/
|
|
68
|
+
async function writeIcon(iconSrc: string, resources: string): Promise<string | undefined> {
|
|
69
|
+
if (!existsSync(iconSrc)) {
|
|
70
|
+
console.warn(`[mirin] icon not found, skipping: ${iconSrc}`);
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const icns = join(resources, "icon.icns");
|
|
74
|
+
|
|
75
|
+
if (iconSrc.endsWith(".icns")) {
|
|
76
|
+
cpSync(iconSrc, icns);
|
|
77
|
+
} else if (iconSrc.endsWith(".iconset")) {
|
|
78
|
+
await $`iconutil -c icns ${iconSrc} -o ${icns}`.quiet();
|
|
79
|
+
} else {
|
|
80
|
+
const iconset = join(resources, "icon.iconset");
|
|
81
|
+
rmSync(iconset, { recursive: true, force: true });
|
|
82
|
+
mkdirSync(iconset, { recursive: true });
|
|
83
|
+
for (const { name, px } of ICONSET_RENDITIONS) {
|
|
84
|
+
await $`sips -z ${px} ${px} ${iconSrc} --out ${join(iconset, name)}`.quiet();
|
|
85
|
+
}
|
|
86
|
+
await $`iconutil -c icns ${iconset} -o ${icns}`.quiet();
|
|
87
|
+
rmSync(iconset, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
return "icon";
|
|
90
|
+
}
|
|
91
|
+
|
|
46
92
|
type PlistValue = string | boolean | Record<string, string>;
|
|
47
93
|
|
|
48
94
|
function plist(entries: Record<string, PlistValue>): string {
|
|
@@ -87,25 +133,29 @@ export async function buildAppBundle(opts: BundleOptions): Promise<{ app: string
|
|
|
87
133
|
cpSync(opts.hostExe, join(macos, appName));
|
|
88
134
|
cpSync(opts.coreDylib, join(macos, "libmirin_core.dylib"));
|
|
89
135
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
136
|
+
// Render the icon (if any) into Resources before writing the plist, so we
|
|
137
|
+
// only set CFBundleIconFile when an icon was actually produced.
|
|
138
|
+
const iconFile = opts.icon ? await writeIcon(opts.icon, join(contents, "Resources")) : undefined;
|
|
139
|
+
|
|
140
|
+
const info: Record<string, PlistValue> = {
|
|
141
|
+
CFBundleDevelopmentRegion: "en",
|
|
142
|
+
CFBundleDisplayName: appName,
|
|
143
|
+
CFBundleExecutable: appName,
|
|
144
|
+
CFBundleIdentifier: bundleId,
|
|
145
|
+
CFBundleInfoDictionaryVersion: "6.0",
|
|
146
|
+
CFBundleName: appName,
|
|
147
|
+
CFBundlePackageType: "APPL",
|
|
148
|
+
CFBundleShortVersionString: "0.0.1",
|
|
149
|
+
CFBundleVersion: "0.0.1",
|
|
150
|
+
LSMinimumSystemVersion: "13.0",
|
|
151
|
+
NSHighResolutionCapable: true,
|
|
152
|
+
NSSupportsAutomaticGraphicsSwitching: true,
|
|
153
|
+
LSFileQuarantineEnabled: true,
|
|
154
|
+
LSEnvironment: { MallocNanoZone: "0" },
|
|
155
|
+
};
|
|
156
|
+
if (iconFile) info.CFBundleIconFile = iconFile;
|
|
157
|
+
|
|
158
|
+
writeFileSync(join(contents, "Info.plist"), plist(info));
|
|
109
159
|
|
|
110
160
|
cpSync(join(cefPath, FRAMEWORK), join(frameworks, FRAMEWORK), {
|
|
111
161
|
recursive: true,
|
package/src/dev.ts
CHANGED
|
@@ -13,12 +13,16 @@ import { mkdirSync } from "node:fs";
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { buildAppBundle } from "./bundle.ts";
|
|
15
15
|
import { resolveArtifacts } from "./artifacts.ts";
|
|
16
|
+
import { sweepBuildTemps } from "./temps.ts";
|
|
16
17
|
|
|
17
18
|
const DEV_URL = "http://localhost:5173";
|
|
18
19
|
|
|
19
20
|
export async function dev(projectDir = process.cwd()): Promise<number> {
|
|
20
21
|
const work = join(projectDir, ".mirin");
|
|
21
22
|
mkdirSync(work, { recursive: true });
|
|
23
|
+
// `bun build --compile` drops temp *.bun-build files in the cwd; clear any left
|
|
24
|
+
// behind by a previously interrupted run so they don't pile up in the project.
|
|
25
|
+
sweepBuildTemps(projectDir);
|
|
22
26
|
|
|
23
27
|
// --- load the manifest ---
|
|
24
28
|
const config = (await import(join(projectDir, "mirin.config.ts"))).default;
|
|
@@ -46,6 +50,7 @@ export async function dev(projectDir = process.cwd()): Promise<number> {
|
|
|
46
50
|
coreDylib: artifacts.coreDylib,
|
|
47
51
|
helperBin: artifacts.helperBin,
|
|
48
52
|
cefPath: artifacts.cefPath,
|
|
53
|
+
icon: config.icon ? join(projectDir, config.icon) : undefined,
|
|
49
54
|
});
|
|
50
55
|
|
|
51
56
|
// --- start Vite ---
|
package/src/temps.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove `bun build --compile` scratch files (`.*.bun-build`) from a directory.
|
|
3
|
+
*
|
|
4
|
+
* `bun build --compile` writes these temp files into the working directory and
|
|
5
|
+
* normally cleans them up — but a hard-killed build leaves them behind, where
|
|
6
|
+
* they accumulate in the project root. The dev/build commands sweep them first.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readdirSync, rmSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
export function sweepBuildTemps(dir: string): void {
|
|
13
|
+
let entries: string[];
|
|
14
|
+
try {
|
|
15
|
+
entries = readdirSync(dir);
|
|
16
|
+
} catch {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (const name of entries) {
|
|
20
|
+
if (name.endsWith(".bun-build")) {
|
|
21
|
+
rmSync(join(dir, name), { force: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|