craft-native 0.0.75 → 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
 
@@ -2124,90 +2200,6 @@ class Telemetry {
2124
2200
  }
2125
2201
  }
2126
2202
  var telemetry = new Telemetry;
2127
- function parseArgv(argv, opts = {}) {
2128
- const result = { _: [] };
2129
- const alias = opts.alias || {};
2130
- const booleans = new Set(opts.boolean || []);
2131
- const aliasOf = {};
2132
- for (const key of Object.keys(alias)) {
2133
- for (const a of alias[key]) {
2134
- aliasOf[a] = key;
2135
- }
2136
- }
2137
- for (const b of booleans) {
2138
- if (alias[b]) {
2139
- for (const a of alias[b])
2140
- booleans.add(a);
2141
- }
2142
- }
2143
- function setKey(key, value) {
2144
- const canonical = aliasOf[key] || key;
2145
- result[canonical] = value;
2146
- if (alias[canonical]) {
2147
- for (const a of alias[canonical])
2148
- result[a] = value;
2149
- }
2150
- if (aliasOf[key] && alias[aliasOf[key]]) {
2151
- for (const a of alias[aliasOf[key]])
2152
- result[a] = value;
2153
- }
2154
- result[key] = value;
2155
- }
2156
- for (let i = 0;i < argv.length; i++) {
2157
- const arg = argv[i];
2158
- if (arg === "--") {
2159
- result._.push(...argv.slice(i + 1));
2160
- break;
2161
- }
2162
- if (arg.startsWith("--")) {
2163
- const eqIdx = arg.indexOf("=");
2164
- if (eqIdx !== -1) {
2165
- const key = arg.slice(2, eqIdx);
2166
- const value = arg.slice(eqIdx + 1);
2167
- setKey(key, value);
2168
- } else {
2169
- const key = arg.slice(2);
2170
- if (key.startsWith("no-")) {
2171
- const actualKey = key.slice(3);
2172
- setKey(actualKey, false);
2173
- continue;
2174
- }
2175
- const canonical = aliasOf[key] || key;
2176
- if (booleans.has(canonical) || booleans.has(key)) {
2177
- setKey(key, true);
2178
- } else {
2179
- const next = argv[i + 1];
2180
- if (next !== undefined && !next.startsWith("-")) {
2181
- setKey(key, next);
2182
- i++;
2183
- } else {
2184
- setKey(key, true);
2185
- }
2186
- }
2187
- }
2188
- } else if (arg.startsWith("-") && arg.length > 1) {
2189
- const chars = arg.slice(1);
2190
- for (let j = 0;j < chars.length; j++) {
2191
- const ch = chars[j];
2192
- const canonical = aliasOf[ch] || ch;
2193
- if (j === chars.length - 1 && !booleans.has(canonical) && !booleans.has(ch)) {
2194
- const next = argv[i + 1];
2195
- if (next !== undefined && !next.startsWith("-")) {
2196
- setKey(ch, next);
2197
- i++;
2198
- } else {
2199
- setKey(ch, true);
2200
- }
2201
- } else {
2202
- setKey(ch, true);
2203
- }
2204
- }
2205
- } else {
2206
- result._.push(arg);
2207
- }
2208
- }
2209
- return result;
2210
- }
2211
2203
  function removeBrackets(v) {
2212
2204
  return v.replace(/[<[].+/, "").trim();
2213
2205
  }
@@ -2304,9 +2296,13 @@ function camelcaseOptionName(name) {
2304
2296
  class ClappError extends Error {
2305
2297
  exitCode = 2;
2306
2298
  isUsageError = true;
2307
- constructor(message) {
2299
+ usage;
2300
+ constructor(message, usage) {
2308
2301
  super(message);
2309
2302
  this.name = this.constructor.name;
2303
+ if (usage !== undefined) {
2304
+ this.usage = usage;
2305
+ }
2310
2306
  if (typeof Error.captureStackTrace === "function") {
2311
2307
  Error.captureStackTrace(this, this.constructor);
2312
2308
  } else {
@@ -2323,6 +2319,12 @@ ${this.stack}`;
2323
2319
  return this.message;
2324
2320
  }
2325
2321
  }
2322
+ function isClappError(err) {
2323
+ if (err instanceof ClappError) {
2324
+ return true;
2325
+ }
2326
+ return !!err && typeof err === "object" && err.name === "ClappError" && typeof err.message === "string";
2327
+ }
2326
2328
  function isUnicodeSupported() {
2327
2329
  const { env } = process22;
2328
2330
  const { TERM, TERM_PROGRAM } = env;
@@ -2404,6 +2406,99 @@ function levenshteinDistance(a, b) {
2404
2406
  function findSimilarCommands(input, commands, maxDistance = 2, maxSuggestions = 3) {
2405
2407
  return commands.map((cmd) => ({ cmd, distance: levenshteinDistance(input, cmd) })).filter(({ distance }) => distance <= maxDistance).sort((a, b) => a.distance - b.distance).slice(0, maxSuggestions).map(({ cmd }) => cmd);
2406
2408
  }
2409
+ function parseArgv(argv, opts = {}) {
2410
+ const result = { _: [] };
2411
+ const alias = opts.alias || {};
2412
+ const booleans = new Set(opts.boolean || []);
2413
+ const aliasOf = {};
2414
+ for (const key of Object.keys(alias)) {
2415
+ for (const a of alias[key]) {
2416
+ aliasOf[a] = key;
2417
+ }
2418
+ }
2419
+ for (const b of booleans) {
2420
+ if (alias[b]) {
2421
+ for (const a of alias[b])
2422
+ booleans.add(a);
2423
+ }
2424
+ }
2425
+ function merge(existing, value) {
2426
+ if (existing === undefined)
2427
+ return value;
2428
+ if (typeof value === "boolean")
2429
+ return value;
2430
+ return Array.isArray(existing) ? [...existing, value] : [existing, value];
2431
+ }
2432
+ function setKey(key, value) {
2433
+ const canonical = aliasOf[key] || key;
2434
+ const merged = merge(result[canonical], value);
2435
+ result[canonical] = merged;
2436
+ if (alias[canonical]) {
2437
+ for (const a of alias[canonical])
2438
+ result[a] = merged;
2439
+ }
2440
+ if (aliasOf[key] && alias[aliasOf[key]]) {
2441
+ for (const a of alias[aliasOf[key]])
2442
+ result[a] = merged;
2443
+ }
2444
+ result[key] = merged;
2445
+ }
2446
+ for (let i = 0;i < argv.length; i++) {
2447
+ const arg = argv[i];
2448
+ if (arg === "--") {
2449
+ result._.push(...argv.slice(i + 1));
2450
+ break;
2451
+ }
2452
+ if (arg.startsWith("--")) {
2453
+ const eqIdx = arg.indexOf("=");
2454
+ if (eqIdx !== -1) {
2455
+ const key = camelcase(arg.slice(2, eqIdx));
2456
+ const value = arg.slice(eqIdx + 1);
2457
+ setKey(key, value);
2458
+ } else {
2459
+ const rawKey = arg.slice(2);
2460
+ if (rawKey.startsWith("no-")) {
2461
+ const actualKey = camelcase(rawKey.slice(3));
2462
+ setKey(actualKey, false);
2463
+ continue;
2464
+ }
2465
+ const key = camelcase(rawKey);
2466
+ const canonical = aliasOf[key] || key;
2467
+ if (booleans.has(canonical) || booleans.has(key)) {
2468
+ setKey(key, true);
2469
+ } else {
2470
+ const next = argv[i + 1];
2471
+ if (next !== undefined && !next.startsWith("-")) {
2472
+ setKey(key, next);
2473
+ i++;
2474
+ } else {
2475
+ setKey(key, true);
2476
+ }
2477
+ }
2478
+ }
2479
+ } else if (arg.startsWith("-") && arg.length > 1) {
2480
+ const chars = arg.slice(1);
2481
+ for (let j = 0;j < chars.length; j++) {
2482
+ const ch = chars[j];
2483
+ const canonical = aliasOf[ch] || ch;
2484
+ if (j === chars.length - 1 && !booleans.has(canonical) && !booleans.has(ch)) {
2485
+ const next = argv[i + 1];
2486
+ if (next !== undefined && !next.startsWith("-")) {
2487
+ setKey(ch, next);
2488
+ i++;
2489
+ } else {
2490
+ setKey(ch, true);
2491
+ }
2492
+ } else {
2493
+ setKey(ch, true);
2494
+ }
2495
+ }
2496
+ } else {
2497
+ result._.push(arg);
2498
+ }
2499
+ }
2500
+ return result;
2501
+ }
2407
2502
 
2408
2503
  class Option {
2409
2504
  rawName;
@@ -2551,9 +2646,9 @@ class Command {
2551
2646
  return this.namespace ? `${this.namespace}:${this.name}` : this.name;
2552
2647
  }
2553
2648
  hasOption(name) {
2554
- name = name.split(".")[0];
2649
+ const normalized = camelcaseOptionName(name.split(".")[0]);
2555
2650
  return !!this.options.find((option) => {
2556
- return option.names.includes(name);
2651
+ return option.names.some((optionName) => camelcaseOptionName(optionName) === normalized);
2557
2652
  });
2558
2653
  }
2559
2654
  outputHelp() {
@@ -2588,6 +2683,7 @@ class Command {
2588
2683
  }
2589
2684
  }
2590
2685
  let commandBody = "";
2686
+ noNamespaceCommands.sort((a, b) => a.rawName < b.rawName ? -1 : a.rawName > b.rawName ? 1 : 0);
2591
2687
  if (noNamespaceCommands.length > 0) {
2592
2688
  commandBody += noNamespaceCommands.map((command) => {
2593
2689
  return ` ${padRight(command.rawName, longestCommandName.length)} ${command.description}`;
@@ -2614,12 +2710,14 @@ class Command {
2614
2710
  body: commandBody
2615
2711
  });
2616
2712
  sections.push({
2617
- title: `For more info, run any command with the \`--help\` flag`,
2618
- body: commands.map((command) => ` $ ${name}${command.displayName === "" ? "" : ` ${command.displayName}`} --help`).join(`
2619
- `)
2713
+ body: `Run \`${name} <command> --help\` for command details.`
2620
2714
  });
2621
2715
  }
2622
- let options = this.isGlobalCommand ? globalOptions : [...this.options, ...globalOptions || []];
2716
+ const localOptionNames = new Set(this.options.flatMap((option) => option.names));
2717
+ let options = this.isGlobalCommand ? globalOptions : [
2718
+ ...this.options,
2719
+ ...(globalOptions || []).filter((option) => !option.names.some((name2) => localOptionNames.has(name2)))
2720
+ ];
2623
2721
  if (!this.isGlobalCommand && !this.isDefaultCommand) {
2624
2722
  options = options.filter((option) => option.name !== "version");
2625
2723
  }
@@ -2663,6 +2761,9 @@ ${section.body}` : section.body;
2663
2761
  console.log(`${name}/${versionNumber} ${platformInfo3}`);
2664
2762
  }
2665
2763
  }
2764
+ get usageLine() {
2765
+ return `$ ${this.cli.name} ${this.usageText || this.rawName}`;
2766
+ }
2666
2767
  checkRequiredArgs() {
2667
2768
  const minimalArgsCount = this.args.filter((arg) => arg.required).length;
2668
2769
  if (this.cli.args.length < minimalArgsCount) {
@@ -2671,7 +2772,7 @@ ${section.body}` : section.body;
2671
2772
  const argNames = missingArgs.map((arg) => `<${arg.value}>`).join(" ");
2672
2773
  throw new ClappError(`Missing required argument${missingArgs.length > 1 ? "s" : ""}: ${argNames}
2673
2774
 
2674
- ` + `Run \`${this.cli.name} ${this.rawName} --help\` for usage information.`);
2775
+ ` + `Run \`${this.cli.name} ${this.rawName} --help\` for usage information.`, this.usageLine);
2675
2776
  }
2676
2777
  }
2677
2778
  checkUnknownOptions() {
@@ -2681,8 +2782,9 @@ ${section.body}` : section.body;
2681
2782
  if (name !== "--" && !this.hasOption(name) && !globalCommand.hasOption(name)) {
2682
2783
  const allOptions = [...globalCommand.options, ...this.options];
2683
2784
  const allOptionNames = allOptions.flatMap((opt) => opt.names);
2785
+ const normalizedName = camelcaseOptionName(name.split(".")[0]);
2684
2786
  const optionFlag = name.length > 1 ? `--${name}` : `-${name}`;
2685
- const suggestions = findSimilarCommands(name, allOptionNames);
2787
+ const suggestions = findSimilarCommands(normalizedName, allOptionNames);
2686
2788
  let errorMsg = `Unknown option \`${optionFlag}\``;
2687
2789
  if (suggestions.length > 0) {
2688
2790
  errorMsg += `
@@ -2697,7 +2799,7 @@ Did you mean one of these?`;
2697
2799
  errorMsg += `
2698
2800
 
2699
2801
  Run \`${this.cli.name} ${this.rawName} --help\` to see available options.`;
2700
- throw new ClappError(errorMsg);
2802
+ throw new ClappError(errorMsg, this.usageLine);
2701
2803
  }
2702
2804
  }
2703
2805
  }
@@ -2712,7 +2814,7 @@ Run \`${this.cli.name} ${this.rawName} --help\` to see available options.`;
2712
2814
  if (value === true || value === false && !hasNegated) {
2713
2815
  throw new ClappError(`Option \`${option.rawName}\` requires a value.
2714
2816
 
2715
- ` + `Example: ${this.cli.name} ${this.rawName} ${option.rawName} <value>`);
2817
+ ` + `Example: ${this.cli.name} ${this.rawName} ${option.rawName} <value>`, this.usageLine);
2716
2818
  }
2717
2819
  }
2718
2820
  }
@@ -3018,6 +3120,25 @@ Received ${signal}, cleaning up...`);
3018
3120
  break;
3019
3121
  }
3020
3122
  }
3123
+ if (shouldParse) {
3124
+ const nextToken = slicedArgv[1];
3125
+ if (nextToken && !nextToken.startsWith("-")) {
3126
+ const joinedName = `${candidateName}:${nextToken}`;
3127
+ for (const command of this.commands) {
3128
+ if (command.isMatched(joinedName)) {
3129
+ const parsed = this.mri(slicedArgv, command);
3130
+ shouldParse = false;
3131
+ const parsedInfo = {
3132
+ ...parsed,
3133
+ args: parsed.args.slice(2)
3134
+ };
3135
+ this.setParsedInfo(parsedInfo, command, joinedName);
3136
+ this.emit(`command:${joinedName}`, command);
3137
+ break;
3138
+ }
3139
+ }
3140
+ }
3141
+ }
3021
3142
  }
3022
3143
  if (shouldParse) {
3023
3144
  for (const command of this.commands) {
@@ -3043,7 +3164,7 @@ Received ${signal}, cleaning up...`);
3043
3164
  if (this.options.debug) {
3044
3165
  this.isDebug = true;
3045
3166
  }
3046
- if (this.options.noInteraction) {
3167
+ if (this.options.noInteraction || this.options.interaction === false) {
3047
3168
  this.isNoInteraction = true;
3048
3169
  }
3049
3170
  if (this.options.env) {
@@ -3055,14 +3176,14 @@ Received ${signal}, cleaning up...`);
3055
3176
  if (this.options.force) {
3056
3177
  this.isForce = true;
3057
3178
  }
3058
- if (this.options.noEmoji !== undefined) {
3059
- this.useEmoji = !this.options.noEmoji;
3179
+ if (this.options.noEmoji || this.options.emoji === false) {
3180
+ this.useEmoji = false;
3060
3181
  }
3061
3182
  if (this.options.theme) {
3062
3183
  this.theme = String(this.options.theme);
3063
3184
  }
3064
- if (this.options.noCache !== undefined) {
3065
- this.isNoCache = Boolean(this.options.noCache);
3185
+ if (this.options.noCache || this.options.cache === false) {
3186
+ this.isNoCache = true;
3066
3187
  }
3067
3188
  if (this.options.help && this.showHelpOnExit) {
3068
3189
  this.outputHelp();
@@ -3143,18 +3264,16 @@ Received ${signal}, cleaning up...`);
3143
3264
  return this.parse(argv, { run: true, exitOnError: true });
3144
3265
  }
3145
3266
  handleUsageError(err) {
3146
- const isClappError = !!err && typeof err === "object" && err.name === "ClappError";
3147
- const isUsage = isClappError && err.isUsageError !== false;
3148
- if (!isUsage)
3267
+ if (!isClappError(err) || err.isUsageError === false)
3149
3268
  return;
3150
- const e = err;
3151
- const raw = e.message ?? "command-line error";
3269
+ const raw = err.message || "command-line error";
3152
3270
  const label = this.name ? `${this.name}: ` : "";
3153
- const suffix = /--help/.test(raw) ? "" : `
3271
+ const suffix = /--help/.test(raw) ? "" : err.usage ? `
3272
+ Usage: ${err.usage}` : `
3154
3273
  Run \`${this.name ?? "cli"} --help\` for usage.`;
3155
3274
  process5.stderr.write(`${label}${raw}${suffix}
3156
3275
  `);
3157
- process5.exit(e.exitCode ?? 2);
3276
+ process5.exit(err.exitCode ?? 2);
3158
3277
  }
3159
3278
  async runMatchedCommand() {
3160
3279
  const { args, options, matchedCommand: command } = this;
@@ -4072,7 +4191,7 @@ function craftBinaryNotFoundMessage(triedPath) {
4072
4191
  `);
4073
4192
  }
4074
4193
  // package.json
4075
- var version = "0.0.75";
4194
+ var version = "0.0.77";
4076
4195
 
4077
4196
  // bin/cli.ts
4078
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.75",
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>",