muon-ui 0.7.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 (46) hide show
  1. package/README.md +9 -12
  2. package/dist/cli.cjs +291 -126
  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-prepare → muon-builder} +0 -0
  10. package/dist/native/linux-armhf/muon-bootstrap +0 -0
  11. package/dist/native/linux-armhf/{muon-prepare → 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/CREDITS.md +24 -0
  18. package/dist/runtime/linux-amd64/libmuon-ui.so +0 -0
  19. package/dist/runtime/linux-amd64/muon-core +0 -0
  20. package/dist/runtime/linux-arm64/CREDITS.md +24 -0
  21. package/dist/runtime/linux-arm64/libmuon-ui.so +0 -0
  22. package/dist/runtime/linux-arm64/muon-core +0 -0
  23. package/dist/runtime/linux-armhf/CREDITS.md +24 -0
  24. package/dist/runtime/linux-armhf/libmuon-ui.so +0 -0
  25. package/dist/runtime/linux-armhf/muon-core +0 -0
  26. package/dist/runtime/windows-amd64/CREDITS.md +24 -0
  27. package/dist/runtime/windows-amd64/libcardio.dll +0 -0
  28. package/dist/runtime/windows-amd64/libmuon-ui.dll +0 -0
  29. package/dist/runtime/windows-amd64/muon-core.exe +0 -0
  30. package/dist/runtime/windows-i686/CREDITS.md +24 -0
  31. package/dist/runtime/windows-i686/libcardio.dll +0 -0
  32. package/dist/runtime/windows-i686/libmuon-ui.dll +0 -0
  33. package/dist/runtime/windows-i686/muon-core.exe +0 -0
  34. package/dist/{vite-options-FFh0NWUa.cjs → vite-internals-HXgH7b5M.cjs} +1460 -121
  35. package/dist/vite-internals-HXgH7b5M.cjs.map +1 -0
  36. package/dist/vite.cjs +452 -10
  37. package/dist/vite.cjs.map +1 -1
  38. package/dist/vite.mjs +1552 -92
  39. package/dist/vite.mjs.map +1 -1
  40. package/muon.d.ts +141 -27
  41. package/package.json +14 -10
  42. package/vite.d.ts +276 -14
  43. package/dist/native/linux-amd64/muon-prepare +0 -0
  44. package/dist/native/windows-amd64/muon-prepare.exe +0 -0
  45. package/dist/native/windows-i686/muon-prepare.exe +0 -0
  46. package/dist/vite-options-FFh0NWUa.cjs.map +0 -1
package/dist/vite.mjs CHANGED
@@ -1,20 +1,21 @@
1
1
  /*!
2
2
  * name: muon-ui
3
- * version: 0.7.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: fe80718aaa617c0c0ea5c816bbe38d22bf2e8402
8
+ * git.commit.hash: 3437b522a51d8772713b72722b109c5ff30fc711
9
9
  */
10
- import { basename, dirname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
11
- import { constants, writeFileSync } from "node:fs";
10
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
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";
15
15
  import { fileURLToPath } from "node:url";
16
16
  import __screwUpDefaultImportModule0 from "adm-zip";
17
17
  import { spawn } from "node:child_process";
18
+ import * as __screwUpDefaultImportModule0$1 from "sharp";
18
19
  //#region \0rolldown/runtime.js
19
20
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
20
21
  //#endregion
@@ -1292,8 +1293,10 @@ var encodeTaggedBytes = (tag, bytes) => Buffer.concat([
1292
1293
  encodeVarUint(BigInt(bytes.length)),
1293
1294
  Buffer.from(bytes)
1294
1295
  ]);
1295
- var isJsonObject$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1296
+ var isJsonObject$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1296
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";
1297
1300
  var isHexString = (value) => value.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(value);
1298
1301
  var decodeHexString = (value) => {
1299
1302
  const bytes = Buffer.alloc(value.length / 2);
@@ -1303,6 +1306,8 @@ var decodeHexString = (value) => {
1303
1306
  var encodeKnownBinaryString = (path, value) => {
1304
1307
  if (isPath(path, "asset", "signature")) return value.length === 40 && isHexString(value) ? decodeHexString(value) : void 0;
1305
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;
1306
1311
  if (isPath(path, "browser", "backgroundColor")) {
1307
1312
  const hex = value.startsWith("#") ? value.slice(1) : value;
1308
1313
  return hex.length === 6 && isHexString(hex) ? decodeHexString(hex) : void 0;
@@ -1325,7 +1330,7 @@ var encodeTlvValue = (value, path) => {
1325
1330
  encodeVarUint(BigInt(value.length)),
1326
1331
  ...value.map((entry) => encodeTlvValue(entry, path))
1327
1332
  ]);
1328
- if (isJsonObject$2(value)) {
1333
+ if (isJsonObject$5(value)) {
1329
1334
  const entries = Object.entries(value);
1330
1335
  return Buffer.concat([
1331
1336
  Buffer.from([tlvObjectTag]),
@@ -1350,8 +1355,8 @@ var encodeMuonConfigTlv = (config) => encodeTlvValue(config, []);
1350
1355
  */
1351
1356
  var createMuonEmbeddedConfigSlot = (payload = void 0) => {
1352
1357
  if (payload !== void 0 && payload.length > muonEmbeddedConfigPayloadCapacity) throw new Error(`Encoded muon config exceeds the embedded slot capacity: ${payload.length} > ${muonEmbeddedConfigPayloadCapacity}`);
1353
- const slot = createEmptySlot();
1354
- if (payload === void 0) return slot;
1358
+ if (payload === void 0) return createEmptySlot();
1359
+ const slot = randomBytes(muonEmbeddedConfigSlotSize);
1355
1360
  Buffer.from(payload).copy(slot, 0);
1356
1361
  return slot;
1357
1362
  };
@@ -1363,9 +1368,12 @@ var createMuonEmbeddedConfigSlot = (payload = void 0) => {
1363
1368
  */
1364
1369
  var createMuonBootstrapEmbeddedConfigSlot = (payload = void 0) => {
1365
1370
  if (payload !== void 0 && payload.length > muonBootstrapEmbeddedConfigPayloadCapacity) throw new Error(`Encoded muon config exceeds the embedded bootstrap slot capacity: ${payload.length} > ${muonBootstrapEmbeddedConfigPayloadCapacity}`);
1366
- const slot = Buffer.alloc(muonBootstrapEmbeddedConfigSlotSize, 0);
1367
- muonBootstrapEmbeddedConfigEmptySlotMarker.copy(slot, 0);
1368
- 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);
1369
1377
  Buffer.from(payload).copy(slot, 0);
1370
1378
  return slot;
1371
1379
  };
@@ -1439,7 +1447,7 @@ var findMuonBootstrapEmbeddedConfigSlot = (content) => {
1439
1447
  if (candidate === void 0) throw new Error("Embedded muon-bootstrap config slot was not found.");
1440
1448
  return candidate;
1441
1449
  };
1442
- var fileExists$2 = async (path) => {
1450
+ var fileExists$6 = async (path) => {
1443
1451
  try {
1444
1452
  return (await stat(path)).isFile();
1445
1453
  } catch {
@@ -1455,10 +1463,10 @@ var resolveMuonConfigInputPath = async (configPath) => {
1455
1463
  "muon.json"
1456
1464
  ]) {
1457
1465
  const candidate = join(directory, fileName);
1458
- if (await fileExists$2(candidate)) return candidate;
1466
+ if (await fileExists$6(candidate)) return candidate;
1459
1467
  }
1460
1468
  }
1461
- if (!await fileExists$2(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}`);
1462
1470
  return configPath;
1463
1471
  };
1464
1472
  var readMuonConfigInput = async (configPath) => {
@@ -1571,7 +1579,7 @@ var targetDescriptors = {
1571
1579
  cefTarget: "linux64",
1572
1580
  os: "linux",
1573
1581
  arch: "amd64",
1574
- distributionDirectoryName: "dist-muon-linux-amd64",
1582
+ distributionDirectoryName: "dist-muon/linux-amd64",
1575
1583
  runtimeExecutableName: "muon-core",
1576
1584
  bootstrapExecutableName: "muon-bootstrap",
1577
1585
  launcherExtension: "",
@@ -1586,7 +1594,7 @@ var targetDescriptors = {
1586
1594
  cefTarget: "linuxarm",
1587
1595
  os: "linux",
1588
1596
  arch: "armhf",
1589
- distributionDirectoryName: "dist-muon-linux-armhf",
1597
+ distributionDirectoryName: "dist-muon/linux-armhf",
1590
1598
  runtimeExecutableName: "muon-core",
1591
1599
  bootstrapExecutableName: "muon-bootstrap",
1592
1600
  launcherExtension: "",
@@ -1601,7 +1609,7 @@ var targetDescriptors = {
1601
1609
  cefTarget: "linuxarm64",
1602
1610
  os: "linux",
1603
1611
  arch: "arm64",
1604
- distributionDirectoryName: "dist-muon-linux-arm64",
1612
+ distributionDirectoryName: "dist-muon/linux-arm64",
1605
1613
  runtimeExecutableName: "muon-core",
1606
1614
  bootstrapExecutableName: "muon-bootstrap",
1607
1615
  launcherExtension: "",
@@ -1616,7 +1624,7 @@ var targetDescriptors = {
1616
1624
  cefTarget: "windows32",
1617
1625
  os: "windows",
1618
1626
  arch: "i686",
1619
- distributionDirectoryName: "dist-muon-windows-i686",
1627
+ distributionDirectoryName: "dist-muon/windows-i686",
1620
1628
  runtimeExecutableName: "muon-core.exe",
1621
1629
  bootstrapExecutableName: "muon-bootstrap.exe",
1622
1630
  launcherExtension: ".exe",
@@ -1636,7 +1644,7 @@ var targetDescriptors = {
1636
1644
  cefTarget: "windows64",
1637
1645
  os: "windows",
1638
1646
  arch: "amd64",
1639
- distributionDirectoryName: "dist-muon-windows-amd64",
1647
+ distributionDirectoryName: "dist-muon/windows-amd64",
1640
1648
  runtimeExecutableName: "muon-core.exe",
1641
1649
  bootstrapExecutableName: "muon-bootstrap.exe",
1642
1650
  launcherExtension: ".exe",
@@ -1661,6 +1669,20 @@ Object.fromEntries(allMuonTargets.map((target) => [targetDescriptors[target].cef
1661
1669
  */
1662
1670
  var getMuonTargetDescriptor = (target) => targetDescriptors[target];
1663
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
+ /**
1664
1686
  * Normalizes a user supplied public Muon target.
1665
1687
  *
1666
1688
  * @param target Target value supplied by the user.
@@ -1707,8 +1729,8 @@ var getDefaultMuonPrepareTarget = (platform, architecture) => {
1707
1729
  throw new Error(`Unsupported Muon prepare target: platform=${platform}, arch=${architecture}`);
1708
1730
  }
1709
1731
  };
1710
- var getPrepareExecutableName = (platform) => platform === "win32" ? "muon-prepare.exe" : "muon-prepare";
1711
- var moduleDirectory$2 = typeof __dirname === "string" ? __dirname : dirname(fileURLToPath(import.meta.url));
1732
+ var getBuilderExecutableName = (platform) => platform === "win32" ? "muon-builder.exe" : "muon-builder";
1733
+ var moduleDirectory$4 = typeof __dirname === "string" ? __dirname : dirname(fileURLToPath(import.meta.url));
1712
1734
  var canExecute = async (path) => {
1713
1735
  try {
1714
1736
  await access(path, constants.X_OK);
@@ -1717,12 +1739,12 @@ var canExecute = async (path) => {
1717
1739
  return false;
1718
1740
  }
1719
1741
  };
1720
- var resolveMuonPrepareExecutable = async (options) => {
1721
- const explicit = options.prepareExecutablePath ?? options.environment.MUON_PREPARE_PATH;
1742
+ var resolveMuonBuilderExecutable = async (options) => {
1743
+ const explicit = options.prepareExecutablePath ?? options.environment.MUON_BUILDER_PATH;
1722
1744
  if (explicit !== void 0 && explicit !== "") return explicit;
1723
- const executableName = getPrepareExecutableName(process.platform);
1745
+ const executableName = getBuilderExecutableName(process.platform);
1724
1746
  const target = getDefaultMuonPrepareTarget(process.platform, process.arch);
1725
- const candidates = [join(moduleDirectory$2, "native", target, executableName), join(moduleDirectory$2, "..", "dist", "native", target, executableName)];
1747
+ const candidates = [join(moduleDirectory$4, "native", target, executableName), join(moduleDirectory$4, "..", "dist", "native", target, executableName)];
1726
1748
  for (const candidate of candidates) if (await canExecute(candidate)) return candidate;
1727
1749
  return candidates[0] ?? executableName;
1728
1750
  };
@@ -1741,14 +1763,100 @@ var createMuonPrepareArguments = (options) => {
1741
1763
  if (options.quiet) args.push("--quiet");
1742
1764
  return args;
1743
1765
  };
1744
- /**
1745
- * Invokes the native muon-prepare executable and returns the prepared runtime.
1746
- *
1747
- * @param options Native prepare invocation options.
1748
- * @returns Prepared runtime location.
1749
- */
1750
- var runMuonPrepare = async (options) => {
1751
- const child = spawn(await resolveMuonPrepareExecutable(options), createMuonPrepareArguments(options), {
1766
+ var spinnerFrames = [
1767
+ "-",
1768
+ "\\",
1769
+ "|",
1770
+ "/"
1771
+ ];
1772
+ var spinnerIntervalMilliseconds = 100;
1773
+ var terminalLineStart = "\r";
1774
+ var terminalClearLine = "\x1B[K";
1775
+ var isMuonPrepareStatusLine = (line) => {
1776
+ const trimmed = line.trimEnd();
1777
+ return trimmed.endsWith("...") || trimmed === "Failed to prepare CEF." || trimmed.startsWith("Downloading CEF binary:");
1778
+ };
1779
+ var shouldKeepMuonPrepareStatusAfterLine = (line) => line.startsWith("Muon files copied to staging:");
1780
+ var createPlainStderrForwarder = () => ({
1781
+ write: (chunk) => {
1782
+ process.stderr.write(chunk);
1783
+ },
1784
+ flush: () => {}
1785
+ });
1786
+ var createSpinnerStderrForwarder = () => {
1787
+ let pending = "";
1788
+ let activeStatus = void 0;
1789
+ let frameIndex = 0;
1790
+ let timer = void 0;
1791
+ const writeRaw = (chunk) => {
1792
+ process.stderr.write(chunk);
1793
+ };
1794
+ const stopTimer = () => {
1795
+ if (timer !== void 0) {
1796
+ clearInterval(timer);
1797
+ timer = void 0;
1798
+ }
1799
+ };
1800
+ const renderStatus = () => {
1801
+ if (activeStatus === void 0) return;
1802
+ writeRaw(`${terminalLineStart}${spinnerFrames[frameIndex] ?? spinnerFrames[0]} ${activeStatus}${terminalClearLine}`);
1803
+ frameIndex = (frameIndex + 1) % spinnerFrames.length;
1804
+ };
1805
+ const ensureTimer = () => {
1806
+ if (timer === void 0) timer = setInterval(renderStatus, spinnerIntervalMilliseconds);
1807
+ };
1808
+ const finishStatus = () => {
1809
+ if (activeStatus === void 0) return;
1810
+ const status = activeStatus;
1811
+ activeStatus = void 0;
1812
+ stopTimer();
1813
+ frameIndex = 0;
1814
+ writeRaw(`${terminalLineStart}${status}${terminalClearLine}\n`);
1815
+ };
1816
+ const startStatus = (status) => {
1817
+ if (activeStatus !== void 0 && activeStatus !== status) finishStatus();
1818
+ activeStatus = status;
1819
+ renderStatus();
1820
+ ensureTimer();
1821
+ };
1822
+ const writeLine = (line) => {
1823
+ const text = line.replace(/\r?\n$/, "");
1824
+ if (isMuonPrepareStatusLine(text)) {
1825
+ startStatus(text.trimEnd());
1826
+ return;
1827
+ }
1828
+ if (activeStatus !== void 0 && shouldKeepMuonPrepareStatusAfterLine(text)) {
1829
+ writeRaw(`${terminalLineStart}${terminalClearLine}${line.endsWith("\n") ? line : `${line}\n`}`);
1830
+ renderStatus();
1831
+ return;
1832
+ }
1833
+ finishStatus();
1834
+ writeRaw(line);
1835
+ };
1836
+ return {
1837
+ write: (chunk) => {
1838
+ pending += chunk;
1839
+ for (;;) {
1840
+ const newlineIndex = pending.indexOf("\n");
1841
+ if (newlineIndex < 0) break;
1842
+ const line = pending.slice(0, newlineIndex + 1);
1843
+ pending = pending.slice(newlineIndex + 1);
1844
+ writeLine(line);
1845
+ }
1846
+ },
1847
+ flush: () => {
1848
+ if (pending.length > 0) {
1849
+ const line = pending;
1850
+ pending = "";
1851
+ writeLine(line);
1852
+ }
1853
+ finishStatus();
1854
+ }
1855
+ };
1856
+ };
1857
+ var createMuonPrepareStderrForwarder = () => process.stderr.isTTY === true ? createSpinnerStderrForwarder() : createPlainStderrForwarder();
1858
+ var runMuonPrepareCommand = async (options) => {
1859
+ const child = spawn(await resolveMuonBuilderExecutable(options), [...options.args], {
1752
1860
  cwd: options.cwd,
1753
1861
  env: options.environment,
1754
1862
  stdio: [
@@ -1761,22 +1869,46 @@ var runMuonPrepare = async (options) => {
1761
1869
  let stderr = "";
1762
1870
  child.stdout.setEncoding("utf8");
1763
1871
  child.stderr.setEncoding("utf8");
1872
+ const stderrForwarder = options.quiet ? void 0 : createMuonPrepareStderrForwarder();
1764
1873
  child.stdout.on("data", (chunk) => {
1765
1874
  stdout += chunk;
1766
1875
  });
1767
1876
  child.stderr.on("data", (chunk) => {
1768
1877
  stderr += chunk;
1769
- if (!options.quiet) process.stderr.write(chunk);
1878
+ stderrForwarder?.write(chunk);
1770
1879
  });
1771
- const exitCode = await new Promise((resolvePromise, reject) => {
1772
- child.on("error", reject);
1773
- child.on("close", (code) => {
1774
- resolvePromise(code ?? 1);
1775
- });
1880
+ const exitCode = await (async () => {
1881
+ try {
1882
+ return await new Promise((resolvePromise, reject) => {
1883
+ child.on("error", reject);
1884
+ child.on("close", (code) => {
1885
+ resolvePromise(code ?? 1);
1886
+ });
1887
+ });
1888
+ } finally {
1889
+ stderrForwarder?.flush();
1890
+ }
1891
+ })();
1892
+ if (exitCode !== 0) throw new Error(`muon-builder failed with exit code ${exitCode}.\n${stderr.trim()}`);
1893
+ return stdout;
1894
+ };
1895
+ /**
1896
+ * Invokes the native muon-builder executable and returns the prepared runtime.
1897
+ *
1898
+ * @param options Native prepare invocation options.
1899
+ * @returns Prepared runtime location.
1900
+ */
1901
+ var runMuonPrepare = async (options) => {
1902
+ const args = createMuonPrepareArguments(options);
1903
+ const stdout = await runMuonPrepareCommand({
1904
+ prepareExecutablePath: options.prepareExecutablePath,
1905
+ environment: options.environment,
1906
+ cwd: options.cwd,
1907
+ quiet: options.quiet,
1908
+ args
1776
1909
  });
1777
- if (exitCode !== 0) throw new Error(`muon-prepare failed with exit code ${exitCode}.\n${stderr.trim()}`);
1778
1910
  const result = JSON.parse(stdout);
1779
- if (result.stagePath !== void 0 && typeof result.stagePath !== "string" || typeof result.muonPath !== "string" || typeof result.cefPath !== "string" || typeof result.cacheHit !== "boolean") throw new Error(`muon-prepare returned invalid JSON: ${stdout}`);
1911
+ if (result.stagePath !== void 0 && typeof result.stagePath !== "string" || typeof result.muonPath !== "string" || typeof result.cefPath !== "string" || typeof result.cacheHit !== "boolean") throw new Error(`muon-builder returned invalid JSON: ${stdout}`);
1780
1912
  return {
1781
1913
  ...result.stagePath === void 0 ? {} : { stagePath: result.stagePath },
1782
1914
  muonPath: result.muonPath,
@@ -1784,6 +1916,776 @@ var runMuonPrepare = async (options) => {
1784
1916
  cacheHit: result.cacheHit
1785
1917
  };
1786
1918
  };
1919
+ /**
1920
+ * Invokes the native muon-builder executable to write Windows PE resources.
1921
+ *
1922
+ * @param options Resource update invocation options.
1923
+ */
1924
+ var runMuonPrepareResourceUpdate = async (options) => {
1925
+ await runMuonPrepareCommand({
1926
+ prepareExecutablePath: options.prepareExecutablePath,
1927
+ environment: options.environment,
1928
+ cwd: options.cwd,
1929
+ quiet: options.quiet,
1930
+ args: [
1931
+ "resource",
1932
+ "--input",
1933
+ options.inputPath,
1934
+ "--updates-json",
1935
+ options.updatesJsonPath,
1936
+ "--output",
1937
+ options.outputPath,
1938
+ ...options.quiet ? ["--quiet"] : []
1939
+ ]
1940
+ });
1941
+ };
1942
+ //#endregion
1943
+ //#region src/windows-icon.ts
1944
+ var sharp = __resolveDefaultExport$1(__screwUpDefaultImportModule0$1, true);
1945
+ globalThis.__screwUpIsInCJS_27e672935538 = false;
1946
+ function __resolveDefaultExport$1(module, isESM) {
1947
+ const __isInCJS = typeof globalThis !== "undefined" && globalThis.__screwUpIsInCJS_27e672935538 === true;
1948
+ const maybe = module;
1949
+ const hasDefault = !!(maybe && typeof maybe === "object" && "default" in maybe);
1950
+ const unwrapNamespaceDefault = (value) => {
1951
+ if (!value || typeof value !== "object") return value;
1952
+ const inner = value;
1953
+ if ((inner.__esModule === true || typeof Symbol !== "undefined" && inner[Symbol.toStringTag] === "Module") && "default" in inner) return inner.default;
1954
+ return value;
1955
+ };
1956
+ const resolvedDefault = hasDefault ? unwrapNamespaceDefault(maybe.default) : void 0;
1957
+ if (__isInCJS) return hasDefault ? resolvedDefault ?? module : module;
1958
+ if (isESM) {
1959
+ if (hasDefault) return resolvedDefault;
1960
+ throw new Error("Default export not found.");
1961
+ }
1962
+ return hasDefault ? resolvedDefault ?? module : module;
1963
+ }
1964
+ var normalizedIconSize = 256;
1965
+ var windowsIconDibSizes = [
1966
+ 16,
1967
+ 24,
1968
+ 32,
1969
+ 48,
1970
+ 64,
1971
+ 128
1972
+ ];
1973
+ var pngSignature = Buffer.from([
1974
+ 137,
1975
+ 80,
1976
+ 78,
1977
+ 71,
1978
+ 13,
1979
+ 10,
1980
+ 26,
1981
+ 10
1982
+ ]);
1983
+ /**
1984
+ * Creates a normalized square PNG icon image.
1985
+ *
1986
+ * @param pngData Source PNG bytes.
1987
+ * @param source Diagnostic source label used in errors.
1988
+ * @param label User-facing diagnostic label.
1989
+ * @returns PNG bytes normalized to the Muon base icon size.
1990
+ * @remarks Non-square images are fitted into transparent padding without
1991
+ * stretching. The normalized PNG is the source for all platform-specific icon
1992
+ * derivatives.
1993
+ */
1994
+ var createNormalizedIconPngData = async (pngData, source, label) => {
1995
+ await assertPngData(pngData, source, label);
1996
+ try {
1997
+ return await sharp(pngData).resize(normalizedIconSize, normalizedIconSize, {
1998
+ fit: "contain",
1999
+ background: {
2000
+ r: 0,
2001
+ g: 0,
2002
+ b: 0,
2003
+ alpha: 0
2004
+ }
2005
+ }).ensureAlpha().png().toBuffer();
2006
+ } catch {
2007
+ throw new Error(`${label} must be a valid PNG: ${source}`);
2008
+ }
2009
+ };
2010
+ /**
2011
+ * Creates a normalized square PNG icon image for Windows resources.
2012
+ *
2013
+ * @param pngData Source PNG bytes.
2014
+ * @param source Diagnostic source label used in errors.
2015
+ * @returns PNG bytes normalized to the Muon base icon size.
2016
+ */
2017
+ var createNormalizedMuonIconPngData = async (pngData, source) => await createNormalizedIconPngData(pngData, source, "Windows resource icon");
2018
+ /**
2019
+ * Creates a Windows ICO file from PNG bytes.
2020
+ *
2021
+ * @param pngData Source PNG bytes.
2022
+ * @param source Diagnostic source label used in errors.
2023
+ * @returns ICO file bytes containing 16, 24, 32, 48, 64, 128, and 256 pixel images.
2024
+ * @remarks The 256px entry is PNG-compressed. Smaller entries are 32-bit DIB
2025
+ * images, matching the common Windows ICO layout recommended by Microsoft.
2026
+ */
2027
+ var createWindowsIconBufferFromPngData = async (pngData, source) => {
2028
+ const normalizedPng = await createNormalizedMuonIconPngData(pngData, source);
2029
+ const images = [];
2030
+ for (const size of windowsIconDibSizes) {
2031
+ const rawImage = await resizePngToRawRgba(normalizedPng, size, source);
2032
+ images.push({
2033
+ data: createDibIconImage(rawImage),
2034
+ size,
2035
+ bitCount: 32
2036
+ });
2037
+ }
2038
+ images.push({
2039
+ data: normalizedPng,
2040
+ size: normalizedIconSize,
2041
+ bitCount: 32
2042
+ });
2043
+ return createIco(images);
2044
+ };
2045
+ /**
2046
+ * Creates a Windows ICO file from a PNG file.
2047
+ *
2048
+ * @param sourcePath Source PNG file path.
2049
+ * @param outputPath Output ICO file path.
2050
+ * @returns Promise resolved when the ICO file has been written.
2051
+ */
2052
+ var createWindowsIconFromPngFile = async (sourcePath, outputPath) => {
2053
+ const icon = await createWindowsIconBufferFromPngData(await readFile(sourcePath), sourcePath);
2054
+ await mkdir(dirname(outputPath), { recursive: true });
2055
+ await writeFile(outputPath, icon);
2056
+ };
2057
+ var assertPngData = async (data, source, label) => {
2058
+ if (data.length === 0) throw new Error(`${label} PNG must not be empty: ${source}`);
2059
+ if (!data.subarray(0, pngSignature.length).equals(pngSignature)) throw new Error(`${label} must be a valid PNG: ${source}`);
2060
+ try {
2061
+ if ((await sharp(data).metadata()).format !== "png") throw new Error();
2062
+ } catch {
2063
+ throw new Error(`${label} must be a valid PNG: ${source}`);
2064
+ }
2065
+ };
2066
+ var resizePngToRawRgba = async (pngData, size, source) => {
2067
+ try {
2068
+ const { data, info } = await sharp(pngData).resize(size, size, {
2069
+ fit: "fill",
2070
+ kernel: "lanczos3"
2071
+ }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
2072
+ if (info.width !== size || info.height !== size || info.channels !== 4) throw new Error();
2073
+ return {
2074
+ data,
2075
+ width: info.width,
2076
+ height: info.height
2077
+ };
2078
+ } catch {
2079
+ throw new Error(`Failed to resize Windows resource icon PNG: ${source}`);
2080
+ }
2081
+ };
2082
+ var createDibIconImage = (image) => {
2083
+ const pixelBytes = image.width * image.height * 4;
2084
+ const maskBytes = Math.ceil(image.width / 32) * 4 * image.height;
2085
+ const output = Buffer.alloc(40 + pixelBytes + maskBytes);
2086
+ output.writeUInt32LE(40, 0);
2087
+ output.writeInt32LE(image.width, 4);
2088
+ output.writeInt32LE(image.height * 2, 8);
2089
+ output.writeUInt16LE(1, 12);
2090
+ output.writeUInt16LE(32, 14);
2091
+ output.writeUInt32LE(0, 16);
2092
+ output.writeUInt32LE(pixelBytes + maskBytes, 20);
2093
+ output.writeInt32LE(0, 24);
2094
+ output.writeInt32LE(0, 28);
2095
+ output.writeUInt32LE(0, 32);
2096
+ output.writeUInt32LE(0, 36);
2097
+ for (let y = 0; y < image.height; y += 1) {
2098
+ const sourceY = image.height - 1 - y;
2099
+ for (let x = 0; x < image.width; x += 1) {
2100
+ const sourceOffset = (sourceY * image.width + x) * 4;
2101
+ const outputOffset = 40 + (y * image.width + x) * 4;
2102
+ output[outputOffset] = image.data[sourceOffset + 2];
2103
+ output[outputOffset + 1] = image.data[sourceOffset + 1];
2104
+ output[outputOffset + 2] = image.data[sourceOffset];
2105
+ output[outputOffset + 3] = image.data[sourceOffset + 3];
2106
+ }
2107
+ }
2108
+ return output;
2109
+ };
2110
+ var createIco = (images) => {
2111
+ const directorySize = 6 + images.length * 16;
2112
+ let imageOffset = directorySize;
2113
+ const header = Buffer.alloc(directorySize);
2114
+ header.writeUInt16LE(0, 0);
2115
+ header.writeUInt16LE(1, 2);
2116
+ header.writeUInt16LE(images.length, 4);
2117
+ const imageData = [];
2118
+ images.forEach((image, index) => {
2119
+ const entryOffset = 6 + index * 16;
2120
+ header[entryOffset] = image.size === 256 ? 0 : image.size;
2121
+ header[entryOffset + 1] = image.size === 256 ? 0 : image.size;
2122
+ header[entryOffset + 2] = 0;
2123
+ header[entryOffset + 3] = 0;
2124
+ header.writeUInt16LE(1, entryOffset + 4);
2125
+ header.writeUInt16LE(image.bitCount, entryOffset + 6);
2126
+ header.writeUInt32LE(image.data.length, entryOffset + 8);
2127
+ header.writeUInt32LE(imageOffset, entryOffset + 12);
2128
+ imageOffset += image.data.length;
2129
+ imageData.push(image.data);
2130
+ });
2131
+ return Buffer.concat([header, ...imageData]);
2132
+ };
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
2196
+ //#region src/windows-resource.ts
2197
+ var defaultLanguage = 1033;
2198
+ var defaultCodePage = 1200;
2199
+ var moduleDirectory$3 = typeof __dirname === "string" ? __dirname : dirname(fileURLToPath(import.meta.url));
2200
+ /**
2201
+ * Merges Windows resource options while preserving field-level fallback.
2202
+ */
2203
+ var mergeMuonWindowsResourceOptions = (primary, fallback) => {
2204
+ if (primary === void 0) return fallback;
2205
+ if (fallback === void 0) return primary;
2206
+ const merged = { ...fallback };
2207
+ if (primary.iconPath !== void 0) merged.iconPath = primary.iconPath;
2208
+ if (primary.productName !== void 0) merged.productName = primary.productName;
2209
+ if (primary.fileDescription !== void 0) merged.fileDescription = primary.fileDescription;
2210
+ if (primary.companyName !== void 0) merged.companyName = primary.companyName;
2211
+ if (primary.version !== void 0) merged.version = primary.version;
2212
+ if (primary.copyright !== void 0) merged.copyright = primary.copyright;
2213
+ if (primary.language !== void 0) merged.language = primary.language;
2214
+ if (primary.codePage !== void 0) merged.codePage = primary.codePage;
2215
+ return merged;
2216
+ };
2217
+ /**
2218
+ * Resolves Windows resource metadata from options, config files, and package metadata.
2219
+ */
2220
+ var resolveMuonWindowsResource = async (input) => {
2221
+ const projectJson = await readProjectJson$1(input.root);
2222
+ const windowsSources = [
2223
+ createOptionsSource$1(input.options, input.root, "windowsResource"),
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,
2229
+ createProjectSource(projectJson, input.root),
2230
+ createPackageSource(input.packageJson, input.root)
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);
2237
+ const productName = resolveStringField$1(sources, "productName", input.defaults.productName);
2238
+ const fileDescription = resolveStringField$1(sources, "fileDescription", input.defaults.fileDescription);
2239
+ const companyName = resolveStringField$1(sources, "companyName", input.defaults.companyName);
2240
+ const version = resolveStringField$1(sources, "version", input.defaults.version);
2241
+ const copyright = resolveOptionalStringField(sources, "copyright", input.defaults.copyright);
2242
+ const language = resolveNumericField(sources, "language", defaultLanguage);
2243
+ const codePage = resolveNumericField(sources, "codePage", defaultCodePage);
2244
+ const fixedVersion = normalizeWindowsVersion(version);
2245
+ return {
2246
+ iconPath: await resolveWindowsIconPath(windowsSources, appIconSources, await resolveDefaultWindowsIcon(input.packageDirectory)),
2247
+ productName,
2248
+ fileDescription,
2249
+ companyName,
2250
+ version,
2251
+ fixedVersion,
2252
+ copyright,
2253
+ language,
2254
+ codePage
2255
+ };
2256
+ };
2257
+ /**
2258
+ * Removes build-only Windows resource settings from runtime config.
2259
+ */
2260
+ var stripBuildOnlyWindowsResourceConfig = (sourceConfig) => {
2261
+ const sourceWindows = sourceConfig.windows;
2262
+ if (!isJsonObject$4(sourceWindows)) return sourceConfig;
2263
+ const windowsConfig = {};
2264
+ for (const [key, value] of Object.entries(sourceWindows)) if (key !== "resource") windowsConfig[key] = value;
2265
+ const output = {};
2266
+ for (const [key, value] of Object.entries(sourceConfig)) if (key !== "windows") output[key] = value;
2267
+ if (Object.keys(windowsConfig).length > 0) output.windows = windowsConfig;
2268
+ return output;
2269
+ };
2270
+ /**
2271
+ * Returns true when the file has a Windows PE header.
2272
+ */
2273
+ var isWindowsPeExecutable = async (path) => {
2274
+ const content = await readFile(path);
2275
+ if (content.length < 64 || content.toString("ascii", 0, 2) !== "MZ") return false;
2276
+ const peOffset = content.readUInt32LE(60);
2277
+ return peOffset + 4 <= content.length && content.toString("ascii", peOffset, peOffset + 4) === "PE\0\0";
2278
+ };
2279
+ /**
2280
+ * Applies resolved resource metadata to a Windows PE executable in place.
2281
+ */
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) => {
2292
+ if (!await isWindowsPeExecutable(input.executablePath)) return false;
2293
+ const mode = (await stat(input.executablePath)).mode & 511;
2294
+ const tempDirectory = await mkdtemp(join(tmpdir(), "muon-windows-resource-"));
2295
+ const updatesJsonPath = join(tempDirectory, "updates.json");
2296
+ const outputPath = join(tempDirectory, "output.exe");
2297
+ const iconPath = join(tempDirectory, "icon.ico");
2298
+ try {
2299
+ await createWindowsIconFromPngFile(input.resource.iconPath, iconPath);
2300
+ await writeFile(updatesJsonPath, `${JSON.stringify(createUpdate(input.resource, iconPath), null, 2)}\n`);
2301
+ await runMuonPrepareResourceUpdate({
2302
+ inputPath: input.executablePath,
2303
+ updatesJsonPath,
2304
+ outputPath,
2305
+ quiet: true,
2306
+ prepareExecutablePath: void 0,
2307
+ environment: input.environment,
2308
+ cwd: input.cwd
2309
+ });
2310
+ await copyFile(outputPath, input.executablePath);
2311
+ await chmod(input.executablePath, mode === 0 ? 493 : mode);
2312
+ return true;
2313
+ } finally {
2314
+ await rm(tempDirectory, {
2315
+ recursive: true,
2316
+ force: true
2317
+ });
2318
+ }
2319
+ };
2320
+ var createEngraverUpdate = (resource, iconPath) => {
2321
+ const strings = {
2322
+ CompanyName: resource.companyName,
2323
+ FileDescription: resource.fileDescription,
2324
+ FileVersion: resource.version,
2325
+ ProductName: resource.productName,
2326
+ ProductVersion: resource.version
2327
+ };
2328
+ if (resource.copyright !== void 0) strings.LegalCopyright = resource.copyright;
2329
+ return {
2330
+ version: {
2331
+ language: resource.language,
2332
+ codePage: resource.codePage,
2333
+ fixed: {
2334
+ fileVersion: resource.fixedVersion,
2335
+ productVersion: resource.fixedVersion,
2336
+ fileOS: "windows32",
2337
+ fileType: "app"
2338
+ },
2339
+ strings
2340
+ },
2341
+ icons: createEngraverIcons(resource, iconPath)
2342
+ };
2343
+ };
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) => {
2351
+ const projectJsonPath = join(root, "project.json");
2352
+ if (!await fileExists$4(projectJsonPath)) return {};
2353
+ return await readJsonObjectFile$3(projectJsonPath, "project.json");
2354
+ };
2355
+ var readJsonObjectFile$3 = async (filePath, label) => {
2356
+ const parsed = (0, import_dist.parse)(await readFile(filePath, "utf8"));
2357
+ if (!isJsonObject$4(parsed)) throw new Error(`${label} must contain a JSON object: ${filePath}`);
2358
+ return parsed;
2359
+ };
2360
+ var createOptionsSource$1 = (options, directory, label) => {
2361
+ if (options === void 0) return;
2362
+ return {
2363
+ options: validateWindowsResourceOptions(options, label),
2364
+ directory
2365
+ };
2366
+ };
2367
+ var readConfigWindowsResourceSource = (config, directory, label) => {
2368
+ const windows = config.windows;
2369
+ if (windows === void 0) return;
2370
+ if (!isJsonObject$4(windows)) throw new Error(`${label} windows must be an object when present.`);
2371
+ const resource = windows.resource;
2372
+ if (resource === void 0) return;
2373
+ return createOptionsSource$1(validateWindowsResourceOptions(resource, `${label} windows.resource`), directory, `${label} windows.resource`);
2374
+ };
2375
+ var createProjectSource = (projectJson, directory) => {
2376
+ const topLevel = {};
2377
+ copyStringField(projectJson, "productName", topLevel, "productName");
2378
+ copyStringField(projectJson, "name", topLevel, "productName");
2379
+ copyStringField(projectJson, "fileDescription", topLevel, "fileDescription");
2380
+ copyStringField(projectJson, "description", topLevel, "fileDescription");
2381
+ copyStringField(projectJson, "companyName", topLevel, "companyName");
2382
+ copyStringField(projectJson, "version", topLevel, "version");
2383
+ copyStringField(projectJson, "copyright", topLevel, "copyright");
2384
+ const author = stringifyAuthor(projectJson.author);
2385
+ if (author !== void 0 && topLevel.companyName === void 0) topLevel.companyName = author;
2386
+ const options = mergeMuonWindowsResourceOptions(readConfigWindowsResourceSource(projectJson, directory, "project.json")?.options, topLevel);
2387
+ if (options === void 0 || Object.keys(options).length === 0) return;
2388
+ return {
2389
+ options,
2390
+ directory
2391
+ };
2392
+ };
2393
+ var createPackageSource = (packageJson, directory) => {
2394
+ const options = {};
2395
+ copyStringField(packageJson, "name", options, "productName");
2396
+ copyStringField(packageJson, "description", options, "fileDescription");
2397
+ copyStringField(packageJson, "version", options, "version");
2398
+ copyStringField(packageJson, "copyright", options, "copyright");
2399
+ const author = stringifyAuthor(packageJson.author);
2400
+ if (author !== void 0) options.companyName = author;
2401
+ return Object.keys(options).length === 0 ? void 0 : {
2402
+ options,
2403
+ directory
2404
+ };
2405
+ };
2406
+ var copyStringField = (source, sourceKey, target, targetKey) => {
2407
+ const value = source[sourceKey];
2408
+ if (typeof value === "string" && value.trim() !== "") target[targetKey] = value.trim();
2409
+ };
2410
+ var validateWindowsResourceOptions = (value, label) => {
2411
+ if (!isJsonObject$4(value)) throw new Error(`${label} must be an object.`);
2412
+ const output = {};
2413
+ copyOptionalStringResourceField(value, "iconPath", output);
2414
+ copyOptionalStringResourceField(value, "productName", output);
2415
+ copyOptionalStringResourceField(value, "fileDescription", output);
2416
+ copyOptionalStringResourceField(value, "companyName", output);
2417
+ copyOptionalStringResourceField(value, "version", output);
2418
+ copyOptionalStringResourceField(value, "copyright", output);
2419
+ copyOptionalNumberResourceField(value, "language", output, label);
2420
+ copyOptionalNumberResourceField(value, "codePage", output, label);
2421
+ return output;
2422
+ };
2423
+ var copyOptionalStringResourceField = (source, key, target) => {
2424
+ const value = source[key];
2425
+ if (value === void 0) return;
2426
+ if (typeof value !== "string") throw new Error(`${key} must be a string when present.`);
2427
+ if (value.trim() !== "") target[key] = value.trim();
2428
+ };
2429
+ var copyOptionalNumberResourceField = (source, key, target, label) => {
2430
+ const value = source[key];
2431
+ if (value === void 0) return;
2432
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > 65535) throw new Error(`${label} ${key} must be an integer from 0 to 65535.`);
2433
+ target[key] = value;
2434
+ };
2435
+ var resolveStringField$1 = (sources, field, fallback) => {
2436
+ return resolveOptionalStringField(sources, field, fallback) ?? fallback;
2437
+ };
2438
+ var resolveOptionalStringField = (sources, field, fallback) => {
2439
+ for (const source of sources) {
2440
+ const value = source.options[field];
2441
+ if (value !== void 0 && value.trim() !== "") return value.trim();
2442
+ }
2443
+ return fallback;
2444
+ };
2445
+ var resolveNumericField = (sources, field, fallback) => {
2446
+ for (const source of sources) {
2447
+ const value = source.options[field];
2448
+ if (value !== void 0) return value;
2449
+ }
2450
+ return fallback;
2451
+ };
2452
+ var resolveWindowsIconPath = async (windowsSources, appIconSources, defaultIconPath) => {
2453
+ for (const source of windowsSources) {
2454
+ const value = source.options.iconPath;
2455
+ if (value !== void 0 && value.trim() !== "") {
2456
+ const iconPath = resolveResourcePath$1(source.directory, value);
2457
+ await assertIconPath$1(iconPath);
2458
+ return iconPath;
2459
+ }
2460
+ }
2461
+ const appIconPath = await resolveMuonAppIconPath(appIconSources);
2462
+ if (appIconPath !== void 0) return appIconPath;
2463
+ await assertIconPath$1(defaultIconPath);
2464
+ return defaultIconPath;
2465
+ };
2466
+ var assertIconPath$1 = async (iconPath) => {
2467
+ if (extname(iconPath).toLowerCase() !== ".png") throw new Error(`Windows resource icon must be a .png file: ${iconPath}`);
2468
+ if (!await fileExists$4(iconPath)) throw new Error(`Windows resource icon does not exist: ${iconPath}`);
2469
+ await createNormalizedMuonIconPngData(await readFile(iconPath), iconPath);
2470
+ };
2471
+ var resolveDefaultWindowsIcon = async (packageDirectory) => {
2472
+ const sourceIconPath = join(moduleDirectory$3, "..", "..", "images", "muon-256.png");
2473
+ const candidates = [
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
2478
+ ];
2479
+ for (const candidate of candidates) if (await fileExists$4(candidate)) return candidate;
2480
+ return sourceIconPath;
2481
+ };
2482
+ var resolveResourcePath$1 = (directory, path) => {
2483
+ return isAbsolute(path) ? path : resolve(directory, path);
2484
+ };
2485
+ var normalizeWindowsVersion = (version) => {
2486
+ const normalized = version.trim();
2487
+ const match = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?(?:$|[-+])/.exec(normalized);
2488
+ if (match === null) throw new Error(`Windows resource version must start with numeric parts: ${version}`);
2489
+ const values = [
2490
+ match[1],
2491
+ match[2],
2492
+ match[3],
2493
+ match[4]
2494
+ ].map((value) => value === void 0 ? 0 : Number.parseInt(value, 10));
2495
+ for (const value of values) if (!Number.isInteger(value) || value < 0 || value > 65535) throw new Error(`Windows resource version part must be from 0 to 65535: ${version}`);
2496
+ return values.join(".");
2497
+ };
2498
+ var stringifyAuthor = (value) => {
2499
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
2500
+ if (!isJsonObject$4(value) || typeof value.name !== "string") return;
2501
+ const name = value.name.trim();
2502
+ if (name === "") return;
2503
+ return value.email === void 0 || typeof value.email !== "string" ? name : `${name} <${value.email}>`;
2504
+ };
2505
+ var fileExists$4 = async (path) => {
2506
+ try {
2507
+ await access(path, constants.F_OK);
2508
+ return true;
2509
+ } catch {
2510
+ return false;
2511
+ }
2512
+ };
2513
+ var isJsonObject$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2514
+ //#endregion
2515
+ //#region src/linux-desktop.ts
2516
+ var defaultIconFileName = "muon-desktop-icon.png";
2517
+ var defaultDesktopConfigFileName = "muon-desktop.json";
2518
+ var moduleDirectory$2 = typeof __dirname === "string" ? __dirname : dirname(fileURLToPath(import.meta.url));
2519
+ /**
2520
+ * Resolves Linux desktop integration metadata from options, config files, and
2521
+ * package metadata.
2522
+ */
2523
+ var resolveMuonLinuxDesktop = async (input) => {
2524
+ const projectJson = await readProjectJson(input.root);
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);
2531
+ return {
2532
+ desktopId: sanitizeDesktopId(resolveStringField(sources, "desktopId", input.defaults.desktopId)),
2533
+ name: resolveStringField(sources, "name", input.defaults.name),
2534
+ comment: resolveStringField(sources, "comment", input.defaults.comment),
2535
+ categories: resolveCategories(sources, input.defaults.categories),
2536
+ startupNotify: resolveBooleanField(sources, "startupNotify", input.defaults.startupNotify),
2537
+ iconPath: await resolveLinuxIconPath(sources, appIconSources, await resolveDefaultLinuxDesktopIcon(input.packageDirectory)),
2538
+ iconFileName: defaultIconFileName,
2539
+ configFileName: defaultDesktopConfigFileName
2540
+ };
2541
+ };
2542
+ /**
2543
+ * Removes build-only Linux desktop settings from runtime config.
2544
+ */
2545
+ var stripBuildOnlyLinuxDesktopConfig = (sourceConfig) => {
2546
+ const sourceLinux = sourceConfig.linux;
2547
+ if (!isJsonObject$3(sourceLinux)) return sourceConfig;
2548
+ const linuxConfig = {};
2549
+ for (const [key, value] of Object.entries(sourceLinux)) if (key !== "desktop") linuxConfig[key] = value;
2550
+ const output = {};
2551
+ for (const [key, value] of Object.entries(sourceConfig)) if (key !== "linux") output[key] = value;
2552
+ if (Object.keys(linuxConfig).length > 0) output.linux = linuxConfig;
2553
+ return output;
2554
+ };
2555
+ /**
2556
+ * Writes normalized Linux desktop sidecar files into a target distribution.
2557
+ */
2558
+ var writeLinuxDesktopDistributionFiles = async (outputPath, desktop) => {
2559
+ const iconData = await createNormalizedIconPngData(await readFile(desktop.iconPath), desktop.iconPath, "Linux desktop icon");
2560
+ await writeFile(join(outputPath, desktop.iconFileName), iconData);
2561
+ await writeFile(join(outputPath, desktop.configFileName), `${JSON.stringify({
2562
+ desktopId: desktop.desktopId,
2563
+ name: desktop.name,
2564
+ comment: desktop.comment,
2565
+ categories: desktop.categories,
2566
+ startupNotify: desktop.startupNotify,
2567
+ iconFileName: desktop.iconFileName
2568
+ }, void 0, 2)}\n`);
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
+ };
2580
+ var createOptionsSource = (options, directory, label) => {
2581
+ if (options === void 0) return;
2582
+ return {
2583
+ options: validateLinuxDesktopOptions(options, label),
2584
+ directory
2585
+ };
2586
+ };
2587
+ var readConfigLinuxDesktopSource = (config, directory, label) => {
2588
+ const linux = config.linux;
2589
+ if (linux === void 0) return;
2590
+ if (!isJsonObject$3(linux)) throw new Error(`${label} linux must be an object when present.`);
2591
+ const desktop = linux.desktop;
2592
+ if (desktop === void 0) return;
2593
+ return createOptionsSource(validateLinuxDesktopOptions(desktop, `${label} linux.desktop`), directory, `${label} linux.desktop`);
2594
+ };
2595
+ var validateLinuxDesktopOptions = (value, label) => {
2596
+ if (!isJsonObject$3(value)) throw new Error(`${label} must be an object.`);
2597
+ const output = {};
2598
+ copyOptionalStringDesktopField(value, "desktopId", output);
2599
+ copyOptionalStringDesktopField(value, "name", output);
2600
+ copyOptionalStringDesktopField(value, "comment", output);
2601
+ copyOptionalStringDesktopField(value, "iconPath", output);
2602
+ copyOptionalCategoriesField(value, output, label);
2603
+ copyOptionalBooleanDesktopField(value, "startupNotify", output, label);
2604
+ return output;
2605
+ };
2606
+ var copyOptionalStringDesktopField = (source, key, target) => {
2607
+ const value = source[key];
2608
+ if (value === void 0) return;
2609
+ if (typeof value !== "string") throw new Error(`${key} must be a string when present.`);
2610
+ if (value.trim() !== "") target[key] = value.trim();
2611
+ };
2612
+ var copyOptionalCategoriesField = (source, target, label) => {
2613
+ const value = source.categories;
2614
+ if (value === void 0) return;
2615
+ if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string" && entry.trim() !== "" && !entry.includes(";") && !entry.includes("\n") && !entry.includes("\r"))) throw new Error(`${label} categories must be a string array without semicolons when present.`);
2616
+ target.categories = value.map((entry) => entry.trim());
2617
+ };
2618
+ var copyOptionalBooleanDesktopField = (source, key, target, label) => {
2619
+ const value = source[key];
2620
+ if (value === void 0) return;
2621
+ if (typeof value !== "boolean") throw new Error(`${label} ${key} must be a boolean when present.`);
2622
+ target[key] = value;
2623
+ };
2624
+ var resolveStringField = (sources, field, fallback) => {
2625
+ for (const source of sources) {
2626
+ const value = source.options[field];
2627
+ if (value !== void 0 && value.trim() !== "") return value.trim();
2628
+ }
2629
+ return fallback;
2630
+ };
2631
+ var resolveCategories = (sources, fallback) => {
2632
+ for (const source of sources) if (source.options.categories !== void 0) return source.options.categories;
2633
+ return fallback;
2634
+ };
2635
+ var resolveBooleanField = (sources, field, fallback) => {
2636
+ for (const source of sources) {
2637
+ const value = source.options[field];
2638
+ if (value !== void 0) return value;
2639
+ }
2640
+ return fallback;
2641
+ };
2642
+ var resolveLinuxIconPath = async (sources, appIconSources, defaultIconPath) => {
2643
+ for (const source of sources) {
2644
+ const value = source.options.iconPath;
2645
+ if (value !== void 0 && value.trim() !== "") {
2646
+ const iconPath = resolveResourcePath(source.directory, value);
2647
+ await assertIconPath(iconPath, true);
2648
+ return iconPath;
2649
+ }
2650
+ }
2651
+ const appIconPath = await resolveMuonAppIconPath(appIconSources);
2652
+ if (appIconPath !== void 0) return appIconPath;
2653
+ if (defaultIconPath !== void 0 && await fileExists$3(defaultIconPath)) {
2654
+ await assertIconPath(defaultIconPath, false);
2655
+ return defaultIconPath;
2656
+ }
2657
+ throw new Error("Linux desktop icon does not exist.");
2658
+ };
2659
+ var assertIconPath = async (iconPath, required) => {
2660
+ if (extname(iconPath).toLowerCase() !== ".png") throw new Error(`Linux desktop icon must be a .png file: ${iconPath}`);
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");
2663
+ };
2664
+ var resolveDefaultLinuxDesktopIcon = async (packageDirectory) => {
2665
+ const candidates = [
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")
2670
+ ];
2671
+ for (const candidate of candidates) if (await fileExists$3(candidate)) return candidate;
2672
+ };
2673
+ var sanitizeDesktopId = (value) => {
2674
+ const sanitized = value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[.-]+/g, "").replace(/[.-]+$/g, "");
2675
+ return sanitized.length > 0 ? sanitized : "muon-app";
2676
+ };
2677
+ var resolveResourcePath = (directory, path) => {
2678
+ return isAbsolute(path) ? path : resolve(directory, path);
2679
+ };
2680
+ var fileExists$3 = async (path) => {
2681
+ try {
2682
+ await access(path, constants.F_OK);
2683
+ return true;
2684
+ } catch {
2685
+ return false;
2686
+ }
2687
+ };
2688
+ var isJsonObject$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1787
2689
  //#endregion
1788
2690
  //#region src/build.ts
1789
2691
  var AdmZip = __resolveDefaultExport(__screwUpDefaultImportModule0, false);
@@ -1806,7 +2708,7 @@ function __resolveDefaultExport(module, isESM) {
1806
2708
  }
1807
2709
  return hasDefault ? resolvedDefault ?? module : module;
1808
2710
  }
1809
- var defaultConfigFileNames = [
2711
+ var defaultConfigFileNames$1 = [
1810
2712
  "muon.json5",
1811
2713
  "muon.jsonc",
1812
2714
  "muon.json"
@@ -1843,18 +2745,58 @@ var buildMuonApp = async (options = {}) => {
1843
2745
  const appName = resolveAppName(packageJson, options.appName);
1844
2746
  const appId = resolveAppId(packageJson, options.appId);
1845
2747
  const buildConfig = await readBuildConfig(root, options.configPath);
1846
- 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);
2755
+ const windowsResource = await resolveMuonWindowsResource({
2756
+ root,
2757
+ packageDirectory,
2758
+ packageJson,
2759
+ muonConfig: sourceConfig,
2760
+ muonConfigDirectory: buildConfig.directory,
2761
+ options: options.windowsResource,
2762
+ appIconPath: options.iconPath,
2763
+ defaults: {
2764
+ productName: appName,
2765
+ fileDescription: appName,
2766
+ companyName: "Unknown",
2767
+ version: "0.0.0",
2768
+ copyright: void 0
2769
+ }
2770
+ });
2771
+ const linuxDesktop = await resolveMuonLinuxDesktop({
2772
+ root,
2773
+ packageDirectory,
2774
+ muonConfig: sourceConfig,
2775
+ muonConfigDirectory: buildConfig.directory,
2776
+ options: options.linuxDesktop,
2777
+ appIconPath: options.iconPath,
2778
+ defaults: {
2779
+ desktopId: appId,
2780
+ name: resolveLinuxDesktopDefaultName(packageJson, appName),
2781
+ comment: resolvePackageDescription(packageJson),
2782
+ categories: ["Utility"],
2783
+ startupNotify: true
2784
+ }
2785
+ });
1847
2786
  const salt = Buffer.from(options.assetSalt ?? randomBytes(assetSaltByteLength));
1848
2787
  const results = [];
1849
2788
  for (const target of targets) {
1850
2789
  const result = await buildMuonTarget({
1851
2790
  packageDirectory,
2791
+ root,
1852
2792
  outputRoot,
1853
2793
  appName,
1854
2794
  appId,
1855
2795
  target,
1856
2796
  assetInput,
1857
- sourceConfig: buildConfig.config,
2797
+ sourceConfig,
2798
+ windowsResource,
2799
+ linuxDesktop,
1858
2800
  salt
1859
2801
  });
1860
2802
  results.push(result);
@@ -1889,8 +2831,8 @@ var normalizeZipPrefix = (prefix) => {
1889
2831
  };
1890
2832
  var readPackageJson = async (root) => {
1891
2833
  const packageJsonPath = join(root, "package.json");
1892
- if (!await fileExists$1(packageJsonPath)) return {};
1893
- return await readJsonObjectFile(packageJsonPath, "package.json");
2834
+ if (!await fileExists$2(packageJsonPath)) return {};
2835
+ return await readJsonObjectFile$1(packageJsonPath, "package.json");
1894
2836
  };
1895
2837
  var resolvePackageName = (packageJson) => {
1896
2838
  return typeof packageJson.name === "string" ? packageJson.name : defaultAppName;
@@ -1912,6 +2854,8 @@ var sanitizeAppId = (value) => {
1912
2854
  const sanitized = (value.startsWith("@") ? value.slice(1) : value).trim().toLowerCase().replace("/", ".").replace(/[^a-z0-9._-]+/g, ".").replace(/^[.]+/g, "").replace(/[.]+$/g, "");
1913
2855
  return sanitized.length > 0 ? sanitized : defaultAppId;
1914
2856
  };
2857
+ var resolveLinuxDesktopDefaultName = (packageJson, appName) => typeof packageJson.name === "string" && packageJson.name.trim() !== "" ? packageJson.name.trim() : appName;
2858
+ var resolvePackageDescription = (packageJson) => typeof packageJson.description === "string" ? packageJson.description.trim() : "";
1915
2859
  var readBuildConfig = async (root, configPath) => {
1916
2860
  const resolvedConfigPath = await resolveConfigPath(root, configPath);
1917
2861
  if (resolvedConfigPath === void 0) return {
@@ -1919,14 +2863,27 @@ var readBuildConfig = async (root, configPath) => {
1919
2863
  directory: root
1920
2864
  };
1921
2865
  return {
1922
- config: await readJsonObjectFile(resolvedConfigPath, "Muon config file"),
2866
+ config: await readJsonObjectFile$1(resolvedConfigPath, "Muon config file"),
1923
2867
  directory: dirname(resolvedConfigPath)
1924
2868
  };
1925
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
+ };
1926
2883
  var readConfigAssetSourcePath = (sourceConfig) => {
1927
2884
  const sourceAsset = sourceConfig.asset;
1928
2885
  if (sourceAsset === void 0) return;
1929
- 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.");
1930
2887
  const sourceAssetPath = sourceAsset.sourcePath;
1931
2888
  if (sourceAssetPath === void 0) return;
1932
2889
  if (typeof sourceAssetPath !== "string") throw new Error("muon.json asset.sourcePath must be a string when present.");
@@ -1935,28 +2892,28 @@ var readConfigAssetSourcePath = (sourceConfig) => {
1935
2892
  var resolveConfigPath = async (root, configPath) => {
1936
2893
  if (configPath !== void 0) {
1937
2894
  const resolvedPath = resolve(root, configPath);
1938
- if (await fileExists$1(resolvedPath)) return resolvedPath;
2895
+ if (await fileExists$2(resolvedPath)) return resolvedPath;
1939
2896
  throw new Error(`Muon config file does not exist: ${resolvedPath}`);
1940
2897
  }
1941
- for (const fileName of defaultConfigFileNames) {
2898
+ for (const fileName of defaultConfigFileNames$1) {
1942
2899
  const candidatePath = join(root, fileName);
1943
- if (await fileExists$1(candidatePath)) return candidatePath;
2900
+ if (await fileExists$2(candidatePath)) return candidatePath;
1944
2901
  }
1945
2902
  };
1946
- var readJsonObjectFile = async (filePath, label) => {
2903
+ var readJsonObjectFile$1 = async (filePath, label) => {
1947
2904
  let content;
1948
2905
  try {
1949
2906
  content = await readFile(filePath, "utf8");
1950
2907
  } catch (error) {
1951
- 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)}`);
1952
2909
  }
1953
2910
  let parsed;
1954
2911
  try {
1955
2912
  parsed = (0, import_dist.parse)(content);
1956
2913
  } catch (error) {
1957
- 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)}`);
1958
2915
  }
1959
- 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}`);
1960
2917
  return parsed;
1961
2918
  };
1962
2919
  var buildMuonTarget = async (input) => {
@@ -1966,6 +2923,8 @@ var buildMuonTarget = async (input) => {
1966
2923
  const outputPath = join(input.outputRoot, descriptor.distributionDirectoryName);
1967
2924
  const launcherPath = join(outputPath, getLauncherFileName(input.appName, descriptor));
1968
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;
1969
2928
  await verifyTargetInputs({
1970
2929
  sourceRuntimePath,
1971
2930
  sourceBootstrapPath,
@@ -1984,8 +2943,11 @@ var buildMuonTarget = async (input) => {
1984
2943
  await chmod(join(outputPath, descriptor.runtimeExecutableName), executableMode);
1985
2944
  await copyFile(sourceBootstrapPath, launcherPath);
1986
2945
  await chmod(launcherPath, executableMode);
1987
- const asset = await writeAssetArchive(input.assetInput, assetZipPath, input.salt);
1988
- const embeddedConfig = createEmbeddedConfig(input.sourceConfig, asset, input.appId);
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);
1989
2951
  await withTemporaryConfig(embeddedConfig, async (configPath) => {
1990
2952
  await embedMuonConfigInRuntime({
1991
2953
  runtimePath: outputPath,
@@ -1998,13 +2960,29 @@ var buildMuonTarget = async (input) => {
1998
2960
  outputPath: void 0
1999
2961
  });
2000
2962
  });
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);
2001
2977
  return {
2002
2978
  target: input.target,
2003
2979
  distributionDirectoryName: descriptor.distributionDirectoryName,
2004
2980
  outputPath,
2005
2981
  launcherPath,
2006
2982
  asset,
2007
- embeddedConfig
2983
+ runtimeAppId,
2984
+ embeddedConfig,
2985
+ ...descriptor.os === "linux" ? { linuxDesktop: input.linuxDesktop } : {}
2008
2986
  };
2009
2987
  };
2010
2988
  var verifyTargetInputs = async (input) => {
@@ -2025,10 +3003,10 @@ var copyRuntimeFiles = async (sourceRuntimePath, outputPath, descriptor) => {
2025
3003
  }
2026
3004
  await copyFile(join(sourceRuntimePath, muonLicenseFileName), join(outputPath, muonLicenseFileName));
2027
3005
  };
2028
- var writeAssetArchive = async (input, outputPath, salt) => {
3006
+ var writeAssetArchive = async (input, outputPath, salt, extraEntries) => {
2029
3007
  const sourceStats = await statOrUndefined(input.sourcePath);
2030
3008
  if (sourceStats === void 0) throw new Error(`Muon asset source does not exist: ${input.sourcePath}`);
2031
- 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;
2032
3010
  if (archive === void 0) throw new Error(`Muon asset source is not a directory or file: ${input.sourcePath}`);
2033
3011
  await writeFile(outputPath, archive);
2034
3012
  return {
@@ -2038,10 +3016,30 @@ var writeAssetArchive = async (input, outputPath, salt) => {
2038
3016
  entryCount: sourceStats.isDirectory() ? readZipEntryCount(archive, outputPath) : readZipEntryCount(archive, input.sourcePath)
2039
3017
  };
2040
3018
  };
2041
- var createAssetArchiveFromDirectory = async (input) => {
3019
+ var createAssetArchiveFromDirectory = async (input, extraEntries) => {
2042
3020
  const entries = await collectZipEntries(input.sourcePath, input.prefix);
2043
3021
  if (entries.length === 0) throw new Error(`Muon asset source has no files: ${input.sourcePath}`);
2044
- 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;
2045
3043
  };
2046
3044
  var readZipEntryCount = (archive, sourcePath) => {
2047
3045
  const endSignature = 101010256;
@@ -2079,13 +3077,20 @@ var createZipArchive = (entries) => {
2079
3077
  for (const entry of entries) zip.addFile(entry.name, entry.data);
2080
3078
  return zip.toBuffer();
2081
3079
  };
2082
- var createEmbeddedConfig = (sourceConfig, asset, appId) => {
3080
+ var createEmbeddedConfig = (sourceConfig, asset, appId, desktopId, initialTitleBarIcon) => {
2083
3081
  const sourceAsset = sourceConfig.asset;
2084
- 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.");
2085
3083
  const sourceBootstrap = sourceConfig.bootstrap;
2086
- 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.");
2087
3088
  return {
2088
- ...sourceConfig,
3089
+ ...runtimeConfig,
3090
+ browser: {
3091
+ ...sourceBrowser ?? {},
3092
+ initialTitleBarIcon
3093
+ },
2089
3094
  asset: {
2090
3095
  ...sourceAsset ?? {},
2091
3096
  sourcePath: appConfigSourcePath,
@@ -2094,10 +3099,22 @@ var createEmbeddedConfig = (sourceConfig, asset, appId) => {
2094
3099
  },
2095
3100
  bootstrap: {
2096
3101
  ...sourceBootstrap ?? {},
2097
- appId
3102
+ appId,
3103
+ desktopId
2098
3104
  }
2099
3105
  };
2100
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
+ };
2101
3118
  var withTemporaryConfig = async (config, callback) => {
2102
3119
  const tempDirectory = await mkdtemp(join(tmpdir(), "muon-build-config-"));
2103
3120
  const configPath = join(tempDirectory, "muon.json");
@@ -2126,7 +3143,7 @@ var statOrUndefined = async (path) => {
2126
3143
  return;
2127
3144
  }
2128
3145
  };
2129
- var fileExists$1 = async (path) => {
3146
+ var fileExists$2 = async (path) => {
2130
3147
  try {
2131
3148
  await access(path, constants.F_OK);
2132
3149
  return true;
@@ -2134,15 +3151,420 @@ var fileExists$1 = async (path) => {
2134
3151
  return false;
2135
3152
  }
2136
3153
  };
2137
- var isJsonObject$1 = (value) => {
3154
+ var isJsonObject$2 = (value) => {
2138
3155
  return typeof value === "object" && value !== null && !Array.isArray(value);
2139
3156
  };
2140
- 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
+ };
2141
3563
  //#endregion
2142
3564
  //#region src/gitignore.ts
2143
3565
  var muonGitignoreEntries = [
2144
3566
  ".muon/",
2145
- "dist-muon-*/",
3567
+ "dist-muon/",
2146
3568
  "artifacts/"
2147
3569
  ];
2148
3570
  var muonGitignoreEntryAliases = {
@@ -2152,11 +3574,13 @@ var muonGitignoreEntryAliases = {
2152
3574
  ".muon",
2153
3575
  "/.muon"
2154
3576
  ],
2155
- "dist-muon-*/": [
2156
- "dist-muon-*/",
2157
- "/dist-muon-*/",
2158
- "dist-muon-*",
2159
- "/dist-muon-*"
3577
+ "dist-muon/": [
3578
+ "dist-muon/",
3579
+ "/dist-muon/",
3580
+ "dist-muon",
3581
+ "/dist-muon",
3582
+ "dist-muon/*",
3583
+ "/dist-muon/*"
2160
3584
  ],
2161
3585
  "artifacts/": [
2162
3586
  "artifacts/",
@@ -2299,7 +3723,7 @@ var getWebSocketOrigin = (startUrl) => {
2299
3723
  else if (url.protocol === "http:") url.protocol = "ws:";
2300
3724
  return url.origin;
2301
3725
  };
2302
- var createMuonOverrideConfig = (startUrl, enableDebugger) => {
3726
+ var createMuonOverrideConfig = (startUrl, enableDebugger, runtimePluginConfig) => {
2303
3727
  const origin = new URL(startUrl).origin;
2304
3728
  return {
2305
3729
  ...enableDebugger ? { cdp: { enable: true } } : {},
@@ -2308,19 +3732,22 @@ var createMuonOverrideConfig = (startUrl, enableDebugger) => {
2308
3732
  ...enableDebugger ? { keybind: {
2309
3733
  devtools: "f12",
2310
3734
  recycle: "ctrl+f12"
2311
- } } : {},
2312
- plugin: { allow: [`${origin}/**`] }
3735
+ } } : {}
3736
+ },
3737
+ plugin: {
3738
+ ...runtimePluginConfig,
3739
+ pages: [`${origin}/**`]
2313
3740
  },
2314
3741
  network: { allow: [`${origin}/**`, `${getWebSocketOrigin(startUrl)}/**`] }
2315
3742
  };
2316
3743
  };
2317
- var writeMuonOverrideConfig = (server, overrideConfigPath, enableDebugger) => {
3744
+ var writeMuonOverrideConfig = (server, overrideConfigPath, pluginOptions, getRuntimePluginConfig) => {
2318
3745
  const startUrl = getBaseUrl(server);
2319
3746
  if (startUrl === void 0) {
2320
3747
  server.config.logger.warn("Muon Vite plugin could not resolve a Vite URL.");
2321
3748
  return false;
2322
3749
  }
2323
- writeFileSync(overrideConfigPath, `${JSON.stringify(createMuonOverrideConfig(startUrl, enableDebugger), null, 2)}\n`);
3750
+ writeFileSync(overrideConfigPath, `${JSON.stringify(createMuonOverrideConfig(startUrl, pluginOptions.enableDebugger !== false, getRuntimePluginConfig()), null, 2)}\n`);
2324
3751
  return true;
2325
3752
  };
2326
3753
  var createRuntimePaths = async (server, stagePath, platform, projectConfigPath) => {
@@ -2342,7 +3769,7 @@ var fileExists = async (path) => {
2342
3769
  }
2343
3770
  };
2344
3771
  var isJsonObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2345
- var getErrorMessage = (error) => error instanceof Error ? error.message : String(error);
3772
+ var getErrorMessage$1 = (error) => error instanceof Error ? error.message : String(error);
2346
3773
  var resolveProjectConfigPath = async (server) => {
2347
3774
  for (const fileName of defaultProjectConfigFileNames) {
2348
3775
  const candidatePath = join(server.config.root, fileName);
@@ -2351,7 +3778,7 @@ var resolveProjectConfigPath = async (server) => {
2351
3778
  if (!isJsonObject((0, import_dist.parse)(await readFile(candidatePath, "utf8")))) throw new Error("muon config root must be an object");
2352
3779
  return candidatePath;
2353
3780
  } catch (error) {
2354
- 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)}`);
2355
3782
  return;
2356
3783
  }
2357
3784
  }
@@ -2379,11 +3806,11 @@ var launchMuon = (paths, platform, server) => {
2379
3806
  windowsHide: false
2380
3807
  });
2381
3808
  child.once("error", (error) => {
2382
- server.config.logger.warn(`Muon startup failed: ${getErrorMessage(error)}`);
3809
+ server.config.logger.warn(`Muon startup failed: ${getErrorMessage$1(error)}`);
2383
3810
  });
2384
3811
  child.unref();
2385
3812
  };
2386
- var startMuonViteBrowserBridge = async ({ server, pluginOptions, platform, architecture, environment }) => {
3813
+ var startMuonViteBrowserBridge = async ({ server, pluginOptions, getRuntimePluginConfig, platform, architecture, environment }) => {
2387
3814
  await ensureMuonGitignoreEntry(server.config.root);
2388
3815
  if (pluginOptions.open === false || server.httpServer === null) return;
2389
3816
  const target = getDefaultMuonPrepareTarget(platform, architecture);
@@ -2403,7 +3830,7 @@ var startMuonViteBrowserBridge = async ({ server, pluginOptions, platform, archi
2403
3830
  environment,
2404
3831
  cwd: server.config.root
2405
3832
  });
2406
- if (preparedRuntime.stagePath === void 0) throw new Error("muon-prepare did not return a staged runtime path.");
3833
+ if (preparedRuntime.stagePath === void 0) throw new Error("muon-builder did not return a staged runtime path.");
2407
3834
  const paths = await createRuntimePaths(server, preparedRuntime.stagePath, platform, await resolveProjectConfigPath(server));
2408
3835
  await writeLaunchScript(paths, platform);
2409
3836
  let cleanupPromise = void 0;
@@ -2429,7 +3856,7 @@ var startMuonViteBrowserBridge = async ({ server, pluginOptions, platform, archi
2429
3856
  cleanup();
2430
3857
  });
2431
3858
  server.httpServer.once("listening", () => {
2432
- if (writeMuonOverrideConfig(server, paths.overrideConfigPath, pluginOptions.enableDebugger !== false)) launchMuon(paths, platform, server);
3859
+ if (writeMuonOverrideConfig(server, paths.overrideConfigPath, pluginOptions, getRuntimePluginConfig)) launchMuon(paths, platform, server);
2433
3860
  });
2434
3861
  };
2435
3862
  //#endregion
@@ -2456,28 +3883,43 @@ var attachMuonVitePluginOptions = (plugin, options) => {
2456
3883
  };
2457
3884
  //#endregion
2458
3885
  //#region src/vite.ts
2459
- var suppressViteMuonBuildEnvironmentKey = "MUON_SUPPRESS_VITE_MUON_BUILD";
3886
+ var getErrorMessage = (error) => error instanceof Error ? error.message : String(error);
2460
3887
  /**
2461
3888
  * Creates a Vite plugin that launches Muon during Vite dev startup.
2462
3889
  *
2463
3890
  * @param options Muon plugin options used for development startup and build.
2464
3891
  * @returns Vite plugin instance.
3892
+ * @defaultValue `options` defaults to `{}`.
2465
3893
  */
2466
3894
  var muon = (options = {}) => {
2467
3895
  let resolvedConfig = void 0;
3896
+ let capabilityResolver = void 0;
3897
+ let resolvedPluginAccess = void 0;
2468
3898
  return attachMuonVitePluginOptions({
2469
3899
  name: "muon",
2470
3900
  config: (config) => {
2471
3901
  if (config.server?.watch === null) return null;
2472
3902
  return { server: { watch: createMuonWatchOptions(config.server?.watch) } };
2473
3903
  },
2474
- configResolved: (config) => {
3904
+ configResolved: async (config) => {
2475
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;
2476
3915
  },
3916
+ resolveId: (source, importer) => capabilityResolver?.resolveId(source, importer)?.id,
3917
+ load: (id) => capabilityResolver?.load(id),
2477
3918
  configureServer: async (server) => {
2478
3919
  await startMuonViteBrowserBridge({
2479
3920
  server,
2480
3921
  pluginOptions: options,
3922
+ getRuntimePluginConfig: () => resolveMuonRuntimePluginConfig(capabilityResolver, resolvedPluginAccess),
2481
3923
  platform: process.platform,
2482
3924
  architecture: process.arch,
2483
3925
  environment: process.env
@@ -2485,10 +3927,10 @@ var muon = (options = {}) => {
2485
3927
  },
2486
3928
  closeBundle: async () => {
2487
3929
  if (resolvedConfig === void 0 || resolvedConfig.command !== "build") return;
2488
- if (process.env[suppressViteMuonBuildEnvironmentKey] === "1") return;
3930
+ if (process.env["MUON_SUPPRESS_VITE_MUON_BUILD"] === "1") return;
2489
3931
  if (options.build === false) return;
2490
3932
  const buildOptions = typeof options.build === "object" ? options.build : {};
2491
- await buildMuonApp(createMuonBuildOptions(resolvedConfig, buildOptions));
3933
+ await buildMuonApp(createMuonBuildOptions(resolvedConfig, buildOptions, resolveMuonRuntimePluginConfig(capabilityResolver, resolvedPluginAccess)));
2492
3934
  }
2493
3935
  }, options);
2494
3936
  };
@@ -2505,7 +3947,7 @@ var createMuonWatchOptions = (watch) => ({
2505
3947
  ...watch ?? {},
2506
3948
  ignored: mergeMuonWatchIgnored(watch?.ignored)
2507
3949
  });
2508
- var createMuonBuildOptions = (config, buildOptions) => {
3950
+ var createMuonBuildOptions = (config, buildOptions, runtimePluginConfig) => {
2509
3951
  const outDir = isAbsolute(config.build.outDir) ? config.build.outDir : resolve(config.root, config.build.outDir);
2510
3952
  const options = {
2511
3953
  root: config.root,
@@ -2518,10 +3960,28 @@ var createMuonBuildOptions = (config, buildOptions) => {
2518
3960
  if (buildOptions.appId !== void 0) options.appId = buildOptions.appId;
2519
3961
  if (buildOptions.outputRoot !== void 0) options.outputRoot = buildOptions.outputRoot;
2520
3962
  if (buildOptions.configPath !== void 0) options.configPath = buildOptions.configPath;
3963
+ if (buildOptions.iconPath !== void 0) options.iconPath = buildOptions.iconPath;
3964
+ if (buildOptions.windowsResource !== void 0) options.windowsResource = buildOptions.windowsResource;
3965
+ if (buildOptions.linuxDesktop !== void 0) options.linuxDesktop = buildOptions.linuxDesktop;
2521
3966
  if (buildOptions.packageDirectory !== void 0) options.packageDirectory = buildOptions.packageDirectory;
2522
3967
  if (buildOptions.assetSalt !== void 0) options.assetSalt = buildOptions.assetSalt;
3968
+ options.runtimePluginConfig = runtimePluginConfig;
2523
3969
  return options;
2524
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
+ };
2525
3985
  //#endregion
2526
3986
  export { muon as default };
2527
3987