muon-ui 0.8.0 → 0.9.0

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.
Files changed (39) hide show
  1. package/README.md +9 -13
  2. package/dist/cli.cjs +48 -32
  3. package/dist/cli.cjs.map +1 -1
  4. package/dist/index.cjs +2 -2
  5. package/dist/index.mjs +2 -2
  6. package/dist/native/linux-amd64/muon-bootstrap +0 -0
  7. package/dist/native/linux-amd64/muon-builder +0 -0
  8. package/dist/native/linux-arm64/muon-bootstrap +0 -0
  9. package/dist/native/linux-arm64/muon-builder +0 -0
  10. package/dist/native/linux-armhf/muon-bootstrap +0 -0
  11. package/dist/native/linux-armhf/muon-builder +0 -0
  12. package/dist/native/muon-256.png +0 -0
  13. package/dist/native/windows-amd64/muon-bootstrap.exe +0 -0
  14. package/dist/native/windows-amd64/muon-builder.exe +0 -0
  15. package/dist/native/windows-i686/muon-bootstrap.exe +0 -0
  16. package/dist/native/windows-i686/muon-builder.exe +0 -0
  17. package/dist/runtime/linux-amd64/libmuon-ui.so +0 -0
  18. package/dist/runtime/linux-amd64/muon-core +0 -0
  19. package/dist/runtime/linux-arm64/libmuon-ui.so +0 -0
  20. package/dist/runtime/linux-arm64/muon-core +0 -0
  21. package/dist/runtime/linux-armhf/libmuon-ui.so +0 -0
  22. package/dist/runtime/linux-armhf/muon-core +0 -0
  23. package/dist/runtime/windows-amd64/libcardio.dll +0 -0
  24. package/dist/runtime/windows-amd64/libmuon-ui.dll +0 -0
  25. package/dist/runtime/windows-amd64/muon-core.exe +0 -0
  26. package/dist/runtime/windows-i686/libcardio.dll +0 -0
  27. package/dist/runtime/windows-i686/libmuon-ui.dll +0 -0
  28. package/dist/runtime/windows-i686/muon-core.exe +0 -0
  29. package/dist/{vite-internals-ChWiL2TL.cjs → vite-internals-HXgH7b5M.cjs} +291 -75
  30. package/dist/vite-internals-HXgH7b5M.cjs.map +1 -0
  31. package/dist/vite.cjs +446 -6
  32. package/dist/vite.cjs.map +1 -1
  33. package/dist/vite.mjs +748 -115
  34. package/dist/vite.mjs.map +1 -1
  35. package/muon.d.ts +12 -0
  36. package/package.json +7 -7
  37. package/vite.d.ts +118 -8
  38. package/dist/native/muon-bootstrap.png +0 -0
  39. package/dist/vite-internals-ChWiL2TL.cjs.map +0 -1
package/dist/vite.mjs CHANGED
@@ -1,14 +1,14 @@
1
1
  /*!
2
2
  * name: muon-ui
3
- * version: 0.8.0
3
+ * version: 0.9.0
4
4
  * description: A multi-platform GUI application framework that uses CEF as its backend
5
5
  * author: Kouji Matsui (@kekyo@mi.kekyo.net)
6
6
  * license: MIT
7
7
  * repository.url: https://github.com/kekyo/muon-ui.git
8
- * git.commit.hash: 9bb6148e155dab16c4ca441073b144e21981b038
8
+ * git.commit.hash: 3437b522a51d8772713b72722b109c5ff30fc711
9
9
  */
10
10
  import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
11
- import { constants, writeFileSync } from "node:fs";
11
+ import { constants, existsSync, readFileSync, writeFileSync } from "node:fs";
12
12
  import { access, chmod, copyFile, cp, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
13
13
  import { createHash, randomBytes } from "node:crypto";
14
14
  import { tmpdir } from "node:os";
@@ -1293,8 +1293,10 @@ var encodeTaggedBytes = (tag, bytes) => Buffer.concat([
1293
1293
  encodeVarUint(BigInt(bytes.length)),
1294
1294
  Buffer.from(bytes)
1295
1295
  ]);
1296
- var isJsonObject$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1296
+ var isJsonObject$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1297
1297
  var isPath = (path, first, second) => path.length === 2 && path[0] === first && path[1] === second;
1298
+ var isPluginSignaturePath = (path) => path.length === 3 && path[0] === "plugin" && path[1] === "plugins" && path[2] === "signature";
1299
+ var isPluginSaltPath = (path) => path.length === 3 && path[0] === "plugin" && path[1] === "plugins" && path[2] === "salt";
1298
1300
  var isHexString = (value) => value.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(value);
1299
1301
  var decodeHexString = (value) => {
1300
1302
  const bytes = Buffer.alloc(value.length / 2);
@@ -1304,6 +1306,8 @@ var decodeHexString = (value) => {
1304
1306
  var encodeKnownBinaryString = (path, value) => {
1305
1307
  if (isPath(path, "asset", "signature")) return value.length === 40 && isHexString(value) ? decodeHexString(value) : void 0;
1306
1308
  if (isPath(path, "asset", "salt")) return isHexString(value) ? decodeHexString(value) : void 0;
1309
+ if (isPluginSignaturePath(path)) return value.length === 40 && isHexString(value) ? decodeHexString(value) : void 0;
1310
+ if (isPluginSaltPath(path)) return isHexString(value) ? decodeHexString(value) : void 0;
1307
1311
  if (isPath(path, "browser", "backgroundColor")) {
1308
1312
  const hex = value.startsWith("#") ? value.slice(1) : value;
1309
1313
  return hex.length === 6 && isHexString(hex) ? decodeHexString(hex) : void 0;
@@ -1326,7 +1330,7 @@ var encodeTlvValue = (value, path) => {
1326
1330
  encodeVarUint(BigInt(value.length)),
1327
1331
  ...value.map((entry) => encodeTlvValue(entry, path))
1328
1332
  ]);
1329
- if (isJsonObject$4(value)) {
1333
+ if (isJsonObject$5(value)) {
1330
1334
  const entries = Object.entries(value);
1331
1335
  return Buffer.concat([
1332
1336
  Buffer.from([tlvObjectTag]),
@@ -1351,8 +1355,8 @@ var encodeMuonConfigTlv = (config) => encodeTlvValue(config, []);
1351
1355
  */
1352
1356
  var createMuonEmbeddedConfigSlot = (payload = void 0) => {
1353
1357
  if (payload !== void 0 && payload.length > muonEmbeddedConfigPayloadCapacity) throw new Error(`Encoded muon config exceeds the embedded slot capacity: ${payload.length} > ${muonEmbeddedConfigPayloadCapacity}`);
1354
- const slot = createEmptySlot();
1355
- if (payload === void 0) return slot;
1358
+ if (payload === void 0) return createEmptySlot();
1359
+ const slot = randomBytes(muonEmbeddedConfigSlotSize);
1356
1360
  Buffer.from(payload).copy(slot, 0);
1357
1361
  return slot;
1358
1362
  };
@@ -1364,9 +1368,12 @@ var createMuonEmbeddedConfigSlot = (payload = void 0) => {
1364
1368
  */
1365
1369
  var createMuonBootstrapEmbeddedConfigSlot = (payload = void 0) => {
1366
1370
  if (payload !== void 0 && payload.length > muonBootstrapEmbeddedConfigPayloadCapacity) throw new Error(`Encoded muon config exceeds the embedded bootstrap slot capacity: ${payload.length} > ${muonBootstrapEmbeddedConfigPayloadCapacity}`);
1367
- const slot = Buffer.alloc(muonBootstrapEmbeddedConfigSlotSize, 0);
1368
- muonBootstrapEmbeddedConfigEmptySlotMarker.copy(slot, 0);
1369
- if (payload === void 0) return slot;
1371
+ if (payload === void 0) {
1372
+ const slot = Buffer.alloc(muonBootstrapEmbeddedConfigSlotSize, 0);
1373
+ muonBootstrapEmbeddedConfigEmptySlotMarker.copy(slot, 0);
1374
+ return slot;
1375
+ }
1376
+ const slot = randomBytes(muonBootstrapEmbeddedConfigSlotSize);
1370
1377
  Buffer.from(payload).copy(slot, 0);
1371
1378
  return slot;
1372
1379
  };
@@ -1440,7 +1447,7 @@ var findMuonBootstrapEmbeddedConfigSlot = (content) => {
1440
1447
  if (candidate === void 0) throw new Error("Embedded muon-bootstrap config slot was not found.");
1441
1448
  return candidate;
1442
1449
  };
1443
- var fileExists$4 = async (path) => {
1450
+ var fileExists$6 = async (path) => {
1444
1451
  try {
1445
1452
  return (await stat(path)).isFile();
1446
1453
  } catch {
@@ -1456,10 +1463,10 @@ var resolveMuonConfigInputPath = async (configPath) => {
1456
1463
  "muon.json"
1457
1464
  ]) {
1458
1465
  const candidate = join(directory, fileName);
1459
- if (await fileExists$4(candidate)) return candidate;
1466
+ if (await fileExists$6(candidate)) return candidate;
1460
1467
  }
1461
1468
  }
1462
- if (!await fileExists$4(configPath)) throw new Error(`muon config does not exist: ${configPath}`);
1469
+ if (!await fileExists$6(configPath)) throw new Error(`muon config does not exist: ${configPath}`);
1463
1470
  return configPath;
1464
1471
  };
1465
1472
  var readMuonConfigInput = async (configPath) => {
@@ -1662,6 +1669,20 @@ Object.fromEntries(allMuonTargets.map((target) => [targetDescriptors[target].cef
1662
1669
  */
1663
1670
  var getMuonTargetDescriptor = (target) => targetDescriptors[target];
1664
1671
  /**
1672
+ * Returns the runtime app identifier embedded for one target distribution.
1673
+ *
1674
+ * @param appId Base sanitized application identifier.
1675
+ * @param target Public Muon target.
1676
+ * @returns Target runtime application identifier.
1677
+ *
1678
+ * @remarks Windows targets append their public architecture name so 32-bit and
1679
+ * 64-bit packages do not share runtime state or installer identities.
1680
+ */
1681
+ var getMuonTargetRuntimeAppId = (appId, target) => {
1682
+ const descriptor = getMuonTargetDescriptor(target);
1683
+ return descriptor.os === "windows" ? `${appId}.${descriptor.arch}` : appId;
1684
+ };
1685
+ /**
1665
1686
  * Normalizes a user supplied public Muon target.
1666
1687
  *
1667
1688
  * @param target Target value supplied by the user.
@@ -2110,6 +2131,68 @@ var createIco = (images) => {
2110
2131
  return Buffer.concat([header, ...imageData]);
2111
2132
  };
2112
2133
  //#endregion
2134
+ //#region src/app-icon.ts
2135
+ /**
2136
+ * ZIP entry used for the generated static application icon asset.
2137
+ */
2138
+ var appIconAssetEntryName = "main/.muon/app-icon.png";
2139
+ /**
2140
+ * Runtime asset URL injected as the generated initial title bar icon.
2141
+ */
2142
+ var appIconAssetUrl = "asset://main/.muon/app-icon.png";
2143
+ /**
2144
+ * Creates a static application icon source from API or CLI options.
2145
+ */
2146
+ var createAppIconOptionsSource = (iconPath, directory, label = "iconPath") => {
2147
+ if (iconPath === void 0) return;
2148
+ if (typeof iconPath !== "string") throw new Error(`${label} must be a string when present.`);
2149
+ if (iconPath.trim() === "") return;
2150
+ return {
2151
+ iconPath: iconPath.trim(),
2152
+ directory
2153
+ };
2154
+ };
2155
+ /**
2156
+ * Reads the top-level `iconPath` from a Muon config object.
2157
+ */
2158
+ var readConfigAppIconSource = (config, directory, label) => {
2159
+ const value = config.iconPath;
2160
+ if (value === void 0) return;
2161
+ if (typeof value !== "string") throw new Error(`${label} iconPath must be a string when present.`);
2162
+ return createAppIconOptionsSource(value, directory);
2163
+ };
2164
+ /**
2165
+ * Reads the top-level `iconPath` from project metadata.
2166
+ */
2167
+ var readProjectAppIconSource = (projectJson, directory) => {
2168
+ const value = projectJson.iconPath;
2169
+ return typeof value === "string" ? createAppIconOptionsSource(value, directory) : void 0;
2170
+ };
2171
+ /**
2172
+ * Resolves and validates the first available static application icon source.
2173
+ */
2174
+ var resolveMuonAppIconPath = async (sources) => {
2175
+ for (const source of sources) {
2176
+ const iconPath = resolveResourcePath$2(source.directory, source.iconPath);
2177
+ await assertMuonAppIconPath(iconPath);
2178
+ return iconPath;
2179
+ }
2180
+ };
2181
+ var assertMuonAppIconPath = async (iconPath) => {
2182
+ if (extname(iconPath).toLowerCase() !== ".png") throw new Error(`Muon app icon must be a .png file: ${iconPath}`);
2183
+ if (!await fileExists$5(iconPath)) throw new Error(`Muon app icon does not exist: ${iconPath}`);
2184
+ await createNormalizedIconPngData(await readFile(iconPath), iconPath, "Muon app icon");
2185
+ };
2186
+ var resolveResourcePath$2 = (directory, value) => isAbsolute(value) ? value : resolve(directory, value);
2187
+ var fileExists$5 = async (path) => {
2188
+ try {
2189
+ await access(path, constants.F_OK);
2190
+ return true;
2191
+ } catch {
2192
+ return false;
2193
+ }
2194
+ };
2195
+ //#endregion
2113
2196
  //#region src/windows-resource.ts
2114
2197
  var defaultLanguage = 1033;
2115
2198
  var defaultCodePage = 1200;
@@ -2135,13 +2218,22 @@ var mergeMuonWindowsResourceOptions = (primary, fallback) => {
2135
2218
  * Resolves Windows resource metadata from options, config files, and package metadata.
2136
2219
  */
2137
2220
  var resolveMuonWindowsResource = async (input) => {
2138
- const projectJson = await readProjectJson(input.root);
2139
- const sources = [
2221
+ const projectJson = await readProjectJson$1(input.root);
2222
+ const windowsSources = [
2140
2223
  createOptionsSource$1(input.options, input.root, "windowsResource"),
2141
2224
  readConfigWindowsResourceSource(input.muonConfig, input.muonConfigDirectory, "muon.json"),
2225
+ readConfigWindowsResourceSource(projectJson, input.root, "project.json")
2226
+ ].filter((source) => source !== void 0);
2227
+ const sources = [
2228
+ ...windowsSources,
2142
2229
  createProjectSource(projectJson, input.root),
2143
2230
  createPackageSource(input.packageJson, input.root)
2144
2231
  ].filter((source) => source !== void 0);
2232
+ const appIconSources = [
2233
+ createAppIconOptionsSource(input.appIconPath, input.root),
2234
+ readConfigAppIconSource(input.muonConfig, input.muonConfigDirectory, "muon.json"),
2235
+ readProjectAppIconSource(projectJson, input.root)
2236
+ ].filter((source) => source !== void 0);
2145
2237
  const productName = resolveStringField$1(sources, "productName", input.defaults.productName);
2146
2238
  const fileDescription = resolveStringField$1(sources, "fileDescription", input.defaults.fileDescription);
2147
2239
  const companyName = resolveStringField$1(sources, "companyName", input.defaults.companyName);
@@ -2151,7 +2243,7 @@ var resolveMuonWindowsResource = async (input) => {
2151
2243
  const codePage = resolveNumericField(sources, "codePage", defaultCodePage);
2152
2244
  const fixedVersion = normalizeWindowsVersion(version);
2153
2245
  return {
2154
- iconPath: await resolveIconPath$1(sources, await resolveDefaultWindowsIcon(input.packageDirectory)),
2246
+ iconPath: await resolveWindowsIconPath(windowsSources, appIconSources, await resolveDefaultWindowsIcon(input.packageDirectory)),
2155
2247
  productName,
2156
2248
  fileDescription,
2157
2249
  companyName,
@@ -2167,7 +2259,7 @@ var resolveMuonWindowsResource = async (input) => {
2167
2259
  */
2168
2260
  var stripBuildOnlyWindowsResourceConfig = (sourceConfig) => {
2169
2261
  const sourceWindows = sourceConfig.windows;
2170
- if (!isJsonObject$3(sourceWindows)) return sourceConfig;
2262
+ if (!isJsonObject$4(sourceWindows)) return sourceConfig;
2171
2263
  const windowsConfig = {};
2172
2264
  for (const [key, value] of Object.entries(sourceWindows)) if (key !== "resource") windowsConfig[key] = value;
2173
2265
  const output = {};
@@ -2188,15 +2280,24 @@ var isWindowsPeExecutable = async (path) => {
2188
2280
  * Applies resolved resource metadata to a Windows PE executable in place.
2189
2281
  */
2190
2282
  var updateWindowsPeResources = async (input) => {
2283
+ return await updateWindowsPeResourceFile(input, createEngraverUpdate);
2284
+ };
2285
+ /**
2286
+ * Applies only resolved icon metadata to a Windows PE executable in place.
2287
+ */
2288
+ var updateWindowsPeIconResource = async (input) => {
2289
+ return await updateWindowsPeResourceFile(input, createEngraverIconUpdate);
2290
+ };
2291
+ var updateWindowsPeResourceFile = async (input, createUpdate) => {
2191
2292
  if (!await isWindowsPeExecutable(input.executablePath)) return false;
2192
2293
  const mode = (await stat(input.executablePath)).mode & 511;
2193
2294
  const tempDirectory = await mkdtemp(join(tmpdir(), "muon-windows-resource-"));
2194
2295
  const updatesJsonPath = join(tempDirectory, "updates.json");
2195
2296
  const outputPath = join(tempDirectory, "output.exe");
2196
- const iconPath = input.resource.iconPath === void 0 ? void 0 : join(tempDirectory, "icon.ico");
2297
+ const iconPath = join(tempDirectory, "icon.ico");
2197
2298
  try {
2198
- if (input.resource.iconPath !== void 0 && iconPath !== void 0) await createWindowsIconFromPngFile(input.resource.iconPath, iconPath);
2199
- await writeFile(updatesJsonPath, `${JSON.stringify(createEngraverUpdate(input.resource, iconPath), null, 2)}\n`);
2299
+ await createWindowsIconFromPngFile(input.resource.iconPath, iconPath);
2300
+ await writeFile(updatesJsonPath, `${JSON.stringify(createUpdate(input.resource, iconPath), null, 2)}\n`);
2200
2301
  await runMuonPrepareResourceUpdate({
2201
2302
  inputPath: input.executablePath,
2202
2303
  updatesJsonPath,
@@ -2237,21 +2338,23 @@ var createEngraverUpdate = (resource, iconPath) => {
2237
2338
  },
2238
2339
  strings
2239
2340
  },
2240
- icons: iconPath === void 0 ? [] : [{
2241
- id: 1,
2242
- language: resource.language,
2243
- path: iconPath
2244
- }]
2341
+ icons: createEngraverIcons(resource, iconPath)
2245
2342
  };
2246
2343
  };
2247
- var readProjectJson = async (root) => {
2344
+ var createEngraverIconUpdate = (resource, iconPath) => ({ icons: createEngraverIcons(resource, iconPath) });
2345
+ var createEngraverIcons = (resource, iconPath) => [{
2346
+ id: 1,
2347
+ language: resource.language,
2348
+ path: iconPath
2349
+ }];
2350
+ var readProjectJson$1 = async (root) => {
2248
2351
  const projectJsonPath = join(root, "project.json");
2249
- if (!await fileExists$3(projectJsonPath)) return {};
2250
- return await readJsonObjectFile$1(projectJsonPath, "project.json");
2352
+ if (!await fileExists$4(projectJsonPath)) return {};
2353
+ return await readJsonObjectFile$3(projectJsonPath, "project.json");
2251
2354
  };
2252
- var readJsonObjectFile$1 = async (filePath, label) => {
2355
+ var readJsonObjectFile$3 = async (filePath, label) => {
2253
2356
  const parsed = (0, import_dist.parse)(await readFile(filePath, "utf8"));
2254
- if (!isJsonObject$3(parsed)) throw new Error(`${label} must contain a JSON object: ${filePath}`);
2357
+ if (!isJsonObject$4(parsed)) throw new Error(`${label} must contain a JSON object: ${filePath}`);
2255
2358
  return parsed;
2256
2359
  };
2257
2360
  var createOptionsSource$1 = (options, directory, label) => {
@@ -2264,14 +2367,13 @@ var createOptionsSource$1 = (options, directory, label) => {
2264
2367
  var readConfigWindowsResourceSource = (config, directory, label) => {
2265
2368
  const windows = config.windows;
2266
2369
  if (windows === void 0) return;
2267
- if (!isJsonObject$3(windows)) throw new Error(`${label} windows must be an object when present.`);
2370
+ if (!isJsonObject$4(windows)) throw new Error(`${label} windows must be an object when present.`);
2268
2371
  const resource = windows.resource;
2269
2372
  if (resource === void 0) return;
2270
2373
  return createOptionsSource$1(validateWindowsResourceOptions(resource, `${label} windows.resource`), directory, `${label} windows.resource`);
2271
2374
  };
2272
2375
  var createProjectSource = (projectJson, directory) => {
2273
2376
  const topLevel = {};
2274
- copyStringField(projectJson, "iconPath", topLevel, "iconPath");
2275
2377
  copyStringField(projectJson, "productName", topLevel, "productName");
2276
2378
  copyStringField(projectJson, "name", topLevel, "productName");
2277
2379
  copyStringField(projectJson, "fileDescription", topLevel, "fileDescription");
@@ -2306,7 +2408,7 @@ var copyStringField = (source, sourceKey, target, targetKey) => {
2306
2408
  if (typeof value === "string" && value.trim() !== "") target[targetKey] = value.trim();
2307
2409
  };
2308
2410
  var validateWindowsResourceOptions = (value, label) => {
2309
- if (!isJsonObject$3(value)) throw new Error(`${label} must be an object.`);
2411
+ if (!isJsonObject$4(value)) throw new Error(`${label} must be an object.`);
2310
2412
  const output = {};
2311
2413
  copyOptionalStringResourceField(value, "iconPath", output);
2312
2414
  copyOptionalStringResourceField(value, "productName", output);
@@ -2347,33 +2449,35 @@ var resolveNumericField = (sources, field, fallback) => {
2347
2449
  }
2348
2450
  return fallback;
2349
2451
  };
2350
- var resolveIconPath$1 = async (sources, defaultIconPath) => {
2351
- for (const source of sources) {
2452
+ var resolveWindowsIconPath = async (windowsSources, appIconSources, defaultIconPath) => {
2453
+ for (const source of windowsSources) {
2352
2454
  const value = source.options.iconPath;
2353
2455
  if (value !== void 0 && value.trim() !== "") {
2354
2456
  const iconPath = resolveResourcePath$1(source.directory, value);
2355
- await assertIconPath$1(iconPath, true);
2457
+ await assertIconPath$1(iconPath);
2356
2458
  return iconPath;
2357
2459
  }
2358
2460
  }
2359
- if (defaultIconPath !== void 0 && await fileExists$3(defaultIconPath)) {
2360
- await assertIconPath$1(defaultIconPath, false);
2361
- return defaultIconPath;
2362
- }
2461
+ const appIconPath = await resolveMuonAppIconPath(appIconSources);
2462
+ if (appIconPath !== void 0) return appIconPath;
2463
+ await assertIconPath$1(defaultIconPath);
2464
+ return defaultIconPath;
2363
2465
  };
2364
- var assertIconPath$1 = async (iconPath, required) => {
2466
+ var assertIconPath$1 = async (iconPath) => {
2365
2467
  if (extname(iconPath).toLowerCase() !== ".png") throw new Error(`Windows resource icon must be a .png file: ${iconPath}`);
2366
- if (required && !await fileExists$3(iconPath)) throw new Error(`Windows resource icon does not exist: ${iconPath}`);
2367
- if (await fileExists$3(iconPath)) await createNormalizedMuonIconPngData(await readFile(iconPath), iconPath);
2468
+ if (!await fileExists$4(iconPath)) throw new Error(`Windows resource icon does not exist: ${iconPath}`);
2469
+ await createNormalizedMuonIconPngData(await readFile(iconPath), iconPath);
2368
2470
  };
2369
2471
  var resolveDefaultWindowsIcon = async (packageDirectory) => {
2472
+ const sourceIconPath = join(moduleDirectory$3, "..", "..", "images", "muon-256.png");
2370
2473
  const candidates = [
2371
- join(resolve(packageDirectory), "native", "muon-bootstrap.png"),
2372
- join(moduleDirectory$3, "native", "muon-bootstrap.png"),
2373
- join(moduleDirectory$3, "..", "dist", "native", "muon-bootstrap.png"),
2374
- join(moduleDirectory$3, "..", "..", "images", "muon-bootstrap-256.png")
2474
+ join(resolve(packageDirectory), "native", "muon-256.png"),
2475
+ join(moduleDirectory$3, "native", "muon-256.png"),
2476
+ join(moduleDirectory$3, "..", "dist", "native", "muon-256.png"),
2477
+ sourceIconPath
2375
2478
  ];
2376
- for (const candidate of candidates) if (await fileExists$3(candidate)) return candidate;
2479
+ for (const candidate of candidates) if (await fileExists$4(candidate)) return candidate;
2480
+ return sourceIconPath;
2377
2481
  };
2378
2482
  var resolveResourcePath$1 = (directory, path) => {
2379
2483
  return isAbsolute(path) ? path : resolve(directory, path);
@@ -2393,12 +2497,12 @@ var normalizeWindowsVersion = (version) => {
2393
2497
  };
2394
2498
  var stringifyAuthor = (value) => {
2395
2499
  if (typeof value === "string" && value.trim() !== "") return value.trim();
2396
- if (!isJsonObject$3(value) || typeof value.name !== "string") return;
2500
+ if (!isJsonObject$4(value) || typeof value.name !== "string") return;
2397
2501
  const name = value.name.trim();
2398
2502
  if (name === "") return;
2399
2503
  return value.email === void 0 || typeof value.email !== "string" ? name : `${name} <${value.email}>`;
2400
2504
  };
2401
- var fileExists$3 = async (path) => {
2505
+ var fileExists$4 = async (path) => {
2402
2506
  try {
2403
2507
  await access(path, constants.F_OK);
2404
2508
  return true;
@@ -2406,7 +2510,7 @@ var fileExists$3 = async (path) => {
2406
2510
  return false;
2407
2511
  }
2408
2512
  };
2409
- var isJsonObject$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2513
+ var isJsonObject$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2410
2514
  //#endregion
2411
2515
  //#region src/linux-desktop.ts
2412
2516
  var defaultIconFileName = "muon-desktop-icon.png";
@@ -2417,14 +2521,20 @@ var moduleDirectory$2 = typeof __dirname === "string" ? __dirname : dirname(file
2417
2521
  * package metadata.
2418
2522
  */
2419
2523
  var resolveMuonLinuxDesktop = async (input) => {
2524
+ const projectJson = await readProjectJson(input.root);
2420
2525
  const sources = [createOptionsSource(input.options, input.root, "linuxDesktop"), readConfigLinuxDesktopSource(input.muonConfig, input.muonConfigDirectory, "muon.json")].filter((source) => source !== void 0);
2526
+ const appIconSources = [
2527
+ createAppIconOptionsSource(input.appIconPath, input.root),
2528
+ readConfigAppIconSource(input.muonConfig, input.muonConfigDirectory, "muon.json"),
2529
+ readProjectAppIconSource(projectJson, input.root)
2530
+ ].filter((source) => source !== void 0);
2421
2531
  return {
2422
2532
  desktopId: sanitizeDesktopId(resolveStringField(sources, "desktopId", input.defaults.desktopId)),
2423
2533
  name: resolveStringField(sources, "name", input.defaults.name),
2424
2534
  comment: resolveStringField(sources, "comment", input.defaults.comment),
2425
2535
  categories: resolveCategories(sources, input.defaults.categories),
2426
2536
  startupNotify: resolveBooleanField(sources, "startupNotify", input.defaults.startupNotify),
2427
- iconPath: await resolveIconPath(sources, await resolveDefaultLinuxDesktopIcon(input.packageDirectory)),
2537
+ iconPath: await resolveLinuxIconPath(sources, appIconSources, await resolveDefaultLinuxDesktopIcon(input.packageDirectory)),
2428
2538
  iconFileName: defaultIconFileName,
2429
2539
  configFileName: defaultDesktopConfigFileName
2430
2540
  };
@@ -2434,7 +2544,7 @@ var resolveMuonLinuxDesktop = async (input) => {
2434
2544
  */
2435
2545
  var stripBuildOnlyLinuxDesktopConfig = (sourceConfig) => {
2436
2546
  const sourceLinux = sourceConfig.linux;
2437
- if (!isJsonObject$2(sourceLinux)) return sourceConfig;
2547
+ if (!isJsonObject$3(sourceLinux)) return sourceConfig;
2438
2548
  const linuxConfig = {};
2439
2549
  for (const [key, value] of Object.entries(sourceLinux)) if (key !== "desktop") linuxConfig[key] = value;
2440
2550
  const output = {};
@@ -2457,6 +2567,16 @@ var writeLinuxDesktopDistributionFiles = async (outputPath, desktop) => {
2457
2567
  iconFileName: desktop.iconFileName
2458
2568
  }, void 0, 2)}\n`);
2459
2569
  };
2570
+ var readJsonObjectFile$2 = async (filePath, label) => {
2571
+ const parsed = (0, import_dist.parse)(await readFile(filePath, "utf8"));
2572
+ if (!isJsonObject$3(parsed)) throw new Error(`${label} must contain a JSON object: ${filePath}`);
2573
+ return parsed;
2574
+ };
2575
+ var readProjectJson = async (root) => {
2576
+ const projectJsonPath = join(root, "project.json");
2577
+ if (!await fileExists$3(projectJsonPath)) return {};
2578
+ return await readJsonObjectFile$2(projectJsonPath, "project.json");
2579
+ };
2460
2580
  var createOptionsSource = (options, directory, label) => {
2461
2581
  if (options === void 0) return;
2462
2582
  return {
@@ -2467,13 +2587,13 @@ var createOptionsSource = (options, directory, label) => {
2467
2587
  var readConfigLinuxDesktopSource = (config, directory, label) => {
2468
2588
  const linux = config.linux;
2469
2589
  if (linux === void 0) return;
2470
- if (!isJsonObject$2(linux)) throw new Error(`${label} linux must be an object when present.`);
2590
+ if (!isJsonObject$3(linux)) throw new Error(`${label} linux must be an object when present.`);
2471
2591
  const desktop = linux.desktop;
2472
2592
  if (desktop === void 0) return;
2473
2593
  return createOptionsSource(validateLinuxDesktopOptions(desktop, `${label} linux.desktop`), directory, `${label} linux.desktop`);
2474
2594
  };
2475
2595
  var validateLinuxDesktopOptions = (value, label) => {
2476
- if (!isJsonObject$2(value)) throw new Error(`${label} must be an object.`);
2596
+ if (!isJsonObject$3(value)) throw new Error(`${label} must be an object.`);
2477
2597
  const output = {};
2478
2598
  copyOptionalStringDesktopField(value, "desktopId", output);
2479
2599
  copyOptionalStringDesktopField(value, "name", output);
@@ -2519,7 +2639,7 @@ var resolveBooleanField = (sources, field, fallback) => {
2519
2639
  }
2520
2640
  return fallback;
2521
2641
  };
2522
- var resolveIconPath = async (sources, defaultIconPath) => {
2642
+ var resolveLinuxIconPath = async (sources, appIconSources, defaultIconPath) => {
2523
2643
  for (const source of sources) {
2524
2644
  const value = source.options.iconPath;
2525
2645
  if (value !== void 0 && value.trim() !== "") {
@@ -2528,7 +2648,9 @@ var resolveIconPath = async (sources, defaultIconPath) => {
2528
2648
  return iconPath;
2529
2649
  }
2530
2650
  }
2531
- if (defaultIconPath !== void 0 && await fileExists$2(defaultIconPath)) {
2651
+ const appIconPath = await resolveMuonAppIconPath(appIconSources);
2652
+ if (appIconPath !== void 0) return appIconPath;
2653
+ if (defaultIconPath !== void 0 && await fileExists$3(defaultIconPath)) {
2532
2654
  await assertIconPath(defaultIconPath, false);
2533
2655
  return defaultIconPath;
2534
2656
  }
@@ -2536,17 +2658,17 @@ var resolveIconPath = async (sources, defaultIconPath) => {
2536
2658
  };
2537
2659
  var assertIconPath = async (iconPath, required) => {
2538
2660
  if (extname(iconPath).toLowerCase() !== ".png") throw new Error(`Linux desktop icon must be a .png file: ${iconPath}`);
2539
- if (required && !await fileExists$2(iconPath)) throw new Error(`Linux desktop icon does not exist: ${iconPath}`);
2540
- if (await fileExists$2(iconPath)) await createNormalizedIconPngData(await readFile(iconPath), iconPath, "Linux desktop icon");
2661
+ if (required && !await fileExists$3(iconPath)) throw new Error(`Linux desktop icon does not exist: ${iconPath}`);
2662
+ if (await fileExists$3(iconPath)) await createNormalizedIconPngData(await readFile(iconPath), iconPath, "Linux desktop icon");
2541
2663
  };
2542
2664
  var resolveDefaultLinuxDesktopIcon = async (packageDirectory) => {
2543
2665
  const candidates = [
2544
- join(resolve(packageDirectory), "native", "muon-bootstrap.png"),
2545
- join(moduleDirectory$2, "native", "muon-bootstrap.png"),
2546
- join(moduleDirectory$2, "..", "dist", "native", "muon-bootstrap.png"),
2547
- join(moduleDirectory$2, "..", "..", "images", "muon-bootstrap-256.png")
2666
+ join(resolve(packageDirectory), "native", "muon-256.png"),
2667
+ join(moduleDirectory$2, "native", "muon-256.png"),
2668
+ join(moduleDirectory$2, "..", "dist", "native", "muon-256.png"),
2669
+ join(moduleDirectory$2, "..", "..", "images", "muon-256.png")
2548
2670
  ];
2549
- for (const candidate of candidates) if (await fileExists$2(candidate)) return candidate;
2671
+ for (const candidate of candidates) if (await fileExists$3(candidate)) return candidate;
2550
2672
  };
2551
2673
  var sanitizeDesktopId = (value) => {
2552
2674
  const sanitized = value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[.-]+/g, "").replace(/[.-]+$/g, "");
@@ -2555,7 +2677,7 @@ var sanitizeDesktopId = (value) => {
2555
2677
  var resolveResourcePath = (directory, path) => {
2556
2678
  return isAbsolute(path) ? path : resolve(directory, path);
2557
2679
  };
2558
- var fileExists$2 = async (path) => {
2680
+ var fileExists$3 = async (path) => {
2559
2681
  try {
2560
2682
  await access(path, constants.F_OK);
2561
2683
  return true;
@@ -2563,7 +2685,7 @@ var fileExists$2 = async (path) => {
2563
2685
  return false;
2564
2686
  }
2565
2687
  };
2566
- var isJsonObject$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2688
+ var isJsonObject$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2567
2689
  //#endregion
2568
2690
  //#region src/build.ts
2569
2691
  var AdmZip = __resolveDefaultExport(__screwUpDefaultImportModule0, false);
@@ -2586,7 +2708,7 @@ function __resolveDefaultExport(module, isESM) {
2586
2708
  }
2587
2709
  return hasDefault ? resolvedDefault ?? module : module;
2588
2710
  }
2589
- var defaultConfigFileNames = [
2711
+ var defaultConfigFileNames$1 = [
2590
2712
  "muon.json5",
2591
2713
  "muon.jsonc",
2592
2714
  "muon.json"
@@ -2623,14 +2745,21 @@ var buildMuonApp = async (options = {}) => {
2623
2745
  const appName = resolveAppName(packageJson, options.appName);
2624
2746
  const appId = resolveAppId(packageJson, options.appId);
2625
2747
  const buildConfig = await readBuildConfig(root, options.configPath);
2626
- const assetInput = resolveAssetInput(root, options.assetSourcePath, options.assetPrefix, buildConfig);
2748
+ const sourceConfig = applyRuntimePluginConfig(buildConfig.config, options.runtimePluginConfig ?? { mode: "simple" });
2749
+ assertNoUserInitialTitleBarIcon(sourceConfig);
2750
+ const resolvedBuildConfig = {
2751
+ ...buildConfig,
2752
+ config: sourceConfig
2753
+ };
2754
+ const assetInput = resolveAssetInput(root, options.assetSourcePath, options.assetPrefix, resolvedBuildConfig);
2627
2755
  const windowsResource = await resolveMuonWindowsResource({
2628
2756
  root,
2629
2757
  packageDirectory,
2630
2758
  packageJson,
2631
- muonConfig: buildConfig.config,
2759
+ muonConfig: sourceConfig,
2632
2760
  muonConfigDirectory: buildConfig.directory,
2633
2761
  options: options.windowsResource,
2762
+ appIconPath: options.iconPath,
2634
2763
  defaults: {
2635
2764
  productName: appName,
2636
2765
  fileDescription: appName,
@@ -2642,9 +2771,10 @@ var buildMuonApp = async (options = {}) => {
2642
2771
  const linuxDesktop = await resolveMuonLinuxDesktop({
2643
2772
  root,
2644
2773
  packageDirectory,
2645
- muonConfig: buildConfig.config,
2774
+ muonConfig: sourceConfig,
2646
2775
  muonConfigDirectory: buildConfig.directory,
2647
2776
  options: options.linuxDesktop,
2777
+ appIconPath: options.iconPath,
2648
2778
  defaults: {
2649
2779
  desktopId: appId,
2650
2780
  name: resolveLinuxDesktopDefaultName(packageJson, appName),
@@ -2664,7 +2794,7 @@ var buildMuonApp = async (options = {}) => {
2664
2794
  appId,
2665
2795
  target,
2666
2796
  assetInput,
2667
- sourceConfig: buildConfig.config,
2797
+ sourceConfig,
2668
2798
  windowsResource,
2669
2799
  linuxDesktop,
2670
2800
  salt
@@ -2701,8 +2831,8 @@ var normalizeZipPrefix = (prefix) => {
2701
2831
  };
2702
2832
  var readPackageJson = async (root) => {
2703
2833
  const packageJsonPath = join(root, "package.json");
2704
- if (!await fileExists$1(packageJsonPath)) return {};
2705
- return await readJsonObjectFile(packageJsonPath, "package.json");
2834
+ if (!await fileExists$2(packageJsonPath)) return {};
2835
+ return await readJsonObjectFile$1(packageJsonPath, "package.json");
2706
2836
  };
2707
2837
  var resolvePackageName = (packageJson) => {
2708
2838
  return typeof packageJson.name === "string" ? packageJson.name : defaultAppName;
@@ -2733,14 +2863,27 @@ var readBuildConfig = async (root, configPath) => {
2733
2863
  directory: root
2734
2864
  };
2735
2865
  return {
2736
- config: await readJsonObjectFile(resolvedConfigPath, "Muon config file"),
2866
+ config: await readJsonObjectFile$1(resolvedConfigPath, "Muon config file"),
2737
2867
  directory: dirname(resolvedConfigPath)
2738
2868
  };
2739
2869
  };
2870
+ var applyRuntimePluginConfig = (sourceConfig, runtimePluginConfig) => {
2871
+ if (runtimePluginConfig === void 0) return sourceConfig;
2872
+ const sourcePlugin = sourceConfig.plugin;
2873
+ if (sourcePlugin !== void 0 && !isJsonObject$2(sourcePlugin)) throw new Error("muon.json plugin must be an object when runtime plugin config is applied.");
2874
+ const plugin = sourcePlugin ?? {};
2875
+ return {
2876
+ ...sourceConfig,
2877
+ plugin: {
2878
+ ...plugin,
2879
+ ...runtimePluginConfig
2880
+ }
2881
+ };
2882
+ };
2740
2883
  var readConfigAssetSourcePath = (sourceConfig) => {
2741
2884
  const sourceAsset = sourceConfig.asset;
2742
2885
  if (sourceAsset === void 0) return;
2743
- if (!isJsonObject$1(sourceAsset)) throw new Error("muon.json asset must be an object when present.");
2886
+ if (!isJsonObject$2(sourceAsset)) throw new Error("muon.json asset must be an object when present.");
2744
2887
  const sourceAssetPath = sourceAsset.sourcePath;
2745
2888
  if (sourceAssetPath === void 0) return;
2746
2889
  if (typeof sourceAssetPath !== "string") throw new Error("muon.json asset.sourcePath must be a string when present.");
@@ -2749,28 +2892,28 @@ var readConfigAssetSourcePath = (sourceConfig) => {
2749
2892
  var resolveConfigPath = async (root, configPath) => {
2750
2893
  if (configPath !== void 0) {
2751
2894
  const resolvedPath = resolve(root, configPath);
2752
- if (await fileExists$1(resolvedPath)) return resolvedPath;
2895
+ if (await fileExists$2(resolvedPath)) return resolvedPath;
2753
2896
  throw new Error(`Muon config file does not exist: ${resolvedPath}`);
2754
2897
  }
2755
- for (const fileName of defaultConfigFileNames) {
2898
+ for (const fileName of defaultConfigFileNames$1) {
2756
2899
  const candidatePath = join(root, fileName);
2757
- if (await fileExists$1(candidatePath)) return candidatePath;
2900
+ if (await fileExists$2(candidatePath)) return candidatePath;
2758
2901
  }
2759
2902
  };
2760
- var readJsonObjectFile = async (filePath, label) => {
2903
+ var readJsonObjectFile$1 = async (filePath, label) => {
2761
2904
  let content;
2762
2905
  try {
2763
2906
  content = await readFile(filePath, "utf8");
2764
2907
  } catch (error) {
2765
- throw new Error(`${label} could not be read: ${filePath}: ${getErrorMessage$1(error)}`);
2908
+ throw new Error(`${label} could not be read: ${filePath}: ${getErrorMessage$3(error)}`);
2766
2909
  }
2767
2910
  let parsed;
2768
2911
  try {
2769
2912
  parsed = (0, import_dist.parse)(content);
2770
2913
  } catch (error) {
2771
- throw new Error(`${label} could not be parsed: ${filePath}: ${getErrorMessage$1(error)}`);
2914
+ throw new Error(`${label} could not be parsed: ${filePath}: ${getErrorMessage$3(error)}`);
2772
2915
  }
2773
- if (!isJsonObject$1(parsed)) throw new Error(`${label} must contain a JSON object: ${filePath}`);
2916
+ if (!isJsonObject$2(parsed)) throw new Error(`${label} must contain a JSON object: ${filePath}`);
2774
2917
  return parsed;
2775
2918
  };
2776
2919
  var buildMuonTarget = async (input) => {
@@ -2780,6 +2923,8 @@ var buildMuonTarget = async (input) => {
2780
2923
  const outputPath = join(input.outputRoot, descriptor.distributionDirectoryName);
2781
2924
  const launcherPath = join(outputPath, getLauncherFileName(input.appName, descriptor));
2782
2925
  const assetZipPath = join(outputPath, "assets.zip");
2926
+ const runtimeAppId = getMuonTargetRuntimeAppId(input.appId, input.target);
2927
+ const appIconPath = descriptor.os === "windows" ? input.windowsResource.iconPath : input.linuxDesktop.iconPath;
2783
2928
  await verifyTargetInputs({
2784
2929
  sourceRuntimePath,
2785
2930
  sourceBootstrapPath,
@@ -2798,8 +2943,11 @@ var buildMuonTarget = async (input) => {
2798
2943
  await chmod(join(outputPath, descriptor.runtimeExecutableName), executableMode);
2799
2944
  await copyFile(sourceBootstrapPath, launcherPath);
2800
2945
  await chmod(launcherPath, executableMode);
2801
- const asset = await writeAssetArchive(input.assetInput, assetZipPath, input.salt);
2802
- const embeddedConfig = createEmbeddedConfig(input.sourceConfig, asset, input.appId, input.linuxDesktop.desktopId);
2946
+ const asset = await writeAssetArchive(input.assetInput, assetZipPath, input.salt, [{
2947
+ name: appIconAssetEntryName,
2948
+ data: await readFile(appIconPath)
2949
+ }]);
2950
+ const embeddedConfig = createEmbeddedConfig(input.sourceConfig, asset, runtimeAppId, input.linuxDesktop.desktopId, appIconAssetUrl);
2803
2951
  await withTemporaryConfig(embeddedConfig, async (configPath) => {
2804
2952
  await embedMuonConfigInRuntime({
2805
2953
  runtimePath: outputPath,
@@ -2812,19 +2960,27 @@ var buildMuonTarget = async (input) => {
2812
2960
  outputPath: void 0
2813
2961
  });
2814
2962
  });
2815
- if (descriptor.os === "windows") await updateWindowsPeResources({
2816
- executablePath: launcherPath,
2817
- resource: input.windowsResource,
2818
- environment: process.env,
2819
- cwd: input.root
2820
- });
2821
- else if (descriptor.os === "linux") await writeLinuxDesktopDistributionFiles(outputPath, input.linuxDesktop);
2963
+ if (descriptor.os === "windows") {
2964
+ await updateWindowsPeIconResource({
2965
+ executablePath: join(outputPath, descriptor.runtimeExecutableName),
2966
+ resource: input.windowsResource,
2967
+ environment: process.env,
2968
+ cwd: input.root
2969
+ });
2970
+ await updateWindowsPeResources({
2971
+ executablePath: launcherPath,
2972
+ resource: input.windowsResource,
2973
+ environment: process.env,
2974
+ cwd: input.root
2975
+ });
2976
+ } else if (descriptor.os === "linux") await writeLinuxDesktopDistributionFiles(outputPath, input.linuxDesktop);
2822
2977
  return {
2823
2978
  target: input.target,
2824
2979
  distributionDirectoryName: descriptor.distributionDirectoryName,
2825
2980
  outputPath,
2826
2981
  launcherPath,
2827
2982
  asset,
2983
+ runtimeAppId,
2828
2984
  embeddedConfig,
2829
2985
  ...descriptor.os === "linux" ? { linuxDesktop: input.linuxDesktop } : {}
2830
2986
  };
@@ -2847,10 +3003,10 @@ var copyRuntimeFiles = async (sourceRuntimePath, outputPath, descriptor) => {
2847
3003
  }
2848
3004
  await copyFile(join(sourceRuntimePath, muonLicenseFileName), join(outputPath, muonLicenseFileName));
2849
3005
  };
2850
- var writeAssetArchive = async (input, outputPath, salt) => {
3006
+ var writeAssetArchive = async (input, outputPath, salt, extraEntries) => {
2851
3007
  const sourceStats = await statOrUndefined(input.sourcePath);
2852
3008
  if (sourceStats === void 0) throw new Error(`Muon asset source does not exist: ${input.sourcePath}`);
2853
- const archive = sourceStats.isDirectory() ? await createAssetArchiveFromDirectory(input) : sourceStats.isFile() ? await readFile(input.sourcePath) : void 0;
3009
+ const archive = sourceStats.isDirectory() ? await createAssetArchiveFromDirectory(input, extraEntries) : sourceStats.isFile() ? await createAssetArchiveFromZipFile(input.sourcePath, extraEntries) : void 0;
2854
3010
  if (archive === void 0) throw new Error(`Muon asset source is not a directory or file: ${input.sourcePath}`);
2855
3011
  await writeFile(outputPath, archive);
2856
3012
  return {
@@ -2860,10 +3016,30 @@ var writeAssetArchive = async (input, outputPath, salt) => {
2860
3016
  entryCount: sourceStats.isDirectory() ? readZipEntryCount(archive, outputPath) : readZipEntryCount(archive, input.sourcePath)
2861
3017
  };
2862
3018
  };
2863
- var createAssetArchiveFromDirectory = async (input) => {
3019
+ var createAssetArchiveFromDirectory = async (input, extraEntries) => {
2864
3020
  const entries = await collectZipEntries(input.sourcePath, input.prefix);
2865
3021
  if (entries.length === 0) throw new Error(`Muon asset source has no files: ${input.sourcePath}`);
2866
- return createZipArchive(entries);
3022
+ return createZipArchive(appendZipEntries(entries, extraEntries));
3023
+ };
3024
+ var createAssetArchiveFromZipFile = async (sourcePath, extraEntries) => {
3025
+ const zip = new AdmZip(await readFile(sourcePath));
3026
+ for (const entry of extraEntries) {
3027
+ assertSafeZipEntryName(entry.name);
3028
+ if (zip.getEntry(entry.name) !== null) throw new Error(`Muon app icon asset entry already exists: ${entry.name}`);
3029
+ zip.addFile(entry.name, entry.data);
3030
+ }
3031
+ return zip.toBuffer();
3032
+ };
3033
+ var appendZipEntries = (entries, extraEntries) => {
3034
+ const output = [...entries];
3035
+ const names = new Set(entries.map((entry) => entry.name));
3036
+ for (const entry of extraEntries) {
3037
+ assertSafeZipEntryName(entry.name);
3038
+ if (names.has(entry.name)) throw new Error(`Muon app icon asset entry already exists: ${entry.name}`);
3039
+ names.add(entry.name);
3040
+ output.push(entry);
3041
+ }
3042
+ return output;
2867
3043
  };
2868
3044
  var readZipEntryCount = (archive, sourcePath) => {
2869
3045
  const endSignature = 101010256;
@@ -2901,13 +3077,20 @@ var createZipArchive = (entries) => {
2901
3077
  for (const entry of entries) zip.addFile(entry.name, entry.data);
2902
3078
  return zip.toBuffer();
2903
3079
  };
2904
- var createEmbeddedConfig = (sourceConfig, asset, appId, desktopId) => {
3080
+ var createEmbeddedConfig = (sourceConfig, asset, appId, desktopId, initialTitleBarIcon) => {
2905
3081
  const sourceAsset = sourceConfig.asset;
2906
- if (sourceAsset !== void 0 && !isJsonObject$1(sourceAsset)) throw new Error("muon.json asset must be an object when present.");
3082
+ if (sourceAsset !== void 0 && !isJsonObject$2(sourceAsset)) throw new Error("muon.json asset must be an object when present.");
2907
3083
  const sourceBootstrap = sourceConfig.bootstrap;
2908
- if (sourceBootstrap !== void 0 && !isJsonObject$1(sourceBootstrap)) throw new Error("muon.json bootstrap must be an object when present.");
3084
+ if (sourceBootstrap !== void 0 && !isJsonObject$2(sourceBootstrap)) throw new Error("muon.json bootstrap must be an object when present.");
3085
+ const runtimeConfig = stripBuildOnlyAppIconConfig(stripBuildOnlyLinuxDesktopConfig(stripBuildOnlyWindowsResourceConfig(sourceConfig)));
3086
+ const sourceBrowser = runtimeConfig.browser;
3087
+ if (sourceBrowser !== void 0 && !isJsonObject$2(sourceBrowser)) throw new Error("muon.json browser must be an object when present.");
2909
3088
  return {
2910
- ...stripBuildOnlyLinuxDesktopConfig(stripBuildOnlyWindowsResourceConfig(sourceConfig)),
3089
+ ...runtimeConfig,
3090
+ browser: {
3091
+ ...sourceBrowser ?? {},
3092
+ initialTitleBarIcon
3093
+ },
2911
3094
  asset: {
2912
3095
  ...sourceAsset ?? {},
2913
3096
  sourcePath: appConfigSourcePath,
@@ -2921,6 +3104,17 @@ var createEmbeddedConfig = (sourceConfig, asset, appId, desktopId) => {
2921
3104
  }
2922
3105
  };
2923
3106
  };
3107
+ var assertNoUserInitialTitleBarIcon = (sourceConfig) => {
3108
+ const sourceBrowser = sourceConfig.browser;
3109
+ if (sourceBrowser === void 0) return;
3110
+ if (!isJsonObject$2(sourceBrowser)) throw new Error("muon.json browser must be an object when present.");
3111
+ if (sourceBrowser.initialTitleBarIcon !== void 0) throw new Error("muon.json browser.initialTitleBarIcon is generated by muon build; use top-level iconPath instead.");
3112
+ };
3113
+ var stripBuildOnlyAppIconConfig = (sourceConfig) => {
3114
+ const output = {};
3115
+ for (const [key, value] of Object.entries(sourceConfig)) if (key !== "iconPath") output[key] = value;
3116
+ return output;
3117
+ };
2924
3118
  var withTemporaryConfig = async (config, callback) => {
2925
3119
  const tempDirectory = await mkdtemp(join(tmpdir(), "muon-build-config-"));
2926
3120
  const configPath = join(tempDirectory, "muon.json");
@@ -2949,7 +3143,7 @@ var statOrUndefined = async (path) => {
2949
3143
  return;
2950
3144
  }
2951
3145
  };
2952
- var fileExists$1 = async (path) => {
3146
+ var fileExists$2 = async (path) => {
2953
3147
  try {
2954
3148
  await access(path, constants.F_OK);
2955
3149
  return true;
@@ -2957,10 +3151,415 @@ var fileExists$1 = async (path) => {
2957
3151
  return false;
2958
3152
  }
2959
3153
  };
2960
- var isJsonObject$1 = (value) => {
3154
+ var isJsonObject$2 = (value) => {
2961
3155
  return typeof value === "object" && value !== null && !Array.isArray(value);
2962
3156
  };
2963
- var getErrorMessage$1 = (error) => error instanceof Error ? error.message : String(error);
3157
+ var getErrorMessage$3 = (error) => error instanceof Error ? error.message : String(error);
3158
+ //#endregion
3159
+ //#region src/capability.ts
3160
+ var virtualModulePrefix = "\0muon-capability:";
3161
+ var moduleNamePattern = /^[A-Za-z_$][A-Za-z0-9_$]*:[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/u;
3162
+ var jsIdentifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/u;
3163
+ var normalizePath = (path) => (path.split("?")[0] ?? "").replaceAll("\\", "/");
3164
+ var hashCapabilityRule = (index, rule) => `cap-${createHash("sha1").update(JSON.stringify({
3165
+ index,
3166
+ pluginName: rule.pluginName,
3167
+ sources: [...rule.sources ?? []],
3168
+ packages: [...rule.packages ?? []],
3169
+ allow: [...rule.allow]
3170
+ })).digest("hex").slice(0, 16)}`;
3171
+ var parseCapabilitySpecifier = (source) => {
3172
+ if (!moduleNamePattern.test(source)) return;
3173
+ const separator = source.indexOf(":");
3174
+ return {
3175
+ moduleName: source,
3176
+ namespace: `${source.slice(0, separator)}.${source.slice(separator + 1)}`
3177
+ };
3178
+ };
3179
+ var escapeRegExp = (source) => source.replace(/[\\^$+?.()|[\]{}]/gu, "\\$&");
3180
+ var globToRegExp = (glob, separator) => {
3181
+ let source = "^";
3182
+ for (let index = 0; index < glob.length; index += 1) {
3183
+ const current = glob[index] ?? "";
3184
+ const next = glob[index + 1];
3185
+ if (current === "*" && next === "*") {
3186
+ source += ".*";
3187
+ index += 1;
3188
+ continue;
3189
+ }
3190
+ if (current === "*") {
3191
+ source += `[^${escapeRegExp(separator)}]*`;
3192
+ continue;
3193
+ }
3194
+ source += escapeRegExp(current);
3195
+ }
3196
+ source += "$";
3197
+ return new RegExp(source, "u");
3198
+ };
3199
+ var isGlobMatch = (glob, value, separator) => globToRegExp(glob, separator).test(value);
3200
+ var toRootRelativeImporter = (root, importer) => normalizePath(relative(root, importer));
3201
+ var readPackageName = (packageJsonPath) => {
3202
+ try {
3203
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
3204
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && typeof parsed.name === "string" ? parsed.name : void 0;
3205
+ } catch {
3206
+ return;
3207
+ }
3208
+ };
3209
+ var getImporterPackageName = (root, importer) => {
3210
+ const normalizedRoot = normalizePath(root);
3211
+ const rootPackageJsonPath = join(root, "package.json");
3212
+ let directory = dirname(normalizePath(importer));
3213
+ for (;;) {
3214
+ const packageJsonPath = join(directory, "package.json");
3215
+ if (packageJsonPath !== rootPackageJsonPath && existsSync(packageJsonPath)) return readPackageName(packageJsonPath);
3216
+ if (normalizePath(directory) === normalizedRoot) return;
3217
+ const parent = dirname(directory);
3218
+ if (parent === directory) return;
3219
+ directory = parent;
3220
+ }
3221
+ };
3222
+ var matchesCapabilityRuleImporter = (rule, root, importer) => {
3223
+ const relativeImporter = toRootRelativeImporter(root, importer);
3224
+ if (rule.sources?.some((pattern) => isGlobMatch(normalizePath(pattern), relativeImporter, "/")) ?? false) return true;
3225
+ const packageName = getImporterPackageName(root, importer);
3226
+ return packageName === void 0 ? false : rule.packages?.includes(packageName) ?? false;
3227
+ };
3228
+ var hasNamespaceFunctionAllow = (namespace, allow) => allow.some((entry) => {
3229
+ if (entry.startsWith(`${namespace}.`)) return true;
3230
+ return isGlobMatch(entry, `${namespace}.placeholder`, ".");
3231
+ });
3232
+ var getExactExportName = (namespace, functionPath) => {
3233
+ const prefix = `${namespace}.`;
3234
+ if (!functionPath.startsWith(prefix)) return;
3235
+ const name = functionPath.slice(prefix.length);
3236
+ if (!jsIdentifierPattern.test(name)) return;
3237
+ return name;
3238
+ };
3239
+ var getExportedFunctions = (namespace, allow) => allow.flatMap((functionPath) => {
3240
+ const exportName = getExactExportName(namespace, functionPath);
3241
+ return exportName === void 0 ? [] : [[exportName, functionPath]];
3242
+ });
3243
+ var createRuleRuntimeEntry = (rule, index) => ({
3244
+ id: hashCapabilityRule(index, rule),
3245
+ allow: [...rule.allow]
3246
+ });
3247
+ var validateCapabilityRule = (rule) => {
3248
+ if ((rule.sources === void 0 || rule.sources.length === 0) && (rule.packages === void 0 || rule.packages.length === 0)) throw new Error("Muon capability import rule requires sources or packages.");
3249
+ };
3250
+ var createModuleVirtualId = (rule) => `${virtualModulePrefix}${rule.index}:${rule.moduleName}`;
3251
+ var parseModuleVirtualId = (id) => {
3252
+ if (!id.startsWith(virtualModulePrefix)) return;
3253
+ const payload = id.slice(17);
3254
+ const separator = payload.indexOf(":");
3255
+ if (separator <= 0) return;
3256
+ const index = Number.parseInt(payload.slice(0, separator), 10);
3257
+ if (!Number.isInteger(index) || index < 0) return;
3258
+ return {
3259
+ index,
3260
+ moduleName: payload.slice(separator + 1)
3261
+ };
3262
+ };
3263
+ var createExecutorSpawnExport = (capabilityId, functionPath) => `export const spawn = async (options = {}) => {
3264
+ const source = await __muonCall(${JSON.stringify(capabilityId)}, ${JSON.stringify(functionPath)}, [JSON.stringify(options ?? {})]);
3265
+ return JSON.parse(source);
3266
+ };`;
3267
+ var createGenericFunctionExport = (exportName, capabilityId, functionPath) => `export const ${exportName} = async (...args) =>
3268
+ await __muonCall(${JSON.stringify(capabilityId)}, ${JSON.stringify(functionPath)}, args);`;
3269
+ var createModuleSource = (rule) => {
3270
+ const exportedFunctions = getExportedFunctions(rule.namespace, rule.rule.allow);
3271
+ if (exportedFunctions.length === 0) throw new Error(`Muon capability import has no exact function exports: ${rule.moduleName}`);
3272
+ return `const __muonCall = globalThis.__muon_plugin_call;
3273
+ if (typeof __muonCall !== "function") {
3274
+ throw new Error("Muon plugin capability bridge is not available.");
3275
+ }
3276
+
3277
+ ${exportedFunctions.map(([exportName, functionPath]) => rule.namespace === "muon.executor" && exportName === "spawn" ? createExecutorSpawnExport(rule.id, functionPath) : createGenericFunctionExport(exportName, rule.id, functionPath)).join("\n\n")}
3278
+ `;
3279
+ };
3280
+ /**
3281
+ * Creates a capability virtual module resolver for a bundler adapter.
3282
+ *
3283
+ * @param root Bundler project root.
3284
+ * @param options Capability import options.
3285
+ */
3286
+ var createMuonCapabilityModuleResolver = (root, options) => {
3287
+ const rules = [...options?.imports ?? []];
3288
+ for (const rule of rules) validateCapabilityRule(rule);
3289
+ const runtimeEntries = rules.map((rule, index) => createRuleRuntimeEntry(rule, index));
3290
+ const resolveRule = (source, importer) => {
3291
+ const parsed = parseCapabilitySpecifier(source);
3292
+ if (parsed === void 0) return;
3293
+ if (!rules.some((rule) => hasNamespaceFunctionAllow(parsed.namespace, rule.allow))) return;
3294
+ if (importer === void 0) throw new Error(`Muon capability import is not allowed without an importer: ${source}`);
3295
+ const relativeImporter = toRootRelativeImporter(root, importer);
3296
+ for (let index = 0; index < rules.length; index += 1) {
3297
+ const rule = rules[index];
3298
+ if (rule === void 0 || !matchesCapabilityRuleImporter(rule, root, importer) || !hasNamespaceFunctionAllow(parsed.namespace, rule.allow)) continue;
3299
+ return {
3300
+ index,
3301
+ id: runtimeEntries[index]?.id ?? hashCapabilityRule(index, rule),
3302
+ rule,
3303
+ namespace: parsed.namespace,
3304
+ moduleName: parsed.moduleName
3305
+ };
3306
+ }
3307
+ throw new Error(`Muon capability import is not allowed: ${source} from ${relativeImporter}`);
3308
+ };
3309
+ return {
3310
+ resolveId: (source, importer) => {
3311
+ const rule = resolveRule(source, importer);
3312
+ return rule === void 0 ? void 0 : { id: createModuleVirtualId(rule) };
3313
+ },
3314
+ load: (id) => {
3315
+ const parsed = parseModuleVirtualId(id);
3316
+ if (parsed === void 0) return;
3317
+ const source = parseCapabilitySpecifier(parsed.moduleName);
3318
+ const rule = rules[parsed.index];
3319
+ const runtimeEntry = runtimeEntries[parsed.index];
3320
+ if (source === void 0 || rule === void 0 || runtimeEntry === void 0) return;
3321
+ return createModuleSource({
3322
+ index: parsed.index,
3323
+ id: runtimeEntry.id,
3324
+ rule,
3325
+ namespace: source.namespace,
3326
+ moduleName: source.moduleName
3327
+ });
3328
+ },
3329
+ getRuntimePluginConfig: () => ({
3330
+ mode: "validate",
3331
+ capabilities: runtimeEntries
3332
+ })
3333
+ };
3334
+ };
3335
+ //#endregion
3336
+ //#region src/plugin-access.ts
3337
+ var defaultConfigFileNames = [
3338
+ "muon.json5",
3339
+ "muon.jsonc",
3340
+ "muon.json"
3341
+ ];
3342
+ var defaultPluginPages = ["asset://main/**"];
3343
+ var isJsonObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3344
+ var isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
3345
+ var isSha1HexString = (value) => value.length === 40 && /^[0-9a-fA-F]+$/.test(value);
3346
+ var isHexByteString = (value) => value.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(value);
3347
+ var getErrorMessage$2 = (error) => error instanceof Error ? error.message : String(error);
3348
+ var fileExists$1 = async (path) => {
3349
+ try {
3350
+ await access(path, constants.F_OK);
3351
+ return true;
3352
+ } catch {
3353
+ return false;
3354
+ }
3355
+ };
3356
+ var resolveMuonConfigPath = async (root, configPath) => {
3357
+ if (configPath !== void 0) {
3358
+ const resolvedPath = resolve(root, configPath);
3359
+ if (await fileExists$1(resolvedPath)) return resolvedPath;
3360
+ throw new Error(`Muon config file does not exist: ${resolvedPath}`);
3361
+ }
3362
+ for (const fileName of defaultConfigFileNames) {
3363
+ const candidatePath = join(root, fileName);
3364
+ if (await fileExists$1(candidatePath)) return candidatePath;
3365
+ }
3366
+ };
3367
+ var readJsonObjectFile = async (path, label) => {
3368
+ let content;
3369
+ try {
3370
+ content = await readFile(path, "utf8");
3371
+ } catch (error) {
3372
+ throw new Error(`${label} could not be read: ${path}: ${getErrorMessage$2(error)}`);
3373
+ }
3374
+ let parsed;
3375
+ try {
3376
+ parsed = (0, import_dist.parse)(content);
3377
+ } catch (error) {
3378
+ throw new Error(`${label} could not be parsed: ${path}: ${getErrorMessage$2(error)}`);
3379
+ }
3380
+ if (!isJsonObject$1(parsed)) throw new Error(`${label} must contain a JSON object: ${path}`);
3381
+ return parsed;
3382
+ };
3383
+ var readOptionalStringArray = (object, key, configPath) => {
3384
+ const value = object[key];
3385
+ if (value === void 0) return;
3386
+ if (!isStringArray(value)) throw new Error(`muon.json ${configPath} must be an array of strings.`);
3387
+ return value;
3388
+ };
3389
+ var readPluginAccessImportOptions = (value, configPath) => {
3390
+ if (!isJsonObject$1(value)) throw new Error(`muon.json ${configPath} must be an object.`);
3391
+ const sources = readOptionalStringArray(value, "sources", `${configPath}.sources`);
3392
+ const packages = readOptionalStringArray(value, "packages", `${configPath}.packages`);
3393
+ const allow = readOptionalStringArray(value, "allow", `${configPath}.allow`);
3394
+ const options = {};
3395
+ if (sources !== void 0) options.sources = sources;
3396
+ if (packages !== void 0) options.packages = packages;
3397
+ if (allow !== void 0) options.allow = allow;
3398
+ return options;
3399
+ };
3400
+ var readPluginAccessEntryOptions = (value, index) => {
3401
+ const configPath = `plugin.plugins[${index}]`;
3402
+ if (!isJsonObject$1(value)) throw new Error(`muon.json ${configPath} must be an object.`);
3403
+ if (typeof value.name !== "string") throw new Error(`muon.json ${configPath}.name must be a string.`);
3404
+ if (value.signature !== void 0) {
3405
+ if (typeof value.signature !== "string") throw new Error(`muon.json ${configPath}.signature must be a string.`);
3406
+ if (!isSha1HexString(value.signature)) throw new Error(`muon.json ${configPath}.signature must be a 40-character SHA-1 hex string.`);
3407
+ }
3408
+ if (value.salt !== void 0) {
3409
+ if (typeof value.salt !== "string") throw new Error(`muon.json ${configPath}.salt must be a string.`);
3410
+ if (!isHexByteString(value.salt)) throw new Error(`muon.json ${configPath}.salt must be a hexadecimal byte string.`);
3411
+ }
3412
+ const allow = readOptionalStringArray(value, "allow", `${configPath}.allow`);
3413
+ const rawImports = value.imports;
3414
+ if (rawImports !== void 0 && !Array.isArray(rawImports)) throw new Error(`muon.json ${configPath}.imports must be an array.`);
3415
+ const imports = rawImports === void 0 ? void 0 : rawImports.map((entry, importIndex) => readPluginAccessImportOptions(entry, `${configPath}.imports[${importIndex}]`));
3416
+ const options = { name: value.name };
3417
+ if (value.signature !== void 0) options.signature = value.signature;
3418
+ if (value.salt !== void 0) options.salt = value.salt;
3419
+ if (allow !== void 0) options.allow = allow;
3420
+ if (imports !== void 0) options.imports = imports;
3421
+ return options;
3422
+ };
3423
+ var readPluginAccessOptions = (config) => {
3424
+ const browser = config.browser;
3425
+ if (isJsonObject$1(browser) && browser.plugin !== void 0) throw new Error("muon.json browser.plugin is no longer supported; use plugin.mode and plugin.pages instead.");
3426
+ const plugin = config.plugin;
3427
+ if (plugin === void 0) return {};
3428
+ if (!isJsonObject$1(plugin)) throw new Error("muon.json plugin must be an object.");
3429
+ const options = {};
3430
+ if (plugin.path !== void 0) {
3431
+ if (typeof plugin.path !== "string") throw new Error("muon.json plugin.path must be a string.");
3432
+ if (plugin.path.length === 0) throw new Error("muon.json plugin.path must not be empty.");
3433
+ options.path = plugin.path;
3434
+ }
3435
+ if (plugin.mode !== void 0) {
3436
+ if (typeof plugin.mode !== "string") throw new Error("muon.json plugin.mode must be a string.");
3437
+ if (plugin.mode.length === 0) throw new Error("muon.json plugin.mode must not be empty.");
3438
+ if (plugin.mode !== "simple" && plugin.mode !== "validate") throw new Error("muon.json plugin.mode has unknown value.");
3439
+ options.mode = plugin.mode;
3440
+ }
3441
+ const pages = readOptionalStringArray(plugin, "pages", "plugin.pages");
3442
+ if (pages !== void 0) options.pages = pages;
3443
+ const rawPlugins = plugin.plugins;
3444
+ if (rawPlugins !== void 0) {
3445
+ if (!Array.isArray(rawPlugins)) throw new Error("muon.json plugin.plugins must be an array.");
3446
+ options.plugins = rawPlugins.map((entry, index) => readPluginAccessEntryOptions(entry, index));
3447
+ }
3448
+ return options;
3449
+ };
3450
+ var hasImportSourceSelector = (entry) => entry.sources !== void 0 && entry.sources.length > 0 || entry.packages !== void 0 && entry.packages.length > 0;
3451
+ var validatePluginAccessOptions = (resolved) => {
3452
+ for (const [pluginIndex, plugin] of resolved.plugins.entries()) {
3453
+ const pluginPath = `plugin.plugins[${pluginIndex}]`;
3454
+ if (plugin.name === "internal" && plugin.signature !== void 0) throw new Error(`muon.json ${pluginPath}.signature is not supported for internal plugins.`);
3455
+ if (plugin.name === "internal" && plugin.salt !== void 0) throw new Error(`muon.json ${pluginPath}.salt is not supported for internal plugins.`);
3456
+ if (plugin.signature !== void 0 && plugin.salt === void 0) throw new Error(`muon.json ${pluginPath}.signature requires ${pluginPath}.salt.`);
3457
+ if (resolved.mode === "validate") {
3458
+ if (plugin.allow !== void 0) throw new Error(`muon.json ${pluginPath}.allow is only supported in simple mode; use ${pluginPath}.imports[].allow in validate mode.`);
3459
+ if (plugin.imports === void 0) throw new Error(`muon.json ${pluginPath}.imports is required in validate mode.`);
3460
+ if (plugin.imports.length === 0) throw new Error(`muon.json ${pluginPath}.imports must not be empty in validate mode.`);
3461
+ for (const [importIndex, entry] of plugin.imports.entries()) {
3462
+ const importPath = `${pluginPath}.imports[${importIndex}]`;
3463
+ if (!hasImportSourceSelector(entry)) throw new Error(`muon.json ${importPath} requires sources or packages.`);
3464
+ if (entry.allow === void 0) throw new Error(`muon.json ${importPath}.allow is required in validate mode.`);
3465
+ if (entry.allow.length === 0) throw new Error(`muon.json ${importPath}.allow must not be empty in validate mode.`);
3466
+ }
3467
+ continue;
3468
+ }
3469
+ if (plugin.allow === void 0) throw new Error(`muon.json ${pluginPath}.allow is required in simple mode.`);
3470
+ if (plugin.imports !== void 0) throw new Error(`muon.json ${pluginPath}.imports is only supported in validate mode.`);
3471
+ }
3472
+ return resolved;
3473
+ };
3474
+ var getValidateImportAllow = (entry, pluginIndex, importIndex) => {
3475
+ if (entry.allow === void 0 || entry.allow.length === 0) throw new Error(`muon.json plugin.plugins[${pluginIndex}].imports[${importIndex}].allow is required in validate mode.`);
3476
+ return entry.allow;
3477
+ };
3478
+ var getSimplePluginAllow = (plugin, pluginIndex) => {
3479
+ if (plugin.allow === void 0) throw new Error(`muon.json plugin.plugins[${pluginIndex}].allow is required in simple mode.`);
3480
+ return plugin.allow;
3481
+ };
3482
+ var toCapabilityImports = (plugins) => plugins.flatMap((plugin, pluginIndex) => (plugin.imports ?? []).map((entry, importIndex) => {
3483
+ if (!hasImportSourceSelector(entry)) throw new Error(`muon.json plugin.plugins[${pluginIndex}].imports[${importIndex}] requires sources or packages.`);
3484
+ const rule = {
3485
+ allow: getValidateImportAllow(entry, pluginIndex, importIndex),
3486
+ pluginName: plugin.name
3487
+ };
3488
+ if (entry.sources !== void 0) rule.sources = entry.sources;
3489
+ if (entry.packages !== void 0) rule.packages = entry.packages;
3490
+ return rule;
3491
+ }));
3492
+ var toValidateRuntimePluginEntries = (plugins) => plugins.map((plugin, pluginIndex) => {
3493
+ const allow = [];
3494
+ const seenAllow = /* @__PURE__ */ new Set();
3495
+ for (const [importIndex, entry] of (plugin.imports ?? []).entries()) for (const functionPath of getValidateImportAllow(entry, pluginIndex, importIndex)) if (!seenAllow.has(functionPath)) {
3496
+ seenAllow.add(functionPath);
3497
+ allow.push(functionPath);
3498
+ }
3499
+ return {
3500
+ name: plugin.name,
3501
+ ...plugin.signature === void 0 ? {} : { signature: plugin.signature },
3502
+ ...plugin.salt === void 0 ? {} : { salt: plugin.salt },
3503
+ allow
3504
+ };
3505
+ });
3506
+ var toSimpleRuntimePluginEntries = (plugins) => plugins.map((plugin, pluginIndex) => ({
3507
+ name: plugin.name,
3508
+ ...plugin.signature === void 0 ? {} : { signature: plugin.signature },
3509
+ ...plugin.salt === void 0 ? {} : { salt: plugin.salt },
3510
+ allow: [...getSimplePluginAllow(plugin, pluginIndex)]
3511
+ }));
3512
+ var shouldOverlayRuntimePlugins = (resolved, override) => override !== false && (override?.plugins !== void 0 || resolved.mode === "validate" && resolved.plugins.length > 0);
3513
+ var toRuntimePluginEntries = (resolved) => resolved.mode === "validate" ? toValidateRuntimePluginEntries(resolved.plugins) : toSimpleRuntimePluginEntries(resolved.plugins);
3514
+ var mergePluginAccessOptions = (base, override) => ({
3515
+ ...override?.path === void 0 ? {} : { path: override.path },
3516
+ mode: override?.mode ?? base.mode ?? "validate",
3517
+ pages: override?.pages ?? base.pages ?? defaultPluginPages,
3518
+ plugins: override?.plugins ?? base.plugins ?? []
3519
+ });
3520
+ var createRuntimeOverlay = (root, resolved, override) => {
3521
+ if (override === false) return {};
3522
+ const path = override?.path !== void 0 ? resolve(root, override.path) : void 0;
3523
+ const plugins = shouldOverlayRuntimePlugins(resolved, override) ? toRuntimePluginEntries(resolved) : void 0;
3524
+ return {
3525
+ pages: resolved.pages,
3526
+ ...path === void 0 ? {} : { path },
3527
+ ...plugins === void 0 ? {} : { plugins }
3528
+ };
3529
+ };
3530
+ /**
3531
+ * Resolves public plugin access config from muon.json and Vite overrides.
3532
+ *
3533
+ * @param input Resolution inputs.
3534
+ * @returns Effective plugin access config for bundler integrations.
3535
+ */
3536
+ var resolveMuonPluginAccessOptions = async ({ root, configPath, pluginAccess, onConfigReadError }) => {
3537
+ const resolvedConfigPath = await resolveMuonConfigPath(root, configPath);
3538
+ let base = {};
3539
+ if (resolvedConfigPath !== void 0) try {
3540
+ base = readPluginAccessOptions(await readJsonObjectFile(resolvedConfigPath, "Muon config file"));
3541
+ } catch (error) {
3542
+ if (onConfigReadError === void 0) throw error;
3543
+ onConfigReadError(error);
3544
+ }
3545
+ if (pluginAccess === false) {
3546
+ const resolved = validatePluginAccessOptions(mergePluginAccessOptions(base, { mode: "simple" }));
3547
+ return {
3548
+ mode: "simple",
3549
+ pages: resolved.pages,
3550
+ capabilityOptions: { imports: [] },
3551
+ runtimeOverlay: createRuntimeOverlay(root, resolved, pluginAccess)
3552
+ };
3553
+ }
3554
+ const resolved = validatePluginAccessOptions(mergePluginAccessOptions(base, pluginAccess));
3555
+ const capabilityOptions = { imports: resolved.mode === "validate" ? toCapabilityImports(resolved.plugins) : [] };
3556
+ return {
3557
+ mode: resolved.mode,
3558
+ pages: resolved.pages,
3559
+ capabilityOptions,
3560
+ runtimeOverlay: createRuntimeOverlay(root, resolved, pluginAccess)
3561
+ };
3562
+ };
2964
3563
  //#endregion
2965
3564
  //#region src/gitignore.ts
2966
3565
  var muonGitignoreEntries = [
@@ -3124,7 +3723,7 @@ var getWebSocketOrigin = (startUrl) => {
3124
3723
  else if (url.protocol === "http:") url.protocol = "ws:";
3125
3724
  return url.origin;
3126
3725
  };
3127
- var createMuonOverrideConfig = (startUrl, enableDebugger) => {
3726
+ var createMuonOverrideConfig = (startUrl, enableDebugger, runtimePluginConfig) => {
3128
3727
  const origin = new URL(startUrl).origin;
3129
3728
  return {
3130
3729
  ...enableDebugger ? { cdp: { enable: true } } : {},
@@ -3133,19 +3732,22 @@ var createMuonOverrideConfig = (startUrl, enableDebugger) => {
3133
3732
  ...enableDebugger ? { keybind: {
3134
3733
  devtools: "f12",
3135
3734
  recycle: "ctrl+f12"
3136
- } } : {},
3137
- plugin: { allow: [`${origin}/**`] }
3735
+ } } : {}
3736
+ },
3737
+ plugin: {
3738
+ ...runtimePluginConfig,
3739
+ pages: [`${origin}/**`]
3138
3740
  },
3139
3741
  network: { allow: [`${origin}/**`, `${getWebSocketOrigin(startUrl)}/**`] }
3140
3742
  };
3141
3743
  };
3142
- var writeMuonOverrideConfig = (server, overrideConfigPath, enableDebugger) => {
3744
+ var writeMuonOverrideConfig = (server, overrideConfigPath, pluginOptions, getRuntimePluginConfig) => {
3143
3745
  const startUrl = getBaseUrl(server);
3144
3746
  if (startUrl === void 0) {
3145
3747
  server.config.logger.warn("Muon Vite plugin could not resolve a Vite URL.");
3146
3748
  return false;
3147
3749
  }
3148
- writeFileSync(overrideConfigPath, `${JSON.stringify(createMuonOverrideConfig(startUrl, enableDebugger), null, 2)}\n`);
3750
+ writeFileSync(overrideConfigPath, `${JSON.stringify(createMuonOverrideConfig(startUrl, pluginOptions.enableDebugger !== false, getRuntimePluginConfig()), null, 2)}\n`);
3149
3751
  return true;
3150
3752
  };
3151
3753
  var createRuntimePaths = async (server, stagePath, platform, projectConfigPath) => {
@@ -3167,7 +3769,7 @@ var fileExists = async (path) => {
3167
3769
  }
3168
3770
  };
3169
3771
  var isJsonObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3170
- var getErrorMessage = (error) => error instanceof Error ? error.message : String(error);
3772
+ var getErrorMessage$1 = (error) => error instanceof Error ? error.message : String(error);
3171
3773
  var resolveProjectConfigPath = async (server) => {
3172
3774
  for (const fileName of defaultProjectConfigFileNames) {
3173
3775
  const candidatePath = join(server.config.root, fileName);
@@ -3176,7 +3778,7 @@ var resolveProjectConfigPath = async (server) => {
3176
3778
  if (!isJsonObject((0, import_dist.parse)(await readFile(candidatePath, "utf8")))) throw new Error("muon config root must be an object");
3177
3779
  return candidatePath;
3178
3780
  } catch (error) {
3179
- server.config.logger.warn(`Muon project config will be ignored because it could not be read or parsed: ${candidatePath}: ${getErrorMessage(error)}`);
3781
+ server.config.logger.warn(`Muon project config will be ignored because it could not be read or parsed: ${candidatePath}: ${getErrorMessage$1(error)}`);
3180
3782
  return;
3181
3783
  }
3182
3784
  }
@@ -3204,11 +3806,11 @@ var launchMuon = (paths, platform, server) => {
3204
3806
  windowsHide: false
3205
3807
  });
3206
3808
  child.once("error", (error) => {
3207
- server.config.logger.warn(`Muon startup failed: ${getErrorMessage(error)}`);
3809
+ server.config.logger.warn(`Muon startup failed: ${getErrorMessage$1(error)}`);
3208
3810
  });
3209
3811
  child.unref();
3210
3812
  };
3211
- var startMuonViteBrowserBridge = async ({ server, pluginOptions, platform, architecture, environment }) => {
3813
+ var startMuonViteBrowserBridge = async ({ server, pluginOptions, getRuntimePluginConfig, platform, architecture, environment }) => {
3212
3814
  await ensureMuonGitignoreEntry(server.config.root);
3213
3815
  if (pluginOptions.open === false || server.httpServer === null) return;
3214
3816
  const target = getDefaultMuonPrepareTarget(platform, architecture);
@@ -3254,7 +3856,7 @@ var startMuonViteBrowserBridge = async ({ server, pluginOptions, platform, archi
3254
3856
  cleanup();
3255
3857
  });
3256
3858
  server.httpServer.once("listening", () => {
3257
- if (writeMuonOverrideConfig(server, paths.overrideConfigPath, pluginOptions.enableDebugger !== false)) launchMuon(paths, platform, server);
3859
+ if (writeMuonOverrideConfig(server, paths.overrideConfigPath, pluginOptions, getRuntimePluginConfig)) launchMuon(paths, platform, server);
3258
3860
  });
3259
3861
  };
3260
3862
  //#endregion
@@ -3281,6 +3883,7 @@ var attachMuonVitePluginOptions = (plugin, options) => {
3281
3883
  };
3282
3884
  //#endregion
3283
3885
  //#region src/vite.ts
3886
+ var getErrorMessage = (error) => error instanceof Error ? error.message : String(error);
3284
3887
  /**
3285
3888
  * Creates a Vite plugin that launches Muon during Vite dev startup.
3286
3889
  *
@@ -3290,19 +3893,33 @@ var attachMuonVitePluginOptions = (plugin, options) => {
3290
3893
  */
3291
3894
  var muon = (options = {}) => {
3292
3895
  let resolvedConfig = void 0;
3896
+ let capabilityResolver = void 0;
3897
+ let resolvedPluginAccess = void 0;
3293
3898
  return attachMuonVitePluginOptions({
3294
3899
  name: "muon",
3295
3900
  config: (config) => {
3296
3901
  if (config.server?.watch === null) return null;
3297
3902
  return { server: { watch: createMuonWatchOptions(config.server?.watch) } };
3298
3903
  },
3299
- configResolved: (config) => {
3904
+ configResolved: async (config) => {
3300
3905
  resolvedConfig = config;
3906
+ resolvedPluginAccess = await resolveMuonPluginAccessOptions({
3907
+ root: config.root,
3908
+ configPath: resolveMuonConfigPathForViteCommand(config, options),
3909
+ pluginAccess: options.pluginAccess,
3910
+ ...config.command === "serve" ? { onConfigReadError: (error) => {
3911
+ config.logger.warn(`Muon project config will be ignored because it could not be read or parsed: ${getErrorMessage(error)}`);
3912
+ } } : {}
3913
+ });
3914
+ capabilityResolver = resolvedPluginAccess.mode === "validate" ? createMuonCapabilityModuleResolver(config.root, resolvedPluginAccess.capabilityOptions) : void 0;
3301
3915
  },
3916
+ resolveId: (source, importer) => capabilityResolver?.resolveId(source, importer)?.id,
3917
+ load: (id) => capabilityResolver?.load(id),
3302
3918
  configureServer: async (server) => {
3303
3919
  await startMuonViteBrowserBridge({
3304
3920
  server,
3305
3921
  pluginOptions: options,
3922
+ getRuntimePluginConfig: () => resolveMuonRuntimePluginConfig(capabilityResolver, resolvedPluginAccess),
3306
3923
  platform: process.platform,
3307
3924
  architecture: process.arch,
3308
3925
  environment: process.env
@@ -3313,7 +3930,7 @@ var muon = (options = {}) => {
3313
3930
  if (process.env["MUON_SUPPRESS_VITE_MUON_BUILD"] === "1") return;
3314
3931
  if (options.build === false) return;
3315
3932
  const buildOptions = typeof options.build === "object" ? options.build : {};
3316
- await buildMuonApp(createMuonBuildOptions(resolvedConfig, buildOptions));
3933
+ await buildMuonApp(createMuonBuildOptions(resolvedConfig, buildOptions, resolveMuonRuntimePluginConfig(capabilityResolver, resolvedPluginAccess)));
3317
3934
  }
3318
3935
  }, options);
3319
3936
  };
@@ -3330,7 +3947,7 @@ var createMuonWatchOptions = (watch) => ({
3330
3947
  ...watch ?? {},
3331
3948
  ignored: mergeMuonWatchIgnored(watch?.ignored)
3332
3949
  });
3333
- var createMuonBuildOptions = (config, buildOptions) => {
3950
+ var createMuonBuildOptions = (config, buildOptions, runtimePluginConfig) => {
3334
3951
  const outDir = isAbsolute(config.build.outDir) ? config.build.outDir : resolve(config.root, config.build.outDir);
3335
3952
  const options = {
3336
3953
  root: config.root,
@@ -3343,12 +3960,28 @@ var createMuonBuildOptions = (config, buildOptions) => {
3343
3960
  if (buildOptions.appId !== void 0) options.appId = buildOptions.appId;
3344
3961
  if (buildOptions.outputRoot !== void 0) options.outputRoot = buildOptions.outputRoot;
3345
3962
  if (buildOptions.configPath !== void 0) options.configPath = buildOptions.configPath;
3963
+ if (buildOptions.iconPath !== void 0) options.iconPath = buildOptions.iconPath;
3346
3964
  if (buildOptions.windowsResource !== void 0) options.windowsResource = buildOptions.windowsResource;
3347
3965
  if (buildOptions.linuxDesktop !== void 0) options.linuxDesktop = buildOptions.linuxDesktop;
3348
3966
  if (buildOptions.packageDirectory !== void 0) options.packageDirectory = buildOptions.packageDirectory;
3349
3967
  if (buildOptions.assetSalt !== void 0) options.assetSalt = buildOptions.assetSalt;
3968
+ options.runtimePluginConfig = runtimePluginConfig;
3350
3969
  return options;
3351
3970
  };
3971
+ var resolveMuonConfigPathForViteCommand = (config, options) => {
3972
+ if (config.command !== "build" || typeof options.build !== "object") return;
3973
+ return options.build.configPath;
3974
+ };
3975
+ var resolveMuonRuntimePluginConfig = (capabilityResolver, pluginAccess) => pluginAccess?.mode === "simple" ? {
3976
+ mode: "simple",
3977
+ ...pluginAccess.runtimeOverlay
3978
+ } : {
3979
+ ...capabilityResolver?.getRuntimePluginConfig() ?? {
3980
+ mode: "validate",
3981
+ capabilities: []
3982
+ },
3983
+ ...pluginAccess?.runtimeOverlay ?? {}
3984
+ };
3352
3985
  //#endregion
3353
3986
  export { muon as default };
3354
3987