muon-ui 0.7.0 → 0.8.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 (43) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.cjs +269 -120
  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-bootstrap.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/muon-core +0 -0
  19. package/dist/runtime/linux-arm64/CREDITS.md +24 -0
  20. package/dist/runtime/linux-arm64/muon-core +0 -0
  21. package/dist/runtime/linux-armhf/CREDITS.md +24 -0
  22. package/dist/runtime/linux-armhf/muon-core +0 -0
  23. package/dist/runtime/windows-amd64/CREDITS.md +24 -0
  24. package/dist/runtime/windows-amd64/libcardio.dll +0 -0
  25. package/dist/runtime/windows-amd64/libmuon-ui.dll +0 -0
  26. package/dist/runtime/windows-amd64/muon-core.exe +0 -0
  27. package/dist/runtime/windows-i686/CREDITS.md +24 -0
  28. package/dist/runtime/windows-i686/libcardio.dll +0 -0
  29. package/dist/runtime/windows-i686/libmuon-ui.dll +0 -0
  30. package/dist/runtime/windows-i686/muon-core.exe +0 -0
  31. package/dist/{vite-options-FFh0NWUa.cjs → vite-internals-ChWiL2TL.cjs} +1225 -102
  32. package/dist/vite-internals-ChWiL2TL.cjs.map +1 -0
  33. package/dist/vite.cjs +10 -8
  34. package/dist/vite.cjs.map +1 -1
  35. package/dist/vite.mjs +876 -49
  36. package/dist/vite.mjs.map +1 -1
  37. package/muon.d.ts +129 -27
  38. package/package.json +14 -10
  39. package/vite.d.ts +165 -13
  40. package/dist/native/linux-amd64/muon-prepare +0 -0
  41. package/dist/native/windows-amd64/muon-prepare.exe +0 -0
  42. package/dist/native/windows-i686/muon-prepare.exe +0 -0
  43. package/dist/vite-options-FFh0NWUa.cjs.map +0 -1
package/dist/vite.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  /*!
2
2
  * name: muon-ui
3
- * version: 0.7.0
3
+ * version: 0.8.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: 9bb6148e155dab16c4ca441073b144e21981b038
9
9
  */
10
- import { basename, dirname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
10
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
11
11
  import { constants, 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";
@@ -15,6 +15,7 @@ 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,7 +1293,7 @@ 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$4 = (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;
1297
1298
  var isHexString = (value) => value.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(value);
1298
1299
  var decodeHexString = (value) => {
@@ -1325,7 +1326,7 @@ var encodeTlvValue = (value, path) => {
1325
1326
  encodeVarUint(BigInt(value.length)),
1326
1327
  ...value.map((entry) => encodeTlvValue(entry, path))
1327
1328
  ]);
1328
- if (isJsonObject$2(value)) {
1329
+ if (isJsonObject$4(value)) {
1329
1330
  const entries = Object.entries(value);
1330
1331
  return Buffer.concat([
1331
1332
  Buffer.from([tlvObjectTag]),
@@ -1439,7 +1440,7 @@ var findMuonBootstrapEmbeddedConfigSlot = (content) => {
1439
1440
  if (candidate === void 0) throw new Error("Embedded muon-bootstrap config slot was not found.");
1440
1441
  return candidate;
1441
1442
  };
1442
- var fileExists$2 = async (path) => {
1443
+ var fileExists$4 = async (path) => {
1443
1444
  try {
1444
1445
  return (await stat(path)).isFile();
1445
1446
  } catch {
@@ -1455,10 +1456,10 @@ var resolveMuonConfigInputPath = async (configPath) => {
1455
1456
  "muon.json"
1456
1457
  ]) {
1457
1458
  const candidate = join(directory, fileName);
1458
- if (await fileExists$2(candidate)) return candidate;
1459
+ if (await fileExists$4(candidate)) return candidate;
1459
1460
  }
1460
1461
  }
1461
- if (!await fileExists$2(configPath)) throw new Error(`muon config does not exist: ${configPath}`);
1462
+ if (!await fileExists$4(configPath)) throw new Error(`muon config does not exist: ${configPath}`);
1462
1463
  return configPath;
1463
1464
  };
1464
1465
  var readMuonConfigInput = async (configPath) => {
@@ -1571,7 +1572,7 @@ var targetDescriptors = {
1571
1572
  cefTarget: "linux64",
1572
1573
  os: "linux",
1573
1574
  arch: "amd64",
1574
- distributionDirectoryName: "dist-muon-linux-amd64",
1575
+ distributionDirectoryName: "dist-muon/linux-amd64",
1575
1576
  runtimeExecutableName: "muon-core",
1576
1577
  bootstrapExecutableName: "muon-bootstrap",
1577
1578
  launcherExtension: "",
@@ -1586,7 +1587,7 @@ var targetDescriptors = {
1586
1587
  cefTarget: "linuxarm",
1587
1588
  os: "linux",
1588
1589
  arch: "armhf",
1589
- distributionDirectoryName: "dist-muon-linux-armhf",
1590
+ distributionDirectoryName: "dist-muon/linux-armhf",
1590
1591
  runtimeExecutableName: "muon-core",
1591
1592
  bootstrapExecutableName: "muon-bootstrap",
1592
1593
  launcherExtension: "",
@@ -1601,7 +1602,7 @@ var targetDescriptors = {
1601
1602
  cefTarget: "linuxarm64",
1602
1603
  os: "linux",
1603
1604
  arch: "arm64",
1604
- distributionDirectoryName: "dist-muon-linux-arm64",
1605
+ distributionDirectoryName: "dist-muon/linux-arm64",
1605
1606
  runtimeExecutableName: "muon-core",
1606
1607
  bootstrapExecutableName: "muon-bootstrap",
1607
1608
  launcherExtension: "",
@@ -1616,7 +1617,7 @@ var targetDescriptors = {
1616
1617
  cefTarget: "windows32",
1617
1618
  os: "windows",
1618
1619
  arch: "i686",
1619
- distributionDirectoryName: "dist-muon-windows-i686",
1620
+ distributionDirectoryName: "dist-muon/windows-i686",
1620
1621
  runtimeExecutableName: "muon-core.exe",
1621
1622
  bootstrapExecutableName: "muon-bootstrap.exe",
1622
1623
  launcherExtension: ".exe",
@@ -1636,7 +1637,7 @@ var targetDescriptors = {
1636
1637
  cefTarget: "windows64",
1637
1638
  os: "windows",
1638
1639
  arch: "amd64",
1639
- distributionDirectoryName: "dist-muon-windows-amd64",
1640
+ distributionDirectoryName: "dist-muon/windows-amd64",
1640
1641
  runtimeExecutableName: "muon-core.exe",
1641
1642
  bootstrapExecutableName: "muon-bootstrap.exe",
1642
1643
  launcherExtension: ".exe",
@@ -1707,8 +1708,8 @@ var getDefaultMuonPrepareTarget = (platform, architecture) => {
1707
1708
  throw new Error(`Unsupported Muon prepare target: platform=${platform}, arch=${architecture}`);
1708
1709
  }
1709
1710
  };
1710
- var getPrepareExecutableName = (platform) => platform === "win32" ? "muon-prepare.exe" : "muon-prepare";
1711
- var moduleDirectory$2 = typeof __dirname === "string" ? __dirname : dirname(fileURLToPath(import.meta.url));
1711
+ var getBuilderExecutableName = (platform) => platform === "win32" ? "muon-builder.exe" : "muon-builder";
1712
+ var moduleDirectory$4 = typeof __dirname === "string" ? __dirname : dirname(fileURLToPath(import.meta.url));
1712
1713
  var canExecute = async (path) => {
1713
1714
  try {
1714
1715
  await access(path, constants.X_OK);
@@ -1717,12 +1718,12 @@ var canExecute = async (path) => {
1717
1718
  return false;
1718
1719
  }
1719
1720
  };
1720
- var resolveMuonPrepareExecutable = async (options) => {
1721
- const explicit = options.prepareExecutablePath ?? options.environment.MUON_PREPARE_PATH;
1721
+ var resolveMuonBuilderExecutable = async (options) => {
1722
+ const explicit = options.prepareExecutablePath ?? options.environment.MUON_BUILDER_PATH;
1722
1723
  if (explicit !== void 0 && explicit !== "") return explicit;
1723
- const executableName = getPrepareExecutableName(process.platform);
1724
+ const executableName = getBuilderExecutableName(process.platform);
1724
1725
  const target = getDefaultMuonPrepareTarget(process.platform, process.arch);
1725
- const candidates = [join(moduleDirectory$2, "native", target, executableName), join(moduleDirectory$2, "..", "dist", "native", target, executableName)];
1726
+ const candidates = [join(moduleDirectory$4, "native", target, executableName), join(moduleDirectory$4, "..", "dist", "native", target, executableName)];
1726
1727
  for (const candidate of candidates) if (await canExecute(candidate)) return candidate;
1727
1728
  return candidates[0] ?? executableName;
1728
1729
  };
@@ -1741,14 +1742,100 @@ var createMuonPrepareArguments = (options) => {
1741
1742
  if (options.quiet) args.push("--quiet");
1742
1743
  return args;
1743
1744
  };
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), {
1745
+ var spinnerFrames = [
1746
+ "-",
1747
+ "\\",
1748
+ "|",
1749
+ "/"
1750
+ ];
1751
+ var spinnerIntervalMilliseconds = 100;
1752
+ var terminalLineStart = "\r";
1753
+ var terminalClearLine = "\x1B[K";
1754
+ var isMuonPrepareStatusLine = (line) => {
1755
+ const trimmed = line.trimEnd();
1756
+ return trimmed.endsWith("...") || trimmed === "Failed to prepare CEF." || trimmed.startsWith("Downloading CEF binary:");
1757
+ };
1758
+ var shouldKeepMuonPrepareStatusAfterLine = (line) => line.startsWith("Muon files copied to staging:");
1759
+ var createPlainStderrForwarder = () => ({
1760
+ write: (chunk) => {
1761
+ process.stderr.write(chunk);
1762
+ },
1763
+ flush: () => {}
1764
+ });
1765
+ var createSpinnerStderrForwarder = () => {
1766
+ let pending = "";
1767
+ let activeStatus = void 0;
1768
+ let frameIndex = 0;
1769
+ let timer = void 0;
1770
+ const writeRaw = (chunk) => {
1771
+ process.stderr.write(chunk);
1772
+ };
1773
+ const stopTimer = () => {
1774
+ if (timer !== void 0) {
1775
+ clearInterval(timer);
1776
+ timer = void 0;
1777
+ }
1778
+ };
1779
+ const renderStatus = () => {
1780
+ if (activeStatus === void 0) return;
1781
+ writeRaw(`${terminalLineStart}${spinnerFrames[frameIndex] ?? spinnerFrames[0]} ${activeStatus}${terminalClearLine}`);
1782
+ frameIndex = (frameIndex + 1) % spinnerFrames.length;
1783
+ };
1784
+ const ensureTimer = () => {
1785
+ if (timer === void 0) timer = setInterval(renderStatus, spinnerIntervalMilliseconds);
1786
+ };
1787
+ const finishStatus = () => {
1788
+ if (activeStatus === void 0) return;
1789
+ const status = activeStatus;
1790
+ activeStatus = void 0;
1791
+ stopTimer();
1792
+ frameIndex = 0;
1793
+ writeRaw(`${terminalLineStart}${status}${terminalClearLine}\n`);
1794
+ };
1795
+ const startStatus = (status) => {
1796
+ if (activeStatus !== void 0 && activeStatus !== status) finishStatus();
1797
+ activeStatus = status;
1798
+ renderStatus();
1799
+ ensureTimer();
1800
+ };
1801
+ const writeLine = (line) => {
1802
+ const text = line.replace(/\r?\n$/, "");
1803
+ if (isMuonPrepareStatusLine(text)) {
1804
+ startStatus(text.trimEnd());
1805
+ return;
1806
+ }
1807
+ if (activeStatus !== void 0 && shouldKeepMuonPrepareStatusAfterLine(text)) {
1808
+ writeRaw(`${terminalLineStart}${terminalClearLine}${line.endsWith("\n") ? line : `${line}\n`}`);
1809
+ renderStatus();
1810
+ return;
1811
+ }
1812
+ finishStatus();
1813
+ writeRaw(line);
1814
+ };
1815
+ return {
1816
+ write: (chunk) => {
1817
+ pending += chunk;
1818
+ for (;;) {
1819
+ const newlineIndex = pending.indexOf("\n");
1820
+ if (newlineIndex < 0) break;
1821
+ const line = pending.slice(0, newlineIndex + 1);
1822
+ pending = pending.slice(newlineIndex + 1);
1823
+ writeLine(line);
1824
+ }
1825
+ },
1826
+ flush: () => {
1827
+ if (pending.length > 0) {
1828
+ const line = pending;
1829
+ pending = "";
1830
+ writeLine(line);
1831
+ }
1832
+ finishStatus();
1833
+ }
1834
+ };
1835
+ };
1836
+ var createMuonPrepareStderrForwarder = () => process.stderr.isTTY === true ? createSpinnerStderrForwarder() : createPlainStderrForwarder();
1837
+ var runMuonPrepareCommand = async (options) => {
1838
+ const child = spawn(await resolveMuonBuilderExecutable(options), [...options.args], {
1752
1839
  cwd: options.cwd,
1753
1840
  env: options.environment,
1754
1841
  stdio: [
@@ -1761,22 +1848,46 @@ var runMuonPrepare = async (options) => {
1761
1848
  let stderr = "";
1762
1849
  child.stdout.setEncoding("utf8");
1763
1850
  child.stderr.setEncoding("utf8");
1851
+ const stderrForwarder = options.quiet ? void 0 : createMuonPrepareStderrForwarder();
1764
1852
  child.stdout.on("data", (chunk) => {
1765
1853
  stdout += chunk;
1766
1854
  });
1767
1855
  child.stderr.on("data", (chunk) => {
1768
1856
  stderr += chunk;
1769
- if (!options.quiet) process.stderr.write(chunk);
1857
+ stderrForwarder?.write(chunk);
1770
1858
  });
1771
- const exitCode = await new Promise((resolvePromise, reject) => {
1772
- child.on("error", reject);
1773
- child.on("close", (code) => {
1774
- resolvePromise(code ?? 1);
1775
- });
1859
+ const exitCode = await (async () => {
1860
+ try {
1861
+ return await new Promise((resolvePromise, reject) => {
1862
+ child.on("error", reject);
1863
+ child.on("close", (code) => {
1864
+ resolvePromise(code ?? 1);
1865
+ });
1866
+ });
1867
+ } finally {
1868
+ stderrForwarder?.flush();
1869
+ }
1870
+ })();
1871
+ if (exitCode !== 0) throw new Error(`muon-builder failed with exit code ${exitCode}.\n${stderr.trim()}`);
1872
+ return stdout;
1873
+ };
1874
+ /**
1875
+ * Invokes the native muon-builder executable and returns the prepared runtime.
1876
+ *
1877
+ * @param options Native prepare invocation options.
1878
+ * @returns Prepared runtime location.
1879
+ */
1880
+ var runMuonPrepare = async (options) => {
1881
+ const args = createMuonPrepareArguments(options);
1882
+ const stdout = await runMuonPrepareCommand({
1883
+ prepareExecutablePath: options.prepareExecutablePath,
1884
+ environment: options.environment,
1885
+ cwd: options.cwd,
1886
+ quiet: options.quiet,
1887
+ args
1776
1888
  });
1777
- if (exitCode !== 0) throw new Error(`muon-prepare failed with exit code ${exitCode}.\n${stderr.trim()}`);
1778
1889
  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}`);
1890
+ 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
1891
  return {
1781
1892
  ...result.stagePath === void 0 ? {} : { stagePath: result.stagePath },
1782
1893
  muonPath: result.muonPath,
@@ -1784,6 +1895,675 @@ var runMuonPrepare = async (options) => {
1784
1895
  cacheHit: result.cacheHit
1785
1896
  };
1786
1897
  };
1898
+ /**
1899
+ * Invokes the native muon-builder executable to write Windows PE resources.
1900
+ *
1901
+ * @param options Resource update invocation options.
1902
+ */
1903
+ var runMuonPrepareResourceUpdate = async (options) => {
1904
+ await runMuonPrepareCommand({
1905
+ prepareExecutablePath: options.prepareExecutablePath,
1906
+ environment: options.environment,
1907
+ cwd: options.cwd,
1908
+ quiet: options.quiet,
1909
+ args: [
1910
+ "resource",
1911
+ "--input",
1912
+ options.inputPath,
1913
+ "--updates-json",
1914
+ options.updatesJsonPath,
1915
+ "--output",
1916
+ options.outputPath,
1917
+ ...options.quiet ? ["--quiet"] : []
1918
+ ]
1919
+ });
1920
+ };
1921
+ //#endregion
1922
+ //#region src/windows-icon.ts
1923
+ var sharp = __resolveDefaultExport$1(__screwUpDefaultImportModule0$1, true);
1924
+ globalThis.__screwUpIsInCJS_27e672935538 = false;
1925
+ function __resolveDefaultExport$1(module, isESM) {
1926
+ const __isInCJS = typeof globalThis !== "undefined" && globalThis.__screwUpIsInCJS_27e672935538 === true;
1927
+ const maybe = module;
1928
+ const hasDefault = !!(maybe && typeof maybe === "object" && "default" in maybe);
1929
+ const unwrapNamespaceDefault = (value) => {
1930
+ if (!value || typeof value !== "object") return value;
1931
+ const inner = value;
1932
+ if ((inner.__esModule === true || typeof Symbol !== "undefined" && inner[Symbol.toStringTag] === "Module") && "default" in inner) return inner.default;
1933
+ return value;
1934
+ };
1935
+ const resolvedDefault = hasDefault ? unwrapNamespaceDefault(maybe.default) : void 0;
1936
+ if (__isInCJS) return hasDefault ? resolvedDefault ?? module : module;
1937
+ if (isESM) {
1938
+ if (hasDefault) return resolvedDefault;
1939
+ throw new Error("Default export not found.");
1940
+ }
1941
+ return hasDefault ? resolvedDefault ?? module : module;
1942
+ }
1943
+ var normalizedIconSize = 256;
1944
+ var windowsIconDibSizes = [
1945
+ 16,
1946
+ 24,
1947
+ 32,
1948
+ 48,
1949
+ 64,
1950
+ 128
1951
+ ];
1952
+ var pngSignature = Buffer.from([
1953
+ 137,
1954
+ 80,
1955
+ 78,
1956
+ 71,
1957
+ 13,
1958
+ 10,
1959
+ 26,
1960
+ 10
1961
+ ]);
1962
+ /**
1963
+ * Creates a normalized square PNG icon image.
1964
+ *
1965
+ * @param pngData Source PNG bytes.
1966
+ * @param source Diagnostic source label used in errors.
1967
+ * @param label User-facing diagnostic label.
1968
+ * @returns PNG bytes normalized to the Muon base icon size.
1969
+ * @remarks Non-square images are fitted into transparent padding without
1970
+ * stretching. The normalized PNG is the source for all platform-specific icon
1971
+ * derivatives.
1972
+ */
1973
+ var createNormalizedIconPngData = async (pngData, source, label) => {
1974
+ await assertPngData(pngData, source, label);
1975
+ try {
1976
+ return await sharp(pngData).resize(normalizedIconSize, normalizedIconSize, {
1977
+ fit: "contain",
1978
+ background: {
1979
+ r: 0,
1980
+ g: 0,
1981
+ b: 0,
1982
+ alpha: 0
1983
+ }
1984
+ }).ensureAlpha().png().toBuffer();
1985
+ } catch {
1986
+ throw new Error(`${label} must be a valid PNG: ${source}`);
1987
+ }
1988
+ };
1989
+ /**
1990
+ * Creates a normalized square PNG icon image for Windows resources.
1991
+ *
1992
+ * @param pngData Source PNG bytes.
1993
+ * @param source Diagnostic source label used in errors.
1994
+ * @returns PNG bytes normalized to the Muon base icon size.
1995
+ */
1996
+ var createNormalizedMuonIconPngData = async (pngData, source) => await createNormalizedIconPngData(pngData, source, "Windows resource icon");
1997
+ /**
1998
+ * Creates a Windows ICO file from PNG bytes.
1999
+ *
2000
+ * @param pngData Source PNG bytes.
2001
+ * @param source Diagnostic source label used in errors.
2002
+ * @returns ICO file bytes containing 16, 24, 32, 48, 64, 128, and 256 pixel images.
2003
+ * @remarks The 256px entry is PNG-compressed. Smaller entries are 32-bit DIB
2004
+ * images, matching the common Windows ICO layout recommended by Microsoft.
2005
+ */
2006
+ var createWindowsIconBufferFromPngData = async (pngData, source) => {
2007
+ const normalizedPng = await createNormalizedMuonIconPngData(pngData, source);
2008
+ const images = [];
2009
+ for (const size of windowsIconDibSizes) {
2010
+ const rawImage = await resizePngToRawRgba(normalizedPng, size, source);
2011
+ images.push({
2012
+ data: createDibIconImage(rawImage),
2013
+ size,
2014
+ bitCount: 32
2015
+ });
2016
+ }
2017
+ images.push({
2018
+ data: normalizedPng,
2019
+ size: normalizedIconSize,
2020
+ bitCount: 32
2021
+ });
2022
+ return createIco(images);
2023
+ };
2024
+ /**
2025
+ * Creates a Windows ICO file from a PNG file.
2026
+ *
2027
+ * @param sourcePath Source PNG file path.
2028
+ * @param outputPath Output ICO file path.
2029
+ * @returns Promise resolved when the ICO file has been written.
2030
+ */
2031
+ var createWindowsIconFromPngFile = async (sourcePath, outputPath) => {
2032
+ const icon = await createWindowsIconBufferFromPngData(await readFile(sourcePath), sourcePath);
2033
+ await mkdir(dirname(outputPath), { recursive: true });
2034
+ await writeFile(outputPath, icon);
2035
+ };
2036
+ var assertPngData = async (data, source, label) => {
2037
+ if (data.length === 0) throw new Error(`${label} PNG must not be empty: ${source}`);
2038
+ if (!data.subarray(0, pngSignature.length).equals(pngSignature)) throw new Error(`${label} must be a valid PNG: ${source}`);
2039
+ try {
2040
+ if ((await sharp(data).metadata()).format !== "png") throw new Error();
2041
+ } catch {
2042
+ throw new Error(`${label} must be a valid PNG: ${source}`);
2043
+ }
2044
+ };
2045
+ var resizePngToRawRgba = async (pngData, size, source) => {
2046
+ try {
2047
+ const { data, info } = await sharp(pngData).resize(size, size, {
2048
+ fit: "fill",
2049
+ kernel: "lanczos3"
2050
+ }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
2051
+ if (info.width !== size || info.height !== size || info.channels !== 4) throw new Error();
2052
+ return {
2053
+ data,
2054
+ width: info.width,
2055
+ height: info.height
2056
+ };
2057
+ } catch {
2058
+ throw new Error(`Failed to resize Windows resource icon PNG: ${source}`);
2059
+ }
2060
+ };
2061
+ var createDibIconImage = (image) => {
2062
+ const pixelBytes = image.width * image.height * 4;
2063
+ const maskBytes = Math.ceil(image.width / 32) * 4 * image.height;
2064
+ const output = Buffer.alloc(40 + pixelBytes + maskBytes);
2065
+ output.writeUInt32LE(40, 0);
2066
+ output.writeInt32LE(image.width, 4);
2067
+ output.writeInt32LE(image.height * 2, 8);
2068
+ output.writeUInt16LE(1, 12);
2069
+ output.writeUInt16LE(32, 14);
2070
+ output.writeUInt32LE(0, 16);
2071
+ output.writeUInt32LE(pixelBytes + maskBytes, 20);
2072
+ output.writeInt32LE(0, 24);
2073
+ output.writeInt32LE(0, 28);
2074
+ output.writeUInt32LE(0, 32);
2075
+ output.writeUInt32LE(0, 36);
2076
+ for (let y = 0; y < image.height; y += 1) {
2077
+ const sourceY = image.height - 1 - y;
2078
+ for (let x = 0; x < image.width; x += 1) {
2079
+ const sourceOffset = (sourceY * image.width + x) * 4;
2080
+ const outputOffset = 40 + (y * image.width + x) * 4;
2081
+ output[outputOffset] = image.data[sourceOffset + 2];
2082
+ output[outputOffset + 1] = image.data[sourceOffset + 1];
2083
+ output[outputOffset + 2] = image.data[sourceOffset];
2084
+ output[outputOffset + 3] = image.data[sourceOffset + 3];
2085
+ }
2086
+ }
2087
+ return output;
2088
+ };
2089
+ var createIco = (images) => {
2090
+ const directorySize = 6 + images.length * 16;
2091
+ let imageOffset = directorySize;
2092
+ const header = Buffer.alloc(directorySize);
2093
+ header.writeUInt16LE(0, 0);
2094
+ header.writeUInt16LE(1, 2);
2095
+ header.writeUInt16LE(images.length, 4);
2096
+ const imageData = [];
2097
+ images.forEach((image, index) => {
2098
+ const entryOffset = 6 + index * 16;
2099
+ header[entryOffset] = image.size === 256 ? 0 : image.size;
2100
+ header[entryOffset + 1] = image.size === 256 ? 0 : image.size;
2101
+ header[entryOffset + 2] = 0;
2102
+ header[entryOffset + 3] = 0;
2103
+ header.writeUInt16LE(1, entryOffset + 4);
2104
+ header.writeUInt16LE(image.bitCount, entryOffset + 6);
2105
+ header.writeUInt32LE(image.data.length, entryOffset + 8);
2106
+ header.writeUInt32LE(imageOffset, entryOffset + 12);
2107
+ imageOffset += image.data.length;
2108
+ imageData.push(image.data);
2109
+ });
2110
+ return Buffer.concat([header, ...imageData]);
2111
+ };
2112
+ //#endregion
2113
+ //#region src/windows-resource.ts
2114
+ var defaultLanguage = 1033;
2115
+ var defaultCodePage = 1200;
2116
+ var moduleDirectory$3 = typeof __dirname === "string" ? __dirname : dirname(fileURLToPath(import.meta.url));
2117
+ /**
2118
+ * Merges Windows resource options while preserving field-level fallback.
2119
+ */
2120
+ var mergeMuonWindowsResourceOptions = (primary, fallback) => {
2121
+ if (primary === void 0) return fallback;
2122
+ if (fallback === void 0) return primary;
2123
+ const merged = { ...fallback };
2124
+ if (primary.iconPath !== void 0) merged.iconPath = primary.iconPath;
2125
+ if (primary.productName !== void 0) merged.productName = primary.productName;
2126
+ if (primary.fileDescription !== void 0) merged.fileDescription = primary.fileDescription;
2127
+ if (primary.companyName !== void 0) merged.companyName = primary.companyName;
2128
+ if (primary.version !== void 0) merged.version = primary.version;
2129
+ if (primary.copyright !== void 0) merged.copyright = primary.copyright;
2130
+ if (primary.language !== void 0) merged.language = primary.language;
2131
+ if (primary.codePage !== void 0) merged.codePage = primary.codePage;
2132
+ return merged;
2133
+ };
2134
+ /**
2135
+ * Resolves Windows resource metadata from options, config files, and package metadata.
2136
+ */
2137
+ var resolveMuonWindowsResource = async (input) => {
2138
+ const projectJson = await readProjectJson(input.root);
2139
+ const sources = [
2140
+ createOptionsSource$1(input.options, input.root, "windowsResource"),
2141
+ readConfigWindowsResourceSource(input.muonConfig, input.muonConfigDirectory, "muon.json"),
2142
+ createProjectSource(projectJson, input.root),
2143
+ createPackageSource(input.packageJson, input.root)
2144
+ ].filter((source) => source !== void 0);
2145
+ const productName = resolveStringField$1(sources, "productName", input.defaults.productName);
2146
+ const fileDescription = resolveStringField$1(sources, "fileDescription", input.defaults.fileDescription);
2147
+ const companyName = resolveStringField$1(sources, "companyName", input.defaults.companyName);
2148
+ const version = resolveStringField$1(sources, "version", input.defaults.version);
2149
+ const copyright = resolveOptionalStringField(sources, "copyright", input.defaults.copyright);
2150
+ const language = resolveNumericField(sources, "language", defaultLanguage);
2151
+ const codePage = resolveNumericField(sources, "codePage", defaultCodePage);
2152
+ const fixedVersion = normalizeWindowsVersion(version);
2153
+ return {
2154
+ iconPath: await resolveIconPath$1(sources, await resolveDefaultWindowsIcon(input.packageDirectory)),
2155
+ productName,
2156
+ fileDescription,
2157
+ companyName,
2158
+ version,
2159
+ fixedVersion,
2160
+ copyright,
2161
+ language,
2162
+ codePage
2163
+ };
2164
+ };
2165
+ /**
2166
+ * Removes build-only Windows resource settings from runtime config.
2167
+ */
2168
+ var stripBuildOnlyWindowsResourceConfig = (sourceConfig) => {
2169
+ const sourceWindows = sourceConfig.windows;
2170
+ if (!isJsonObject$3(sourceWindows)) return sourceConfig;
2171
+ const windowsConfig = {};
2172
+ for (const [key, value] of Object.entries(sourceWindows)) if (key !== "resource") windowsConfig[key] = value;
2173
+ const output = {};
2174
+ for (const [key, value] of Object.entries(sourceConfig)) if (key !== "windows") output[key] = value;
2175
+ if (Object.keys(windowsConfig).length > 0) output.windows = windowsConfig;
2176
+ return output;
2177
+ };
2178
+ /**
2179
+ * Returns true when the file has a Windows PE header.
2180
+ */
2181
+ var isWindowsPeExecutable = async (path) => {
2182
+ const content = await readFile(path);
2183
+ if (content.length < 64 || content.toString("ascii", 0, 2) !== "MZ") return false;
2184
+ const peOffset = content.readUInt32LE(60);
2185
+ return peOffset + 4 <= content.length && content.toString("ascii", peOffset, peOffset + 4) === "PE\0\0";
2186
+ };
2187
+ /**
2188
+ * Applies resolved resource metadata to a Windows PE executable in place.
2189
+ */
2190
+ var updateWindowsPeResources = async (input) => {
2191
+ if (!await isWindowsPeExecutable(input.executablePath)) return false;
2192
+ const mode = (await stat(input.executablePath)).mode & 511;
2193
+ const tempDirectory = await mkdtemp(join(tmpdir(), "muon-windows-resource-"));
2194
+ const updatesJsonPath = join(tempDirectory, "updates.json");
2195
+ const outputPath = join(tempDirectory, "output.exe");
2196
+ const iconPath = input.resource.iconPath === void 0 ? void 0 : join(tempDirectory, "icon.ico");
2197
+ try {
2198
+ if (input.resource.iconPath !== void 0 && iconPath !== void 0) await createWindowsIconFromPngFile(input.resource.iconPath, iconPath);
2199
+ await writeFile(updatesJsonPath, `${JSON.stringify(createEngraverUpdate(input.resource, iconPath), null, 2)}\n`);
2200
+ await runMuonPrepareResourceUpdate({
2201
+ inputPath: input.executablePath,
2202
+ updatesJsonPath,
2203
+ outputPath,
2204
+ quiet: true,
2205
+ prepareExecutablePath: void 0,
2206
+ environment: input.environment,
2207
+ cwd: input.cwd
2208
+ });
2209
+ await copyFile(outputPath, input.executablePath);
2210
+ await chmod(input.executablePath, mode === 0 ? 493 : mode);
2211
+ return true;
2212
+ } finally {
2213
+ await rm(tempDirectory, {
2214
+ recursive: true,
2215
+ force: true
2216
+ });
2217
+ }
2218
+ };
2219
+ var createEngraverUpdate = (resource, iconPath) => {
2220
+ const strings = {
2221
+ CompanyName: resource.companyName,
2222
+ FileDescription: resource.fileDescription,
2223
+ FileVersion: resource.version,
2224
+ ProductName: resource.productName,
2225
+ ProductVersion: resource.version
2226
+ };
2227
+ if (resource.copyright !== void 0) strings.LegalCopyright = resource.copyright;
2228
+ return {
2229
+ version: {
2230
+ language: resource.language,
2231
+ codePage: resource.codePage,
2232
+ fixed: {
2233
+ fileVersion: resource.fixedVersion,
2234
+ productVersion: resource.fixedVersion,
2235
+ fileOS: "windows32",
2236
+ fileType: "app"
2237
+ },
2238
+ strings
2239
+ },
2240
+ icons: iconPath === void 0 ? [] : [{
2241
+ id: 1,
2242
+ language: resource.language,
2243
+ path: iconPath
2244
+ }]
2245
+ };
2246
+ };
2247
+ var readProjectJson = async (root) => {
2248
+ const projectJsonPath = join(root, "project.json");
2249
+ if (!await fileExists$3(projectJsonPath)) return {};
2250
+ return await readJsonObjectFile$1(projectJsonPath, "project.json");
2251
+ };
2252
+ var readJsonObjectFile$1 = async (filePath, label) => {
2253
+ const parsed = (0, import_dist.parse)(await readFile(filePath, "utf8"));
2254
+ if (!isJsonObject$3(parsed)) throw new Error(`${label} must contain a JSON object: ${filePath}`);
2255
+ return parsed;
2256
+ };
2257
+ var createOptionsSource$1 = (options, directory, label) => {
2258
+ if (options === void 0) return;
2259
+ return {
2260
+ options: validateWindowsResourceOptions(options, label),
2261
+ directory
2262
+ };
2263
+ };
2264
+ var readConfigWindowsResourceSource = (config, directory, label) => {
2265
+ const windows = config.windows;
2266
+ if (windows === void 0) return;
2267
+ if (!isJsonObject$3(windows)) throw new Error(`${label} windows must be an object when present.`);
2268
+ const resource = windows.resource;
2269
+ if (resource === void 0) return;
2270
+ return createOptionsSource$1(validateWindowsResourceOptions(resource, `${label} windows.resource`), directory, `${label} windows.resource`);
2271
+ };
2272
+ var createProjectSource = (projectJson, directory) => {
2273
+ const topLevel = {};
2274
+ copyStringField(projectJson, "iconPath", topLevel, "iconPath");
2275
+ copyStringField(projectJson, "productName", topLevel, "productName");
2276
+ copyStringField(projectJson, "name", topLevel, "productName");
2277
+ copyStringField(projectJson, "fileDescription", topLevel, "fileDescription");
2278
+ copyStringField(projectJson, "description", topLevel, "fileDescription");
2279
+ copyStringField(projectJson, "companyName", topLevel, "companyName");
2280
+ copyStringField(projectJson, "version", topLevel, "version");
2281
+ copyStringField(projectJson, "copyright", topLevel, "copyright");
2282
+ const author = stringifyAuthor(projectJson.author);
2283
+ if (author !== void 0 && topLevel.companyName === void 0) topLevel.companyName = author;
2284
+ const options = mergeMuonWindowsResourceOptions(readConfigWindowsResourceSource(projectJson, directory, "project.json")?.options, topLevel);
2285
+ if (options === void 0 || Object.keys(options).length === 0) return;
2286
+ return {
2287
+ options,
2288
+ directory
2289
+ };
2290
+ };
2291
+ var createPackageSource = (packageJson, directory) => {
2292
+ const options = {};
2293
+ copyStringField(packageJson, "name", options, "productName");
2294
+ copyStringField(packageJson, "description", options, "fileDescription");
2295
+ copyStringField(packageJson, "version", options, "version");
2296
+ copyStringField(packageJson, "copyright", options, "copyright");
2297
+ const author = stringifyAuthor(packageJson.author);
2298
+ if (author !== void 0) options.companyName = author;
2299
+ return Object.keys(options).length === 0 ? void 0 : {
2300
+ options,
2301
+ directory
2302
+ };
2303
+ };
2304
+ var copyStringField = (source, sourceKey, target, targetKey) => {
2305
+ const value = source[sourceKey];
2306
+ if (typeof value === "string" && value.trim() !== "") target[targetKey] = value.trim();
2307
+ };
2308
+ var validateWindowsResourceOptions = (value, label) => {
2309
+ if (!isJsonObject$3(value)) throw new Error(`${label} must be an object.`);
2310
+ const output = {};
2311
+ copyOptionalStringResourceField(value, "iconPath", output);
2312
+ copyOptionalStringResourceField(value, "productName", output);
2313
+ copyOptionalStringResourceField(value, "fileDescription", output);
2314
+ copyOptionalStringResourceField(value, "companyName", output);
2315
+ copyOptionalStringResourceField(value, "version", output);
2316
+ copyOptionalStringResourceField(value, "copyright", output);
2317
+ copyOptionalNumberResourceField(value, "language", output, label);
2318
+ copyOptionalNumberResourceField(value, "codePage", output, label);
2319
+ return output;
2320
+ };
2321
+ var copyOptionalStringResourceField = (source, key, target) => {
2322
+ const value = source[key];
2323
+ if (value === void 0) return;
2324
+ if (typeof value !== "string") throw new Error(`${key} must be a string when present.`);
2325
+ if (value.trim() !== "") target[key] = value.trim();
2326
+ };
2327
+ var copyOptionalNumberResourceField = (source, key, target, label) => {
2328
+ const value = source[key];
2329
+ if (value === void 0) return;
2330
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > 65535) throw new Error(`${label} ${key} must be an integer from 0 to 65535.`);
2331
+ target[key] = value;
2332
+ };
2333
+ var resolveStringField$1 = (sources, field, fallback) => {
2334
+ return resolveOptionalStringField(sources, field, fallback) ?? fallback;
2335
+ };
2336
+ var resolveOptionalStringField = (sources, field, fallback) => {
2337
+ for (const source of sources) {
2338
+ const value = source.options[field];
2339
+ if (value !== void 0 && value.trim() !== "") return value.trim();
2340
+ }
2341
+ return fallback;
2342
+ };
2343
+ var resolveNumericField = (sources, field, fallback) => {
2344
+ for (const source of sources) {
2345
+ const value = source.options[field];
2346
+ if (value !== void 0) return value;
2347
+ }
2348
+ return fallback;
2349
+ };
2350
+ var resolveIconPath$1 = async (sources, defaultIconPath) => {
2351
+ for (const source of sources) {
2352
+ const value = source.options.iconPath;
2353
+ if (value !== void 0 && value.trim() !== "") {
2354
+ const iconPath = resolveResourcePath$1(source.directory, value);
2355
+ await assertIconPath$1(iconPath, true);
2356
+ return iconPath;
2357
+ }
2358
+ }
2359
+ if (defaultIconPath !== void 0 && await fileExists$3(defaultIconPath)) {
2360
+ await assertIconPath$1(defaultIconPath, false);
2361
+ return defaultIconPath;
2362
+ }
2363
+ };
2364
+ var assertIconPath$1 = async (iconPath, required) => {
2365
+ if (extname(iconPath).toLowerCase() !== ".png") throw new Error(`Windows resource icon must be a .png file: ${iconPath}`);
2366
+ if (required && !await fileExists$3(iconPath)) throw new Error(`Windows resource icon does not exist: ${iconPath}`);
2367
+ if (await fileExists$3(iconPath)) await createNormalizedMuonIconPngData(await readFile(iconPath), iconPath);
2368
+ };
2369
+ var resolveDefaultWindowsIcon = async (packageDirectory) => {
2370
+ const candidates = [
2371
+ join(resolve(packageDirectory), "native", "muon-bootstrap.png"),
2372
+ join(moduleDirectory$3, "native", "muon-bootstrap.png"),
2373
+ join(moduleDirectory$3, "..", "dist", "native", "muon-bootstrap.png"),
2374
+ join(moduleDirectory$3, "..", "..", "images", "muon-bootstrap-256.png")
2375
+ ];
2376
+ for (const candidate of candidates) if (await fileExists$3(candidate)) return candidate;
2377
+ };
2378
+ var resolveResourcePath$1 = (directory, path) => {
2379
+ return isAbsolute(path) ? path : resolve(directory, path);
2380
+ };
2381
+ var normalizeWindowsVersion = (version) => {
2382
+ const normalized = version.trim();
2383
+ const match = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?(?:$|[-+])/.exec(normalized);
2384
+ if (match === null) throw new Error(`Windows resource version must start with numeric parts: ${version}`);
2385
+ const values = [
2386
+ match[1],
2387
+ match[2],
2388
+ match[3],
2389
+ match[4]
2390
+ ].map((value) => value === void 0 ? 0 : Number.parseInt(value, 10));
2391
+ 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}`);
2392
+ return values.join(".");
2393
+ };
2394
+ var stringifyAuthor = (value) => {
2395
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
2396
+ if (!isJsonObject$3(value) || typeof value.name !== "string") return;
2397
+ const name = value.name.trim();
2398
+ if (name === "") return;
2399
+ return value.email === void 0 || typeof value.email !== "string" ? name : `${name} <${value.email}>`;
2400
+ };
2401
+ var fileExists$3 = async (path) => {
2402
+ try {
2403
+ await access(path, constants.F_OK);
2404
+ return true;
2405
+ } catch {
2406
+ return false;
2407
+ }
2408
+ };
2409
+ var isJsonObject$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2410
+ //#endregion
2411
+ //#region src/linux-desktop.ts
2412
+ var defaultIconFileName = "muon-desktop-icon.png";
2413
+ var defaultDesktopConfigFileName = "muon-desktop.json";
2414
+ var moduleDirectory$2 = typeof __dirname === "string" ? __dirname : dirname(fileURLToPath(import.meta.url));
2415
+ /**
2416
+ * Resolves Linux desktop integration metadata from options, config files, and
2417
+ * package metadata.
2418
+ */
2419
+ var resolveMuonLinuxDesktop = async (input) => {
2420
+ const sources = [createOptionsSource(input.options, input.root, "linuxDesktop"), readConfigLinuxDesktopSource(input.muonConfig, input.muonConfigDirectory, "muon.json")].filter((source) => source !== void 0);
2421
+ return {
2422
+ desktopId: sanitizeDesktopId(resolveStringField(sources, "desktopId", input.defaults.desktopId)),
2423
+ name: resolveStringField(sources, "name", input.defaults.name),
2424
+ comment: resolveStringField(sources, "comment", input.defaults.comment),
2425
+ categories: resolveCategories(sources, input.defaults.categories),
2426
+ startupNotify: resolveBooleanField(sources, "startupNotify", input.defaults.startupNotify),
2427
+ iconPath: await resolveIconPath(sources, await resolveDefaultLinuxDesktopIcon(input.packageDirectory)),
2428
+ iconFileName: defaultIconFileName,
2429
+ configFileName: defaultDesktopConfigFileName
2430
+ };
2431
+ };
2432
+ /**
2433
+ * Removes build-only Linux desktop settings from runtime config.
2434
+ */
2435
+ var stripBuildOnlyLinuxDesktopConfig = (sourceConfig) => {
2436
+ const sourceLinux = sourceConfig.linux;
2437
+ if (!isJsonObject$2(sourceLinux)) return sourceConfig;
2438
+ const linuxConfig = {};
2439
+ for (const [key, value] of Object.entries(sourceLinux)) if (key !== "desktop") linuxConfig[key] = value;
2440
+ const output = {};
2441
+ for (const [key, value] of Object.entries(sourceConfig)) if (key !== "linux") output[key] = value;
2442
+ if (Object.keys(linuxConfig).length > 0) output.linux = linuxConfig;
2443
+ return output;
2444
+ };
2445
+ /**
2446
+ * Writes normalized Linux desktop sidecar files into a target distribution.
2447
+ */
2448
+ var writeLinuxDesktopDistributionFiles = async (outputPath, desktop) => {
2449
+ const iconData = await createNormalizedIconPngData(await readFile(desktop.iconPath), desktop.iconPath, "Linux desktop icon");
2450
+ await writeFile(join(outputPath, desktop.iconFileName), iconData);
2451
+ await writeFile(join(outputPath, desktop.configFileName), `${JSON.stringify({
2452
+ desktopId: desktop.desktopId,
2453
+ name: desktop.name,
2454
+ comment: desktop.comment,
2455
+ categories: desktop.categories,
2456
+ startupNotify: desktop.startupNotify,
2457
+ iconFileName: desktop.iconFileName
2458
+ }, void 0, 2)}\n`);
2459
+ };
2460
+ var createOptionsSource = (options, directory, label) => {
2461
+ if (options === void 0) return;
2462
+ return {
2463
+ options: validateLinuxDesktopOptions(options, label),
2464
+ directory
2465
+ };
2466
+ };
2467
+ var readConfigLinuxDesktopSource = (config, directory, label) => {
2468
+ const linux = config.linux;
2469
+ if (linux === void 0) return;
2470
+ if (!isJsonObject$2(linux)) throw new Error(`${label} linux must be an object when present.`);
2471
+ const desktop = linux.desktop;
2472
+ if (desktop === void 0) return;
2473
+ return createOptionsSource(validateLinuxDesktopOptions(desktop, `${label} linux.desktop`), directory, `${label} linux.desktop`);
2474
+ };
2475
+ var validateLinuxDesktopOptions = (value, label) => {
2476
+ if (!isJsonObject$2(value)) throw new Error(`${label} must be an object.`);
2477
+ const output = {};
2478
+ copyOptionalStringDesktopField(value, "desktopId", output);
2479
+ copyOptionalStringDesktopField(value, "name", output);
2480
+ copyOptionalStringDesktopField(value, "comment", output);
2481
+ copyOptionalStringDesktopField(value, "iconPath", output);
2482
+ copyOptionalCategoriesField(value, output, label);
2483
+ copyOptionalBooleanDesktopField(value, "startupNotify", output, label);
2484
+ return output;
2485
+ };
2486
+ var copyOptionalStringDesktopField = (source, key, target) => {
2487
+ const value = source[key];
2488
+ if (value === void 0) return;
2489
+ if (typeof value !== "string") throw new Error(`${key} must be a string when present.`);
2490
+ if (value.trim() !== "") target[key] = value.trim();
2491
+ };
2492
+ var copyOptionalCategoriesField = (source, target, label) => {
2493
+ const value = source.categories;
2494
+ if (value === void 0) return;
2495
+ 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.`);
2496
+ target.categories = value.map((entry) => entry.trim());
2497
+ };
2498
+ var copyOptionalBooleanDesktopField = (source, key, target, label) => {
2499
+ const value = source[key];
2500
+ if (value === void 0) return;
2501
+ if (typeof value !== "boolean") throw new Error(`${label} ${key} must be a boolean when present.`);
2502
+ target[key] = value;
2503
+ };
2504
+ var resolveStringField = (sources, field, fallback) => {
2505
+ for (const source of sources) {
2506
+ const value = source.options[field];
2507
+ if (value !== void 0 && value.trim() !== "") return value.trim();
2508
+ }
2509
+ return fallback;
2510
+ };
2511
+ var resolveCategories = (sources, fallback) => {
2512
+ for (const source of sources) if (source.options.categories !== void 0) return source.options.categories;
2513
+ return fallback;
2514
+ };
2515
+ var resolveBooleanField = (sources, field, fallback) => {
2516
+ for (const source of sources) {
2517
+ const value = source.options[field];
2518
+ if (value !== void 0) return value;
2519
+ }
2520
+ return fallback;
2521
+ };
2522
+ var resolveIconPath = async (sources, defaultIconPath) => {
2523
+ for (const source of sources) {
2524
+ const value = source.options.iconPath;
2525
+ if (value !== void 0 && value.trim() !== "") {
2526
+ const iconPath = resolveResourcePath(source.directory, value);
2527
+ await assertIconPath(iconPath, true);
2528
+ return iconPath;
2529
+ }
2530
+ }
2531
+ if (defaultIconPath !== void 0 && await fileExists$2(defaultIconPath)) {
2532
+ await assertIconPath(defaultIconPath, false);
2533
+ return defaultIconPath;
2534
+ }
2535
+ throw new Error("Linux desktop icon does not exist.");
2536
+ };
2537
+ var assertIconPath = async (iconPath, required) => {
2538
+ if (extname(iconPath).toLowerCase() !== ".png") throw new Error(`Linux desktop icon must be a .png file: ${iconPath}`);
2539
+ if (required && !await fileExists$2(iconPath)) throw new Error(`Linux desktop icon does not exist: ${iconPath}`);
2540
+ if (await fileExists$2(iconPath)) await createNormalizedIconPngData(await readFile(iconPath), iconPath, "Linux desktop icon");
2541
+ };
2542
+ var resolveDefaultLinuxDesktopIcon = async (packageDirectory) => {
2543
+ const candidates = [
2544
+ join(resolve(packageDirectory), "native", "muon-bootstrap.png"),
2545
+ join(moduleDirectory$2, "native", "muon-bootstrap.png"),
2546
+ join(moduleDirectory$2, "..", "dist", "native", "muon-bootstrap.png"),
2547
+ join(moduleDirectory$2, "..", "..", "images", "muon-bootstrap-256.png")
2548
+ ];
2549
+ for (const candidate of candidates) if (await fileExists$2(candidate)) return candidate;
2550
+ };
2551
+ var sanitizeDesktopId = (value) => {
2552
+ const sanitized = value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[.-]+/g, "").replace(/[.-]+$/g, "");
2553
+ return sanitized.length > 0 ? sanitized : "muon-app";
2554
+ };
2555
+ var resolveResourcePath = (directory, path) => {
2556
+ return isAbsolute(path) ? path : resolve(directory, path);
2557
+ };
2558
+ var fileExists$2 = async (path) => {
2559
+ try {
2560
+ await access(path, constants.F_OK);
2561
+ return true;
2562
+ } catch {
2563
+ return false;
2564
+ }
2565
+ };
2566
+ var isJsonObject$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1787
2567
  //#endregion
1788
2568
  //#region src/build.ts
1789
2569
  var AdmZip = __resolveDefaultExport(__screwUpDefaultImportModule0, false);
@@ -1844,17 +2624,49 @@ var buildMuonApp = async (options = {}) => {
1844
2624
  const appId = resolveAppId(packageJson, options.appId);
1845
2625
  const buildConfig = await readBuildConfig(root, options.configPath);
1846
2626
  const assetInput = resolveAssetInput(root, options.assetSourcePath, options.assetPrefix, buildConfig);
2627
+ const windowsResource = await resolveMuonWindowsResource({
2628
+ root,
2629
+ packageDirectory,
2630
+ packageJson,
2631
+ muonConfig: buildConfig.config,
2632
+ muonConfigDirectory: buildConfig.directory,
2633
+ options: options.windowsResource,
2634
+ defaults: {
2635
+ productName: appName,
2636
+ fileDescription: appName,
2637
+ companyName: "Unknown",
2638
+ version: "0.0.0",
2639
+ copyright: void 0
2640
+ }
2641
+ });
2642
+ const linuxDesktop = await resolveMuonLinuxDesktop({
2643
+ root,
2644
+ packageDirectory,
2645
+ muonConfig: buildConfig.config,
2646
+ muonConfigDirectory: buildConfig.directory,
2647
+ options: options.linuxDesktop,
2648
+ defaults: {
2649
+ desktopId: appId,
2650
+ name: resolveLinuxDesktopDefaultName(packageJson, appName),
2651
+ comment: resolvePackageDescription(packageJson),
2652
+ categories: ["Utility"],
2653
+ startupNotify: true
2654
+ }
2655
+ });
1847
2656
  const salt = Buffer.from(options.assetSalt ?? randomBytes(assetSaltByteLength));
1848
2657
  const results = [];
1849
2658
  for (const target of targets) {
1850
2659
  const result = await buildMuonTarget({
1851
2660
  packageDirectory,
2661
+ root,
1852
2662
  outputRoot,
1853
2663
  appName,
1854
2664
  appId,
1855
2665
  target,
1856
2666
  assetInput,
1857
2667
  sourceConfig: buildConfig.config,
2668
+ windowsResource,
2669
+ linuxDesktop,
1858
2670
  salt
1859
2671
  });
1860
2672
  results.push(result);
@@ -1912,6 +2724,8 @@ var sanitizeAppId = (value) => {
1912
2724
  const sanitized = (value.startsWith("@") ? value.slice(1) : value).trim().toLowerCase().replace("/", ".").replace(/[^a-z0-9._-]+/g, ".").replace(/^[.]+/g, "").replace(/[.]+$/g, "");
1913
2725
  return sanitized.length > 0 ? sanitized : defaultAppId;
1914
2726
  };
2727
+ var resolveLinuxDesktopDefaultName = (packageJson, appName) => typeof packageJson.name === "string" && packageJson.name.trim() !== "" ? packageJson.name.trim() : appName;
2728
+ var resolvePackageDescription = (packageJson) => typeof packageJson.description === "string" ? packageJson.description.trim() : "";
1915
2729
  var readBuildConfig = async (root, configPath) => {
1916
2730
  const resolvedConfigPath = await resolveConfigPath(root, configPath);
1917
2731
  if (resolvedConfigPath === void 0) return {
@@ -1985,7 +2799,7 @@ var buildMuonTarget = async (input) => {
1985
2799
  await copyFile(sourceBootstrapPath, launcherPath);
1986
2800
  await chmod(launcherPath, executableMode);
1987
2801
  const asset = await writeAssetArchive(input.assetInput, assetZipPath, input.salt);
1988
- const embeddedConfig = createEmbeddedConfig(input.sourceConfig, asset, input.appId);
2802
+ const embeddedConfig = createEmbeddedConfig(input.sourceConfig, asset, input.appId, input.linuxDesktop.desktopId);
1989
2803
  await withTemporaryConfig(embeddedConfig, async (configPath) => {
1990
2804
  await embedMuonConfigInRuntime({
1991
2805
  runtimePath: outputPath,
@@ -1998,13 +2812,21 @@ var buildMuonTarget = async (input) => {
1998
2812
  outputPath: void 0
1999
2813
  });
2000
2814
  });
2815
+ if (descriptor.os === "windows") await updateWindowsPeResources({
2816
+ executablePath: launcherPath,
2817
+ resource: input.windowsResource,
2818
+ environment: process.env,
2819
+ cwd: input.root
2820
+ });
2821
+ else if (descriptor.os === "linux") await writeLinuxDesktopDistributionFiles(outputPath, input.linuxDesktop);
2001
2822
  return {
2002
2823
  target: input.target,
2003
2824
  distributionDirectoryName: descriptor.distributionDirectoryName,
2004
2825
  outputPath,
2005
2826
  launcherPath,
2006
2827
  asset,
2007
- embeddedConfig
2828
+ embeddedConfig,
2829
+ ...descriptor.os === "linux" ? { linuxDesktop: input.linuxDesktop } : {}
2008
2830
  };
2009
2831
  };
2010
2832
  var verifyTargetInputs = async (input) => {
@@ -2079,13 +2901,13 @@ var createZipArchive = (entries) => {
2079
2901
  for (const entry of entries) zip.addFile(entry.name, entry.data);
2080
2902
  return zip.toBuffer();
2081
2903
  };
2082
- var createEmbeddedConfig = (sourceConfig, asset, appId) => {
2904
+ var createEmbeddedConfig = (sourceConfig, asset, appId, desktopId) => {
2083
2905
  const sourceAsset = sourceConfig.asset;
2084
2906
  if (sourceAsset !== void 0 && !isJsonObject$1(sourceAsset)) throw new Error("muon.json asset must be an object when present.");
2085
2907
  const sourceBootstrap = sourceConfig.bootstrap;
2086
2908
  if (sourceBootstrap !== void 0 && !isJsonObject$1(sourceBootstrap)) throw new Error("muon.json bootstrap must be an object when present.");
2087
2909
  return {
2088
- ...sourceConfig,
2910
+ ...stripBuildOnlyLinuxDesktopConfig(stripBuildOnlyWindowsResourceConfig(sourceConfig)),
2089
2911
  asset: {
2090
2912
  ...sourceAsset ?? {},
2091
2913
  sourcePath: appConfigSourcePath,
@@ -2094,7 +2916,8 @@ var createEmbeddedConfig = (sourceConfig, asset, appId) => {
2094
2916
  },
2095
2917
  bootstrap: {
2096
2918
  ...sourceBootstrap ?? {},
2097
- appId
2919
+ appId,
2920
+ desktopId
2098
2921
  }
2099
2922
  };
2100
2923
  };
@@ -2142,7 +2965,7 @@ var getErrorMessage$1 = (error) => error instanceof Error ? error.message : Stri
2142
2965
  //#region src/gitignore.ts
2143
2966
  var muonGitignoreEntries = [
2144
2967
  ".muon/",
2145
- "dist-muon-*/",
2968
+ "dist-muon/",
2146
2969
  "artifacts/"
2147
2970
  ];
2148
2971
  var muonGitignoreEntryAliases = {
@@ -2152,11 +2975,13 @@ var muonGitignoreEntryAliases = {
2152
2975
  ".muon",
2153
2976
  "/.muon"
2154
2977
  ],
2155
- "dist-muon-*/": [
2156
- "dist-muon-*/",
2157
- "/dist-muon-*/",
2158
- "dist-muon-*",
2159
- "/dist-muon-*"
2978
+ "dist-muon/": [
2979
+ "dist-muon/",
2980
+ "/dist-muon/",
2981
+ "dist-muon",
2982
+ "/dist-muon",
2983
+ "dist-muon/*",
2984
+ "/dist-muon/*"
2160
2985
  ],
2161
2986
  "artifacts/": [
2162
2987
  "artifacts/",
@@ -2403,7 +3228,7 @@ var startMuonViteBrowserBridge = async ({ server, pluginOptions, platform, archi
2403
3228
  environment,
2404
3229
  cwd: server.config.root
2405
3230
  });
2406
- if (preparedRuntime.stagePath === void 0) throw new Error("muon-prepare did not return a staged runtime path.");
3231
+ if (preparedRuntime.stagePath === void 0) throw new Error("muon-builder did not return a staged runtime path.");
2407
3232
  const paths = await createRuntimePaths(server, preparedRuntime.stagePath, platform, await resolveProjectConfigPath(server));
2408
3233
  await writeLaunchScript(paths, platform);
2409
3234
  let cleanupPromise = void 0;
@@ -2456,12 +3281,12 @@ var attachMuonVitePluginOptions = (plugin, options) => {
2456
3281
  };
2457
3282
  //#endregion
2458
3283
  //#region src/vite.ts
2459
- var suppressViteMuonBuildEnvironmentKey = "MUON_SUPPRESS_VITE_MUON_BUILD";
2460
3284
  /**
2461
3285
  * Creates a Vite plugin that launches Muon during Vite dev startup.
2462
3286
  *
2463
3287
  * @param options Muon plugin options used for development startup and build.
2464
3288
  * @returns Vite plugin instance.
3289
+ * @defaultValue `options` defaults to `{}`.
2465
3290
  */
2466
3291
  var muon = (options = {}) => {
2467
3292
  let resolvedConfig = void 0;
@@ -2485,7 +3310,7 @@ var muon = (options = {}) => {
2485
3310
  },
2486
3311
  closeBundle: async () => {
2487
3312
  if (resolvedConfig === void 0 || resolvedConfig.command !== "build") return;
2488
- if (process.env[suppressViteMuonBuildEnvironmentKey] === "1") return;
3313
+ if (process.env["MUON_SUPPRESS_VITE_MUON_BUILD"] === "1") return;
2489
3314
  if (options.build === false) return;
2490
3315
  const buildOptions = typeof options.build === "object" ? options.build : {};
2491
3316
  await buildMuonApp(createMuonBuildOptions(resolvedConfig, buildOptions));
@@ -2518,6 +3343,8 @@ var createMuonBuildOptions = (config, buildOptions) => {
2518
3343
  if (buildOptions.appId !== void 0) options.appId = buildOptions.appId;
2519
3344
  if (buildOptions.outputRoot !== void 0) options.outputRoot = buildOptions.outputRoot;
2520
3345
  if (buildOptions.configPath !== void 0) options.configPath = buildOptions.configPath;
3346
+ if (buildOptions.windowsResource !== void 0) options.windowsResource = buildOptions.windowsResource;
3347
+ if (buildOptions.linuxDesktop !== void 0) options.linuxDesktop = buildOptions.linuxDesktop;
2521
3348
  if (buildOptions.packageDirectory !== void 0) options.packageDirectory = buildOptions.packageDirectory;
2522
3349
  if (buildOptions.assetSalt !== void 0) options.assetSalt = buildOptions.assetSalt;
2523
3350
  return options;