extension 4.1.12 → 4.1.13

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 (30) hide show
  1. package/dist/49.cjs +2 -10
  2. package/dist/browsers.cjs +355 -111
  3. package/dist/cli.cjs +1012 -518
  4. package/dist/extension/browsers/browsers-lib/messages.d.ts +1 -1
  5. package/dist/extension/browsers/browsers-lib/newtab-override.d.ts +1 -0
  6. package/dist/extension/browsers/browsers-lib/resolve-profile.d.ts +1 -0
  7. package/dist/extension/browsers/run-chromium/chromium-launch/browser-config.d.ts +8 -1
  8. package/dist/extension/browsers/run-chromium/chromium-launch/index.d.ts +1 -0
  9. package/dist/extension/browsers/run-firefox/firefox-launch/binary-detector.d.ts +10 -0
  10. package/dist/extension/browsers/run-firefox/firefox-launch/browser-config.d.ts +9 -0
  11. package/dist/extension/browsers/run-firefox/firefox-launch/dry-run.d.ts +1 -1
  12. package/dist/extension/browsers/run-firefox/firefox-launch/index.d.ts +1 -0
  13. package/dist/extension/browsers/run-firefox/firefox-types.d.ts +1 -1
  14. package/dist/extension/browsers/run-firefox/rdp/rdp-extension-controller/index.d.ts +1 -0
  15. package/dist/extension/browsers/run-firefox/rdp/remote-firefox/index.d.ts +1 -0
  16. package/dist/extension/browsers/run-firefox/rdp/remote-firefox/manager-tab.d.ts +10 -0
  17. package/dist/extension/browsers/run-firefox/rdp/remote-firefox/messaging-client.d.ts +1 -0
  18. package/dist/extension/browsers/run-firefox/rdp/remote-firefox/transport.d.ts +3 -1
  19. package/dist/extension/commands/doctor.d.ts +1 -1
  20. package/dist/extension/helpers/cli-argv.d.ts +8 -0
  21. package/dist/extension/helpers/cli-options.d.ts +7 -0
  22. package/dist/extension/helpers/messages.d.ts +5 -0
  23. package/dist/extension/helpers/msg-placeholders.d.ts +2 -0
  24. package/dist/extension/helpers/output-flag.d.ts +6 -0
  25. package/dist/extension/helpers/session-project-path.d.ts +4 -0
  26. package/dist/extension/helpers/telemetry-cli.d.ts +1 -0
  27. package/dist/extension/helpers/template-catalog.d.ts +1 -1
  28. package/package.json +4 -4
  29. package/types/assets.d.ts +65 -0
  30. package/types/index.d.ts +1 -60
package/dist/cli.cjs CHANGED
@@ -642,12 +642,6 @@ var __webpack_modules__ = {
642
642
  function firefoxLaunchCalled() {
643
643
  return `${getLoggingPrefix('debug')} browser launch browser=firefox`;
644
644
  }
645
- function firefoxBinaryArgsExtracted(args) {
646
- return `${getLoggingPrefix('debug')} browser args="${args}"`;
647
- }
648
- function firefoxNoBinaryArgsFound() {
649
- return `${getLoggingPrefix('debug')} browser args=none`;
650
- }
651
645
  function firefoxFailedToStart(error) {
652
646
  return `${getLoggingPrefix('error')} Firefox failed to start.\n${pintor__rspack_import_8_default().red(errorDetail(error))}`;
653
647
  }
@@ -657,8 +651,8 @@ var __webpack_modules__ = {
657
651
  function firefoxDryRunBinary(path) {
658
652
  return `${getLoggingPrefix('info')} ${pintor__rspack_import_8_default().gray('PATH')} ${pintor__rspack_import_8_default().underline(path)}`;
659
653
  }
660
- function firefoxDryRunConfig(cfg) {
661
- return `${getLoggingPrefix('info')} ${pintor__rspack_import_8_default().gray('CONFIG')} ${pintor__rspack_import_8_default().gray(cfg)}`;
654
+ function firefoxDryRunArgs(args) {
655
+ return `${getLoggingPrefix('info')} ${pintor__rspack_import_8_default().gray('ARGS')} ${pintor__rspack_import_8_default().gray(args)}`;
662
656
  }
663
657
  function safariBuildCalled() {
664
658
  return `${getLoggingPrefix('debug')} browser build browser=safari`;
@@ -958,7 +952,6 @@ var __webpack_modules__ = {
958
952
  bv: ()=>cdpUnifiedExtensionLog,
959
953
  cD: ()=>targetActorHasActiveRequestError,
960
954
  cF: ()=>safariRebuilt,
961
- dN: ()=>firefoxNoBinaryArgsFound,
962
955
  dk: ()=>generalBrowserError,
963
956
  dw: ()=>devFirefoxProfilePath,
964
957
  e_: ()=>runningInDevelopment,
@@ -969,14 +962,13 @@ var __webpack_modules__ = {
969
962
  fw: ()=>safariOpening,
970
963
  hE: ()=>cdpClientConnectionClosed,
971
964
  io: ()=>emptyLine,
972
- iv: ()=>firefoxBinaryArgsExtracted,
973
965
  jP: ()=>rdpInvalidRequestPayload,
974
966
  jR: ()=>safariBuilding,
975
967
  jc: ()=>safariProjectStale,
976
968
  jk: ()=>requireGeckoBinaryForGeckoBased,
977
- k6: ()=>firefoxDryRunConfig,
978
969
  kK: ()=>preferringSystemBrowserOverSnapshot,
979
970
  l9: ()=>parsingPacketError,
971
+ lO: ()=>firefoxDryRunArgs,
980
972
  l_: ()=>safariConverted,
981
973
  ne: ()=>requireChromiumBinaryForChromiumBased,
982
974
  nn: ()=>chromeProcessExited,
@@ -999,6 +991,22 @@ var __webpack_modules__ = {
999
991
  yc: ()=>safariDryRunNotBuilding
1000
992
  });
1001
993
  },
994
+ "./browsers/browsers-lib/newtab-override.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
995
+ var node_fs__rspack_import_0 = __webpack_require__("node:fs");
996
+ var node_path__rspack_import_1 = __webpack_require__("node:path");
997
+ function manifestDeclaresNewtabOverride(outPath) {
998
+ try {
999
+ const manifest = JSON.parse(node_fs__rspack_import_0.readFileSync(node_path__rspack_import_1.join(outPath, 'manifest.json'), 'utf-8'));
1000
+ const overrides = manifest?.chrome_url_overrides || manifest?.['chromium:chrome_url_overrides'] || manifest?.['chrome:chrome_url_overrides'] || manifest?.['gecko:chrome_url_overrides'] || manifest?.['firefox:chrome_url_overrides'];
1001
+ return Boolean(overrides && 'string' == typeof overrides.newtab && overrides.newtab.trim().length > 0);
1002
+ } catch {
1003
+ return false;
1004
+ }
1005
+ }
1006
+ __webpack_require__.d(__webpack_exports__, {
1007
+ c: ()=>manifestDeclaresNewtabOverride
1008
+ });
1009
+ },
1002
1010
  "./browsers/browsers-lib/output-binaries-resolver.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1003
1011
  var node_fs__rspack_import_0 = __webpack_require__("node:fs");
1004
1012
  var node_path__rspack_import_1 = __webpack_require__("node:path");
@@ -1474,6 +1482,7 @@ var __webpack_modules__ = {
1474
1482
  }
1475
1483
  function resolveProfileConfig(input) {
1476
1484
  const { managedBaseDir, useSystemProfile, persistProfile, keepProfileChanges, copyFromProfile, resolveExplicit } = input;
1485
+ const provision = false !== input.provision;
1477
1486
  const rawProfile = normalizeProfileOption(input.rawProfile);
1478
1487
  if (false === rawProfile || useSystemProfile) return {
1479
1488
  kind: 'system',
@@ -1504,6 +1513,14 @@ var __webpack_modules__ = {
1504
1513
  profilePath = external_node_path_.join(managedBaseDir, human);
1505
1514
  }
1506
1515
  const isFreshTarget = !external_node_fs_.existsSync(profilePath) || 0 === external_node_fs_.readdirSync(profilePath).length;
1516
+ if (!provision) return {
1517
+ kind: 'managed',
1518
+ profilePath,
1519
+ persisted,
1520
+ ...hasCopyFrom(copyFromProfile) && isFreshTarget ? {
1521
+ seededFrom: copyFromProfile.trim()
1522
+ } : {}
1523
+ };
1507
1524
  external_node_fs_.mkdirSync(profilePath, {
1508
1525
  recursive: true
1509
1526
  });
@@ -1976,6 +1993,7 @@ var __webpack_modules__ = {
1976
1993
  preferences: opts.preferences,
1977
1994
  browserFlags: opts.browserFlags,
1978
1995
  startingUrl: opts.startingUrl,
1996
+ noOpen: opts.noOpen,
1979
1997
  geckoBinary: opts.geckoBinary,
1980
1998
  instanceId: opts.instanceId,
1981
1999
  port: opts.port,
@@ -2170,6 +2188,38 @@ var __webpack_modules__ = {
2170
2188
  var messaging = __webpack_require__("./helpers/messaging.ts");
2171
2189
  var banner = __webpack_require__("./browsers/browsers-lib/banner.ts");
2172
2190
  var instance_registry = __webpack_require__("./browsers/browsers-lib/instance-registry.ts");
2191
+ const MSG_REFERENCE = /__MSG_([A-Za-z0-9_@]+?)__/g;
2192
+ function collectMsgReferences(value) {
2193
+ const found = [];
2194
+ const seen = new Set();
2195
+ const visit = (node)=>{
2196
+ if ('string' == typeof node) {
2197
+ MSG_REFERENCE.lastIndex = 0;
2198
+ let match = MSG_REFERENCE.exec(node);
2199
+ while(null !== match){
2200
+ const name = match[1];
2201
+ if (name && !name.startsWith('@@') && !seen.has(name)) {
2202
+ seen.add(name);
2203
+ found.push(name);
2204
+ }
2205
+ match = MSG_REFERENCE.exec(node);
2206
+ }
2207
+ return;
2208
+ }
2209
+ if (Array.isArray(node)) {
2210
+ for (const item of node)visit(item);
2211
+ return;
2212
+ }
2213
+ if (node && 'object' == typeof node) for (const item of Object.values(node))visit(item);
2214
+ };
2215
+ visit(value);
2216
+ return found;
2217
+ }
2218
+ function findUndefinedMsgReferences(value, catalogKeys) {
2219
+ const lower = new Set();
2220
+ for (const key of catalogKeys)lower.add(String(key).toLowerCase());
2221
+ return collectMsgReferences(value).filter((name)=>!lower.has(name.toLowerCase()));
2222
+ }
2173
2223
  function diagnoseChromiumManifestRefusal(manifest) {
2174
2224
  const m = manifest;
2175
2225
  if (2 === Number(m?.manifest_version)) return 'mv2';
@@ -2304,7 +2354,7 @@ var __webpack_modules__ = {
2304
2354
  if (external_node_fs_.existsSync(catalogPath)) try {
2305
2355
  const raw = external_node_fs_.readFileSync(catalogPath, 'utf8').replace(/^\uFEFF/, '');
2306
2356
  const catalog = JSON.parse(raw);
2307
- catalogKeys = new Set(Object.keys(catalog || {}).map((key)=>key.toLowerCase()));
2357
+ catalogKeys = new Set(Object.keys(catalog || {}));
2308
2358
  } catch {
2309
2359
  blockers.push(`default_locale: _locales/${defaultLocale}/messages.json is not valid JSON, Chrome refuses the whole extension.`);
2310
2360
  }
@@ -2312,11 +2362,7 @@ var __webpack_modules__ = {
2312
2362
  } catch {
2313
2363
  return blockers;
2314
2364
  }
2315
- if (catalogKeys) {
2316
- const refs = new Set();
2317
- collectMsgRefs(m, refs);
2318
- for (const ref of refs)if (!catalogKeys.has(ref.toLowerCase())) blockers.push(`__MSG_${ref}__: used in the manifest but not defined in _locales/${defaultLocale}/messages.json, Chrome refuses the whole extension.`);
2319
- }
2365
+ if (catalogKeys) for (const ref of findUndefinedMsgReferences(m, catalogKeys))blockers.push(`__MSG_${ref}__: used in the manifest but not defined in _locales/${defaultLocale}/messages.json, Chrome refuses the whole extension.`);
2320
2366
  } else try {
2321
2367
  if (external_node_fs_.existsSync(localesDir)) {
2322
2368
  const hasCatalog = external_node_fs_.readdirSync(localesDir).some((entry)=>external_node_fs_.existsSync(external_node_path_.join(localesDir, entry, 'messages.json')));
@@ -2337,13 +2383,6 @@ var __webpack_modules__ = {
2337
2383
  } catch {}
2338
2384
  return [];
2339
2385
  }
2340
- function collectMsgRefs(value, out) {
2341
- if ('string' == typeof value) {
2342
- const match = /^__MSG_(.+)__$/.exec(value.trim());
2343
- if (match && !match[1].startsWith('@@')) out.add(match[1]);
2344
- } else if (Array.isArray(value)) for (const item of value)collectMsgRefs(item, out);
2345
- else if (value && 'object' == typeof value) for (const item of Object.values(value))collectMsgRefs(item, out);
2346
- }
2347
2386
  function isValidDottedVersion(version) {
2348
2387
  if (!version) return false;
2349
2388
  const parts = version.split('.');
@@ -2524,7 +2563,19 @@ var __webpack_modules__ = {
2524
2563
  });
2525
2564
  external_node_fs_.writeFileSync(preferencesPath, JSON.stringify(mergedPreferences), 'utf8');
2526
2565
  }
2527
- function browserConfig(compilation, configOptions) {
2566
+ function chromiumLaunchPlan(binary, chromiumConfig, startingUrl) {
2567
+ return {
2568
+ binary,
2569
+ args: startingUrl ? [
2570
+ ...chromiumConfig,
2571
+ startingUrl
2572
+ ] : [
2573
+ ...chromiumConfig
2574
+ ]
2575
+ };
2576
+ }
2577
+ function browserConfig(compilation, configOptions, mode = {}) {
2578
+ const provision = false !== mode.provision;
2528
2579
  const extensionsToLoad = (0, runtime_options.fT)(configOptions.extension);
2529
2580
  const devWantsCDP = compilation?.options?.mode === 'development';
2530
2581
  const rawProfile = configOptions.profile;
@@ -2548,17 +2599,18 @@ var __webpack_modules__ = {
2548
2599
  persistProfile: configOptions.persistProfile,
2549
2600
  keepProfileChanges: configOptions.keepProfileChanges,
2550
2601
  copyFromProfile: configOptions.copyFromProfile,
2551
- resolveExplicit: (trimmedProfile)=>external_node_path_.isAbsolute(trimmedProfile) ? trimmedProfile : external_node_path_.resolve(contextDir, trimmedProfile)
2602
+ resolveExplicit: (trimmedProfile)=>external_node_path_.isAbsolute(trimmedProfile) ? trimmedProfile : external_node_path_.resolve(contextDir, trimmedProfile),
2603
+ provision
2552
2604
  });
2553
2605
  const userProfilePath = resolved.profilePath;
2554
- if ('managed' === resolved.kind) {
2606
+ if ('managed' === resolved.kind && provision) {
2555
2607
  if ((0, messaging._o)()) (0, messaging._w)(messages.sY(shownPath(userProfilePath)));
2556
2608
  if (!resolved.persisted) try {
2557
2609
  const maxAgeHours = parseInt(String(process.env.EXTENSION_TMP_PROFILE_MAX_AGE_HOURS || ''), 10);
2558
2610
  (0, shared_utils.CW)(managedBaseDir, external_node_path_.basename(userProfilePath), Number.isFinite(maxAgeHours) ? maxAgeHours : 12);
2559
2611
  } catch {}
2560
2612
  }
2561
- if (userProfilePath) {
2613
+ if (userProfilePath && provision) {
2562
2614
  external_node_fs_.mkdirSync(userProfilePath, {
2563
2615
  recursive: true
2564
2616
  });
@@ -2879,7 +2931,10 @@ var __webpack_modules__ = {
2879
2931
  process.exit(1);
2880
2932
  }
2881
2933
  }
2882
- if (this.options?.dryRun || process.env.VITEST || process.env.VITEST_WORKER_ID) return void logChromiumDryRun(this.options?.chromiumBinary ? (0, wsl_support.f7)(String(this.options.chromiumBinary)) : 'chromium-mock-binary', []);
2934
+ const inTestRunner = Boolean(process.env.VITEST || process.env.VITEST_WORKER_ID);
2935
+ const dryRun = Boolean(this.options?.dryRun);
2936
+ if (inTestRunner && !dryRun) return void logChromiumDryRun(this.options?.chromiumBinary ? (0, wsl_support.f7)(String(this.options.chromiumBinary)) : 'chromium-mock-binary', []);
2937
+ if (inTestRunner && dryRun && !this.options?.chromiumBinary) return void this.printDryRunPlan(compilation, 'chromium-mock-binary');
2883
2938
  const browser = this.options?.browser;
2884
2939
  let browserBinaryLocation = null;
2885
2940
  let printedGuidance = false;
@@ -3188,6 +3243,8 @@ var __webpack_modules__ = {
3188
3243
  instanceId: this.options.instanceId,
3189
3244
  extension: extensionsToLoad,
3190
3245
  logLevel: this.options.logLevel
3246
+ }, {
3247
+ provision: !dryRun
3191
3248
  });
3192
3249
  } catch (error) {
3193
3250
  if (shared_utils.ci(error)) (0, ready_stamp.iD)((0, extension_output_path.W)(compilation, void 0), {
@@ -3220,7 +3277,7 @@ var __webpack_modules__ = {
3220
3277
  enableCdp
3221
3278
  });
3222
3279
  } catch {}
3223
- if (this.options.dryRun) return void logChromiumDryRun(browserBinaryLocation, chromiumConfig);
3280
+ if (dryRun) return void this.printDryRunPlan(compilation, browserBinaryLocation, chromiumConfig);
3224
3281
  const usePipe = chromiumConfig.includes('--remote-debugging-pipe');
3225
3282
  this.closeHandlerContext = {
3226
3283
  isDevMode: 'development' === compilation.options.mode,
@@ -3290,14 +3347,18 @@ var __webpack_modules__ = {
3290
3347
  reportReady();
3291
3348
  }
3292
3349
  }
3350
+ printDryRunPlan(compilation, binary, chromiumConfig) {
3351
+ const flags = chromiumConfig ?? browserConfig(compilation, {
3352
+ ...this.options
3353
+ }, {
3354
+ provision: false
3355
+ });
3356
+ const plan = chromiumLaunchPlan(binary, flags, this.options?.startingUrl);
3357
+ logChromiumDryRun(plan.binary, plan.args);
3358
+ }
3293
3359
  async launchWithDirectSpawn(binary, chromeFlags, usePipe = false) {
3294
3360
  if ((0, messaging._o)()) this.logger.info(messages.xy());
3295
- const launchArgs = this.options?.startingUrl ? [
3296
- ...chromeFlags,
3297
- this.options.startingUrl
3298
- ] : [
3299
- ...chromeFlags
3300
- ];
3361
+ const { args: launchArgs } = chromiumLaunchPlan(binary, chromeFlags, this.options?.startingUrl);
3301
3362
  const stdio = usePipe ? [
3302
3363
  'ignore',
3303
3364
  'ignore',
@@ -3570,6 +3631,29 @@ var __webpack_modules__ = {
3570
3631
  return /^(1|true)$/i.test(String(env.MOZ_HEADLESS || '').trim()) || (0, shared_utils.ZO)(env);
3571
3632
  }
3572
3633
  class FirefoxBinaryDetector {
3634
+ static launchPlan(input) {
3635
+ if (input.profilePath) return FirefoxBinaryDetector.generateFirefoxArgs(input.binaryPath, input.profilePath, input.debugPort, input.binaryArgs, input.headless);
3636
+ const args = [
3637
+ ...input.headless ? [
3638
+ '-headless'
3639
+ ] : [],
3640
+ ...input.debugPort > 0 ? [
3641
+ '-start-debugger-server',
3642
+ String(input.debugPort)
3643
+ ] : [],
3644
+ ...'win32' === process.platform ? [
3645
+ '-wait-for-browser'
3646
+ ] : [],
3647
+ ...input.headless ? [] : [
3648
+ '--foreground'
3649
+ ],
3650
+ ...input.binaryArgs
3651
+ ];
3652
+ return {
3653
+ binary: input.binaryPath,
3654
+ args
3655
+ };
3656
+ }
3573
3657
  static generateFirefoxArgs(binaryPath, profilePath, debugPort, additionalArgs = [], headless = false) {
3574
3658
  const headlessArgs = headless ? [
3575
3659
  '-headless'
@@ -3699,7 +3783,8 @@ var __webpack_modules__ = {
3699
3783
  ...customPrefs
3700
3784
  };
3701
3785
  }
3702
- async function browserConfig(compilation, configOptions) {
3786
+ async function resolveFirefoxLaunchConfig(compilation, configOptions, mode = {}) {
3787
+ const provision = false !== mode.provision;
3703
3788
  const { browser, profile, browserFlags = [] } = configOptions;
3704
3789
  const binaryArgs = [];
3705
3790
  const excludeFlags = configOptions.excludeBrowserFlags || [];
@@ -3727,17 +3812,18 @@ var __webpack_modules__ = {
3727
3812
  persistProfile: configOptions.persistProfile,
3728
3813
  keepProfileChanges: configOptions.keepProfileChanges,
3729
3814
  copyFromProfile: configOptions.copyFromProfile,
3730
- resolveExplicit: (trimmedProfile)=>external_node_path_.isAbsolute(trimmedProfile) ? trimmedProfile : external_node_path_.resolve(contextDir, trimmedProfile)
3815
+ resolveExplicit: (trimmedProfile)=>external_node_path_.isAbsolute(trimmedProfile) ? trimmedProfile : external_node_path_.resolve(contextDir, trimmedProfile),
3816
+ provision
3731
3817
  });
3732
3818
  const profilePath = resolved.profilePath;
3733
- if ('managed' === resolved.kind) {
3819
+ if ('managed' === resolved.kind && provision) {
3734
3820
  if ((0, messaging._o)()) (0, messaging._w)(messages.sY(shownPath(profilePath)));
3735
3821
  if (!resolved.persisted) try {
3736
3822
  const maxAgeHours = parseInt(String(process.env.EXTENSION_TMP_PROFILE_MAX_AGE_HOURS || ''), 10);
3737
3823
  (0, shared_utils.CW)(managedBaseDir, external_node_path_.basename(profilePath), Number.isFinite(maxAgeHours) ? maxAgeHours : 12);
3738
3824
  } catch {}
3739
3825
  }
3740
- if (profilePath) {
3826
+ if (profilePath && provision) {
3741
3827
  try {
3742
3828
  external_node_fs_.mkdirSync(profilePath, {
3743
3829
  recursive: true
@@ -3750,7 +3836,7 @@ var __webpack_modules__ = {
3750
3836
  });
3751
3837
  } catch {}
3752
3838
  }
3753
- if (profilePath) try {
3839
+ if (profilePath && provision) try {
3754
3840
  const prefs = getPreferences(configOptions?.preferences || {});
3755
3841
  function serializeValue(value) {
3756
3842
  if ('string' == typeof value) return JSON.stringify(value);
@@ -3770,13 +3856,17 @@ var __webpack_modules__ = {
3770
3856
  ];
3771
3857
  if (binaryArgs.length > 0) parts.unshift(`--binary-args="${binaryArgs.join(' ')}"`);
3772
3858
  if (profilePath) parts.splice(1, 0, `--profile="${profilePath}"`);
3773
- return parts.join(' ');
3859
+ return {
3860
+ profilePath,
3861
+ binaryArgs,
3862
+ config: parts.join(' ')
3863
+ };
3774
3864
  }
3775
- function logFirefoxDryRun(browserBinaryLocation, firefoxConfig) {
3865
+ function logFirefoxDryRun(browserBinaryLocation, launchArgs) {
3776
3866
  if ((0, messaging._o)()) (0, messaging._w)(messages.A9());
3777
3867
  (0, messaging._w)(messages.qn());
3778
3868
  (0, messaging._w)(messages.LD(browserBinaryLocation));
3779
- (0, messaging._w)(messages.k6(firefoxConfig));
3869
+ (0, messaging._w)(messages.lO(launchArgs.join(' ')));
3780
3870
  }
3781
3871
  const activeInstances = new Set();
3782
3872
  let globalHandlersInstalled = false;
@@ -3841,6 +3931,7 @@ var __webpack_modules__ = {
3841
3931
  activeInstances.delete(instance);
3842
3932
  };
3843
3933
  }
3934
+ var newtab_override = __webpack_require__("./browsers/browsers-lib/newtab-override.ts");
3844
3935
  var constants = __webpack_require__("./browsers/browsers-lib/constants.ts");
3845
3936
  var manifest_readiness = __webpack_require__("./browsers/run-chromium/manifest-readiness.ts");
3846
3937
  function resolveAddonDirectory(baseDir, inputPath) {
@@ -4094,6 +4185,110 @@ var __webpack_modules__ = {
4094
4185
  } catch {}
4095
4186
  });
4096
4187
  }
4188
+ const MANAGER_ADDON_ID = 'devtools@extension.js';
4189
+ const OPEN_NEW_TAB_EXPRESSION = "(function () { var api = globalThis.browser || globalThis.chrome; api.tabs.create({active: true}); return true;})()";
4190
+ const TARGET_WAIT_MS = 2000;
4191
+ function reportStep(step, detail) {
4192
+ if (!(0, messaging._o)()) return;
4193
+ const reason = detail instanceof Error ? detail.message : void 0 === detail ? '' : ` ${JSON.stringify(detail).slice(0, 300)}`;
4194
+ (0, messaging.mR)(`[browser] Firefox new-tab courtesy: ${step}${reason}`);
4195
+ }
4196
+ async function findManagerAddon(client) {
4197
+ const reply = await client.request({
4198
+ to: 'root',
4199
+ type: 'listAddons'
4200
+ });
4201
+ return (reply?.addons || []).find((addon)=>addon?.id === MANAGER_ADDON_ID);
4202
+ }
4203
+ function manager_tab_unsubscribe(client, listener) {
4204
+ if ('function' == typeof client.off) client.off('message', listener);
4205
+ else client.removeListener?.('message', listener);
4206
+ }
4207
+ function isBackgroundPage(target) {
4208
+ return /_generated_background_page\.html|\/background/.test(String(target?.url || ''));
4209
+ }
4210
+ async function resolveConsoleActorViaWatcher(client, descriptorActor) {
4211
+ const watcher = await client.request({
4212
+ to: descriptorActor,
4213
+ type: 'getWatcher',
4214
+ isServerTargetSwitchingEnabled: true
4215
+ });
4216
+ const noop = ()=>{};
4217
+ if (!watcher?.actor) return {
4218
+ release: noop
4219
+ };
4220
+ const watcherActor = watcher.actor;
4221
+ const release = ()=>{
4222
+ client.request({
4223
+ to: watcherActor,
4224
+ type: 'unwatchTargets',
4225
+ targetType: 'frame'
4226
+ }).catch(()=>{});
4227
+ };
4228
+ const targets = [];
4229
+ const collect = (message)=>{
4230
+ const event = message;
4231
+ if (event?.type === 'target-available-form' && event.target) targets.push(event.target);
4232
+ };
4233
+ client.on('message', collect);
4234
+ try {
4235
+ collect(await client.request({
4236
+ to: watcherActor,
4237
+ type: 'watchTargets',
4238
+ targetType: 'frame'
4239
+ }));
4240
+ const deadline = Date.now() + TARGET_WAIT_MS;
4241
+ while(Date.now() < deadline){
4242
+ const background = targets.find((target)=>target.consoleActor && isBackgroundPage(target));
4243
+ if (background) return {
4244
+ consoleActor: background.consoleActor,
4245
+ release
4246
+ };
4247
+ await new Promise((resolve)=>setTimeout(resolve, 50));
4248
+ }
4249
+ return {
4250
+ consoleActor: targets.find((target)=>target.consoleActor)?.consoleActor,
4251
+ release
4252
+ };
4253
+ } finally{
4254
+ manager_tab_unsubscribe(client, collect);
4255
+ }
4256
+ }
4257
+ async function resolveConsoleActor(client, addon) {
4258
+ if (addon.consoleActor) return {
4259
+ consoleActor: addon.consoleActor,
4260
+ release: ()=>{}
4261
+ };
4262
+ if (!addon.actor) return {
4263
+ release: ()=>{}
4264
+ };
4265
+ return await resolveConsoleActorViaWatcher(client, addon.actor);
4266
+ }
4267
+ async function openManagerNewTab(client) {
4268
+ try {
4269
+ const addon = await findManagerAddon(client);
4270
+ if (!addon) {
4271
+ reportStep('manager add-on not listed');
4272
+ return false;
4273
+ }
4274
+ const { consoleActor, release } = await resolveConsoleActor(client, addon);
4275
+ if (!consoleActor) {
4276
+ release();
4277
+ reportStep('no console actor for the manager background page');
4278
+ return false;
4279
+ }
4280
+ try {
4281
+ const result = await client.evaluate(consoleActor, OPEN_NEW_TAB_EXPRESSION);
4282
+ reportStep('tabs.create evaluated', result);
4283
+ } finally{
4284
+ release();
4285
+ }
4286
+ return true;
4287
+ } catch (error) {
4288
+ reportStep('failed', error);
4289
+ return false;
4290
+ }
4291
+ }
4097
4292
  function requestErrorToMessage(error) {
4098
4293
  if (error instanceof Error) return String(error);
4099
4294
  try {
@@ -4282,21 +4477,33 @@ var __webpack_modules__ = {
4282
4477
  const raw = parseInt(String(process.env.EXTENSION_RDP_REQUEST_TIMEOUT_MS || ''), 10);
4283
4478
  return Number.isFinite(raw) && raw > 0 ? raw : 30000;
4284
4479
  }
4480
+ function surfaceTransportError(emitter, error) {
4481
+ if (emitter.listenerCount('error') > 0) return void emitter.emit('error', error);
4482
+ (0, messaging.Gg)(error instanceof Error ? error.message : String(error));
4483
+ }
4285
4484
  class RdpTransport extends external_node_events_default() {
4286
4485
  async connect(port, host = '127.0.0.1') {
4287
4486
  await new Promise((resolve, reject)=>{
4487
+ let connected = false;
4288
4488
  try {
4289
4489
  const c = external_node_net_default().createConnection({
4290
4490
  host,
4291
4491
  port
4292
4492
  }, ()=>{
4493
+ connected = true;
4293
4494
  if ((0, messaging._o)()) (0, messaging._w)(messages.EI(host, port));
4294
4495
  resolve();
4295
4496
  });
4296
4497
  this.conn = c;
4498
+ this.lost = false;
4297
4499
  c.on('data', this.onData.bind(this));
4298
- c.on('error', reject);
4299
- c.on('end', this.onEnd.bind(this));
4500
+ c.on('error', (err)=>{
4501
+ if (connected) return;
4502
+ this.conn = void 0;
4503
+ reject(err);
4504
+ });
4505
+ c.on('end', this.onConnectionLost.bind(this));
4506
+ c.on('close', this.onConnectionLost.bind(this));
4300
4507
  c.on('timeout', this.onTimeout.bind(this));
4301
4508
  } catch (err) {
4302
4509
  reject(err);
@@ -4304,11 +4511,15 @@ var __webpack_modules__ = {
4304
4511
  });
4305
4512
  }
4306
4513
  disconnect() {
4307
- if (!this.conn) return;
4308
- this.conn.removeAllListeners();
4309
- this.conn.end();
4310
- const err = new Error(messages.qN('firefox'));
4311
- this.rejectAll(err);
4514
+ const c = this.conn;
4515
+ if (!c) return;
4516
+ this.conn = void 0;
4517
+ this.lost = true;
4518
+ this.incoming = Buffer.alloc(0);
4519
+ c.removeAllListeners();
4520
+ c.on('error', ()=>{});
4521
+ c.end();
4522
+ this.rejectAll(new Error(messages.qN('firefox')));
4312
4523
  }
4313
4524
  rejectAll(error) {
4314
4525
  for (const entry of this.active.values()){
@@ -4380,8 +4591,8 @@ var __webpack_modules__ = {
4380
4591
  const { remainingData, parsedMessage, error, fatal } = parseRdpFrame(this.incoming);
4381
4592
  this.incoming = remainingData;
4382
4593
  if (error) {
4383
- this.emit('error', new Error(messages.l9('firefox', error)));
4384
- if (fatal) this.disconnect();
4594
+ surfaceTransportError(this, new Error(messages.l9('firefox', error)));
4595
+ if (fatal) this.onConnectionLost();
4385
4596
  return !fatal;
4386
4597
  }
4387
4598
  if (!parsedMessage) return false;
@@ -4389,10 +4600,11 @@ var __webpack_modules__ = {
4389
4600
  return true;
4390
4601
  }
4391
4602
  handleMessage(message) {
4392
- if (!message.from) return void this.emit('error', new Error(messages.HH('firefox', message)));
4393
- const entry = this.active.get(message.from);
4603
+ const from = message.from;
4604
+ if (!from) return void surfaceTransportError(this, new Error(messages.HH('firefox', message)));
4605
+ const entry = this.active.get(from);
4394
4606
  if (entry) {
4395
- this.active.delete(message.from);
4607
+ this.active.delete(from);
4396
4608
  if (entry.timer) clearTimeout(entry.timer);
4397
4609
  if (message.error) entry.deferred.reject(message);
4398
4610
  else entry.deferred.resolve(message);
@@ -4401,7 +4613,17 @@ var __webpack_modules__ = {
4401
4613
  }
4402
4614
  this.emit('message', message);
4403
4615
  }
4404
- onEnd() {
4616
+ onConnectionLost() {
4617
+ if (this.lost) return;
4618
+ this.lost = true;
4619
+ const c = this.conn;
4620
+ this.conn = void 0;
4621
+ this.incoming = Buffer.alloc(0);
4622
+ if (c) {
4623
+ c.removeAllListeners();
4624
+ c.on('error', ()=>{});
4625
+ c.destroy();
4626
+ }
4405
4627
  this.rejectAll(new Error(messages.qN('firefox')));
4406
4628
  this.emit('end');
4407
4629
  }
@@ -4409,7 +4631,7 @@ var __webpack_modules__ = {
4409
4631
  this.emit('timeout');
4410
4632
  }
4411
4633
  constructor(...args){
4412
- super(...args), _define_property(this, "conn", void 0), _define_property(this, "incoming", Buffer.alloc(0)), _define_property(this, "active", new Map()), _define_property(this, "pending", []);
4634
+ super(...args), _define_property(this, "conn", void 0), _define_property(this, "incoming", Buffer.alloc(0)), _define_property(this, "active", new Map()), _define_property(this, "pending", []), _define_property(this, "lost", false);
4413
4635
  }
4414
4636
  }
4415
4637
  function messaging_client_define_property(obj, key, value) {
@@ -4422,15 +4644,20 @@ var __webpack_modules__ = {
4422
4644
  else obj[key] = value;
4423
4645
  return obj;
4424
4646
  }
4647
+ const RECONNECT_MAX_ATTEMPTS = 5;
4648
+ const RECONNECT_RETRY_DELAY_MS = 1000;
4425
4649
  class MessagingClient extends external_node_events_default() {
4426
4650
  async connect(port) {
4427
- this.lastPort = port;
4428
4651
  this.disconnectedByUser = false;
4652
+ await this.openTransport(port);
4653
+ }
4654
+ async openTransport(port) {
4655
+ this.lastPort = port;
4429
4656
  await this.transport.connect(port);
4430
4657
  if (!this.forwardingSetup) {
4431
4658
  this.forwardingSetup = true;
4432
4659
  this.transport.on('message', (message)=>this.emit('message', message));
4433
- this.transport.on('error', (error)=>this.emit('error', error));
4660
+ this.transport.on('error', (error)=>surfaceTransportError(this, error));
4434
4661
  this.transport.on('end', ()=>{
4435
4662
  this.emit('end');
4436
4663
  this.attemptReconnect();
@@ -4445,20 +4672,21 @@ var __webpack_modules__ = {
4445
4672
  async attemptReconnect() {
4446
4673
  if (this.disconnectedByUser || this.reconnecting || !this.lastPort) return;
4447
4674
  this.reconnecting = true;
4448
- const maxAttempts = 5;
4449
- const retryDelay = 1000;
4450
- for(let i = 0; i < maxAttempts; i++){
4451
- await new Promise((r)=>setTimeout(r, retryDelay));
4452
- try {
4453
- this.transport = new RdpTransport();
4454
- this.forwardingSetup = false;
4455
- await this.connect(this.lastPort);
4456
- this.reconnecting = false;
4457
- this.emit('reconnected');
4458
- return;
4459
- } catch {}
4675
+ try {
4676
+ for(let i = 0; i < RECONNECT_MAX_ATTEMPTS; i++){
4677
+ await new Promise((r)=>setTimeout(r, RECONNECT_RETRY_DELAY_MS));
4678
+ if (this.disconnectedByUser) return;
4679
+ try {
4680
+ this.transport = new RdpTransport();
4681
+ this.forwardingSetup = false;
4682
+ await this.openTransport(this.lastPort);
4683
+ this.emit('reconnected');
4684
+ return;
4685
+ } catch {}
4686
+ }
4687
+ } finally{
4688
+ this.reconnecting = false;
4460
4689
  }
4461
- this.reconnecting = false;
4462
4690
  }
4463
4691
  async request(requestProps) {
4464
4692
  if ('string' == typeof requestProps) return await this.transport.request({
@@ -4647,6 +4875,10 @@ var __webpack_modules__ = {
4647
4875
  });
4648
4876
  if (!bannerPrinted) throw new Error(messages.WV(this.options.browser, 'Failed to print runningInDevelopment banner; add-on may not be installed.'));
4649
4877
  }
4878
+ async openNewTab() {
4879
+ if (!this.client) return false;
4880
+ return await openManagerNewTab(this.client);
4881
+ }
4650
4882
  async enableUnifiedLogging(opts) {
4651
4883
  try {
4652
4884
  if (this.loggingAttached) return;
@@ -4700,6 +4932,9 @@ var __webpack_modules__ = {
4700
4932
  getAddonInstallRefusalReason() {
4701
4933
  return this.remote.getAddonInstallRefusalReason();
4702
4934
  }
4935
+ async openNewTab() {
4936
+ return await this.remote.openNewTab();
4937
+ }
4703
4938
  getExtensionId() {
4704
4939
  return this.remote.getDerivedExtensionId();
4705
4940
  }
@@ -4762,6 +4997,14 @@ var __webpack_modules__ = {
4762
4997
  } catch (error) {
4763
4998
  throw withRefusalReason(error);
4764
4999
  }
5000
+ try {
5001
+ const outPath = String(compilation?.options?.output?.path || '');
5002
+ if (!plugin.startingUrl && !plugin.noOpen && (0, newtab_override.c)(outPath)) {
5003
+ const live = plugin.rdpController || controller;
5004
+ const opened = await live.openNewTab?.();
5005
+ if (!opened && (0, messaging._o)()) (0, messaging.mR)('[browser] Firefox: could not open the new-tab override page.');
5006
+ }
5007
+ } catch {}
4765
5008
  return controller;
4766
5009
  }
4767
5010
  var external_node_child_process_ = __webpack_require__("node:child_process");
@@ -4844,7 +5087,7 @@ var __webpack_modules__ = {
4844
5087
  debug: (...a)=>console?.debug?.(...a)
4845
5088
  };
4846
5089
  await this.launch(compilation, options);
4847
- if ('development' === options.mode && !this.host.extensionLoadRefused) this.ctx.logger?.info?.((0, ready_message.G)(options.mode, this.host.browser));
5090
+ if (options?.mode === 'development' && !this.host.extensionLoadRefused) this.ctx.logger?.info?.((0, ready_message.G)(options.mode, this.host.browser));
4848
5091
  this.ctx.didLaunch = true;
4849
5092
  }
4850
5093
  apply(compiler) {
@@ -4909,12 +5152,10 @@ var __webpack_modules__ = {
4909
5152
  if (process.env.VITEST || process.env.VITEST_WORKER_ID) throw new Error('Firefox not installed or binary path not found');
4910
5153
  process.exit(1);
4911
5154
  };
4912
- if (this.host.dryRun || process.env.VITEST || process.env.VITEST_WORKER_ID) {
4913
- try {
4914
- logFirefoxDryRun('firefox-mock-binary', '--binary-args=""');
4915
- } catch {}
4916
- return;
4917
- }
5155
+ const inTestRunner = Boolean(process.env.VITEST || process.env.VITEST_WORKER_ID);
5156
+ const dryRun = Boolean(this.host.dryRun);
5157
+ if (inTestRunner && !dryRun) return void logFirefoxDryRun('firefox-mock-binary', []);
5158
+ if (inTestRunner && dryRun && !this.host.geckoBinary) return void await this.printDryRunPlan(compilation, options, 'firefox-mock-binary');
4918
5159
  let browserBinaryLocation = resolveManagedBinary();
4919
5160
  let skipDetection = Boolean(browserBinaryLocation);
4920
5161
  const engineBased = 'gecko-based' === this.host.browser || 'firefox-based' === this.host.browser;
@@ -5008,25 +5249,25 @@ var __webpack_modules__ = {
5008
5249
  rdpPort: debugPort
5009
5250
  });
5010
5251
  const effectiveInstanceId = this.host.instanceId;
5011
- let firefoxCfg;
5012
- firefoxCfg = await browserConfig(compilation, {
5252
+ const launchConfig = await resolveFirefoxLaunchConfig(compilation, {
5013
5253
  ...options,
5014
5254
  profile: this.host.profile,
5015
5255
  preferences: this.host.preferences || {},
5016
5256
  instanceId: effectiveInstanceId
5257
+ }, {
5258
+ provision: !dryRun
5259
+ });
5260
+ const { profilePath, binaryArgs: firefoxArgs } = launchConfig;
5261
+ const plan = FirefoxBinaryDetector.launchPlan({
5262
+ binaryPath,
5263
+ profilePath,
5264
+ debugPort,
5265
+ binaryArgs: firefoxArgs,
5266
+ headless: isFirefoxHeadlessRequested()
5017
5267
  });
5018
- if (this.host.dryRun) return void logFirefoxDryRun(binaryPath, firefoxCfg);
5019
- const firefoxArgs = [];
5020
- const binaryArgsMatch = firefoxCfg.match(/--binary-args="([^"]*)"/);
5021
- if (binaryArgsMatch) {
5022
- const rawArgs = String(binaryArgsMatch[1] || '').trim();
5023
- if (rawArgs) firefoxArgs.push(...rawArgs.split(' ').filter((arg)=>arg.trim().length > 0));
5024
- if ((0, messaging._o)()) this.ctx.logger?.info?.(messages.iv(binaryArgsMatch[1]));
5025
- } else if ((0, messaging._o)()) this.ctx.logger?.info?.(messages.dN());
5026
- const profileMatch = firefoxCfg.match(/--profile="([^"]*)"/);
5027
- if (profileMatch) {
5028
- const profilePath = profileMatch[1];
5029
- const { binary, args } = FirefoxBinaryDetector.generateFirefoxArgs(binaryPath, profilePath, debugPort, firefoxArgs, isFirefoxHeadlessRequested());
5268
+ if (dryRun) return void logFirefoxDryRun(plan.binary, plan.args);
5269
+ if (profilePath) {
5270
+ const { binary, args } = plan;
5030
5271
  this.host.launchProfilePath = profilePath;
5031
5272
  this.child = await this.spawnFirefoxChild(binary, args, wslFallbackBinary);
5032
5273
  (0, ready_stamp.M)(this.extensionOutputPath, {
@@ -5066,23 +5307,7 @@ var __webpack_modules__ = {
5066
5307
  } catch {}
5067
5308
  } else {
5068
5309
  if ((0, messaging._o)()) this.ctx.logger?.warn?.('[browser] Firefox profile not set; skipping RDP add-on install.');
5069
- const args = [
5070
- ...isFirefoxHeadlessRequested() ? [
5071
- '-headless'
5072
- ] : [],
5073
- ...debugPort > 0 ? [
5074
- '-start-debugger-server',
5075
- String(debugPort)
5076
- ] : [],
5077
- ...'win32' === process.platform ? [
5078
- '-wait-for-browser'
5079
- ] : [],
5080
- ...isFirefoxHeadlessRequested() ? [] : [
5081
- '--foreground'
5082
- ],
5083
- ...firefoxArgs
5084
- ];
5085
- this.child = await this.spawnFirefoxChild(binaryPath, args, wslFallbackBinary);
5310
+ this.child = await this.spawnFirefoxChild(plan.binary, plan.args, wslFallbackBinary);
5086
5311
  (0, ready_stamp.M)(this.extensionOutputPath, {
5087
5312
  browserPid: this.child?.pid,
5088
5313
  extensionId: this.extensionOutputPath ? (0, banner.MT)(this.extensionOutputPath) : void 0
@@ -5092,6 +5317,24 @@ var __webpack_modules__ = {
5092
5317
  this.scheduleWatchTimeout();
5093
5318
  }
5094
5319
  }
5320
+ async printDryRunPlan(compilation, options, binaryPath) {
5321
+ const launchConfig = await resolveFirefoxLaunchConfig(compilation, {
5322
+ ...options,
5323
+ profile: this.host.profile,
5324
+ preferences: this.host.preferences || {},
5325
+ instanceId: this.host.instanceId
5326
+ }, {
5327
+ provision: false
5328
+ });
5329
+ const plan = FirefoxBinaryDetector.launchPlan({
5330
+ binaryPath,
5331
+ profilePath: launchConfig.profilePath,
5332
+ debugPort: (0, shared_utils.jl)(this.host.port, this.host.instanceId),
5333
+ binaryArgs: launchConfig.binaryArgs,
5334
+ headless: isFirefoxHeadlessRequested()
5335
+ });
5336
+ logFirefoxDryRun(plan.binary, plan.args);
5337
+ }
5095
5338
  resolveSpawnStdio() {
5096
5339
  if ('win32' !== process.platform) return [
5097
5340
  'pipe',
@@ -5305,6 +5548,7 @@ var __webpack_modules__ = {
5305
5548
  preferences: pluginOptions.preferences,
5306
5549
  browserFlags: pluginOptions.browserFlags,
5307
5550
  startingUrl: pluginOptions.startingUrl,
5551
+ noOpen: pluginOptions.noOpen,
5308
5552
  geckoBinary: pluginOptions.geckoBinary,
5309
5553
  instanceId: pluginOptions.instanceId,
5310
5554
  port: pluginOptions.port,
@@ -6617,257 +6861,6 @@ var __webpack_modules__ = {
6617
6861
  if (!Number.isFinite(n)) return min;
6618
6862
  return Math.min(Math.max(n, min), max);
6619
6863
  }
6620
- const KNOWN_COMMANDS = new Set([
6621
- 'create',
6622
- 'dev',
6623
- 'start',
6624
- 'preview',
6625
- 'build',
6626
- 'install',
6627
- 'uninstall',
6628
- 'telemetry',
6629
- 'unknown'
6630
- ]);
6631
- function detectInvokedCommand(argv) {
6632
- for(let i = 2; i < argv.length; i += 1){
6633
- const arg = argv[i];
6634
- if (!(!arg || arg.startsWith('-'))) {
6635
- if (KNOWN_COMMANDS.has(arg)) return arg;
6636
- break;
6637
- }
6638
- }
6639
- return 'unknown';
6640
- }
6641
- function readArgValue(argv, names) {
6642
- for(let i = 2; i < argv.length; i += 1){
6643
- const arg = argv[i];
6644
- if (arg) for (const name of names){
6645
- if (arg === name) {
6646
- const next = argv[i + 1];
6647
- return next && !next.startsWith('-') ? next : void 0;
6648
- }
6649
- if (arg.startsWith(`${name}=`)) return arg.slice(name.length + 1);
6650
- }
6651
- }
6652
- }
6653
- function telemetryCommandContext(command, argv = process.argv) {
6654
- if ('create' !== command) return {};
6655
- return {
6656
- template: readArgValue(argv, [
6657
- '--template',
6658
- '-t'
6659
- ]),
6660
- source: readArgValue(argv, [
6661
- '--source'
6662
- ]) || 'cli'
6663
- };
6664
- }
6665
- const consent = resolveTelemetryConsent(process.argv);
6666
- const invoked = detectInvokedCommand(process.argv);
6667
- const telemetry_cli_version = String(getCliPackageJson().version || '0.0.0');
6668
- const telemetry = new Telemetry({
6669
- app: 'extension',
6670
- version: telemetry_cli_version,
6671
- disabled: !consent.enabled
6672
- });
6673
- function getTelemetryConsent() {
6674
- return consent;
6675
- }
6676
- function setTelemetryConsent(value) {
6677
- const ok = writeConsent(value);
6678
- const storage = resolveTelemetryStorage();
6679
- return {
6680
- ok,
6681
- path: storage?.consentFile ?? null
6682
- };
6683
- }
6684
- let tracked = false;
6685
- function markTracked() {
6686
- if (tracked) return false;
6687
- tracked = true;
6688
- return true;
6689
- }
6690
- function markCommandSuccess(command = invoked) {
6691
- if (!markTracked()) return;
6692
- telemetry.track('command_executed', {
6693
- command,
6694
- success: true,
6695
- version: telemetry_cli_version,
6696
- ...telemetryCommandContext(command)
6697
- });
6698
- }
6699
- function markCommandFailure(command = invoked) {
6700
- if (!markTracked()) return;
6701
- telemetry.track('command_failed', {
6702
- command,
6703
- success: false,
6704
- version: telemetry_cli_version,
6705
- ...telemetryCommandContext(command)
6706
- });
6707
- }
6708
- function printOptOutNoticeIfFirstRun() {
6709
- if (!consent.enabled || 'default' !== consent.source) return;
6710
- const storage = resolveTelemetryStorage();
6711
- if (!storage) return;
6712
- const written = writeConsent('enabled');
6713
- if (!written) return;
6714
- console.error(`${external_pintor_default().gray('⏵⏵⏵')} Extension.js collects anonymous, opt-out telemetry (two events: ${external_pintor_default().cyan('command_executed')} + ${external_pintor_default().cyan('command_failed')}). Disable with ${external_pintor_default().cyan('extension telemetry disable')}, ${external_pintor_default().cyan('EXTENSION_TELEMETRY=0')}, or ${external_pintor_default().cyan('--no-telemetry')}. See docs/TELEMETRY.md.`);
6715
- }
6716
- if (consent.enabled) {
6717
- printOptOutNoticeIfFirstRun();
6718
- process.on('beforeExit', async ()=>{
6719
- if (!tracked) if ((process.exitCode ?? 0) === 0) markCommandSuccess();
6720
- else markCommandFailure();
6721
- await telemetry.flush();
6722
- });
6723
- process.on('uncaughtException', ()=>{
6724
- markCommandFailure();
6725
- });
6726
- process.on('unhandledRejection', ()=>{
6727
- markCommandFailure();
6728
- });
6729
- }
6730
- const TELEMETRY_FLUSH_TIMEOUT_MS = 500;
6731
- async function exitAfterDrain(code) {
6732
- try {
6733
- await Promise.race([
6734
- telemetry.flush(),
6735
- new Promise((resolve)=>setTimeout(resolve, TELEMETRY_FLUSH_TIMEOUT_MS).unref?.())
6736
- ]);
6737
- } catch {}
6738
- await Promise.all([
6739
- process.stdout,
6740
- process.stderr
6741
- ].map((stream)=>new Promise((resolve)=>{
6742
- if (0 === stream.writableLength) return resolve();
6743
- stream.write('', ()=>resolve());
6744
- })));
6745
- process.exit(code);
6746
- }
6747
- var external_node_module_ = __webpack_require__("node:module");
6748
- const external_node_url_namespaceObject = require("node:url");
6749
- const extension_develop_runtime_require = (0, external_node_module_.createRequire)(__rslib_import_meta_url__);
6750
- function parseJsonSafe(filePath) {
6751
- return JSON.parse(external_node_fs_default().readFileSync(filePath, 'utf8'));
6752
- }
6753
- function resolveDevelopRootFromDir(dir) {
6754
- try {
6755
- const packageJsonPath = external_node_path_default().join(dir, 'package.json');
6756
- if (!external_node_fs_default().existsSync(packageJsonPath)) return;
6757
- const pkg = parseJsonSafe(packageJsonPath);
6758
- return pkg?.name === 'extension-develop' ? dir : void 0;
6759
- } catch {
6760
- return;
6761
- }
6762
- }
6763
- function resolveWorkspaceDevelopRoot(startDir) {
6764
- let currentDir = external_node_path_default().resolve(startDir);
6765
- if (currentDir.split(external_node_path_default().sep).includes('node_modules')) return;
6766
- for(let depth = 0; depth < 8; depth += 1){
6767
- const candidate = external_node_path_default().join(currentDir, 'programs', 'develop');
6768
- const resolved = resolveDevelopRootFromDir(candidate);
6769
- if (resolved) return resolved;
6770
- const parent = external_node_path_default().dirname(currentDir);
6771
- if (parent === currentDir) break;
6772
- currentDir = parent;
6773
- }
6774
- }
6775
- function resolveInstalledDevelopRoot() {
6776
- try {
6777
- const packageJsonPath = extension_develop_runtime_require.resolve('extension-develop/package.json');
6778
- return resolveDevelopRootFromDir(external_node_path_default().dirname(packageJsonPath));
6779
- } catch {
6780
- try {
6781
- const entryPath = extension_develop_runtime_require.resolve('extension-develop');
6782
- return resolveDevelopRootFromDir(external_node_path_default().dirname(external_node_path_default().dirname(entryPath)));
6783
- } catch {
6784
- return;
6785
- }
6786
- }
6787
- }
6788
- function resolvePreferredDevelopRoot(startDir) {
6789
- const workspaceRoot = resolveWorkspaceDevelopRoot(startDir);
6790
- if (workspaceRoot) return {
6791
- root: workspaceRoot,
6792
- source: 'workspace'
6793
- };
6794
- const envRoot = process.env.EXTENSION_DEVELOP_ROOT ? resolveDevelopRootFromDir(external_node_path_default().resolve(process.env.EXTENSION_DEVELOP_ROOT)) : void 0;
6795
- if (envRoot) return {
6796
- root: envRoot,
6797
- source: 'env'
6798
- };
6799
- const installedRoot = resolveInstalledDevelopRoot();
6800
- if (installedRoot) return {
6801
- root: installedRoot,
6802
- source: 'installed'
6803
- };
6804
- return {
6805
- root: void 0,
6806
- source: 'missing'
6807
- };
6808
- }
6809
- function resolveDevelopDistEntry(root, name = 'module') {
6810
- const base = external_node_path_default().join(root, 'dist', name);
6811
- const candidates = [
6812
- `${base}.mjs`,
6813
- `${base}.cjs`,
6814
- `${base}.js`,
6815
- base
6816
- ];
6817
- for (const candidate of candidates)if (external_node_fs_default().existsSync(candidate)) return candidate;
6818
- }
6819
- function resolveExtensionDevelopRoot(startDir = __dirname) {
6820
- const { root, source } = resolvePreferredDevelopRoot(startDir);
6821
- if (!root) throw new Error('Unable to locate the extension-develop runtime.');
6822
- process.env.EXTENSION_DEVELOP_ROOT = root;
6823
- if ('workspace' === source) process.env.EXTENSION_CREATE_DEVELOP_ROOT = root;
6824
- return root;
6825
- }
6826
- function resolveExtensionDevelopVersion(startDir = __dirname, fallbackVersion) {
6827
- try {
6828
- const root = resolveExtensionDevelopRoot(startDir);
6829
- const packageJsonPath = external_node_path_default().join(root, 'package.json');
6830
- const pkg = parseJsonSafe(packageJsonPath);
6831
- return pkg?.version || fallbackVersion || '0.0.0';
6832
- } catch {
6833
- return fallbackVersion || '0.0.0';
6834
- }
6835
- }
6836
- async function importModule(filePath) {
6837
- const mod = await import((0, external_node_url_namespaceObject.pathToFileURL)(filePath).href);
6838
- return mod?.default && 'object' == typeof mod.default ? {
6839
- ...mod.default,
6840
- ...mod
6841
- } : mod;
6842
- }
6843
- async function loadExtensionDevelopModule(startDir = __dirname) {
6844
- const root = resolveExtensionDevelopRoot(startDir);
6845
- const { source } = resolvePreferredDevelopRoot(startDir);
6846
- const distEntry = resolveDevelopDistEntry(root);
6847
- if (distEntry) return importModule(distEntry);
6848
- if ('workspace' === source) throw new Error(`Local extension-develop runtime is not built at ${external_node_path_default().join(root, 'dist')}. Run \`pnpm --filter extension-develop compile\` before invoking the local CLI.`);
6849
- return await import("extension-develop");
6850
- }
6851
- async function loadExtensionDevelopPreviewModule(startDir = __dirname) {
6852
- const root = resolveExtensionDevelopRoot(startDir);
6853
- const { source } = resolvePreferredDevelopRoot(startDir);
6854
- const previewEntry = resolveDevelopDistEntry(root, 'preview');
6855
- if (previewEntry) return importModule(previewEntry);
6856
- const fullEntry = resolveDevelopDistEntry(root);
6857
- if (fullEntry) return importModule(fullEntry);
6858
- if ('workspace' === source) throw new Error(`Local extension-develop runtime is not built at ${external_node_path_default().join(root, 'dist')}. Run \`pnpm --filter extension-develop compile\` before invoking the local CLI.`);
6859
- return await import("extension-develop/preview");
6860
- }
6861
- async function loadExtensionDevelopBridgeModule(startDir = __dirname) {
6862
- const root = resolveExtensionDevelopRoot(startDir);
6863
- const { source } = resolvePreferredDevelopRoot(startDir);
6864
- const bridgeEntry = resolveDevelopDistEntry(root, 'bridge');
6865
- if (bridgeEntry) return importModule(bridgeEntry);
6866
- if ('workspace' === source) throw new Error(`Local extension-develop runtime is not built at ${external_node_path_default().join(root, 'dist')}. Run \`pnpm --filter extension-develop compile\` before invoking the local CLI.`);
6867
- const bridgeSpecifier = 'extension-develop/bridge';
6868
- return await import(bridgeSpecifier);
6869
- }
6870
- var messaging = __webpack_require__("./helpers/messaging.ts");
6871
6864
  const TEMPLATE_CORPUS_REPO = 'extension-js/examples';
6872
6865
  const TEMPLATE_CORPUS_REF = 'cb6a25377bd9516a1e55447a2010537019851ab2';
6873
6866
  const TEMPLATE_CORPUS_SLUGS = [
@@ -6931,7 +6924,7 @@ var __webpack_modules__ = {
6931
6924
  "typescript",
6932
6925
  'vue'
6933
6926
  ];
6934
- const DEFAULT_TEMPLATE = "javascript";
6927
+ const DEFAULT_TEMPLATE = "typescript";
6935
6928
  const BUNDLED_TEMPLATES = [
6936
6929
  "javascript"
6937
6930
  ];
@@ -7093,6 +7086,9 @@ var __webpack_modules__ = {
7093
7086
  function listTemplates() {
7094
7087
  return TEMPLATE_GROUPS.flatMap((group)=>group.templates);
7095
7088
  }
7089
+ function templateAliasFor(name) {
7090
+ return TEMPLATE_ALIASES.find((alias)=>alias.name === name);
7091
+ }
7096
7092
  function wrapSlugs(slugs, indent, width) {
7097
7093
  const lines = [];
7098
7094
  let current = '';
@@ -7124,32 +7120,290 @@ var __webpack_modules__ = {
7124
7120
  lines.push(` ${title('Aliases')}`);
7125
7121
  for (const alias of TEMPLATE_ALIASES)lines.push(` ${slug(alias.name)} ${dim(`still works, ${alias.note}`)}`);
7126
7122
  }
7127
- return lines.join('\n');
7123
+ return lines.join('\n');
7124
+ }
7125
+ function renderCreateTemplateHelp() {
7126
+ const total = listTemplates().length;
7127
+ const dim = (text)=>external_pintor_default().gray(text);
7128
+ const defaultIsBundled = BUNDLED_TEMPLATES.includes(DEFAULT_TEMPLATE);
7129
+ return [
7130
+ '',
7131
+ external_pintor_default().underline(external_pintor_default().blue(`Templates (${total})`)),
7132
+ renderTemplateList(),
7133
+ '',
7134
+ ` ${external_pintor_default().green('Default')}`,
7135
+ ` ${external_pintor_default().blue(DEFAULT_TEMPLATE)} ${dim('is used when --template is omitted.')}`,
7136
+ ...defaultIsBundled ? [
7137
+ dim(' It ships inside the CLI, so it scaffolds with no network.')
7138
+ ] : [
7139
+ dim(' It downloads the catalog archive like every other name.')
7140
+ ],
7141
+ '',
7142
+ ` ${external_pintor_default().green('Everything else')}`,
7143
+ dim(' downloads the catalog archive at create time, so it needs the'),
7144
+ dim(' network and takes longer. A GitHub URL or a ZIP URL also works.'),
7145
+ ` ${external_pintor_default().blue(TEMPLATE_CATALOG_URL)}`,
7146
+ ''
7147
+ ].join('\n');
7148
+ }
7149
+ const KNOWN_COMMANDS = new Set([
7150
+ 'create',
7151
+ 'dev',
7152
+ 'start',
7153
+ 'preview',
7154
+ 'build',
7155
+ 'install',
7156
+ 'uninstall',
7157
+ 'telemetry',
7158
+ 'unknown'
7159
+ ]);
7160
+ function detectInvokedCommand(argv) {
7161
+ for(let i = 2; i < argv.length; i += 1){
7162
+ const arg = argv[i];
7163
+ if (!(!arg || arg.startsWith('-'))) {
7164
+ if (KNOWN_COMMANDS.has(arg)) return arg;
7165
+ break;
7166
+ }
7167
+ }
7168
+ return 'unknown';
7169
+ }
7170
+ function readArgValue(argv, names) {
7171
+ for(let i = 2; i < argv.length; i += 1){
7172
+ const arg = argv[i];
7173
+ if (arg) for (const name of names){
7174
+ if (arg === name) {
7175
+ const next = argv[i + 1];
7176
+ return next && !next.startsWith('-') ? next : void 0;
7177
+ }
7178
+ if (arg.startsWith(`${name}=`)) return arg.slice(name.length + 1);
7179
+ }
7180
+ }
7181
+ }
7182
+ function advertisedTemplateName(value) {
7183
+ if (!value) return;
7184
+ const name = value.trim();
7185
+ if (!name) return;
7186
+ if (listTemplates().includes(name)) return name;
7187
+ if (templateAliasFor(name)) return name;
7188
+ }
7189
+ function telemetryCommandContext(command, argv = process.argv) {
7190
+ if ('create' !== command) return {};
7191
+ return {
7192
+ template: advertisedTemplateName(readArgValue(argv, [
7193
+ '--template',
7194
+ '-t'
7195
+ ])),
7196
+ source: readArgValue(argv, [
7197
+ '--source'
7198
+ ]) || 'cli'
7199
+ };
7200
+ }
7201
+ const consent = resolveTelemetryConsent(process.argv);
7202
+ const invoked = detectInvokedCommand(process.argv);
7203
+ const telemetry_cli_version = String(getCliPackageJson().version || '0.0.0');
7204
+ const telemetry = new Telemetry({
7205
+ app: 'extension',
7206
+ version: telemetry_cli_version,
7207
+ disabled: !consent.enabled
7208
+ });
7209
+ function getTelemetryConsent() {
7210
+ return consent;
7211
+ }
7212
+ function setTelemetryConsent(value) {
7213
+ const ok = writeConsent(value);
7214
+ const storage = resolveTelemetryStorage();
7215
+ return {
7216
+ ok,
7217
+ path: storage?.consentFile ?? null
7218
+ };
7219
+ }
7220
+ let tracked = false;
7221
+ function markTracked() {
7222
+ if (tracked) return false;
7223
+ tracked = true;
7224
+ return true;
7225
+ }
7226
+ function markCommandSuccess(command = invoked) {
7227
+ if (!markTracked()) return;
7228
+ telemetry.track('command_executed', {
7229
+ command,
7230
+ success: true,
7231
+ version: telemetry_cli_version,
7232
+ ...telemetryCommandContext(command)
7233
+ });
7234
+ }
7235
+ function markCommandFailure(command = invoked) {
7236
+ if (!markTracked()) return;
7237
+ telemetry.track('command_failed', {
7238
+ command,
7239
+ success: false,
7240
+ version: telemetry_cli_version,
7241
+ ...telemetryCommandContext(command)
7242
+ });
7243
+ }
7244
+ function printOptOutNoticeIfFirstRun() {
7245
+ if (!consent.enabled || 'default' !== consent.source) return;
7246
+ const storage = resolveTelemetryStorage();
7247
+ if (!storage) return;
7248
+ const written = writeConsent('enabled');
7249
+ if (!written) return;
7250
+ console.error(`${external_pintor_default().gray('⏵⏵⏵')} Extension.js collects anonymous, opt-out telemetry (two events: ${external_pintor_default().cyan('command_executed')} + ${external_pintor_default().cyan('command_failed')}). Disable with ${external_pintor_default().cyan('extension telemetry disable')}, ${external_pintor_default().cyan('EXTENSION_TELEMETRY=0')}, or ${external_pintor_default().cyan('--no-telemetry')}. See docs/TELEMETRY.md.`);
7251
+ }
7252
+ if (consent.enabled) {
7253
+ printOptOutNoticeIfFirstRun();
7254
+ process.on('beforeExit', async ()=>{
7255
+ if (!tracked) if ((process.exitCode ?? 0) === 0) markCommandSuccess();
7256
+ else markCommandFailure();
7257
+ await telemetry.flush();
7258
+ });
7259
+ process.on('uncaughtException', ()=>{
7260
+ markCommandFailure();
7261
+ });
7262
+ process.on('unhandledRejection', ()=>{
7263
+ markCommandFailure();
7264
+ });
7265
+ }
7266
+ const TELEMETRY_FLUSH_TIMEOUT_MS = 500;
7267
+ async function exitAfterDrain(code) {
7268
+ try {
7269
+ await Promise.race([
7270
+ telemetry.flush(),
7271
+ new Promise((resolve)=>setTimeout(resolve, TELEMETRY_FLUSH_TIMEOUT_MS).unref?.())
7272
+ ]);
7273
+ } catch {}
7274
+ await Promise.all([
7275
+ process.stdout,
7276
+ process.stderr
7277
+ ].map((stream)=>new Promise((resolve)=>{
7278
+ if (0 === stream.writableLength) return resolve();
7279
+ stream.write('', ()=>resolve());
7280
+ })));
7281
+ process.exit(code);
7282
+ }
7283
+ var external_node_module_ = __webpack_require__("node:module");
7284
+ const external_node_url_namespaceObject = require("node:url");
7285
+ const extension_develop_runtime_require = (0, external_node_module_.createRequire)(__rslib_import_meta_url__);
7286
+ function parseJsonSafe(filePath) {
7287
+ return JSON.parse(external_node_fs_default().readFileSync(filePath, 'utf8'));
7288
+ }
7289
+ function resolveDevelopRootFromDir(dir) {
7290
+ try {
7291
+ const packageJsonPath = external_node_path_default().join(dir, 'package.json');
7292
+ if (!external_node_fs_default().existsSync(packageJsonPath)) return;
7293
+ const pkg = parseJsonSafe(packageJsonPath);
7294
+ return pkg?.name === 'extension-develop' ? dir : void 0;
7295
+ } catch {
7296
+ return;
7297
+ }
7298
+ }
7299
+ function resolveWorkspaceDevelopRoot(startDir) {
7300
+ let currentDir = external_node_path_default().resolve(startDir);
7301
+ if (currentDir.split(external_node_path_default().sep).includes('node_modules')) return;
7302
+ for(let depth = 0; depth < 8; depth += 1){
7303
+ const candidate = external_node_path_default().join(currentDir, 'programs', 'develop');
7304
+ const resolved = resolveDevelopRootFromDir(candidate);
7305
+ if (resolved) return resolved;
7306
+ const parent = external_node_path_default().dirname(currentDir);
7307
+ if (parent === currentDir) break;
7308
+ currentDir = parent;
7309
+ }
7310
+ }
7311
+ function resolveInstalledDevelopRoot() {
7312
+ try {
7313
+ const packageJsonPath = extension_develop_runtime_require.resolve('extension-develop/package.json');
7314
+ return resolveDevelopRootFromDir(external_node_path_default().dirname(packageJsonPath));
7315
+ } catch {
7316
+ try {
7317
+ const entryPath = extension_develop_runtime_require.resolve('extension-develop');
7318
+ return resolveDevelopRootFromDir(external_node_path_default().dirname(external_node_path_default().dirname(entryPath)));
7319
+ } catch {
7320
+ return;
7321
+ }
7322
+ }
7323
+ }
7324
+ function resolvePreferredDevelopRoot(startDir) {
7325
+ const workspaceRoot = resolveWorkspaceDevelopRoot(startDir);
7326
+ if (workspaceRoot) return {
7327
+ root: workspaceRoot,
7328
+ source: 'workspace'
7329
+ };
7330
+ const envRoot = process.env.EXTENSION_DEVELOP_ROOT ? resolveDevelopRootFromDir(external_node_path_default().resolve(process.env.EXTENSION_DEVELOP_ROOT)) : void 0;
7331
+ if (envRoot) return {
7332
+ root: envRoot,
7333
+ source: 'env'
7334
+ };
7335
+ const installedRoot = resolveInstalledDevelopRoot();
7336
+ if (installedRoot) return {
7337
+ root: installedRoot,
7338
+ source: 'installed'
7339
+ };
7340
+ return {
7341
+ root: void 0,
7342
+ source: 'missing'
7343
+ };
7344
+ }
7345
+ function resolveDevelopDistEntry(root, name = 'module') {
7346
+ const base = external_node_path_default().join(root, 'dist', name);
7347
+ const candidates = [
7348
+ `${base}.mjs`,
7349
+ `${base}.cjs`,
7350
+ `${base}.js`,
7351
+ base
7352
+ ];
7353
+ for (const candidate of candidates)if (external_node_fs_default().existsSync(candidate)) return candidate;
7354
+ }
7355
+ function resolveExtensionDevelopRoot(startDir = __dirname) {
7356
+ const { root, source } = resolvePreferredDevelopRoot(startDir);
7357
+ if (!root) throw new Error('Unable to locate the extension-develop runtime.');
7358
+ process.env.EXTENSION_DEVELOP_ROOT = root;
7359
+ if ('workspace' === source) process.env.EXTENSION_CREATE_DEVELOP_ROOT = root;
7360
+ return root;
7361
+ }
7362
+ function resolveExtensionDevelopVersion(startDir = __dirname, fallbackVersion) {
7363
+ try {
7364
+ const root = resolveExtensionDevelopRoot(startDir);
7365
+ const packageJsonPath = external_node_path_default().join(root, 'package.json');
7366
+ const pkg = parseJsonSafe(packageJsonPath);
7367
+ return pkg?.version || fallbackVersion || '0.0.0';
7368
+ } catch {
7369
+ return fallbackVersion || '0.0.0';
7370
+ }
7128
7371
  }
7129
- function renderCreateTemplateHelp() {
7130
- const total = listTemplates().length;
7131
- const dim = (text)=>external_pintor_default().gray(text);
7132
- const defaultIsBundled = BUNDLED_TEMPLATES.includes(DEFAULT_TEMPLATE);
7133
- return [
7134
- '',
7135
- external_pintor_default().underline(external_pintor_default().blue(`Templates (${total})`)),
7136
- renderTemplateList(),
7137
- '',
7138
- ` ${external_pintor_default().green('Default')}`,
7139
- ` ${external_pintor_default().blue(DEFAULT_TEMPLATE)} ${dim('is used when --template is omitted.')}`,
7140
- ...defaultIsBundled ? [
7141
- dim(' It ships inside the CLI, so it scaffolds with no network.')
7142
- ] : [
7143
- dim(' It downloads the catalog archive like every other name.')
7144
- ],
7145
- '',
7146
- ` ${external_pintor_default().green('Everything else')}`,
7147
- dim(' downloads the catalog archive at create time, so it needs the'),
7148
- dim(' network and takes longer. A GitHub URL or a ZIP URL also works.'),
7149
- ` ${external_pintor_default().blue(TEMPLATE_CATALOG_URL)}`,
7150
- ''
7151
- ].join('\n');
7372
+ async function importModule(filePath) {
7373
+ const mod = await import((0, external_node_url_namespaceObject.pathToFileURL)(filePath).href);
7374
+ return mod?.default && 'object' == typeof mod.default ? {
7375
+ ...mod.default,
7376
+ ...mod
7377
+ } : mod;
7378
+ }
7379
+ async function loadExtensionDevelopModule(startDir = __dirname) {
7380
+ const root = resolveExtensionDevelopRoot(startDir);
7381
+ const { source } = resolvePreferredDevelopRoot(startDir);
7382
+ const distEntry = resolveDevelopDistEntry(root);
7383
+ if (distEntry) return importModule(distEntry);
7384
+ if ('workspace' === source) throw new Error(`Local extension-develop runtime is not built at ${external_node_path_default().join(root, 'dist')}. Run \`pnpm --filter extension-develop compile\` before invoking the local CLI.`);
7385
+ return await import("extension-develop");
7386
+ }
7387
+ async function loadExtensionDevelopPreviewModule(startDir = __dirname) {
7388
+ const root = resolveExtensionDevelopRoot(startDir);
7389
+ const { source } = resolvePreferredDevelopRoot(startDir);
7390
+ const previewEntry = resolveDevelopDistEntry(root, 'preview');
7391
+ if (previewEntry) return importModule(previewEntry);
7392
+ const fullEntry = resolveDevelopDistEntry(root);
7393
+ if (fullEntry) return importModule(fullEntry);
7394
+ if ('workspace' === source) throw new Error(`Local extension-develop runtime is not built at ${external_node_path_default().join(root, 'dist')}. Run \`pnpm --filter extension-develop compile\` before invoking the local CLI.`);
7395
+ return await import("extension-develop/preview");
7396
+ }
7397
+ async function loadExtensionDevelopBridgeModule(startDir = __dirname) {
7398
+ const root = resolveExtensionDevelopRoot(startDir);
7399
+ const { source } = resolvePreferredDevelopRoot(startDir);
7400
+ const bridgeEntry = resolveDevelopDistEntry(root, 'bridge');
7401
+ if (bridgeEntry) return importModule(bridgeEntry);
7402
+ if ('workspace' === source) throw new Error(`Local extension-develop runtime is not built at ${external_node_path_default().join(root, 'dist')}. Run \`pnpm --filter extension-develop compile\` before invoking the local CLI.`);
7403
+ const bridgeSpecifier = 'extension-develop/bridge';
7404
+ return await import(bridgeSpecifier);
7152
7405
  }
7406
+ var messaging = __webpack_require__("./helpers/messaging.ts");
7153
7407
  function getLoggingPrefix(type) {
7154
7408
  return (0, messaging.Pl)(type);
7155
7409
  }
@@ -7856,9 +8110,135 @@ Cross-browser compatibility
7856
8110
  function deprecatedOutputAlias(flag) {
7857
8111
  return `${getLoggingPrefix('warn')} ${messages_code(flag)} is deprecated and maps to ${messages_code('--output')}.\nPass ${messages_code('--output')} ${messages_arg('<pretty|json>')} instead.`;
7858
8112
  }
8113
+ const OPEN_SURFACE_NOUN = {
8114
+ popup: 'popup',
8115
+ sidebar: 'side panel',
8116
+ action: 'action popup'
8117
+ };
8118
+ function openSurfaceNoun(surface) {
8119
+ return OPEN_SURFACE_NOUN[surface] || surface;
8120
+ }
8121
+ function openSurfaceGestureStep(surface) {
8122
+ return `Click the extension's toolbar entry, or its row in the extensions menu, to open the ${openSurfaceNoun(surface)}.`;
8123
+ }
8124
+ function openSurfaceNeedsGesturePlain(surface) {
8125
+ return `Chromium opens the ${openSurfaceNoun(surface)} only in response to a user gesture, so extension open ${surface} can't open it from the command line.`;
8126
+ }
8127
+ function openSurfaceNeedsGesture(surface) {
8128
+ return `${getLoggingPrefix('error')} Chromium opens the ${openSurfaceNoun(surface)} only in response to a user gesture, so ${messages_code(`extension open ${surface}`)} can't open it from the command line.\n` + openSurfaceGestureStep(surface);
8129
+ }
8130
+ function sessionOnPort(browser, port) {
8131
+ return port ? `for ${browser} on port ${port}` : `for ${browser}`;
8132
+ }
8133
+ function controlDisabledInSessionPlain(browser, port, flag) {
8134
+ return `The dev session ${sessionOnPort(browser, port)} refused the controller because control is off in that session. Restart the dev session with ${flag} to turn it on, or run extension doctor when a session started with ${flag} still answers this way.`;
8135
+ }
8136
+ function controlDisabledInSession(browser, port, flag) {
8137
+ return `${getLoggingPrefix('error')} The dev session ${sessionOnPort(browser, port)} refused the controller because control is off in that session.\nRestart the dev session with ${messages_code(flag)} to turn it on, or run ${messages_code('extension doctor')} when a session started with ${messages_code(flag)} still answers this way.`;
8138
+ }
7859
8139
  function noBrowserNotSupportedForCommand(command) {
7860
8140
  return `${getLoggingPrefix('error')} ${messages_code('--no-browser')} is only supported for ${messages_code('dev')}, ${messages_code('start')}, and ${messages_code('preview')}.\n${messaging.Sc.label('GOT')} ${messages_code(command || '(none)')}`;
7861
8141
  }
8142
+ let warnedDeprecatedAlias = false;
8143
+ function warnDeprecatedOutputAlias(flag) {
8144
+ if (warnedDeprecatedAlias) return;
8145
+ warnedDeprecatedAlias = true;
8146
+ console.error(deprecatedOutputAlias(flag));
8147
+ }
8148
+ function normalizeOutputFormat(value) {
8149
+ const v = String(value ?? '').trim().toLowerCase();
8150
+ return 'pretty' === v || 'json' === v ? v : void 0;
8151
+ }
8152
+ const normalize = normalizeOutputFormat;
8153
+ function isJsonOutput(opts) {
8154
+ return 'json' === resolveOutputFormat(opts);
8155
+ }
8156
+ function resolveOutputFormat(opts) {
8157
+ const direct = normalize(opts.output);
8158
+ if (direct) return direct;
8159
+ const aliases = [
8160
+ [
8161
+ '--format',
8162
+ opts.format
8163
+ ],
8164
+ [
8165
+ '--wait-format',
8166
+ opts.waitFormat
8167
+ ]
8168
+ ];
8169
+ for (const [flag, value] of aliases){
8170
+ const resolved = normalize(value);
8171
+ if (resolved) {
8172
+ warnDeprecatedOutputAlias(flag);
8173
+ return resolved;
8174
+ }
8175
+ }
8176
+ return 'pretty';
8177
+ }
8178
+ function resolveSessionProjectPath(bridge, pathArg) {
8179
+ const inputPath = external_node_path_default().resolve(pathArg || process.cwd());
8180
+ const resolve = bridge?.resolveSessionProjectRoot;
8181
+ if ('function' != typeof resolve) return inputPath;
8182
+ try {
8183
+ const resolved = resolve(inputPath);
8184
+ return 'string' == typeof resolved && resolved ? resolved : inputPath;
8185
+ } catch {
8186
+ return inputPath;
8187
+ }
8188
+ }
8189
+ function sessionFilePath(bridge, exportName, projectPath, browser, fileName) {
8190
+ const fn = bridge?.[exportName];
8191
+ if ('function' == typeof fn) try {
8192
+ const resolved = fn(projectPath, browser);
8193
+ if ('string' == typeof resolved && resolved) return resolved;
8194
+ } catch {}
8195
+ return external_node_path_default().join(projectPath, 'dist', 'extension-js', browser, fileName);
8196
+ }
8197
+ function sessionReadyPath(bridge, projectPath, browser) {
8198
+ return sessionFilePath(bridge, 'readyContractPath', projectPath, browser, 'ready.json');
8199
+ }
8200
+ function sessionLogsPath(bridge, projectPath, browser) {
8201
+ return sessionFilePath(bridge, 'logsPath', projectPath, browser, 'logs.ndjson');
8202
+ }
8203
+ function parseLogSinceLocal(value) {
8204
+ if (null == value || '' === value) return null;
8205
+ if ('number' == typeof value) return Number.isFinite(value) ? {
8206
+ seq: value
8207
+ } : void 0;
8208
+ const text = String(value).trim();
8209
+ if (/^\d+(?:\.\d+)?$/.test(text)) return {
8210
+ seq: Number(text)
8211
+ };
8212
+ const time = Date.parse(text);
8213
+ return Number.isFinite(time) ? {
8214
+ time
8215
+ } : void 0;
8216
+ }
8217
+ function isAfterSinceLocal(event, since) {
8218
+ if ('seq' in since) return !('number' == typeof event.seq && event.seq <= since.seq);
8219
+ const raw = event.timestamp;
8220
+ const time = 'number' == typeof raw ? raw : 'string' == typeof event.ts ? Date.parse(String(event.ts)) : NaN;
8221
+ return !Number.isFinite(time) || time > since.time;
8222
+ }
8223
+ function sinceHelpersFrom(bridge) {
8224
+ const candidate = bridge;
8225
+ return {
8226
+ parseLogSince: 'function' == typeof candidate?.parseLogSince ? candidate.parseLogSince : parseLogSinceLocal,
8227
+ isAfterSince: 'function' == typeof candidate?.isAfterSince ? candidate.isAfterSince : isAfterSinceLocal
8228
+ };
8229
+ }
8230
+ function parseLogLine(line) {
8231
+ let parsed;
8232
+ try {
8233
+ parsed = JSON.parse(line);
8234
+ } catch {
8235
+ return null;
8236
+ }
8237
+ return isLogEventLike(parsed) ? parsed : null;
8238
+ }
8239
+ function isLogEventLike(value) {
8240
+ return Boolean(value) && 'object' == typeof value && !Array.isArray(value);
8241
+ }
7862
8242
  const LEVEL_ORDER = [
7863
8243
  'error',
7864
8244
  'warn',
@@ -7887,10 +8267,9 @@ Cross-browser compatibility
7887
8267
  return candidates.some((c)=>re ? re.test(c) : c.includes(pattern));
7888
8268
  };
7889
8269
  }
7890
- function makeFilter(opts) {
8270
+ function makeFilter(opts, since, helpers) {
7891
8271
  const minLevel = String(opts.level || 'all').toLowerCase();
7892
8272
  const contexts = opts.context && 'all' !== opts.context.toLowerCase() ? new Set(opts.context.split(',').map((c)=>c.trim())) : null;
7893
- const sinceSeq = null != opts.since ? Number(opts.since) : null;
7894
8273
  const urlMatches = opts.url ? makeUrlMatcher(opts.url) : null;
7895
8274
  const tabId = null != opts.tab && '' !== opts.tab ? Number(opts.tab) : null;
7896
8275
  return (event)=>{
@@ -7901,14 +8280,15 @@ Cross-browser compatibility
7901
8280
  if ('all' !== minLevel && 'off' !== minLevel) {
7902
8281
  if (levelRank(String(event.level || '')) > levelRank(minLevel)) return false;
7903
8282
  }
7904
- if (null != sinceSeq && Number.isFinite(sinceSeq) && 'number' == typeof event.seq && event.seq <= sinceSeq) return false;
8283
+ if (since && !helpers.isAfterSince(event, since)) return false;
7905
8284
  if (urlMatches && !urlMatches(event)) return false;
7906
8285
  if (null != tabId && Number.isFinite(tabId) && event.tabId !== tabId) return false;
7907
8286
  return true;
7908
8287
  };
7909
8288
  }
7910
8289
  function resolveFormat(opts) {
7911
- if (opts.output) return opts.output;
8290
+ const requested = String(opts.output ?? '').trim().toLowerCase();
8291
+ if ('pretty' === requested || 'json' === requested || 'ndjson' === requested) return requested;
7912
8292
  return process.stdout.isTTY ? 'pretty' : 'ndjson';
7913
8293
  }
7914
8294
  function printEvent(event, format) {
@@ -7929,18 +8309,28 @@ Cross-browser compatibility
7929
8309
  console.log(JSON.stringify(frame));
7930
8310
  }
7931
8311
  }
7932
- function logsFilePath(projectPath, browser) {
7933
- return external_node_path_default().resolve(projectPath, 'dist', 'extension-js', browser, 'logs.ndjson');
7934
- }
7935
8312
  function registerLogsCommand(program) {
7936
- program.command('logs').arguments('[project-path]').usage('[project-path] [options]').description(commandDescriptions.logs).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', '[experimental] show only structured dx.signal diagnostics. No emitter ships yet, so this currently prints nothing').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 (projectPathArg, options)=>{
7937
- const projectPath = external_node_path_default().resolve(projectPathArg || process.cwd());
8313
+ program.command('logs').arguments('[project-path]').usage('[project-path] [options]').description(commandDescriptions.logs).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', '[experimental] show only structured dx.signal diagnostics. No emitter ships yet, so this currently prints nothing').option('--since <seq|iso>', 'only show events after this sequence number or ISO timestamp').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 (projectPathArg, options)=>{
8314
+ const bridge = await loadExtensionDevelopBridgeModule();
8315
+ const projectPath = resolveSessionProjectPath(bridge, projectPathArg);
7938
8316
  const browser = options.browser || 'chromium';
7939
8317
  const format = resolveFormat(options);
7940
- const matches = makeFilter(options);
8318
+ const helpers = sinceHelpersFrom(bridge);
8319
+ const since = helpers.parseLogSince(options.since);
8320
+ if (null != options.since && '' !== options.since && void 0 === since) {
8321
+ const message = `extension logs --since expects a sequence number or an ISO timestamp, got: ${options.since}`;
8322
+ console.error(message);
8323
+ if ('pretty' !== format) writeFrame(messaging.Pr.fail('logs', 'usage', {
8324
+ code: messaging.Lp.E_INVALID_OPTION,
8325
+ message,
8326
+ name: 'CliError'
8327
+ }));
8328
+ process.exit(1);
8329
+ }
8330
+ const matches = makeFilter(options, since ?? null, helpers);
7941
8331
  if (options.signalsOnly) console.error("extension logs --signals-only: no signals emitter ships in this build, so dev sessions record no dx.signal events and this filter will print nothing.");
7942
8332
  if (options.follow) return void await followLogs(projectPath, browser, format, matches);
7943
- const file = logsFilePath(projectPath, browser);
8333
+ const file = sessionLogsPath(bridge, projectPath, browser);
7944
8334
  if (!external_node_fs_default().existsSync(file)) {
7945
8335
  const message = `No logs found at ${file}. Start a dev session (extension dev) first, or pass --browser to match it.`;
7946
8336
  console.error(message);
@@ -7953,21 +8343,17 @@ Cross-browser compatibility
7953
8343
  }
7954
8344
  const lines = external_node_fs_default().readFileSync(file, 'utf-8').split('\n').filter(Boolean);
7955
8345
  for (const line of lines){
7956
- let event;
7957
- try {
7958
- event = JSON.parse(line);
7959
- } catch {
7960
- continue;
7961
- }
7962
- if (matches(event)) printEvent(event, format);
8346
+ const event = parseLogLine(line);
8347
+ if (event && matches(event)) printEvent(event, format);
7963
8348
  }
7964
8349
  });
7965
8350
  }
7966
8351
  async function followLogs(projectPath, browser, format, matches) {
7967
- const { BridgeConsumer, readReadyContract } = await loadExtensionDevelopBridgeModule();
8352
+ const bridge = await loadExtensionDevelopBridgeModule();
8353
+ const { BridgeConsumer, readReadyContract } = bridge;
7968
8354
  const ready = readReadyContract(projectPath, browser);
7969
8355
  if (!ready) {
7970
- const message = `No active dev session control channel found for ${browser}. Run \`extension dev --browser=${browser}\` first.`;
8356
+ const message = `No active dev session control channel found for ${browser}. Looked at ${sessionReadyPath(bridge, projectPath, browser)}. Run \`extension dev --browser=${browser}\` first.`;
7971
8357
  console.error(message);
7972
8358
  if ('pretty' !== format) writeFrame(messaging.Pr.fail('logs', 'not-found', {
7973
8359
  code: messaging.Lp.E_SESSION_NOT_FOUND,
@@ -7976,6 +8362,19 @@ Cross-browser compatibility
7976
8362
  }));
7977
8363
  process.exit(1);
7978
8364
  }
8365
+ let settle = ()=>{};
8366
+ const settled = new Promise((resolve)=>{
8367
+ settle = resolve;
8368
+ });
8369
+ const refusals = new Set([
8370
+ bridge.CLOSE_BAD_INSTANCE,
8371
+ bridge.CLOSE_BAD_HELLO,
8372
+ bridge.CLOSE_CONTROL_UNAVAILABLE
8373
+ ].filter((code)=>'number' == typeof code));
8374
+ const sessionStillNamed = ()=>{
8375
+ const current = readReadyContract(projectPath, browser);
8376
+ return Boolean(current) && 'stopped' !== current.status && current.instanceId === ready.instanceId && current.controlPort === ready.controlPort;
8377
+ };
7979
8378
  const consumer = new BridgeConsumer({
7980
8379
  controlPort: ready.controlPort,
7981
8380
  instanceId: ready.instanceId,
@@ -7985,6 +8384,18 @@ Cross-browser compatibility
7985
8384
  },
7986
8385
  onGap: (gap)=>{
7987
8386
  console.error(`… ${gap.dropped} event(s) dropped (${gap.reason}), stream is behind`);
8387
+ },
8388
+ onClose: (close)=>{
8389
+ if (!refusals.has(close.code) && sessionStillNamed()) return;
8390
+ consumer.close();
8391
+ const why = close.reason ? `${close.code}, ${close.reason}` : close.code;
8392
+ console.error(`extension logs --follow: the control channel closed (${why}) and the dev session is over.`);
8393
+ if ('pretty' !== format) console.log(JSON.stringify(messaging.Pr.ok('logs', 'closed', {
8394
+ closeCode: close.code,
8395
+ reason: close.reason,
8396
+ follow: true
8397
+ })));
8398
+ settle();
7988
8399
  }
7989
8400
  });
7990
8401
  const shutdown = (signal)=>{
@@ -7998,7 +8409,7 @@ Cross-browser compatibility
7998
8409
  process.on('SIGINT', ()=>shutdown('SIGINT'));
7999
8410
  process.on('SIGTERM', ()=>shutdown('SIGTERM'));
8000
8411
  consumer.start();
8001
- await new Promise(()=>{});
8412
+ await settled;
8002
8413
  }
8003
8414
  function readRecentConsole(projectPath, browser, target, limit) {
8004
8415
  const file = external_node_path_default().resolve(projectPath, 'dist', 'extension-js', browser, 'logs.ndjson');
@@ -8098,15 +8509,54 @@ Cross-browser compatibility
8098
8509
  }
8099
8510
  function fail(message, frame) {
8100
8511
  console.error(message);
8101
- if (frame && 'json' === frame.output) act_writeFrame(messaging.Pr.fail(frame.command, statusForCode(frame.code), {
8512
+ if (frame && 'json' === normalizeOutputFormat(frame.output)) act_writeFrame(messaging.Pr.fail(frame.command, statusForCode(frame.code), {
8102
8513
  code: frame.code,
8103
- message,
8104
- name: 'CliError'
8514
+ message: frame.plain ?? message,
8515
+ name: 'CliError',
8516
+ ...frame.hint ? {
8517
+ hint: frame.hint
8518
+ } : {}
8105
8519
  }));
8106
8520
  process.exit(1);
8107
8521
  }
8522
+ function isChromiumFamily(browser) {
8523
+ return !/firefox|gecko|safari|webkit/i.test(browser);
8524
+ }
8525
+ function readSessionManifest(bridge, projectPath, browser) {
8526
+ const candidates = [];
8527
+ const readDocument = bridge?.readReadyContractDocument;
8528
+ if ('function' == typeof readDocument) try {
8529
+ const doc = readDocument(projectPath, browser);
8530
+ if (doc && 'string' == typeof doc.distPath) candidates.push(external_node_path_default().join(doc.distPath, 'manifest.json'));
8531
+ if (doc && 'string' == typeof doc.manifestPath) candidates.push(doc.manifestPath);
8532
+ } catch {}
8533
+ candidates.push(external_node_path_default().join(projectPath, 'manifest.json'));
8534
+ for (const file of candidates)try {
8535
+ const parsed = JSON.parse(external_node_fs_default().readFileSync(file, 'utf-8'));
8536
+ if (parsed && 'object' == typeof parsed && !Array.isArray(parsed)) return parsed;
8537
+ } catch {}
8538
+ }
8539
+ function manifestDeclaresPopup(manifest) {
8540
+ for (const [key, value] of Object.entries(manifest)){
8541
+ if (!/(?:^|:)(?:action|browser_action)$/.test(key)) continue;
8542
+ const popup = value?.default_popup;
8543
+ if ('string' == typeof popup && popup.trim()) return true;
8544
+ }
8545
+ return false;
8546
+ }
8547
+ function gestureRefusal(surface, bridge, projectPath, browser) {
8548
+ if (!isChromiumFamily(browser)) return;
8549
+ const gated = 'popup' === surface || 'sidebar' === surface || 'action' === surface && manifestDeclaresPopup(readSessionManifest(bridge, projectPath, browser) ?? {});
8550
+ if (!gated) return;
8551
+ return {
8552
+ message: openSurfaceNeedsGesture(surface),
8553
+ plain: openSurfaceNeedsGesturePlain(surface),
8554
+ code: messaging.Lp.E_USER_GESTURE_REQUIRED,
8555
+ hint: openSurfaceGestureStep(surface)
8556
+ };
8557
+ }
8108
8558
  function printResult(result, output, command) {
8109
- if ('json' === output) return void console.log(JSON.stringify(buildActEnvelope(command, result)));
8559
+ if ('json' === normalizeOutputFormat(output)) return void console.log(JSON.stringify(buildActEnvelope(command, result)));
8110
8560
  if (result.ok) {
8111
8561
  const value = result.value;
8112
8562
  console.log('string' == typeof value ? value : JSON.stringify(value, null, 2));
@@ -8125,16 +8575,24 @@ Cross-browser compatibility
8125
8575
  console.error(`${err.name}: ${err.message}${err.engine ? ` (engine: ${err.engine})` : ''}`);
8126
8576
  }
8127
8577
  async function runCommand(input) {
8128
- const projectPath = external_node_path_default().resolve(input.projectPathArg || process.cwd());
8578
+ const bridge = await loadExtensionDevelopBridgeModule();
8579
+ const projectPath = resolveSessionProjectPath(bridge, input.projectPathArg);
8129
8580
  const browser = input.opts.browser || 'chromium';
8130
- const { BridgeController, readReadyContract, readControlToken } = await loadExtensionDevelopBridgeModule();
8581
+ const { BridgeController, readReadyContract, readControlToken } = bridge;
8131
8582
  const outputFrame = {
8132
8583
  command: input.command,
8133
8584
  output: input.opts.output
8134
8585
  };
8135
8586
  const unlockFlag = 'eval' === input.op ? '--allow-eval' : '--allow-control';
8587
+ const refusal = input.preflight?.(bridge, projectPath, browser);
8588
+ if (refusal) fail(refusal.message, {
8589
+ ...outputFrame,
8590
+ code: refusal.code,
8591
+ plain: refusal.plain,
8592
+ hint: refusal.hint
8593
+ });
8136
8594
  const ready = readReadyContract(projectPath, browser);
8137
- if (!ready) fail(`No active control channel found for ${browser}. Run \`extension dev --browser=${browser} ${unlockFlag}\` first.`, {
8595
+ if (!ready) fail(`No active control channel found for ${browser}. Looked at ${sessionReadyPath(bridge, projectPath, browser)}. Run \`extension dev --browser=${browser} ${unlockFlag}\` first.`, {
8138
8596
  ...outputFrame,
8139
8597
  code: messaging.Lp.E_SESSION_NOT_FOUND
8140
8598
  });
@@ -8150,6 +8608,12 @@ Cross-browser compatibility
8150
8608
  } catch (err) {
8151
8609
  controller.close();
8152
8610
  const message = err?.message || 'could not connect to the control channel';
8611
+ const closeCode = err?.closeCode;
8612
+ if (4003 === closeCode || /code 4003\b/.test(message)) fail(controlDisabledInSession(browser, ready.controlPort, unlockFlag), {
8613
+ ...outputFrame,
8614
+ code: messaging.Lp.E_CONTROL_DENIED,
8615
+ plain: controlDisabledInSessionPlain(browser, ready.controlPort, unlockFlag)
8616
+ });
8153
8617
  fail(message, {
8154
8618
  ...outputFrame,
8155
8619
  code: /code 40\d\d/.test(message) ? messaging.Lp.E_CONTROL_DENIED : messaging.Lp.E_CONTROL_UNAVAILABLE
@@ -8321,13 +8785,25 @@ Cross-browser compatibility
8321
8785
  context
8322
8786
  },
8323
8787
  args,
8324
- opts
8788
+ opts,
8789
+ preflight: (bridge, projectPath, browser)=>gestureRefusal(surface, bridge, projectPath, browser)
8325
8790
  });
8326
8791
  });
8327
8792
  }
8328
8793
  var safari_launch = __webpack_require__("./browsers/run-safari/safari-launch/index.ts");
8329
8794
  var safari_config = __webpack_require__("./browsers/run-safari/safari-launch/safari-config.ts");
8330
8795
  var safari_packager = __webpack_require__("./browsers/run-safari/safari-packager.ts");
8796
+ async function resolveConfigBrowser(projectPath, command) {
8797
+ try {
8798
+ const develop = await loadExtensionDevelopModule();
8799
+ if ('function' != typeof develop.loadCommandConfig) return;
8800
+ const config = await develop.loadCommandConfig(projectPath, command);
8801
+ const browser = config?.browser;
8802
+ return 'string' == typeof browser && browser.length > 0 ? browser : void 0;
8803
+ } catch {
8804
+ return;
8805
+ }
8806
+ }
8331
8807
  function parseLogContexts(raw) {
8332
8808
  if (!raw || 0 === String(raw).trim().length) return;
8333
8809
  if ('all' === String(raw).trim().toLowerCase()) return;
@@ -8438,7 +8914,8 @@ Cross-browser compatibility
8438
8914
  return null;
8439
8915
  }
8440
8916
  function registerBuildCommand(program) {
8441
- program.command('build').arguments('[project-name]').usage('[path-to-remote-extension] [options]').description(commandDescriptions.build).option(`--browser <${BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds a Safari app via Xcode (macOS only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `false`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--zip [boolean]', 'whether or not to compress the extension into a ZIP file. Defaults to `false`', parseOptionalBoolean).option('--zip-source [boolean]', 'whether or not to include the source files in the ZIP file. Defaults to `false`', parseOptionalBoolean).option('--zip-filename <string>', 'specify the name of the ZIP file. Defaults to the extension name and version').option('--silent [boolean]', 'suppress the build summary output. Defaults to `false`', parseOptionalBoolean).option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--mode <development|production|none>', 'bundler mode override (also sets NODE_ENV). Defaults to `production`').option('--open [boolean]', 'open the built Safari app after packaging (safari targets only). Defaults to `false`', parseOptionalBoolean).option('--app-name <name>', 'override the Safari app name (safari targets only). Defaults to the manifest `name`').option('--bundle-id <reverse.dns>', 'set a user-owned Safari bundle identifier (safari targets only). Defaults to a generated dev.extensionjs.* id').option('--development-team <id>', 'sign the Safari app with an Apple Developer team id (safari targets only). Without it the build is ad-hoc signed, which Safari treats as unsigned: the extension then needs Develop \u25b8 Allow Unsigned Extensions re-ticked on every launch. A signed build is listed and stays enabled across restarts').option('--macos-only [boolean]', 'generate a macOS-only Safari Xcode project (safari targets only). Pass `false` for a universal macOS + iOS project. Defaults to `true`', parseOptionalBoolean).option('--force-regenerate', 'regenerate the Safari Xcode project even when up to date (safari targets only)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).action(async (pathOrRemoteUrl, { browser = 'chromium', ...buildOptions })=>{
8917
+ program.command('build').arguments('[project-name]').usage('[path-to-remote-extension] [options]').description(commandDescriptions.build).option(`--browser <${BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds a Safari app via Xcode (macOS only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `false`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--zip [boolean]', 'whether or not to compress the extension into a ZIP file. Defaults to `false`', parseOptionalBoolean).option('--zip-source [boolean]', 'whether or not to include the source files in the ZIP file. Defaults to `false`', parseOptionalBoolean).option('--zip-filename <string>', 'specify the name of the ZIP file. Defaults to the extension name and version').option('--silent [boolean]', 'suppress the build summary output. Defaults to `false`', parseOptionalBoolean).option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--mode <development|production|none>', 'bundler mode override (also sets NODE_ENV). Defaults to `production`').option('--open [boolean]', 'open the built Safari app after packaging (safari targets only). Defaults to `false`', parseOptionalBoolean).option('--app-name <name>', 'override the Safari app name (safari targets only). Defaults to the manifest `name`').option('--bundle-id <reverse.dns>', 'set a user-owned Safari bundle identifier (safari targets only). Defaults to a generated dev.extensionjs.* id').option('--development-team <id>', 'sign the Safari app with an Apple Developer team id (safari targets only). Without it the build is ad-hoc signed, which Safari treats as unsigned: the extension then needs Develop \u25b8 Allow Unsigned Extensions re-ticked on every launch. A signed build is listed and stays enabled across restarts').option('--macos-only [boolean]', 'generate a macOS-only Safari Xcode project (safari targets only). Pass `false` for a universal macOS + iOS project. Defaults to `true`', parseOptionalBoolean).option('--force-regenerate', 'regenerate the Safari Xcode project even when up to date (safari targets only)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).action(async (pathOrRemoteUrl, { browser: cliBrowser, ...buildOptions })=>{
8918
+ const browser = cliBrowser ?? await resolveConfigBrowser(pathOrRemoteUrl || process.cwd(), 'build') ?? 'chromium';
8442
8919
  if (buildOptions.debug || buildOptions.author || buildOptions.authorMode) {
8443
8920
  process.env.EXTENSION_DEBUG = '1';
8444
8921
  process.env.EXTENSION_AUTHOR_MODE = 'true';
@@ -8502,7 +8979,7 @@ Cross-browser compatibility
8502
8979
  }
8503
8980
  }
8504
8981
  const { extensionBuild } = await loadExtensionDevelopModule();
8505
- const asJson = 'json' === buildOptions.output;
8982
+ const asJson = isJsonOutput(buildOptions);
8506
8983
  if (asJson) process.env.EXTENSION_OUTPUT = 'json';
8507
8984
  const built = [];
8508
8985
  const summaries = [];
@@ -8601,7 +9078,9 @@ Cross-browser compatibility
8601
9078
  }
8602
9079
  function registerCreateCommand(program) {
8603
9080
  program.command('create').arguments('<project-name|project-path>').usage('<project-name|project-path> [options]').description(commandDescriptions.create).option('-t, --template <template-name>', `catalog name, GitHub URL, or ZIP URL to scaffold from; every catalog name is listed below (default: ${DEFAULT_TEMPLATE})`).option('--install [boolean]', 'whether or not to install the dependencies after creating the project (disabled by default, pass --install to opt in)', parseOptionalBoolean, false).option('--source <source>', 'attribution tag for where this create was initiated (e.g. cli, templates); recorded in anonymous telemetry only').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addHelpText('after', renderCreateTemplateHelp()).action(async (pathOrRemoteUrl, { template, install, output })=>{
8604
- const asJson = 'json' === output;
9081
+ const asJson = isJsonOutput({
9082
+ output
9083
+ });
8605
9084
  if (!process.env.EXTENSION_CREATE_DEVELOP_ROOT) try {
8606
9085
  process.env.EXTENSION_CREATE_DEVELOP_ROOT = resolveExtensionDevelopRoot();
8607
9086
  } catch {
@@ -8672,6 +9151,59 @@ Cross-browser compatibility
8672
9151
  ].includes(normalized)) return true;
8673
9152
  }
8674
9153
  }
9154
+ function scanArgvValue(argv, flag) {
9155
+ const equalArg = argv.find((arg)=>arg.startsWith(`${flag}=`));
9156
+ if (equalArg) return equalArg.slice(flag.length + 1);
9157
+ const flagIndex = argv.indexOf(flag);
9158
+ if (flagIndex >= 0) return argv[flagIndex + 1] || '';
9159
+ }
9160
+ function collectValuedLongFlags(program) {
9161
+ const flags = new Set();
9162
+ const visit = (command)=>{
9163
+ for (const option of command.options)if (option.long && (option.required || option.optional)) flags.add(option.long);
9164
+ for (const sub of command.commands)visit(sub);
9165
+ };
9166
+ visit(program);
9167
+ return flags;
9168
+ }
9169
+ function resolveCommandFromArgv(argv, valuedFlags = new Set()) {
9170
+ for(let i = 2; i < argv.length; i += 1){
9171
+ const arg = argv[i];
9172
+ if (arg.startsWith('-')) {
9173
+ if (valuedFlags.has(arg)) i += 1;
9174
+ continue;
9175
+ }
9176
+ return arg;
9177
+ }
9178
+ }
9179
+ function rewriteOutputAliasArgv(argv, valuedFlags = new Set()) {
9180
+ const index = argv.findIndex((arg)=>'--format' === arg || arg.startsWith('--format='));
9181
+ if (index < 0) return {
9182
+ argv,
9183
+ rewritten: false
9184
+ };
9185
+ const next = [
9186
+ ...argv
9187
+ ];
9188
+ const tokens = [];
9189
+ if (next[index].startsWith('--format=')) {
9190
+ tokens.push(`--output=${next[index].slice(9)}`);
9191
+ next.splice(index, 1);
9192
+ } else {
9193
+ tokens.push('--output');
9194
+ const value = next[index + 1];
9195
+ next.splice(index, void 0 === value || value.startsWith('-') ? 1 : 2);
9196
+ if (void 0 !== value && !value.startsWith('-')) tokens.push(value);
9197
+ }
9198
+ const command = resolveCommandFromArgv(next, valuedFlags);
9199
+ const commandIndex = command ? next.indexOf(command, 2) : -1;
9200
+ if (commandIndex >= index) next.splice(commandIndex + 1, 0, ...tokens);
9201
+ else next.splice(index, 0, ...tokens);
9202
+ return {
9203
+ argv: next,
9204
+ rewritten: true
9205
+ };
9206
+ }
8675
9207
  const FRAMED_FLAG = 'extensionEnvelopePrinted';
8676
9208
  function markErrorFramed(err) {
8677
9209
  if (err && 'object' == typeof err) try {
@@ -8682,11 +9214,7 @@ Cross-browser compatibility
8682
9214
  return Boolean(err && 'object' == typeof err && true === err[FRAMED_FLAG]);
8683
9215
  }
8684
9216
  function wantsJsonOutput(argv) {
8685
- const equalArg = argv.find((arg)=>arg.startsWith('--output='));
8686
- if (equalArg) return 'json' === equalArg.slice(9).trim().toLowerCase();
8687
- const flagIndex = argv.indexOf('--output');
8688
- if (flagIndex >= 0) return 'json' === String(argv[flagIndex + 1] || '').trim().toLowerCase();
8689
- return false;
9217
+ return 'json' === normalizeOutputFormat(scanArgvValue(argv, '--output'));
8690
9218
  }
8691
9219
  function isCommanderError(err) {
8692
9220
  return Boolean(err && 'object' == typeof err && 'string' == typeof err.code && String(err.code).startsWith('commander.'));
@@ -8784,16 +9312,14 @@ Cross-browser compatibility
8784
9312
  console.log(JSON.stringify(frame));
8785
9313
  }
8786
9314
  }
8787
- async function resolveConfigBrowser(projectPath, command) {
8788
- try {
8789
- const develop = await loadExtensionDevelopModule();
8790
- if ('function' != typeof develop.loadCommandConfig) return;
8791
- const config = await develop.loadCommandConfig(projectPath, command);
8792
- const browser = config?.browser;
8793
- return 'string' == typeof browser && browser.length > 0 ? browser : void 0;
8794
- } catch {
8795
- return;
8796
- }
9315
+ function geckoBinaryOption() {
9316
+ return new external_commander_namespaceObject.Option('--gecko-binary <path-to-binary>', 'specify a path to the Gecko binary (alias: --firefox-binary). This option overrides the --browser setting. Defaults to the system default');
9317
+ }
9318
+ function firefoxBinaryAliasOption() {
9319
+ return new external_commander_namespaceObject.Option('--firefox-binary <path-to-binary>').hideHelp();
9320
+ }
9321
+ function cliGeckoBinary(opts) {
9322
+ return opts.geckoBinary ?? opts.firefoxBinary;
8797
9323
  }
8798
9324
  async function resolveNoBrowser(projectPath, command) {
8799
9325
  if ('1' === process.env.EXTENSION_CLI_NO_BROWSER) return true;
@@ -8806,38 +9332,6 @@ Cross-browser compatibility
8806
9332
  return false;
8807
9333
  }
8808
9334
  }
8809
- let warnedDeprecatedAlias = false;
8810
- function warnDeprecatedOutputAlias(flag) {
8811
- if (warnedDeprecatedAlias) return;
8812
- warnedDeprecatedAlias = true;
8813
- console.error(deprecatedOutputAlias(flag));
8814
- }
8815
- function normalize(value) {
8816
- const v = String(value ?? '').trim().toLowerCase();
8817
- return 'pretty' === v || 'json' === v ? v : void 0;
8818
- }
8819
- function resolveOutputFormat(opts) {
8820
- const direct = normalize(opts.output);
8821
- if (direct) return direct;
8822
- const aliases = [
8823
- [
8824
- '--format',
8825
- opts.format
8826
- ],
8827
- [
8828
- '--wait-format',
8829
- opts.waitFormat
8830
- ]
8831
- ];
8832
- for (const [flag, value] of aliases){
8833
- const resolved = normalize(value);
8834
- if (resolved) {
8835
- warnDeprecatedOutputAlias(flag);
8836
- return resolved;
8837
- }
8838
- }
8839
- return 'pretty';
8840
- }
8841
9335
  const POLL_INTERVAL_MS = 2000;
8842
9336
  const HARD_EXIT_GRACE_MS = 5000;
8843
9337
  function isProcessAlive(pid) {
@@ -8897,9 +9391,17 @@ Cross-browser compatibility
8897
9391
  if (!value) return false;
8898
9392
  return /^https?:\/\//i.test(value);
8899
9393
  }
8900
- function resolveProjectPath(pathOrRemoteUrl) {
8901
- if (!pathOrRemoteUrl) return process.cwd();
8902
- return external_node_path_.isAbsolute(pathOrRemoteUrl) ? pathOrRemoteUrl : external_node_path_.join(process.cwd(), pathOrRemoteUrl);
9394
+ async function resolveProjectPath(pathOrRemoteUrl) {
9395
+ let bridge;
9396
+ try {
9397
+ bridge = await loadExtensionDevelopBridgeModule();
9398
+ } catch {
9399
+ bridge = void 0;
9400
+ }
9401
+ return {
9402
+ projectPath: resolveSessionProjectPath(bridge, pathOrRemoteUrl),
9403
+ bridge
9404
+ };
8903
9405
  }
8904
9406
  function parseWaitTimeoutMs(value) {
8905
9407
  const fallback = 60000;
@@ -8911,7 +9413,7 @@ Cross-browser compatibility
8911
9413
  return fallback;
8912
9414
  }
8913
9415
  function parseWaitFormat(value) {
8914
- return 'json' === value ? 'json' : 'pretty';
9416
+ return normalizeOutputFormat(value) ?? 'pretty';
8915
9417
  }
8916
9418
  class WaitModeError extends Error {
8917
9419
  constructor(message, code){
@@ -8966,7 +9468,7 @@ Cross-browser compatibility
8966
9468
  return false;
8967
9469
  }
8968
9470
  async function waitForReadyContract(options) {
8969
- const readyPath = external_node_path_.join(options.projectPath, 'dist', 'extension-js', options.browser, 'ready.json');
9471
+ const readyPath = sessionReadyPath(options.bridge, options.projectPath, options.browser);
8970
9472
  const start = Date.now();
8971
9473
  while(Date.now() - start < options.timeoutMs){
8972
9474
  if (external_node_fs_.existsSync(readyPath)) try {
@@ -9002,7 +9504,7 @@ Cross-browser compatibility
9002
9504
  }
9003
9505
  async function runWaitMode(options) {
9004
9506
  if (isHttpUrl(options.pathOrRemoteUrl)) throw new WaitModeError('--wait requires a local project path (remote URLs are not supported)', messaging.Lp.E_ARGS);
9005
- const projectPath = resolveProjectPath(options.pathOrRemoteUrl);
9507
+ const { projectPath, bridge } = await resolveProjectPath(options.pathOrRemoteUrl);
9006
9508
  const timeoutMs = parseWaitTimeoutMs(options.waitTimeout);
9007
9509
  const format = parseWaitFormat(options.waitFormat);
9008
9510
  const results = [];
@@ -9010,6 +9512,7 @@ Cross-browser compatibility
9010
9512
  const payload = await waitForReadyContract({
9011
9513
  command: options.command,
9012
9514
  projectPath,
9515
+ bridge,
9013
9516
  browser,
9014
9517
  timeoutMs
9015
9518
  });
@@ -9041,7 +9544,7 @@ Cross-browser compatibility
9041
9544
  return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 8080;
9042
9545
  }
9043
9546
  function registerDevCommand(program) {
9044
- program.command('dev').arguments('[project-path|remote-url]').usage('[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; pair with --output json for machine output\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 <${BROWSER_TARGETS_HELP}>`, '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('--safari-binary <path-to-binary>', 'specify the Safari binary to open after packaging (safari targets only)').option('--app-name <name>', 'override the Safari app name (safari targets only). Defaults to the manifest `name`').option('--bundle-id <reverse.dns>', 'set a user-owned Safari bundle identifier (safari targets only). Defaults to a generated dev.extensionjs.* id').option('--development-team <id>', 'sign the Safari app with an Apple Developer team id (safari targets only). Without it the build is ad-hoc signed, which Safari treats as unsigned: the extension then needs Develop \u25b8 Allow Unsigned Extensions re-ticked on every launch. A signed build is listed and stays enabled across restarts').option('--macos-only [boolean]', 'generate a macOS-only Safari Xcode project (safari targets only). Pass `false` for a universal macOS + iOS project. Defaults to `true`', parseOptionalBoolean).option('--force-regenerate', 'regenerate the Safari Xcode project even when up to date (safari targets only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `true`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--no-open', '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('--public-host <host>', 'connectable host the browser (HMR + reload bridge) dials when it differs from the bind host (e.g. a remote/devcontainer). Defaults to the bind host, or 127.0.0.1 when bound to 0.0.0.0').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--wait [boolean]', 'wait for dist/extension-js/<browser>/ready.json and exit', parseOptionalBoolean).option('--wait-timeout <ms>', 'timeout in milliseconds when using --wait (default: 60000)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--wait-format <pretty|json>').hideHelp()).addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).option('--allow-control', 'enable the agent-bridge control channel for bounded act (storage/reload/open): see `extension reload|storage|open`').option('--allow-eval', 'additionally enable `extension eval` (implies --allow-control, runs arbitrary code in a context, writes a 0600 session token)').option('--parent-pid <pid>', 'exit when the given process dies. For wrappers that spawn `extension dev`, so a leaked dev server can never outlive its owner').action(async (pathOrRemoteUrl, options, command)=>{
9547
+ program.command('dev').arguments('[project-path|remote-url]').usage('[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; pair with --output json for machine output\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 <${BROWSER_TARGETS_HELP}>`, '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').addOption(geckoBinaryOption()).addOption(firefoxBinaryAliasOption()).option('--safari-binary <path-to-binary>', 'specify the Safari binary to open after packaging (safari targets only)').option('--app-name <name>', 'override the Safari app name (safari targets only). Defaults to the manifest `name`').option('--bundle-id <reverse.dns>', 'set a user-owned Safari bundle identifier (safari targets only). Defaults to a generated dev.extensionjs.* id').option('--development-team <id>', 'sign the Safari app with an Apple Developer team id (safari targets only). Without it the build is ad-hoc signed, which Safari treats as unsigned: the extension then needs Develop \u25b8 Allow Unsigned Extensions re-ticked on every launch. A signed build is listed and stays enabled across restarts').option('--macos-only [boolean]', 'generate a macOS-only Safari Xcode project (safari targets only). Pass `false` for a universal macOS + iOS project. Defaults to `true`', parseOptionalBoolean).option('--force-regenerate', 'regenerate the Safari Xcode project even when up to date (safari targets only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `true`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--no-open', '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('--public-host <host>', 'connectable host the browser (HMR + reload bridge) dials when it differs from the bind host (e.g. a remote/devcontainer). Defaults to the bind host, or 127.0.0.1 when bound to 0.0.0.0').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--wait [boolean]', 'wait for dist/extension-js/<browser>/ready.json and exit', parseOptionalBoolean).option('--wait-timeout <ms>', 'timeout in milliseconds when using --wait (default: 60000)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--wait-format <pretty|json>').hideHelp()).addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).option('--allow-control', 'enable the agent-bridge control channel for bounded act (storage/reload/open): see `extension reload|storage|open`').option('--allow-eval', 'additionally enable `extension eval` (implies --allow-control, runs arbitrary code in a context, writes a 0600 session token)').option('--parent-pid <pid>', 'exit when the given process dies. For wrappers that spawn `extension dev`, so a leaked dev server can never outlive its owner').action(async (pathOrRemoteUrl, options, command)=>{
9045
9548
  const { browser: cliBrowser, ...devOptions } = options;
9046
9549
  const browser = cliBrowser ?? await resolveConfigBrowser(pathOrRemoteUrl || process.cwd(), 'dev') ?? 'chromium';
9047
9550
  if (devOptions.debug || devOptions.author || devOptions.authorMode) {
@@ -9175,7 +9678,7 @@ Cross-browser compatibility
9175
9678
  profile: (0, resolve_profile.Md)(devOptions.profile),
9176
9679
  browser: vendor,
9177
9680
  chromiumBinary: devOptions.chromiumBinary,
9178
- geckoBinary: devOptions.geckoBinary,
9681
+ geckoBinary: cliGeckoBinary(devOptions),
9179
9682
  polyfill: explicitCliValue(command, 'polyfill', explicitOptionalBoolean(devOptions.polyfill)),
9180
9683
  noOpen: explicitNoOpen,
9181
9684
  macOsOnly: opts.macosOnly,
@@ -9244,13 +9747,12 @@ Cross-browser compatibility
9244
9747
  return [];
9245
9748
  }
9246
9749
  }
9247
- function resolveDoctorBrowser(projectPathArg, optsBrowser) {
9750
+ function resolveDoctorBrowser(projectPath, optsBrowser) {
9248
9751
  if (optsBrowser) return {
9249
9752
  browser: optsBrowser,
9250
9753
  sessionBrowsers: []
9251
9754
  };
9252
- const projectPath = external_node_path_default().resolve(projectPathArg || process.cwd());
9253
- const sessionBrowsers = listSessionBrowsers(projectPath);
9755
+ const sessionBrowsers = listSessionBrowsers(external_node_path_default().resolve(projectPath || process.cwd()));
9254
9756
  if (1 === sessionBrowsers.length) return {
9255
9757
  browser: sessionBrowsers[0],
9256
9758
  sessionBrowsers
@@ -9269,8 +9771,9 @@ Cross-browser compatibility
9269
9771
  };
9270
9772
  }
9271
9773
  async function runDoctor(projectPathArg, opts) {
9272
- const projectPath = external_node_path_default().resolve(projectPathArg || process.cwd());
9273
- const { browser, sessionBrowsers } = resolveDoctorBrowser(projectPathArg, opts.browser);
9774
+ const bridge = await loadExtensionDevelopBridgeModule();
9775
+ const projectPath = resolveSessionProjectPath(bridge, projectPathArg);
9776
+ const { browser, sessionBrowsers } = resolveDoctorBrowser(projectPath, opts.browser);
9274
9777
  const results = [];
9275
9778
  if (sessionBrowsers.length > 1) results.push({
9276
9779
  check: 'session-resolution',
@@ -9285,13 +9788,13 @@ Cross-browser compatibility
9285
9788
  detail: `skipped: blocked by ${blockedBy}`
9286
9789
  });
9287
9790
  };
9288
- const { BridgeController, readReadyContract, readControlToken, readPersistedControlPort, controlPortFilePath } = await loadExtensionDevelopBridgeModule();
9791
+ const { BridgeController, readReadyContract, readControlToken, readPersistedControlPort, controlPortFilePath } = bridge;
9289
9792
  const ready = readReadyContract(projectPath, browser);
9290
9793
  if (!ready) {
9291
9794
  results.push({
9292
9795
  check: 'ready-contract',
9293
9796
  status: 'fail',
9294
- detail: `no ready contract at dist/extension-js/${browser}/ready.json`,
9797
+ detail: `no ready contract at ${sessionReadyPath(bridge, projectPath, browser)}`,
9295
9798
  remediation: `Start a dev session first: extension dev --browser=${browser} --allow-control (add --allow-eval for the eval verb)`
9296
9799
  });
9297
9800
  for (const check of [
@@ -9392,7 +9895,7 @@ Cross-browser compatibility
9392
9895
  detail = 'refused: ready.json instanceId no longer matches the live server';
9393
9896
  remediation = "A newer session overwrote the contract, or this dist belongs to another session, re-read ready.json or restart the dev session";
9394
9897
  } else if ('4003' === code) {
9395
- detail = 'refused: session was not started with --allow-control';
9898
+ detail = 'refused: control is off in the session that answered';
9396
9899
  remediation = `Restart with control enabled: extension dev --browser=${browser} --allow-control`;
9397
9900
  }
9398
9901
  results.push({
@@ -9513,8 +10016,8 @@ Cross-browser compatibility
9513
10016
  browser: messaging.Lp.E_BROWSER_LAUNCH
9514
10017
  };
9515
10018
  function registerDoctorCommand(program) {
9516
- program.command('doctor').argument('[project-path]', 'path to the extension project root').option('--browser <chrome | chromium | edge | firefox>', 'which session to diagnose (defaults to the single live session, else chromium)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').description(commandDescriptions.doctor).action(async (projectPathArg, opts)=>{
9517
- const asJson = 'json' === opts.output;
10019
+ program.command('doctor').argument('[project-path]', 'path to the extension project root or the folder holding its manifest').option('--browser <chrome | chromium | edge | firefox>', 'which session to diagnose (defaults to the single live session, else chromium)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').description(commandDescriptions.doctor).action(async (projectPathArg, opts)=>{
10020
+ const asJson = isJsonOutput(opts);
9518
10021
  let results;
9519
10022
  try {
9520
10023
  results = await runDoctor(projectPathArg, opts);
@@ -9596,7 +10099,7 @@ Cross-browser compatibility
9596
10099
  }
9597
10100
  function registerInstallCommand(program) {
9598
10101
  program.command('install').arguments('[browser-name]').usage('[browser-name] [options]').description(commandDescriptions.install).option(`--browser <${MANAGED_INSTALL_TARGETS_HELP}>`, 'override the positional browser name. Supports comma-separated values and `all`.').option('--where', 'print the resolved managed browser cache root').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').action(async (browserArg, options)=>{
9599
- const asJson = 'json' === options.output;
10102
+ const asJson = isJsonOutput(options);
9600
10103
  const named = Boolean(options.browser || browserArg);
9601
10104
  const selectedBrowser = options.browser || browserArg || 'chromium';
9602
10105
  const browserList = installTargets(selectedBrowser);
@@ -9636,7 +10139,9 @@ Cross-browser compatibility
9636
10139
  }));
9637
10140
  });
9638
10141
  program.command('uninstall').usage('<browser-name> | --all | --where').description(commandDescriptions.uninstall).option(`--browser <${MANAGED_INSTALL_TARGETS_HELP}>`, 'browser(s) to uninstall. Supports comma-separated values and `all`.').option('--all', 'remove all managed browser binaries').option('--where', 'print the resolved managed browser cache root').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').argument('[browser-name]').action(async (browserArg, { browser, all, where, output })=>{
9639
- const asJson = 'json' === output;
10142
+ const asJson = isJsonOutput({
10143
+ output
10144
+ });
9640
10145
  const named = Boolean(browserArg || browser);
9641
10146
  const selected = all || 'all' === browserArg || 'all' === browser ? 'all' : browser || browserArg;
9642
10147
  const { extensionUninstall, getManagedBrowsersCacheRoot, getManagedBrowserInstallDir } = await import("extension-install");
@@ -9704,14 +10209,15 @@ Cross-browser compatibility
9704
10209
  'Manifest file not found'
9705
10210
  ];
9706
10211
  function registerPreviewCommand(program) {
9707
- program.command('preview').arguments('[project-name]').usage('[path-to-remote-extension] [options]').description(commandDescriptions.preview).addHelpText('after', '\nAdditional option:\n --no-browser do not launch the browser\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(`--browser <${NO_SAFARI_BROWSER_TARGETS_HELP}>`, '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('--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('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--output-path <dir>', 'path to an existing unpacked extension directory. Defaults to dist/<browser> when available').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).action(async (pathOrRemoteUrl, options, command)=>{
9708
- const { browser = 'chromium', ...previewOptions } = options;
10212
+ program.command('preview').arguments('[project-name]').usage('[path-to-remote-extension] [options]').description(commandDescriptions.preview).addHelpText('after', '\nAdditional option:\n --no-browser do not launch the browser\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(`--browser <${NO_SAFARI_BROWSER_TARGETS_HELP}>`, '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').addOption(geckoBinaryOption()).addOption(firefoxBinaryAliasOption()).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('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--output-path <dir>', 'path to an existing unpacked extension directory. Defaults to dist/<browser> when available').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).action(async (pathOrRemoteUrl, options, command)=>{
10213
+ const { browser: cliBrowser, ...previewOptions } = options;
10214
+ const browser = cliBrowser ?? await resolveConfigBrowser(pathOrRemoteUrl || process.cwd(), 'preview') ?? 'chromium';
9709
10215
  if (previewOptions.debug || previewOptions.author || previewOptions.authorMode) {
9710
10216
  process.env.EXTENSION_DEBUG = '1';
9711
10217
  process.env.EXTENSION_AUTHOR_MODE = 'true';
9712
10218
  if (!process.env.EXTENSION_VERBOSE) process.env.EXTENSION_VERBOSE = '1';
9713
10219
  }
9714
- const asJson = 'json' === previewOptions.output;
10220
+ const asJson = isJsonOutput(previewOptions);
9715
10221
  if (asJson) process.env.EXTENSION_OUTPUT = 'json';
9716
10222
  const emit = (frame)=>{
9717
10223
  console.log(JSON.stringify(frame));
@@ -9756,7 +10262,7 @@ Cross-browser compatibility
9756
10262
  profile: (0, resolve_profile.Md)(previewOptions.profile),
9757
10263
  browser: vendor,
9758
10264
  chromiumBinary: previewOptions.chromiumBinary,
9759
- geckoBinary: previewOptions.geckoBinary,
10265
+ geckoBinary: cliGeckoBinary(previewOptions),
9760
10266
  startingUrl: previewOptions.startingUrl,
9761
10267
  port: previewOptions.port,
9762
10268
  noBrowser: await resolveNoBrowser(pathOrRemoteUrl || process.cwd(), 'preview'),
@@ -9882,7 +10388,7 @@ Cross-browser compatibility
9882
10388
  }
9883
10389
  function registerPublishCommand(program) {
9884
10390
  program.command('publish').arguments('[project-path]').usage('[project-path] [options]').description(commandDescriptions.publish).option('--token <token>', 'platform access token (or EXTENSION_DEV_TOKEN, or the stored login)').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('--project <slug>', 'name the project this publish is for, when it is not the directory you are in').option('--output <pretty|json>', 'output format (default pretty)').action(async (projectPathArg, opts)=>{
9885
- const asJson = 'json' === opts.output;
10391
+ const asJson = isJsonOutput(opts);
9886
10392
  const failWith = async (status, error, prose, hint)=>{
9887
10393
  if (asJson) console.log(JSON.stringify(messaging.Pr.fail('publish', status, error, hint ? {
9888
10394
  hint
@@ -9968,8 +10474,9 @@ Cross-browser compatibility
9968
10474
  return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 8080;
9969
10475
  }
9970
10476
  function registerStartCommand(program) {
9971
- program.command('start').arguments('[project-path|remote-url]').usage('[project-path|remote-url] [options]').description(commandDescriptions.start).addHelpText('after', '\nAdditional options:\n --no-browser do not launch the browser (build still runs)\n --wait wait for ready contract and exit; pair with --output json for machine output\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(`--browser <${NO_SAFARI_BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `true`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--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('--starting-url <url>', 'specify the starting URL for the browser. Defaults to `undefined`').option('--port <port>', 'specify the port to use for the development server. Defaults to `8080`').option('--host <host>', 'specify the host to bind the dev server to. Use 0.0.0.0 for Docker/devcontainers. Defaults to `127.0.0.1`').option('--public-host <host>', 'connectable host the browser (HMR + reload bridge) dials when it differs from the bind host (e.g. a remote/devcontainer). Defaults to the bind host, or 127.0.0.1 when bound to 0.0.0.0').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--wait [boolean]', 'wait for dist/extension-js/<browser>/ready.json and exit', parseOptionalBoolean).option('--wait-timeout <ms>', 'timeout in milliseconds when using --wait (default: 60000)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--wait-format <pretty|json>').hideHelp()).addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).action(async (pathOrRemoteUrl, options, command)=>{
9972
- const { browser = 'chromium', ...startOptions } = options;
10477
+ program.command('start').arguments('[project-path|remote-url]').usage('[project-path|remote-url] [options]').description(commandDescriptions.start).addHelpText('after', '\nAdditional options:\n --no-browser do not launch the browser (build still runs)\n --wait wait for ready contract and exit; pair with --output json for machine output\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(`--browser <${NO_SAFARI_BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `true`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').addOption(geckoBinaryOption()).addOption(firefoxBinaryAliasOption()).option('--starting-url <url>', 'specify the starting URL for the browser. Defaults to `undefined`').option('--port <port>', 'specify the port to use for the development server. Defaults to `8080`').option('--host <host>', 'specify the host to bind the dev server to. Use 0.0.0.0 for Docker/devcontainers. Defaults to `127.0.0.1`').option('--public-host <host>', 'connectable host the browser (HMR + reload bridge) dials when it differs from the bind host (e.g. a remote/devcontainer). Defaults to the bind host, or 127.0.0.1 when bound to 0.0.0.0').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--wait [boolean]', 'wait for dist/extension-js/<browser>/ready.json and exit', parseOptionalBoolean).option('--wait-timeout <ms>', 'timeout in milliseconds when using --wait (default: 60000)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--wait-format <pretty|json>').hideHelp()).addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).action(async (pathOrRemoteUrl, options, command)=>{
10478
+ const { browser: cliBrowser, ...startOptions } = options;
10479
+ const browser = cliBrowser ?? await resolveConfigBrowser(pathOrRemoteUrl || process.cwd(), 'start') ?? 'chromium';
9973
10480
  if (startOptions.debug || startOptions.author || startOptions.authorMode) {
9974
10481
  process.env.EXTENSION_DEBUG = '1';
9975
10482
  process.env.EXTENSION_AUTHOR_MODE = 'true';
@@ -10069,7 +10576,7 @@ Cross-browser compatibility
10069
10576
  profile: (0, resolve_profile.Md)(startOptions.profile),
10070
10577
  browser: vendor,
10071
10578
  chromiumBinary: startOptions.chromiumBinary,
10072
- geckoBinary: startOptions.geckoBinary,
10579
+ geckoBinary: cliGeckoBinary(startOptions),
10073
10580
  startingUrl: startOptions.startingUrl,
10074
10581
  port: startOptions.port,
10075
10582
  host: startOptions.host,
@@ -10090,7 +10597,7 @@ Cross-browser compatibility
10090
10597
  function registerTelemetryCommand(program) {
10091
10598
  program.command('telemetry').argument('[action]', 'enable | disable | status (default: status)').description(commandDescriptions.telemetry).option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').action(async (action, opts = {})=>{
10092
10599
  const normalized = String(action || 'status').trim().toLowerCase();
10093
- const asJson = 'json' === opts.output;
10600
+ const asJson = isJsonOutput(opts);
10094
10601
  const emit = (frame)=>{
10095
10602
  console.log(JSON.stringify(frame));
10096
10603
  };
@@ -10165,12 +10672,6 @@ Cross-browser compatibility
10165
10672
  return resolveExtensionDevelopVersion(__dirname, index_cliPackageJson.version);
10166
10673
  }
10167
10674
  process.env.EXTENSION_DEVELOP_VERSION = developVersion();
10168
- function scanArgvValue(argv, flag) {
10169
- const equalArg = argv.find((arg)=>arg.startsWith(`${flag}=`));
10170
- if (equalArg) return equalArg.slice(flag.length + 1);
10171
- const flagIndex = argv.indexOf(flag);
10172
- if (flagIndex >= 0) return argv[flagIndex + 1] || '';
10173
- }
10174
10675
  function resolveAIHelpFormatFromArgv(argv) {
10175
10676
  const direct = scanArgvValue(argv, '--output');
10176
10677
  if (void 0 !== direct) return direct;
@@ -10181,29 +10682,21 @@ Cross-browser compatibility
10181
10682
  }
10182
10683
  return 'pretty';
10183
10684
  }
10184
- function resolveCommandFromArgv(argv) {
10185
- for(let i = 2; i < argv.length; i += 1){
10186
- const arg = argv[i];
10187
- if (!arg.startsWith('-')) return arg;
10188
- }
10685
+ let valuedLongFlags = new Set();
10686
+ function index_resolveCommandFromArgv(argv) {
10687
+ return resolveCommandFromArgv(argv, valuedLongFlags);
10189
10688
  }
10190
10689
  function failBeforeParse(argv, humanMessage, code, plainMessage, flag) {
10191
10690
  console.error(humanMessage);
10192
- if (wantsJsonOutput(argv)) writeStdoutFrame(earlyExitEnvelope(resolveCommandFromArgv(argv) || 'extension', code, plainMessage, {
10691
+ if (wantsJsonOutput(argv)) writeStdoutFrame(earlyExitEnvelope(index_resolveCommandFromArgv(argv) || 'extension', code, plainMessage, {
10193
10692
  flag
10194
10693
  }));
10195
10694
  process.exit(1);
10196
10695
  }
10197
10696
  function applyOutputAliasArgvShim(argv) {
10198
- const index = argv.findIndex((arg)=>'--format' === arg || arg.startsWith('--format='));
10199
- if (index < 0) return argv;
10200
10697
  if (argv.includes('--ai-help')) return argv;
10201
- const next = [
10202
- ...argv
10203
- ];
10204
- if (next[index].startsWith('--format=')) next[index] = `--output=${next[index].slice(9)}`;
10205
- else next[index] = '--output';
10206
- warnDeprecatedOutputAlias('--format');
10698
+ const { argv: next, rewritten } = rewriteOutputAliasArgv(argv, valuedLongFlags);
10699
+ if (rewritten) warnDeprecatedOutputAlias('--format');
10207
10700
  return next;
10208
10701
  }
10209
10702
  function applyNoBrowserArgvShim(argv) {
@@ -10212,7 +10705,7 @@ Cross-browser compatibility
10212
10705
  let nextArgv = argv;
10213
10706
  const hasNoReload = nextArgv.includes('--no-reload');
10214
10707
  if (hasNoReload) {
10215
- const command = resolveCommandFromArgv(nextArgv);
10708
+ const command = index_resolveCommandFromArgv(nextArgv);
10216
10709
  if ('dev' !== command) {
10217
10710
  const message = `--no-reload is only supported on \`extension dev\` (got: ${command || 'no command'}).`;
10218
10711
  failBeforeParse(nextArgv, message, messaging.Lp.E_FLAG_NOT_SUPPORTED_HERE, message, '--no-reload');
@@ -10222,7 +10715,7 @@ Cross-browser compatibility
10222
10715
  }
10223
10716
  const hasNoBrowser = nextArgv.includes('--no-browser');
10224
10717
  if (!hasNoBrowser) return nextArgv;
10225
- const command = resolveCommandFromArgv(nextArgv);
10718
+ const command = index_resolveCommandFromArgv(nextArgv);
10226
10719
  const supportsNoBrowser = 'dev' === command || 'start' === command || 'preview' === command;
10227
10720
  if (!supportsNoBrowser) failBeforeParse(nextArgv, noBrowserNotSupportedForCommand(command), messaging.Lp.E_FLAG_NOT_SUPPORTED_HERE, command ? `--no-browser is not supported on \`extension ${command}\`.` : '--no-browser needs a command that launches a browser.', '--no-browser');
10228
10721
  process.env.EXTENSION_CLI_NO_BROWSER = '1';
@@ -10249,6 +10742,7 @@ Cross-browser compatibility
10249
10742
  registerTelemetryCommand(extensionJs);
10250
10743
  registerDoctorCommand(extensionJs);
10251
10744
  registerCapabilitiesCommand(extensionJs);
10745
+ valuedLongFlags = collectValuedLongFlags(extensionJs);
10252
10746
  function runAIHelp() {
10253
10747
  const format = resolveAIHelpFormatFromArgv(process.argv).trim().toLowerCase();
10254
10748
  if ('json' === format) {
@@ -10272,7 +10766,7 @@ Cross-browser compatibility
10272
10766
  else {
10273
10767
  const argv = applyNoBrowserArgvShim(applyOutputAliasArgvShim(process.argv));
10274
10768
  const asJson = wantsJsonOutput(argv);
10275
- const commandName = resolveCommandFromArgv(argv) || 'extension';
10769
+ const commandName = index_resolveCommandFromArgv(argv) || 'extension';
10276
10770
  (async ()=>{
10277
10771
  try {
10278
10772
  await extensionJs.parseAsync(argv);