craft-native 0.0.104 → 0.0.105

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -20,6 +20,7 @@ var __require = import.meta.require;
20
20
  // src/package.ts
21
21
  var exports_package = {};
22
22
  __export(exports_package, {
23
+ windowsNotificationAppId: () => windowsNotificationAppId,
23
24
  windowsExecutableName: () => windowsExecutableName,
24
25
  windowsArchitecture: () => windowsArchitecture,
25
26
  urlTypesEntry: () => urlTypesEntry,
@@ -32,11 +33,14 @@ __export(exports_package, {
32
33
  pack: () => pack,
33
34
  notarytoolArguments: () => notarytoolArguments,
34
35
  macOSInfoPlist: () => macOSInfoPlist,
36
+ linuxDesktopEntry: () => linuxDesktopEntry,
35
37
  formatPackagingCommandError: () => formatPackagingCommandError,
36
38
  dmgCreateArguments: () => dmgCreateArguments,
37
39
  dmgCapacityMegabytes: () => dmgCapacityMegabytes,
38
40
  codesignArguments: () => codesignArguments,
39
- candleArguments: () => candleArguments
41
+ candleArguments: () => candleArguments,
42
+ WINDOWS_NOTIFICATION_ID_FILE: () => WINDOWS_NOTIFICATION_ID_FILE,
43
+ DEFAULT_DEB_DEPENDENCIES: () => DEFAULT_DEB_DEPENDENCIES
40
44
  });
41
45
  import { spawn } from "child_process";
42
46
  import { createHash } from "crypto";
@@ -277,6 +281,9 @@ async function packageWindows(config, outDir) {
277
281
  outputPath: join(outDir, `${name}-${version2}.msi`),
278
282
  manufacturer: config.author || "Unknown",
279
283
  architecture: opts.architecture || windowsArchitecture(process.arch),
284
+ additionalFiles: opts.additionalFiles,
285
+ urlSchemes: opts.urlSchemes,
286
+ launchUrl: opts.launchUrl,
280
287
  certificatePath: opts.certificatePath,
281
288
  certificatePassword: opts.certificatePassword
282
289
  });
@@ -292,7 +299,9 @@ async function packageWindows(config, outDir) {
292
299
  const zipResult = await createZIP({
293
300
  name,
294
301
  version: version2,
302
+ manufacturer: config.author || "Unknown",
295
303
  binaryPath: config.binaryPath,
304
+ additionalFiles: opts.additionalFiles,
296
305
  outputPath: join(outDir, `${name}-${version2}-windows.zip`)
297
306
  });
298
307
  results.push({
@@ -317,7 +326,9 @@ async function packageLinux(config, outDir) {
317
326
  outputPath: join(outDir, `${name}_${version2}_amd64.deb`),
318
327
  description: config.description || "",
319
328
  maintainer: config.author || "Unknown",
320
- dependencies: opts.debDependencies || ["libgtk-3-0", "libwebkit2gtk-4.1-37"]
329
+ dependencies: opts.debDependencies || [...DEFAULT_DEB_DEPENDENCIES],
330
+ urlSchemes: opts.urlSchemes,
331
+ launchUrl: opts.launchUrl
321
332
  });
322
333
  results.push({
323
334
  success: debResult.success,
@@ -363,17 +374,7 @@ async function packageLinux(config, outDir) {
363
374
  return results;
364
375
  }
365
376
  function urlTypesEntry(bundleId, schemes) {
366
- const seen = new Set;
367
- const unique = [];
368
- for (const scheme of schemes) {
369
- if (!URL_SCHEME.test(scheme))
370
- throw new Error(`Invalid URL scheme ${JSON.stringify(scheme)}: must start with a letter and contain only letters, digits, "+", "-" or "."`);
371
- const key = scheme.toLowerCase();
372
- if (seen.has(key))
373
- continue;
374
- seen.add(key);
375
- unique.push(scheme);
376
- }
377
+ const unique = uniqueUrlSchemes(schemes);
377
378
  if (unique.length === 0)
378
379
  return null;
379
380
  const list = unique.map((scheme) => ` <string>${xml(scheme)}</string>`).join(`
@@ -392,6 +393,46 @@ ${list}
392
393
  </dict>
393
394
  </array>`;
394
395
  }
396
+ function uniqueUrlSchemes(schemes) {
397
+ const seen = new Set;
398
+ const unique = [];
399
+ for (const scheme of schemes) {
400
+ if (!URL_SCHEME.test(scheme))
401
+ throw new Error(`Invalid URL scheme ${JSON.stringify(scheme)}: must start with a letter and contain only letters, digits, "+", "-" or "."`);
402
+ const key = scheme.toLowerCase();
403
+ if (seen.has(key))
404
+ continue;
405
+ seen.add(key);
406
+ unique.push(scheme);
407
+ }
408
+ return unique;
409
+ }
410
+ function launchPageUrl(value) {
411
+ let url;
412
+ try {
413
+ url = new URL(value);
414
+ } catch {
415
+ throw new Error(`Invalid packaged app launch URL: ${JSON.stringify(value)}`);
416
+ }
417
+ if (!["http:", "https:", "file:"].includes(url.protocol))
418
+ throw new Error(`Unsupported packaged app launch URL protocol: ${url.protocol}`);
419
+ return url.href;
420
+ }
421
+ function desktopExecArgument(value) {
422
+ return `"${value.replaceAll("%", "%%").replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("$", "\\$").replaceAll("`", "\\`")}"`;
423
+ }
424
+ function linuxDesktopEntry(name, binaryName, urlSchemes = [], launchUrl) {
425
+ const schemes = uniqueUrlSchemes(urlSchemes);
426
+ const page = launchUrl ? ` --url ${desktopExecArgument(launchPageUrl(launchUrl))}` : "";
427
+ return `[Desktop Entry]
428
+ Type=Application
429
+ Name=${name}
430
+ Exec=/usr/bin/${binaryName}${page}${schemes.length ? " --deep-link %u" : ""}
431
+ Terminal=false
432
+ Categories=Utility;
433
+ ${schemes.length ? `MimeType=${schemes.map((scheme) => `x-scheme-handler/${scheme.toLowerCase()};`).join("")}
434
+ ` : ""}`;
435
+ }
395
436
  function macOSInfoPlist(metadata) {
396
437
  const entries = [
397
438
  ["CFBundleExecutable", metadata.name],
@@ -429,7 +470,7 @@ function codesignArguments(opts) {
429
470
  "--force",
430
471
  "--sign",
431
472
  opts.identity,
432
- "--timestamp",
473
+ ...opts.identity === "-" ? [] : ["--timestamp"],
433
474
  ...opts.hardenedRuntime ? ["--options", "runtime"] : [],
434
475
  ...opts.entitlements ? ["--entitlements", opts.entitlements] : [],
435
476
  "--deep",
@@ -654,6 +695,9 @@ function deterministicGuid(value) {
654
695
  const hex = digest.join("").toUpperCase();
655
696
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
656
697
  }
698
+ function windowsNotificationAppId(name, manufacturer) {
699
+ return `Craft.${deterministicGuid(`${manufacturer.trim() || "Unknown"}/${name}`)}`;
700
+ }
657
701
  function windowsArchitecture(architecture) {
658
702
  if (architecture === "ia32" || architecture === "x86")
659
703
  return "x86";
@@ -661,7 +705,7 @@ function windowsArchitecture(architecture) {
661
705
  return architecture;
662
706
  throw new Error(`Unsupported Windows package architecture: ${architecture}`);
663
707
  }
664
- function renderWixSource(opts, sourceName) {
708
+ function renderWixSource(opts, sourceName, additionalFileNames = []) {
665
709
  if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:\.[0-9]+)?$/.test(opts.version))
666
710
  throw new Error(`MSI version must have 3 or 4 numeric parts: ${opts.version}`);
667
711
  const id = wixIdentifier(opts.name);
@@ -669,6 +713,9 @@ function renderWixSource(opts, sourceName) {
669
713
  const upgradeCode = deterministicGuid(`${manufacturer}/${opts.name}`);
670
714
  const programFilesFolder = opts.architecture === "x86" ? "ProgramFilesFolder" : "ProgramFiles64Folder";
671
715
  const win64 = opts.architecture === "x86" ? "no" : "yes";
716
+ const notificationAppId = windowsNotificationAppId(opts.name, manufacturer);
717
+ const schemes = uniqueUrlSchemes(opts.urlSchemes || []);
718
+ const page = opts.launchUrl ? ` --url &quot;${xml(launchPageUrl(opts.launchUrl))}&quot;` : "";
672
719
  return `<?xml version="1.0" encoding="UTF-8"?>
673
720
  <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
674
721
  <Product Id="*" Name="${xml(opts.name)}" Language="1033" Version="${opts.version}" Manufacturer="${xml(manufacturer)}" UpgradeCode="${upgradeCode}">
@@ -679,18 +726,59 @@ function renderWixSource(opts, sourceName) {
679
726
  <Directory Id="${programFilesFolder}">
680
727
  <Directory Id="INSTALLFOLDER" Name="${xml(opts.name)}">
681
728
  <Component Id="${id}Executable" Guid="*" Win64="${win64}">
682
- <File Id="${id}File" Source="${xml(sourceName)}" KeyPath="yes" />
729
+ <File Id="${id}File" Source="${xml(sourceName)}" KeyPath="yes">
730
+ <Shortcut Id="${id}StartMenuShortcut" Directory="ProgramMenuFolder" Name="${xml(opts.name)}" WorkingDirectory="INSTALLFOLDER"${page ? ` Arguments="${page.trim()}"` : ""}>
731
+ <ShortcutProperty Key="System.AppUserModel.ID" Value="${notificationAppId}" />
732
+ </Shortcut>
733
+ </File>
734
+ </Component>
735
+ <Component Id="${id}NotificationIdentity" Guid="*" Win64="${win64}">
736
+ <File Id="${id}NotificationIdentityFile" Source="${WINDOWS_NOTIFICATION_ID_FILE}" KeyPath="yes" />
683
737
  </Component>
738
+ ${additionalFileNames.map((name, index) => ` <Component Id="${id}Extra${index}" Guid="*" Win64="${win64}">
739
+ <File Id="${id}ExtraFile${index}" Source="${xml(name)}" KeyPath="yes" />
740
+ </Component>`).join(`
741
+ `)}
742
+ ${schemes.map((scheme, index) => ` <Component Id="${id}Protocol${index}" Guid="${deterministicGuid(`${manufacturer}/${opts.name}/protocol/${scheme.toLowerCase()}`)}" Win64="${win64}">
743
+ <RegistryKey Root="HKLM" Key="Software\\Classes\\${xml(scheme.toLowerCase())}" Action="createAndRemoveOnUninstall">
744
+ <RegistryValue Type="string" Value="URL:${xml(opts.name)} Protocol" KeyPath="yes" />
745
+ <RegistryValue Type="string" Name="URL Protocol" Value="" />
746
+ <RegistryKey Key="shell\\open\\command">
747
+ <RegistryValue Type="string" Value="&quot;[#${id}File]&quot;${page} --deep-link &quot;%1&quot;" />
748
+ </RegistryKey>
749
+ </RegistryKey>
750
+ </Component>`).join(`
751
+ `)}
684
752
  </Directory>
685
753
  </Directory>
754
+ <Directory Id="ProgramMenuFolder" />
686
755
  </Directory>
687
756
  <Feature Id="ProductFeature" Title="${xml(opts.name)}" Level="1">
688
757
  <ComponentRef Id="${id}Executable" />
758
+ <ComponentRef Id="${id}NotificationIdentity" />
759
+ ${additionalFileNames.map((_, index) => ` <ComponentRef Id="${id}Extra${index}" />`).join(`
760
+ `)}
761
+ ${schemes.map((_, index) => ` <ComponentRef Id="${id}Protocol${index}" />`).join(`
762
+ `)}
689
763
  </Feature>
690
764
  </Product>
691
765
  </Wix>
692
766
  `;
693
767
  }
768
+ function windowsCompanionFiles(paths, executableName) {
769
+ const seen = new Set([executableName.toLowerCase(), WINDOWS_NOTIFICATION_ID_FILE.toLowerCase()]);
770
+ return (paths || []).map((path2) => {
771
+ const name = basename(path2);
772
+ if (!name || /[<>:"/\\|?*\u0000-\u001F]/.test(name) || /[. ]$/.test(name) || /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i.test(name))
773
+ throw new Error(`Invalid Windows companion file name: ${JSON.stringify(name)}`);
774
+ if (seen.has(name.toLowerCase()))
775
+ throw new Error(`Duplicate Windows package file: ${name}`);
776
+ seen.add(name.toLowerCase());
777
+ if (!existsSync3(path2) || lstatSync(path2).isDirectory())
778
+ throw new Error(`Windows companion file not found: ${path2}`);
779
+ return { path: path2, name };
780
+ });
781
+ }
694
782
  function windowsExecutableName(name) {
695
783
  if (name.length === 0 || name.trim() !== name || /[<>:"/\\|?*\u0000-\u001F]/.test(name) || /[. ]$/.test(name))
696
784
  throw new Error(`Invalid Windows application name: ${JSON.stringify(name)}`);
@@ -712,11 +800,15 @@ async function createMSI(opts) {
712
800
  const tempDir = mkdtempSync(join(tmpdir(), "craft-msi-"));
713
801
  try {
714
802
  const binaryName = windowsExecutableName(opts.name);
803
+ const additionalFiles = windowsCompanionFiles(opts.additionalFiles, binaryName);
715
804
  const sourcePath = join(tempDir, binaryName);
716
805
  const wxsPath = join(tempDir, "installer.wxs");
717
806
  const wixobjPath = join(tempDir, "installer.wixobj");
718
807
  copyFileSync(opts.binaryPath, sourcePath);
719
- writeFileSync2(wxsPath, renderWixSource(opts, binaryName));
808
+ writeFileSync2(join(tempDir, WINDOWS_NOTIFICATION_ID_FILE), windowsNotificationAppId(opts.name, opts.manufacturer));
809
+ for (const file of additionalFiles)
810
+ copyFileSync(file.path, join(tempDir, file.name));
811
+ writeFileSync2(wxsPath, renderWixSource(opts, binaryName, additionalFiles.map((file) => file.name)));
720
812
  await runCommand("candle.exe", candleArguments(opts.architecture, wixobjPath, wxsPath), tempDir);
721
813
  await runCommand("light.exe", ["-nologo", "-sval", "-out", opts.outputPath, wixobjPath], tempDir);
722
814
  if (opts.certificatePath) {
@@ -738,8 +830,13 @@ async function createMSI(opts) {
738
830
  }
739
831
  async function createZIP(opts) {
740
832
  try {
741
- const data = new Uint8Array(readFileSync2(opts.binaryPath));
742
- const zip = buildZip([{ name: `${opts.name}.exe`, data }]);
833
+ const binaryName = windowsExecutableName(opts.name);
834
+ const additionalFiles = windowsCompanionFiles(opts.additionalFiles, binaryName);
835
+ const entries = [{ name: binaryName, data: new Uint8Array(readFileSync2(opts.binaryPath)) }];
836
+ entries.push({ name: WINDOWS_NOTIFICATION_ID_FILE, data: new TextEncoder().encode(windowsNotificationAppId(opts.name, opts.manufacturer)) });
837
+ for (const file of additionalFiles)
838
+ entries.push({ name: file.name, data: new Uint8Array(readFileSync2(file.path)) });
839
+ const zip = buildZip(entries);
743
840
  writeFileSync2(opts.outputPath, zip);
744
841
  return { success: true, outputPath: opts.outputPath };
745
842
  } catch (err) {
@@ -769,6 +866,7 @@ async function createDEB(opts) {
769
866
  return;
770
867
  }
771
868
  }
869
+ const desktopContent = linuxDesktopEntry(opts.name, sanitizedName, opts.urlSchemes, opts.launchUrl);
772
870
  const tempDir = mkdtempSync(join(tmpdir(), "craft-deb-"));
773
871
  const debianDir = join(tempDir, "DEBIAN");
774
872
  const binDir = join(tempDir, "usr", "bin");
@@ -789,13 +887,6 @@ Maintainer: ${maintainer}
789
887
  Description: ${description || opts.name}
790
888
  `;
791
889
  writeFileSync2(join(debianDir, "control"), controlContent);
792
- const desktopContent = `[Desktop Entry]
793
- Type=Application
794
- Name=${opts.name}
795
- Exec=/usr/bin/${binaryName}
796
- Terminal=false
797
- Categories=Utility;
798
- `;
799
890
  writeFileSync2(join(applicationsDir, `${binaryName}.desktop`), desktopContent);
800
891
  const proc = spawn("dpkg-deb", ["--build", tempDir, opts.outputPath]);
801
892
  proc.on("close", (code) => {
@@ -1031,8 +1122,9 @@ async function pack(options) {
1031
1122
  platforms: [detectPlatform()]
1032
1123
  });
1033
1124
  }
1034
- var CRC32_TABLE, URL_SCHEME, MEBIBYTE, HDIUTIL_MAX_ATTEMPTS = 3, runHdiutil = (args) => runTool("hdiutil", args);
1125
+ var DEFAULT_DEB_DEPENDENCIES, CRC32_TABLE, URL_SCHEME, MEBIBYTE, HDIUTIL_MAX_ATTEMPTS = 3, runHdiutil = (args) => runTool("hdiutil", args), WINDOWS_NOTIFICATION_ID_FILE = "craft-notification-id.txt";
1035
1126
  var init_package = __esm(() => {
1127
+ DEFAULT_DEB_DEPENDENCIES = ["libgtk-3-0", "libwebkit2gtk-4.1-37", "libnotify-bin"];
1036
1128
  CRC32_TABLE = (() => {
1037
1129
  const table = new Uint32Array(256);
1038
1130
  for (let n = 0;n < 256; n++) {
@@ -4621,7 +4713,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
4621
4713
  `);
4622
4714
  }
4623
4715
  // package.json
4624
- var version = "0.0.104";
4716
+ var version = "0.0.105";
4625
4717
 
4626
4718
  // bin/cli.ts
4627
4719
  var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
package/dist/index.cjs CHANGED
@@ -310,7 +310,7 @@ function craftBinaryNotFoundMessage(triedPath) {
310
310
  `);
311
311
  }
312
312
  // package.json
313
- var version = "0.0.104";
313
+ var version = "0.0.105";
314
314
 
315
315
  // src/sdk-version.ts
316
316
  var SDK_VERSION = version;
@@ -322,6 +322,7 @@ var import_fs = require("fs");
322
322
  var import_os = require("os");
323
323
  var import_path = require("path");
324
324
  var import_zlib = require("zlib");
325
+ var DEFAULT_DEB_DEPENDENCIES = ["libgtk-3-0", "libwebkit2gtk-4.1-37", "libnotify-bin"];
325
326
  var CRC32_TABLE = (() => {
326
327
  const table = new Uint32Array(256);
327
328
  for (let n = 0;n < 256; n++) {
@@ -553,6 +554,9 @@ async function packageWindows(config, outDir) {
553
554
  outputPath: import_path.join(outDir, `${name}-${version2}.msi`),
554
555
  manufacturer: config.author || "Unknown",
555
556
  architecture: opts.architecture || windowsArchitecture(process.arch),
557
+ additionalFiles: opts.additionalFiles,
558
+ urlSchemes: opts.urlSchemes,
559
+ launchUrl: opts.launchUrl,
556
560
  certificatePath: opts.certificatePath,
557
561
  certificatePassword: opts.certificatePassword
558
562
  });
@@ -568,7 +572,9 @@ async function packageWindows(config, outDir) {
568
572
  const zipResult = await createZIP({
569
573
  name,
570
574
  version: version2,
575
+ manufacturer: config.author || "Unknown",
571
576
  binaryPath: config.binaryPath,
577
+ additionalFiles: opts.additionalFiles,
572
578
  outputPath: import_path.join(outDir, `${name}-${version2}-windows.zip`)
573
579
  });
574
580
  results.push({
@@ -593,7 +599,9 @@ async function packageLinux(config, outDir) {
593
599
  outputPath: import_path.join(outDir, `${name}_${version2}_amd64.deb`),
594
600
  description: config.description || "",
595
601
  maintainer: config.author || "Unknown",
596
- dependencies: opts.debDependencies || ["libgtk-3-0", "libwebkit2gtk-4.1-37"]
602
+ dependencies: opts.debDependencies || [...DEFAULT_DEB_DEPENDENCIES],
603
+ urlSchemes: opts.urlSchemes,
604
+ launchUrl: opts.launchUrl
597
605
  });
598
606
  results.push({
599
607
  success: debResult.success,
@@ -640,17 +648,7 @@ async function packageLinux(config, outDir) {
640
648
  }
641
649
  var URL_SCHEME = /^[a-z][a-z0-9+.-]*$/i;
642
650
  function urlTypesEntry(bundleId, schemes) {
643
- const seen = new Set;
644
- const unique = [];
645
- for (const scheme of schemes) {
646
- if (!URL_SCHEME.test(scheme))
647
- throw new Error(`Invalid URL scheme ${JSON.stringify(scheme)}: must start with a letter and contain only letters, digits, "+", "-" or "."`);
648
- const key = scheme.toLowerCase();
649
- if (seen.has(key))
650
- continue;
651
- seen.add(key);
652
- unique.push(scheme);
653
- }
651
+ const unique = uniqueUrlSchemes(schemes);
654
652
  if (unique.length === 0)
655
653
  return null;
656
654
  const list = unique.map((scheme) => ` <string>${xml(scheme)}</string>`).join(`
@@ -669,6 +667,46 @@ ${list}
669
667
  </dict>
670
668
  </array>`;
671
669
  }
670
+ function uniqueUrlSchemes(schemes) {
671
+ const seen = new Set;
672
+ const unique = [];
673
+ for (const scheme of schemes) {
674
+ if (!URL_SCHEME.test(scheme))
675
+ throw new Error(`Invalid URL scheme ${JSON.stringify(scheme)}: must start with a letter and contain only letters, digits, "+", "-" or "."`);
676
+ const key = scheme.toLowerCase();
677
+ if (seen.has(key))
678
+ continue;
679
+ seen.add(key);
680
+ unique.push(scheme);
681
+ }
682
+ return unique;
683
+ }
684
+ function launchPageUrl(value) {
685
+ let url;
686
+ try {
687
+ url = new URL(value);
688
+ } catch {
689
+ throw new Error(`Invalid packaged app launch URL: ${JSON.stringify(value)}`);
690
+ }
691
+ if (!["http:", "https:", "file:"].includes(url.protocol))
692
+ throw new Error(`Unsupported packaged app launch URL protocol: ${url.protocol}`);
693
+ return url.href;
694
+ }
695
+ function desktopExecArgument(value) {
696
+ return `"${value.replaceAll("%", "%%").replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("$", "\\$").replaceAll("`", "\\`")}"`;
697
+ }
698
+ function linuxDesktopEntry(name, binaryName, urlSchemes = [], launchUrl) {
699
+ const schemes = uniqueUrlSchemes(urlSchemes);
700
+ const page = launchUrl ? ` --url ${desktopExecArgument(launchPageUrl(launchUrl))}` : "";
701
+ return `[Desktop Entry]
702
+ Type=Application
703
+ Name=${name}
704
+ Exec=/usr/bin/${binaryName}${page}${schemes.length ? " --deep-link %u" : ""}
705
+ Terminal=false
706
+ Categories=Utility;
707
+ ${schemes.length ? `MimeType=${schemes.map((scheme) => `x-scheme-handler/${scheme.toLowerCase()};`).join("")}
708
+ ` : ""}`;
709
+ }
672
710
  function macOSInfoPlist(metadata) {
673
711
  const entries = [
674
712
  ["CFBundleExecutable", metadata.name],
@@ -706,7 +744,7 @@ function codesignArguments(opts) {
706
744
  "--force",
707
745
  "--sign",
708
746
  opts.identity,
709
- "--timestamp",
747
+ ...opts.identity === "-" ? [] : ["--timestamp"],
710
748
  ...opts.hardenedRuntime ? ["--options", "runtime"] : [],
711
749
  ...opts.entitlements ? ["--entitlements", opts.entitlements] : [],
712
750
  "--deep",
@@ -934,6 +972,10 @@ function deterministicGuid(value) {
934
972
  const hex = digest.join("").toUpperCase();
935
973
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
936
974
  }
975
+ var WINDOWS_NOTIFICATION_ID_FILE = "craft-notification-id.txt";
976
+ function windowsNotificationAppId(name, manufacturer) {
977
+ return `Craft.${deterministicGuid(`${manufacturer.trim() || "Unknown"}/${name}`)}`;
978
+ }
937
979
  function windowsArchitecture(architecture) {
938
980
  if (architecture === "ia32" || architecture === "x86")
939
981
  return "x86";
@@ -941,7 +983,7 @@ function windowsArchitecture(architecture) {
941
983
  return architecture;
942
984
  throw new Error(`Unsupported Windows package architecture: ${architecture}`);
943
985
  }
944
- function renderWixSource(opts, sourceName) {
986
+ function renderWixSource(opts, sourceName, additionalFileNames = []) {
945
987
  if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:\.[0-9]+)?$/.test(opts.version))
946
988
  throw new Error(`MSI version must have 3 or 4 numeric parts: ${opts.version}`);
947
989
  const id = wixIdentifier(opts.name);
@@ -949,6 +991,9 @@ function renderWixSource(opts, sourceName) {
949
991
  const upgradeCode = deterministicGuid(`${manufacturer}/${opts.name}`);
950
992
  const programFilesFolder = opts.architecture === "x86" ? "ProgramFilesFolder" : "ProgramFiles64Folder";
951
993
  const win64 = opts.architecture === "x86" ? "no" : "yes";
994
+ const notificationAppId = windowsNotificationAppId(opts.name, manufacturer);
995
+ const schemes = uniqueUrlSchemes(opts.urlSchemes || []);
996
+ const page = opts.launchUrl ? ` --url &quot;${xml(launchPageUrl(opts.launchUrl))}&quot;` : "";
952
997
  return `<?xml version="1.0" encoding="UTF-8"?>
953
998
  <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
954
999
  <Product Id="*" Name="${xml(opts.name)}" Language="1033" Version="${opts.version}" Manufacturer="${xml(manufacturer)}" UpgradeCode="${upgradeCode}">
@@ -959,18 +1004,59 @@ function renderWixSource(opts, sourceName) {
959
1004
  <Directory Id="${programFilesFolder}">
960
1005
  <Directory Id="INSTALLFOLDER" Name="${xml(opts.name)}">
961
1006
  <Component Id="${id}Executable" Guid="*" Win64="${win64}">
962
- <File Id="${id}File" Source="${xml(sourceName)}" KeyPath="yes" />
1007
+ <File Id="${id}File" Source="${xml(sourceName)}" KeyPath="yes">
1008
+ <Shortcut Id="${id}StartMenuShortcut" Directory="ProgramMenuFolder" Name="${xml(opts.name)}" WorkingDirectory="INSTALLFOLDER"${page ? ` Arguments="${page.trim()}"` : ""}>
1009
+ <ShortcutProperty Key="System.AppUserModel.ID" Value="${notificationAppId}" />
1010
+ </Shortcut>
1011
+ </File>
1012
+ </Component>
1013
+ <Component Id="${id}NotificationIdentity" Guid="*" Win64="${win64}">
1014
+ <File Id="${id}NotificationIdentityFile" Source="${WINDOWS_NOTIFICATION_ID_FILE}" KeyPath="yes" />
963
1015
  </Component>
1016
+ ${additionalFileNames.map((name, index) => ` <Component Id="${id}Extra${index}" Guid="*" Win64="${win64}">
1017
+ <File Id="${id}ExtraFile${index}" Source="${xml(name)}" KeyPath="yes" />
1018
+ </Component>`).join(`
1019
+ `)}
1020
+ ${schemes.map((scheme, index) => ` <Component Id="${id}Protocol${index}" Guid="${deterministicGuid(`${manufacturer}/${opts.name}/protocol/${scheme.toLowerCase()}`)}" Win64="${win64}">
1021
+ <RegistryKey Root="HKLM" Key="Software\\Classes\\${xml(scheme.toLowerCase())}" Action="createAndRemoveOnUninstall">
1022
+ <RegistryValue Type="string" Value="URL:${xml(opts.name)} Protocol" KeyPath="yes" />
1023
+ <RegistryValue Type="string" Name="URL Protocol" Value="" />
1024
+ <RegistryKey Key="shell\\open\\command">
1025
+ <RegistryValue Type="string" Value="&quot;[#${id}File]&quot;${page} --deep-link &quot;%1&quot;" />
1026
+ </RegistryKey>
1027
+ </RegistryKey>
1028
+ </Component>`).join(`
1029
+ `)}
964
1030
  </Directory>
965
1031
  </Directory>
1032
+ <Directory Id="ProgramMenuFolder" />
966
1033
  </Directory>
967
1034
  <Feature Id="ProductFeature" Title="${xml(opts.name)}" Level="1">
968
1035
  <ComponentRef Id="${id}Executable" />
1036
+ <ComponentRef Id="${id}NotificationIdentity" />
1037
+ ${additionalFileNames.map((_, index) => ` <ComponentRef Id="${id}Extra${index}" />`).join(`
1038
+ `)}
1039
+ ${schemes.map((_, index) => ` <ComponentRef Id="${id}Protocol${index}" />`).join(`
1040
+ `)}
969
1041
  </Feature>
970
1042
  </Product>
971
1043
  </Wix>
972
1044
  `;
973
1045
  }
1046
+ function windowsCompanionFiles(paths, executableName) {
1047
+ const seen = new Set([executableName.toLowerCase(), WINDOWS_NOTIFICATION_ID_FILE.toLowerCase()]);
1048
+ return (paths || []).map((path) => {
1049
+ const name = import_path.basename(path);
1050
+ if (!name || /[<>:"/\\|?*\u0000-\u001F]/.test(name) || /[. ]$/.test(name) || /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i.test(name))
1051
+ throw new Error(`Invalid Windows companion file name: ${JSON.stringify(name)}`);
1052
+ if (seen.has(name.toLowerCase()))
1053
+ throw new Error(`Duplicate Windows package file: ${name}`);
1054
+ seen.add(name.toLowerCase());
1055
+ if (!import_fs.existsSync(path) || import_fs.lstatSync(path).isDirectory())
1056
+ throw new Error(`Windows companion file not found: ${path}`);
1057
+ return { path, name };
1058
+ });
1059
+ }
974
1060
  function windowsExecutableName(name) {
975
1061
  if (name.length === 0 || name.trim() !== name || /[<>:"/\\|?*\u0000-\u001F]/.test(name) || /[. ]$/.test(name))
976
1062
  throw new Error(`Invalid Windows application name: ${JSON.stringify(name)}`);
@@ -992,11 +1078,15 @@ async function createMSI(opts) {
992
1078
  const tempDir = import_fs.mkdtempSync(import_path.join(import_os.tmpdir(), "craft-msi-"));
993
1079
  try {
994
1080
  const binaryName = windowsExecutableName(opts.name);
1081
+ const additionalFiles = windowsCompanionFiles(opts.additionalFiles, binaryName);
995
1082
  const sourcePath = import_path.join(tempDir, binaryName);
996
1083
  const wxsPath = import_path.join(tempDir, "installer.wxs");
997
1084
  const wixobjPath = import_path.join(tempDir, "installer.wixobj");
998
1085
  import_fs.copyFileSync(opts.binaryPath, sourcePath);
999
- import_fs.writeFileSync(wxsPath, renderWixSource(opts, binaryName));
1086
+ import_fs.writeFileSync(import_path.join(tempDir, WINDOWS_NOTIFICATION_ID_FILE), windowsNotificationAppId(opts.name, opts.manufacturer));
1087
+ for (const file of additionalFiles)
1088
+ import_fs.copyFileSync(file.path, import_path.join(tempDir, file.name));
1089
+ import_fs.writeFileSync(wxsPath, renderWixSource(opts, binaryName, additionalFiles.map((file) => file.name)));
1000
1090
  await runCommand("candle.exe", candleArguments(opts.architecture, wixobjPath, wxsPath), tempDir);
1001
1091
  await runCommand("light.exe", ["-nologo", "-sval", "-out", opts.outputPath, wixobjPath], tempDir);
1002
1092
  if (opts.certificatePath) {
@@ -1018,8 +1108,13 @@ async function createMSI(opts) {
1018
1108
  }
1019
1109
  async function createZIP(opts) {
1020
1110
  try {
1021
- const data = new Uint8Array(import_fs.readFileSync(opts.binaryPath));
1022
- const zip = buildZip([{ name: `${opts.name}.exe`, data }]);
1111
+ const binaryName = windowsExecutableName(opts.name);
1112
+ const additionalFiles = windowsCompanionFiles(opts.additionalFiles, binaryName);
1113
+ const entries = [{ name: binaryName, data: new Uint8Array(import_fs.readFileSync(opts.binaryPath)) }];
1114
+ entries.push({ name: WINDOWS_NOTIFICATION_ID_FILE, data: new TextEncoder().encode(windowsNotificationAppId(opts.name, opts.manufacturer)) });
1115
+ for (const file of additionalFiles)
1116
+ entries.push({ name: file.name, data: new Uint8Array(import_fs.readFileSync(file.path)) });
1117
+ const zip = buildZip(entries);
1023
1118
  import_fs.writeFileSync(opts.outputPath, zip);
1024
1119
  return { success: true, outputPath: opts.outputPath };
1025
1120
  } catch (err) {
@@ -1049,6 +1144,7 @@ async function createDEB(opts) {
1049
1144
  return;
1050
1145
  }
1051
1146
  }
1147
+ const desktopContent = linuxDesktopEntry(opts.name, sanitizedName, opts.urlSchemes, opts.launchUrl);
1052
1148
  const tempDir = import_fs.mkdtempSync(import_path.join(import_os.tmpdir(), "craft-deb-"));
1053
1149
  const debianDir = import_path.join(tempDir, "DEBIAN");
1054
1150
  const binDir = import_path.join(tempDir, "usr", "bin");
@@ -1069,13 +1165,6 @@ Maintainer: ${maintainer}
1069
1165
  Description: ${description || opts.name}
1070
1166
  `;
1071
1167
  import_fs.writeFileSync(import_path.join(debianDir, "control"), controlContent);
1072
- const desktopContent = `[Desktop Entry]
1073
- Type=Application
1074
- Name=${opts.name}
1075
- Exec=/usr/bin/${binaryName}
1076
- Terminal=false
1077
- Categories=Utility;
1078
- `;
1079
1168
  import_fs.writeFileSync(import_path.join(applicationsDir, `${binaryName}.desktop`), desktopContent);
1080
1169
  const proc = import_child_process.spawn("dpkg-deb", ["--build", tempDir, opts.outputPath]);
1081
1170
  proc.on("close", (code) => {
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ function craftBinaryNotFoundMessage(triedPath) {
58
58
  `);
59
59
  }
60
60
  // package.json
61
- var version = "0.0.104";
61
+ var version = "0.0.105";
62
62
 
63
63
  // src/sdk-version.ts
64
64
  var SDK_VERSION = version;
@@ -82,6 +82,7 @@ import {
82
82
  import { homedir, tmpdir } from "os";
83
83
  import { basename, join } from "path";
84
84
  import { deflateRawSync } from "zlib";
85
+ var DEFAULT_DEB_DEPENDENCIES = ["libgtk-3-0", "libwebkit2gtk-4.1-37", "libnotify-bin"];
85
86
  var CRC32_TABLE = (() => {
86
87
  const table = new Uint32Array(256);
87
88
  for (let n = 0;n < 256; n++) {
@@ -313,6 +314,9 @@ async function packageWindows(config, outDir) {
313
314
  outputPath: join(outDir, `${name}-${version2}.msi`),
314
315
  manufacturer: config.author || "Unknown",
315
316
  architecture: opts.architecture || windowsArchitecture(process.arch),
317
+ additionalFiles: opts.additionalFiles,
318
+ urlSchemes: opts.urlSchemes,
319
+ launchUrl: opts.launchUrl,
316
320
  certificatePath: opts.certificatePath,
317
321
  certificatePassword: opts.certificatePassword
318
322
  });
@@ -328,7 +332,9 @@ async function packageWindows(config, outDir) {
328
332
  const zipResult = await createZIP({
329
333
  name,
330
334
  version: version2,
335
+ manufacturer: config.author || "Unknown",
331
336
  binaryPath: config.binaryPath,
337
+ additionalFiles: opts.additionalFiles,
332
338
  outputPath: join(outDir, `${name}-${version2}-windows.zip`)
333
339
  });
334
340
  results.push({
@@ -353,7 +359,9 @@ async function packageLinux(config, outDir) {
353
359
  outputPath: join(outDir, `${name}_${version2}_amd64.deb`),
354
360
  description: config.description || "",
355
361
  maintainer: config.author || "Unknown",
356
- dependencies: opts.debDependencies || ["libgtk-3-0", "libwebkit2gtk-4.1-37"]
362
+ dependencies: opts.debDependencies || [...DEFAULT_DEB_DEPENDENCIES],
363
+ urlSchemes: opts.urlSchemes,
364
+ launchUrl: opts.launchUrl
357
365
  });
358
366
  results.push({
359
367
  success: debResult.success,
@@ -400,17 +408,7 @@ async function packageLinux(config, outDir) {
400
408
  }
401
409
  var URL_SCHEME = /^[a-z][a-z0-9+.-]*$/i;
402
410
  function urlTypesEntry(bundleId, schemes) {
403
- const seen = new Set;
404
- const unique = [];
405
- for (const scheme of schemes) {
406
- if (!URL_SCHEME.test(scheme))
407
- throw new Error(`Invalid URL scheme ${JSON.stringify(scheme)}: must start with a letter and contain only letters, digits, "+", "-" or "."`);
408
- const key = scheme.toLowerCase();
409
- if (seen.has(key))
410
- continue;
411
- seen.add(key);
412
- unique.push(scheme);
413
- }
411
+ const unique = uniqueUrlSchemes(schemes);
414
412
  if (unique.length === 0)
415
413
  return null;
416
414
  const list = unique.map((scheme) => ` <string>${xml(scheme)}</string>`).join(`
@@ -429,6 +427,46 @@ ${list}
429
427
  </dict>
430
428
  </array>`;
431
429
  }
430
+ function uniqueUrlSchemes(schemes) {
431
+ const seen = new Set;
432
+ const unique = [];
433
+ for (const scheme of schemes) {
434
+ if (!URL_SCHEME.test(scheme))
435
+ throw new Error(`Invalid URL scheme ${JSON.stringify(scheme)}: must start with a letter and contain only letters, digits, "+", "-" or "."`);
436
+ const key = scheme.toLowerCase();
437
+ if (seen.has(key))
438
+ continue;
439
+ seen.add(key);
440
+ unique.push(scheme);
441
+ }
442
+ return unique;
443
+ }
444
+ function launchPageUrl(value) {
445
+ let url;
446
+ try {
447
+ url = new URL(value);
448
+ } catch {
449
+ throw new Error(`Invalid packaged app launch URL: ${JSON.stringify(value)}`);
450
+ }
451
+ if (!["http:", "https:", "file:"].includes(url.protocol))
452
+ throw new Error(`Unsupported packaged app launch URL protocol: ${url.protocol}`);
453
+ return url.href;
454
+ }
455
+ function desktopExecArgument(value) {
456
+ return `"${value.replaceAll("%", "%%").replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("$", "\\$").replaceAll("`", "\\`")}"`;
457
+ }
458
+ function linuxDesktopEntry(name, binaryName, urlSchemes = [], launchUrl) {
459
+ const schemes = uniqueUrlSchemes(urlSchemes);
460
+ const page = launchUrl ? ` --url ${desktopExecArgument(launchPageUrl(launchUrl))}` : "";
461
+ return `[Desktop Entry]
462
+ Type=Application
463
+ Name=${name}
464
+ Exec=/usr/bin/${binaryName}${page}${schemes.length ? " --deep-link %u" : ""}
465
+ Terminal=false
466
+ Categories=Utility;
467
+ ${schemes.length ? `MimeType=${schemes.map((scheme) => `x-scheme-handler/${scheme.toLowerCase()};`).join("")}
468
+ ` : ""}`;
469
+ }
432
470
  function macOSInfoPlist(metadata) {
433
471
  const entries = [
434
472
  ["CFBundleExecutable", metadata.name],
@@ -466,7 +504,7 @@ function codesignArguments(opts) {
466
504
  "--force",
467
505
  "--sign",
468
506
  opts.identity,
469
- "--timestamp",
507
+ ...opts.identity === "-" ? [] : ["--timestamp"],
470
508
  ...opts.hardenedRuntime ? ["--options", "runtime"] : [],
471
509
  ...opts.entitlements ? ["--entitlements", opts.entitlements] : [],
472
510
  "--deep",
@@ -694,6 +732,10 @@ function deterministicGuid(value) {
694
732
  const hex = digest.join("").toUpperCase();
695
733
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
696
734
  }
735
+ var WINDOWS_NOTIFICATION_ID_FILE = "craft-notification-id.txt";
736
+ function windowsNotificationAppId(name, manufacturer) {
737
+ return `Craft.${deterministicGuid(`${manufacturer.trim() || "Unknown"}/${name}`)}`;
738
+ }
697
739
  function windowsArchitecture(architecture) {
698
740
  if (architecture === "ia32" || architecture === "x86")
699
741
  return "x86";
@@ -701,7 +743,7 @@ function windowsArchitecture(architecture) {
701
743
  return architecture;
702
744
  throw new Error(`Unsupported Windows package architecture: ${architecture}`);
703
745
  }
704
- function renderWixSource(opts, sourceName) {
746
+ function renderWixSource(opts, sourceName, additionalFileNames = []) {
705
747
  if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:\.[0-9]+)?$/.test(opts.version))
706
748
  throw new Error(`MSI version must have 3 or 4 numeric parts: ${opts.version}`);
707
749
  const id = wixIdentifier(opts.name);
@@ -709,6 +751,9 @@ function renderWixSource(opts, sourceName) {
709
751
  const upgradeCode = deterministicGuid(`${manufacturer}/${opts.name}`);
710
752
  const programFilesFolder = opts.architecture === "x86" ? "ProgramFilesFolder" : "ProgramFiles64Folder";
711
753
  const win64 = opts.architecture === "x86" ? "no" : "yes";
754
+ const notificationAppId = windowsNotificationAppId(opts.name, manufacturer);
755
+ const schemes = uniqueUrlSchemes(opts.urlSchemes || []);
756
+ const page = opts.launchUrl ? ` --url &quot;${xml(launchPageUrl(opts.launchUrl))}&quot;` : "";
712
757
  return `<?xml version="1.0" encoding="UTF-8"?>
713
758
  <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
714
759
  <Product Id="*" Name="${xml(opts.name)}" Language="1033" Version="${opts.version}" Manufacturer="${xml(manufacturer)}" UpgradeCode="${upgradeCode}">
@@ -719,18 +764,59 @@ function renderWixSource(opts, sourceName) {
719
764
  <Directory Id="${programFilesFolder}">
720
765
  <Directory Id="INSTALLFOLDER" Name="${xml(opts.name)}">
721
766
  <Component Id="${id}Executable" Guid="*" Win64="${win64}">
722
- <File Id="${id}File" Source="${xml(sourceName)}" KeyPath="yes" />
767
+ <File Id="${id}File" Source="${xml(sourceName)}" KeyPath="yes">
768
+ <Shortcut Id="${id}StartMenuShortcut" Directory="ProgramMenuFolder" Name="${xml(opts.name)}" WorkingDirectory="INSTALLFOLDER"${page ? ` Arguments="${page.trim()}"` : ""}>
769
+ <ShortcutProperty Key="System.AppUserModel.ID" Value="${notificationAppId}" />
770
+ </Shortcut>
771
+ </File>
772
+ </Component>
773
+ <Component Id="${id}NotificationIdentity" Guid="*" Win64="${win64}">
774
+ <File Id="${id}NotificationIdentityFile" Source="${WINDOWS_NOTIFICATION_ID_FILE}" KeyPath="yes" />
723
775
  </Component>
776
+ ${additionalFileNames.map((name, index) => ` <Component Id="${id}Extra${index}" Guid="*" Win64="${win64}">
777
+ <File Id="${id}ExtraFile${index}" Source="${xml(name)}" KeyPath="yes" />
778
+ </Component>`).join(`
779
+ `)}
780
+ ${schemes.map((scheme, index) => ` <Component Id="${id}Protocol${index}" Guid="${deterministicGuid(`${manufacturer}/${opts.name}/protocol/${scheme.toLowerCase()}`)}" Win64="${win64}">
781
+ <RegistryKey Root="HKLM" Key="Software\\Classes\\${xml(scheme.toLowerCase())}" Action="createAndRemoveOnUninstall">
782
+ <RegistryValue Type="string" Value="URL:${xml(opts.name)} Protocol" KeyPath="yes" />
783
+ <RegistryValue Type="string" Name="URL Protocol" Value="" />
784
+ <RegistryKey Key="shell\\open\\command">
785
+ <RegistryValue Type="string" Value="&quot;[#${id}File]&quot;${page} --deep-link &quot;%1&quot;" />
786
+ </RegistryKey>
787
+ </RegistryKey>
788
+ </Component>`).join(`
789
+ `)}
724
790
  </Directory>
725
791
  </Directory>
792
+ <Directory Id="ProgramMenuFolder" />
726
793
  </Directory>
727
794
  <Feature Id="ProductFeature" Title="${xml(opts.name)}" Level="1">
728
795
  <ComponentRef Id="${id}Executable" />
796
+ <ComponentRef Id="${id}NotificationIdentity" />
797
+ ${additionalFileNames.map((_, index) => ` <ComponentRef Id="${id}Extra${index}" />`).join(`
798
+ `)}
799
+ ${schemes.map((_, index) => ` <ComponentRef Id="${id}Protocol${index}" />`).join(`
800
+ `)}
729
801
  </Feature>
730
802
  </Product>
731
803
  </Wix>
732
804
  `;
733
805
  }
806
+ function windowsCompanionFiles(paths, executableName) {
807
+ const seen = new Set([executableName.toLowerCase(), WINDOWS_NOTIFICATION_ID_FILE.toLowerCase()]);
808
+ return (paths || []).map((path) => {
809
+ const name = basename(path);
810
+ if (!name || /[<>:"/\\|?*\u0000-\u001F]/.test(name) || /[. ]$/.test(name) || /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i.test(name))
811
+ throw new Error(`Invalid Windows companion file name: ${JSON.stringify(name)}`);
812
+ if (seen.has(name.toLowerCase()))
813
+ throw new Error(`Duplicate Windows package file: ${name}`);
814
+ seen.add(name.toLowerCase());
815
+ if (!existsSync2(path) || lstatSync(path).isDirectory())
816
+ throw new Error(`Windows companion file not found: ${path}`);
817
+ return { path, name };
818
+ });
819
+ }
734
820
  function windowsExecutableName(name) {
735
821
  if (name.length === 0 || name.trim() !== name || /[<>:"/\\|?*\u0000-\u001F]/.test(name) || /[. ]$/.test(name))
736
822
  throw new Error(`Invalid Windows application name: ${JSON.stringify(name)}`);
@@ -752,11 +838,15 @@ async function createMSI(opts) {
752
838
  const tempDir = mkdtempSync(join(tmpdir(), "craft-msi-"));
753
839
  try {
754
840
  const binaryName = windowsExecutableName(opts.name);
841
+ const additionalFiles = windowsCompanionFiles(opts.additionalFiles, binaryName);
755
842
  const sourcePath = join(tempDir, binaryName);
756
843
  const wxsPath = join(tempDir, "installer.wxs");
757
844
  const wixobjPath = join(tempDir, "installer.wixobj");
758
845
  copyFileSync(opts.binaryPath, sourcePath);
759
- writeFileSync(wxsPath, renderWixSource(opts, binaryName));
846
+ writeFileSync(join(tempDir, WINDOWS_NOTIFICATION_ID_FILE), windowsNotificationAppId(opts.name, opts.manufacturer));
847
+ for (const file of additionalFiles)
848
+ copyFileSync(file.path, join(tempDir, file.name));
849
+ writeFileSync(wxsPath, renderWixSource(opts, binaryName, additionalFiles.map((file) => file.name)));
760
850
  await runCommand("candle.exe", candleArguments(opts.architecture, wixobjPath, wxsPath), tempDir);
761
851
  await runCommand("light.exe", ["-nologo", "-sval", "-out", opts.outputPath, wixobjPath], tempDir);
762
852
  if (opts.certificatePath) {
@@ -778,8 +868,13 @@ async function createMSI(opts) {
778
868
  }
779
869
  async function createZIP(opts) {
780
870
  try {
781
- const data = new Uint8Array(readFileSync(opts.binaryPath));
782
- const zip = buildZip([{ name: `${opts.name}.exe`, data }]);
871
+ const binaryName = windowsExecutableName(opts.name);
872
+ const additionalFiles = windowsCompanionFiles(opts.additionalFiles, binaryName);
873
+ const entries = [{ name: binaryName, data: new Uint8Array(readFileSync(opts.binaryPath)) }];
874
+ entries.push({ name: WINDOWS_NOTIFICATION_ID_FILE, data: new TextEncoder().encode(windowsNotificationAppId(opts.name, opts.manufacturer)) });
875
+ for (const file of additionalFiles)
876
+ entries.push({ name: file.name, data: new Uint8Array(readFileSync(file.path)) });
877
+ const zip = buildZip(entries);
783
878
  writeFileSync(opts.outputPath, zip);
784
879
  return { success: true, outputPath: opts.outputPath };
785
880
  } catch (err) {
@@ -809,6 +904,7 @@ async function createDEB(opts) {
809
904
  return;
810
905
  }
811
906
  }
907
+ const desktopContent = linuxDesktopEntry(opts.name, sanitizedName, opts.urlSchemes, opts.launchUrl);
812
908
  const tempDir = mkdtempSync(join(tmpdir(), "craft-deb-"));
813
909
  const debianDir = join(tempDir, "DEBIAN");
814
910
  const binDir = join(tempDir, "usr", "bin");
@@ -829,13 +925,6 @@ Maintainer: ${maintainer}
829
925
  Description: ${description || opts.name}
830
926
  `;
831
927
  writeFileSync(join(debianDir, "control"), controlContent);
832
- const desktopContent = `[Desktop Entry]
833
- Type=Application
834
- Name=${opts.name}
835
- Exec=/usr/bin/${binaryName}
836
- Terminal=false
837
- Categories=Utility;
838
- `;
839
928
  writeFileSync(join(applicationsDir, `${binaryName}.desktop`), desktopContent);
840
929
  const proc = spawn("dpkg-deb", ["--build", tempDir, opts.outputPath]);
841
930
  proc.on("close", (code) => {
package/dist/package.d.ts CHANGED
@@ -4,6 +4,8 @@
4
4
  * Enables users to create installers for their Craft applications
5
5
  * across all platforms (macOS, Windows, Linux)
6
6
  */
7
+ /** Runtime packages required by the default Linux DEB, including notify-send. */
8
+ export declare const DEFAULT_DEB_DEPENDENCIES: readonly ['libgtk-3-0', 'libwebkit2gtk-4.1-37', 'libnotify-bin'];
7
9
  export interface PackageConfig {
8
10
  /** Application name */
9
11
  name: string;
@@ -95,6 +97,12 @@ export interface PackageConfig {
95
97
  msi?: boolean;
96
98
  /** Create ZIP archive */
97
99
  zip?: boolean;
100
+ /** Files installed beside the executable (for example WebView2Loader.dll) */
101
+ additionalFiles?: string[];
102
+ /** Custom URI schemes registered by the MSI installer */
103
+ urlSchemes?: string[];
104
+ /** App page loaded when the MSI shortcut or a registered URI starts Craft. */
105
+ launchUrl?: string;
98
106
  /** Architecture encoded in MSI metadata (defaults to the native host) */
99
107
  architecture?: 'x86' | 'x64' | 'arm64';
100
108
  /** Code signing certificate */
@@ -106,13 +114,17 @@ export interface PackageConfig {
106
114
  linux?: {
107
115
  /** Create DEB package */
108
116
  deb?: boolean;
117
+ /** Custom URI schemes advertised by the DEB desktop entry */
118
+ urlSchemes?: string[];
119
+ /** App page loaded when the DEB desktop entry starts Craft. */
120
+ launchUrl?: string;
109
121
  /** Create RPM package */
110
122
  rpm?: boolean;
111
123
  /** Create AppImage */
112
124
  appImage?: boolean;
113
125
  /** Desktop categories */
114
126
  categories?: string[];
115
- /** Dependencies (Debian) */
127
+ /** Dependencies (Debian); replaces the defaults, including libnotify-bin */
116
128
  debDependencies?: string[];
117
129
  /** Dependencies (RPM) */
118
130
  rpmDependencies?: string[];
@@ -125,6 +137,9 @@ interface MSIOptions {
125
137
  outputPath: string;
126
138
  manufacturer: string;
127
139
  architecture: 'x86' | 'x64' | 'arm64';
140
+ additionalFiles?: string[];
141
+ urlSchemes?: string[];
142
+ launchUrl?: string;
128
143
  certificatePath?: string;
129
144
  certificatePassword?: string;
130
145
  }
@@ -164,6 +179,7 @@ export interface MacOSBundleMetadata {
164
179
  * dicts would only invent distinctions LaunchServices does not care about.
165
180
  */
166
181
  export declare function urlTypesEntry(bundleId: string, schemes: readonly string[]): string | null;
182
+ export declare function linuxDesktopEntry(name: string, binaryName: string, urlSchemes?: readonly string[], launchUrl?: string): string;
167
183
  /**
168
184
  * Render `Contents/Info.plist` for a macOS app bundle.
169
185
  *
@@ -247,8 +263,11 @@ export declare function pkgbuildArguments(opts: {
247
263
  outputPath: string;
248
264
  installerIdentity?: string;
249
265
  }): string[];
266
+ export declare const WINDOWS_NOTIFICATION_ID_FILE = "craft-notification-id.txt";
267
+ /** Shared by the MSI shortcut and the installed executable's toast bridge. */
268
+ export declare function windowsNotificationAppId(name: string, manufacturer: string): string;
250
269
  export declare function windowsArchitecture(architecture: string): 'x86' | 'x64' | 'arm64';
251
- export declare function renderWixSource(opts: Pick<MSIOptions, 'name' | 'version' | 'manufacturer' | 'architecture'>, sourceName: string): string;
270
+ export declare function renderWixSource(opts: Pick<MSIOptions, 'name' | 'version' | 'manufacturer' | 'architecture' | 'urlSchemes' | 'launchUrl'>, sourceName: string, additionalFileNames?: string[]): string;
252
271
  export declare function windowsExecutableName(name: string): string;
253
272
  /**
254
273
  * `candle.exe` arguments.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "craft-native",
3
- "version": "0.0.104",
3
+ "version": "0.0.105",
4
4
  "type": "module",
5
5
  "description": "Build desktop apps with web languages - TypeScript SDK for Craft",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",