craft-native 0.0.76 → 0.0.77

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
@@ -22,9 +22,12 @@ var exports_package = {};
22
22
  __export(exports_package, {
23
23
  windowsExecutableName: () => windowsExecutableName,
24
24
  windowsArchitecture: () => windowsArchitecture,
25
+ urlTypesEntry: () => urlTypesEntry,
25
26
  shouldRetryHdiutil: () => shouldRetryHdiutil,
26
27
  renderWixSource: () => renderWixSource,
27
28
  productbuildArguments: () => productbuildArguments,
29
+ pkgbuildComponentPlist: () => pkgbuildComponentPlist,
30
+ pkgbuildArguments: () => pkgbuildArguments,
28
31
  packageApp: () => packageApp,
29
32
  pack: () => pack,
30
33
  notarytoolArguments: () => notarytoolArguments,
@@ -162,6 +165,7 @@ async function packageMacOS(config, outDir) {
162
165
  category: opts.category,
163
166
  minimumSystemVersion: opts.minimumSystemVersion,
164
167
  menuBarOnly: opts.menuBarOnly,
168
+ urlSchemes: opts.urlSchemes,
165
169
  binaryPath: config.binaryPath,
166
170
  iconPath: config.iconPath,
167
171
  provisioningProfile: opts.provisioningProfile,
@@ -358,6 +362,36 @@ async function packageLinux(config, outDir) {
358
362
  }
359
363
  return results;
360
364
  }
365
+ 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
+ if (unique.length === 0)
378
+ return null;
379
+ const list = unique.map((scheme) => ` <string>${xml(scheme)}</string>`).join(`
380
+ `);
381
+ return ` <key>CFBundleURLTypes</key>
382
+ <array>
383
+ <dict>
384
+ <key>CFBundleURLName</key>
385
+ <string>${xml(bundleId)}</string>
386
+ <key>CFBundleTypeRole</key>
387
+ <string>Viewer</string>
388
+ <key>CFBundleURLSchemes</key>
389
+ <array>
390
+ ${list}
391
+ </array>
392
+ </dict>
393
+ </array>`;
394
+ }
361
395
  function macOSInfoPlist(metadata) {
362
396
  const entries = [
363
397
  ["CFBundleExecutable", metadata.name],
@@ -376,9 +410,12 @@ function macOSInfoPlist(metadata) {
376
410
  entries.push(["LSMinimumSystemVersion", metadata.minimumSystemVersion]);
377
411
  if (metadata.menuBarOnly)
378
412
  entries.push(["LSUIElement", true]);
379
- const body = entries.map(([key, value]) => ` <key>${xml(key)}</key>
413
+ const scalars = entries.map(([key, value]) => ` <key>${xml(key)}</key>
380
414
  ${value === true ? " <true/>" : ` <string>${xml(value)}</string>`}`).join(`
381
415
  `);
416
+ const urlTypes = urlTypesEntry(metadata.bundleId, metadata.urlSchemes || []);
417
+ const body = urlTypes ? `${scalars}
418
+ ${urlTypes}` : scalars;
382
419
  return `<?xml version="1.0" encoding="UTF-8"?>
383
420
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
384
421
  <plist version="1.0">
@@ -528,6 +565,43 @@ async function createDMG(opts) {
528
565
  return { success: false, error: failures.join(`
529
566
  `) };
530
567
  }
568
+ function pkgbuildComponentPlist(rootRelativeBundlePath) {
569
+ return `<?xml version="1.0" encoding="UTF-8"?>
570
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
571
+ <plist version="1.0">
572
+ <array>
573
+ <dict>
574
+ <key>BundleHasStrictIdentifier</key>
575
+ <true/>
576
+ <key>BundleIsRelocatable</key>
577
+ <false/>
578
+ <key>BundleIsVersionChecked</key>
579
+ <false/>
580
+ <key>BundleOverwriteAction</key>
581
+ <string>upgrade</string>
582
+ <key>RootRelativeBundlePath</key>
583
+ <string>${xml(rootRelativeBundlePath)}</string>
584
+ </dict>
585
+ </array>
586
+ </plist>
587
+ `;
588
+ }
589
+ function pkgbuildArguments(opts) {
590
+ return [
591
+ "--root",
592
+ opts.root,
593
+ "--component-plist",
594
+ opts.componentPlistPath,
595
+ "--identifier",
596
+ opts.identifier,
597
+ "--version",
598
+ opts.version,
599
+ "--install-location",
600
+ "/",
601
+ ...opts.installerIdentity ? ["--sign", opts.installerIdentity] : [],
602
+ opts.outputPath
603
+ ];
604
+ }
531
605
  async function createPKG(opts) {
532
606
  if (!/^[a-zA-Z0-9._-]+$/.test(opts.identifier) || !opts.identifier.includes("."))
533
607
  return { success: false, error: `Invalid pkg identifier "${opts.identifier}"; expected reverse-DNS like com.example.app` };
@@ -545,21 +619,22 @@ async function createPKG(opts) {
545
619
  }
546
620
  const tempDir = mkdtempSync(join(tmpdir(), "craft-pkg-"));
547
621
  try {
548
- const appsDir = join(tempDir, "Applications");
549
- mkdirSync(appsDir, { recursive: true });
550
- cpSync(opts.appBundlePath, join(appsDir, basename(opts.appBundlePath)), { recursive: true });
551
- const built = await runTool("pkgbuild", [
552
- "--root",
553
- tempDir,
554
- "--identifier",
555
- opts.identifier,
556
- "--version",
557
- opts.version,
558
- "--install-location",
559
- "/",
560
- ...opts.installerIdentity ? ["--sign", opts.installerIdentity] : [],
561
- opts.outputPath
562
- ]);
622
+ const root = join(tempDir, "root");
623
+ const appsDir = join(root, "Applications");
624
+ mkdirSync(appsDir, { recursive: true, mode: 493 });
625
+ chmodSync(root, 493);
626
+ const bundleName = basename(opts.appBundlePath);
627
+ cpSync(opts.appBundlePath, join(appsDir, bundleName), { recursive: true });
628
+ const componentPlistPath = join(tempDir, "component.plist");
629
+ writeFileSync(componentPlistPath, pkgbuildComponentPlist(`Applications/${bundleName}`));
630
+ const built = await runTool("pkgbuild", pkgbuildArguments({
631
+ root,
632
+ componentPlistPath,
633
+ identifier: opts.identifier,
634
+ version: opts.version,
635
+ outputPath: opts.outputPath,
636
+ installerIdentity: opts.installerIdentity
637
+ }));
563
638
  return built.success ? { success: true, outputPath: opts.outputPath } : { success: false, error: built.error };
564
639
  } finally {
565
640
  rmSync(tempDir, { recursive: true, force: true });
@@ -956,7 +1031,7 @@ async function pack(options) {
956
1031
  platforms: [detectPlatform()]
957
1032
  });
958
1033
  }
959
- var CRC32_TABLE, MEBIBYTE, HDIUTIL_MAX_ATTEMPTS = 3, runHdiutil = (args) => runTool("hdiutil", args);
1034
+ var CRC32_TABLE, URL_SCHEME, MEBIBYTE, HDIUTIL_MAX_ATTEMPTS = 3, runHdiutil = (args) => runTool("hdiutil", args);
960
1035
  var init_package = __esm(() => {
961
1036
  CRC32_TABLE = (() => {
962
1037
  const table = new Uint32Array(256);
@@ -968,6 +1043,7 @@ var init_package = __esm(() => {
968
1043
  }
969
1044
  return table;
970
1045
  })();
1046
+ URL_SCHEME = /^[a-z][a-z0-9+.-]*$/i;
971
1047
  MEBIBYTE = 1024 * 1024;
972
1048
  });
973
1049
 
@@ -4115,7 +4191,7 @@ function craftBinaryNotFoundMessage(triedPath) {
4115
4191
  `);
4116
4192
  }
4117
4193
  // package.json
4118
- var version = "0.0.76";
4194
+ var version = "0.0.77";
4119
4195
 
4120
4196
  // bin/cli.ts
4121
4197
  var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
package/dist/index.cjs CHANGED
@@ -425,6 +425,7 @@ async function packageMacOS(config, outDir) {
425
425
  category: opts.category,
426
426
  minimumSystemVersion: opts.minimumSystemVersion,
427
427
  menuBarOnly: opts.menuBarOnly,
428
+ urlSchemes: opts.urlSchemes,
428
429
  binaryPath: config.binaryPath,
429
430
  iconPath: config.iconPath,
430
431
  provisioningProfile: opts.provisioningProfile,
@@ -621,6 +622,37 @@ async function packageLinux(config, outDir) {
621
622
  }
622
623
  return results;
623
624
  }
625
+ var URL_SCHEME = /^[a-z][a-z0-9+.-]*$/i;
626
+ function urlTypesEntry(bundleId, schemes) {
627
+ const seen = new Set;
628
+ const unique = [];
629
+ for (const scheme of schemes) {
630
+ if (!URL_SCHEME.test(scheme))
631
+ throw new Error(`Invalid URL scheme ${JSON.stringify(scheme)}: must start with a letter and contain only letters, digits, "+", "-" or "."`);
632
+ const key = scheme.toLowerCase();
633
+ if (seen.has(key))
634
+ continue;
635
+ seen.add(key);
636
+ unique.push(scheme);
637
+ }
638
+ if (unique.length === 0)
639
+ return null;
640
+ const list = unique.map((scheme) => ` <string>${xml(scheme)}</string>`).join(`
641
+ `);
642
+ return ` <key>CFBundleURLTypes</key>
643
+ <array>
644
+ <dict>
645
+ <key>CFBundleURLName</key>
646
+ <string>${xml(bundleId)}</string>
647
+ <key>CFBundleTypeRole</key>
648
+ <string>Viewer</string>
649
+ <key>CFBundleURLSchemes</key>
650
+ <array>
651
+ ${list}
652
+ </array>
653
+ </dict>
654
+ </array>`;
655
+ }
624
656
  function macOSInfoPlist(metadata) {
625
657
  const entries = [
626
658
  ["CFBundleExecutable", metadata.name],
@@ -639,9 +671,12 @@ function macOSInfoPlist(metadata) {
639
671
  entries.push(["LSMinimumSystemVersion", metadata.minimumSystemVersion]);
640
672
  if (metadata.menuBarOnly)
641
673
  entries.push(["LSUIElement", true]);
642
- const body = entries.map(([key, value]) => ` <key>${xml(key)}</key>
674
+ const scalars = entries.map(([key, value]) => ` <key>${xml(key)}</key>
643
675
  ${value === true ? " <true/>" : ` <string>${xml(value)}</string>`}`).join(`
644
676
  `);
677
+ const urlTypes = urlTypesEntry(metadata.bundleId, metadata.urlSchemes || []);
678
+ const body = urlTypes ? `${scalars}
679
+ ${urlTypes}` : scalars;
645
680
  return `<?xml version="1.0" encoding="UTF-8"?>
646
681
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
647
682
  <plist version="1.0">
@@ -794,6 +829,43 @@ async function createDMG(opts) {
794
829
  return { success: false, error: failures.join(`
795
830
  `) };
796
831
  }
832
+ function pkgbuildComponentPlist(rootRelativeBundlePath) {
833
+ return `<?xml version="1.0" encoding="UTF-8"?>
834
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
835
+ <plist version="1.0">
836
+ <array>
837
+ <dict>
838
+ <key>BundleHasStrictIdentifier</key>
839
+ <true/>
840
+ <key>BundleIsRelocatable</key>
841
+ <false/>
842
+ <key>BundleIsVersionChecked</key>
843
+ <false/>
844
+ <key>BundleOverwriteAction</key>
845
+ <string>upgrade</string>
846
+ <key>RootRelativeBundlePath</key>
847
+ <string>${xml(rootRelativeBundlePath)}</string>
848
+ </dict>
849
+ </array>
850
+ </plist>
851
+ `;
852
+ }
853
+ function pkgbuildArguments(opts) {
854
+ return [
855
+ "--root",
856
+ opts.root,
857
+ "--component-plist",
858
+ opts.componentPlistPath,
859
+ "--identifier",
860
+ opts.identifier,
861
+ "--version",
862
+ opts.version,
863
+ "--install-location",
864
+ "/",
865
+ ...opts.installerIdentity ? ["--sign", opts.installerIdentity] : [],
866
+ opts.outputPath
867
+ ];
868
+ }
797
869
  async function createPKG(opts) {
798
870
  if (!/^[a-zA-Z0-9._-]+$/.test(opts.identifier) || !opts.identifier.includes("."))
799
871
  return { success: false, error: `Invalid pkg identifier "${opts.identifier}"; expected reverse-DNS like com.example.app` };
@@ -811,21 +883,22 @@ async function createPKG(opts) {
811
883
  }
812
884
  const tempDir = import_fs.mkdtempSync(import_path.join(import_os.tmpdir(), "craft-pkg-"));
813
885
  try {
814
- const appsDir = import_path.join(tempDir, "Applications");
815
- import_fs.mkdirSync(appsDir, { recursive: true });
816
- import_fs.cpSync(opts.appBundlePath, import_path.join(appsDir, import_path.basename(opts.appBundlePath)), { recursive: true });
817
- const built = await runTool("pkgbuild", [
818
- "--root",
819
- tempDir,
820
- "--identifier",
821
- opts.identifier,
822
- "--version",
823
- opts.version,
824
- "--install-location",
825
- "/",
826
- ...opts.installerIdentity ? ["--sign", opts.installerIdentity] : [],
827
- opts.outputPath
828
- ]);
886
+ const root = import_path.join(tempDir, "root");
887
+ const appsDir = import_path.join(root, "Applications");
888
+ import_fs.mkdirSync(appsDir, { recursive: true, mode: 493 });
889
+ import_fs.chmodSync(root, 493);
890
+ const bundleName = import_path.basename(opts.appBundlePath);
891
+ import_fs.cpSync(opts.appBundlePath, import_path.join(appsDir, bundleName), { recursive: true });
892
+ const componentPlistPath = import_path.join(tempDir, "component.plist");
893
+ import_fs.writeFileSync(componentPlistPath, pkgbuildComponentPlist(`Applications/${bundleName}`));
894
+ const built = await runTool("pkgbuild", pkgbuildArguments({
895
+ root,
896
+ componentPlistPath,
897
+ identifier: opts.identifier,
898
+ version: opts.version,
899
+ outputPath: opts.outputPath,
900
+ installerIdentity: opts.installerIdentity
901
+ }));
829
902
  return built.success ? { success: true, outputPath: opts.outputPath } : { success: false, error: built.error };
830
903
  } finally {
831
904
  import_fs.rmSync(tempDir, { recursive: true, force: true });
@@ -10624,7 +10697,9 @@ class CraftApp {
10624
10697
  }
10625
10698
  buildArgs() {
10626
10699
  const args = [];
10627
- const { window: window3, html, url } = this.config;
10700
+ const { window: window3, html, url, appName } = this.config;
10701
+ if (appName)
10702
+ args.push("--app-name", appName);
10628
10703
  if (!window3?.menubarOnly) {
10629
10704
  if (url) {
10630
10705
  args.push("--url", url);
@@ -10658,6 +10733,8 @@ class CraftApp {
10658
10733
  args.push("--hot-reload");
10659
10734
  if (window3?.devTools === false)
10660
10735
  args.push("--no-devtools");
10736
+ else if (window3?.devTools === true)
10737
+ args.push("--dev-tools");
10661
10738
  if (window3?.systemTray)
10662
10739
  args.push("--system-tray");
10663
10740
  if (window3?.hideDockIcon)
@@ -10666,6 +10743,8 @@ class CraftApp {
10666
10743
  args.push("--menubar-only");
10667
10744
  if (window3?.titlebarHidden)
10668
10745
  args.push("--titlebar-hidden");
10746
+ if (window3?.headless)
10747
+ args.push("--headless");
10669
10748
  if (window3?.webSidebarMaterial) {
10670
10749
  args.push("--web-sidebar-material");
10671
10750
  if (window3?.webSidebarWidth)
@@ -10675,6 +10754,8 @@ class CraftApp {
10675
10754
  }
10676
10755
  if (window3?.icon)
10677
10756
  args.push("--icon", window3.icon);
10757
+ if (window3?.frameAutosave)
10758
+ args.push("--frame-autosave", window3.frameAutosave);
10678
10759
  if (window3?.nativeSidebar) {
10679
10760
  args.push("--native-sidebar");
10680
10761
  if (window3?.sidebarWidth)
package/dist/index.d.cts CHANGED
@@ -59,4 +59,4 @@ export declare function show(html: string, options?: WindowOptions): Promise<voi
59
59
  * Quick helper to load a URL
60
60
  */
61
61
  export declare function loadURL(url: string, options?: WindowOptions): Promise<void>;
62
- export type { WindowOptions, CraftSidebarAPI, AppConfig, CraftTrayAPI, CraftWindowAPI, CraftAppAPI, CraftBridgeAPI, Permission, HapticType, CameraOptions, PhotoPickerOptions, CraftMobileAPI, CraftFileSystemAPI, CraftDatabaseAPI, CraftHttpAPI, CraftCryptoAPI, CraftEventType, CraftEventMap, CraftEventHandler, CraftEventEmitter, IOSConfig, AndroidConfig, MacOSConfig, WindowsConfig, LinuxConfig, CraftAppConfig, } from './types.js';
62
+ export type { WindowOptions, CraftSidebarAPI, AppConfig, CraftTrayAPI, CraftWindowAPI, CraftAppAPI, CraftBridgeAPI, Permission, HapticType, CameraOptions, PhotoPickerOptions, CraftMobileAPI, CraftFileSystemAPI, CraftDatabaseAPI, CraftHttpAPI, CraftCryptoAPI, CraftCapabilities, CapabilityNamespace, CapabilityNamespaceStatus, CapabilityAction, CraftPreferencesAPI, CraftPreferencesInfo, CraftSettingsAPI, PreferenceValue, CraftEventType, CraftEventMap, CraftEventHandler, CraftEventEmitter, IOSConfig, AndroidConfig, MacOSConfig, WindowsConfig, LinuxConfig, CraftAppConfig, } from './types.js';
package/dist/index.d.ts CHANGED
@@ -59,4 +59,4 @@ export declare function show(html: string, options?: WindowOptions): Promise<voi
59
59
  * Quick helper to load a URL
60
60
  */
61
61
  export declare function loadURL(url: string, options?: WindowOptions): Promise<void>;
62
- export type { WindowOptions, CraftSidebarAPI, AppConfig, CraftTrayAPI, CraftWindowAPI, CraftAppAPI, CraftBridgeAPI, Permission, HapticType, CameraOptions, PhotoPickerOptions, CraftMobileAPI, CraftFileSystemAPI, CraftDatabaseAPI, CraftHttpAPI, CraftCryptoAPI, CraftEventType, CraftEventMap, CraftEventHandler, CraftEventEmitter, IOSConfig, AndroidConfig, MacOSConfig, WindowsConfig, LinuxConfig, CraftAppConfig, } from './types.js';
62
+ export type { WindowOptions, CraftSidebarAPI, AppConfig, CraftTrayAPI, CraftWindowAPI, CraftAppAPI, CraftBridgeAPI, Permission, HapticType, CameraOptions, PhotoPickerOptions, CraftMobileAPI, CraftFileSystemAPI, CraftDatabaseAPI, CraftHttpAPI, CraftCryptoAPI, CraftCapabilities, CapabilityNamespace, CapabilityNamespaceStatus, CapabilityAction, CraftPreferencesAPI, CraftPreferencesInfo, CraftSettingsAPI, PreferenceValue, CraftEventType, CraftEventMap, CraftEventHandler, CraftEventEmitter, IOSConfig, AndroidConfig, MacOSConfig, WindowsConfig, LinuxConfig, CraftAppConfig, } from './types.js';
package/dist/index.js CHANGED
@@ -195,6 +195,7 @@ async function packageMacOS(config, outDir) {
195
195
  category: opts.category,
196
196
  minimumSystemVersion: opts.minimumSystemVersion,
197
197
  menuBarOnly: opts.menuBarOnly,
198
+ urlSchemes: opts.urlSchemes,
198
199
  binaryPath: config.binaryPath,
199
200
  iconPath: config.iconPath,
200
201
  provisioningProfile: opts.provisioningProfile,
@@ -391,6 +392,37 @@ async function packageLinux(config, outDir) {
391
392
  }
392
393
  return results;
393
394
  }
395
+ var URL_SCHEME = /^[a-z][a-z0-9+.-]*$/i;
396
+ function urlTypesEntry(bundleId, 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
+ if (unique.length === 0)
409
+ return null;
410
+ const list = unique.map((scheme) => ` <string>${xml(scheme)}</string>`).join(`
411
+ `);
412
+ return ` <key>CFBundleURLTypes</key>
413
+ <array>
414
+ <dict>
415
+ <key>CFBundleURLName</key>
416
+ <string>${xml(bundleId)}</string>
417
+ <key>CFBundleTypeRole</key>
418
+ <string>Viewer</string>
419
+ <key>CFBundleURLSchemes</key>
420
+ <array>
421
+ ${list}
422
+ </array>
423
+ </dict>
424
+ </array>`;
425
+ }
394
426
  function macOSInfoPlist(metadata) {
395
427
  const entries = [
396
428
  ["CFBundleExecutable", metadata.name],
@@ -409,9 +441,12 @@ function macOSInfoPlist(metadata) {
409
441
  entries.push(["LSMinimumSystemVersion", metadata.minimumSystemVersion]);
410
442
  if (metadata.menuBarOnly)
411
443
  entries.push(["LSUIElement", true]);
412
- const body = entries.map(([key, value]) => ` <key>${xml(key)}</key>
444
+ const scalars = entries.map(([key, value]) => ` <key>${xml(key)}</key>
413
445
  ${value === true ? " <true/>" : ` <string>${xml(value)}</string>`}`).join(`
414
446
  `);
447
+ const urlTypes = urlTypesEntry(metadata.bundleId, metadata.urlSchemes || []);
448
+ const body = urlTypes ? `${scalars}
449
+ ${urlTypes}` : scalars;
415
450
  return `<?xml version="1.0" encoding="UTF-8"?>
416
451
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
417
452
  <plist version="1.0">
@@ -564,6 +599,43 @@ async function createDMG(opts) {
564
599
  return { success: false, error: failures.join(`
565
600
  `) };
566
601
  }
602
+ function pkgbuildComponentPlist(rootRelativeBundlePath) {
603
+ return `<?xml version="1.0" encoding="UTF-8"?>
604
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
605
+ <plist version="1.0">
606
+ <array>
607
+ <dict>
608
+ <key>BundleHasStrictIdentifier</key>
609
+ <true/>
610
+ <key>BundleIsRelocatable</key>
611
+ <false/>
612
+ <key>BundleIsVersionChecked</key>
613
+ <false/>
614
+ <key>BundleOverwriteAction</key>
615
+ <string>upgrade</string>
616
+ <key>RootRelativeBundlePath</key>
617
+ <string>${xml(rootRelativeBundlePath)}</string>
618
+ </dict>
619
+ </array>
620
+ </plist>
621
+ `;
622
+ }
623
+ function pkgbuildArguments(opts) {
624
+ return [
625
+ "--root",
626
+ opts.root,
627
+ "--component-plist",
628
+ opts.componentPlistPath,
629
+ "--identifier",
630
+ opts.identifier,
631
+ "--version",
632
+ opts.version,
633
+ "--install-location",
634
+ "/",
635
+ ...opts.installerIdentity ? ["--sign", opts.installerIdentity] : [],
636
+ opts.outputPath
637
+ ];
638
+ }
567
639
  async function createPKG(opts) {
568
640
  if (!/^[a-zA-Z0-9._-]+$/.test(opts.identifier) || !opts.identifier.includes("."))
569
641
  return { success: false, error: `Invalid pkg identifier "${opts.identifier}"; expected reverse-DNS like com.example.app` };
@@ -581,21 +653,22 @@ async function createPKG(opts) {
581
653
  }
582
654
  const tempDir = mkdtempSync(join(tmpdir(), "craft-pkg-"));
583
655
  try {
584
- const appsDir = join(tempDir, "Applications");
585
- mkdirSync(appsDir, { recursive: true });
586
- cpSync(opts.appBundlePath, join(appsDir, basename(opts.appBundlePath)), { recursive: true });
587
- const built = await runTool("pkgbuild", [
588
- "--root",
589
- tempDir,
590
- "--identifier",
591
- opts.identifier,
592
- "--version",
593
- opts.version,
594
- "--install-location",
595
- "/",
596
- ...opts.installerIdentity ? ["--sign", opts.installerIdentity] : [],
597
- opts.outputPath
598
- ]);
656
+ const root = join(tempDir, "root");
657
+ const appsDir = join(root, "Applications");
658
+ mkdirSync(appsDir, { recursive: true, mode: 493 });
659
+ chmodSync(root, 493);
660
+ const bundleName = basename(opts.appBundlePath);
661
+ cpSync(opts.appBundlePath, join(appsDir, bundleName), { recursive: true });
662
+ const componentPlistPath = join(tempDir, "component.plist");
663
+ writeFileSync(componentPlistPath, pkgbuildComponentPlist(`Applications/${bundleName}`));
664
+ const built = await runTool("pkgbuild", pkgbuildArguments({
665
+ root,
666
+ componentPlistPath,
667
+ identifier: opts.identifier,
668
+ version: opts.version,
669
+ outputPath: opts.outputPath,
670
+ installerIdentity: opts.installerIdentity
671
+ }));
599
672
  return built.success ? { success: true, outputPath: opts.outputPath } : { success: false, error: built.error };
600
673
  } finally {
601
674
  rmSync(tempDir, { recursive: true, force: true });
@@ -10394,7 +10467,9 @@ class CraftApp {
10394
10467
  }
10395
10468
  buildArgs() {
10396
10469
  const args = [];
10397
- const { window: window3, html, url } = this.config;
10470
+ const { window: window3, html, url, appName } = this.config;
10471
+ if (appName)
10472
+ args.push("--app-name", appName);
10398
10473
  if (!window3?.menubarOnly) {
10399
10474
  if (url) {
10400
10475
  args.push("--url", url);
@@ -10428,6 +10503,8 @@ class CraftApp {
10428
10503
  args.push("--hot-reload");
10429
10504
  if (window3?.devTools === false)
10430
10505
  args.push("--no-devtools");
10506
+ else if (window3?.devTools === true)
10507
+ args.push("--dev-tools");
10431
10508
  if (window3?.systemTray)
10432
10509
  args.push("--system-tray");
10433
10510
  if (window3?.hideDockIcon)
@@ -10436,6 +10513,8 @@ class CraftApp {
10436
10513
  args.push("--menubar-only");
10437
10514
  if (window3?.titlebarHidden)
10438
10515
  args.push("--titlebar-hidden");
10516
+ if (window3?.headless)
10517
+ args.push("--headless");
10439
10518
  if (window3?.webSidebarMaterial) {
10440
10519
  args.push("--web-sidebar-material");
10441
10520
  if (window3?.webSidebarWidth)
@@ -10445,6 +10524,8 @@ class CraftApp {
10445
10524
  }
10446
10525
  if (window3?.icon)
10447
10526
  args.push("--icon", window3.icon);
10527
+ if (window3?.frameAutosave)
10528
+ args.push("--frame-autosave", window3.frameAutosave);
10448
10529
  if (window3?.nativeSidebar) {
10449
10530
  args.push("--native-sidebar");
10450
10531
  if (window3?.sidebarWidth)
package/dist/package.d.ts CHANGED
@@ -74,6 +74,20 @@ export interface PackageConfig {
74
74
  * the helper lands next to `process.execPath`.
75
75
  */
76
76
  additionalExecutables?: string[];
77
+ /**
78
+ * URL schemes the app registers as a handler for — `['myapp']` makes
79
+ * `myapp://…` open it.
80
+ *
81
+ * Emitted as `CFBundleURLTypes`. Without this key macOS never dispatches
82
+ * a URL to the app at all, so Craft's receive path (the `kAEGetURL`
83
+ * AppleEvent handler behind `craft.deepLink`) can be fully working and
84
+ * still never hear anything.
85
+ *
86
+ * Schemes are matched case-insensitively by LaunchServices and are
87
+ * global to the machine: pick something specific to the app, not `app`
88
+ * or `open`.
89
+ */
90
+ urlSchemes?: string[];
77
91
  };
78
92
  /** Windows-specific options */
79
93
  windows?: {
@@ -138,7 +152,18 @@ export interface MacOSBundleMetadata {
138
152
  minimumSystemVersion?: string;
139
153
  /** Sets `LSUIElement` — no Dock icon, no app switcher entry */
140
154
  menuBarOnly?: boolean;
155
+ /** URL schemes the app handles, emitted as `CFBundleURLTypes` */
156
+ urlSchemes?: string[];
141
157
  }
158
+ /**
159
+ * `CFBundleURLTypes` for the schemes an app handles, or `null` if it handles
160
+ * none.
161
+ *
162
+ * All the schemes go in one URL type. They are alternatives for reaching the
163
+ * same app, not different kinds of document, so splitting them across several
164
+ * dicts would only invent distinctions LaunchServices does not care about.
165
+ */
166
+ export declare function urlTypesEntry(bundleId: string, schemes: readonly string[]): string | null;
142
167
  /**
143
168
  * Render `Contents/Info.plist` for a macOS app bundle.
144
169
  *
@@ -178,6 +203,50 @@ export declare function dmgCreateArguments(opts: {
178
203
  volumeName: string;
179
204
  }, contentBytes: number): string[];
180
205
  export declare function shouldRetryHdiutil(error: string, attempt: number, maxAttempts?: number): boolean;
206
+ /**
207
+ * Helper: Create PKG from app bundle
208
+ *
209
+ * Two flavours share this entry point:
210
+ * - a plain `pkgbuild` installer for direct distribution, and
211
+ * - a signed `productbuild` submission package for the Mac App Store, which is
212
+ * the only form App Store Connect accepts.
213
+ */
214
+ /**
215
+ * The component property list `pkgbuild` is given for the app bundle.
216
+ *
217
+ * Written out rather than left to `pkgbuild`'s inference, because its default
218
+ * for a bundle is `BundleIsRelocatable = true`, which puts this in the package:
219
+ *
220
+ * <relocate><bundle id="dev.example.app"/></relocate>
221
+ *
222
+ * That directive tells `installer` the payload path is only a suggestion: it
223
+ * looks the bundle identifier up on the target volume and, if the system has a
224
+ * copy of that bundle registered anywhere, writes the payload over *that* copy
225
+ * instead — and exits 0. The user sees "The install was successful" and finds
226
+ * nothing at /Applications.
227
+ *
228
+ * It is intermittent by nature, since it turns on whether the system has
229
+ * indexed some other copy yet, which is exactly how it behaved: the native
230
+ * lifecycle workflow's macOS legs failed only on slow runs, on both
231
+ * architectures, with `installer` reporting success and the app absent.
232
+ *
233
+ * `BundleIsVersionChecked` is off for a related reason — with it on, installing
234
+ * an *older* version over a newer one is skipped, which silently turns a
235
+ * rollback into a no-op.
236
+ *
237
+ * The `pkg-info` attribute `relocatable="false"` that appears either way is not
238
+ * this setting and does not govern it; only the `<relocate>` element does.
239
+ */
240
+ export declare function pkgbuildComponentPlist(rootRelativeBundlePath: string): string;
241
+ /** Build the `pkgbuild` argument list for a non-App-Store package. */
242
+ export declare function pkgbuildArguments(opts: {
243
+ root: string;
244
+ componentPlistPath: string;
245
+ identifier: string;
246
+ version: string;
247
+ outputPath: string;
248
+ installerIdentity?: string;
249
+ }): string[];
181
250
  export declare function windowsArchitecture(architecture: string): 'x86' | 'x64' | 'arm64';
182
251
  export declare function renderWixSource(opts: Pick<MSIOptions, 'name' | 'version' | 'manufacturer' | 'architecture'>, sourceName: string): string;
183
252
  export declare function windowsExecutableName(name: string): string;
package/dist/types.d.ts CHANGED
@@ -55,6 +55,39 @@ export interface WindowOptions {
55
55
  * Window title
56
56
  */
57
57
  title?: string;
58
+ /**
59
+ * Build the window without ever putting it on screen (macOS).
60
+ *
61
+ * The page loads and runs JavaScript exactly as it would visibly, and it
62
+ * remains capturable — a snapshot taken after script has mutated the DOM
63
+ * reflects the mutation.
64
+ *
65
+ * **It does not animate.** An unshown window does not drive the compositor,
66
+ * so `requestAnimationFrame` stops after one frame and page timers fall to
67
+ * roughly 1Hz. Anything that advances itself — CSS or canvas animation, a
68
+ * charting library redrawing on rAF, "wait until the spinner stops" — sees
69
+ * the first frame forever. Drive the page with explicit calls and take
70
+ * readiness from load completion, not from a polling loop inside the page.
71
+ *
72
+ * Contradicts `menubarOnly`, which has no window to hide; passing both is an
73
+ * error rather than a silent no-op.
74
+ */
75
+ headless?: boolean;
76
+ /**
77
+ * Remember this window's size and position across launches, under this name
78
+ * (macOS).
79
+ *
80
+ * `width`/`height`/`x`/`y` become **first-launch defaults**: they are what
81
+ * the window opens at until there is a saved frame to restore, and the saved
82
+ * frame wins from then on. Without this the window forgets its geometry
83
+ * every launch, and an app that wants the standard behaviour has to wire
84
+ * `onResize`/`onMove` to its own storage and reimplement what AppKit does in
85
+ * one call.
86
+ *
87
+ * The name is the key AppKit stores under, so it must be stable across
88
+ * launches and distinct per window.
89
+ */
90
+ frameAutosave?: string;
58
91
  /**
59
92
  * Window width in pixels
60
93
  * @default 800
@@ -454,6 +487,19 @@ export interface AppConfig {
454
487
  * Window options
455
488
  */
456
489
  window?: WindowOptions;
490
+ /**
491
+ * The name macOS shows for the app: the App menu title, and the
492
+ * "About X" / "Hide X" / "Quit X" items.
493
+ *
494
+ * Without it those read the executable's name, so every app launched
495
+ * through the shared `craft` binary calls itself "craft" in the menu bar.
496
+ * A packaged `.app` gets this from `CFBundleName` instead; this is what
497
+ * gives a dev-mode app its identity back without a packaging step.
498
+ *
499
+ * Not the name `ps` and Activity Monitor show — that comes from the
500
+ * executable and cannot be changed from inside the process.
501
+ */
502
+ appName?: string;
457
503
  /**
458
504
  * Path to Craft binary (auto-detected if not provided)
459
505
  */
@@ -923,6 +969,254 @@ export interface CraftBridgeAPI {
923
969
  * Screen-sharing and screen-recording detection (macOS)
924
970
  */
925
971
  screenSharing?: CraftScreenSharingAPI;
972
+ /**
973
+ * System-wide hotkeys (macOS).
974
+ *
975
+ * Absent in effect on Linux and Windows: the calls exist, and every
976
+ * registration is refused, because Craft has no implementation there and a
977
+ * shortcut that can never fire is worse than one that was never accepted.
978
+ */
979
+ shortcuts?: CraftGlobalShortcutsAPI;
980
+ /**
981
+ * A small scalar preference store (macOS: CFPreferences).
982
+ */
983
+ prefs?: CraftPreferencesAPI;
984
+ /**
985
+ * The Cmd+, convention: where the App menu's Settings… item arrives.
986
+ */
987
+ settings?: CraftSettingsAPI;
988
+ /**
989
+ * What the native side actually serves.
990
+ *
991
+ * Always present: it is how you find out whether anything else here is.
992
+ */
993
+ capabilities?: () => Promise<CraftCapabilities>;
994
+ /** The last capabilities answer, or null if nothing has asked yet. */
995
+ capabilitiesSync?: () => CraftCapabilities | null;
996
+ /**
997
+ * Whether one surface is known to work — `craft.supports('tray.destroy')`.
998
+ *
999
+ * Fails **open**: returns true before capabilities have been fetched, and
1000
+ * true for a namespace craft has not audited. A feature-detection mechanism
1001
+ * that breaks working code when it cannot see itself would be worse than the
1002
+ * gaps it describes.
1003
+ */
1004
+ supports?: (path: string) => boolean;
1005
+ }
1006
+ /**
1007
+ * How much craft is willing to claim about a namespace.
1008
+ *
1009
+ * - `declared` — audited: the action list below is complete and enforced by a
1010
+ * conformance test against the dispatch chain.
1011
+ * - `undeclared` — routed, but not audited. Craft claims nothing either way;
1012
+ * treat it as "try it and handle failure", not as "missing".
1013
+ * - `unavailable` — reachable and known not to work. `reason` says why.
1014
+ * - `unrouted` — implemented natively but absent from the dispatcher, so no
1015
+ * message can reach it.
1016
+ */
1017
+ export type CapabilityNamespaceStatus = 'declared' | 'undeclared' | 'unavailable' | 'unrouted';
1018
+ export interface CapabilityAction {
1019
+ status: 'live' | 'unavailable';
1020
+ /** Whether native sends a reply the caller is waiting on. */
1021
+ reply: 'none' | 'result';
1022
+ /** Present when the action is unavailable. */
1023
+ reason?: string;
1024
+ }
1025
+ export interface CapabilityNamespace {
1026
+ status: CapabilityNamespaceStatus;
1027
+ reason?: string;
1028
+ /** Present only for `declared` namespaces. */
1029
+ actions?: Record<string, CapabilityAction>;
1030
+ }
1031
+ /**
1032
+ * What the native binary behind this page actually serves.
1033
+ *
1034
+ * The injected bridge script is one blob, the same in every build, so the
1035
+ * presence of `window.craft.x` has never been evidence that anything is behind
1036
+ * it. This is the evidence.
1037
+ */
1038
+ export interface CraftCapabilities {
1039
+ namespaces: Record<string, CapabilityNamespace>;
1040
+ /**
1041
+ * Every `craft:*` event channel, and whether anything native emits on it.
1042
+ *
1043
+ * A `false` here means subscribing would work and never fire — which cannot
1044
+ * be derived from the action tables, so it is tracked separately by the
1045
+ * emitters themselves.
1046
+ */
1047
+ channels: Record<string, boolean>;
1048
+ }
1049
+ /**
1050
+ * The only value types `craft.prefs` stores.
1051
+ *
1052
+ * Anything else is refused in the page, before it can reach native. That is
1053
+ * deliberate rather than a limitation dodged: the preferences API raises an
1054
+ * Objective-C exception for a value that is not a property-list type, and Zig
1055
+ * cannot catch one — so refusing containers here is what makes that crash
1056
+ * unreachable. Serialise structure yourself:
1057
+ * `prefs.set(k, JSON.stringify(v))`.
1058
+ */
1059
+ export type PreferenceValue = string | number | boolean;
1060
+ export interface CraftPreferencesInfo {
1061
+ /**
1062
+ * The preferences domain in use — the bundle identifier inside a packaged
1063
+ * `.app`, the executable's name otherwise.
1064
+ */
1065
+ domain: string;
1066
+ /** The key prefix craft namespaces its own preferences under. */
1067
+ prefix: string;
1068
+ /** How many keys the app currently has stored. */
1069
+ count: number;
1070
+ /** A copy-pasteable command that prints the domain. */
1071
+ readCommand: string;
1072
+ }
1073
+ /**
1074
+ * A small preference store over the platform's own mechanism, so `defaults
1075
+ * read` and a native settings pane both see what the app wrote.
1076
+ *
1077
+ * macOS only. Values are stored as native property-list types under a reserved
1078
+ * key prefix, so clearing craft's preferences cannot disturb the AppKit and
1079
+ * WebKit keys that share the same domain.
1080
+ */
1081
+ export interface CraftPreferencesAPI {
1082
+ /**
1083
+ * Read a preference.
1084
+ *
1085
+ * `fallback` is returned when the key is absent — and also when what is
1086
+ * stored is of a different type from the fallback, which is how a value left
1087
+ * behind by an older build of the app degrades to the default rather than to
1088
+ * a surprise. Call `get(key)` with no fallback to read whatever is stored.
1089
+ *
1090
+ * Rejects with `code: 'PREFS_FOREIGN_VALUE'` if something outside craft
1091
+ * wrote a value craft cannot represent, rather than coercing it away.
1092
+ */
1093
+ get: <T extends PreferenceValue>(key: string, fallback?: T) => Promise<T | undefined>;
1094
+ /**
1095
+ * Write a preference. Resolves once the value is on disk, not merely once
1096
+ * the message was posted.
1097
+ *
1098
+ * Rejects with `code: 'PREFS_UNSUPPORTED_VALUE'` for anything that is not a
1099
+ * string, number or boolean; `'PREFS_NON_FINITE'` for NaN or Infinity;
1100
+ * `'PREFS_VALUE_TOO_LARGE'` past 8 KiB; `'PREFS_BAD_KEY'` for a key outside
1101
+ * `/^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/`.
1102
+ */
1103
+ set: (key: string, value: PreferenceValue) => Promise<void>;
1104
+ /** Remove a preference. Resolves to whether it was there to begin with. */
1105
+ delete: (key: string) => Promise<boolean>;
1106
+ /**
1107
+ * Remove every preference this app has set, and nothing else. Resolves to
1108
+ * how many were removed.
1109
+ */
1110
+ clear: () => Promise<number>;
1111
+ /** Every key this app has set, sorted. */
1112
+ keys: () => Promise<string[]>;
1113
+ /**
1114
+ * Which domain the preferences are actually landing in.
1115
+ *
1116
+ * Worth having: an unbundled dev-mode binary writes to a domain named after
1117
+ * the executable while a packaged `.app` writes to its bundle identifier, so
1118
+ * preferences set during development can appear to vanish once the app is
1119
+ * packaged.
1120
+ */
1121
+ info: () => Promise<CraftPreferencesInfo>;
1122
+ }
1123
+ /**
1124
+ * The Cmd+, convention.
1125
+ *
1126
+ * craft's default App menu ships a `Settings…` item, and this is where its
1127
+ * click arrives. An app that replaces the whole menu bar with
1128
+ * `craft.menu.set()` loses the default item, and can declare
1129
+ * `{ id: 'settings', role: 'settings', label: 'Settings…', shortcut: 'cmd+,' }`
1130
+ * to get it back — the same handler answers either way.
1131
+ */
1132
+ export interface CraftSettingsAPI {
1133
+ /** Subscribe to Settings being opened. Returns an unsubscribe function. */
1134
+ onOpen: (handler: (event: {
1135
+ source: string;
1136
+ }) => void) => () => void;
1137
+ /**
1138
+ * Open settings from the app's own UI — a gear button, say — so it lands in
1139
+ * the same handler as Cmd+, rather than needing a second code path. Purely
1140
+ * page-local; nothing crosses the bridge.
1141
+ */
1142
+ open: (source?: string) => Promise<void>;
1143
+ }
1144
+ /** A global hotkey, as `craft.shortcuts.list()` reports it. */
1145
+ export interface GlobalShortcut {
1146
+ /** The id the app registered it under. */
1147
+ id: string;
1148
+ /**
1149
+ * Craft's canonical spelling of the combination — `Cmd+Delete`, whatever
1150
+ * the app originally wrote — so it can be passed straight back to
1151
+ * `register()`.
1152
+ */
1153
+ accelerator: string;
1154
+ /** The key alone, canonically named. */
1155
+ key: string;
1156
+ /** False while `disable()` has the key released back to the system. */
1157
+ enabled: boolean;
1158
+ }
1159
+ /** Why a registration was refused. */
1160
+ export interface GlobalShortcutError {
1161
+ /** The id from the payload that failed, or `''` if it had none. */
1162
+ id: string;
1163
+ /** Craft's error code, e.g. `NATIVE_CALL_FAILED`, `INVALID_PARAMETER`. */
1164
+ code: string;
1165
+ message: string;
1166
+ }
1167
+ /**
1168
+ * System-wide hotkeys: they fire whether or not the app is frontmost.
1169
+ *
1170
+ * Accelerators are `+`-separated and case-insensitive — `'Cmd+Shift+H'`. The
1171
+ * last component is the key, everything before it a modifier (`Cmd`/`Command`/
1172
+ * `Meta`, `Ctrl`/`Control`, `Alt`/`Option`, `Shift`, or `CmdOrCtrl` for the
1173
+ * platform's own). Keys are named by position on the keyboard, not by the
1174
+ * character they produce, so a binding survives a layout change.
1175
+ *
1176
+ * At least one of Command, Control or Option is required, except on the
1177
+ * function keys: a bare global hotkey on `H` would mean no application on the
1178
+ * system ever saw the user type an h again.
1179
+ */
1180
+ export interface CraftGlobalShortcutsAPI {
1181
+ /**
1182
+ * Reserve a combination system-wide. Registering an `id` twice replaces the
1183
+ * first binding.
1184
+ *
1185
+ * The promise resolves once the message is posted, **not** once the key is
1186
+ * reserved — so a combination that belongs to another app or to the system
1187
+ * resolves here and reports on {@link CraftGlobalShortcutsAPI.onError}.
1188
+ */
1189
+ register: (id: string, accelerator: string) => Promise<void>;
1190
+ /** Give the key back to the system and forget the shortcut. */
1191
+ unregister: (id: string) => Promise<void>;
1192
+ /** The same, for every shortcut this app holds. */
1193
+ unregisterAll: () => Promise<void>;
1194
+ /**
1195
+ * Stop firing *and release the key*, so other apps can use it again. The
1196
+ * shortcut stays listed. Holding a reservation while dropping the event
1197
+ * would make the combination dead in every other app for as long as Craft
1198
+ * ran.
1199
+ */
1200
+ disable: (id: string) => Promise<void>;
1201
+ /**
1202
+ * Take the key back. Can fail if something else claimed it while it was
1203
+ * released, which reports on {@link CraftGlobalShortcutsAPI.onError}.
1204
+ */
1205
+ enable: (id: string) => Promise<void>;
1206
+ /** Whether the id is known — enabled or not. */
1207
+ isRegistered: (id: string) => Promise<boolean>;
1208
+ list: () => Promise<GlobalShortcut[]>;
1209
+ /** Every press of a registered, enabled shortcut. Returns an unsubscribe. */
1210
+ on: (handler: (event: {
1211
+ id: string;
1212
+ accelerator: string;
1213
+ }) => void) => () => void;
1214
+ /**
1215
+ * Where every fire-and-forget call reports its failure — `register`,
1216
+ * `enable`, `disable`, `unregister`. (`isRegistered` and `list` are
1217
+ * requests and reject their own promise instead.) Returns an unsubscribe.
1218
+ */
1219
+ onError: (handler: (error: GlobalShortcutError) => void) => () => void;
926
1220
  }
927
1221
  /**
928
1222
  * Focus / Do Not Disturb authorization state, mirroring
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "craft-native",
3
- "version": "0.0.76",
3
+ "version": "0.0.77",
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>",