extension 4.1.20 → 4.1.21

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.cjs CHANGED
@@ -750,7 +750,7 @@ var __webpack_modules__ = {
750
750
  return `${getLoggingPrefix('warn')} safari-web-extension-converter reported ${pintor__rspack_import_8_default().yellow(String(warnings.length))} ${1 === warnings.length ? 'warning' : 'warnings'}, some manifest keys/APIs may not be supported by Safari:\n` + warnings.map((line)=>` ${pintor__rspack_import_8_default().gray('•')} ${line}`).join('\n') + (kept.length ? `\nExtension.js kept ${1 === kept.length ? 'one of these keys' : 'some of these keys'} on purpose:\n${keptLines}` : '');
751
751
  }
752
752
  function safariDefaultBundleIdNote(bundleId) {
753
- return `${getLoggingPrefix('warn')} The bundle id ${pintor__rspack_import_8_default().gray(bundleId)} is generated from the app name.\nEvery project built from the same source shares this id, and the first team to register it takes it.\nSet your own with ${pintor__rspack_import_8_default().blue('--bundle-id')} before your first submission.\nAfter a submission, a new id is a new extension with none of your users.`;
753
+ return `${getLoggingPrefix('info')} Bundle id ${pintor__rspack_import_8_default().gray(bundleId)} (generated, set ${pintor__rspack_import_8_default().blue('--bundle-id')} to own it).`;
754
754
  }
755
755
  function safariOpenHint(appPath, appName) {
756
756
  return `${getLoggingPrefix('info')} Launch it once to register with Safari: ${pintor__rspack_import_8_default().blue('open')} ${pintor__rspack_import_8_default().underline(`"${appPath}"`)}\nThen turn on ${pintor__rspack_import_8_default().yellow(appName)} in Safari ▸ Settings ▸ Extensions.`;
@@ -772,6 +772,9 @@ var __webpack_modules__ = {
772
772
  function safariRegistered(appName) {
773
773
  return `${getLoggingPrefix('success')} Safari recognizes ${pintor__rspack_import_8_default().yellow(appName)}, finish enabling it with the steps above.`;
774
774
  }
775
+ function safariPidUnresolved(appName) {
776
+ return `${getLoggingPrefix('warn')} Opened ${pintor__rspack_import_8_default().yellow(appName)} but could not find its process id.\nThe session's ready.json names the app and the extension without a ${pintor__rspack_import_8_default().gray('browserPid')}.`;
777
+ }
775
778
  function safariNotYetRegistered(appName) {
776
779
  return `${getLoggingPrefix('info')} Safari hasn't picked up ${pintor__rspack_import_8_default().yellow(appName)} yet.\nOpen the app once, then check Safari ▸ Settings ▸ Extensions.`;
777
780
  }
@@ -1033,6 +1036,7 @@ var __webpack_modules__ = {
1033
1036
  jR: ()=>safariBuilding,
1034
1037
  jc: ()=>safariProjectStale,
1035
1038
  jk: ()=>requireGeckoBinaryForGeckoBased,
1039
+ jn: ()=>safariPidUnresolved,
1036
1040
  kK: ()=>preferringSystemBrowserOverSnapshot,
1037
1041
  l9: ()=>parsingPacketError,
1038
1042
  lO: ()=>firefoxDryRunArgs,
@@ -1405,6 +1409,7 @@ var __webpack_modules__ = {
1405
1409
  "./browsers/browsers-lib/ready-stamp.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1406
1410
  var node_fs__rspack_import_0 = __webpack_require__("node:fs");
1407
1411
  var node_path__rspack_import_1 = __webpack_require__("node:path");
1412
+ var _write_json_atomic__rspack_import_2 = __webpack_require__("./browsers/browsers-lib/write-json-atomic.ts");
1408
1413
  function readyPathFor(extensionOutputPath) {
1409
1414
  return node_path__rspack_import_1.join(node_path__rspack_import_1.dirname(extensionOutputPath), 'extension-js', node_path__rspack_import_1.basename(extensionOutputPath), 'ready.json');
1410
1415
  }
@@ -1416,7 +1421,7 @@ var __webpack_modules__ = {
1416
1421
  const ready = JSON.parse(node_fs__rspack_import_0.readFileSync(readyPath, 'utf-8'));
1417
1422
  if (ready.rdpPort === rdpPort) return;
1418
1423
  ready.rdpPort = rdpPort;
1419
- node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1424
+ (0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
1420
1425
  } catch {}
1421
1426
  }
1422
1427
  function stampReadyBrowserLaunch(extensionOutputPath, details) {
@@ -1434,7 +1439,7 @@ var __webpack_modules__ = {
1434
1439
  if (binary) ready.binary = binary;
1435
1440
  const provenance = String(details?.binaryProvenance || '').trim();
1436
1441
  if (provenance) ready.binaryProvenance = provenance;
1437
- node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1442
+ (0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
1438
1443
  } catch {}
1439
1444
  }
1440
1445
  function stampReadyExtensionId(extensionOutputPath, extensionId) {
@@ -1446,7 +1451,7 @@ var __webpack_modules__ = {
1446
1451
  const ready = JSON.parse(node_fs__rspack_import_0.readFileSync(readyPath, 'utf-8'));
1447
1452
  if (ready.extensionId === id) return;
1448
1453
  ready.extensionId = id;
1449
- node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1454
+ (0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
1450
1455
  } catch {}
1451
1456
  }
1452
1457
  function stampReadyExtensionLoadRefused(extensionOutputPath, reason) {
@@ -1461,7 +1466,7 @@ var __webpack_modules__ = {
1461
1466
  ready.message = `${browserLabel.charAt(0).toUpperCase() + browserLabel.slice(1)} refused to load the extension at ${extensionOutputPath}${reason ? `: ${reason}` : ''}`;
1462
1467
  ready.extensionLoadRefusedAt = new Date().toISOString();
1463
1468
  if (reason) ready.extensionLoadRefusedReason = reason;
1464
- node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1469
+ (0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
1465
1470
  } catch {}
1466
1471
  }
1467
1472
  function stampReadyProfileLocked(extensionOutputPath, details) {
@@ -1475,7 +1480,7 @@ var __webpack_modules__ = {
1475
1480
  ready.message = String(details?.message || '').trim() || 'the browser profile is already in use by another session';
1476
1481
  ready.profileLockedAt = new Date().toISOString();
1477
1482
  if (details?.owner) ready.profileLockOwner = details.owner;
1478
- node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1483
+ (0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
1479
1484
  } catch {}
1480
1485
  }
1481
1486
  function stampReadyBrowserExited(extensionOutputPath, code) {
@@ -1491,7 +1496,7 @@ var __webpack_modules__ = {
1491
1496
  ready.code = 'browser_exited';
1492
1497
  ready.message = `the ${ready.browser || 'browser'} process exited (code ${code ?? 'unknown'}); nothing is running`;
1493
1498
  }
1494
- node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1499
+ (0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
1495
1500
  } catch {}
1496
1501
  }
1497
1502
  __webpack_require__.d(__webpack_exports__, {
@@ -1955,6 +1960,27 @@ var __webpack_modules__ = {
1955
1960
  yO: ()=>probeChromiumBinaryVersion
1956
1961
  });
1957
1962
  },
1963
+ "./browsers/browsers-lib/write-json-atomic.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1964
+ var node_fs__rspack_import_0 = __webpack_require__("node:fs");
1965
+ function writeJsonAtomic(filePath, value) {
1966
+ const tmpPath = `${filePath}.tmp-${process.pid}`;
1967
+ try {
1968
+ node_fs__rspack_import_0.writeFileSync(tmpPath, JSON.stringify(value, null, 2), 'utf-8');
1969
+ node_fs__rspack_import_0.renameSync(tmpPath, filePath);
1970
+ return true;
1971
+ } catch {
1972
+ try {
1973
+ node_fs__rspack_import_0.rmSync(tmpPath, {
1974
+ force: true
1975
+ });
1976
+ } catch {}
1977
+ return false;
1978
+ }
1979
+ }
1980
+ __webpack_require__.d(__webpack_exports__, {
1981
+ Z: ()=>writeJsonAtomic
1982
+ });
1983
+ },
1958
1984
  "./browsers/browsers-lib/wsl-support.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1959
1985
  __webpack_require__.d(__webpack_exports__, {
1960
1986
  qc: ()=>external_wsl_support_namespaceObject.hasGuiDisplay,
@@ -5807,7 +5833,6 @@ var __webpack_modules__ = {
5807
5833
  QW: ()=>safariBuildPreflight,
5808
5834
  iF: ()=>safariPreflightError
5809
5835
  });
5810
- var external_node_child_process_ = __webpack_require__("node:child_process");
5811
5836
  var external_node_fs_ = __webpack_require__("node:fs");
5812
5837
  var messaging = __webpack_require__("./helpers/messaging.ts");
5813
5838
  var banner = __webpack_require__("./browsers/browsers-lib/banner.ts");
@@ -5821,6 +5846,7 @@ var __webpack_modules__ = {
5821
5846
  (0, messaging._w)(messages.Aj(xcodebuildCmd));
5822
5847
  }
5823
5848
  var safari_config = __webpack_require__("./browsers/run-safari/safari-launch/safari-config.ts");
5849
+ var external_node_child_process_ = __webpack_require__("node:child_process");
5824
5850
  function isMacOS() {
5825
5851
  return 'darwin' === process.platform;
5826
5852
  }
@@ -5878,20 +5904,6 @@ var __webpack_modules__ = {
5878
5904
  ok: Boolean(converter && xcodebuild)
5879
5905
  };
5880
5906
  }
5881
- function fallbackLogger() {
5882
- return {
5883
- info: (...a)=>(0, messaging._w)(...a),
5884
- warn: (...a)=>(0, messaging.mR)(...a),
5885
- error: (...a)=>(0, messaging.Gg)(...a),
5886
- debug: (...a)=>console?.debug?.(...a)
5887
- };
5888
- }
5889
- function isTestEnv() {
5890
- return Boolean(process.env.VITEST || process.env.VITEST_WORKER_ID);
5891
- }
5892
- function delay(ms) {
5893
- return new Promise((resolve)=>setTimeout(resolve, ms));
5894
- }
5895
5907
  const TOOL_TAIL_LINES = 50;
5896
5908
  const TOOL_TAIL_BYTES = 8192;
5897
5909
  function toolOutputTail(output) {
@@ -5930,6 +5942,60 @@ var __webpack_modules__ = {
5930
5942
  }));
5931
5943
  });
5932
5944
  }
5945
+ async function resolvePidForBundle(bundleId) {
5946
+ for(let attempt = 0; attempt < 10; attempt += 1){
5947
+ const pid = await new Promise((resolve)=>{
5948
+ const child = (0, external_node_child_process_.spawn)("osascript", [
5949
+ '-e',
5950
+ `tell application "System Events" to get unix id of first process whose bundle identifier is "${bundleId}"`
5951
+ ]);
5952
+ let out = '';
5953
+ child.stdout.on('data', (d)=>out += String(d));
5954
+ child.on('error', ()=>resolve(null));
5955
+ child.on('close', ()=>{
5956
+ const n = Number(String(out).trim());
5957
+ resolve(Number.isFinite(n) && n > 0 ? n : null);
5958
+ });
5959
+ });
5960
+ if (pid) return pid;
5961
+ await new Promise((r)=>setTimeout(r, 500));
5962
+ }
5963
+ return null;
5964
+ }
5965
+ function createSafariTools() {
5966
+ return {
5967
+ detectToolchain: ()=>detectSafariToolchain(),
5968
+ runConverter: (args)=>runTool('xcrun', args),
5969
+ runXcodebuild: (args)=>runTool('xcodebuild', args),
5970
+ openApp: (target)=>runTool('open', [
5971
+ target
5972
+ ]),
5973
+ openSafari: (binary)=>runTool('open', [
5974
+ '-a',
5975
+ binary
5976
+ ]),
5977
+ resolvePid: (bundleId)=>resolvePidForBundle(bundleId),
5978
+ pluginkitList: async ()=>{
5979
+ const { ok, output } = await runTool('pluginkit', [
5980
+ '-m'
5981
+ ], {
5982
+ quiet: true
5983
+ });
5984
+ return ok ? output : '';
5985
+ }
5986
+ };
5987
+ }
5988
+ function fallbackLogger() {
5989
+ return {
5990
+ info: (...a)=>(0, messaging._w)(...a),
5991
+ warn: (...a)=>(0, messaging.mR)(...a),
5992
+ error: (...a)=>(0, messaging.Gg)(...a),
5993
+ debug: (...a)=>console?.debug?.(...a)
5994
+ };
5995
+ }
5996
+ function delay(ms) {
5997
+ return new Promise((resolve)=>setTimeout(resolve, ms));
5998
+ }
5933
5999
  function converterWarnings(output) {
5934
6000
  const lines = output.split(/\r?\n/);
5935
6001
  const collected = [];
@@ -5950,15 +6016,11 @@ var __webpack_modules__ = {
5950
6016
  }
5951
6017
  return collected;
5952
6018
  }
5953
- async function confirmRegisteredWithSafari(bundleIdentifier) {
6019
+ async function confirmRegisteredWithSafari(tools, bundleIdentifier) {
5954
6020
  const needle = `${bundleIdentifier}.Extension`;
5955
6021
  for(let attempt = 0; attempt < 6; attempt += 1){
5956
- const { ok, output } = await runTool('pluginkit', [
5957
- '-m'
5958
- ], {
5959
- quiet: true
5960
- });
5961
- if (ok && output.includes(needle)) return true;
6022
+ const listing = await tools.pluginkitList();
6023
+ if (listing.includes(needle)) return true;
5962
6024
  await delay(800);
5963
6025
  }
5964
6026
  return false;
@@ -5973,6 +6035,10 @@ var __webpack_modules__ = {
5973
6035
  macOsOnly: config.macOsOnly
5974
6036
  };
5975
6037
  }
6038
+ function completePackage(config, logger, mode) {
6039
+ if ('full' === mode && config.bundleIdDerived) logger.info?.(messages.$q(config.bundleIdentifier));
6040
+ return describePackage(config);
6041
+ }
5976
6042
  function safariPreflightError() {
5977
6043
  const tc = detectSafariToolchain();
5978
6044
  if (!tc.platformOk) return messages.G3(process.platform);
@@ -5996,26 +6062,6 @@ var __webpack_modules__ = {
5996
6062
  severity: 'ok'
5997
6063
  };
5998
6064
  }
5999
- async function resolvePidForBundle(bundleId) {
6000
- for(let attempt = 0; attempt < 10; attempt += 1){
6001
- const pid = await new Promise((resolve)=>{
6002
- const child = (0, external_node_child_process_.spawn)("osascript", [
6003
- '-e',
6004
- `tell application "System Events" to get unix id of first process whose bundle identifier is "${bundleId}"`
6005
- ]);
6006
- let out = '';
6007
- child.stdout.on('data', (d)=>out += String(d));
6008
- child.on('error', ()=>resolve(null));
6009
- child.on('close', ()=>{
6010
- const n = Number(String(out).trim());
6011
- resolve(Number.isFinite(n) && n > 0 ? n : null);
6012
- });
6013
- });
6014
- if (pid) return pid;
6015
- await new Promise((r)=>setTimeout(r, 500));
6016
- }
6017
- return null;
6018
- }
6019
6065
  async function announceSafariDevSession(host, config, appPath) {
6020
6066
  try {
6021
6067
  await (0, banner.ai)({
@@ -6031,16 +6077,16 @@ var __webpack_modules__ = {
6031
6077
  } catch {}
6032
6078
  }
6033
6079
  async function runSafariPipeline(compilation, host, logger, mode) {
6080
+ const tools = host.tools || createSafariTools();
6034
6081
  const config = (0, safari_config.F7)(compilation, host);
6035
6082
  const converterArgs = (0, safari_config.w7)(config);
6036
6083
  const xcodebuildArgs = (0, safari_config.vx)(config);
6037
6084
  const isSigned = Boolean(config.developmentTeam);
6038
- if ('full' === mode && config.bundleIdDerived) logger.warn?.(messages.$q(config.bundleIdentifier));
6039
- if (host.dryRun || isTestEnv()) {
6085
+ if (host.dryRun) {
6040
6086
  logSafariDryRun(`xcrun ${converterArgs.join(' ')}`, `xcodebuild ${xcodebuildArgs.join(' ')}`);
6041
- return describePackage(config);
6087
+ return completePackage(config, logger, mode);
6042
6088
  }
6043
- const toolchain = detectSafariToolchain();
6089
+ const toolchain = tools.detectToolchain();
6044
6090
  if (!toolchain.platformOk) {
6045
6091
  logger.warn?.(messages.G3(process.platform));
6046
6092
  return describePackage(config);
@@ -6064,7 +6110,7 @@ var __webpack_modules__ = {
6064
6110
  }
6065
6111
  const { saved, restore } = (0, safari_config._D)(config);
6066
6112
  logger.info?.(messages.uK(config.extensionDir));
6067
- const converted = await runTool('xcrun', converterArgs);
6113
+ const converted = await tools.runConverter(converterArgs);
6068
6114
  if (!converted.ok) {
6069
6115
  const tail = toolOutputTail(converted.output);
6070
6116
  logger.error?.(messages.A('safari-web-extension-converter', converted.code, tail));
@@ -6081,7 +6127,7 @@ var __webpack_modules__ = {
6081
6127
  logger.info?.(messages.l_(config.projectLocation));
6082
6128
  } else if ('full' === mode) logger.info?.(messages.oM());
6083
6129
  if ('full' === mode) logger.info?.(messages.jR((0, safari_config.aD)(config)));
6084
- const built = await runTool('xcodebuild', xcodebuildArgs);
6130
+ const built = await tools.runXcodebuild(xcodebuildArgs);
6085
6131
  if (!built.ok) {
6086
6132
  const tail = toolOutputTail(built.output);
6087
6133
  logger.error?.(messages.A('xcodebuild', built.code, tail));
@@ -6096,30 +6142,26 @@ var __webpack_modules__ = {
6096
6142
  if (!config.open) {
6097
6143
  logger.info?.(messages.aO(appPath, config.appName));
6098
6144
  if (host.announceDevReady) await announceSafariDevSession(host, config, appPath);
6099
- return describePackage(config);
6145
+ return completePackage(config, logger, mode);
6100
6146
  }
6101
6147
  const target = external_node_fs_.existsSync(appPath) ? appPath : (0, safari_config.Gi)(config);
6102
6148
  logger.info?.(messages.fw(target));
6103
- await runTool('open', [
6104
- target
6105
- ]);
6106
- if (config.safariBinary) await runTool('open', [
6107
- '-a',
6108
- config.safariBinary
6109
- ]);
6149
+ await tools.openApp(target);
6150
+ if (config.safariBinary) await tools.openSafari(config.safariBinary);
6110
6151
  const raisedBundleId = config.safariBinary ? 'com.apple.Safari' : config.bundleIdentifier;
6111
- const browserPid = await resolvePidForBundle(raisedBundleId);
6112
- if (browserPid) (0, ready_stamp.M)(config.extensionDir, {
6113
- browserPid,
6152
+ const browserPid = await tools.resolvePid(raisedBundleId);
6153
+ (0, ready_stamp.M)(config.extensionDir, {
6154
+ browserPid: browserPid || void 0,
6114
6155
  binary: config.safariBinary || appPath,
6115
6156
  binaryProvenance: config.safariBinary ? 'pinned' : 'system',
6116
6157
  extensionId: `${config.bundleIdentifier}.Extension`
6117
6158
  });
6159
+ if (!browserPid) logger.warn?.(messages.jn(config.appName));
6118
6160
  logger.info?.(messages.fO(config.appName, isSigned));
6119
- if (await confirmRegisteredWithSafari(config.bundleIdentifier)) logger.info?.(messages.fd(config.appName));
6161
+ if (await confirmRegisteredWithSafari(tools, config.bundleIdentifier)) logger.info?.(messages.fd(config.appName));
6120
6162
  else logger.info?.(messages.qY(config.appName));
6121
6163
  if (host.announceDevReady) await announceSafariDevSession(host, config, appPath);
6122
- return describePackage(config);
6164
+ return completePackage(config, logger, mode);
6123
6165
  }
6124
6166
  async function packageSafariExtension(host, outputPath, logger, mode = 'full') {
6125
6167
  const compilation = {
@@ -6239,9 +6281,10 @@ var __webpack_modules__ = {
6239
6281
  function manifestFingerprintPath(config) {
6240
6282
  return node_path__rspack_import_1.join(config.projectLocation, '.manifest-fingerprint');
6241
6283
  }
6284
+ const HOT_UPDATE_DIR = 'hot';
6242
6285
  function topLevelEntries(extensionDir) {
6243
6286
  try {
6244
- return node_fs__rspack_import_0.readdirSync(extensionDir).sort();
6287
+ return node_fs__rspack_import_0.readdirSync(extensionDir).filter((entry)=>entry !== HOT_UPDATE_DIR).sort();
6245
6288
  } catch {
6246
6289
  return [];
6247
6290
  }
@@ -6370,7 +6413,7 @@ var __webpack_modules__ = {
6370
6413
  return Object.fromEntries(Object.entries(input).filter(([, value])=>void 0 !== value));
6371
6414
  }
6372
6415
  function createSafariPackager(options = {}) {
6373
- const { browser = 'safari', noOpen = true, dryRun = false, logger, ...identity } = options;
6416
+ const { browser = 'safari', noOpen = true, dryRun = false, logger, tools, ...identity } = options;
6374
6417
  return async (distPath, mode = 'full', overrides)=>await (0, _safari_launch__rspack_import_0.Ah)({
6375
6418
  extension: [
6376
6419
  distPath
@@ -6378,6 +6421,7 @@ var __webpack_modules__ = {
6378
6421
  browser,
6379
6422
  noOpen,
6380
6423
  dryRun,
6424
+ tools,
6381
6425
  ...withoutUndefined(identity),
6382
6426
  ...withoutUndefined(overrides)
6383
6427
  }, distPath, logger, mode);
@@ -6705,6 +6749,8 @@ var __webpack_modules__ = {
6705
6749
  const MIN_NODE_MINOR = 12;
6706
6750
  const MIN_DENO_MAJOR = 2;
6707
6751
  const MIN_DENO_MINOR = 5;
6752
+ const MIN_BUN_MAJOR = 1;
6753
+ const MIN_BUN_MINOR = 2;
6708
6754
  const BLOCKED_DENO_VERSIONS = [
6709
6755
  '2.8.0'
6710
6756
  ];
@@ -6721,6 +6767,9 @@ var __webpack_modules__ = {
6721
6767
  if (BLOCKED_DENO_VERSIONS.includes(version)) return false;
6722
6768
  return meetsFloor(version, MIN_DENO_MAJOR, MIN_DENO_MINOR);
6723
6769
  }
6770
+ function isSupportedBunVersion(version) {
6771
+ return meetsFloor(version, MIN_BUN_MAJOR, MIN_BUN_MINOR);
6772
+ }
6724
6773
  function detectBunVersion(versions = process.versions) {
6725
6774
  const bunVersion = versions.bun;
6726
6775
  return 'string' == typeof bunVersion && bunVersion.length > 0 ? bunVersion : void 0;
@@ -6733,8 +6782,10 @@ var __webpack_modules__ = {
6733
6782
  if (BLOCKED_DENO_VERSIONS.includes(version)) return `[Extension.js] Deno ${version} cannot load node:querystring inside the bundler, so every build fails on it. Deno fixed that in 2.8.1. Run deno upgrade to build the extension on Deno.`;
6734
6783
  return `[Extension.js] Requires Deno >= ${MIN_DENO_MAJOR}.${MIN_DENO_MINOR} (you are on ${version}). Run deno upgrade, or run the extension CLI on Node.js >= ${MIN_NODE_MAJOR}.${MIN_NODE_MINOR} instead.`;
6735
6784
  }
6736
- function unsupportedNodeVersionMessage(version, bunVersion) {
6737
- if (bunVersion) return `[Extension.js] The extension CLI runs on Node.js, not on the Bun runtime (Bun ${bunVersion}, reporting Node.js ${version}), so the Node.js you have installed is not the problem. Run it on Node.js instead: drop --bun from bunx or bun run, or unset run.bun in bunfig.toml, plain bunx runs the extension CLI on Node.js.`;
6785
+ function unsupportedBunVersionMessage(version) {
6786
+ return `[Extension.js] Requires Bun >= ${MIN_BUN_MAJOR}.${MIN_BUN_MINOR} (you are on ${version}). Run bun upgrade, or run the extension CLI on Node.js >= ${MIN_NODE_MAJOR}.${MIN_NODE_MINOR} instead.`;
6787
+ }
6788
+ function unsupportedNodeVersionMessage(version) {
6738
6789
  return `[Extension.js] Requires Node.js >= ${MIN_NODE_MAJOR}.${MIN_NODE_MINOR} (you are on ${version}). Upgrade Node.js to run the extension CLI.`;
6739
6790
  }
6740
6791
  function enforceSupportedNodeVersion(version = process.versions.node, bunVersion = detectBunVersion(), denoVersion = detectDenoVersion()) {
@@ -6744,8 +6795,14 @@ var __webpack_modules__ = {
6744
6795
  process.exit(1);
6745
6796
  return;
6746
6797
  }
6747
- if (!bunVersion && isSupportedNodeVersion(version)) return;
6748
- console.error(unsupportedNodeVersionMessage(version, bunVersion));
6798
+ if (bunVersion) {
6799
+ if (isSupportedBunVersion(bunVersion)) return;
6800
+ console.error(unsupportedBunVersionMessage(bunVersion));
6801
+ process.exit(1);
6802
+ return;
6803
+ }
6804
+ if (isSupportedNodeVersion(version)) return;
6805
+ console.error(unsupportedNodeVersionMessage(version));
6749
6806
  process.exit(1);
6750
6807
  }
6751
6808
  enforceSupportedNodeVersion();
@@ -7141,7 +7198,7 @@ var __webpack_modules__ = {
7141
7198
  return Math.min(Math.max(n, min), max);
7142
7199
  }
7143
7200
  const TEMPLATE_CORPUS_REPO = 'extension-js/examples';
7144
- const TEMPLATE_CORPUS_REF = '39448a235ea1b02516e6d555bcae409db2258b7f';
7201
+ const TEMPLATE_CORPUS_REF = '06d33e35894516a460409dca06d1614e291e6252';
7145
7202
  const TEMPLATE_CORPUS_SLUGS = [
7146
7203
  'action',
7147
7204
  'action-locales',
@@ -8254,7 +8311,10 @@ Docker / Devcontainers / Codespaces
8254
8311
  - Use ${messages_code('--host 0.0.0.0')} to bind the dev server on all interfaces so HMR is reachable from the host.
8255
8312
  - Use ${messages_code('--no-browser')} inside the container and load the extension manually from ${messages_code('dist/<browser>/')} in your host browser.
8256
8313
  - Chromium sandbox flags (${messages_code('--no-sandbox')}) are added automatically when Docker, Podman, devcontainers, or Codespaces are detected.
8257
- - File watching uses polling by default (1 s interval), which works across bind-mounted volumes.
8314
+ - File watching uses native file events by default, which keeps rebuilds fast.
8315
+ - Set ${messages_code(messages_arg('EXTENSION_WATCH_POLL=true'))} to poll instead when native events do not fire,
8316
+ as in a container, a virtual machine, or a network or bind mount.
8317
+ Tune it with ${messages_code(messages_arg('EXTENSION_WATCH_POLL_INTERVAL'))} (default 1000 ms).
8258
8318
  - Example: ${messages_code('extension dev ./my-ext --host 0.0.0.0 --no-browser --port 8080')}
8259
8319
 
8260
8320
  Flatpak Firefox
@@ -8427,7 +8487,9 @@ Cross-browser compatibility
8427
8487
  ],
8428
8488
  notes: [
8429
8489
  'Use --no-browser inside the container and load dist/<browser>/ in the host browser',
8430
- 'File watching uses polling (1s interval) for bind-mount compatibility',
8490
+ 'File watching uses native file events by default',
8491
+ 'Set EXTENSION_WATCH_POLL=true to poll instead when native events do not fire, as in a container, a virtual machine, or a network or bind mount',
8492
+ 'EXTENSION_WATCH_POLL_INTERVAL sets the poll interval in ms (default 1000)',
8431
8493
  '--no-sandbox is added automatically when a container environment is detected'
8432
8494
  ]
8433
8495
  }
@@ -8458,12 +8520,45 @@ Cross-browser compatibility
8458
8520
  }
8459
8521
  const OPEN_SURFACE_NOUN = {
8460
8522
  popup: 'popup',
8523
+ options: 'options page',
8461
8524
  sidebar: 'side panel',
8462
8525
  action: 'action popup'
8463
8526
  };
8464
8527
  function openSurfaceNoun(surface) {
8465
8528
  return OPEN_SURFACE_NOUN[surface] || surface;
8466
8529
  }
8530
+ function openSurfaceAction(surface) {
8531
+ return 'command' === surface ? 'trigger the command' : `open the ${openSurfaceNoun(surface)}`;
8532
+ }
8533
+ function asSentence(text) {
8534
+ const trimmed = text.trim();
8535
+ const capitalized = trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
8536
+ return capitalized.endsWith('.') ? capitalized : `${capitalized}.`;
8537
+ }
8538
+ function listenerCount(count, event) {
8539
+ return `${count} ${event} ${1 === count ? 'listener' : 'listeners'}`;
8540
+ }
8541
+ function openedSurface(surface) {
8542
+ return `${getLoggingPrefix('success')} Opened the ${openSurfaceNoun(surface)}.`;
8543
+ }
8544
+ function replayedActionClick(listeners) {
8545
+ return `${getLoggingPrefix('success')} Replayed ${listenerCount(listeners, 'onClicked')} without a user gesture.`;
8546
+ }
8547
+ function replayedCommand(listeners, command) {
8548
+ const name = command ? ` for ${messages_arg(command)}` : '';
8549
+ return `${getLoggingPrefix('success')} Replayed ${listenerCount(listeners, 'onCommand')}${name} without a user gesture.`;
8550
+ }
8551
+ function openSurfaceWarning(warning) {
8552
+ return `${getLoggingPrefix('warn')} ${asSentence(warning)}`;
8553
+ }
8554
+ function openSurfaceFailed(surface, reason, engine, remedy) {
8555
+ const engineNote = engine ? messages_arg(` (engine ${engine})`) : '';
8556
+ return [
8557
+ `${getLoggingPrefix('error')} Can't ${openSurfaceAction(surface)}.`,
8558
+ `${messaging.Sc.label('REASON')} ${messaging.Sc.val(reason)}${engineNote}`,
8559
+ remedy
8560
+ ].filter(Boolean).join('\n');
8561
+ }
8467
8562
  function openSurfaceGestureStep(surface) {
8468
8563
  return `Click the extension's toolbar entry, or its row in the extensions menu, to open the ${openSurfaceNoun(surface)}.`;
8469
8564
  }
@@ -8473,6 +8568,12 @@ Cross-browser compatibility
8473
8568
  function openSurfaceNeedsGesture(surface) {
8474
8569
  return `${getLoggingPrefix('error')} Chromium opens the ${openSurfaceNoun(surface)} only in response to a user gesture, so ${messages_code(`extension open ${surface}`)} can't open it from the command line.\n` + openSurfaceGestureStep(surface);
8475
8570
  }
8571
+ function doctorHeader(browser, passes, total) {
8572
+ return `doctor (${browser}), ${passes}/${total} checks passed`;
8573
+ }
8574
+ function doctorRemedy(check, remediation) {
8575
+ return `\n${check}: ${remediation}`;
8576
+ }
8476
8577
  function sessionOnPort(browser, port) {
8477
8578
  return port ? `for ${browser} on port ${port}` : `for ${browser}`;
8478
8579
  }
@@ -8735,11 +8836,20 @@ Cross-browser compatibility
8735
8836
  if ('json' === format) return void console.log(JSON.stringify(event, null, 2));
8736
8837
  console.log(formatPrettyLogLine(event));
8737
8838
  }
8839
+ function levelToken(level) {
8840
+ const word = level.toUpperCase();
8841
+ if ('error' === level) return external_pintor_default().red(word);
8842
+ if ('warn' === level) return external_pintor_default().brightYellow(word);
8843
+ if ('info' === level) return external_pintor_default().gray(word);
8844
+ if ('debug' === level) return external_pintor_default().dim(external_pintor_default().gray(word));
8845
+ return word;
8846
+ }
8738
8847
  function formatPrettyLogLine(event) {
8739
8848
  const parts = Array.isArray(event.messageParts) ? event.messageParts.map((p)=>'string' == typeof p ? p : JSON.stringify(p)).join(' ') : '';
8740
8849
  const code = event.code ? ` ${event.code}` : '';
8741
8850
  const remediation = event.remediation ? `\n ↳ ${event.remediation}` : '';
8742
- return `[${event.seq ?? '-'}] ${String(event.level || 'log').toUpperCase()} (${event.context})${code} ${parts}${remediation}`;
8851
+ const level = String(event.level || 'log').toLowerCase();
8852
+ return `[${event.seq ?? '-'}] ${levelToken(level)} ${external_pintor_default().dim(`(${event.context})`)}${code} ${parts}${remediation}`;
8743
8853
  }
8744
8854
  function writeFrame(frame) {
8745
8855
  try {
@@ -8822,7 +8932,7 @@ Cross-browser compatibility
8822
8932
  if (matches(event)) printEvent(event, format);
8823
8933
  },
8824
8934
  onGap: (gap)=>{
8825
- console.error(`… ${gap.dropped} event(s) dropped (${gap.reason}), stream is behind`);
8935
+ console.error(external_pintor_default().dim(external_pintor_default().gray(`… ${gap.dropped} event(s) dropped (${gap.reason}), stream is behind`)));
8826
8936
  },
8827
8937
  onClose: (close)=>{
8828
8938
  if (!refusals.has(close.code) && sessionStillNamed()) return;
@@ -8994,11 +9104,33 @@ Cross-browser compatibility
8994
9104
  hint: openSurfaceGestureStep(surface)
8995
9105
  };
8996
9106
  }
8997
- function printResult(result, output, command) {
9107
+ function openResultLines(value) {
9108
+ if (!value || 'object' != typeof value) return;
9109
+ const reply = value;
9110
+ if ('string' == typeof reply.opened) return [
9111
+ openedSurface(reply.opened)
9112
+ ];
9113
+ if ('popup' === reply.triggered) return [
9114
+ openedSurface('action')
9115
+ ];
9116
+ if ('number' != typeof reply.listeners) return;
9117
+ const lines = [];
9118
+ if ('onClicked' === reply.triggered) lines.push(replayedActionClick(reply.listeners));
9119
+ else {
9120
+ if ('command' !== reply.triggered) return;
9121
+ lines.push(replayedCommand(reply.listeners, 'string' == typeof reply.command ? reply.command : void 0));
9122
+ }
9123
+ if ('string' == typeof reply.warning && reply.warning) lines.push(openSurfaceWarning(reply.warning));
9124
+ return lines;
9125
+ }
9126
+ function printResult(result, output, command, pretty) {
8998
9127
  if ('json' === normalizeOutputFormat(output)) return void console.log(JSON.stringify(buildActEnvelope(command, result)));
8999
9128
  if (result.ok) {
9000
9129
  const value = result.value;
9001
- console.log('string' == typeof value ? value : JSON.stringify(value, null, 2));
9130
+ const lines = pretty?.value(value) ?? [
9131
+ 'string' == typeof value ? value : JSON.stringify(value, null, 2)
9132
+ ];
9133
+ for (const line of lines)console.log(line);
9002
9134
  const consoleRecords = result.console;
9003
9135
  if (Array.isArray(consoleRecords)) {
9004
9136
  if (0 === consoleRecords.length) console.log('(no console lines captured)');
@@ -9007,6 +9139,11 @@ Cross-browser compatibility
9007
9139
  if (result.truncated) console.error('… result truncated (byte cap)');
9008
9140
  return;
9009
9141
  }
9142
+ if (pretty) {
9143
+ const { error } = buildActEnvelope(command, result);
9144
+ console.error(pretty.failure(error));
9145
+ return;
9146
+ }
9010
9147
  const err = result.error || {
9011
9148
  name: 'Error',
9012
9149
  message: 'command failed'
@@ -9080,7 +9217,7 @@ Cross-browser compatibility
9080
9217
  if (result?.ok && input.augment) try {
9081
9218
  Object.assign(result, input.augment(projectPath, browser, result));
9082
9219
  } catch {}
9083
- printResult(result, input.opts.output, input.command);
9220
+ printResult(result, input.opts.output, input.command, input.pretty);
9084
9221
  await exitAfterDrain(result.ok ? 0 : 1);
9085
9222
  }
9086
9223
  function targetFrom(opts, fallback = 'background') {
@@ -9225,7 +9362,11 @@ Cross-browser compatibility
9225
9362
  },
9226
9363
  args,
9227
9364
  opts,
9228
- preflight: (bridge, projectPath, browser)=>gestureRefusal(surface, bridge, projectPath, browser)
9365
+ preflight: (bridge, projectPath, browser)=>gestureRefusal(surface, bridge, projectPath, browser),
9366
+ pretty: {
9367
+ value: openResultLines,
9368
+ failure: (error)=>openSurfaceFailed(surface, error.message, error.engine, error.hint)
9369
+ }
9229
9370
  });
9230
9371
  });
9231
9372
  }
@@ -9264,7 +9405,7 @@ Cross-browser compatibility
9264
9405
  return values.length > 0 ? values : void 0;
9265
9406
  }
9266
9407
  function registerBuildCommand(program) {
9267
- program.command('build').arguments('[project-name]').usage('[path-to-remote-extension] [options]').description(commandDescriptions.build).option(`--browser <${BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds a Safari app via Xcode (macOS only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `false`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--zip [boolean]', 'whether or not to compress the extension into a ZIP file. Defaults to `false`', parseOptionalBoolean).option('--zip-source [boolean]', 'whether or not to include the source files in the ZIP file. Defaults to `false`', parseOptionalBoolean).option('--zip-filename <string>', 'specify the name of the ZIP file. Defaults to the extension name and version').option('--silent [boolean]', 'suppress the build summary output. Defaults to `false`', parseOptionalBoolean).option('--addon-lint [boolean]', 'check Firefox builds against addons.mozilla.org rules with addons-linter when it is installed. Findings print as warnings. Defaults to `true`', parseOptionalBoolean).option('--no-addon-lint', 'skip the addons.mozilla.org lint of Firefox builds').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--mode <development|production|none>', 'bundler mode override (also sets NODE_ENV). Defaults to `production`').option('--open [boolean]', 'open the built Safari app after packaging (safari targets only). Defaults to `false`', parseOptionalBoolean).option('--app-name <name>', 'override the Safari app name (safari targets only). Defaults to the manifest `name`').option('--bundle-id <reverse.dns>', 'set a user-owned Safari bundle identifier (safari targets only). Defaults to a generated dev.extensionjs.* id').option('--development-team <id>', 'sign the Safari app with an Apple Developer team id (safari targets only). Without it the build is ad-hoc signed, which Safari loads locally with no per-launch toggle. Use a team id for a stable identity you can distribute').option('--macos-only [boolean]', 'generate a macOS-only Safari Xcode project (safari targets only). Pass `false` for a universal macOS + iOS project. Defaults to `true`', parseOptionalBoolean).option('--force-regenerate', 'regenerate the Safari Xcode project even when up to date (safari targets only)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).action(async (pathOrRemoteUrl, { browser: cliBrowser, ...buildOptions })=>{
9408
+ program.command('build').arguments('[project-name]').usage('[path-to-remote-extension] [options]').description(commandDescriptions.build).option(`--browser <${BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds a Safari app via Xcode (macOS only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `false`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--zip [boolean]', 'whether or not to compress the extension into a ZIP file. Defaults to `false`', parseOptionalBoolean).option('--zip-source [boolean]', 'whether or not to include the source files in the ZIP file. Defaults to `false`', parseOptionalBoolean).option('--zip-filename <string>', 'specify the name of the ZIP file. Defaults to the extension name and version').option('--silent [boolean]', 'suppress the build summary output. Defaults to `false`', parseOptionalBoolean).option('--addon-lint [boolean]', 'check Firefox builds against addons.mozilla.org rules with addons-linter when it is installed. Findings print as warnings. Defaults to `true`', parseOptionalBoolean).option('--no-addon-lint', 'skip the addons.mozilla.org lint of Firefox builds').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--mode <development|production|none>', 'bundler mode override (also sets NODE_ENV). Defaults to `production`').option('--open [boolean]', 'open the built Safari app after packaging (safari targets only). Defaults to `false`', parseOptionalBoolean).option('--app-name <name>', 'override the Safari app name (safari targets only). Defaults to the manifest `name`').option('--bundle-id <reverse.dns>', 'set a user-owned Safari bundle identifier (safari targets only). Defaults to a generated dev.extensionjs.* id shared by every build of the same source, so set your own before the first submission').option('--development-team <id>', 'sign the Safari app with an Apple Developer team id (safari targets only). Without it the build is ad-hoc signed, which Safari loads locally with no per-launch toggle. Use a team id for a stable identity you can distribute').option('--macos-only [boolean]', 'generate a macOS-only Safari Xcode project (safari targets only). Pass `false` for a universal macOS + iOS project. Defaults to `true`', parseOptionalBoolean).option('--force-regenerate', 'regenerate the Safari Xcode project even when up to date (safari targets only)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).action(async (pathOrRemoteUrl, { browser: cliBrowser, ...buildOptions })=>{
9268
9409
  const browser = cliBrowser ?? await resolveConfigBrowser(pathOrRemoteUrl || process.cwd(), 'build') ?? 'chromium';
9269
9410
  if (buildOptions.debug || buildOptions.author || buildOptions.authorMode) {
9270
9411
  process.env.EXTENSION_DEBUG = '1';
@@ -9826,37 +9967,45 @@ Cross-browser compatibility
9826
9967
  async function waitForReadyContract(options) {
9827
9968
  const readyPath = sessionReadyPath(options.bridge, options.projectPath, options.browser);
9828
9969
  const start = Date.now();
9970
+ let lastReadFailure = '';
9829
9971
  while(Date.now() - start < options.timeoutMs){
9830
- if (external_node_fs_.existsSync(readyPath)) try {
9831
- const payload = JSON.parse(external_node_fs_.readFileSync(readyPath, 'utf8'));
9832
- const isLive = isProcessLikelyAlive(payload.pid);
9833
- const isFresh = isFreshContractPayload(payload);
9834
- if (payload.command !== options.command) {
9835
- await new Promise((resolve)=>setTimeout(resolve, 250));
9836
- continue;
9972
+ if (external_node_fs_.existsSync(readyPath)) {
9973
+ let payload = null;
9974
+ try {
9975
+ payload = JSON.parse(external_node_fs_.readFileSync(readyPath, 'utf8'));
9976
+ lastReadFailure = '';
9977
+ } catch (error) {
9978
+ payload = null;
9979
+ lastReadFailure = error instanceof Error ? error.message : String(error);
9837
9980
  }
9838
- if (!isLive) {
9839
- if ('start' !== options.command || !isFresh) {
9981
+ if (payload) {
9982
+ const isLive = isProcessLikelyAlive(payload.pid);
9983
+ const isFresh = isFreshContractPayload(payload);
9984
+ if (payload.command !== options.command) {
9840
9985
  await new Promise((resolve)=>setTimeout(resolve, 250));
9841
9986
  continue;
9842
9987
  }
9843
- }
9844
- if ('ready' === payload.status) return payload;
9845
- if ('error' === payload.status) {
9846
- if (!isLive && 'start' !== options.command) {
9847
- await new Promise((resolve)=>setTimeout(resolve, 250));
9848
- continue;
9988
+ if (!isLive) {
9989
+ if ('start' !== options.command || !isFresh) {
9990
+ await new Promise((resolve)=>setTimeout(resolve, 250));
9991
+ continue;
9992
+ }
9993
+ }
9994
+ if ('ready' === payload.status) return payload;
9995
+ if ('error' === payload.status) {
9996
+ if (!isLive && 'start' !== options.command) {
9997
+ await new Promise((resolve)=>setTimeout(resolve, 250));
9998
+ continue;
9999
+ }
10000
+ const detail = payload.message || payload.errors?.[0] || 'unknown error';
10001
+ throw new WaitModeError(String(detail), messaging.Lp.E_INTERNAL);
9849
10002
  }
9850
- const detail = payload.message || payload.errors?.[0] || 'unknown error';
9851
- throw new WaitModeError(String(detail), messaging.Lp.E_INTERNAL);
9852
10003
  }
9853
- } catch (error) {
9854
- if (error instanceof Error) throw error;
9855
- throw new Error(String(error));
9856
10004
  }
9857
10005
  await new Promise((resolve)=>setTimeout(resolve, 250));
9858
10006
  }
9859
- throw new WaitModeError(`Timed out waiting for ready contract at ${readyPath} (${options.timeoutMs} ms)`, messaging.Lp.E_READY_TIMEOUT);
10007
+ const timeoutDetail = lastReadFailure ? `. The last read of the file failed to parse as JSON: ${lastReadFailure}` : '';
10008
+ throw new WaitModeError(`Timed out waiting for ready contract at ${readyPath} (${options.timeoutMs} ms)${timeoutDetail}`, messaging.Lp.E_READY_TIMEOUT);
9860
10009
  }
9861
10010
  async function runWaitMode(options) {
9862
10011
  if (isHttpUrl(options.pathOrRemoteUrl)) throw new WaitModeError('--wait requires a local project path (remote URLs are not supported)', messaging.Lp.E_ARGS);
@@ -9900,7 +10049,7 @@ Cross-browser compatibility
9900
10049
  return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 8080;
9901
10050
  }
9902
10051
  function registerDevCommand(program) {
9903
- program.command('dev').arguments('[project-path|remote-url]').usage('[project-path|remote-url] [options]').description(commandDescriptions.dev).addHelpText('after', "\nAdditional options:\n --no-browser stop the browser launch, the dev server still starts\n --no-reload emit a dev-mode dist without the content-script reload runtime, tabs need a manual reload to see changes\n --wait wait for ready contract and exit, pair with --output json for machine output\n" + BROWSER_LAUNCH_HELP_FOOTER).option('--profile <path-to-file | boolean>', 'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile').option(`-b, --browser <${BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds and opens a Safari app via Xcode (macOS only)').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').addOption(geckoBinaryOption()).addOption(firefoxBinaryAliasOption()).option('--safari-binary <path-to-binary>', 'specify the Safari binary to open after packaging (safari targets only)').option('--app-name <name>', 'override the Safari app name (safari targets only). Defaults to the manifest `name`').option('--bundle-id <reverse.dns>', 'set a user-owned Safari bundle identifier (safari targets only). Defaults to a generated dev.extensionjs.* id').option('--development-team <id>', 'sign the Safari app with an Apple Developer team id (safari targets only). Without it the build is ad-hoc signed, which Safari loads locally with no per-launch toggle. Use a team id for a stable identity you can distribute').option('--macos-only [boolean]', 'generate a macOS-only Safari Xcode project (safari targets only). Pass `false` for a universal macOS + iOS project. Defaults to `true`', parseOptionalBoolean).option('--force-regenerate', 'regenerate the Safari Xcode project even when up to date (safari targets only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `true`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--no-open', NO_OPEN_FLAG_DESCRIPTION).option('--starting-url <url>', 'specify the starting URL for the browser. Defaults to `undefined`').option('--port <port>', 'specify the port to use for the development server. Defaults to `8080`').option('--host <host>', 'specify the host to bind the dev server to. Use 0.0.0.0 for Docker/devcontainers. Defaults to `127.0.0.1`').option('--public-host <host>', 'connectable host the browser (HMR + reload bridge) dials when it differs from the bind host (e.g. a remote/devcontainer). Defaults to the bind host, or 127.0.0.1 when bound to 0.0.0.0').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--wait [boolean]', 'wait for dist/extension-js/<browser>/ready.json and exit', parseOptionalBoolean).option('--wait-timeout <ms>', 'timeout in milliseconds when using --wait (default: 60000)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--wait-format <pretty|json>').hideHelp()).addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).option('--allow-control', 'enable the agent-bridge control channel for bounded act (storage/reload/open): see `extension reload|storage|open`').option('--allow-eval', 'additionally enable `extension eval` (implies --allow-control, runs arbitrary code in a context, writes a 0600 session token)').option('--parent-pid <pid>', 'exit when the given process dies. For wrappers that spawn `extension dev`, so a leaked dev server can never outlive its owner').action(async (pathOrRemoteUrl, options, command)=>{
10052
+ program.command('dev').arguments('[project-path|remote-url]').usage('[project-path|remote-url] [options]').description(commandDescriptions.dev).addHelpText('after', "\nAdditional options:\n --no-browser stop the browser launch, the dev server still starts\n --no-reload emit a dev-mode dist without the content-script reload runtime, tabs need a manual reload to see changes\n --wait wait for ready contract and exit, pair with --output json for machine output\n" + BROWSER_LAUNCH_HELP_FOOTER).option('--profile <path-to-file | boolean>', 'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile').option(`-b, --browser <${BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds and opens a Safari app via Xcode (macOS only)').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').addOption(geckoBinaryOption()).addOption(firefoxBinaryAliasOption()).option('--safari-binary <path-to-binary>', 'specify the Safari binary to open after packaging (safari targets only)').option('--app-name <name>', 'override the Safari app name (safari targets only). Defaults to the manifest `name`').option('--bundle-id <reverse.dns>', 'set a user-owned Safari bundle identifier (safari targets only). Defaults to a generated dev.extensionjs.* id shared by every build of the same source, so set your own before the first submission').option('--development-team <id>', 'sign the Safari app with an Apple Developer team id (safari targets only). Without it the build is ad-hoc signed, which Safari loads locally with no per-launch toggle. Use a team id for a stable identity you can distribute').option('--macos-only [boolean]', 'generate a macOS-only Safari Xcode project (safari targets only). Pass `false` for a universal macOS + iOS project. Defaults to `true`', parseOptionalBoolean).option('--force-regenerate', 'regenerate the Safari Xcode project even when up to date (safari targets only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `true`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--no-open', NO_OPEN_FLAG_DESCRIPTION).option('--starting-url <url>', 'specify the starting URL for the browser. Defaults to `undefined`').option('--port <port>', 'specify the port to use for the development server. Defaults to `8080`').option('--host <host>', 'specify the host to bind the dev server to. Use 0.0.0.0 for Docker/devcontainers. Defaults to `127.0.0.1`').option('--public-host <host>', 'connectable host the browser (HMR + reload bridge) dials when it differs from the bind host (e.g. a remote/devcontainer). Defaults to the bind host, or 127.0.0.1 when bound to 0.0.0.0').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--wait [boolean]', 'wait for dist/extension-js/<browser>/ready.json and exit', parseOptionalBoolean).option('--wait-timeout <ms>', 'timeout in milliseconds when using --wait (default: 60000)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--wait-format <pretty|json>').hideHelp()).addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).option('--allow-control', 'enable the agent-bridge control channel for bounded act (storage/reload/open): see `extension reload|storage|open`').option('--allow-eval', 'additionally enable `extension eval` (implies --allow-control, runs arbitrary code in a context, writes a 0600 session token)').option('--parent-pid <pid>', 'exit when the given process dies. For wrappers that spawn `extension dev`, so a leaked dev server can never outlive its owner').action(async (pathOrRemoteUrl, options, command)=>{
9904
10053
  const { browser: cliBrowser, ...devOptions } = options;
9905
10054
  const browser = cliBrowser ?? await resolveConfigBrowser(pathOrRemoteUrl || process.cwd(), 'dev') ?? 'chromium';
9906
10055
  if (devOptions.debug || devOptions.author || devOptions.authorMode) {
@@ -10356,19 +10505,19 @@ Cross-browser compatibility
10356
10505
  });
10357
10506
  return results;
10358
10507
  }
10508
+ function checkGlyph(status) {
10509
+ if ('pass' === status) return external_pintor_default().green('✓');
10510
+ if ('fail' === status) return external_pintor_default().red('✗');
10511
+ if ('warn' === status) return external_pintor_default().brightYellow('!');
10512
+ return external_pintor_default().gray('–');
10513
+ }
10359
10514
  function printPretty(results, browser) {
10360
10515
  const passes = results.filter((r)=>'pass' === r.status).length;
10361
- const glyph = {
10362
- pass: '✓',
10363
- fail: '✗',
10364
- warn: '!',
10365
- skip: '–'
10366
- };
10367
- console.log(`doctor (${browser}), ${passes}/${results.length} checks passed`);
10516
+ console.log(doctorHeader(browser, passes, results.length));
10368
10517
  const width = Math.max(...results.map((r)=>r.check.length));
10369
- for (const r of results)console.log(` ${glyph[r.status]} ${r.check.padEnd(width)} ${r.detail}`);
10518
+ for (const r of results)console.log(` ${checkGlyph(r.status)} ${r.check.padEnd(width)} ${r.detail}`);
10370
10519
  const advisory = results.find((r)=>'fail' === r.status) ?? results.find((r)=>'warn' === r.status);
10371
- if (advisory?.remediation) console.log(`\n${advisory.check}: ${advisory.remediation}`);
10520
+ if (advisory?.remediation) console.log(doctorRemedy(advisory.check, advisory.remediation));
10372
10521
  }
10373
10522
  const CHECK_CODES = {
10374
10523
  'ready-contract': messaging.Lp.E_SESSION_NOT_FOUND,