@mirinjs/cli 0.1.0-alpha.22 → 0.1.0-alpha.24
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 +2 -1
- package/src/bundle/macos/app.ts +18 -5
- package/src/dev.ts +2 -1
- package/src/icons/macos/appearance.ts +46 -33
- package/src/icons/sources.ts +13 -0
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.24",
|
|
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.24",
|
|
31
|
+
"mirinjs": "0.1.0-alpha.24"
|
|
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.24",
|
|
35
|
+
"@mirinjs/win32-x64": "0.1.0-alpha.24",
|
|
36
|
+
"@mirinjs/win32-arm64": "0.1.0-alpha.24",
|
|
37
|
+
"@mirinjs/linux-arm64": "0.1.0-alpha.24",
|
|
38
|
+
"@mirinjs/linux-x64": "0.1.0-alpha.24"
|
|
39
39
|
},
|
|
40
40
|
"publishConfig": {
|
|
41
41
|
"access": "public"
|
package/src/build.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { buildAppBundle } from "./bundle/macos/index.ts";
|
|
|
22
22
|
import { normalizeCefLocales } from "./bundle/shared/cef-locales.ts";
|
|
23
23
|
import { buildWindowsBundle } from "./bundle/windows/index.ts";
|
|
24
24
|
import { compileWorkers, normalizeSidecars } from "./extras.ts";
|
|
25
|
-
import { resolveIconSource } from "./icons/sources.ts";
|
|
25
|
+
import { resolveIconFallback, resolveIconSource } from "./icons/sources.ts";
|
|
26
26
|
import { makeWindowsIcon } from "./icons/windows/index.ts";
|
|
27
27
|
import { buildLinuxPackages, resolveLinuxFormats } from "./package/linux/index.ts";
|
|
28
28
|
import { sweepBuildTemps } from "./temps.ts";
|
|
@@ -297,6 +297,7 @@ export async function build(
|
|
|
297
297
|
cefLocales,
|
|
298
298
|
version,
|
|
299
299
|
icon: appIconPath,
|
|
300
|
+
iconFallback: resolveIconFallback(config.icon, projectDir),
|
|
300
301
|
signIdentity,
|
|
301
302
|
urlSchemes: config.urlSchemes,
|
|
302
303
|
resources: { ...resources, sidecars },
|
package/src/bundle/macos/app.ts
CHANGED
|
@@ -52,6 +52,8 @@ export interface BundleOptions {
|
|
|
52
52
|
* .icon document (the only form carrying appearance variants). Optional.
|
|
53
53
|
*/
|
|
54
54
|
icon?: string;
|
|
55
|
+
/** `.icns`-capable source used when an `.icon` document can't be compiled. */
|
|
56
|
+
iconFallback?: string;
|
|
55
57
|
/** Codesign identity; "-" (default) is ad-hoc. Set to a Developer ID to ship. */
|
|
56
58
|
signIdentity?: string;
|
|
57
59
|
/** Custom URL schemes -> Info.plist CFBundleURLTypes (deep links). */
|
|
@@ -144,7 +146,12 @@ interface IconKeys {
|
|
|
144
146
|
* `Assets.car` (macOS 26+ light/dark/tinted) and contributes the `.icns` actool
|
|
145
147
|
* derives from it; every other source takes the plain iconutil path.
|
|
146
148
|
*/
|
|
147
|
-
async function writeIcon(
|
|
149
|
+
async function writeIcon(
|
|
150
|
+
iconSrc: string,
|
|
151
|
+
resources: string,
|
|
152
|
+
work: string,
|
|
153
|
+
fallback?: string,
|
|
154
|
+
): Promise<IconKeys> {
|
|
148
155
|
if (!existsSync(iconSrc)) {
|
|
149
156
|
console.warn(`[mirin] icon not found, skipping: ${iconSrc}`);
|
|
150
157
|
return {};
|
|
@@ -152,10 +159,16 @@ async function writeIcon(iconSrc: string, resources: string, work: string): Prom
|
|
|
152
159
|
|
|
153
160
|
const icns = join(resources, "icon.icns");
|
|
154
161
|
if (isIconComposerDoc(iconSrc)) {
|
|
155
|
-
// An Icon Composer document can't go through iconutil, so the
|
|
156
|
-
//
|
|
162
|
+
// An Icon Composer document can't go through iconutil, so the .icns comes
|
|
163
|
+
// from actool — or, when no installed Xcode can compile the document, from
|
|
164
|
+
// the fallback source. A bundle must never ship iconless.
|
|
157
165
|
const catalog = await writeAppearanceCatalog(iconSrc, resources, work);
|
|
158
|
-
if (catalog?.icns)
|
|
166
|
+
if (catalog?.icns) {
|
|
167
|
+
cpSync(catalog.icns, icns);
|
|
168
|
+
} else if (fallback && existsSync(fallback)) {
|
|
169
|
+
console.warn(`[mirin] falling back to ${fallback} for the macOS icon.`);
|
|
170
|
+
await writeIcns(fallback, icns, resources);
|
|
171
|
+
}
|
|
159
172
|
return { iconFile: existsSync(icns) ? "icon" : undefined, iconName: catalog?.name };
|
|
160
173
|
}
|
|
161
174
|
|
|
@@ -215,7 +228,7 @@ export async function buildAppBundle(opts: BundleOptions): Promise<{ app: string
|
|
|
215
228
|
// only set the icon keys when an icon was actually produced.
|
|
216
229
|
const iconWork = join(opts.outDir, ".mirin-icon");
|
|
217
230
|
const { iconFile, iconName } = opts.icon
|
|
218
|
-
? await writeIcon(opts.icon, join(contents, "Resources"), iconWork)
|
|
231
|
+
? await writeIcon(opts.icon, join(contents, "Resources"), iconWork, opts.iconFallback)
|
|
219
232
|
: ({} as IconKeys);
|
|
220
233
|
rmSync(iconWork, { recursive: true, force: true });
|
|
221
234
|
const version = opts.version ?? "0.0.1";
|
package/src/dev.ts
CHANGED
|
@@ -30,7 +30,7 @@ const IS_WINDOWS = process.platform === "win32";
|
|
|
30
30
|
const IS_LINUX = process.platform === "linux";
|
|
31
31
|
|
|
32
32
|
import { compileWorkers, normalizeSidecars } from "./extras.ts";
|
|
33
|
-
import { resolveIconSource } from "./icons/sources.ts";
|
|
33
|
+
import { resolveIconFallback, resolveIconSource } from "./icons/sources.ts";
|
|
34
34
|
import { createReporter } from "./shared/report.ts";
|
|
35
35
|
import { DevSession } from "./shared/session.ts";
|
|
36
36
|
import { sweepBuildTemps } from "./temps.ts";
|
|
@@ -155,6 +155,7 @@ export async function dev(projectDir = process.cwd(), options?: DevOptions): Pro
|
|
|
155
155
|
helperBin: artifacts.helperBin,
|
|
156
156
|
cefPath: artifacts.cefPath,
|
|
157
157
|
icon: iconSrc,
|
|
158
|
+
iconFallback: resolveIconFallback(config.icon, projectDir),
|
|
158
159
|
urlSchemes: config.urlSchemes,
|
|
159
160
|
});
|
|
160
161
|
bundlePhase?.ok();
|
|
@@ -21,21 +21,27 @@ export const isIconComposerDoc = (path: string): boolean => path.endsWith(".icon
|
|
|
21
21
|
const ICON_NAME = "AppIcon";
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* Every actool on this machine, newest Xcode first. `xcrun` alone is not
|
|
25
|
+
* enough: it honours the selected developer dir, which is often Command Line
|
|
26
|
+
* Tools (no actool) or an Xcode too old for Icon Composer documents.
|
|
27
27
|
*/
|
|
28
|
-
async function
|
|
28
|
+
async function findActools(): Promise<string[]> {
|
|
29
|
+
const found: string[] = [];
|
|
30
|
+
const push = (path: string) => {
|
|
31
|
+
if (existsSync(path) && !found.includes(path)) found.push(path);
|
|
32
|
+
};
|
|
33
|
+
// CI images install versioned Xcodes (Xcode_26.1.app, …); newest first, since
|
|
34
|
+
// only recent actools can compile an Icon Composer document.
|
|
35
|
+
const apps = (await $`ls -d /Applications/Xcode*.app`.nothrow().quiet()).stdout
|
|
36
|
+
.toString()
|
|
37
|
+
.split("\n")
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.sort()
|
|
40
|
+
.reverse();
|
|
41
|
+
for (const app of apps) push(join(app.trim(), "Contents/Developer/usr/bin/actool"));
|
|
29
42
|
const xcrun = await $`xcrun --find actool`.nothrow().quiet();
|
|
30
|
-
if (xcrun.exitCode === 0)
|
|
31
|
-
|
|
32
|
-
if (path && existsSync(path)) return path;
|
|
33
|
-
}
|
|
34
|
-
for (const app of ["/Applications/Xcode.app", "/Applications/Xcode-beta.app"]) {
|
|
35
|
-
const path = join(app, "Contents/Developer/usr/bin/actool");
|
|
36
|
-
if (existsSync(path)) return path;
|
|
37
|
-
}
|
|
38
|
-
return undefined;
|
|
43
|
+
if (xcrun.exitCode === 0) push(xcrun.stdout.toString().trim());
|
|
44
|
+
return found;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
export interface AppearanceCatalog {
|
|
@@ -59,11 +65,11 @@ export async function writeAppearanceCatalog(
|
|
|
59
65
|
// what the caller passed and of the subprocess working directory.
|
|
60
66
|
const resources = resolve(resourcesDir);
|
|
61
67
|
const work = resolve(workDir);
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
68
|
+
const actools = await findActools();
|
|
69
|
+
if (actools.length === 0) {
|
|
64
70
|
console.warn(
|
|
65
71
|
"[mirin] actool not found — appearance variants need a full Xcode " +
|
|
66
|
-
"(Command Line Tools alone is not enough)
|
|
72
|
+
"(Command Line Tools alone is not enough).",
|
|
67
73
|
);
|
|
68
74
|
return undefined;
|
|
69
75
|
}
|
|
@@ -74,23 +80,30 @@ export async function writeAppearanceCatalog(
|
|
|
74
80
|
const doc = join(work, `${ICON_NAME}.icon`);
|
|
75
81
|
cpSync(iconDoc, doc, { recursive: true });
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
// Old actools exit 0 for an .icon document while emitting no catalog (they
|
|
84
|
+
// warn that the platform can't target macOS 26), so success is judged by the
|
|
85
|
+
// artifact, not the exit code — and every installed Xcode gets a turn.
|
|
86
|
+
const car = join(work, "compiled", "Assets.car");
|
|
87
|
+
let lastOutput = "";
|
|
88
|
+
for (const actool of actools) {
|
|
89
|
+
const compiled = join(work, "compiled");
|
|
90
|
+
rmSync(compiled, { recursive: true, force: true });
|
|
91
|
+
mkdirSync(compiled, { recursive: true });
|
|
92
|
+
const res =
|
|
93
|
+
await $`${actool} --output-format human-readable-text --notices --warnings --app-icon ${ICON_NAME} --output-partial-info-plist ${join(work, "partial.plist")} --enable-on-demand-resources NO --target-device mac --minimum-deployment-target 26.0 --platform macosx --compile ${compiled} ${doc}`
|
|
94
|
+
.nothrow()
|
|
95
|
+
.quiet();
|
|
96
|
+
lastOutput = res.stdout.toString().trim();
|
|
97
|
+
if (existsSync(car)) {
|
|
98
|
+
cpSync(car, join(resources, "Assets.car"));
|
|
99
|
+
const icns = join(compiled, `${ICON_NAME}.icns`);
|
|
100
|
+
return { name: ICON_NAME, icns: existsSync(icns) ? icns : undefined };
|
|
101
|
+
}
|
|
91
102
|
}
|
|
92
103
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
104
|
+
console.warn(
|
|
105
|
+
`[mirin] no installed Xcode could compile the .icon document (tried ${actools.length}).\n` +
|
|
106
|
+
lastOutput,
|
|
107
|
+
);
|
|
108
|
+
return undefined;
|
|
96
109
|
}
|
package/src/icons/sources.ts
CHANGED
|
@@ -23,3 +23,16 @@ export function resolveIconSource(
|
|
|
23
23
|
const picked = typeof icon === "string" ? icon : (icon[platform] ?? icon.default);
|
|
24
24
|
return picked ? join(projectDir, picked) : undefined;
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The `.icns`-capable fallback behind a macOS `.icon` document: the `default`
|
|
29
|
+
* entry when per-platform sources are configured. A failed catalog compile
|
|
30
|
+
* must degrade to a plain icon, never to an iconless bundle.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveIconFallback(
|
|
33
|
+
icon: string | IconSources | undefined,
|
|
34
|
+
projectDir: string,
|
|
35
|
+
): string | undefined {
|
|
36
|
+
if (!icon || typeof icon === "string") return undefined;
|
|
37
|
+
return icon.default ? join(projectDir, icon.default) : undefined;
|
|
38
|
+
}
|