extension 3.17.0 → 3.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/322.cjs +89 -11
  2. package/dist/browsers.cjs +263 -206
  3. package/dist/cli.cjs +1312 -413
  4. package/dist/extension/browsers/browsers-lib/messages.d.ts +17 -0
  5. package/dist/extension/browsers/browsers-lib/output-binaries-resolver.d.ts +1 -1
  6. package/dist/extension/browsers/browsers-lib/shared-utils.d.ts +1 -0
  7. package/dist/extension/browsers/browsers-lib/wsl-support.d.ts +3 -0
  8. package/dist/extension/browsers/browsers-types.d.ts +1 -1
  9. package/dist/extension/browsers/run-chromium/chromium-launch/process-handlers.d.ts +8 -1
  10. package/dist/extension/browsers/run-chromium/chromium-launch/wsl-support.d.ts +2 -9
  11. package/dist/extension/browsers/run-chromium/chromium-source-inspection/cdp-client.d.ts +6 -0
  12. package/dist/extension/browsers/run-chromium/chromium-source-inspection/cdp-extension-controller/logging.d.ts +0 -5
  13. package/dist/extension/browsers/run-chromium/chromium-source-inspection/index.d.ts +1 -0
  14. package/dist/extension/browsers/run-firefox/firefox-launch/binary-detector.d.ts +0 -4
  15. package/dist/extension/browsers/run-firefox/firefox-launch/process-handlers.d.ts +5 -1
  16. package/dist/extension/browsers/run-firefox/firefox-launch/wsl-support.d.ts +2 -4
  17. package/dist/extension/browsers/run-firefox/firefox-source-inspection/remote-firefox/evaluate.d.ts +1 -0
  18. package/dist/extension/browsers/run-safari/safari-launch/dry-run.d.ts +1 -0
  19. package/dist/extension/browsers/run-safari/safari-launch/index.d.ts +5 -0
  20. package/dist/extension/browsers/run-safari/safari-launch/safari-config.d.ts +16 -0
  21. package/dist/extension/browsers/run-safari/safari-launch/toolchain.d.ts +10 -0
  22. package/dist/extension/browsers/run-safari/safari-types.d.ts +18 -0
  23. package/dist/extension/commands/act.d.ts +6 -0
  24. package/dist/extension/commands/dev-wait.d.ts +2 -0
  25. package/dist/extension/commands/logs.d.ts +2 -0
  26. package/dist/extension/commands/publish.d.ts +15 -0
  27. package/dist/extension/helpers/extension-develop-runtime.d.ts +5 -0
  28. package/dist/extension/helpers/messages.d.ts +18 -1
  29. package/dist/extension/helpers/telemetry.d.ts +1 -0
  30. package/dist/extension/helpers/vendors.d.ts +2 -1
  31. package/package.json +5 -5
package/dist/cli.cjs CHANGED
@@ -455,8 +455,10 @@ var __webpack_modules__ = {
455
455
  $wD: ()=>bestEffortBannerPrintFailed,
456
456
  A9Y: ()=>firefoxLaunchCalled,
457
457
  AGJ: ()=>enhancedProcessManagementForceKill,
458
+ Ajw: ()=>safariDryRunXcodebuild,
458
459
  CYH: ()=>devChromiumDebugPort,
459
460
  Cny: ()=>chromeProcessError,
461
+ Csg: ()=>safariToolchainMissing,
460
462
  DhR: ()=>invalidGeckoBinaryPath,
461
463
  DyD: ()=>firefoxRdpRuntimeReinjectionResult,
462
464
  E8B: ()=>chromiumDryRunFlags,
@@ -465,10 +467,12 @@ var __webpack_modules__ = {
465
467
  F41: ()=>enhancedProcessManagementUncaughtException,
466
468
  FFz: ()=>cdpClientFoundTargets,
467
469
  FmF: ()=>connectionClosedError,
470
+ G3E: ()=>safariRequiresMacOS,
468
471
  GqE: ()=>locatingBrowser,
469
472
  HHw: ()=>messageWithoutSenderError,
470
473
  IhA: ()=>browserNotInstalledError,
471
474
  KK1: ()=>cdpProtocolEventHandlerError,
475
+ KP6: ()=>safariBuildCalled,
472
476
  LDU: ()=>firefoxDryRunBinary,
473
477
  M3V: ()=>cdpClientConnected,
474
478
  MGf: ()=>prettyPuppeteerInstallGuidance,
@@ -479,6 +483,8 @@ var __webpack_modules__ = {
479
483
  Nk3: ()=>chromiumDryRunNotLaunching,
480
484
  Q0p: ()=>chromiumDryRunBinary,
481
485
  Qiq: ()=>cdpClientLoadEventTimeout,
486
+ Qrh: ()=>safariFailed,
487
+ Rl_: ()=>safariBuilt,
482
488
  Spm: ()=>invalidChromiumBinaryPath,
483
489
  TFX: ()=>enhancedProcessManagementCleanupError,
484
490
  Ter: ()=>cdpClientTargetWebSocketUrlStored,
@@ -487,43 +493,54 @@ var __webpack_modules__ = {
487
493
  WVu: ()=>addonInstallError,
488
494
  XHr: ()=>enhancedProcessManagementTerminating,
489
495
  XOv: ()=>firefoxRdpClientTestingEvaluation,
496
+ Xiv: ()=>safariXcodeRequired,
490
497
  Z5i: ()=>cdpClientExtensionInfoFailed,
491
498
  _D4: ()=>browserLaunchError,
492
499
  aIt: ()=>devChromeProfilePath,
493
500
  bvI: ()=>cdpUnifiedExtensionLog,
494
501
  cD1: ()=>targetActorHasActiveRequestError,
502
+ cFJ: ()=>safariRebuilt,
495
503
  ccn: ()=>cdpClientExtensionReloadFailed,
496
504
  dNY: ()=>firefoxNoBinaryArgsFound,
497
505
  dkE: ()=>generalBrowserError,
498
506
  dwQ: ()=>devFirefoxProfilePath,
499
507
  eO_: ()=>firefoxInspectSourceNonFatal,
500
508
  e_P: ()=>runningInDevelopment,
509
+ fLj: ()=>safariDryRunConverter,
510
+ fOJ: ()=>safariNextSteps,
501
511
  fRy: ()=>cdpClientConnectionError,
512
+ fdc: ()=>safariRegistered,
513
+ fwU: ()=>safariOpening,
502
514
  hE8: ()=>cdpClientConnectionClosed,
503
515
  io8: ()=>emptyLine,
504
516
  ivb: ()=>firefoxBinaryArgsExtracted,
505
517
  jPE: ()=>rdpInvalidRequestPayload,
518
+ jRH: ()=>safariBuilding,
506
519
  jk4: ()=>requireGeckoBinaryForGeckoBased,
507
520
  k6b: ()=>firefoxDryRunConfig,
508
521
  l9J: ()=>parsingPacketError,
509
522
  lID: ()=>firefoxRdpReloadCapabilitySummary,
523
+ l_B: ()=>safariConverted,
510
524
  nek: ()=>requireChromiumBinaryForChromiumBased,
511
525
  nnm: ()=>chromeProcessExited,
512
526
  p8m: ()=>cdpClientAttachedToTarget,
513
527
  pdv: ()=>devFirefoxDebugPort,
514
528
  qNy: ()=>messagingClientClosedError,
529
+ qYJ: ()=>safariNotYetRegistered,
515
530
  qnG: ()=>firefoxDryRunNotLaunching,
516
531
  r0s: ()=>cdpPendingRejectFailed,
517
532
  sYQ: ()=>creatingUserProfile,
518
533
  sew: ()=>separatorLine,
519
534
  tUu: ()=>firefoxRdpClientFailedToGetMainHTML,
535
+ uKN: ()=>safariConverting,
520
536
  wXK: ()=>cdpAutoAttachSetupFailed,
521
537
  wk1: ()=>chromeFailedToSpawn,
522
538
  xlM: ()=>cdpClientExtensionLoadFailed,
523
539
  xxG: ()=>cdpFailedToHandleMessage,
524
540
  xyq: ()=>chromeInitializingEnhancedReload,
525
541
  y7j: ()=>skippingBrowserLaunchDueToCompileErrors,
526
- yEr: ()=>cdpClientExtensionUnloadFailed
542
+ yEr: ()=>cdpClientExtensionUnloadFailed,
543
+ ycc: ()=>safariDryRunNotBuilding
527
544
  });
528
545
  var path__rspack_import_0 = __webpack_require__("path");
529
546
  var fs__rspack_import_1 = __webpack_require__("fs");
@@ -787,6 +804,63 @@ var __webpack_modules__ = {
787
804
  function firefoxDryRunConfig(cfg) {
788
805
  return `${getLoggingPrefix('info')} [browser] Config: ${pintor__rspack_import_4_default().gray(cfg)}`;
789
806
  }
807
+ function safariBuildCalled() {
808
+ return `${getLoggingPrefix('info')} Safari build requested.`;
809
+ }
810
+ function prettyPlatform(platform) {
811
+ if ('win32' === platform) return 'Windows';
812
+ if ('linux' === platform) return 'Linux';
813
+ return platform;
814
+ }
815
+ function safariRequiresMacOS(platform) {
816
+ return `${getLoggingPrefix('warn')} Safari extensions can only be built on macOS.\nDetected ${pintor__rspack_import_4_default().gray(prettyPlatform(platform))} — skipping Safari packaging. The web-extension build in ${pintor__rspack_import_4_default().yellow('dist/safari')} is still complete and can be packaged later on a Mac with Xcode.`;
817
+ }
818
+ function safariXcodeRequired(developerDir) {
819
+ const current = developerDir ? `${pintor__rspack_import_4_default().gray('Active toolchain:')} ${pintor__rspack_import_4_default().underline(developerDir)}` : `${pintor__rspack_import_4_default().gray('No active developer directory was found.')}`;
820
+ return `${getLoggingPrefix('error')} Safari packaging needs the full Xcode app (not just the Command Line Tools).\n${pintor__rspack_import_4_default().red('NOT FOUND')} ${pintor__rspack_import_4_default().underline('safari-web-extension-converter')}\n${current}\n\nTo enable Safari builds:\n- Install ${pintor__rspack_import_4_default().yellow('Xcode')} from the Mac App Store, then\n- Point the toolchain at it: ${pintor__rspack_import_4_default().blue('sudo xcode-select --switch')} ${pintor__rspack_import_4_default().gray('/Applications/Xcode.app')}\n- Finish setup once: ${pintor__rspack_import_4_default().blue('xcodebuild -runFirstLaunch')}\n\nPrefer to keep building now? Target another browser via ${pintor__rspack_import_4_default().blue('--browser')} ${pintor__rspack_import_4_default().gray('<chrome|edge|firefox>')}.`;
821
+ }
822
+ function safariToolchainMissing(tool) {
823
+ return `${getLoggingPrefix('error')} Safari packaging tool not found: ${pintor__rspack_import_4_default().underline(tool)}\nYour Xcode install looks incomplete. Try ${pintor__rspack_import_4_default().blue('xcodebuild -runFirstLaunch')}, or reinstall Xcode from the Mac App Store.`;
824
+ }
825
+ function safariConverting(extensionDir) {
826
+ return `${getLoggingPrefix('info')} Converting web extension into a Safari app project from ${pintor__rspack_import_4_default().underline(extensionDir)}`;
827
+ }
828
+ function safariConverted(projectDir) {
829
+ return `${getLoggingPrefix('success')} Generated Safari Xcode project at ${pintor__rspack_import_4_default().underline(projectDir)}`;
830
+ }
831
+ function safariBuilding(scheme) {
832
+ return `${getLoggingPrefix('info')} Building Safari app with xcodebuild (scheme: ${pintor__rspack_import_4_default().gray(scheme)})`;
833
+ }
834
+ function safariBuilt(appPath) {
835
+ return `${getLoggingPrefix('success')} Built Safari app at ${pintor__rspack_import_4_default().underline(appPath)}`;
836
+ }
837
+ function safariOpening(target) {
838
+ return `${getLoggingPrefix('info')} Opening ${pintor__rspack_import_4_default().underline(target)}`;
839
+ }
840
+ function safariFailed(error) {
841
+ return `${getLoggingPrefix('error')} Safari build failed:\n${pintor__rspack_import_4_default().red(errorDetail(error))}`;
842
+ }
843
+ function safariDryRunNotBuilding() {
844
+ return `${getLoggingPrefix('info')} [browser] Dry run: not building Safari app`;
845
+ }
846
+ function safariDryRunConverter(cmd) {
847
+ return `${getLoggingPrefix('info')} [browser] Converter: ${pintor__rspack_import_4_default().gray(cmd)}`;
848
+ }
849
+ function safariDryRunXcodebuild(cmd) {
850
+ return `${getLoggingPrefix('info')} [browser] xcodebuild: ${pintor__rspack_import_4_default().gray(cmd)}`;
851
+ }
852
+ function safariNextSteps(appName) {
853
+ return `${getLoggingPrefix('info')} One-time setup to load ${pintor__rspack_import_4_default().brightBlue(appName)} in Safari:\n ${pintor__rspack_import_4_default().gray('1.')} Safari ▸ Settings ▸ Advanced ▸ check ${pintor__rspack_import_4_default().yellow('“Show features for web developers”')}\n ${pintor__rspack_import_4_default().gray('2.')} Safari ▸ Develop ▸ ${pintor__rspack_import_4_default().yellow('Allow Unsigned Extensions')} ${pintor__rspack_import_4_default().gray('(resets each launch)')}\n ${pintor__rspack_import_4_default().gray('3.')} Safari ▸ Settings ▸ Extensions ▸ turn on ${pintor__rspack_import_4_default().yellow(appName)}\n ${pintor__rspack_import_4_default().gray('→')} The app window that just opened can also take you there.`;
854
+ }
855
+ function safariRegistered(appName) {
856
+ return `${getLoggingPrefix('success')} Safari recognizes ${pintor__rspack_import_4_default().brightBlue(appName)} — finish enabling it with the steps above.`;
857
+ }
858
+ function safariNotYetRegistered(appName) {
859
+ return `${getLoggingPrefix('warn')} Safari hasn't picked up ${pintor__rspack_import_4_default().brightBlue(appName)} yet. Open the app once, then check Safari ▸ Settings ▸ Extensions.`;
860
+ }
861
+ function safariRebuilt(appName) {
862
+ return `${getLoggingPrefix('success')} Rebuilt ${pintor__rspack_import_4_default().brightBlue(appName)} — reload the page (or toggle the extension) in Safari to see changes.`;
863
+ }
790
864
  function cdpClientFoundTargets(count) {
791
865
  return `${getLoggingPrefix('info')} Chrome found ${pintor__rspack_import_4_default().gray(count.toString())} targets`;
792
866
  }
@@ -999,6 +1073,7 @@ var __webpack_modules__ = {
999
1073
  function managedBrowserCacheEnv(cacheRoot, browser) {
1000
1074
  const root = String(cacheRoot || '').trim();
1001
1075
  if (!root) return {};
1076
+ if ('safari' === browser || 'webkit-based' === browser) return {};
1002
1077
  if ('chrome' === browser) return {
1003
1078
  PUPPETEER_CACHE_DIR: path__rspack_import_1.join(root, 'chrome', 'chrome')
1004
1079
  };
@@ -1034,19 +1109,13 @@ var __webpack_modules__ = {
1034
1109
  const versionDirs = entries.filter((entry)=>entry.isDirectory() && versionDirPattern.test(entry.name)).map((entry)=>path__rspack_import_1.join(root, entry.name));
1035
1110
  for (const dir of versionDirs)candidateFiles.push(...buildCandidates(dir, browser));
1036
1111
  } catch {}
1037
- let matched = false;
1038
1112
  for (const candidate of candidateFiles)try {
1039
- if (candidate && fs__rspack_import_0.existsSync(candidate)) {
1040
- matched = true;
1041
- return candidate;
1042
- }
1113
+ if (candidate && fs__rspack_import_0.existsSync(candidate)) return candidate;
1043
1114
  } catch {}
1044
- if (!matched) {
1045
- const names = executableNamesFor(browser);
1046
- for (const root of scanRoots){
1047
- const found = findExecutableUnder(root, names, 6);
1048
- if (found) return found;
1049
- }
1115
+ const names = executableNamesFor(browser);
1116
+ for (const root of scanRoots){
1117
+ const found = findExecutableUnder(root, names, 6);
1118
+ if (found) return found;
1050
1119
  }
1051
1120
  return null;
1052
1121
  }
@@ -1224,7 +1293,8 @@ var __webpack_modules__ = {
1224
1293
  RE: ()=>markManagedEphemeralProfile,
1225
1294
  aY: ()=>findAvailablePortNear,
1226
1295
  jl: ()=>deriveDebugPortWithInstance,
1227
- ov: ()=>filterBrowserFlags
1296
+ ov: ()=>filterBrowserFlags,
1297
+ sW: ()=>removeManagedEphemeralProfile
1228
1298
  });
1229
1299
  var fs__rspack_import_0 = __webpack_require__("fs");
1230
1300
  var os__rspack_import_1 = __webpack_require__("os");
@@ -1342,6 +1412,18 @@ var __webpack_modules__ = {
1342
1412
  fs__rspack_import_0.writeFileSync(path__rspack_import_2.join(profilePath, MANAGED_EPHEMERAL_PROFILE_MARKER), 'managed-ephemeral-profile\n', 'utf8');
1343
1413
  } catch {}
1344
1414
  }
1415
+ function removeManagedEphemeralProfile(profilePath) {
1416
+ try {
1417
+ if (!profilePath) return;
1418
+ if ('dev' === path__rspack_import_2.basename(profilePath)) return;
1419
+ const markerPath = path__rspack_import_2.join(profilePath, MANAGED_EPHEMERAL_PROFILE_MARKER);
1420
+ if (!fs__rspack_import_0.existsSync(markerPath)) return;
1421
+ fs__rspack_import_0.rmSync(profilePath, {
1422
+ recursive: true,
1423
+ force: true
1424
+ });
1425
+ } catch {}
1426
+ }
1345
1427
  function cleanupOldTempProfiles(baseDir, excludeBasename, maxAgeHours = 12) {
1346
1428
  try {
1347
1429
  if (!fs__rspack_import_0.existsSync(baseDir)) return;
@@ -1372,6 +1454,37 @@ var __webpack_modules__ = {
1372
1454
  } catch {}
1373
1455
  }
1374
1456
  },
1457
+ "./browsers/browsers-lib/wsl-support.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1458
+ __webpack_require__.d(__webpack_exports__, {
1459
+ EV: ()=>isWslEnv,
1460
+ f7: ()=>normalizeBinaryPathForWsl,
1461
+ qc: ()=>hasGuiDisplay
1462
+ });
1463
+ var os__rspack_import_0 = __webpack_require__("os");
1464
+ function isWslEnv() {
1465
+ if ('linux' !== process.platform) return false;
1466
+ const hasWslEnv = Boolean(String(process.env.WSL_DISTRO_NAME || '').trim() || String(process.env.WSL_INTEROP || '').trim() || String(process.env.WSLENV || '').trim());
1467
+ if (hasWslEnv) return true;
1468
+ return /microsoft/i.test(os__rspack_import_0.release());
1469
+ }
1470
+ function hasGuiDisplay() {
1471
+ const display = String(process.env.DISPLAY || '').trim();
1472
+ const waylandDisplay = String(process.env.WAYLAND_DISPLAY || '').trim();
1473
+ return display.length > 0 || waylandDisplay.length > 0;
1474
+ }
1475
+ function normalizeBinaryPathForWsl(input) {
1476
+ let value = String(input || '').trim();
1477
+ if (!value) return value;
1478
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
1479
+ if (!isWslEnv()) return value;
1480
+ if (/^[a-zA-Z]:[\\/]/.test(value)) {
1481
+ const drive = value[0].toLowerCase();
1482
+ const rest = value.slice(2).replace(/\\/g, '/').replace(/^\/+/, '');
1483
+ return `/mnt/${drive}/${rest}`;
1484
+ }
1485
+ return value;
1486
+ }
1487
+ },
1375
1488
  "./browsers/index.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1376
1489
  __webpack_require__.d(__webpack_exports__, {
1377
1490
  launchBrowser: ()=>launchBrowser
@@ -1897,7 +2010,9 @@ var __webpack_modules__ = {
1897
2010
  '--disable-dev-shm-usage'
1898
2011
  ];
1899
2012
  const baseFlags = [
1900
- `--load-extension=${extensionsToLoad.join()}`,
2013
+ ...extensionsToLoad.length ? [
2014
+ `--load-extension=${extensionsToLoad.join()}`
2015
+ ] : [],
1901
2016
  ...userProfilePath ? [
1902
2017
  `--user-data-dir=${userProfilePath}`
1903
2018
  ] : [],
@@ -1914,53 +2029,81 @@ var __webpack_modules__ = {
1914
2029
  return baseFlags;
1915
2030
  }
1916
2031
  var external_child_process_ = __webpack_require__("child_process");
1917
- function setupProcessSignalHandlers(browser, child, cleanupInstance) {
1918
- const cleanup = ()=>{
1919
- try {
1920
- if ('true' === process.env.EXTENSION_AUTHOR_MODE) console.log(messages.VkK(browser));
1921
- if (child && !child.killed) {
1922
- if ('win32' === process.platform) try {
1923
- (0, external_child_process_.spawn)('taskkill', [
1924
- '/PID',
1925
- String(child.pid),
1926
- '/T',
1927
- '/F'
1928
- ], {
1929
- stdio: 'ignore',
1930
- windowsHide: true
1931
- }).on('error', ()=>{});
1932
- } catch {}
1933
- if ('true' === process.env.EXTENSION_AUTHOR_MODE) console.log(messages.XHr(browser));
1934
- child.kill('SIGTERM');
1935
- setTimeout(()=>{
1936
- if (child && !child.killed) {
1937
- if ('true' === process.env.EXTENSION_AUTHOR_MODE) console.log(messages.AGJ(browser));
1938
- child.kill('SIGKILL');
1939
- }
1940
- }, 5000);
1941
- }
1942
- cleanupInstance();
1943
- } catch (error) {
1944
- console.error(messages.TFX(browser, error));
2032
+ const activeInstances = new Set();
2033
+ let globalHandlersInstalled = false;
2034
+ function cleanupOne(instance) {
2035
+ if (instance.isCleaningUp) return;
2036
+ instance.isCleaningUp = true;
2037
+ const { browser, child, cleanupInstance } = instance;
2038
+ try {
2039
+ if ('true' === process.env.EXTENSION_AUTHOR_MODE) console.log(messages.VkK(browser));
2040
+ if (child && !child.killed) {
2041
+ if ('win32' === process.platform) try {
2042
+ (0, external_child_process_.spawn)('taskkill', [
2043
+ '/PID',
2044
+ String(child.pid),
2045
+ '/T',
2046
+ '/F'
2047
+ ], {
2048
+ stdio: 'ignore',
2049
+ windowsHide: true
2050
+ }).on('error', ()=>{});
2051
+ } catch {}
2052
+ if ('true' === process.env.EXTENSION_AUTHOR_MODE) console.log(messages.XHr(browser));
2053
+ child.kill('SIGTERM');
2054
+ const killTimer = setTimeout(()=>{
2055
+ if (child && !child.killed) {
2056
+ if ('true' === process.env.EXTENSION_AUTHOR_MODE) console.log(messages.AGJ(browser));
2057
+ child.kill('SIGKILL');
2058
+ }
2059
+ }, 5000);
2060
+ killTimer.unref?.();
1945
2061
  }
1946
- };
1947
- process.on('SIGINT', cleanup);
1948
- process.on('SIGTERM', cleanup);
1949
- process.on('SIGHUP', cleanup);
1950
- process.on('exit', cleanup);
2062
+ cleanupInstance();
2063
+ } catch (error) {
2064
+ console.error(messages.TFX(browser, error));
2065
+ }
2066
+ }
2067
+ function cleanupAll() {
2068
+ for (const instance of activeInstances)cleanupOne(instance);
2069
+ }
2070
+ function firstBrowserLabel() {
2071
+ for (const instance of activeInstances)return instance.browser;
2072
+ return 'chrome';
2073
+ }
2074
+ function installGlobalHandlersOnce() {
2075
+ if (globalHandlersInstalled) return;
2076
+ globalHandlersInstalled = true;
2077
+ process.on('SIGINT', cleanupAll);
2078
+ process.on('SIGTERM', cleanupAll);
2079
+ process.on('SIGHUP', cleanupAll);
2080
+ process.on('exit', cleanupAll);
1951
2081
  process.on('uncaughtException', (error)=>{
1952
2082
  if (isBenignSocketTeardown(error)) return;
1953
- console.error(messages.F41(browser, error));
1954
- cleanup();
2083
+ console.error(messages.F41(firstBrowserLabel(), error));
2084
+ cleanupAll();
1955
2085
  process.exit(1);
1956
2086
  });
1957
2087
  process.on('unhandledRejection', (reason)=>{
1958
2088
  if (isBenignSocketTeardown(reason)) return;
1959
- console.error(messages.N4O(browser, reason));
1960
- cleanup();
2089
+ console.error(messages.N4O(firstBrowserLabel(), reason));
2090
+ cleanupAll();
1961
2091
  process.exit(1);
1962
2092
  });
1963
2093
  }
2094
+ function setupProcessSignalHandlers(browser, child, cleanupInstance) {
2095
+ const instance = {
2096
+ browser,
2097
+ child,
2098
+ cleanupInstance,
2099
+ isCleaningUp: false
2100
+ };
2101
+ activeInstances.add(instance);
2102
+ installGlobalHandlersOnce();
2103
+ return ()=>{
2104
+ activeInstances.delete(instance);
2105
+ };
2106
+ }
1964
2107
  const BENIGN_SOCKET_ERROR_CODES = new Set([
1965
2108
  'ECONNRESET',
1966
2109
  'EPIPE',
@@ -1980,18 +2123,7 @@ var __webpack_modules__ = {
1980
2123
  }
1981
2124
  var extension_output_path = __webpack_require__("./browsers/run-chromium/chromium-launch/extension-output-path.ts");
1982
2125
  var manifest_readiness = __webpack_require__("./browsers/run-chromium/manifest-readiness.ts");
1983
- var external_os_ = __webpack_require__("os");
1984
- function isWslEnv() {
1985
- if ('linux' !== process.platform) return false;
1986
- const hasWslEnv = Boolean(String(process.env.WSL_DISTRO_NAME || '').trim() || String(process.env.WSL_INTEROP || '').trim() || String(process.env.WSLENV || '').trim());
1987
- if (hasWslEnv) return true;
1988
- return /microsoft/i.test(external_os_.release());
1989
- }
1990
- function hasGuiDisplay() {
1991
- const display = String(process.env.DISPLAY || '').trim();
1992
- const waylandDisplay = String(process.env.WAYLAND_DISPLAY || '').trim();
1993
- return display.length > 0 || waylandDisplay.length > 0;
1994
- }
2126
+ var wsl_support = __webpack_require__("./browsers/browsers-lib/wsl-support.ts");
1995
2127
  const LINUX_BROWSER_PATHS = {
1996
2128
  chrome: [
1997
2129
  '/opt/google/chrome/chrome',
@@ -2014,7 +2146,7 @@ var __webpack_modules__ = {
2014
2146
  ]
2015
2147
  };
2016
2148
  function resolveWslLinuxBinary(browser) {
2017
- if (!isWslEnv() || !hasGuiDisplay()) return null;
2149
+ if (!(0, wsl_support.EV)() || !(0, wsl_support.qc)()) return null;
2018
2150
  const candidates = LINUX_BROWSER_PATHS[browser] || LINUX_BROWSER_PATHS['chrome'];
2019
2151
  for (const candidate of candidates)if (external_fs_.existsSync(candidate)) return candidate;
2020
2152
  return null;
@@ -2036,26 +2168,14 @@ var __webpack_modules__ = {
2036
2168
  }
2037
2169
  function preferRealChromeBinary(binary) {
2038
2170
  if (!binary) return binary || null;
2039
- if (!isWslEnv() || !hasGuiDisplay()) return binary;
2171
+ if (!(0, wsl_support.EV)() || !(0, wsl_support.qc)()) return binary;
2040
2172
  if (!looksLikeChromeWrapperScript(binary)) return binary;
2041
2173
  const realBinary = '/opt/google/chrome/chrome';
2042
2174
  if (external_fs_.existsSync(realBinary)) return realBinary;
2043
2175
  return binary;
2044
2176
  }
2045
- function normalizeBinaryPathForWsl(input) {
2046
- let value = String(input || '').trim();
2047
- if (!value) return value;
2048
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
2049
- if (!isWslEnv()) return value;
2050
- if (/^[a-zA-Z]:[\\/]/.test(value)) {
2051
- const drive = value[0].toLowerCase();
2052
- const rest = value.slice(2).replace(/\\/g, '/').replace(/^\/+/, '');
2053
- return `/mnt/${drive}/${rest}`;
2054
- }
2055
- return value;
2056
- }
2057
2177
  function resolveWslWindowsBinary(browser) {
2058
- if (!isWslEnv()) return null;
2178
+ if (!(0, wsl_support.EV)()) return null;
2059
2179
  const chromeCandidates = [
2060
2180
  '/mnt/c/Program Files/Google/Chrome/Application/chrome.exe',
2061
2181
  '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe'
@@ -2106,7 +2226,7 @@ var __webpack_modules__ = {
2106
2226
  try {
2107
2227
  return await spawnOnce(binary);
2108
2228
  } catch (error) {
2109
- if (isWslEnv()) {
2229
+ if ((0, wsl_support.EV)()) {
2110
2230
  const fallback = resolveWslWindowsBinary(browser);
2111
2231
  if (fallback && fallback !== binary) {
2112
2232
  logger?.warn?.('[browser] WSL detected: retrying with Windows browser binary.');
@@ -2185,7 +2305,7 @@ var __webpack_modules__ = {
2185
2305
  let printedGuidance = false;
2186
2306
  const normalizePath = (p)=>{
2187
2307
  if (!p) return null;
2188
- const normalized = normalizeBinaryPathForWsl(p);
2308
+ const normalized = (0, wsl_support.f7)(p);
2189
2309
  return normalized || null;
2190
2310
  };
2191
2311
  const isUsableBinary = (p)=>Boolean(p && external_fs_.existsSync(p));
@@ -2214,7 +2334,7 @@ var __webpack_modules__ = {
2214
2334
  };
2215
2335
  browserBinaryLocation = resolveManagedBinary();
2216
2336
  let skipDetection = Boolean(browserBinaryLocation);
2217
- if (!browserBinaryLocation && isWslEnv()) {
2337
+ if (!browserBinaryLocation && (0, wsl_support.EV)()) {
2218
2338
  const linuxFallback = resolveWslLinuxBinary(browser);
2219
2339
  if (linuxFallback) {
2220
2340
  browserBinaryLocation = linuxFallback;
@@ -2262,7 +2382,7 @@ var __webpack_modules__ = {
2262
2382
  if (!located) throw new Error(getInstallGuidanceText('chrome'));
2263
2383
  const normalized = normalizePath(located || null);
2264
2384
  if (isUsableBinary(normalized)) {
2265
- if (looksOfficialChromeBinaryPath(normalized) && !isWslEnv()) {
2385
+ if (looksOfficialChromeBinaryPath(normalized) && !(0, wsl_support.EV)()) {
2266
2386
  printInstallGuidance(getInstallGuidanceText('chrome'), browser);
2267
2387
  return null;
2268
2388
  }
@@ -2276,7 +2396,7 @@ var __webpack_modules__ = {
2276
2396
  }) || null;
2277
2397
  const normalized = normalizePath(candidate || null);
2278
2398
  if (normalized) {
2279
- if (looksOfficialChromeBinaryPath(normalized) && !isWslEnv()) {
2399
+ if (looksOfficialChromeBinaryPath(normalized) && !(0, wsl_support.EV)()) {
2280
2400
  printInstallGuidance(getInstallGuidanceText('chrome'), browser);
2281
2401
  candidate = null;
2282
2402
  }
@@ -2500,7 +2620,7 @@ var __webpack_modules__ = {
2500
2620
  'pipe',
2501
2621
  'pipe'
2502
2622
  ] : 'ignore';
2503
- const normalizedBinary = normalizeBinaryPathForWsl(binary);
2623
+ const normalizedBinary = (0, wsl_support.f7)(binary);
2504
2624
  try {
2505
2625
  const child = await spawnChromiumProcess({
2506
2626
  binary: normalizedBinary,
@@ -2510,13 +2630,17 @@ var __webpack_modules__ = {
2510
2630
  logger: this.logger
2511
2631
  });
2512
2632
  if ('true' === process.env.EXTENSION_AUTHOR_MODE) this.logger.debug?.('[browser] Final Chrome flags:', launchArgs.join(' '));
2633
+ let disposeSignalHandlers;
2513
2634
  child.on('close', (code)=>{
2514
2635
  if ('true' === process.env.EXTENSION_AUTHOR_MODE) this.logger.info(messages.nnm(code || 0));
2636
+ disposeSignalHandlers?.();
2637
+ const userDataDir = launchArgs.find((arg)=>arg.startsWith('--user-data-dir='))?.slice('--user-data-dir='.length).replace(/^"|"$/g, '');
2638
+ shared_utils.sW(userDataDir);
2515
2639
  });
2516
2640
  child.on('error', (error)=>{
2517
2641
  this.logger.error(messages.Cny(error));
2518
2642
  });
2519
- setupProcessSignalHandlers(this.options?.browser, child, ()=>{});
2643
+ disposeSignalHandlers = setupProcessSignalHandlers(this.options?.browser, child, ()=>{});
2520
2644
  return child;
2521
2645
  } catch (error) {
2522
2646
  this.logger.error(messages.wk1(error));
@@ -2830,65 +2954,88 @@ var __webpack_modules__ = {
2830
2954
  var ready_message = __webpack_require__("./browsers/browsers-lib/ready-message.ts");
2831
2955
  var runtime_options = __webpack_require__("./browsers/browsers-lib/runtime-options.ts");
2832
2956
  var external_child_process_ = __webpack_require__("child_process");
2833
- function setupFirefoxProcessHandlers(browser, childRef, cleanupInstance) {
2834
- let isCleaningUp = false;
2835
- const attemptCleanup = async ()=>{
2836
- if (isCleaningUp) return;
2837
- isCleaningUp = true;
2838
- try {
2839
- if ('true' === process.env.EXTENSION_AUTHOR_MODE) console.log(messages.VkK(browser));
2840
- const child = childRef();
2841
- if (child && !child.killed) {
2842
- if ('win32' === process.platform) try {
2843
- (0, external_child_process_.spawn)('taskkill', [
2844
- '/PID',
2845
- String(child.pid),
2846
- '/T',
2847
- '/F'
2848
- ], {
2849
- stdio: 'ignore',
2850
- windowsHide: true
2851
- }).on('error', ()=>{});
2852
- } catch {}
2853
- if ('true' === process.env.EXTENSION_AUTHOR_MODE) console.log(messages.XHr(browser));
2854
- child.kill('SIGTERM');
2855
- setTimeout(()=>{
2856
- if (child && !child.killed) {
2857
- if ('true' === process.env.EXTENSION_AUTHOR_MODE) console.log(messages.AGJ(browser));
2858
- child.kill('SIGKILL');
2859
- }
2860
- }, 5000);
2861
- }
2862
- await cleanupInstance();
2863
- } catch (error) {
2864
- console.error(messages.TFX(browser, error));
2957
+ const activeInstances = new Set();
2958
+ let globalHandlersInstalled = false;
2959
+ async function attemptCleanup(instance) {
2960
+ if (instance.isCleaningUp) return;
2961
+ instance.isCleaningUp = true;
2962
+ const { browser, childRef, cleanupInstance } = instance;
2963
+ try {
2964
+ if ('true' === process.env.EXTENSION_AUTHOR_MODE) console.log(messages.VkK(browser));
2965
+ const child = childRef();
2966
+ if (child && !child.killed) {
2967
+ if ('win32' === process.platform) try {
2968
+ (0, external_child_process_.spawn)('taskkill', [
2969
+ '/PID',
2970
+ String(child.pid),
2971
+ '/T',
2972
+ '/F'
2973
+ ], {
2974
+ stdio: 'ignore',
2975
+ windowsHide: true
2976
+ }).on('error', ()=>{});
2977
+ } catch {}
2978
+ if ('true' === process.env.EXTENSION_AUTHOR_MODE) console.log(messages.XHr(browser));
2979
+ child.kill('SIGTERM');
2980
+ const killTimer = setTimeout(()=>{
2981
+ if (child && !child.killed) {
2982
+ if ('true' === process.env.EXTENSION_AUTHOR_MODE) console.log(messages.AGJ(browser));
2983
+ child.kill('SIGKILL');
2984
+ }
2985
+ }, 5000);
2986
+ killTimer.unref?.();
2865
2987
  }
2866
- };
2867
- const onExit = ()=>{
2868
- attemptCleanup();
2869
- };
2870
- process.on('exit', onExit);
2871
- const onSignal = ()=>{
2872
- attemptCleanup();
2873
- };
2874
- process.on('SIGINT', onSignal);
2875
- process.on('SIGTERM', onSignal);
2876
- process.on('SIGHUP', onSignal);
2877
- process.on('SIGBREAK', onSignal);
2878
- process.on('beforeExit', onSignal);
2988
+ await cleanupInstance();
2989
+ } catch (error) {
2990
+ console.error(messages.TFX(browser, error));
2991
+ }
2992
+ }
2993
+ function cleanupAllInstances() {
2994
+ for (const instance of activeInstances)attemptCleanup(instance);
2995
+ }
2996
+ function firstBrowserLabel() {
2997
+ for (const instance of activeInstances)return instance.browser;
2998
+ return 'firefox';
2999
+ }
3000
+ function installGlobalHandlersOnce() {
3001
+ if (globalHandlersInstalled) return;
3002
+ globalHandlersInstalled = true;
3003
+ process.on('exit', cleanupAllInstances);
3004
+ process.on('SIGINT', cleanupAllInstances);
3005
+ process.on('SIGTERM', cleanupAllInstances);
3006
+ process.on('SIGHUP', cleanupAllInstances);
3007
+ process.on('SIGBREAK', cleanupAllInstances);
3008
+ process.on('beforeExit', cleanupAllInstances);
2879
3009
  process.on('uncaughtException', async (error)=>{
2880
3010
  if (isBenignSocketTeardown(error)) return;
2881
- console.error(messages.F41(browser, error));
2882
- await attemptCleanup();
3011
+ console.error(messages.F41(firstBrowserLabel(), error));
3012
+ await Promise.all([
3013
+ ...activeInstances
3014
+ ].map((i)=>attemptCleanup(i)));
2883
3015
  process.exit(1);
2884
3016
  });
2885
3017
  process.on('unhandledRejection', async (reason)=>{
2886
3018
  if (isBenignSocketTeardown(reason)) return;
2887
- console.error(messages.N4O(browser, reason));
2888
- await attemptCleanup();
3019
+ console.error(messages.N4O(firstBrowserLabel(), reason));
3020
+ await Promise.all([
3021
+ ...activeInstances
3022
+ ].map((i)=>attemptCleanup(i)));
2889
3023
  process.exit(1);
2890
3024
  });
2891
3025
  }
3026
+ function setupFirefoxProcessHandlers(browser, childRef, cleanupInstance) {
3027
+ const instance = {
3028
+ browser,
3029
+ childRef,
3030
+ cleanupInstance,
3031
+ isCleaningUp: false
3032
+ };
3033
+ activeInstances.add(instance);
3034
+ installGlobalHandlersOnce();
3035
+ return ()=>{
3036
+ activeInstances.delete(instance);
3037
+ };
3038
+ }
2892
3039
  const BENIGN_SOCKET_ERROR_CODES = new Set([
2893
3040
  'ECONNRESET',
2894
3041
  'EPIPE',
@@ -2951,6 +3098,10 @@ var __webpack_modules__ = {
2951
3098
  else obj[key] = value;
2952
3099
  return obj;
2953
3100
  }
3101
+ function rdpRequestTimeoutMs() {
3102
+ const raw = parseInt(String(process.env.EXTENSION_RDP_REQUEST_TIMEOUT_MS || ''), 10);
3103
+ return Number.isFinite(raw) && raw > 0 ? raw : 30000;
3104
+ }
2954
3105
  class RdpTransport extends external_events_default() {
2955
3106
  async connect(port, host = '127.0.0.1') {
2956
3107
  await new Promise((resolve, reject)=>{
@@ -2980,7 +3131,10 @@ var __webpack_modules__ = {
2980
3131
  this.rejectAll(err);
2981
3132
  }
2982
3133
  rejectAll(error) {
2983
- for (const d of this.active.values())d.reject(error);
3134
+ for (const entry of this.active.values()){
3135
+ if (entry.timer) clearTimeout(entry.timer);
3136
+ entry.deferred.reject(error);
3137
+ }
2984
3138
  this.active.clear();
2985
3139
  for (const { deferred } of this.pending)deferred.reject(error);
2986
3140
  this.pending = [];
@@ -3006,7 +3160,10 @@ var __webpack_modules__ = {
3006
3160
  flush() {
3007
3161
  this.pending = this.pending.filter(({ to, payload, deferred })=>{
3008
3162
  if (this.active.has(to)) return true;
3009
- if (!this.conn) throw new Error(messages.FmF('firefox'));
3163
+ if (!this.conn) {
3164
+ deferred.reject(new Error(messages.FmF('firefox')));
3165
+ return false;
3166
+ }
3010
3167
  try {
3011
3168
  this.conn.write(buildRdpFrame(payload));
3012
3169
  this.expectReply(to, deferred);
@@ -3018,7 +3175,19 @@ var __webpack_modules__ = {
3018
3175
  }
3019
3176
  expectReply(to, deferred) {
3020
3177
  if (this.active.has(to)) throw new Error(messages.cD1('firefox', to));
3021
- this.active.set(to, deferred);
3178
+ const timeoutMs = rdpRequestTimeoutMs();
3179
+ const timer = setTimeout(()=>{
3180
+ const entry = this.active.get(to);
3181
+ if (!entry) return;
3182
+ this.active.delete(to);
3183
+ entry.deferred.reject(new Error(`RDP request to "${to}" timed out after ${timeoutMs}ms`));
3184
+ this.flush();
3185
+ }, timeoutMs);
3186
+ timer.unref?.();
3187
+ this.active.set(to, {
3188
+ deferred,
3189
+ timer
3190
+ });
3022
3191
  }
3023
3192
  onData(buf) {
3024
3193
  this.incoming = Buffer.concat([
@@ -3041,17 +3210,19 @@ var __webpack_modules__ = {
3041
3210
  }
3042
3211
  handleMessage(message) {
3043
3212
  if (!message.from) return void this.emit('error', new Error(messages.HHw('firefox', message)));
3044
- const deferred = this.active.get(message.from);
3045
- if (deferred) {
3213
+ const entry = this.active.get(message.from);
3214
+ if (entry) {
3046
3215
  this.active.delete(message.from);
3047
- if (message.error) deferred.reject(message);
3048
- else deferred.resolve(message);
3216
+ if (entry.timer) clearTimeout(entry.timer);
3217
+ if (message.error) entry.deferred.reject(message);
3218
+ else entry.deferred.resolve(message);
3049
3219
  this.flush();
3050
3220
  return;
3051
3221
  }
3052
3222
  this.emit('message', message);
3053
3223
  }
3054
3224
  onEnd() {
3225
+ this.rejectAll(new Error(messages.qNy('firefox')));
3055
3226
  this.emit('end');
3056
3227
  }
3057
3228
  onTimeout() {
@@ -3473,6 +3644,7 @@ var __webpack_modules__ = {
3473
3644
  } catch {}
3474
3645
  return merged;
3475
3646
  }
3647
+ const GET_PAGE_HTML_WITH_SHADOW_EXPRESSION = "(() => { try { var selector = '#extension-root,[data-extension-root]:not([data-extension-root=\"extension-js-devtools\"])'; var serializeShadowRoot = function(shadowRoot, serializer) { if (!shadowRoot) return ''; var stylesheetCss = Array.from(shadowRoot.styleSheets || []).map(function(sheet) { try { return Array.from(sheet.cssRules || []).map(function(rule) { return String(rule.cssText || ''); }).join('\\n'); } catch (e) { return ''; } }).filter(Boolean).join('\\n'); var adoptedCss = Array.from(shadowRoot.adoptedStyleSheets || []).map(function(sheet) { try { return Array.from(sheet.cssRules || []).map(function(rule) { return String(rule.cssText || ''); }).join('\\n'); } catch (e) { return ''; } }).filter(Boolean).join('\\n'); var stylesheetMarkup = stylesheetCss ? '<style>' + stylesheetCss + '</style>' : ''; var adoptedMarkup = adoptedCss ? '<style>' + adoptedCss + '</style>' : ''; var childMarkup = Array.from(shadowRoot.childNodes || []).map(function(node) { try { if (node && node.nodeType === 1 && String(node.nodeName || '').toLowerCase() === 'style') { return '<style>' + String(node.textContent || '') + '</style>'; } return serializer.serializeToString(node); } catch (e) { return ''; } }).join(''); return stylesheetMarkup + adoptedMarkup + childMarkup; }; var cloned = document.documentElement.cloneNode(true); var clonedHosts = Array.from(cloned.querySelectorAll(selector)); var liveHosts = Array.from(document.querySelectorAll(selector)); if (!clonedHosts.length) { var body = cloned.querySelector('body') || cloned; var newRoot = document.createElement('div'); newRoot.id='extension-root'; body.appendChild(newRoot); clonedHosts = [newRoot]; } var s = new XMLSerializer(); for (var i = 0; i < clonedHosts.length; i++) { var host = clonedHosts[i]; var live = liveHosts[i]; var shadow = ''; try { if (live && live.shadowRoot) { shadow = serializeShadowRoot(live.shadowRoot, s); } } catch (e) {} try { host.innerHTML = shadow; } catch (e) {} } return String('<!DOCTYPE html>' + (cloned.outerHTML || document.documentElement.outerHTML)); } catch(e) { try { return String(document.documentElement.outerHTML); } catch(_) { return '' } } })()";
3476
3648
  function messaging_client_define_property(obj, key, value) {
3477
3649
  if (key in obj) Object.defineProperty(obj, key, {
3478
3650
  value: value,
@@ -3562,11 +3734,7 @@ var __webpack_modules__ = {
3562
3734
  }
3563
3735
  }
3564
3736
  async addTab(url) {
3565
- try {
3566
- return await addTab(this.transport, url);
3567
- } catch (e) {
3568
- throw e;
3569
- }
3737
+ return addTab(this.transport, url);
3570
3738
  }
3571
3739
  async navigateViaScript(consoleActor, url) {
3572
3740
  await navigateViaScript(this.transport, consoleActor, url);
@@ -3624,7 +3792,7 @@ var __webpack_modules__ = {
3624
3792
  const shadowContent = await this.extractShadowContent(actorToUse);
3625
3793
  if (!shadowContent) return mainHTML;
3626
3794
  try {
3627
- const mergedResp = await this.evaluateRaw(actorToUse, "(() => { try { var selector = '#extension-root,[data-extension-root]:not([data-extension-root=\"extension-js-devtools\"])'; var serializeShadowRoot = function(shadowRoot, serializer) { if (!shadowRoot) return ''; var stylesheetCss = Array.from(shadowRoot.styleSheets || []).map(function(sheet) { try { return Array.from(sheet.cssRules || []).map(function(rule) { return String(rule.cssText || ''); }).join('\\n'); } catch (e) { return ''; } }).filter(Boolean).join('\\n'); var adoptedCss = Array.from(shadowRoot.adoptedStyleSheets || []).map(function(sheet) { try { return Array.from(sheet.cssRules || []).map(function(rule) { return String(rule.cssText || ''); }).join('\\n'); } catch (e) { return ''; } }).filter(Boolean).join('\\n'); var stylesheetMarkup = stylesheetCss ? '<style>' + stylesheetCss + '</style>' : ''; var adoptedMarkup = adoptedCss ? '<style>' + adoptedCss + '</style>' : ''; var childMarkup = Array.from(shadowRoot.childNodes || []).map(function(node) { try { if (node && node.nodeType === 1 && String(node.nodeName || '').toLowerCase() === 'style') { return '<style>' + String(node.textContent || '') + '</style>'; } return serializer.serializeToString(node); } catch (e) { return ''; } }).join(''); return stylesheetMarkup + adoptedMarkup + childMarkup; }; var cloned = document.documentElement.cloneNode(true); var clonedHosts = Array.from(cloned.querySelectorAll(selector)); var liveHosts = Array.from(document.querySelectorAll(selector)); if (!clonedHosts.length) { var body = cloned.querySelector('body') || cloned; var newRoot = document.createElement('div'); newRoot.id='extension-root'; body.appendChild(newRoot); clonedHosts = [newRoot]; } var s = new XMLSerializer(); for (var i = 0; i < clonedHosts.length; i++) { var host = clonedHosts[i]; var live = liveHosts[i]; var shadow = ''; try { if (live && live.shadowRoot) { shadow = serializeShadowRoot(live.shadowRoot, s); } } catch (e) {} try { host.innerHTML = shadow; } catch (e) {} } return String('<!DOCTYPE html>' + (cloned.outerHTML || document.documentElement.outerHTML)); } catch(e) { try { return String(document.documentElement.outerHTML); } catch(_) { return '' } } })()");
3795
+ const mergedResp = await this.evaluateRaw(actorToUse, GET_PAGE_HTML_WITH_SHADOW_EXPRESSION);
3628
3796
  const mergedHtml = await this.coerceResponseToString(actorToUse, mergedResp, {
3629
3797
  fallbackToFullDocument: false
3630
3798
  });
@@ -3736,7 +3904,8 @@ var __webpack_modules__ = {
3736
3904
  console.log(JSON.stringify(event));
3737
3905
  }
3738
3906
  function resolveAddonDirectory(baseDir, inputPath) {
3739
- let candidate = inputPath.replace(/\"/g, '');
3907
+ let candidate = inputPath.trim();
3908
+ if (candidate.startsWith('"') && candidate.endsWith('"') || candidate.startsWith("'") && candidate.endsWith("'")) candidate = candidate.slice(1, -1);
3740
3909
  if (!external_path_.isAbsolute(candidate)) candidate = external_path_.resolve(baseDir, candidate);
3741
3910
  try {
3742
3911
  const stat = external_fs_.statSync(candidate);
@@ -3978,7 +4147,7 @@ var __webpack_modules__ = {
3978
4147
  const shadowContent = await client.extractShadowContent?.(descriptorActor);
3979
4148
  if (!shadowContent) return mainHTML;
3980
4149
  try {
3981
- const mergedResp = await client.evaluateRaw(descriptorActor, "(() => { try { var selector = '#extension-root,[data-extension-root]:not([data-extension-root=\"extension-js-devtools\"])'; var serializeShadowRoot = function(shadowRoot, serializer) { if (!shadowRoot) return ''; var stylesheetCss = Array.from(shadowRoot.styleSheets || []).map(function(sheet) { try { return Array.from(sheet.cssRules || []).map(function(rule) { return String(rule.cssText || ''); }).join('\\n'); } catch (e) { return ''; } }).filter(Boolean).join('\\n'); var adoptedCss = Array.from(shadowRoot.adoptedStyleSheets || []).map(function(sheet) { try { return Array.from(sheet.cssRules || []).map(function(rule) { return String(rule.cssText || ''); }).join('\\n'); } catch (e) { return ''; } }).filter(Boolean).join('\\n'); var stylesheetMarkup = stylesheetCss ? '<style>' + stylesheetCss + '</style>' : ''; var adoptedMarkup = adoptedCss ? '<style>' + adoptedCss + '</style>' : ''; var childMarkup = Array.from(shadowRoot.childNodes || []).map(function(node) { try { if (node && node.nodeType === 1 && String(node.nodeName || '').toLowerCase() === 'style') { return '<style>' + String(node.textContent || '') + '</style>'; } return serializer.serializeToString(node); } catch (e) { return ''; } }).join(''); return stylesheetMarkup + adoptedMarkup + childMarkup; }; var cloned = document.documentElement.cloneNode(true); var clonedHosts = Array.from(cloned.querySelectorAll(selector)); var liveHosts = Array.from(document.querySelectorAll(selector)); if (!clonedHosts.length) { var body = cloned.querySelector('body') || cloned; var newRoot = document.createElement('div'); newRoot.id='extension-root'; body.appendChild(newRoot); clonedHosts = [newRoot]; } var s = new XMLSerializer(); for (var i = 0; i < clonedHosts.length; i++) { var host = clonedHosts[i]; var live = liveHosts[i]; var shadow = ''; try { if (live && live.shadowRoot) { shadow = serializeShadowRoot(live.shadowRoot, s); } } catch (e) {} try { host.innerHTML = shadow; } catch (e) {} } return String('<!DOCTYPE html>' + (cloned.outerHTML || document.documentElement.outerHTML)); } catch(e) { try { return String(document.documentElement.outerHTML); } catch(_) { return '' } } })()");
4150
+ const mergedResp = await client.evaluateRaw(descriptorActor, GET_PAGE_HTML_WITH_SHADOW_EXPRESSION);
3982
4151
  const mergedHtml = await client.coerceResponseToString?.(descriptorActor, mergedResp, {
3983
4152
  fallbackToFullDocument: false
3984
4153
  });
@@ -4694,8 +4863,6 @@ var __webpack_modules__ = {
4694
4863
  if (profilePath) parts.splice(1, 0, `--profile="${profilePath}"`);
4695
4864
  return parts.join(' ');
4696
4865
  }
4697
- const external_util_namespaceObject = require("util");
4698
- const execFile = (0, external_util_namespaceObject.promisify)(external_child_process_.execFile);
4699
4866
  function parseFlatpakBinary(binary) {
4700
4867
  if (!binary || !binary.startsWith('flatpak:')) return null;
4701
4868
  const appId = binary.substring(8).trim();
@@ -4747,57 +4914,20 @@ var __webpack_modules__ = {
4747
4914
  args
4748
4915
  };
4749
4916
  }
4750
- static async validateFirefoxBinary(binaryPath) {
4751
- try {
4752
- const { stdout } = await execFile(binaryPath, [
4753
- '--version'
4754
- ]);
4755
- const version = stdout.trim();
4756
- return {
4757
- version,
4758
- path: binaryPath
4759
- };
4760
- } catch (error) {
4761
- throw new Error(`Failed to validate Firefox binary: ${error}`);
4762
- }
4763
- }
4764
- }
4765
- var external_os_ = __webpack_require__("os");
4766
- function isWslEnv() {
4767
- if ('linux' !== process.platform) return false;
4768
- const hasWslEnv = Boolean(String(process.env.WSL_DISTRO_NAME || '').trim() || String(process.env.WSL_INTEROP || '').trim() || String(process.env.WSLENV || '').trim());
4769
- if (hasWslEnv) return true;
4770
- return /microsoft/i.test(external_os_.release());
4771
- }
4772
- function hasGuiDisplay() {
4773
- const display = String(process.env.DISPLAY || '').trim();
4774
- const waylandDisplay = String(process.env.WAYLAND_DISPLAY || '').trim();
4775
- return display.length > 0 || waylandDisplay.length > 0;
4776
4917
  }
4918
+ var wsl_support = __webpack_require__("./browsers/browsers-lib/wsl-support.ts");
4777
4919
  const LINUX_FIREFOX_PATHS = [
4778
4920
  '/usr/bin/firefox',
4779
4921
  '/snap/bin/firefox',
4780
4922
  '/opt/firefox/firefox'
4781
4923
  ];
4782
4924
  function resolveWslLinuxBinary() {
4783
- if (!isWslEnv() || !hasGuiDisplay()) return null;
4925
+ if (!(0, wsl_support.EV)() || !(0, wsl_support.qc)()) return null;
4784
4926
  for (const candidate of LINUX_FIREFOX_PATHS)if (external_fs_.existsSync(candidate)) return candidate;
4785
4927
  return null;
4786
4928
  }
4787
- function normalizeBinaryPathForWsl(input) {
4788
- let value = String(input || '').trim();
4789
- if (!value) return value;
4790
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
4791
- if (!isWslEnv()) return value;
4792
- if (/^[a-zA-Z]:[\\/]/.test(value)) {
4793
- const drive = value[0].toLowerCase();
4794
- const rest = value.slice(2).replace(/\\/g, '/').replace(/^\/+/, '');
4795
- return `/mnt/${drive}/${rest}`;
4796
- }
4797
- return value;
4798
- }
4799
4929
  function resolveWslWindowsBinary() {
4800
- if (!isWslEnv()) return null;
4930
+ if (!(0, wsl_support.EV)()) return null;
4801
4931
  const candidates = [
4802
4932
  '/mnt/c/Program Files/Mozilla Firefox/firefox.exe',
4803
4933
  '/mnt/c/Program Files (x86)/Mozilla Firefox/firefox.exe'
@@ -4838,7 +4968,7 @@ var __webpack_modules__ = {
4838
4968
  try {
4839
4969
  return await spawnOnce(binary);
4840
4970
  } catch (error) {
4841
- if (isWslEnv() && fallbackBinary && fallbackBinary !== binary) {
4971
+ if ((0, wsl_support.EV)() && fallbackBinary && fallbackBinary !== binary) {
4842
4972
  logger?.warn?.('[browser] WSL detected: retrying with Windows Firefox binary.');
4843
4973
  return await spawnOnce(fallbackBinary);
4844
4974
  }
@@ -4899,7 +5029,7 @@ var __webpack_modules__ = {
4899
5029
  if (compilationErrors.length > 0) return void this.ctx.logger?.info?.(messages.y7j());
4900
5030
  if ('true' === process.env.EXTENSION_AUTHOR_MODE) this.ctx.logger?.info?.(messages.A9Y());
4901
5031
  const normalizePath = (value)=>{
4902
- const normalized = value ? normalizeBinaryPathForWsl(value) : null;
5032
+ const normalized = value ? (0, wsl_support.f7)(value) : null;
4903
5033
  return normalized && external_fs_.existsSync(normalized) ? normalized : null;
4904
5034
  };
4905
5035
  const resolveManagedBinary = ()=>normalizePath((0, output_binaries_resolver.kI)(compilation, 'firefox') || null);
@@ -4930,7 +5060,7 @@ var __webpack_modules__ = {
4930
5060
  let browserBinaryLocation = resolveManagedBinary();
4931
5061
  let skipDetection = Boolean(browserBinaryLocation);
4932
5062
  const engineBased = 'gecko-based' === this.host.browser || 'firefox-based' === this.host.browser;
4933
- if (!browserBinaryLocation && !engineBased && isWslEnv()) {
5063
+ if (!browserBinaryLocation && !engineBased && (0, wsl_support.EV)()) {
4934
5064
  const linuxFallback = resolveWslLinuxBinary();
4935
5065
  if (linuxFallback) {
4936
5066
  browserBinaryLocation = linuxFallback;
@@ -4987,17 +5117,12 @@ var __webpack_modules__ = {
4987
5117
  }
4988
5118
  }
4989
5119
  const binaryPath = browserBinaryLocation;
4990
- const wslFallbackBinary = isWslEnv() && !engineBased ? resolveWslFallback() : null;
4991
- if (!isFlatpak) {
4992
- try {
4993
- this.host.browserVersionLine = (0, external_firefox_location2_.getFirefoxVersion)(binaryPath, {
4994
- allowExec: true
4995
- }) || '';
4996
- } catch {}
4997
- try {
4998
- await FirefoxBinaryDetector.validateFirefoxBinary(binaryPath);
4999
- } catch {}
5000
- }
5120
+ const wslFallbackBinary = (0, wsl_support.EV)() && !engineBased ? resolveWslFallback() : null;
5121
+ if (!isFlatpak) try {
5122
+ this.host.browserVersionLine = (0, external_firefox_location2_.getFirefoxVersion)(binaryPath, {
5123
+ allowExec: true
5124
+ }) || '';
5125
+ } catch {}
5001
5126
  const extensionsToLoad = (0, runtime_options.fT)(this.host.extension);
5002
5127
  (0, runtime_options.sl)(extensionsToLoad, 'function' == typeof this.ctx.setExtensionRoot ? this.ctx.setExtensionRoot : void 0);
5003
5128
  const desiredDebugPort = (0, shared_utils.jl)(this.host.port, this.host.instanceId);
@@ -5026,6 +5151,9 @@ var __webpack_modules__ = {
5026
5151
  const profilePath = profileMatch[1];
5027
5152
  const { binary, args } = FirefoxBinaryDetector.generateFirefoxArgs(binaryPath, profilePath, debugPort, firefoxArgs);
5028
5153
  this.child = await this.spawnFirefoxChild(binary, args, wslFallbackBinary);
5154
+ this.child.on('close', ()=>{
5155
+ (0, shared_utils.sW)(profilePath);
5156
+ });
5029
5157
  this.wireChildLifecycle();
5030
5158
  const ctrl = await setupRdpAfterLaunch(this.host, compilation, debugPort);
5031
5159
  this.host.rdpController = ctrl;
@@ -5093,6 +5221,7 @@ var __webpack_modules__ = {
5093
5221
  if (process.env.VITEST || process.env.VITEST_WORKER_ID) throw new Error('Firefox startup timed out');
5094
5222
  process.exit(1);
5095
5223
  });
5224
+ let disposeProcessHandlers;
5096
5225
  child.on('close', (_code)=>{
5097
5226
  if (this.watchTimeout) {
5098
5227
  clearTimeout(this.watchTimeout);
@@ -5102,9 +5231,10 @@ var __webpack_modules__ = {
5102
5231
  this.cleanupInstance().catch((err)=>{
5103
5232
  if ('true' === process.env.EXTENSION_AUTHOR_MODE) this.ctx.logger?.error?.(`[browser] Cleanup error on child close: ${err?.message || err}`);
5104
5233
  });
5234
+ disposeProcessHandlers?.();
5105
5235
  });
5106
5236
  this.pipeChildOutput(child);
5107
- setupFirefoxProcessHandlers(this.host.browser, ()=>this.child, ()=>this.cleanupInstance());
5237
+ disposeProcessHandlers = setupFirefoxProcessHandlers(this.host.browser, ()=>this.child, ()=>this.cleanupInstance());
5108
5238
  }
5109
5239
  async cleanupInstance() {
5110
5240
  try {
@@ -5112,11 +5242,12 @@ var __webpack_modules__ = {
5112
5242
  try {
5113
5243
  this.child.kill('SIGTERM');
5114
5244
  } catch {}
5115
- setTimeout(()=>{
5245
+ const killTimer = setTimeout(()=>{
5116
5246
  try {
5117
5247
  if (this.child && !this.child.killed) this.child.kill('SIGKILL');
5118
5248
  } catch {}
5119
5249
  }, 2000);
5250
+ killTimer.unref?.();
5120
5251
  }
5121
5252
  } catch {}
5122
5253
  }
@@ -5637,7 +5768,7 @@ var __webpack_modules__ = {
5637
5768
  if ('info' === type) return external_pintor_default().gray('⏵⏵⏵');
5638
5769
  return external_pintor_default().green('⏵⏵⏵');
5639
5770
  }
5640
- const code = (text)=>external_pintor_default().blue(text);
5771
+ const messages_code = (text)=>external_pintor_default().blue(text);
5641
5772
  const messages_arg = (text)=>external_pintor_default().gray(text);
5642
5773
  const fmt = {
5643
5774
  heading: (title)=>external_pintor_default().underline(external_pintor_default().blue(title)),
@@ -5667,6 +5798,13 @@ var __webpack_modules__ = {
5667
5798
  start: 'Builds and starts the extension in production mode',
5668
5799
  preview: 'Previews the extension in production mode without building',
5669
5800
  build: 'Builds the extension for packaging/distribution',
5801
+ logs: 'Prints or streams logs from every context of a running dev session',
5802
+ eval: 'Evaluates an expression in a running extension context (requires --allow-eval)',
5803
+ storage: 'Reads or writes chrome.storage in a running extension (requires --allow-control)',
5804
+ reload: 'Reloads a running extension or tab (requires --allow-control)',
5805
+ open: 'Opens an extension surface — popup, options, or sidebar (requires --allow-control)',
5806
+ inspect: 'Inspects a page/content DOM via the agent bridge (CDP-free; requires --allow-control)',
5807
+ publish: 'Publishes to extension.dev and prints a shareable URL (requires EXTENSION_DEV_TOKEN)',
5670
5808
  install: 'Installs a managed browser binary into Extension.js cache',
5671
5809
  uninstall: 'Removes managed browser binaries from Extension.js cache',
5672
5810
  telemetry: 'Manage anonymous telemetry consent (enable, disable, or show status)'
@@ -5679,8 +5817,10 @@ var __webpack_modules__ = {
5679
5817
  return `${getLoggingPrefix('error')} Failed to check for updates.\n${external_pintor_default().red(String(err?.message || err))}`;
5680
5818
  }
5681
5819
  function checkUpdates(packageJson, update) {
5682
- const suffix = external_pintor_default().gray(`(version ${String(update.latest)} is available!)`);
5683
- const message = `${getLoggingPrefix('info')} 🧩 ${external_pintor_default().blue('Extension.js')} update available.\n\nYou are currently using version ${external_pintor_default().red(String(packageJson.version))}. Latest stable is ${external_pintor_default().green(String(update.latest))}.\nUpdate to the latest stable to get fixes and new features.`;
5820
+ const latest = String(update.latest);
5821
+ const releaseNotesUrl = `https://github.com/extension-js/extension.js/releases/tag/v${latest}`;
5822
+ const suffix = external_pintor_default().gray(`(version ${latest} is available!)`);
5823
+ const message = `${getLoggingPrefix('info')} 🧩 ${external_pintor_default().blue('Extension.js')} update available.\n\nYou are currently using version ${external_pintor_default().red(String(packageJson.version))}. Latest stable is ${external_pintor_default().green(latest)}.\nSee what's new: ${external_pintor_default().underline(releaseNotesUrl)}\nUpdate to the latest stable to get fixes and new features.`;
5684
5824
  return {
5685
5825
  suffix,
5686
5826
  message
@@ -5693,111 +5833,132 @@ Usage: extension [command] [options]
5693
5833
 
5694
5834
  Notes
5695
5835
  - All high-level commands offer their own \`--help\` with usage and flag lists.
5696
- - Telemetry is anonymous and privacy-safe by default; see ${code('docs/TELEMETRY.md')} for the full contract.
5836
+ - Telemetry is anonymous and privacy-safe by default; see ${messages_code('docs/TELEMETRY.md')} for the full contract.
5697
5837
 
5698
5838
  Example
5699
- - ${code('extension create --help')} outputs information about the "create" command.
5839
+ - ${messages_code('extension create --help')} outputs information about the "create" command.
5700
5840
 
5701
5841
  Available Commands
5702
- - ${code('extension create ' + messages_arg('<project-name|project-path>'))}
5842
+ - ${messages_code('extension create ' + messages_arg('<project-name|project-path>'))}
5703
5843
  ${commandDescriptions.create}
5704
5844
 
5705
- - ${code('extension dev ' + messages_arg('[project-path|remote-url]'))}
5845
+ - ${messages_code('extension dev ' + messages_arg('[project-path|remote-url]'))}
5706
5846
  ${commandDescriptions.dev}
5707
5847
 
5708
- - ${code('extension start ' + messages_arg('[project-path|remote-url]'))}
5848
+ - ${messages_code('extension start ' + messages_arg('[project-path|remote-url]'))}
5709
5849
  ${commandDescriptions.start}
5710
5850
 
5711
- - ${code('extension preview ' + messages_arg('[project-path|remote-url]'))}
5851
+ - ${messages_code('extension preview ' + messages_arg('[project-path|remote-url]'))}
5712
5852
  ${commandDescriptions.preview}
5713
5853
 
5714
- - ${code('extension build ' + messages_arg('[project-path|remote-url]'))}
5854
+ - ${messages_code('extension build ' + messages_arg('[project-path|remote-url]'))}
5715
5855
  ${commandDescriptions.build}
5716
5856
 
5717
- - ${code('extension install ' + messages_arg('<chrome|chromium|edge|firefox>'))}
5857
+ - ${messages_code('extension logs ' + messages_arg('[project-path]'))}
5858
+ ${commandDescriptions.logs}
5859
+
5860
+ - ${messages_code('extension eval ' + messages_arg('<expression> [project-path]'))}
5861
+ ${commandDescriptions.eval}
5862
+
5863
+ - ${messages_code('extension storage ' + messages_arg('<get|set> [key] [value] [project-path]'))}
5864
+ ${commandDescriptions.storage}
5865
+
5866
+ - ${messages_code('extension reload ' + messages_arg('[project-path]'))}
5867
+ ${commandDescriptions.reload}
5868
+
5869
+ - ${messages_code('extension open ' + messages_arg('<popup|options|sidebar> [project-path]'))}
5870
+ ${commandDescriptions.open}
5871
+
5872
+ - ${messages_code('extension inspect ' + messages_arg('[project-path] --tab <id>'))}
5873
+ ${commandDescriptions.inspect}
5874
+
5875
+ - ${messages_code('extension publish ' + messages_arg('[project-path]'))}
5876
+ ${commandDescriptions.publish}
5877
+
5878
+ - ${messages_code('extension install ' + messages_arg('<chrome|chromium|edge|firefox>'))}
5718
5879
  ${commandDescriptions.install}
5719
5880
 
5720
- - ${code('extension install ' + messages_arg('--browser <chrome|chromium|edge|firefox|chromium-based|gecko-based|firefox-based|all>'))}
5721
- Install multiple browsers, browser families, or ${code('all')}
5881
+ - ${messages_code('extension install ' + messages_arg('--browser <chrome|chromium|edge|firefox|chromium-based|gecko-based|firefox-based|all>'))}
5882
+ Install multiple browsers, browser families, or ${messages_code('all')}
5722
5883
 
5723
- - ${code('extension install --where')}
5724
- Prints the managed browser cache root (or browser install path(s) when a browser name or ${code('--browser')} is provided)
5884
+ - ${messages_code('extension install --where')}
5885
+ Prints the managed browser cache root (or browser install path(s) when a browser name or ${messages_code('--browser')} is provided)
5725
5886
 
5726
- - ${code('extension uninstall ' + messages_arg('<chrome|chromium|edge|firefox> | --all'))}
5887
+ - ${messages_code('extension uninstall ' + messages_arg('<chrome|chromium|edge|firefox> | --all'))}
5727
5888
  ${commandDescriptions.uninstall}
5728
5889
 
5729
- - ${code('extension uninstall --where')}
5890
+ - ${messages_code('extension uninstall --where')}
5730
5891
  Prints the managed browser cache root (or browser install path(s) when --browser/--all is provided)
5731
5892
 
5732
- - ${code('extension telemetry ' + messages_arg('<enable|disable|status>'))}
5893
+ - ${messages_code('extension telemetry ' + messages_arg('<enable|disable|status>'))}
5733
5894
  ${commandDescriptions.telemetry}
5734
5895
 
5735
5896
  Common Options
5736
- - ${code('--browser')} ${messages_arg('<chrome|edge|firefox|chromium|chromium-based|gecko-based|firefox-based>')} Target browser/engine (default: chromium)
5737
- - ${code('--profile')} ${messages_arg('<path|boolean>')} Browser profile configuration
5738
- - ${code('--polyfill')} ${messages_arg('[boolean]')} Enable/disable cross-browser polyfill
5739
- - ${code('--no-telemetry')} Disable anonymous telemetry for this run (persistent toggle: ${code('extension telemetry disable')}, or ${code('EXTENSION_TELEMETRY=0')})
5740
- - ${code('--ai-help')} Show AI-assistant oriented help and tips
5741
- - ${code('--format')} ${messages_arg('<pretty|json>')} Output format for ${code('--ai-help')} (default: pretty)
5742
- - ${code('--help')} Show help output
5743
- - ${code('--port')} ${messages_arg('<number>')} Development server port (default: 8080; use 0 for OS-assigned)
5744
- - ${code('--host')} ${messages_arg('<address>')} Dev server host (default: 127.0.0.1; use 0.0.0.0 for Docker/devcontainers)
5745
- - ${code('--starting-url')} ${messages_arg('<url>')} Initial URL to load in browser
5746
- - ${code('--silent')} ${messages_arg('[boolean]')} Suppress console output during build
5897
+ - ${messages_code('--browser')} ${messages_arg('<chrome|edge|firefox|chromium|chromium-based|gecko-based|firefox-based>')} Target browser/engine (default: chromium)
5898
+ - ${messages_code('--profile')} ${messages_arg('<path|boolean>')} Browser profile configuration
5899
+ - ${messages_code('--polyfill')} ${messages_arg('[boolean]')} Enable/disable cross-browser polyfill
5900
+ - ${messages_code('--no-telemetry')} Disable anonymous telemetry for this run (persistent toggle: ${messages_code('extension telemetry disable')}, or ${messages_code('EXTENSION_TELEMETRY=0')})
5901
+ - ${messages_code('--ai-help')} Show AI-assistant oriented help and tips
5902
+ - ${messages_code('--format')} ${messages_arg('<pretty|json>')} Output format for ${messages_code('--ai-help')} (default: pretty)
5903
+ - ${messages_code('--help')} Show help output
5904
+ - ${messages_code('--port')} ${messages_arg('<number>')} Development server port (default: 8080; use 0 for OS-assigned)
5905
+ - ${messages_code('--host')} ${messages_arg('<address>')} Dev server host (default: 127.0.0.1; use 0.0.0.0 for Docker/devcontainers)
5906
+ - ${messages_code('--starting-url')} ${messages_arg('<url>')} Initial URL to load in browser
5907
+ - ${messages_code('--silent')} ${messages_arg('[boolean]')} Suppress console output during build
5747
5908
 
5748
5909
  Source Inspection (dev command)
5749
- - ${code('--source')} ${messages_arg('<url|boolean>')} Open URL and print HTML after content scripts inject (dev only)
5910
+ - ${messages_code('--source')} ${messages_arg('<url|boolean>')} Open URL and print HTML after content scripts inject (dev only)
5750
5911
  - When provided without a URL, falls back to ${messages_arg('--starting-url')} or ${messages_arg('https://example.com')}
5751
- - For ${code('extension dev')}, watch mode is enabled by default when ${code('--source')} is present
5752
- - ${messages_arg('Note:')} ${code('extension preview')} and ${code('extension start')} do not run source inspection in run-only preview mode.
5753
- - ${messages_arg('Automation sync:')} when using ${code('extension dev --no-browser')} or ${code('extension start --no-browser')}, run ${code('extension <dev|start> --wait --browser=<browser>')} in a second process to gate on ${code('ready.json')} (add ${code('--wait-format=json')} for machine-readable output).
5754
- - ${code('--watch-source')} ${messages_arg('[boolean]')} Re-print HTML on rebuilds or file changes
5755
- - ${code('--source-format')} ${messages_arg('<pretty|json|ndjson>')} Output format for page HTML (defaults to ${code('--log-format')} when present)
5756
- - ${code('--source-summary')} ${messages_arg('[boolean]')} Output a compact summary instead of full HTML
5757
- - ${code('--source-meta')} ${messages_arg('[boolean]')} Output page metadata (readyState, viewport, frames)
5758
- - ${code('--source-probe')} ${messages_arg('<selectors>')} Comma-separated CSS selectors to probe
5759
- - ${code('--source-tree')} ${messages_arg('<off|root-only>')} Output a compact extension root tree
5760
- - ${code('--source-console')} ${messages_arg('[boolean]')} Output console summary (best-effort)
5761
- - ${code('--source-dom')} ${messages_arg('[boolean]')} Output DOM snapshots and diffs
5762
- - ${code('--source-max-bytes')} ${messages_arg('<bytes>')} Limit HTML output size (0 disables truncation)
5763
- - ${code('--source-redact')} ${messages_arg('<off|safe|strict>')} Redact sensitive HTML content (default: safe for JSON/NDJSON)
5764
- - ${code('--source-include-shadow')} ${messages_arg('<off|open-only|all>')} Control Shadow DOM inclusion (default: open-only)
5765
- - ${code('--source-diff')} ${messages_arg('[boolean]')} Include diff metadata on watch updates
5912
+ - For ${messages_code('extension dev')}, watch mode is enabled by default when ${messages_code('--source')} is present
5913
+ - ${messages_arg('Note:')} ${messages_code('extension preview')} and ${messages_code('extension start')} do not run source inspection in run-only preview mode.
5914
+ - ${messages_arg('Automation sync:')} when using ${messages_code('extension dev --no-browser')} or ${messages_code('extension start --no-browser')}, run ${messages_code('extension <dev|start> --wait --browser=<browser>')} in a second process to gate on ${messages_code('ready.json')} (add ${messages_code('--wait-format=json')} for machine-readable output).
5915
+ - ${messages_code('--watch-source')} ${messages_arg('[boolean]')} Re-print HTML on rebuilds or file changes
5916
+ - ${messages_code('--source-format')} ${messages_arg('<pretty|json|ndjson>')} Output format for page HTML (defaults to ${messages_code('--log-format')} when present)
5917
+ - ${messages_code('--source-summary')} ${messages_arg('[boolean]')} Output a compact summary instead of full HTML
5918
+ - ${messages_code('--source-meta')} ${messages_arg('[boolean]')} Output page metadata (readyState, viewport, frames)
5919
+ - ${messages_code('--source-probe')} ${messages_arg('<selectors>')} Comma-separated CSS selectors to probe
5920
+ - ${messages_code('--source-tree')} ${messages_arg('<off|root-only>')} Output a compact extension root tree
5921
+ - ${messages_code('--source-console')} ${messages_arg('[boolean]')} Output console summary (best-effort)
5922
+ - ${messages_code('--source-dom')} ${messages_arg('[boolean]')} Output DOM snapshots and diffs
5923
+ - ${messages_code('--source-max-bytes')} ${messages_arg('<bytes>')} Limit HTML output size (0 disables truncation)
5924
+ - ${messages_code('--source-redact')} ${messages_arg('<off|safe|strict>')} Redact sensitive HTML content (default: safe for JSON/NDJSON)
5925
+ - ${messages_code('--source-include-shadow')} ${messages_arg('<off|open-only|all>')} Control Shadow DOM inclusion (default: open-only)
5926
+ - ${messages_code('--source-diff')} ${messages_arg('[boolean]')} Include diff metadata on watch updates
5766
5927
 
5767
5928
  Browser-Specific Options
5768
- - ${code('--chromium-binary')} ${messages_arg('<path>')} Custom Chromium binary path
5769
- - ${code('--gecko-binary')}/${code('--firefox-binary')} ${messages_arg('<path>')} Custom Firefox/Gecko binary path
5770
- Use ${code('flatpak:org.mozilla.firefox')} as the path to launch a Flatpak-installed Firefox
5929
+ - ${messages_code('--chromium-binary')} ${messages_arg('<path>')} Custom Chromium binary path
5930
+ - ${messages_code('--gecko-binary')}/${messages_code('--firefox-binary')} ${messages_arg('<path>')} Custom Firefox/Gecko binary path
5931
+ Use ${messages_code('flatpak:org.mozilla.firefox')} as the path to launch a Flatpak-installed Firefox
5771
5932
 
5772
5933
  Build Options
5773
- - ${code('--zip')} ${messages_arg('[boolean]')} Create ZIP archive of built extension
5774
- - ${code('--zip-source')} ${messages_arg('[boolean]')} Include source files in ZIP
5775
- - ${code('--zip-filename')} ${messages_arg('<name>')} Custom ZIP filename
5934
+ - ${messages_code('--zip')} ${messages_arg('[boolean]')} Create ZIP archive of built extension
5935
+ - ${messages_code('--zip-source')} ${messages_arg('[boolean]')} Include source files in ZIP
5936
+ - ${messages_code('--zip-filename')} ${messages_arg('<name>')} Custom ZIP filename
5776
5937
 
5777
5938
  ${external_pintor_default().underline('Centralized Logger (terminal output)')}
5778
5939
  - The manager extension embeds a centralized logger that streams events to the CLI.
5779
- - Enable and filter logs directly via ${code('extension dev')} flags:
5780
- - ${code('--logs')} ${messages_arg('<off|error|warn|info|debug|trace>')} Minimum level (default: off)
5781
- - ${code('--log-context')} ${messages_arg('<list|all>')} Contexts: background,content,page,sidebar,popup,options,devtools
5782
- - ${code('--log-format')} ${messages_arg('<pretty|json|ndjson>')} Output format (default: pretty)
5783
- - ${code('--no-log-timestamps')} Hide ISO timestamps in pretty output
5784
- - ${code('--no-log-color')} Disable color in pretty output
5785
- - ${code('--log-url')} ${messages_arg('<substring|/regex/>')} Filter by event.url
5786
- - ${code('--log-tab')} ${messages_arg('<id>')} Filter by tabId
5787
- - Example: ${code('extension dev ./my-ext --logs=debug --log-context=all --log-format=pretty')}
5940
+ - Enable and filter logs directly via ${messages_code('extension dev')} flags:
5941
+ - ${messages_code('--logs')} ${messages_arg('<off|error|warn|info|debug|trace>')} Minimum level (default: off)
5942
+ - ${messages_code('--log-context')} ${messages_arg('<list|all>')} Contexts: background,content,page,sidebar,popup,options,devtools
5943
+ - ${messages_code('--log-format')} ${messages_arg('<pretty|json|ndjson>')} Output format (default: pretty)
5944
+ - ${messages_code('--no-log-timestamps')} Hide ISO timestamps in pretty output
5945
+ - ${messages_code('--no-log-color')} Disable color in pretty output
5946
+ - ${messages_code('--log-url')} ${messages_arg('<substring|/regex/>')} Filter by event.url
5947
+ - ${messages_code('--log-tab')} ${messages_arg('<id>')} Filter by tabId
5948
+ - Example: ${messages_code('extension dev ./my-ext --logs=debug --log-context=all --log-format=pretty')}
5788
5949
 
5789
- ${code('extension --help')}
5950
+ ${messages_code('extension --help')}
5790
5951
  This command outputs a help file with key command options.
5791
5952
 
5792
5953
  ${external_pintor_default().underline('Path Resolution (important)')}
5793
- - Leading ${code('/')} in manifest/HTML means extension root (the directory containing ${code('manifest.json')}).
5794
- - Relative paths resolve from the ${code('manifest.json')} directory.
5954
+ - Leading ${messages_code('/')} in manifest/HTML means extension root (the directory containing ${messages_code('manifest.json')}).
5955
+ - Relative paths resolve from the ${messages_code('manifest.json')} directory.
5795
5956
  - Absolute OS paths are used as-is.
5796
5957
 
5797
5958
 
5798
5959
  AI Assistants
5799
- - For AI-oriented guidance and deeper tips, run ${code('extension --ai-help')}
5800
- - For machine-readable AI guidance, run ${code('extension --ai-help --format json')}
5960
+ - For AI-oriented guidance and deeper tips, run ${messages_code('extension --ai-help')}
5961
+ - For machine-readable AI guidance, run ${messages_code('extension --ai-help --format json')}
5801
5962
 
5802
5963
  Report issues
5803
5964
  - ${external_pintor_default().underline('https://github.com/cezaraugusto/extension/issues/new')}`;
@@ -5805,99 +5966,102 @@ AI Assistants
5805
5966
  function unsupportedBrowserFlag(value, supported) {
5806
5967
  return `${getLoggingPrefix('error')} Unsupported --browser value: ${value}. Supported: ${supported.join(', ')}.`;
5807
5968
  }
5969
+ function safariCommandNotSupported(command) {
5970
+ return `${getLoggingPrefix('error')} ${messages_code(command)} can't load an extension into Safari automatically.\nSafari extensions ship inside a signed app and are enabled by hand, so there's no live browser session to load into — unlike Chromium and Firefox.\nBuild the Safari app instead: ${messages_code('extension build --browser safari')}\nThen open the generated app and enable it in Safari → Settings → Extensions.`;
5971
+ }
5808
5972
  function programAIHelp() {
5809
5973
  return `\n${getLoggingPrefix('info')} ${external_pintor_default().gray('Development tips for extension developers and AI assistants')}
5810
5974
 
5811
5975
  Browser-Specific Configuration
5812
5976
  - Use browser prefixes in manifest.json for browser-specific fields:
5813
- ${code('{"firefox:manifest": 2, "chrome:manifest": 3}')}
5977
+ ${messages_code('{"firefox:manifest": 2, "chrome:manifest": 3}')}
5814
5978
  This applies manifest v2 to Firefox only, v3 to Chrome/Edge.
5815
5979
 
5816
5980
  Centralized Logger (for AI & CI)
5817
5981
  - Logs from all contexts are centralized by the manager extension and streamed to the CLI.
5818
- - Prefer these flags to control terminal logs during ${code('extension dev')}:
5819
- - ${code('--logs')} ${messages_arg('<off|error|warn|info|debug|trace>')} Minimum level
5820
- - ${code('--log-context')} ${messages_arg('<list|all>')} Contexts to include
5821
- - ${code('--log-format')} ${messages_arg('<pretty|json|ndjson>')} Pretty for humans; JSON for machines/NDJSON pipelines
5822
- - ${code('--no-log-timestamps')} ${messages_arg(' ')} Disable timestamps (pretty)
5823
- - ${code('--no-log-color')} ${messages_arg(' ')} Disable ANSI colors (pretty)
5824
- - ${code('--log-url')} ${messages_arg('<substring|/regex/>')} Filter by URL
5825
- - ${code('--log-tab')} ${messages_arg('<id>')} Filter by tabId
5826
- - Good CI pattern: ${code('EXTENSION_AUTHOR_MODE=development EXTENSION_AUTO_EXIT_MS=6000 extension dev ./ext --logs=info --log-format=json')}
5982
+ - Prefer these flags to control terminal logs during ${messages_code('extension dev')}:
5983
+ - ${messages_code('--logs')} ${messages_arg('<off|error|warn|info|debug|trace>')} Minimum level
5984
+ - ${messages_code('--log-context')} ${messages_arg('<list|all>')} Contexts to include
5985
+ - ${messages_code('--log-format')} ${messages_arg('<pretty|json|ndjson>')} Pretty for humans; JSON for machines/NDJSON pipelines
5986
+ - ${messages_code('--no-log-timestamps')} ${messages_arg(' ')} Disable timestamps (pretty)
5987
+ - ${messages_code('--no-log-color')} ${messages_arg(' ')} Disable ANSI colors (pretty)
5988
+ - ${messages_code('--log-url')} ${messages_arg('<substring|/regex/>')} Filter by URL
5989
+ - ${messages_code('--log-tab')} ${messages_arg('<id>')} Filter by tabId
5990
+ - Good CI pattern: ${messages_code('EXTENSION_AUTHOR_MODE=development EXTENSION_AUTO_EXIT_MS=6000 extension dev ./ext --logs=info --log-format=json')}
5827
5991
 
5828
5992
  Special Folders for Entrypoints
5829
5993
  - Use special folders to handle entrypoints and assets not declared in manifest.json:
5830
- - ${external_pintor_default().underline(code('public/'))} - Static assets automatically copied to build (resolves to output root)
5831
- - ${external_pintor_default().underline(code('pages/'))} - HTML files not declared in manifest (e.g., welcome pages)
5832
- - ${external_pintor_default().underline(code("scripts/"))} - JavaScript files not declared in manifest (e.g., executable scripts)
5994
+ - ${external_pintor_default().underline(messages_code('public/'))} - Static assets automatically copied to build (resolves to output root)
5995
+ - ${external_pintor_default().underline(messages_code('pages/'))} - HTML files not declared in manifest (e.g., welcome pages)
5996
+ - ${external_pintor_default().underline(messages_code("scripts/"))} - JavaScript files not declared in manifest (e.g., executable scripts)
5833
5997
 
5834
5998
  Predictable Output Paths
5835
5999
  - Core HTML destinations are standardized across browsers so you can reference them safely in code/tests:
5836
- - ${code('devtools_page')} → ${code('devtools/index.html')}
5837
- - ${code('sidebar_action.default_panel')} (MV2) and ${code('side_panel.default_path')} (MV3) → ${code('sidebar/index.html')}
5838
- - ${code('options_ui.page')} and ${code('options_page')} → ${code('options/index.html')}
5839
- - ${code('background.page')} → ${code('background/index.html')}
5840
- - ${code('action.default_popup')}, ${code('browser_action.default_popup')}, ${code('page_action.default_popup')} → ${code('action/index.html')}
6000
+ - ${messages_code('devtools_page')} → ${messages_code('devtools/index.html')}
6001
+ - ${messages_code('sidebar_action.default_panel')} (MV2) and ${messages_code('side_panel.default_path')} (MV3) → ${messages_code('sidebar/index.html')}
6002
+ - ${messages_code('options_ui.page')} and ${messages_code('options_page')} → ${messages_code('options/index.html')}
6003
+ - ${messages_code('background.page')} → ${messages_code('background/index.html')}
6004
+ - ${messages_code('action.default_popup')}, ${messages_code('browser_action.default_popup')}, ${messages_code('page_action.default_popup')} → ${messages_code('action/index.html')}
5841
6005
  - Other predictable outputs:
5842
- - ${code('chrome_url_overrides.*')} → ${code('chrome_url_overrides/<key>.html')}
5843
- - ${code("content_scripts[n].js/css")} → ${code("content_scripts/content-<n>.{js,css}")}
5844
- - ${code('sandbox.pages[]')} → ${code('sandbox/page-<n>.html')}
5845
- - ${code("user_scripts.api_script")} → ${code("user_scripts/api_script.js")}
5846
- - ${code('icons/*')} → ${code('icons/')} (feature-specific icon folders preserved where applicable)
6006
+ - ${messages_code('chrome_url_overrides.*')} → ${messages_code('chrome_url_overrides/<key>.html')}
6007
+ - ${messages_code("content_scripts[n].js/css")} → ${messages_code("content_scripts/content-<n>.{js,css}")}
6008
+ - ${messages_code('sandbox.pages[]')} → ${messages_code('sandbox/page-<n>.html')}
6009
+ - ${messages_code("user_scripts.api_script")} → ${messages_code("user_scripts/api_script.js")}
6010
+ - ${messages_code('icons/*')} → ${messages_code('icons/')} (feature-specific icon folders preserved where applicable)
5847
6011
 
5848
6012
  Public & Special Folders (Output Behavior)
5849
- - ${external_pintor_default().underline(code('public/'))} is the web root in output. Authors can use ${code('/foo')}, ${code('/public/foo')}, ${code('public/foo')}, or ${code('./public/foo')} and they all emit as ${code('dist/<browser>/foo')}.
5850
- - ${external_pintor_default().underline(code('pages/'))} files emit as ${code('pages/<name>.html')}. Relative assets referenced inside page HTML are emitted under ${code('assets/')} preserving relative structure; public-root URLs are preserved.
5851
- - ${external_pintor_default().underline(code("scripts/"))} files emit as ${code("scripts/<name>.js")} with extracted CSS when applicable.
6013
+ - ${external_pintor_default().underline(messages_code('public/'))} is the web root in output. Authors can use ${messages_code('/foo')}, ${messages_code('/public/foo')}, ${messages_code('public/foo')}, or ${messages_code('./public/foo')} and they all emit as ${messages_code('dist/<browser>/foo')}.
6014
+ - ${external_pintor_default().underline(messages_code('pages/'))} files emit as ${messages_code('pages/<name>.html')}. Relative assets referenced inside page HTML are emitted under ${messages_code('assets/')} preserving relative structure; public-root URLs are preserved.
6015
+ - ${external_pintor_default().underline(messages_code("scripts/"))} files emit as ${messages_code("scripts/<name>.js")} with extracted CSS when applicable.
5852
6016
 
5853
6017
  Shadow DOM for Content Scripts
5854
- - Add ${code('use shadow-dom')} directive to content scripts for style isolation
5855
- - Automatically creates ${code('#extension-root')} element with shadow DOM
6018
+ - Add ${messages_code('use shadow-dom')} directive to content scripts for style isolation
6019
+ - Automatically creates ${messages_code('#extension-root')} element with shadow DOM
5856
6020
  - All CSS imports are automatically injected into shadow DOM
5857
6021
  - Prevents style conflicts with host page
5858
6022
 
5859
6023
  Environment Variables
5860
- - Use ${code(messages_arg('EXTENSION_PUBLIC_*'))} prefix for variables accessible in extension code
5861
- - Supported in both ${code('process.env')} and ${code('import.meta.env')}
5862
- - Environment file priority: ${external_pintor_default().underline(code(messages_arg('.env.{browser}.{mode}')))} > ${external_pintor_default().underline(code(messages_arg('.env.{browser}')))} > ${external_pintor_default().underline(code(messages_arg('.env.{mode}')))} > ${external_pintor_default().underline(code(messages_arg('.env')))}
5863
- - Example: ${code(messages_arg('EXTENSION_PUBLIC_API_KEY=your_key'))}
6024
+ - Use ${messages_code(messages_arg('EXTENSION_PUBLIC_*'))} prefix for variables accessible in extension code
6025
+ - Supported in both ${messages_code('process.env')} and ${messages_code('import.meta.env')}
6026
+ - Environment file priority: ${external_pintor_default().underline(messages_code(messages_arg('.env.{browser}.{mode}')))} > ${external_pintor_default().underline(messages_code(messages_arg('.env.{browser}')))} > ${external_pintor_default().underline(messages_code(messages_arg('.env.{mode}')))} > ${external_pintor_default().underline(messages_code(messages_arg('.env')))}
6027
+ - Example: ${messages_code(messages_arg('EXTENSION_PUBLIC_API_KEY=your_key'))}
5864
6028
 
5865
6029
  Available Templates
5866
- - ${external_pintor_default().green('Frameworks')}: ${code(messages_arg('react'))}, ${code(messages_arg('preact'))}, ${code(messages_arg('vue'))}, ${code(messages_arg('svelte'))}
5867
- - ${external_pintor_default().green('Languages')}: ${code(messages_arg("javascript"))}, ${code(messages_arg("typescript"))}
5868
- - ${external_pintor_default().green('Contexts')}: ${code(messages_arg('content'))} (content scripts), ${code(messages_arg('new'))} (new tab), ${code(messages_arg('action'))} (popup)
5869
- - ${external_pintor_default().green('Styling')}: ${code(messages_arg('tailwind'))}, ${code(messages_arg('sass'))}, ${code(messages_arg('less'))}
5870
- - ${external_pintor_default().green('Configs')}: ${code(messages_arg('eslint'))}, ${code(messages_arg('prettier'))}, ${code(messages_arg('stylelint'))}
6030
+ - ${external_pintor_default().green('Frameworks')}: ${messages_code(messages_arg('react'))}, ${messages_code(messages_arg('preact'))}, ${messages_code(messages_arg('vue'))}, ${messages_code(messages_arg('svelte'))}
6031
+ - ${external_pintor_default().green('Languages')}: ${messages_code(messages_arg("javascript"))}, ${messages_code(messages_arg("typescript"))}
6032
+ - ${external_pintor_default().green('Contexts')}: ${messages_code(messages_arg('content'))} (content scripts), ${messages_code(messages_arg('new'))} (new tab), ${messages_code(messages_arg('action'))} (popup)
6033
+ - ${external_pintor_default().green('Styling')}: ${messages_code(messages_arg('tailwind'))}, ${messages_code(messages_arg('sass'))}, ${messages_code(messages_arg('less'))}
6034
+ - ${external_pintor_default().green('Configs')}: ${messages_code(messages_arg('eslint'))}, ${messages_code(messages_arg('prettier'))}, ${messages_code(messages_arg('stylelint'))}
5871
6035
 
5872
6036
  Webpack/Rspack Configuration
5873
- - Create ${external_pintor_default().underline(code(messages_arg('extension.config.js')))} for custom webpack configuration
6037
+ - Create ${external_pintor_default().underline(messages_code(messages_arg('extension.config.js')))} for custom webpack configuration
5874
6038
  - Function receives base config, return modified config
5875
6039
  - Supports all webpack/rspack loaders and plugins
5876
6040
  - Example:
5877
- ${code('export default {')}
5878
- ${code(' config: (config) => {')}
5879
- ${code(" config.module.rules.push({ test: /\\.svg$/, use: ['@svgr/webpack'] })")}
5880
- ${code(' return config')}
5881
- ${code(' }')}
5882
- ${code('}')}
6041
+ ${messages_code('export default {')}
6042
+ ${messages_code(' config: (config) => {')}
6043
+ ${messages_code(" config.module.rules.push({ test: /\\.svg$/, use: ['@svgr/webpack'] })")}
6044
+ ${messages_code(' return config')}
6045
+ ${messages_code(' }')}
6046
+ ${messages_code('}')}
5883
6047
 
5884
6048
  Managed Dependencies (Important)
5885
6049
  - ${external_pintor_default().green('Do not add')} packages that ${external_pintor_default().blue('Extension.js')} already ships in its own toolchain.
5886
- - The guard only triggers when a managed package is declared in your ${code('package.json')} ${external_pintor_default().gray('and')} is referenced in your ${external_pintor_default().underline(code('extension.config.js'))}.
6050
+ - The guard only triggers when a managed package is declared in your ${messages_code('package.json')} ${external_pintor_default().gray('and')} is imported (as a module specifier) in your ${external_pintor_default().underline(messages_code('extension.config.js'))}.
5887
6051
  - In that case, the program will ${external_pintor_default().red('print an error and abort')} to avoid version conflicts.
5888
- - Remove the duplicate from your project ${code('package.json')} or avoid referencing it in ${external_pintor_default().underline(code('extension.config.js'))} and rely on the built-in version instead.
6052
+ - Remove the duplicate from your project ${messages_code('package.json')} or avoid referencing it in ${external_pintor_default().underline(messages_code('extension.config.js'))} and rely on the built-in version instead.
5889
6053
  - If you truly need a different version, open an issue so we can evaluate a safe upgrade.
5890
6054
 
5891
6055
  Framework-Specific Configuration
5892
- - Create ${external_pintor_default().underline(code(messages_arg('vue.loader.js')))} for Vue-specific loader configuration
5893
- - Create ${external_pintor_default().underline(code(messages_arg('svelte.loader.js')))} for Svelte-specific loader configuration
6056
+ - Create ${external_pintor_default().underline(messages_code(messages_arg('vue.loader.js')))} for Vue-specific loader configuration
6057
+ - Create ${external_pintor_default().underline(messages_code(messages_arg('svelte.loader.js')))} for Svelte-specific loader configuration
5894
6058
  - Automatically detected and used by Extension.js
5895
6059
  - Example svelte.loader.js:
5896
- ${code('module.exports = {')}
5897
- ${code(' preprocess: require("svelte-preprocess")({')}
5898
- ${code(" typescript: true")}
5899
- ${code(' })')}
5900
- ${code('}')}
6060
+ ${messages_code('module.exports = {')}
6061
+ ${messages_code(' preprocess: require("svelte-preprocess")({')}
6062
+ ${messages_code(" typescript: true")}
6063
+ ${messages_code(' })')}
6064
+ ${messages_code('}')}
5901
6065
 
5902
6066
  Hot Module Replacement (HMR)
5903
6067
  - Automatically enabled in development mode
@@ -5907,63 +6071,63 @@ Hot Module Replacement (HMR)
5907
6071
  - Service workers, _locales and manifest changes reload the extension
5908
6072
 
5909
6073
  Source Inspection & Real-Time Monitoring
5910
- - Use ${code('extension dev --source')} ${messages_arg('<url|boolean>')} to inspect page HTML after content script injection
6074
+ - Use ${messages_code('extension dev --source')} ${messages_arg('<url|boolean>')} to inspect page HTML after content script injection
5911
6075
  - When no URL is provided, falls back to ${messages_arg('--starting-url')} or ${messages_arg('https://example.com')}
5912
- - Watch mode is enabled by default when ${code('--source')} is present
5913
- - Use ${code('--watch-source')} to re-print HTML on rebuilds or file changes
5914
- - Use ${code('--source-format')} ${messages_arg('<pretty|json|ndjson>')} for machine-friendly HTML output
5915
- - Use ${code('--source-summary')} to emit a compact JSON summary instead of full HTML
5916
- - Use ${code('--source-meta')} to emit page metadata (readyState, viewport, frames)
5917
- - Use ${code('--source-probe')} to probe CSS selectors for quick validation
5918
- - Use ${code('--source-tree')} to emit a compact extension root tree
5919
- - Use ${code('--source-console')} to emit a console summary (best-effort)
5920
- - Use ${code('--source-dom')} to emit DOM snapshots and diffs
5921
- - Use ${code('--source-redact')} ${messages_arg('<off|safe|strict>')} to redact sensitive content
5922
- - Use ${code('--source-max-bytes')} ${messages_arg('<bytes>')} to limit output size
5923
- - Use ${code('--source-diff')} ${messages_arg('[boolean]')} to emit diff metadata for watch updates
6076
+ - Watch mode is enabled by default when ${messages_code('--source')} is present
6077
+ - Use ${messages_code('--watch-source')} to re-print HTML on rebuilds or file changes
6078
+ - Use ${messages_code('--source-format')} ${messages_arg('<pretty|json|ndjson>')} for machine-friendly HTML output
6079
+ - Use ${messages_code('--source-summary')} to emit a compact JSON summary instead of full HTML
6080
+ - Use ${messages_code('--source-meta')} to emit page metadata (readyState, viewport, frames)
6081
+ - Use ${messages_code('--source-probe')} to probe CSS selectors for quick validation
6082
+ - Use ${messages_code('--source-tree')} to emit a compact extension root tree
6083
+ - Use ${messages_code('--source-console')} to emit a console summary (best-effort)
6084
+ - Use ${messages_code('--source-dom')} to emit DOM snapshots and diffs
6085
+ - Use ${messages_code('--source-redact')} ${messages_arg('<off|safe|strict>')} to redact sensitive content
6086
+ - Use ${messages_code('--source-max-bytes')} ${messages_arg('<bytes>')} to limit output size
6087
+ - Use ${messages_code('--source-diff')} ${messages_arg('[boolean]')} to emit diff metadata for watch updates
5924
6088
  - Source events include frame context (frameId/frameUrl), and console summaries include best-effort script URLs.
5925
- - Action timeline events ${code('action_event')} report navigation, injection, rebuilds, snapshots, and reloads.
6089
+ - Action timeline events ${messages_code('action_event')} report navigation, injection, rebuilds, snapshots, and reloads.
5926
6090
  - Automatically enables Chrome remote debugging (port 9222) when source inspection is active
5927
- - Extracts Shadow DOM content from ${code('#extension-root')} or ${code('[data-extension-root=\"true\"]')} elements
6091
+ - Extracts Shadow DOM content from ${messages_code('#extension-root')} or ${messages_code('[data-extension-root=\"true\"]')} elements
5928
6092
  - Useful for debugging content script behavior and style injection
5929
- - Example: ${code('extension dev --source=' + messages_arg('https://example.com'))}
5930
- - ${messages_arg('Note:')} ${code('preview/start')} run in run-only mode and do not perform source inspection.
5931
- - For machine synchronization with ${code('--no-browser')}, use ${code('extension dev --wait --browser=<browser>')} or ${code('extension start --wait --browser=<browser>')} in a second process (use ${code('--wait-format=json')} when a parser consumes stdout).
6093
+ - Example: ${messages_code('extension dev --source=' + messages_arg('https://example.com'))}
6094
+ - ${messages_arg('Note:')} ${messages_code('preview/start')} run in run-only mode and do not perform source inspection.
6095
+ - For machine synchronization with ${messages_code('--no-browser')}, use ${messages_code('extension dev --wait --browser=<browser>')} or ${messages_code('extension start --wait --browser=<browser>')} in a second process (use ${messages_code('--wait-format=json')} when a parser consumes stdout).
5932
6096
 
5933
6097
  Non-Destructive Testing in CI
5934
- - Prefer ${code('EXTENSION_AUTHOR_MODE=development')} to copy local templates and avoid network.
5935
- - Reuse Playwright's Chromium via ${code('--chromium-binary')} path when available.
5936
- - Set ${code(messages_arg('EXTENSION_AUTO_EXIT_MS'))} and ${code(messages_arg('EXTENSION_FORCE_KILL_MS'))} for non-interactive dev sessions.
6098
+ - Prefer ${messages_code('EXTENSION_AUTHOR_MODE=development')} to copy local templates and avoid network.
6099
+ - Reuse Playwright's Chromium via ${messages_code('--chromium-binary')} path when available.
6100
+ - Set ${messages_code(messages_arg('EXTENSION_AUTO_EXIT_MS'))} and ${messages_code(messages_arg('EXTENSION_FORCE_KILL_MS'))} for non-interactive dev sessions.
5937
6101
 
5938
6102
  File Watching & HMR Examples
5939
6103
  - Content script JS/TS changes trigger reinjection; CSS changes update styles live.
5940
- - For watch-source HTML prints, update a visible string in ${code("content/scripts.*")} and assert it appears in stdout.
6104
+ - For watch-source HTML prints, update a visible string in ${messages_code("content/scripts.*")} and assert it appears in stdout.
5941
6105
 
5942
6106
  Troubleshooting
5943
- - If HTML is not printed, ensure ${code('--source')} is provided and browser launched with debugging port.
5944
- - Use ${code('--silent true')} during builds to reduce noise; logs still surface errors.
5945
- - When ports conflict, pass ${code('--port 0')} to auto-select an available port.
5946
- - In Docker/devcontainers, pass ${code('--host 0.0.0.0')} so the dev server is reachable from the host.
6107
+ - If HTML is not printed, ensure ${messages_code('--source')} is provided and browser launched with debugging port.
6108
+ - Use ${messages_code('--silent true')} during builds to reduce noise; logs still surface errors.
6109
+ - When ports conflict, pass ${messages_code('--port 0')} to auto-select an available port.
6110
+ - In Docker/devcontainers, pass ${messages_code('--host 0.0.0.0')} so the dev server is reachable from the host.
5947
6111
 
5948
6112
  Non-Interactive / Auto Mode (CI)
5949
- - Set ${code(messages_arg('EXTENSION_AUTO_EXIT_MS'))} to enable self-termination after N milliseconds.
5950
- Useful when ${code('pnpm extension dev')} would otherwise hang under Rspack watch.
5951
- Example: ${code(messages_arg('EXTENSION_AUTO_EXIT_MS=6000'))} pnpm extension dev ./templates/react --browser chrome --source ${messages_arg('https://example.com')}
5952
- - Optional: ${code(messages_arg('EXTENSION_FORCE_KILL_MS'))} to hard-exit after N ms as a fallback (defaults to auto-exit + 4000).
6113
+ - Set ${messages_code(messages_arg('EXTENSION_AUTO_EXIT_MS'))} to enable self-termination after N milliseconds.
6114
+ Useful when ${messages_code('pnpm extension dev')} would otherwise hang under Rspack watch.
6115
+ Example: ${messages_code(messages_arg('EXTENSION_AUTO_EXIT_MS=6000'))} pnpm extension dev ./templates/react --browser chrome --source ${messages_arg('https://example.com')}
6116
+ - Optional: ${messages_code(messages_arg('EXTENSION_FORCE_KILL_MS'))} to hard-exit after N ms as a fallback (defaults to auto-exit + 4000).
5953
6117
 
5954
6118
  Docker / Devcontainers / Codespaces
5955
- - Use ${code('--host 0.0.0.0')} to bind the dev server on all interfaces so HMR is reachable from the host.
5956
- - Use ${code('--no-browser')} inside the container and load the extension manually from ${code('dist/<browser>/')} in your host browser.
5957
- - Chromium sandbox flags (${code('--no-sandbox')}) are added automatically when Docker, Podman, devcontainers, or Codespaces are detected.
6119
+ - Use ${messages_code('--host 0.0.0.0')} to bind the dev server on all interfaces so HMR is reachable from the host.
6120
+ - Use ${messages_code('--no-browser')} inside the container and load the extension manually from ${messages_code('dist/<browser>/')} in your host browser.
6121
+ - Chromium sandbox flags (${messages_code('--no-sandbox')}) are added automatically when Docker, Podman, devcontainers, or Codespaces are detected.
5958
6122
  - File watching uses polling by default (1 s interval), which works across bind-mounted volumes.
5959
- - Example: ${code('extension dev ./my-ext --host 0.0.0.0 --no-browser --port 8080')}
6123
+ - Example: ${messages_code('extension dev ./my-ext --host 0.0.0.0 --no-browser --port 8080')}
5960
6124
 
5961
6125
  Flatpak Firefox
5962
- - Use ${code('--gecko-binary flatpak:org.mozilla.firefox')} (or ${code('--firefox-binary')}) to launch a Flatpak-installed Firefox.
5963
- - Extension.js rewrites the binary path to ${code('flatpak run')} with the correct filesystem grants automatically.
6126
+ - Use ${messages_code('--gecko-binary flatpak:org.mozilla.firefox')} (or ${messages_code('--firefox-binary')}) to launch a Flatpak-installed Firefox.
6127
+ - Extension.js rewrites the binary path to ${messages_code('flatpak run')} with the correct filesystem grants automatically.
5964
6128
 
5965
6129
  Cross-Browser Compatibility
5966
- - Use ${code('--polyfill')} flag to enable webextension-polyfill
6130
+ - Use ${messages_code('--polyfill')} flag to enable webextension-polyfill
5967
6131
  - Automatically handles browser API differences
5968
6132
  - Supports Chrome, Edge, Firefox with single codebase`;
5969
6133
  }
@@ -5996,6 +6160,41 @@ Cross-Browser Compatibility
5996
6160
  summary: commandDescriptions.build,
5997
6161
  supportsSourceInspection: false
5998
6162
  },
6163
+ {
6164
+ name: 'logs',
6165
+ summary: commandDescriptions.logs,
6166
+ supportsSourceInspection: false
6167
+ },
6168
+ {
6169
+ name: 'eval',
6170
+ summary: commandDescriptions.eval,
6171
+ supportsSourceInspection: false
6172
+ },
6173
+ {
6174
+ name: 'storage',
6175
+ summary: commandDescriptions.storage,
6176
+ supportsSourceInspection: false
6177
+ },
6178
+ {
6179
+ name: 'reload',
6180
+ summary: commandDescriptions.reload,
6181
+ supportsSourceInspection: false
6182
+ },
6183
+ {
6184
+ name: 'open',
6185
+ summary: commandDescriptions.open,
6186
+ supportsSourceInspection: false
6187
+ },
6188
+ {
6189
+ name: 'inspect',
6190
+ summary: commandDescriptions.inspect,
6191
+ supportsSourceInspection: true
6192
+ },
6193
+ {
6194
+ name: 'publish',
6195
+ summary: commandDescriptions.publish,
6196
+ supportsSourceInspection: false
6197
+ },
5999
6198
  {
6000
6199
  name: 'install',
6001
6200
  summary: commandDescriptions.install,
@@ -6095,9 +6294,45 @@ Cross-Browser Compatibility
6095
6294
  },
6096
6295
  managedDependencies: {
6097
6296
  enforcement: 'guarded',
6098
- trigger: 'when managed packages are declared in package.json and referenced in extension.config',
6297
+ trigger: 'when managed packages are declared in package.json and imported as a module specifier in extension.config',
6099
6298
  action: 'print an error and abort'
6100
6299
  },
6300
+ readyContract: {
6301
+ readyPath: 'dist/extension-js/<browser>/ready.json',
6302
+ eventsPath: 'dist/extension-js/<browser>/events.ndjson',
6303
+ waitFlag: '--wait blocks until ready.json reports ready (or error) then exits; pair with --wait-format=json for machine output',
6304
+ statuses: [
6305
+ 'starting',
6306
+ 'ready',
6307
+ 'error'
6308
+ ],
6309
+ readyFields: [
6310
+ 'status',
6311
+ 'command',
6312
+ 'browser',
6313
+ 'runId',
6314
+ 'startedAt',
6315
+ 'distPath',
6316
+ 'manifestPath',
6317
+ 'port',
6318
+ 'pid',
6319
+ 'ts',
6320
+ 'compiledAt',
6321
+ 'errors'
6322
+ ],
6323
+ eventTypes: [
6324
+ 'compile_start',
6325
+ 'compile_success',
6326
+ 'compile_error',
6327
+ 'shutdown'
6328
+ ],
6329
+ notes: [
6330
+ 'ready.json is written atomically by the build (dev/start) on each compile',
6331
+ 'events.ndjson is an append-only build timeline with durationMs and errorCount per entry',
6332
+ '--wait requires a local project path (remote URLs are not supported)',
6333
+ 'consumers should verify pid liveness and recency before trusting a contract'
6334
+ ]
6335
+ },
6101
6336
  dockerAndContainers: {
6102
6337
  hostFlag: '--host 0.0.0.0 binds the dev server to all interfaces',
6103
6338
  sandboxDetection: [
@@ -6131,22 +6366,22 @@ Cross-Browser Compatibility
6131
6366
  };
6132
6367
  }
6133
6368
  function invalidAIHelpFormat(value) {
6134
- return `${getLoggingPrefix('error')} Invalid value for ${code('--format')}: ${external_pintor_default().red(String(value))}\nAllowed values: ${messages_arg('pretty, json')}. Example: ${code('extension --ai-help --format json')}`;
6369
+ return `${getLoggingPrefix('error')} Invalid value for ${messages_code('--format')}: ${external_pintor_default().red(String(value))}\nAllowed values: ${messages_arg('pretty, json')}. Example: ${messages_code('extension --ai-help --format json')}`;
6135
6370
  }
6136
6371
  function sourceInspectionNotSupported(command) {
6137
- return `${getLoggingPrefix('error')} ${code(`extension ${command}`)} currently runs in run-only preview mode and does not support source inspection.\nUse ${code('extension dev --source <url>')} for source inspection features.`;
6372
+ return `${getLoggingPrefix('error')} ${messages_code(`extension ${command}`)} currently runs in run-only preview mode and does not support source inspection.\nUse ${messages_code('extension dev --source <url>')} for source inspection features.`;
6138
6373
  }
6139
6374
  function removedNoRunnerFlag() {
6140
- return `${getLoggingPrefix('error')} ${code('--no-runner')} was removed.\nUse ${code('--no-browser')} instead.`;
6375
+ return `${getLoggingPrefix('error')} ${messages_code('--no-runner')} was removed.\nUse ${messages_code('--no-browser')} instead.`;
6141
6376
  }
6142
6377
  function noBrowserNotSupportedForCommand(command) {
6143
- return `${getLoggingPrefix('error')} ${code('--no-browser')} is only supported for ${code('dev')}, ${code('start')}, and ${code('preview')}.\nReceived command: ${code(command || '(none)')}`;
6378
+ return `${getLoggingPrefix('error')} ${messages_code('--no-browser')} is only supported for ${messages_code('dev')}, ${messages_code('start')}, and ${messages_code('preview')}.\nReceived command: ${messages_code(command || '(none)')}`;
6144
6379
  }
6145
6380
  function sourceIncompatibleWithWait() {
6146
- return `${getLoggingPrefix('error')} ${code('--source')} cannot be combined with ${code('--wait')}.\nSource inspection requires a live browser session; ${code('--wait')} exits as soon as the ready contract is satisfied.\nRun them separately: ${code('extension dev --wait')} for the readiness gate, then ${code('extension dev --source <url>')} for inspection.`;
6381
+ return `${getLoggingPrefix('error')} ${messages_code('--source')} cannot be combined with ${messages_code('--wait')}.\nSource inspection requires a live browser session; ${messages_code('--wait')} exits as soon as the ready contract is satisfied.\nRun them separately: ${messages_code('extension dev --wait')} for the readiness gate, then ${messages_code('extension dev --source <url>')} for inspection.`;
6147
6382
  }
6148
6383
  function sourceIncompatibleWithNoBrowser() {
6149
- return `${getLoggingPrefix('error')} ${code('--source')} cannot be combined with ${code('--no-browser')}.\nSource inspection drives a real browser via CDP/RDP and cannot run headlessly against the dev server alone.\nDrop ${code('--no-browser')} or omit the ${code('--source')} flags to continue.`;
6384
+ return `${getLoggingPrefix('error')} ${messages_code('--source')} cannot be combined with ${messages_code('--no-browser')}.\nSource inspection drives a real browser via CDP/RDP and cannot run headlessly against the dev server alone.\nDrop ${messages_code('--no-browser')} or omit the ${messages_code('--source')} flags to continue.`;
6150
6385
  }
6151
6386
  const external_semver_namespaceObject = require("semver");
6152
6387
  const external_node_fs_namespaceObject = require("node:fs");
@@ -6303,6 +6538,15 @@ Cross-Browser Compatibility
6303
6538
  if ('workspace' === source) throw new Error(`Local extension-develop runtime is not built at ${external_path_default().join(root, 'dist')}. Run \`pnpm --filter extension-develop compile\` before invoking the local CLI.`);
6304
6539
  return await import("extension-develop/preview");
6305
6540
  }
6541
+ async function loadExtensionDevelopBridgeModule(startDir = __dirname) {
6542
+ const root = resolveExtensionDevelopRoot(startDir);
6543
+ const { source } = resolvePreferredDevelopRoot(startDir);
6544
+ const bridgeEntry = resolveDevelopDistEntry(root, 'bridge');
6545
+ if (bridgeEntry) return importModule(bridgeEntry);
6546
+ if ('workspace' === source) throw new Error(`Local extension-develop runtime is not built at ${external_path_default().join(root, 'dist')}. Run \`pnpm --filter extension-develop compile\` before invoking the local CLI.`);
6547
+ const bridgeSpecifier = 'extension-develop/bridge';
6548
+ return await import(bridgeSpecifier);
6549
+ }
6306
6550
  const external_node_os_namespaceObject = require("node:os");
6307
6551
  var external_node_os_default = /*#__PURE__*/ __webpack_require__.n(external_node_os_namespaceObject);
6308
6552
  const external_node_crypto_namespaceObject = require("node:crypto");
@@ -6321,7 +6565,7 @@ Cross-Browser Compatibility
6321
6565
  const DEFAULT_MAX_EVENTS = Number(process.env.EXTENSION_TELEMETRY_MAX_EVENTS || 3);
6322
6566
  const DEFAULT_DEBOUNCE_MS = Number(process.env.EXTENSION_TELEMETRY_DEBOUNCE_MS || 60000);
6323
6567
  const DEFAULT_TIMEOUT_MS = Number(process.env.EXTENSION_TELEMETRY_TIMEOUT_MS || 300);
6324
- const DEFAULT_POSTHOG_KEY = process.env.POSTHOG_KEY || '';
6568
+ const DEFAULT_POSTHOG_KEY = process.env.POSTHOG_KEY || 'phc_Np5x3Jg3h2V7kTFtNch2uz6QBaWDycQpIidzX5PetaN';
6325
6569
  const DEFAULT_POSTHOG_HOST = process.env.POSTHOG_HOST || 'https://us.i.posthog.com';
6326
6570
  function isCI() {
6327
6571
  const v = process.env;
@@ -6645,6 +6889,9 @@ Cross-Browser Compatibility
6645
6889
  markCommandFailure();
6646
6890
  });
6647
6891
  }
6892
+ function isSafariVendor(value) {
6893
+ return 'safari' === value || 'webkit-based' === value;
6894
+ }
6648
6895
  function parseOptionalBoolean(value) {
6649
6896
  if (void 0 === value) return true;
6650
6897
  const normalized = String(value).trim().toLowerCase();
@@ -6671,7 +6918,9 @@ Cross-Browser Compatibility
6671
6918
  'chromium',
6672
6919
  'chromium-based',
6673
6920
  'gecko-based',
6674
- 'firefox-based'
6921
+ 'firefox-based',
6922
+ 'safari',
6923
+ 'webkit-based'
6675
6924
  ];
6676
6925
  for (const v of vendorsList)if (!supported.includes(v)) {
6677
6926
  onInvalid(v, supported);
@@ -6702,6 +6951,7 @@ Cross-Browser Compatibility
6702
6951
  });
6703
6952
  });
6704
6953
  }
6954
+ const READY_CONTRACT_FRESHNESS_MS = 60000;
6705
6955
  function isHttpUrl(value) {
6706
6956
  if (!value) return false;
6707
6957
  return /^https?:\/\//i.test(value);
@@ -6731,7 +6981,7 @@ Cross-Browser Compatibility
6731
6981
  return false;
6732
6982
  }
6733
6983
  }
6734
- function isFreshContractPayload(payload, maxAgeMs) {
6984
+ function isFreshContractPayload(payload) {
6735
6985
  const candidates = [
6736
6986
  payload.ts,
6737
6987
  payload.compiledAt,
@@ -6740,7 +6990,7 @@ Cross-Browser Compatibility
6740
6990
  for (const candidate of candidates){
6741
6991
  if (!candidate) continue;
6742
6992
  const stamp = Date.parse(candidate);
6743
- if (Number.isFinite(stamp)) return Date.now() - stamp <= maxAgeMs;
6993
+ if (Number.isFinite(stamp)) return Date.now() - stamp <= READY_CONTRACT_FRESHNESS_MS;
6744
6994
  }
6745
6995
  return false;
6746
6996
  }
@@ -6751,7 +7001,7 @@ Cross-Browser Compatibility
6751
7001
  if (external_node_fs_namespaceObject.existsSync(readyPath)) try {
6752
7002
  const payload = JSON.parse(external_node_fs_namespaceObject.readFileSync(readyPath, 'utf8'));
6753
7003
  const isLive = isProcessLikelyAlive(payload.pid);
6754
- const isFresh = isFreshContractPayload(payload, options.timeoutMs);
7004
+ const isFresh = isFreshContractPayload(payload);
6755
7005
  if (payload.command !== options.command) {
6756
7006
  await new Promise((resolve)=>setTimeout(resolve, 250));
6757
7007
  continue;
@@ -6807,6 +7057,272 @@ Cross-Browser Compatibility
6807
7057
  };
6808
7058
  }
6809
7059
  var browsers = __webpack_require__("./browsers/index.ts");
7060
+ var external_child_process_ = __webpack_require__("child_process");
7061
+ var messages = __webpack_require__("./browsers/browsers-lib/messages.ts");
7062
+ function isMacOS() {
7063
+ return 'darwin' === process.platform;
7064
+ }
7065
+ function activeDeveloperDir() {
7066
+ try {
7067
+ const result = (0, external_child_process_.spawnSync)('xcode-select', [
7068
+ '-p'
7069
+ ], {
7070
+ encoding: 'utf8',
7071
+ timeout: 15000
7072
+ });
7073
+ if (0 !== result.status) return null;
7074
+ const resolved = String(result.stdout || '').trim();
7075
+ return resolved.length > 0 ? resolved : null;
7076
+ } catch {
7077
+ return null;
7078
+ }
7079
+ }
7080
+ function findWithXcrun(tool) {
7081
+ try {
7082
+ const result = (0, external_child_process_.spawnSync)('xcrun', [
7083
+ '--find',
7084
+ tool
7085
+ ], {
7086
+ encoding: 'utf8',
7087
+ timeout: 15000
7088
+ });
7089
+ if (0 !== result.status) return null;
7090
+ const resolved = String(result.stdout || '').trim();
7091
+ return resolved.length > 0 ? resolved : null;
7092
+ } catch {
7093
+ return null;
7094
+ }
7095
+ }
7096
+ function detectSafariToolchain() {
7097
+ const platformOk = isMacOS();
7098
+ if (!platformOk) return {
7099
+ platformOk: false,
7100
+ developerDir: null,
7101
+ needsFullXcode: false,
7102
+ converter: null,
7103
+ xcodebuild: null,
7104
+ ok: false
7105
+ };
7106
+ const developerDir = activeDeveloperDir();
7107
+ const needsFullXcode = !developerDir || /CommandLineTools/i.test(developerDir);
7108
+ const converter = findWithXcrun('safari-web-extension-converter');
7109
+ const xcodebuild = findWithXcrun('xcodebuild');
7110
+ return {
7111
+ platformOk,
7112
+ developerDir,
7113
+ needsFullXcode,
7114
+ converter,
7115
+ xcodebuild,
7116
+ ok: Boolean(converter && xcodebuild)
7117
+ };
7118
+ }
7119
+ function logSafariDryRun(converterCmd, xcodebuildCmd) {
7120
+ console.log(messages.KP6());
7121
+ console.log(messages.ycc());
7122
+ console.log(messages.fLj(converterCmd));
7123
+ console.log(messages.Ajw(xcodebuildCmd));
7124
+ }
7125
+ function sanitizeAppName(value) {
7126
+ const cleaned = String(value || '').replace(/[\\/:*?"<>|]+/g, ' ').replace(/\s+/g, ' ').trim();
7127
+ return cleaned.length > 0 ? cleaned : 'Extension';
7128
+ }
7129
+ function bundleSegment(appName) {
7130
+ return String(appName || '').replace(/[^A-Za-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'extension';
7131
+ }
7132
+ function deriveBundleId(appName) {
7133
+ return `dev.extensionjs.${bundleSegment(appName)}`;
7134
+ }
7135
+ function readManifest(extensionDir) {
7136
+ try {
7137
+ const manifestPath = external_path_.join(extensionDir, 'manifest.json');
7138
+ if (external_fs_.existsSync(manifestPath)) return JSON.parse(external_fs_.readFileSync(manifestPath, 'utf8')) || {};
7139
+ } catch {}
7140
+ return {};
7141
+ }
7142
+ function resolveSafariBuildConfig(compilation, host) {
7143
+ const extensionDir = String(compilation?.options?.output?.path || '');
7144
+ const manifest = readManifest(extensionDir);
7145
+ const appName = sanitizeAppName(String(host.appName || manifest?.name || 'Extension'));
7146
+ const bundleIdentifier = deriveBundleId(appName);
7147
+ const projectLocation = `${extensionDir.replace(/[\\/]+$/, '')}-xcode`;
7148
+ return {
7149
+ extensionDir,
7150
+ projectLocation,
7151
+ appName,
7152
+ bundleIdentifier,
7153
+ macOsOnly: false !== host.macOsOnly,
7154
+ language: 'swift',
7155
+ open: !host.noOpen,
7156
+ safariBinary: host.safariBinary
7157
+ };
7158
+ }
7159
+ function composeConverterArgs(config) {
7160
+ const args = [
7161
+ 'safari-web-extension-converter',
7162
+ config.extensionDir,
7163
+ '--project-location',
7164
+ config.projectLocation,
7165
+ '--app-name',
7166
+ config.appName,
7167
+ '--bundle-identifier',
7168
+ config.bundleIdentifier,
7169
+ '--no-prompt',
7170
+ '--no-open',
7171
+ '--force',
7172
+ 'objc' === config.language ? '--objc' : '--swift'
7173
+ ];
7174
+ if (config.macOsOnly) args.push('--macos-only');
7175
+ return args;
7176
+ }
7177
+ function macOsSchemeName(config) {
7178
+ return config.macOsOnly ? config.appName : `${config.appName} (macOS)`;
7179
+ }
7180
+ function xcodeProjectPath(config) {
7181
+ return external_path_.join(config.projectLocation, config.appName, `${config.appName}.xcodeproj`);
7182
+ }
7183
+ function derivedDataPath(config) {
7184
+ return external_path_.join(config.projectLocation, '.derived');
7185
+ }
7186
+ function builtAppPath(config) {
7187
+ return external_path_.join(derivedDataPath(config), 'Build', 'Products', 'Release', `${config.appName}.app`);
7188
+ }
7189
+ function composeXcodebuildArgs(config) {
7190
+ return [
7191
+ '-project',
7192
+ xcodeProjectPath(config),
7193
+ '-scheme',
7194
+ macOsSchemeName(config),
7195
+ '-configuration',
7196
+ 'Release',
7197
+ '-derivedDataPath',
7198
+ derivedDataPath(config),
7199
+ 'CODE_SIGN_IDENTITY=-',
7200
+ 'CODE_SIGNING_REQUIRED=NO',
7201
+ 'CODE_SIGNING_ALLOWED=YES',
7202
+ 'build'
7203
+ ];
7204
+ }
7205
+ function fallbackLogger() {
7206
+ return {
7207
+ info: (...a)=>console.log(...a),
7208
+ warn: (...a)=>console.warn(...a),
7209
+ error: (...a)=>console.error(...a),
7210
+ debug: (...a)=>console?.debug?.(...a)
7211
+ };
7212
+ }
7213
+ function isTestEnv() {
7214
+ return Boolean(process.env.VITEST || process.env.VITEST_WORKER_ID);
7215
+ }
7216
+ function delay(ms) {
7217
+ return new Promise((resolve)=>setTimeout(resolve, ms));
7218
+ }
7219
+ function runTool(bin, args) {
7220
+ const inheritOutput = 'true' === process.env.EXTENSION_AUTHOR_MODE;
7221
+ return new Promise((resolve)=>{
7222
+ const child = (0, external_child_process_.spawn)(bin, args, {
7223
+ stdio: inheritOutput ? 'inherit' : 'ignore'
7224
+ });
7225
+ child.on('error', ()=>resolve(false));
7226
+ child.on('close', (code)=>resolve(0 === code));
7227
+ });
7228
+ }
7229
+ function runToolCapture(bin, args) {
7230
+ return new Promise((resolve)=>{
7231
+ let stdout = '';
7232
+ const child = (0, external_child_process_.spawn)(bin, args, {
7233
+ stdio: [
7234
+ 'ignore',
7235
+ 'pipe',
7236
+ 'pipe'
7237
+ ]
7238
+ });
7239
+ child.stdout?.on('data', (chunk)=>{
7240
+ stdout += String(chunk);
7241
+ });
7242
+ child.on('error', ()=>resolve({
7243
+ ok: false,
7244
+ stdout
7245
+ }));
7246
+ child.on('close', (code)=>resolve({
7247
+ ok: 0 === code,
7248
+ stdout
7249
+ }));
7250
+ });
7251
+ }
7252
+ async function confirmRegisteredWithSafari(bundleIdentifier) {
7253
+ const needle = `${bundleIdentifier}.Extension`;
7254
+ for(let attempt = 0; attempt < 6; attempt += 1){
7255
+ const { ok, stdout } = await runToolCapture('pluginkit', [
7256
+ '-m'
7257
+ ]);
7258
+ if (ok && stdout.includes(needle)) return true;
7259
+ await delay(800);
7260
+ }
7261
+ return false;
7262
+ }
7263
+ function safariPreflightError() {
7264
+ const tc = detectSafariToolchain();
7265
+ if (!tc.platformOk) return messages.G3E(process.platform);
7266
+ if (!tc.ok) {
7267
+ if (tc.needsFullXcode) return messages.Xiv(tc.developerDir);
7268
+ return messages.Csg(tc.converter ? 'xcodebuild' : 'safari-web-extension-converter');
7269
+ }
7270
+ return null;
7271
+ }
7272
+ async function runSafariPipeline(compilation, host, logger, mode) {
7273
+ const config = resolveSafariBuildConfig(compilation, host);
7274
+ const converterArgs = composeConverterArgs(config);
7275
+ const xcodebuildArgs = composeXcodebuildArgs(config);
7276
+ if (host.dryRun || isTestEnv()) return void logSafariDryRun(`xcrun ${converterArgs.join(' ')}`, `xcodebuild ${xcodebuildArgs.join(' ')}`);
7277
+ const toolchain = detectSafariToolchain();
7278
+ if (!toolchain.platformOk) return void logger.warn?.(messages.G3E(process.platform));
7279
+ if (!toolchain.ok) {
7280
+ if (toolchain.needsFullXcode) logger.error?.(messages.Xiv(toolchain.developerDir));
7281
+ else {
7282
+ const missing = toolchain.converter ? 'xcodebuild' : 'safari-web-extension-converter';
7283
+ logger.error?.(messages.Csg(missing));
7284
+ }
7285
+ return;
7286
+ }
7287
+ const projectExists = external_fs_.existsSync(xcodeProjectPath(config));
7288
+ if (!projectExists || host.forceRegenerate) {
7289
+ logger.info?.(messages.uKN(config.extensionDir));
7290
+ if (!await runTool('xcrun', converterArgs)) return void logger.error?.(messages.Qrh(new Error('safari-web-extension-converter failed')));
7291
+ logger.info?.(messages.l_B(config.projectLocation));
7292
+ }
7293
+ if ('full' === mode) logger.info?.(messages.jRH(macOsSchemeName(config)));
7294
+ if (!await runTool('xcodebuild', xcodebuildArgs)) return void logger.error?.(messages.Qrh(new Error('xcodebuild failed')));
7295
+ const appPath = builtAppPath(config);
7296
+ if ('resync' === mode) return void logger.info?.(messages.cFJ(config.appName));
7297
+ logger.info?.(messages.Rl_(appPath));
7298
+ if (config.open) {
7299
+ const target = external_fs_.existsSync(appPath) ? appPath : xcodeProjectPath(config);
7300
+ logger.info?.(messages.fwU(target));
7301
+ await runTool('open', [
7302
+ target
7303
+ ]);
7304
+ if (config.safariBinary) await runTool('open', [
7305
+ '-a',
7306
+ config.safariBinary
7307
+ ]);
7308
+ }
7309
+ logger.info?.(messages.fOJ(config.appName));
7310
+ if (await confirmRegisteredWithSafari(config.bundleIdentifier)) logger.info?.(messages.fdc(config.appName));
7311
+ else logger.warn?.(messages.qYJ(config.appName));
7312
+ }
7313
+ async function packageSafariExtension(host, outputPath, logger, mode = 'full') {
7314
+ const compilation = {
7315
+ options: {
7316
+ output: {
7317
+ path: outputPath
7318
+ }
7319
+ },
7320
+ outputOptions: {
7321
+ path: outputPath
7322
+ }
7323
+ };
7324
+ await runSafariPipeline(compilation, host, logger || fallbackLogger(), mode);
7325
+ }
6810
7326
  function normalizeSourceOption(source, startingUrl) {
6811
7327
  if (!source) return;
6812
7328
  const hasExplicitSourceString = 'string' == typeof source && 'true' !== String(source).trim().toLowerCase();
@@ -6913,7 +7429,7 @@ Cross-Browser Compatibility
6913
7429
  return values.length > 0 ? values : void 0;
6914
7430
  }
6915
7431
  function registerDevCommand(program) {
6916
- program.command('dev').arguments('[project-path|remote-url]').usage('dev [project-path|remote-url] [options]').description(commandDescriptions.dev).addHelpText('after', "\nAdditional options:\n --no-browser do not launch the browser (dev server still starts)\n --no-reload emit a dev-mode dist without the content-script reload runtime; tabs need manual reload to see changes\n --wait wait for ready contract and exit\n --wait-format pretty|json output for wait mode\n").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 <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based>', 'specify a browser/engine to run. Defaults to `chromium`').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').option('--gecko-binary, --firefox-binary <path-to-binary>', 'specify a path to the Gecko binary. This option overrides the --browser setting. Defaults to the system default').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `false`').option('--no-open', 'do not open the browser automatically (default: open)').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('--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('--source [url]', "[experimental] opens the provided URL in Chrome and prints the full, live HTML of the page after content scripts are injected").option('--watch-source [boolean]', '[experimental] re-print HTML on rebuilds or file changes (defaults to true when --source is present)', parseOptionalBoolean).option('--source-format <pretty|json|ndjson>', '[experimental] output format for source HTML (defaults to --log-format when present, otherwise JSON when --source is used)').option('--source-summary [boolean]', '[experimental] output a compact summary instead of full HTML', parseOptionalBoolean).option('--source-meta [boolean]', '[experimental] output page metadata (readyState, viewport, frames)', parseOptionalBoolean).option('--source-probe <selectors>', '[experimental] comma-separated CSS selectors to probe').option('--source-tree <off|root-only>', '[experimental] output a compact extension root tree').option('--source-console [boolean]', '[experimental] output console summary (best-effort)', parseOptionalBoolean).option('--source-dom [boolean]', '[experimental] output DOM snapshots and diffs (default: true when watch is enabled)', parseOptionalBoolean).option('--source-max-bytes <bytes>', '[experimental] limit HTML output size in bytes (0 disables truncation)').option('--source-redact <off|safe|strict>', '[experimental] redact sensitive content in HTML output (default: safe for JSON/NDJSON)').option('--source-include-shadow <off|open-only|all>', '[experimental] control Shadow DOM inclusion in HTML output (default: open-only)').option('--source-diff [boolean]', '[experimental] include diff metadata on watch updates (default: true when watch is enabled)', parseOptionalBoolean).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('--wait-format <pretty|json>', 'output format for --wait results (default: pretty)').option('--author, --author-mode', '[internal] enable maintainer diagnostics (does not affect user runtime logs)').action(async function(pathOrRemoteUrl, { browser = 'chromium', ...devOptions }) {
7432
+ program.command('dev').arguments('[project-path|remote-url]').usage('dev [project-path|remote-url] [options]').description(commandDescriptions.dev).addHelpText('after', "\nAdditional options:\n --no-browser do not launch the browser (dev server still starts)\n --no-reload emit a dev-mode dist without the content-script reload runtime; tabs need manual reload to see changes\n --wait wait for ready contract and exit\n --wait-format pretty|json output for wait mode\n").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 <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based | safari | webkit-based>', 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds and opens a Safari app via Xcode (macOS only; no live reload)').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').option('--gecko-binary, --firefox-binary <path-to-binary>', 'specify a path to the Gecko binary. This option overrides the --browser setting. Defaults to the system default').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `true`').option('--no-open', 'do not open the browser automatically (default: open)').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('--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('--source [url]', "[experimental] opens the provided URL in Chrome and prints the full, live HTML of the page after content scripts are injected").option('--watch-source [boolean]', '[experimental] re-print HTML on rebuilds or file changes (defaults to true when --source is present)', parseOptionalBoolean).option('--source-format <pretty|json|ndjson>', '[experimental] output format for source HTML (defaults to --log-format when present, otherwise JSON when --source is used)').option('--source-summary [boolean]', '[experimental] output a compact summary instead of full HTML', parseOptionalBoolean).option('--source-meta [boolean]', '[experimental] output page metadata (readyState, viewport, frames)', parseOptionalBoolean).option('--source-probe <selectors>', '[experimental] comma-separated CSS selectors to probe').option('--source-tree <off|root-only>', '[experimental] output a compact extension root tree').option('--source-console [boolean]', '[experimental] output console summary (best-effort)', parseOptionalBoolean).option('--source-dom [boolean]', '[experimental] output DOM snapshots and diffs (default: true when watch is enabled)', parseOptionalBoolean).option('--source-max-bytes <bytes>', '[experimental] limit HTML output size in bytes (0 disables truncation)').option('--source-redact <off|safe|strict>', '[experimental] redact sensitive content in HTML output (default: safe for JSON/NDJSON)').option('--source-include-shadow <off|open-only|all>', '[experimental] control Shadow DOM inclusion in HTML output (default: open-only)').option('--source-diff [boolean]', '[experimental] include diff metadata on watch updates (default: true when watch is enabled)', parseOptionalBoolean).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('--wait-format <pretty|json>', 'output format for --wait results (default: pretty)').option('--author, --author-mode', '[internal] enable maintainer diagnostics (does not affect user runtime logs)').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` (runs arbitrary code in a context; writes a 0600 session token)').action(async function(pathOrRemoteUrl, { browser = 'chromium', ...devOptions }) {
6917
7433
  if (devOptions.author || devOptions['authorMode']) {
6918
7434
  process.env.EXTENSION_AUTHOR_MODE = 'true';
6919
7435
  if (!process.env.EXTENSION_VERBOSE) process.env.EXTENSION_VERBOSE = '1';
@@ -6922,6 +7438,13 @@ Cross-Browser Compatibility
6922
7438
  validateVendorsOrExit(list, (invalid, supported)=>{
6923
7439
  console.error(unsupportedBrowserFlag(invalid, supported));
6924
7440
  });
7441
+ if (list.some(isSafariVendor)) {
7442
+ const issue = safariPreflightError();
7443
+ if (issue) {
7444
+ console.error(issue);
7445
+ process.exit(1);
7446
+ }
7447
+ }
6925
7448
  if (devOptions.wait) {
6926
7449
  const waitResult = await runWaitMode({
6927
7450
  command: 'dev',
@@ -7000,7 +7523,17 @@ Cross-Browser Compatibility
7000
7523
  logColor: false !== devOptions.logColor,
7001
7524
  logUrl: devOptions.logUrl,
7002
7525
  logTab: devOptions.logTab,
7003
- launcher: noBrowser ? void 0 : browsers.launchBrowser
7526
+ launcher: noBrowser ? void 0 : browsers.launchBrowser,
7527
+ safariPackager: async (distPath, mode)=>{
7528
+ await packageSafariExtension({
7529
+ extension: [
7530
+ distPath
7531
+ ],
7532
+ browser: vendor,
7533
+ noOpen: false === devOptions.open,
7534
+ dryRun: false
7535
+ }, distPath, void 0, mode);
7536
+ }
7004
7537
  };
7005
7538
  await extensionDev(pathOrRemoteUrl, devArgs);
7006
7539
  }
@@ -7017,6 +7550,10 @@ Cross-Browser Compatibility
7017
7550
  validateVendorsOrExit(list, (invalid, supported)=>{
7018
7551
  console.error(unsupportedBrowserFlag(invalid, supported));
7019
7552
  });
7553
+ if (list.some(isSafariVendor)) {
7554
+ console.error(safariCommandNotSupported('start'));
7555
+ process.exit(1);
7556
+ }
7020
7557
  if (startOptions.wait) {
7021
7558
  const waitResult = await runWaitMode({
7022
7559
  command: 'start',
@@ -7058,6 +7595,7 @@ Cross-Browser Compatibility
7058
7595
  geckoBinary: startOptions.geckoBinary,
7059
7596
  startingUrl: startOptions.startingUrl,
7060
7597
  port: startOptions.port,
7598
+ host: startOptions.host,
7061
7599
  noBrowser: false,
7062
7600
  extensions: parseExtensionsList(startOptions.extensions),
7063
7601
  metadataCommand: 'start',
@@ -7082,6 +7620,10 @@ Cross-Browser Compatibility
7082
7620
  validateVendorsOrExit(list, (invalid, supported)=>{
7083
7621
  console.error(unsupportedBrowserFlag(invalid, supported));
7084
7622
  });
7623
+ if (list.some(isSafariVendor)) {
7624
+ console.error(safariCommandNotSupported('preview'));
7625
+ process.exit(1);
7626
+ }
7085
7627
  if (!process.env.EXTJS_LIGHT) {
7086
7628
  const isRemote = 'string' == typeof pathOrRemoteUrl && /^https?:/i.test(pathOrRemoteUrl);
7087
7629
  if (isRemote) process.env.EXTJS_LIGHT = '1';
@@ -7113,7 +7655,7 @@ Cross-Browser Compatibility
7113
7655
  });
7114
7656
  }
7115
7657
  function registerBuildCommand(program) {
7116
- program.command('build').arguments('[project-name]').usage('build [path-to-remote-extension] [options]').description(commandDescriptions.build).option('--browser <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based>', 'specify a browser/engine to run. Defaults to `chromium`').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `false`').option('--zip [boolean]', 'whether or not to compress the extension into a ZIP file. Defaults to `false`').option('--zip-source [boolean]', 'whether or not to include the source files in the ZIP file. Defaults to `false`').option('--zip-filename <string>', 'specify the name of the ZIP file. Defaults to the extension name and version').option('--silent [boolean]', 'whether or not to open the browser automatically. Defaults to `false`').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('--author, --author-mode', '[internal] enable maintainer diagnostics (does not affect user runtime logs)').action(async function(pathOrRemoteUrl, { browser = 'chromium', ...buildOptions }) {
7658
+ program.command('build').arguments('[project-name]').usage('build [path-to-remote-extension] [options]').description(commandDescriptions.build).option('--browser <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based | safari | webkit-based>', '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`').option('--zip [boolean]', 'whether or not to compress the extension into a ZIP file. Defaults to `false`').option('--zip-source [boolean]', 'whether or not to include the source files in the ZIP file. Defaults to `false`').option('--zip-filename <string>', 'specify the name of the ZIP file. Defaults to the extension name and version').option('--silent [boolean]', 'whether or not to open the browser automatically. Defaults to `false`').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('--author, --author-mode', '[internal] enable maintainer diagnostics (does not affect user runtime logs)').action(async function(pathOrRemoteUrl, { browser = 'chromium', ...buildOptions }) {
7117
7659
  if (buildOptions.author || buildOptions['authorMode']) {
7118
7660
  process.env.EXTENSION_AUTHOR_MODE = 'true';
7119
7661
  if (!process.env.EXTENSION_VERBOSE) process.env.EXTENSION_VERBOSE = '1';
@@ -7131,6 +7673,13 @@ Cross-Browser Compatibility
7131
7673
  process.exit(1);
7132
7674
  }
7133
7675
  }
7676
+ if (list.some(isSafariVendor)) {
7677
+ const issue = safariPreflightError();
7678
+ if (issue) {
7679
+ console.error(issue);
7680
+ process.exit(1);
7681
+ }
7682
+ }
7134
7683
  const { extensionBuild } = await loadExtensionDevelopModule();
7135
7684
  for (const vendor of list)await extensionBuild(pathOrRemoteUrl, {
7136
7685
  browser: vendor,
@@ -7141,39 +7690,388 @@ Cross-Browser Compatibility
7141
7690
  silent: buildOptions.silent,
7142
7691
  install: buildOptions.install,
7143
7692
  extensions: parseExtensionsList(buildOptions.extensions),
7144
- mode
7693
+ mode,
7694
+ safariPackager: async (distPath, packagerMode)=>{
7695
+ await packageSafariExtension({
7696
+ extension: [
7697
+ distPath
7698
+ ],
7699
+ browser: vendor,
7700
+ noOpen: !!buildOptions.silent,
7701
+ dryRun: false
7702
+ }, distPath, void 0, packagerMode);
7703
+ }
7145
7704
  });
7146
7705
  });
7147
7706
  }
7148
- function resolveManagedBrowsersCacheRoot() {
7149
- const explicit = String(process.env.EXT_BROWSERS_CACHE_DIR || '').trim();
7150
- if (explicit) return external_node_path_default().resolve(explicit);
7151
- const isWin = 'win32' === process.platform;
7152
- const isMac = 'darwin' === process.platform;
7153
- if (isWin) {
7154
- const local = String(process.env.LOCALAPPDATA || '').trim();
7155
- if (local) return external_node_path_default().join(local, 'extension.js', 'browsers');
7156
- const userProfile = String(process.env.USERPROFILE || '').trim();
7157
- if (userProfile) return external_node_path_default().join(userProfile, 'AppData', 'Local', 'extension.js', 'browsers');
7158
- return external_node_path_default().resolve(process.cwd(), '.cache', 'extension.js', 'browsers');
7707
+ const LEVEL_ORDER = [
7708
+ 'error',
7709
+ 'warn',
7710
+ 'info',
7711
+ 'debug',
7712
+ 'trace'
7713
+ ];
7714
+ function levelRank(level) {
7715
+ const l = 'log' === level ? 'info' : level;
7716
+ const i = LEVEL_ORDER.indexOf(l);
7717
+ return -1 === i ? LEVEL_ORDER.length : i;
7718
+ }
7719
+ function makeUrlMatcher(pattern) {
7720
+ const hasGlob = pattern.includes('*');
7721
+ let re = null;
7722
+ if (hasGlob) {
7723
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
7724
+ re = new RegExp(escaped);
7725
+ }
7726
+ return (event)=>{
7727
+ const candidates = [
7728
+ event.url,
7729
+ event.hostname
7730
+ ].filter((v)=>'string' == typeof v);
7731
+ if (0 === candidates.length) return false;
7732
+ return candidates.some((c)=>re ? re.test(c) : c.includes(pattern));
7733
+ };
7734
+ }
7735
+ function makeFilter(opts) {
7736
+ const minLevel = String(opts.level || 'all').toLowerCase();
7737
+ const contexts = opts.context && 'all' !== opts.context.toLowerCase() ? new Set(opts.context.split(',').map((c)=>c.trim())) : null;
7738
+ const sinceSeq = null != opts.since ? Number(opts.since) : null;
7739
+ const urlMatches = opts.url ? makeUrlMatcher(opts.url) : null;
7740
+ const tabId = null != opts.tab && '' !== opts.tab ? Number(opts.tab) : null;
7741
+ return (event)=>{
7742
+ if (!event || 'object' != typeof event) return false;
7743
+ if ('header' === event.type) return false;
7744
+ if (opts.signalsOnly && 'dx.signal' !== event.eventType) return false;
7745
+ if (contexts && !contexts.has(event.context)) return false;
7746
+ if ('all' !== minLevel && 'off' !== minLevel) {
7747
+ if (levelRank(event.level) > levelRank(minLevel)) return false;
7748
+ }
7749
+ if (null != sinceSeq && Number.isFinite(sinceSeq) && 'number' == typeof event.seq && event.seq <= sinceSeq) return false;
7750
+ if (urlMatches && !urlMatches(event)) return false;
7751
+ if (null != tabId && Number.isFinite(tabId) && event.tabId !== tabId) return false;
7752
+ return true;
7753
+ };
7754
+ }
7755
+ function resolveFormat(opts) {
7756
+ if (opts.output) return opts.output;
7757
+ return process.stdout.isTTY ? 'pretty' : 'ndjson';
7758
+ }
7759
+ function printEvent(event, format) {
7760
+ if ('ndjson' === format) return void console.log(JSON.stringify(event));
7761
+ if ('json' === format) return void console.log(JSON.stringify(event, null, 2));
7762
+ const parts = Array.isArray(event.messageParts) ? event.messageParts.map((p)=>'string' == typeof p ? p : JSON.stringify(p)).join(' ') : '';
7763
+ const code = event.code ? ` ${event.code}` : '';
7764
+ const remediation = event.remediation ? `\n ↳ ${event.remediation}` : '';
7765
+ console.log(`[${event.seq ?? '-'}] ${String(event.level || 'log').toUpperCase()} (${event.context})${code} ${parts}${remediation}`);
7766
+ }
7767
+ function logsFilePath(projectPath, browser) {
7768
+ return external_path_default().resolve(projectPath, 'dist', 'extension-js', browser, 'logs.ndjson');
7769
+ }
7770
+ function registerLogsCommand(program) {
7771
+ program.command('logs').arguments('[project-path]').usage('logs [project-path] [options]').description('Print or stream logs from every context of a running dev session (agent bridge)').option('--browser <chrome | chromium | edge | firefox>', 'which dist/extension-js/<browser> to read. Defaults to `chromium`').option('--follow', 'stream live via the control channel instead of printing and exiting').option('--context <list>', 'comma-separated contexts (background, content, popup, options, sidebar, devtools, page)').option('--level <off|error|warn|info|debug|trace|all>', 'minimum severity to show. Defaults to `all`').option('--signals-only', 'show only structured dx.signal diagnostics').option('--since <seq|iso>', 'only show events after this sequence number').option('--url <glob|substring>', 'only events whose url/hostname matches (glob with * or plain substring)').option('--tab <id>', 'only events from this tab id').option('--output <pretty|json|ndjson>', 'output format. Defaults to pretty on a TTY, ndjson when piped').action(async function(projectPathArg, options) {
7772
+ const projectPath = external_path_default().resolve(projectPathArg || process.cwd());
7773
+ const browser = options.browser || 'chromium';
7774
+ const format = resolveFormat(options);
7775
+ const matches = makeFilter(options);
7776
+ if (options.follow) return void await followLogs(projectPath, browser, format, matches);
7777
+ const file = logsFilePath(projectPath, browser);
7778
+ if (!external_fs_default().existsSync(file)) {
7779
+ console.error(`No logs found at ${file}. Start a dev session (extension dev) first, or pass --browser to match it.`);
7780
+ process.exit(1);
7781
+ }
7782
+ const lines = external_fs_default().readFileSync(file, 'utf-8').split('\n').filter(Boolean);
7783
+ for (const line of lines){
7784
+ let event;
7785
+ try {
7786
+ event = JSON.parse(line);
7787
+ } catch {
7788
+ continue;
7789
+ }
7790
+ if (matches(event)) printEvent(event, format);
7791
+ }
7792
+ });
7793
+ }
7794
+ async function followLogs(projectPath, browser, format, matches) {
7795
+ const { BridgeConsumer, readReadyContract } = await loadExtensionDevelopBridgeModule();
7796
+ const ready = readReadyContract(projectPath, browser);
7797
+ if (!ready) {
7798
+ console.error(`No active dev session control channel found for ${browser}. Run \`extension dev --browser=${browser}\` first.`);
7799
+ process.exit(1);
7159
7800
  }
7160
- if (isMac) {
7161
- const home = String(process.env.HOME || '').trim();
7162
- if (home) return external_node_path_default().join(home, 'Library', 'Caches', 'extension.js', 'browsers');
7163
- return external_node_path_default().resolve(process.cwd(), '.cache', 'extension.js', 'browsers');
7801
+ const consumer = new BridgeConsumer({
7802
+ controlPort: ready.controlPort,
7803
+ instanceId: ready.instanceId,
7804
+ reconnect: true,
7805
+ onLog: (event)=>{
7806
+ if (matches(event)) printEvent(event, format);
7807
+ },
7808
+ onGap: (gap)=>{
7809
+ console.error(`… ${gap.dropped} event(s) dropped (${gap.reason}) — stream is behind`);
7810
+ }
7811
+ });
7812
+ const shutdown = ()=>{
7813
+ consumer.close();
7814
+ process.exit(0);
7815
+ };
7816
+ process.on('SIGINT', shutdown);
7817
+ process.on('SIGTERM', shutdown);
7818
+ consumer.start();
7819
+ await new Promise(()=>{});
7820
+ }
7821
+ function readRecentConsole(projectPath, browser, target, limit) {
7822
+ const file = external_path_default().resolve(projectPath, 'dist', 'extension-js', browser, 'logs.ndjson');
7823
+ let lines;
7824
+ try {
7825
+ lines = external_fs_default().readFileSync(file, 'utf-8').split('\n').filter(Boolean);
7826
+ } catch {
7827
+ return [];
7164
7828
  }
7165
- const xdg = String(process.env.XDG_CACHE_HOME || '').trim();
7166
- if (xdg) return external_node_path_default().join(xdg, 'extension.js', 'browsers');
7167
- const home = String(process.env.HOME || '').trim();
7168
- if (home) return external_node_path_default().join(home, '.cache', 'extension.js', 'browsers');
7169
- return external_node_path_default().resolve(process.cwd(), '.cache', 'extension.js', 'browsers');
7829
+ const out = [];
7830
+ for (const line of lines){
7831
+ let e;
7832
+ try {
7833
+ e = JSON.parse(line);
7834
+ } catch {
7835
+ continue;
7836
+ }
7837
+ if (e && 'header' !== e.type) {
7838
+ if (!target.context || e.context === target.context) {
7839
+ if (null == target.tabId || e.tabId === target.tabId) out.push({
7840
+ seq: e.seq,
7841
+ level: e.level,
7842
+ context: e.context,
7843
+ messageParts: e.messageParts,
7844
+ eventType: e.eventType,
7845
+ code: e.code,
7846
+ tabId: e.tabId
7847
+ });
7848
+ }
7849
+ }
7850
+ }
7851
+ return out.slice(Math.max(0, out.length - limit));
7852
+ }
7853
+ function fail(message) {
7854
+ console.error(message);
7855
+ process.exit(1);
7856
+ }
7857
+ function printResult(result, output) {
7858
+ if ('json' === output) return void console.log(JSON.stringify(result));
7859
+ if (result.ok) {
7860
+ const value = result.value;
7861
+ console.log('string' == typeof value ? value : JSON.stringify(value, null, 2));
7862
+ if (result.truncated) console.error('… result truncated (byte cap)');
7863
+ return;
7864
+ }
7865
+ const err = result.error || {
7866
+ name: 'Error',
7867
+ message: 'command failed'
7868
+ };
7869
+ console.error(`${err.name}: ${err.message}${err.engine ? ` (engine: ${err.engine})` : ''}`);
7870
+ }
7871
+ async function runCommand(input) {
7872
+ const projectPath = external_path_default().resolve(input.projectPathArg || process.cwd());
7873
+ const browser = input.opts.browser || 'chromium';
7874
+ const { BridgeController, readReadyContract, readControlToken } = await loadExtensionDevelopBridgeModule();
7875
+ const ready = readReadyContract(projectPath, browser);
7876
+ if (!ready) fail(`No active control channel found for ${browser}. Run \`extension dev --browser=${browser} --allow-control\` first.`);
7877
+ const token = input.needsToken ? readControlToken(projectPath) : void 0;
7878
+ const controller = new BridgeController({
7879
+ controlPort: ready.controlPort,
7880
+ instanceId: ready.instanceId,
7881
+ token: token ?? void 0
7882
+ });
7883
+ try {
7884
+ await controller.connect();
7885
+ } catch (err) {
7886
+ controller.close();
7887
+ fail(err?.message || 'could not connect to the control channel');
7888
+ }
7889
+ const timeoutMs = input.opts.timeout ? Number(input.opts.timeout) : 5000;
7890
+ let result;
7891
+ try {
7892
+ result = await controller.command({
7893
+ op: input.op,
7894
+ target: input.target,
7895
+ args: input.args,
7896
+ timeoutMs
7897
+ });
7898
+ } catch (err) {
7899
+ controller.close();
7900
+ fail(err?.message || 'command failed');
7901
+ } finally{
7902
+ controller.close();
7903
+ }
7904
+ if (result && result.ok && input.augment) try {
7905
+ Object.assign(result, input.augment(projectPath, browser, result));
7906
+ } catch {}
7907
+ printResult(result, input.opts.output);
7908
+ process.exit(result.ok ? 0 : 1);
7170
7909
  }
7171
- function normalizeInstallVendor(vendor) {
7172
- const value = String(vendor).trim().toLowerCase();
7173
- if ('chromium-based' === value) return 'chromium';
7174
- if ('gecko-based' === value || 'firefox-based' === value) return 'firefox';
7175
- if ('chrome' === value || 'chromium' === value || 'edge' === value || 'firefox' === value) return value;
7176
- return 'chromium';
7910
+ function targetFrom(opts, fallback = 'background') {
7911
+ const context = opts.context || fallback;
7912
+ const target = {
7913
+ context
7914
+ };
7915
+ if (opts.url) target.url = opts.url;
7916
+ if (null != opts.tab && '' !== opts.tab) target.tabId = Number(opts.tab);
7917
+ return target;
7918
+ }
7919
+ const commonOptions = (cmd)=>cmd.option('--browser <chrome | chromium | edge | firefox>', 'which session to target (default chromium)').option('--timeout <ms>', 'command timeout in milliseconds (default 5000)').option('--output <pretty|json>', 'output format (default pretty)');
7920
+ function registerActCommands(program) {
7921
+ commonOptions(program.command('eval').arguments('<expression> [project-path]').description('Evaluate an expression in a running extension context (requires --allow-eval)').option('--context <background|popup|options|sidebar|devtools|content|page>', 'target context (default background)').option('--url <glob|substring>', 'for content/page: document(s) to target').option('--tab <id>', 'for content/page: a specific tab')).action(async function(expression, projectPathArg, opts) {
7922
+ await runCommand({
7923
+ projectPathArg,
7924
+ op: 'eval',
7925
+ target: targetFrom(opts),
7926
+ args: {
7927
+ expression
7928
+ },
7929
+ needsToken: true,
7930
+ opts
7931
+ });
7932
+ });
7933
+ commonOptions(program.command('storage').arguments('<action> [project-path]').description('Read or write chrome.storage in a running extension (requires --allow-control)').option('--area <local|sync|session|managed>', 'storage area (default local)').option('--key <key>', 'key to get or set').option('--value <json>', 'JSON value to set (with set)').option('--context <background|popup|options|sidebar|content>', 'target context (default background)')).action(async function(action, projectPathArg, opts) {
7934
+ const area = opts.area || 'local';
7935
+ if ('get' === action) return void await runCommand({
7936
+ projectPathArg,
7937
+ op: 'storage.get',
7938
+ target: targetFrom(opts),
7939
+ args: opts.key ? {
7940
+ area,
7941
+ key: opts.key
7942
+ } : {
7943
+ area
7944
+ },
7945
+ opts
7946
+ });
7947
+ if ('set' === action) {
7948
+ if (!opts.key || null == opts.value) fail('storage set requires --key and --value');
7949
+ let parsed;
7950
+ try {
7951
+ parsed = JSON.parse(opts.value);
7952
+ } catch {
7953
+ parsed = opts.value;
7954
+ }
7955
+ await runCommand({
7956
+ projectPathArg,
7957
+ op: 'storage.set',
7958
+ target: targetFrom(opts),
7959
+ args: {
7960
+ area,
7961
+ items: {
7962
+ [opts.key]: parsed
7963
+ }
7964
+ },
7965
+ opts
7966
+ });
7967
+ return;
7968
+ }
7969
+ fail(`unknown storage action: ${action} (use get or set)`);
7970
+ });
7971
+ commonOptions(program.command('reload').arguments('[project-path]').description('Reload a running extension or tab (requires --allow-control)').option('--context <background|content|page>', 'target context (default background)').option('--tab <id>', 'for content/page: a specific tab')).action(async function(projectPathArg, opts) {
7972
+ await runCommand({
7973
+ projectPathArg,
7974
+ op: 'reload',
7975
+ target: targetFrom(opts),
7976
+ opts
7977
+ });
7978
+ });
7979
+ commonOptions(program.command('inspect').arguments('[project-path]').description('Inspect a page/content DOM via the agent bridge (CDP-free; requires --allow-control). For closed shadow roots use dev --source --deep-dom.').option('--context <content|page|popup|options|sidebar|devtools>', 'what to inspect: content/page (needs --tab) or an open surface (default content)').option('--tab <id>', 'tab id to inspect (required for content/page)').option('--include <list>', 'comma-separated: html,summary (default summary)').option('--max-bytes <n>', 'cap on returned HTML bytes (default 262144)').option('--with-console [n]', 'also include the last n console lines for the target (default 20)')).action(async function(projectPathArg, opts) {
7980
+ const include = opts.include ? opts.include.split(',').map((s)=>s.trim()).filter(Boolean) : [
7981
+ 'summary'
7982
+ ];
7983
+ const target = targetFrom(opts, 'content');
7984
+ await runCommand({
7985
+ projectPathArg,
7986
+ op: 'inspect',
7987
+ target,
7988
+ args: {
7989
+ include,
7990
+ maxBytes: opts.maxBytes ? Number(opts.maxBytes) : void 0
7991
+ },
7992
+ opts,
7993
+ augment: opts.withConsole ? (projectPath, browser)=>{
7994
+ const n = 'string' == typeof opts.withConsole && '' !== opts.withConsole ? Number(opts.withConsole) : 20;
7995
+ return {
7996
+ console: readRecentConsole(projectPath, browser, target, Number.isFinite(n) && n > 0 ? n : 20)
7997
+ };
7998
+ } : void 0
7999
+ });
8000
+ });
8001
+ commonOptions(program.command('open').arguments('<surface> [project-path]').description('Open an extension surface — popup, options, or sidebar (requires --allow-control)')).action(async function(surface, projectPathArg, opts) {
8002
+ const allowed = [
8003
+ 'popup',
8004
+ 'options',
8005
+ 'sidebar'
8006
+ ];
8007
+ if (!allowed.includes(surface)) fail(`unknown surface: ${surface} (use popup, options, or sidebar)`);
8008
+ await runCommand({
8009
+ projectPathArg,
8010
+ op: 'open',
8011
+ target: {
8012
+ context: surface
8013
+ },
8014
+ args: {
8015
+ surface
8016
+ },
8017
+ opts
8018
+ });
8019
+ });
8020
+ }
8021
+ const DEFAULT_API = 'https://www.extension.dev';
8022
+ function buildPublishRequest(opts) {
8023
+ const token = String(opts.token || process.env.EXTENSION_DEV_TOKEN || '').trim();
8024
+ if (!token) throw new Error("No token. Set EXTENSION_DEV_TOKEN (create one in the extension.dev dashboard, or via the project access-tokens API) or pass --token.");
8025
+ const base = String(opts.api || process.env.EXTENSION_DEV_API_URL || DEFAULT_API).replace(/\/+$/, '');
8026
+ const body = {};
8027
+ if (null != opts.ttl && '' !== opts.ttl) body.ttlHours = Number(opts.ttl);
8028
+ if (opts.buildSha) body.buildSha = opts.buildSha;
8029
+ return {
8030
+ url: `${base}/api/cli/publish`,
8031
+ headers: {
8032
+ authorization: `Bearer ${token}`,
8033
+ 'content-type': 'application/json'
8034
+ },
8035
+ body: JSON.stringify(body)
8036
+ };
8037
+ }
8038
+ function registerPublishCommand(program) {
8039
+ program.command('publish').arguments('[project-path]').usage('publish [project-path] [options]').description('Publish to extension.dev and print a shareable URL (requires EXTENSION_DEV_TOKEN)').option('--token <token>', 'extension.dev access token (or EXTENSION_DEV_TOKEN)').option('--api <url>', 'platform base URL (or EXTENSION_DEV_API_URL)').option('--ttl <hours>', 'share-link lifetime in hours (1–168, default 24)').option('--build-sha <sha>', 'pin the share URL to a specific build').option('--output <pretty|json>', 'output format (default pretty)').action(async function(_projectPathArg, opts) {
8040
+ let req;
8041
+ try {
8042
+ req = buildPublishRequest(opts);
8043
+ } catch (err) {
8044
+ console.error(err?.message || 'publish failed');
8045
+ process.exit(1);
8046
+ }
8047
+ let res;
8048
+ try {
8049
+ res = await fetch(req.url, {
8050
+ method: 'POST',
8051
+ headers: req.headers,
8052
+ body: req.body
8053
+ });
8054
+ } catch (err) {
8055
+ console.error(`Could not reach ${req.url}: ${err?.message || err}`);
8056
+ process.exit(1);
8057
+ }
8058
+ const text = await res.text();
8059
+ let data;
8060
+ try {
8061
+ data = JSON.parse(text);
8062
+ } catch {
8063
+ data = {
8064
+ message: text
8065
+ };
8066
+ }
8067
+ if (!res.ok) {
8068
+ console.error(`publish failed (${res.status}): ${data?.message || text || 'unknown error'}`);
8069
+ process.exit(1);
8070
+ }
8071
+ if ('json' === opts.output) console.log(JSON.stringify(data));
8072
+ else console.log(data.shareUrl || JSON.stringify(data));
8073
+ process.exit(0);
8074
+ });
7177
8075
  }
7178
8076
  function registerInstallCommand(program) {
7179
8077
  program.command('install').arguments('[browser-name]').usage('[browser-name] [options]').description(commandDescriptions.install).option('--browser <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based | all>', 'override the positional browser name. Supports comma-separated values and `all`.').option('--where', 'print the resolved managed browser cache root').action(async function(browserArg, options) {
@@ -7182,37 +8080,35 @@ Cross-Browser Compatibility
7182
8080
  validateVendorsOrExit(browserList, (invalid, supported)=>{
7183
8081
  console.error(unsupportedBrowserFlag(invalid, supported));
7184
8082
  });
8083
+ const { extensionInstall, getManagedBrowsersCacheRoot, getManagedBrowserInstallDir } = await import("extension-install");
7185
8084
  if (options.where) {
7186
- const root = resolveManagedBrowsersCacheRoot();
7187
- if (options.browser || browserArg) for (const browser of browserList)console.log(external_node_path_default().join(root, normalizeInstallVendor(browser)));
7188
- else console.log(root);
8085
+ if (options.browser || browserArg) for (const browser of browserList)console.log(getManagedBrowserInstallDir(browser));
8086
+ else console.log(getManagedBrowsersCacheRoot());
7189
8087
  return;
7190
8088
  }
7191
- const { extensionInstall } = await import("extension-install");
7192
8089
  for (const browser of browserList)await extensionInstall({
7193
8090
  browser
7194
8091
  });
7195
8092
  });
7196
8093
  program.command('uninstall').usage('uninstall <browser-name> | uninstall --all | uninstall --where').description(commandDescriptions.uninstall).option('--browser <browser-name>', 'browser to uninstall').option('--all', 'remove all managed browser binaries').option('--where', 'print the resolved managed browser cache root').argument('[browser-name]').action(async function(browserArg, { browser, all, where }) {
7197
8094
  const target = browserArg || browser;
8095
+ const { extensionUninstall, getManagedBrowsersCacheRoot, getManagedBrowserInstallDir } = await import("extension-install");
7198
8096
  if (where) {
7199
- const root = resolveManagedBrowsersCacheRoot();
7200
8097
  if (all) for (const browser of [
7201
8098
  'chrome',
7202
8099
  'chromium',
7203
8100
  'edge',
7204
8101
  'firefox'
7205
- ])console.log(external_node_path_default().join(root, browser));
8102
+ ])console.log(getManagedBrowserInstallDir(browser));
7206
8103
  else if (target) {
7207
8104
  const list = vendors(target);
7208
8105
  validateVendorsOrExit(list, (invalid, supported)=>{
7209
8106
  console.error(unsupportedBrowserFlag(invalid, supported));
7210
8107
  });
7211
- for (const browser of list)console.log(external_node_path_default().join(root, normalizeInstallVendor(browser)));
7212
- } else console.log(root);
8108
+ for (const browser of list)console.log(getManagedBrowserInstallDir(browser));
8109
+ } else console.log(getManagedBrowsersCacheRoot());
7213
8110
  return;
7214
8111
  }
7215
- const { extensionUninstall } = await import("extension-install");
7216
8112
  await extensionUninstall({
7217
8113
  browser: target,
7218
8114
  all
@@ -7358,6 +8254,9 @@ Cross-Browser Compatibility
7358
8254
  registerStartCommand(extensionJs);
7359
8255
  registerPreviewCommand(extensionJs);
7360
8256
  registerBuildCommand(extensionJs);
8257
+ registerLogsCommand(extensionJs);
8258
+ registerActCommands(extensionJs);
8259
+ registerPublishCommand(extensionJs);
7361
8260
  registerInstallCommand(extensionJs);
7362
8261
  registerTelemetryCommand(extensionJs);
7363
8262
  extensionJs.on('option:ai-help', function() {