extension 4.0.27 → 4.0.29-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/browsers.cjs CHANGED
@@ -2423,7 +2423,6 @@ var __webpack_modules__ = {
2423
2423
  '--disable-breakpad',
2424
2424
  '--disable-component-update',
2425
2425
  '--disable-domain-reliability',
2426
- '--disable-sync',
2427
2426
  '--no-pings',
2428
2427
  '--enable-features=SidePanelUpdates',
2429
2428
  '--disable-features=DisableLoadExtensionCommandLineSwitch',
@@ -3890,7 +3889,7 @@ var __webpack_modules__ = {
3890
3889
  }
3891
3890
  return false;
3892
3891
  }
3893
- async function printRunningInDevelopmentSummary(candidateAddonPaths, browser, extensionId, browserVersionLine) {
3892
+ async function printRunningInDevelopmentSummary(candidateAddonPaths, browser, extensionId, browserVersionLine, launchIdentity) {
3894
3893
  try {
3895
3894
  let chosenPath = null;
3896
3895
  for (const p of candidateAddonPaths){
@@ -3928,7 +3927,10 @@ var __webpack_modules__ = {
3928
3927
  name: manifest.name,
3929
3928
  version: manifest.version
3930
3929
  }),
3931
- browserVersionLine
3930
+ browserVersionLine,
3931
+ profilePath: launchIdentity?.profilePath,
3932
+ binaryPath: launchIdentity?.binaryPath,
3933
+ binaryProvenance: launchIdentity?.binaryProvenance
3932
3934
  });
3933
3935
  return printed;
3934
3936
  } catch {
@@ -4587,7 +4589,11 @@ var __webpack_modules__ = {
4587
4589
  }
4588
4590
  } catch {}
4589
4591
  this.lastInstalledAddonPath = primaryAddonPath;
4590
- const bannerPrinted = await printRunningInDevelopmentSummary(candidateAddonPaths, 'firefox', this.derivedExtensionId, this.options.browserVersionLine);
4592
+ const bannerPrinted = await printRunningInDevelopmentSummary(candidateAddonPaths, 'firefox', this.derivedExtensionId, this.options.browserVersionLine, {
4593
+ profilePath: this.options.launchProfilePath,
4594
+ binaryPath: this.options.launchBinaryPath,
4595
+ binaryProvenance: this.options.launchBinaryProvenance
4596
+ });
4591
4597
  if (!bannerPrinted) throw new Error(messages.WV(this.options.browser, 'Failed to print runningInDevelopment banner; add-on may not be installed.'));
4592
4598
  }
4593
4599
  async enableUnifiedLogging(opts) {
@@ -4665,7 +4671,10 @@ var __webpack_modules__ = {
4665
4671
  instanceId: plugin.instanceId,
4666
4672
  port: debugPort,
4667
4673
  resolvedRdpPort: normalizedDebugPort,
4668
- browserVersionLine: plugin.browserVersionLine
4674
+ browserVersionLine: plugin.browserVersionLine,
4675
+ launchProfilePath: plugin.launchProfilePath,
4676
+ launchBinaryPath: plugin.launchBinaryPath,
4677
+ launchBinaryProvenance: plugin.launchBinaryProvenance
4669
4678
  });
4670
4679
  this.debugPort = normalizedDebugPort;
4671
4680
  }
@@ -4928,6 +4937,12 @@ var __webpack_modules__ = {
4928
4937
  }
4929
4938
  const binaryPath = browserBinaryLocation;
4930
4939
  const wslFallbackBinary = (0, wsl_support.EV)() && !engineBased ? resolveWslFallback() : null;
4940
+ this.host.launchBinaryPath = binaryPath;
4941
+ this.host.launchBinaryProvenance = (0, shared_utils.RU)({
4942
+ binaryPath,
4943
+ managedCacheRoot: String((0, output_binaries_resolver.LB)(compilation) || ''),
4944
+ pinnedByFlag: Boolean(this.host.geckoBinary)
4945
+ });
4931
4946
  if (!isFlatpak) try {
4932
4947
  this.host.browserVersionLine = (0, external_firefox_location2_.getFirefoxVersion)(binaryPath, {
4933
4948
  allowExec: true
@@ -4961,6 +4976,7 @@ var __webpack_modules__ = {
4961
4976
  if (profileMatch) {
4962
4977
  const profilePath = profileMatch[1];
4963
4978
  const { binary, args } = FirefoxBinaryDetector.generateFirefoxArgs(binaryPath, profilePath, debugPort, firefoxArgs, isFirefoxHeadlessRequested());
4979
+ this.host.launchProfilePath = profilePath;
4964
4980
  this.child = await this.spawnFirefoxChild(binary, args, wslFallbackBinary);
4965
4981
  (0, ready_stamp.M)(this.extensionOutputPath, {
4966
4982
  profilePath,
@@ -5325,7 +5341,9 @@ var __webpack_modules__ = {
5325
5341
  var external_node_child_process_ = __webpack_require__("node:child_process");
5326
5342
  var external_node_fs_ = __webpack_require__("node:fs");
5327
5343
  var messaging = __webpack_require__("./helpers/messaging.ts");
5344
+ var banner = __webpack_require__("./browsers/browsers-lib/banner.ts");
5328
5345
  var messages = __webpack_require__("./browsers/browsers-lib/messages.ts");
5346
+ var ready_message = __webpack_require__("./browsers/browsers-lib/ready-message.ts");
5329
5347
  function logSafariDryRun(converterCmd, xcodebuildCmd) {
5330
5348
  if ((0, messaging._o)()) (0, messaging._w)(messages.KP());
5331
5349
  (0, messaging._w)(messages.yc());
@@ -5491,6 +5509,20 @@ var __webpack_modules__ = {
5491
5509
  severity: 'ok'
5492
5510
  };
5493
5511
  }
5512
+ async function announceSafariDevSession(host, config, appPath) {
5513
+ try {
5514
+ await (0, banner.ai)({
5515
+ browser: host.browser,
5516
+ outPath: config.extensionDir,
5517
+ getInfo: async ()=>({
5518
+ extensionId: `${config.bundleIdentifier}.Extension`,
5519
+ name: config.appName
5520
+ }),
5521
+ binaryPath: appPath
5522
+ });
5523
+ (0, messaging._w)((0, ready_message.G)('development', String(host.browser)));
5524
+ } catch {}
5525
+ }
5494
5526
  async function runSafariPipeline(compilation, host, logger, mode) {
5495
5527
  const config = (0, safari_config.F7)(compilation, host);
5496
5528
  const converterArgs = (0, safari_config.w7)(config);
@@ -5555,6 +5587,7 @@ var __webpack_modules__ = {
5555
5587
  logger.info?.(messages.Rl(appPath));
5556
5588
  if (!config.open) {
5557
5589
  logger.info?.(messages.aO(appPath, config.appName));
5590
+ if (host.announceDevReady) await announceSafariDevSession(host, config, appPath);
5558
5591
  return describePackage(config);
5559
5592
  }
5560
5593
  const target = external_node_fs_.existsSync(appPath) ? appPath : (0, safari_config.Gi)(config);
@@ -5569,6 +5602,7 @@ var __webpack_modules__ = {
5569
5602
  logger.info?.(messages.fO(config.appName));
5570
5603
  if (await confirmRegisteredWithSafari(config.bundleIdentifier)) logger.info?.(messages.fd(config.appName));
5571
5604
  else logger.info?.(messages.qY(config.appName));
5605
+ if (host.announceDevReady) await announceSafariDevSession(host, config, appPath);
5572
5606
  return describePackage(config);
5573
5607
  }
5574
5608
  async function packageSafariExtension(host, outputPath, logger, mode = 'full') {
package/dist/cli.cjs CHANGED
@@ -2414,7 +2414,6 @@ var __webpack_modules__ = {
2414
2414
  '--disable-breakpad',
2415
2415
  '--disable-component-update',
2416
2416
  '--disable-domain-reliability',
2417
- '--disable-sync',
2418
2417
  '--no-pings',
2419
2418
  '--enable-features=SidePanelUpdates',
2420
2419
  '--disable-features=DisableLoadExtensionCommandLineSwitch',
@@ -3881,7 +3880,7 @@ var __webpack_modules__ = {
3881
3880
  }
3882
3881
  return false;
3883
3882
  }
3884
- async function printRunningInDevelopmentSummary(candidateAddonPaths, browser, extensionId, browserVersionLine) {
3883
+ async function printRunningInDevelopmentSummary(candidateAddonPaths, browser, extensionId, browserVersionLine, launchIdentity) {
3885
3884
  try {
3886
3885
  let chosenPath = null;
3887
3886
  for (const p of candidateAddonPaths){
@@ -3919,7 +3918,10 @@ var __webpack_modules__ = {
3919
3918
  name: manifest.name,
3920
3919
  version: manifest.version
3921
3920
  }),
3922
- browserVersionLine
3921
+ browserVersionLine,
3922
+ profilePath: launchIdentity?.profilePath,
3923
+ binaryPath: launchIdentity?.binaryPath,
3924
+ binaryProvenance: launchIdentity?.binaryProvenance
3923
3925
  });
3924
3926
  return printed;
3925
3927
  } catch {
@@ -4578,7 +4580,11 @@ var __webpack_modules__ = {
4578
4580
  }
4579
4581
  } catch {}
4580
4582
  this.lastInstalledAddonPath = primaryAddonPath;
4581
- const bannerPrinted = await printRunningInDevelopmentSummary(candidateAddonPaths, 'firefox', this.derivedExtensionId, this.options.browserVersionLine);
4583
+ const bannerPrinted = await printRunningInDevelopmentSummary(candidateAddonPaths, 'firefox', this.derivedExtensionId, this.options.browserVersionLine, {
4584
+ profilePath: this.options.launchProfilePath,
4585
+ binaryPath: this.options.launchBinaryPath,
4586
+ binaryProvenance: this.options.launchBinaryProvenance
4587
+ });
4582
4588
  if (!bannerPrinted) throw new Error(messages.WV(this.options.browser, 'Failed to print runningInDevelopment banner; add-on may not be installed.'));
4583
4589
  }
4584
4590
  async enableUnifiedLogging(opts) {
@@ -4656,7 +4662,10 @@ var __webpack_modules__ = {
4656
4662
  instanceId: plugin.instanceId,
4657
4663
  port: debugPort,
4658
4664
  resolvedRdpPort: normalizedDebugPort,
4659
- browserVersionLine: plugin.browserVersionLine
4665
+ browserVersionLine: plugin.browserVersionLine,
4666
+ launchProfilePath: plugin.launchProfilePath,
4667
+ launchBinaryPath: plugin.launchBinaryPath,
4668
+ launchBinaryProvenance: plugin.launchBinaryProvenance
4660
4669
  });
4661
4670
  this.debugPort = normalizedDebugPort;
4662
4671
  }
@@ -4919,6 +4928,12 @@ var __webpack_modules__ = {
4919
4928
  }
4920
4929
  const binaryPath = browserBinaryLocation;
4921
4930
  const wslFallbackBinary = (0, wsl_support.EV)() && !engineBased ? resolveWslFallback() : null;
4931
+ this.host.launchBinaryPath = binaryPath;
4932
+ this.host.launchBinaryProvenance = (0, shared_utils.RU)({
4933
+ binaryPath,
4934
+ managedCacheRoot: String((0, output_binaries_resolver.LB)(compilation) || ''),
4935
+ pinnedByFlag: Boolean(this.host.geckoBinary)
4936
+ });
4922
4937
  if (!isFlatpak) try {
4923
4938
  this.host.browserVersionLine = (0, external_firefox_location2_.getFirefoxVersion)(binaryPath, {
4924
4939
  allowExec: true
@@ -4952,6 +4967,7 @@ var __webpack_modules__ = {
4952
4967
  if (profileMatch) {
4953
4968
  const profilePath = profileMatch[1];
4954
4969
  const { binary, args } = FirefoxBinaryDetector.generateFirefoxArgs(binaryPath, profilePath, debugPort, firefoxArgs, isFirefoxHeadlessRequested());
4970
+ this.host.launchProfilePath = profilePath;
4955
4971
  this.child = await this.spawnFirefoxChild(binary, args, wslFallbackBinary);
4956
4972
  (0, ready_stamp.M)(this.extensionOutputPath, {
4957
4973
  profilePath,
@@ -5316,7 +5332,9 @@ var __webpack_modules__ = {
5316
5332
  var external_node_child_process_ = __webpack_require__("node:child_process");
5317
5333
  var external_node_fs_ = __webpack_require__("node:fs");
5318
5334
  var messaging = __webpack_require__("./helpers/messaging.ts");
5335
+ var banner = __webpack_require__("./browsers/browsers-lib/banner.ts");
5319
5336
  var messages = __webpack_require__("./browsers/browsers-lib/messages.ts");
5337
+ var ready_message = __webpack_require__("./browsers/browsers-lib/ready-message.ts");
5320
5338
  function logSafariDryRun(converterCmd, xcodebuildCmd) {
5321
5339
  if ((0, messaging._o)()) (0, messaging._w)(messages.KP());
5322
5340
  (0, messaging._w)(messages.yc());
@@ -5482,6 +5500,20 @@ var __webpack_modules__ = {
5482
5500
  severity: 'ok'
5483
5501
  };
5484
5502
  }
5503
+ async function announceSafariDevSession(host, config, appPath) {
5504
+ try {
5505
+ await (0, banner.ai)({
5506
+ browser: host.browser,
5507
+ outPath: config.extensionDir,
5508
+ getInfo: async ()=>({
5509
+ extensionId: `${config.bundleIdentifier}.Extension`,
5510
+ name: config.appName
5511
+ }),
5512
+ binaryPath: appPath
5513
+ });
5514
+ (0, messaging._w)((0, ready_message.G)('development', String(host.browser)));
5515
+ } catch {}
5516
+ }
5485
5517
  async function runSafariPipeline(compilation, host, logger, mode) {
5486
5518
  const config = (0, safari_config.F7)(compilation, host);
5487
5519
  const converterArgs = (0, safari_config.w7)(config);
@@ -5546,6 +5578,7 @@ var __webpack_modules__ = {
5546
5578
  logger.info?.(messages.Rl(appPath));
5547
5579
  if (!config.open) {
5548
5580
  logger.info?.(messages.aO(appPath, config.appName));
5581
+ if (host.announceDevReady) await announceSafariDevSession(host, config, appPath);
5549
5582
  return describePackage(config);
5550
5583
  }
5551
5584
  const target = external_node_fs_.existsSync(appPath) ? appPath : (0, safari_config.Gi)(config);
@@ -5560,6 +5593,7 @@ var __webpack_modules__ = {
5560
5593
  logger.info?.(messages.fO(config.appName));
5561
5594
  if (await confirmRegisteredWithSafari(config.bundleIdentifier)) logger.info?.(messages.fd(config.appName));
5562
5595
  else logger.info?.(messages.qY(config.appName));
5596
+ if (host.announceDevReady) await announceSafariDevSession(host, config, appPath);
5563
5597
  return describePackage(config);
5564
5598
  }
5565
5599
  async function packageSafariExtension(host, outputPath, logger, mode = 'full') {
@@ -7248,12 +7282,6 @@ Public & special folders (output behavior)
7248
7282
  - ${external_pintor_default().underline(messages_code('pages/'))} files emit as ${messages_code('pages/<name>.html')}. Relative assets referenced inside page HTML are emitted under ${messages_code('assets/')} preserving relative structure, public-root URLs are preserved.
7249
7283
  - ${external_pintor_default().underline(messages_code("scripts/"))} files emit as ${messages_code("scripts/<name>.js")} with extracted CSS when applicable.
7250
7284
 
7251
- Shadow DOM for content scripts
7252
- - Add ${messages_code('use shadow-dom')} directive to content scripts for style isolation
7253
- - Automatically creates ${messages_code('#extension-root')} element with shadow DOM
7254
- - All CSS imports are automatically injected into shadow DOM
7255
- - Prevents style conflicts with host page
7256
-
7257
7285
  Environment variables
7258
7286
  - Use ${messages_code(messages_arg('EXTENSION_PUBLIC_*'))} prefix for variables accessible in extension code
7259
7287
  - Supported in both ${messages_code('process.env')} and ${messages_code('import.meta.env')}
@@ -7410,9 +7438,11 @@ Cross-browser compatibility
7410
7438
  ...group.templates
7411
7439
  ]
7412
7440
  })),
7413
- aliases: TEMPLATE_ALIASES.map((alias)=>({
7414
- ...alias
7415
- })),
7441
+ ...TEMPLATE_ALIASES.length > 0 && {
7442
+ aliases: TEMPLATE_ALIASES.map((alias)=>({
7443
+ ...alias
7444
+ }))
7445
+ },
7416
7446
  notes: [
7417
7447
  `extension create <name> with no --template scaffolds ${DEFAULT_TEMPLATE}`,
7418
7448
  `${DEFAULT_TEMPLATE} is bundled with the CLI and scaffolds offline. Every other name downloads the examples archive at create time`,
@@ -7454,7 +7484,8 @@ Cross-browser compatibility
7454
7484
  statuses: [
7455
7485
  'starting',
7456
7486
  'ready',
7457
- 'error'
7487
+ 'error',
7488
+ 'stopped'
7458
7489
  ],
7459
7490
  readyFields: [
7460
7491
  'status',
@@ -7478,7 +7509,7 @@ Cross-browser compatibility
7478
7509
  ],
7479
7510
  notes: [
7480
7511
  'ready.json is written atomically by the build (dev/start) on each compile',
7481
- 'events.ndjson is an append-only build timeline with durationMs and errorCount per entry',
7512
+ 'events.ndjson is reset at every run start and appends entries with runId, durationMs and errorCount for that run only, join on runId across runs',
7482
7513
  '--wait requires a local project path (remote URLs are not supported)',
7483
7514
  'consumers should verify pid liveness and recency before trusting a contract'
7484
7515
  ]
@@ -7526,6 +7557,146 @@ Cross-browser compatibility
7526
7557
  function noBrowserNotSupportedForCommand(command) {
7527
7558
  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)')}`;
7528
7559
  }
7560
+ const LEVEL_ORDER = [
7561
+ 'error',
7562
+ 'warn',
7563
+ 'info',
7564
+ 'debug',
7565
+ 'trace'
7566
+ ];
7567
+ function levelRank(level) {
7568
+ const l = 'log' === level ? 'info' : level;
7569
+ const i = LEVEL_ORDER.indexOf(l);
7570
+ return -1 === i ? LEVEL_ORDER.length : i;
7571
+ }
7572
+ function makeUrlMatcher(pattern) {
7573
+ const hasGlob = pattern.includes('*');
7574
+ let re = null;
7575
+ if (hasGlob) {
7576
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
7577
+ re = new RegExp(escaped);
7578
+ }
7579
+ return (event)=>{
7580
+ const candidates = [
7581
+ event.url,
7582
+ event.hostname
7583
+ ].filter((v)=>'string' == typeof v);
7584
+ if (0 === candidates.length) return false;
7585
+ return candidates.some((c)=>re ? re.test(c) : c.includes(pattern));
7586
+ };
7587
+ }
7588
+ function makeFilter(opts) {
7589
+ const minLevel = String(opts.level || 'all').toLowerCase();
7590
+ const contexts = opts.context && 'all' !== opts.context.toLowerCase() ? new Set(opts.context.split(',').map((c)=>c.trim())) : null;
7591
+ const sinceSeq = null != opts.since ? Number(opts.since) : null;
7592
+ const urlMatches = opts.url ? makeUrlMatcher(opts.url) : null;
7593
+ const tabId = null != opts.tab && '' !== opts.tab ? Number(opts.tab) : null;
7594
+ return (event)=>{
7595
+ if (!event || 'object' != typeof event) return false;
7596
+ if ('header' === event.type) return false;
7597
+ if (opts.signalsOnly && 'dx.signal' !== event.eventType) return false;
7598
+ if (contexts && !contexts.has(String(event.context))) return false;
7599
+ if ('all' !== minLevel && 'off' !== minLevel) {
7600
+ if (levelRank(String(event.level || '')) > levelRank(minLevel)) return false;
7601
+ }
7602
+ if (null != sinceSeq && Number.isFinite(sinceSeq) && 'number' == typeof event.seq && event.seq <= sinceSeq) return false;
7603
+ if (urlMatches && !urlMatches(event)) return false;
7604
+ if (null != tabId && Number.isFinite(tabId) && event.tabId !== tabId) return false;
7605
+ return true;
7606
+ };
7607
+ }
7608
+ function resolveFormat(opts) {
7609
+ if (opts.output) return opts.output;
7610
+ return process.stdout.isTTY ? 'pretty' : 'ndjson';
7611
+ }
7612
+ function printEvent(event, format) {
7613
+ if ('ndjson' === format) return void console.log(JSON.stringify(event));
7614
+ if ('json' === format) return void console.log(JSON.stringify(event, null, 2));
7615
+ console.log(formatPrettyLogLine(event));
7616
+ }
7617
+ function formatPrettyLogLine(event) {
7618
+ const parts = Array.isArray(event.messageParts) ? event.messageParts.map((p)=>'string' == typeof p ? p : JSON.stringify(p)).join(' ') : '';
7619
+ const code = event.code ? ` ${event.code}` : '';
7620
+ const remediation = event.remediation ? `\n ↳ ${event.remediation}` : '';
7621
+ return `[${event.seq ?? '-'}] ${String(event.level || 'log').toUpperCase()} (${event.context})${code} ${parts}${remediation}`;
7622
+ }
7623
+ function writeFrame(frame) {
7624
+ try {
7625
+ external_node_fs_default().writeSync(1, `${JSON.stringify(frame)}\n`);
7626
+ } catch {
7627
+ console.log(JSON.stringify(frame));
7628
+ }
7629
+ }
7630
+ function logsFilePath(projectPath, browser) {
7631
+ return external_node_path_default().resolve(projectPath, 'dist', 'extension-js', browser, 'logs.ndjson');
7632
+ }
7633
+ function registerLogsCommand(program) {
7634
+ 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)=>{
7635
+ const projectPath = external_node_path_default().resolve(projectPathArg || process.cwd());
7636
+ const browser = options.browser || 'chromium';
7637
+ const format = resolveFormat(options);
7638
+ const matches = makeFilter(options);
7639
+ if (options.follow) return void await followLogs(projectPath, browser, format, matches);
7640
+ const file = logsFilePath(projectPath, browser);
7641
+ if (!external_node_fs_default().existsSync(file)) {
7642
+ const message = `No logs found at ${file}. Start a dev session (extension dev) first, or pass --browser to match it.`;
7643
+ console.error(message);
7644
+ if ('pretty' !== format) writeFrame(messaging.Pr.fail('logs', 'not-found', {
7645
+ code: messaging.Lp.E_LOGS_NOT_FOUND,
7646
+ message,
7647
+ name: 'CliError'
7648
+ }));
7649
+ process.exit(1);
7650
+ }
7651
+ const lines = external_node_fs_default().readFileSync(file, 'utf-8').split('\n').filter(Boolean);
7652
+ for (const line of lines){
7653
+ let event;
7654
+ try {
7655
+ event = JSON.parse(line);
7656
+ } catch {
7657
+ continue;
7658
+ }
7659
+ if (matches(event)) printEvent(event, format);
7660
+ }
7661
+ });
7662
+ }
7663
+ async function followLogs(projectPath, browser, format, matches) {
7664
+ const { BridgeConsumer, readReadyContract } = await loadExtensionDevelopBridgeModule();
7665
+ const ready = readReadyContract(projectPath, browser);
7666
+ if (!ready) {
7667
+ const message = `No active dev session control channel found for ${browser}. Run \`extension dev --browser=${browser}\` first.`;
7668
+ console.error(message);
7669
+ if ('pretty' !== format) writeFrame(messaging.Pr.fail('logs', 'not-found', {
7670
+ code: messaging.Lp.E_SESSION_NOT_FOUND,
7671
+ message,
7672
+ name: 'CliError'
7673
+ }));
7674
+ process.exit(1);
7675
+ }
7676
+ const consumer = new BridgeConsumer({
7677
+ controlPort: ready.controlPort,
7678
+ instanceId: ready.instanceId,
7679
+ reconnect: true,
7680
+ onLog: (event)=>{
7681
+ if (matches(event)) printEvent(event, format);
7682
+ },
7683
+ onGap: (gap)=>{
7684
+ console.error(`… ${gap.dropped} event(s) dropped (${gap.reason}), stream is behind`);
7685
+ }
7686
+ });
7687
+ const shutdown = (signal)=>{
7688
+ consumer.close();
7689
+ if ('pretty' !== format) console.log(JSON.stringify(messaging.Pr.ok('logs', 'interrupted', {
7690
+ signal,
7691
+ follow: true
7692
+ })));
7693
+ exitAfterDrain(0);
7694
+ };
7695
+ process.on('SIGINT', ()=>shutdown('SIGINT'));
7696
+ process.on('SIGTERM', ()=>shutdown('SIGTERM'));
7697
+ consumer.start();
7698
+ await new Promise(()=>{});
7699
+ }
7529
7700
  function readRecentConsole(projectPath, browser, target, limit) {
7530
7701
  const file = external_node_path_default().resolve(projectPath, 'dist', 'extension-js', browser, 'logs.ndjson');
7531
7702
  let lines;
@@ -7615,7 +7786,7 @@ Cross-browser compatibility
7615
7786
  })
7616
7787
  };
7617
7788
  }
7618
- function writeFrame(frame) {
7789
+ function act_writeFrame(frame) {
7619
7790
  try {
7620
7791
  external_node_fs_default().writeSync(1, `${JSON.stringify(frame)}\n`);
7621
7792
  } catch {
@@ -7624,7 +7795,7 @@ Cross-browser compatibility
7624
7795
  }
7625
7796
  function fail(message, frame) {
7626
7797
  console.error(message);
7627
- if (frame && 'json' === frame.output) writeFrame(messaging.Pr.fail(frame.command, statusForCode(frame.code), {
7798
+ if (frame && 'json' === frame.output) act_writeFrame(messaging.Pr.fail(frame.command, statusForCode(frame.code), {
7628
7799
  code: frame.code,
7629
7800
  message,
7630
7801
  name: 'CliError'
@@ -7636,6 +7807,11 @@ Cross-browser compatibility
7636
7807
  if (result.ok) {
7637
7808
  const value = result.value;
7638
7809
  console.log('string' == typeof value ? value : JSON.stringify(value, null, 2));
7810
+ const consoleRecords = result.console;
7811
+ if (Array.isArray(consoleRecords)) {
7812
+ if (0 === consoleRecords.length) console.log('(no console lines captured)');
7813
+ for (const record of consoleRecords)console.log(formatPrettyLogLine(record));
7814
+ }
7639
7815
  if (result.truncated) console.error('… result truncated (byte cap)');
7640
7816
  return;
7641
7817
  }
@@ -7653,8 +7829,9 @@ Cross-browser compatibility
7653
7829
  command: input.command,
7654
7830
  output: input.opts.output
7655
7831
  };
7832
+ const unlockFlag = 'eval' === input.op ? '--allow-eval' : '--allow-control';
7656
7833
  const ready = readReadyContract(projectPath, browser);
7657
- if (!ready) fail(`No active control channel found for ${browser}. Run \`extension dev --browser=${browser} --allow-control\` first.`, {
7834
+ if (!ready) fail(`No active control channel found for ${browser}. Run \`extension dev --browser=${browser} ${unlockFlag}\` first.`, {
7658
7835
  ...outputFrame,
7659
7836
  code: messaging.Lp.E_SESSION_NOT_FOUND
7660
7837
  });
@@ -7662,7 +7839,8 @@ Cross-browser compatibility
7662
7839
  const controller = new BridgeController({
7663
7840
  controlPort: ready.controlPort,
7664
7841
  instanceId: ready.instanceId,
7665
- token: token ?? void 0
7842
+ token: token ?? void 0,
7843
+ unlockFlag
7666
7844
  });
7667
7845
  try {
7668
7846
  await controller.connect();
@@ -7780,7 +7958,7 @@ Cross-browser compatibility
7780
7958
  opts
7781
7959
  });
7782
7960
  });
7783
- commonOptions(program.command('inspect').arguments('[project-path]').description(commandDescriptions.inspect).option('--context <content|page|popup|options|sidebar|devtools|newtab|history|bookmarks>', 'what to inspect: content/page or an open surface, including url-override pages (default content)').option('--url <glob|substring>', 'for content/page: document to target (resolved to its tab)').option('--tab <id>', 'for content/page: a specific tab (default: the --url match, else the active tab)').option('--list-tabs', 'list open tabs as {tabId,url,title} and exit').option('--include <list>', 'comma-separated: html,summary (default summary)').option('--max-bytes <n>', 'cap on returned HTML bytes (default 262144)').option('--with-console [n]', 'also include the last n console lines for the target (default 20)')).action(async (projectPathArg, opts)=>{
7961
+ commonOptions(program.command('inspect').arguments('[project-path]').description(commandDescriptions.inspect).option('--context <content|page|popup|options|sidebar|devtools|newtab|history|bookmarks>', 'what to inspect: content/page or an open surface, including url-override pages (default content)').option('--url <glob|substring>', 'for content/page: document to target (resolved to its tab)').option('--tab <id>', 'for content/page: a specific tab (default: the --url match, else the active tab)').option('--list-tabs', 'list open tabs as {id,url,title,active,windowId} and exit (pass id to --tab)').option('--include <list>', 'comma-separated: html,summary (default summary)').option('--max-bytes <n>', 'cap on returned HTML bytes (default 262144)').option('--with-console [n]', 'also include the last n console lines for the target (default 20)')).action(async (projectPathArg, opts)=>{
7784
7962
  if (opts.listTabs) return void await runCommand({
7785
7963
  projectPathArg,
7786
7964
  command: 'inspect',
@@ -7870,6 +8048,25 @@ Cross-browser compatibility
7870
8048
  function isSafariVendor(value) {
7871
8049
  return 'safari' === value || 'webkit-based' === value;
7872
8050
  }
8051
+ const SUPPORTED_BROWSER_TARGETS = [
8052
+ 'chrome',
8053
+ 'chromium',
8054
+ 'edge',
8055
+ 'firefox',
8056
+ 'brave',
8057
+ 'opera',
8058
+ 'vivaldi',
8059
+ 'yandex',
8060
+ 'waterfox',
8061
+ 'librewolf',
8062
+ 'chromium-based',
8063
+ 'gecko-based',
8064
+ 'firefox-based',
8065
+ 'safari',
8066
+ 'webkit-based'
8067
+ ];
8068
+ const BROWSER_TARGETS_HELP = SUPPORTED_BROWSER_TARGETS.join(' | ');
8069
+ const NO_SAFARI_BROWSER_TARGETS_HELP = SUPPORTED_BROWSER_TARGETS.filter((target)=>!isSafariVendor(target)).join(' | ');
7873
8070
  function parseOptionalBoolean(value) {
7874
8071
  if (void 0 === value) return true;
7875
8072
  const normalized = String(value).trim().toLowerCase();
@@ -7895,23 +8092,7 @@ Cross-browser compatibility
7895
8092
  'firefox'
7896
8093
  ] : vendors(browser);
7897
8094
  function validateVendors(vendorsList, onInvalid) {
7898
- const supported = [
7899
- 'chrome',
7900
- 'edge',
7901
- 'firefox',
7902
- 'chromium',
7903
- 'brave',
7904
- 'opera',
7905
- 'vivaldi',
7906
- 'yandex',
7907
- 'waterfox',
7908
- 'librewolf',
7909
- 'chromium-based',
7910
- 'gecko-based',
7911
- 'firefox-based',
7912
- 'safari',
7913
- 'webkit-based'
7914
- ];
8095
+ const supported = SUPPORTED_BROWSER_TARGETS;
7915
8096
  for (const v of vendorsList)if (!supported.includes(v)) {
7916
8097
  onInvalid(v, supported);
7917
8098
  return false;
@@ -7919,7 +8100,7 @@ Cross-browser compatibility
7919
8100
  return true;
7920
8101
  }
7921
8102
  function registerBuildCommand(program) {
7922
- program.command('build').arguments('[project-name]').usage('[path-to-remote-extension] [options]').description(commandDescriptions.build).option('--browser <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based | safari | webkit-based>', 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds a Safari app via Xcode (macOS only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `false`', 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('--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 })=>{
8103
+ 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('--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 })=>{
7923
8104
  if (buildOptions.debug || buildOptions.author || buildOptions.authorMode) {
7924
8105
  process.env.EXTENSION_DEBUG = '1';
7925
8106
  process.env.EXTENSION_AUTHOR_MODE = 'true';
@@ -8506,7 +8687,7 @@ Cross-browser compatibility
8506
8687
  return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 8080;
8507
8688
  }
8508
8689
  function registerDevCommand(program) {
8509
- 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 <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based | safari | webkit-based>', 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds and opens a Safari app via Xcode (macOS only; no live reload)').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').option('--gecko-binary, --firefox-binary <path-to-binary>', 'specify a path to the Gecko binary. This option overrides the --browser setting. Defaults to the system default').option('--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('--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` (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)=>{
8690
+ 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('--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)=>{
8510
8691
  const { browser = 'chromium', ...devOptions } = options;
8511
8692
  if (devOptions.debug || devOptions.author || devOptions.authorMode) {
8512
8693
  process.env.EXTENSION_DEBUG = '1';
@@ -8652,7 +8833,8 @@ Cross-browser compatibility
8652
8833
  launcher: noBrowser ? void 0 : browsers.launchBrowser,
8653
8834
  safariPackager: (0, safari_packager.P)({
8654
8835
  browser: vendor,
8655
- noOpen: explicitNoOpen ?? false
8836
+ noOpen: explicitNoOpen ?? false,
8837
+ announceDevReady: true
8656
8838
  })
8657
8839
  };
8658
8840
  try {
@@ -9100,150 +9282,13 @@ Cross-browser compatibility
9100
9282
  }));
9101
9283
  });
9102
9284
  }
9103
- const LEVEL_ORDER = [
9104
- 'error',
9105
- 'warn',
9106
- 'info',
9107
- 'debug',
9108
- 'trace'
9109
- ];
9110
- function levelRank(level) {
9111
- const l = 'log' === level ? 'info' : level;
9112
- const i = LEVEL_ORDER.indexOf(l);
9113
- return -1 === i ? LEVEL_ORDER.length : i;
9114
- }
9115
- function makeUrlMatcher(pattern) {
9116
- const hasGlob = pattern.includes('*');
9117
- let re = null;
9118
- if (hasGlob) {
9119
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
9120
- re = new RegExp(escaped);
9121
- }
9122
- return (event)=>{
9123
- const candidates = [
9124
- event.url,
9125
- event.hostname
9126
- ].filter((v)=>'string' == typeof v);
9127
- if (0 === candidates.length) return false;
9128
- return candidates.some((c)=>re ? re.test(c) : c.includes(pattern));
9129
- };
9130
- }
9131
- function makeFilter(opts) {
9132
- const minLevel = String(opts.level || 'all').toLowerCase();
9133
- const contexts = opts.context && 'all' !== opts.context.toLowerCase() ? new Set(opts.context.split(',').map((c)=>c.trim())) : null;
9134
- const sinceSeq = null != opts.since ? Number(opts.since) : null;
9135
- const urlMatches = opts.url ? makeUrlMatcher(opts.url) : null;
9136
- const tabId = null != opts.tab && '' !== opts.tab ? Number(opts.tab) : null;
9137
- return (event)=>{
9138
- if (!event || 'object' != typeof event) return false;
9139
- if ('header' === event.type) return false;
9140
- if (opts.signalsOnly && 'dx.signal' !== event.eventType) return false;
9141
- if (contexts && !contexts.has(String(event.context))) return false;
9142
- if ('all' !== minLevel && 'off' !== minLevel) {
9143
- if (levelRank(String(event.level || '')) > levelRank(minLevel)) return false;
9144
- }
9145
- if (null != sinceSeq && Number.isFinite(sinceSeq) && 'number' == typeof event.seq && event.seq <= sinceSeq) return false;
9146
- if (urlMatches && !urlMatches(event)) return false;
9147
- if (null != tabId && Number.isFinite(tabId) && event.tabId !== tabId) return false;
9148
- return true;
9149
- };
9150
- }
9151
- function resolveFormat(opts) {
9152
- if (opts.output) return opts.output;
9153
- return process.stdout.isTTY ? 'pretty' : 'ndjson';
9154
- }
9155
- function printEvent(event, format) {
9156
- if ('ndjson' === format) return void console.log(JSON.stringify(event));
9157
- if ('json' === format) return void console.log(JSON.stringify(event, null, 2));
9158
- const parts = Array.isArray(event.messageParts) ? event.messageParts.map((p)=>'string' == typeof p ? p : JSON.stringify(p)).join(' ') : '';
9159
- const code = event.code ? ` ${event.code}` : '';
9160
- const remediation = event.remediation ? `\n ↳ ${event.remediation}` : '';
9161
- console.log(`[${event.seq ?? '-'}] ${String(event.level || 'log').toUpperCase()} (${event.context})${code} ${parts}${remediation}`);
9162
- }
9163
- function logs_writeFrame(frame) {
9164
- try {
9165
- external_node_fs_default().writeSync(1, `${JSON.stringify(frame)}\n`);
9166
- } catch {
9167
- console.log(JSON.stringify(frame));
9168
- }
9169
- }
9170
- function logsFilePath(projectPath, browser) {
9171
- return external_node_path_default().resolve(projectPath, 'dist', 'extension-js', browser, 'logs.ndjson');
9172
- }
9173
- function registerLogsCommand(program) {
9174
- 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', 'show only structured dx.signal diagnostics').option('--since <seq|iso>', 'only show events after this sequence number').option('--url <glob|substring>', 'only events whose url/hostname matches (glob with * or plain substring)').option('--tab <id>', 'only events from this tab id').option('--output <pretty|json|ndjson>', 'output format. Defaults to pretty on a TTY, ndjson when piped').action(async (projectPathArg, options)=>{
9175
- const projectPath = external_node_path_default().resolve(projectPathArg || process.cwd());
9176
- const browser = options.browser || 'chromium';
9177
- const format = resolveFormat(options);
9178
- const matches = makeFilter(options);
9179
- if (options.follow) return void await followLogs(projectPath, browser, format, matches);
9180
- const file = logsFilePath(projectPath, browser);
9181
- if (!external_node_fs_default().existsSync(file)) {
9182
- const message = `No logs found at ${file}. Start a dev session (extension dev) first, or pass --browser to match it.`;
9183
- console.error(message);
9184
- if ('pretty' !== format) logs_writeFrame(messaging.Pr.fail('logs', 'not-found', {
9185
- code: messaging.Lp.E_LOGS_NOT_FOUND,
9186
- message,
9187
- name: 'CliError'
9188
- }));
9189
- process.exit(1);
9190
- }
9191
- const lines = external_node_fs_default().readFileSync(file, 'utf-8').split('\n').filter(Boolean);
9192
- for (const line of lines){
9193
- let event;
9194
- try {
9195
- event = JSON.parse(line);
9196
- } catch {
9197
- continue;
9198
- }
9199
- if (matches(event)) printEvent(event, format);
9200
- }
9201
- });
9202
- }
9203
- async function followLogs(projectPath, browser, format, matches) {
9204
- const { BridgeConsumer, readReadyContract } = await loadExtensionDevelopBridgeModule();
9205
- const ready = readReadyContract(projectPath, browser);
9206
- if (!ready) {
9207
- const message = `No active dev session control channel found for ${browser}. Run \`extension dev --browser=${browser}\` first.`;
9208
- console.error(message);
9209
- if ('pretty' !== format) logs_writeFrame(messaging.Pr.fail('logs', 'not-found', {
9210
- code: messaging.Lp.E_SESSION_NOT_FOUND,
9211
- message,
9212
- name: 'CliError'
9213
- }));
9214
- process.exit(1);
9215
- }
9216
- const consumer = new BridgeConsumer({
9217
- controlPort: ready.controlPort,
9218
- instanceId: ready.instanceId,
9219
- reconnect: true,
9220
- onLog: (event)=>{
9221
- if (matches(event)) printEvent(event, format);
9222
- },
9223
- onGap: (gap)=>{
9224
- console.error(`… ${gap.dropped} event(s) dropped (${gap.reason}), stream is behind`);
9225
- }
9226
- });
9227
- const shutdown = (signal)=>{
9228
- consumer.close();
9229
- if ('pretty' !== format) console.log(JSON.stringify(messaging.Pr.ok('logs', 'interrupted', {
9230
- signal,
9231
- follow: true
9232
- })));
9233
- exitAfterDrain(0);
9234
- };
9235
- process.on('SIGINT', ()=>shutdown('SIGINT'));
9236
- process.on('SIGTERM', ()=>shutdown('SIGTERM'));
9237
- consumer.start();
9238
- await new Promise(()=>{});
9239
- }
9240
9285
  var run_only = __webpack_require__("./browsers/run-only.ts");
9241
9286
  const PREVIEW_NOT_FOUND_NEEDLES = [
9242
9287
  'Preview is run-only',
9243
9288
  'Manifest file not found'
9244
9289
  ];
9245
9290
  function registerPreviewCommand(program) {
9246
- 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 <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based>', 'specify a browser/engine to run. Defaults to `chromium`').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').option('--gecko-binary, --firefox-binary <path-to-binary>', 'specify a path to the Gecko binary. This option overrides the --browser setting. Defaults to the system default').option('--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 <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)=>{
9291
+ 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 <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)=>{
9247
9292
  const { browser = 'chromium', ...previewOptions } = options;
9248
9293
  if (previewOptions.debug || previewOptions.author || previewOptions.authorMode) {
9249
9294
  process.env.EXTENSION_DEBUG = '1';
@@ -9340,39 +9385,75 @@ Cross-browser compatibility
9340
9385
  const base = xdg || external_node_path_default().join(external_node_os_default().homedir(), '.config');
9341
9386
  return external_node_path_default().join(base, 'extension-dev', 'auth.json');
9342
9387
  }
9343
- function readStoredLoginToken() {
9388
+ function readStoredLogin() {
9344
9389
  try {
9345
9390
  const raw = external_node_fs_default().readFileSync(storedLoginPath(), 'utf8');
9346
9391
  const data = JSON.parse(raw);
9347
- if (!data || 'object' != typeof data) return '';
9348
- if (1 !== data.version) return '';
9392
+ if (!data || 'object' != typeof data) return null;
9393
+ if (1 !== data.version) return null;
9349
9394
  const token = String(data.token || '').trim();
9350
- if (!token) return '';
9395
+ if (!token) return null;
9351
9396
  const expiresAt = Number(data.expiresAt || 0);
9352
- if (expiresAt && expiresAt <= Math.floor(Date.now() / 1000)) return '';
9353
- return token;
9397
+ if (expiresAt && expiresAt <= Math.floor(Date.now() / 1000)) return null;
9398
+ return {
9399
+ token,
9400
+ projectSlug: String(data.projectSlug || '').trim(),
9401
+ workspaceSlug: String(data.workspaceSlug || '').trim()
9402
+ };
9354
9403
  } catch {
9355
- return '';
9404
+ return null;
9356
9405
  }
9357
9406
  }
9358
- function buildPublishRequest(opts) {
9359
- const token = String(opts.token || process.env.EXTENSION_DEV_TOKEN || readStoredLoginToken()).trim();
9407
+ function readLocalProjectName(projectPath) {
9408
+ const fromJson = (file)=>{
9409
+ try {
9410
+ const data = JSON.parse(external_node_fs_default().readFileSync(file, 'utf8'));
9411
+ return String(data?.name || '').trim();
9412
+ } catch {
9413
+ return '';
9414
+ }
9415
+ };
9416
+ return fromJson(external_node_path_default().join(projectPath, 'package.json')) || fromJson(external_node_path_default().join(projectPath, 'manifest.json')) || fromJson(external_node_path_default().join(projectPath, 'src', 'manifest.json')) || external_node_path_default().basename(external_node_path_default().resolve(projectPath));
9417
+ }
9418
+ function slugish(value) {
9419
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
9420
+ }
9421
+ function buildPublishPlan(opts) {
9422
+ const stored = readStoredLogin();
9423
+ const flagToken = String(opts.token || '').trim();
9424
+ const envToken = String(process.env.EXTENSION_DEV_TOKEN || '').trim();
9425
+ const token = flagToken || envToken || stored?.token || '';
9426
+ const source = flagToken ? 'flag' : envToken ? 'env' : 'stored-login';
9360
9427
  if (!token) throw new Error(`No token. Publishing needs an extension.dev access token.\nGet one: ${PUBLISH_DOCS_URL}\n` + NO_TOKEN_REMEDY);
9428
+ const projectPath = external_node_path_default().resolve(opts.projectPath || process.cwd());
9429
+ const localName = readLocalProjectName(projectPath);
9430
+ const confirmed = String(opts.project || '').trim();
9431
+ const scopedSlug = 'stored-login' === source ? stored?.projectSlug || '' : '';
9432
+ if (confirmed && scopedSlug && slugish(confirmed) !== slugish(scopedSlug)) throw new Error(`You asked to publish "${confirmed}" but your stored login is scoped to "${scopedSlug}".\nPass --token for ${confirmed}, or run npx @extension.dev/mcp login for it.`);
9433
+ if (scopedSlug && slugish(scopedSlug) !== slugish(localName) && slugish(confirmed) !== slugish(scopedSlug)) throw new Error(`Your stored login is scoped to the "${scopedSlug}" project${stored?.workspaceSlug ? ` in workspace "${stored.workspaceSlug}"` : ''}, but ${projectPath} is "${localName}".\nPublishing here would share ${scopedSlug}, not ${localName}.\nRun extension publish inside ${scopedSlug}, pass --project ${scopedSlug} to publish that project on purpose, or pass --token / EXTENSION_DEV_TOKEN for this one.`);
9361
9434
  const base = String(opts.api || process.env.EXTENSION_DEV_API_URL || DEFAULT_API).replace(/\/+$/, '');
9362
9435
  const body = {};
9363
9436
  if (null != opts.ttl && '' !== opts.ttl) body.ttlHours = Number(opts.ttl);
9364
9437
  if (opts.buildSha) body.buildSha = opts.buildSha;
9365
9438
  return {
9366
- url: `${base}/api/cli/publish`,
9367
- headers: {
9368
- authorization: `Bearer ${token}`,
9369
- 'content-type': 'application/json'
9439
+ request: {
9440
+ url: `${base}/api/cli/publish`,
9441
+ headers: {
9442
+ authorization: `Bearer ${token}`,
9443
+ 'content-type': 'application/json'
9444
+ },
9445
+ body: JSON.stringify(body)
9370
9446
  },
9371
- body: JSON.stringify(body)
9447
+ scope: {
9448
+ source,
9449
+ actsFor: scopedSlug || confirmed || localName,
9450
+ workspace: 'stored-login' === source ? stored?.workspaceSlug || '' : '',
9451
+ localName
9452
+ }
9372
9453
  };
9373
9454
  }
9374
9455
  function registerPublishCommand(program) {
9375
- program.command('publish').arguments('[project-path]').usage('[project-path] [options]').description(commandDescriptions.publish).option('--token <token>', 'extension.dev 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('--output <pretty|json>', 'output format (default pretty)').action(async (_projectPathArg, opts)=>{
9456
+ program.command('publish').arguments('[project-path]').usage('[project-path] [options]').description(commandDescriptions.publish).option('--token <token>', 'extension.dev 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)=>{
9376
9457
  const asJson = 'json' === opts.output;
9377
9458
  const failWith = async (status, error, prose, hint)=>{
9378
9459
  if (asJson) console.log(JSON.stringify(messaging.Pr.fail('publish', status, error, hint ? {
@@ -9382,8 +9463,14 @@ Cross-browser compatibility
9382
9463
  await exitAfterDrain(1);
9383
9464
  };
9384
9465
  let req;
9466
+ let scope;
9385
9467
  try {
9386
- req = buildPublishRequest(opts);
9468
+ const plan = buildPublishPlan({
9469
+ ...opts,
9470
+ projectPath: projectPathArg
9471
+ });
9472
+ req = plan.request;
9473
+ scope = plan.scope;
9387
9474
  } catch (err) {
9388
9475
  const message = err?.message || 'publish failed: no token';
9389
9476
  await failWith('denied', {
@@ -9425,8 +9512,16 @@ Cross-browser compatibility
9425
9512
  }, message);
9426
9513
  return;
9427
9514
  }
9428
- if (asJson) console.log(JSON.stringify(messaging.Pr.ok('publish', 'published', data)));
9429
- else console.log(data.shareUrl || JSON.stringify(data));
9515
+ if (asJson) console.log(JSON.stringify(messaging.Pr.ok('publish', 'published', {
9516
+ ...data,
9517
+ project: scope.actsFor,
9518
+ workspace: scope.workspace || void 0,
9519
+ tokenSource: scope.source
9520
+ })));
9521
+ else {
9522
+ console.error(`Published ${scope.actsFor}${scope.workspace ? ` (workspace ${scope.workspace})` : ''}:`);
9523
+ console.log(data.shareUrl || JSON.stringify(data));
9524
+ }
9430
9525
  await exitAfterDrain(0);
9431
9526
  });
9432
9527
  }
@@ -9444,7 +9539,7 @@ Cross-browser compatibility
9444
9539
  return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 8080;
9445
9540
  }
9446
9541
  function registerStartCommand(program) {
9447
- 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 <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based>', 'specify a browser/engine to run. Defaults to `chromium`').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `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]', '[experimental] 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)=>{
9542
+ 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)=>{
9448
9543
  const { browser = 'chromium', ...startOptions } = options;
9449
9544
  if (startOptions.debug || startOptions.author || startOptions.authorMode) {
9450
9545
  process.env.EXTENSION_DEBUG = '1';
@@ -35,7 +35,7 @@ export interface BrowserConfig {
35
35
  *
36
36
  * Each flag disables or modifies a specific browser feature for a more controlled development environment.
37
37
  */
38
- export type DefaultBrowserFlags = '--no-first-run' | '--disable-client-side-phishing-detection' | '--disable-component-extensions-with-background-pages' | '--disable-default-apps' | '--disable-features=InterestFeedContentSuggestions' | '--disable-features=Translate' | '--hide-scrollbars' | '--mute-audio' | '--no-default-browser-check' | '--ash-no-nudges' | '--disable-search-engine-choice-screen' | '--disable-features=MediaRoute' | '--use-mock-keychain' | '--disable-background-networking' | '--disable-breakpad' | '--disable-component-update' | '--disable-domain-reliability' | '--disable-features=AutofillServerCommunicatio' | '--disable-features=CertificateTransparencyComponentUpdate' | '--disable-sync' | '--disable-features=OptimizationHints' | '--disable-features=DialMediaRouteProvider' | '--no-pings' | '--enable-features=SidePanelUpdates' | '--enable-unsafe-extension-debugging' | '--silent-debugger-extension-api';
38
+ export type DefaultBrowserFlags = '--no-first-run' | '--disable-client-side-phishing-detection' | '--disable-component-extensions-with-background-pages' | '--disable-default-apps' | '--disable-features=InterestFeedContentSuggestions' | '--disable-features=Translate' | '--hide-scrollbars' | '--mute-audio' | '--no-default-browser-check' | '--ash-no-nudges' | '--disable-search-engine-choice-screen' | '--disable-features=MediaRoute' | '--use-mock-keychain' | '--disable-background-networking' | '--disable-breakpad' | '--disable-component-update' | '--disable-domain-reliability' | '--disable-features=AutofillServerCommunicatio' | '--disable-features=CertificateTransparencyComponentUpdate' | '--disable-sync' | '--disable-features=OptimizationHints' | '--disable-features=DialMediaRouteProvider' | '--no-pings' | '--enable-features=SidePanelUpdates' | '--disable-features=DisableLoadExtensionCommandLineSwitch' | '--disable-features=ExtensionDisableUnsupportedDeveloper' | '--enable-unsafe-extension-debugging' | '--silent-debugger-extension-api';
39
39
  /**
40
40
  * Options for the browser plugin.
41
41
  */
@@ -6,6 +6,9 @@ export type FirefoxPluginLike = Pick<PluginInterface, 'extension' | 'browserFlag
6
6
  export interface FirefoxPluginRuntime extends FirefoxPluginLike {
7
7
  rdpController?: FirefoxRDPController;
8
8
  browserVersionLine?: string;
9
+ launchProfilePath?: string;
10
+ launchBinaryPath?: string;
11
+ launchBinaryProvenance?: 'managed' | 'pinned' | 'system' | 'snapshot';
9
12
  logSink?: BrowserLogSink;
10
13
  extensionLoadRefused?: string;
11
14
  retryAddonInstall?: () => Promise<ExtensionLoadRetryResult>;
@@ -12,6 +12,9 @@ type PluginLike = {
12
12
  watchSource?: boolean;
13
13
  port?: number | string;
14
14
  browserVersionLine?: string;
15
+ launchProfilePath?: string;
16
+ launchBinaryPath?: string;
17
+ launchBinaryProvenance?: 'managed' | 'pinned' | 'system' | 'snapshot';
15
18
  };
16
19
  export declare class FirefoxRDPController {
17
20
  private readonly remote;
@@ -1,4 +1,8 @@
1
- export declare function printRunningInDevelopmentSummary(candidateAddonPaths: string[], browser: 'firefox', extensionId?: string, browserVersionLine?: string): Promise<boolean>;
1
+ export declare function printRunningInDevelopmentSummary(candidateAddonPaths: string[], browser: 'firefox', extensionId?: string, browserVersionLine?: string, launchIdentity?: {
2
+ profilePath?: string;
3
+ binaryPath?: string;
4
+ binaryProvenance?: 'managed' | 'pinned' | 'system' | 'snapshot';
5
+ }): Promise<boolean>;
2
6
  export declare function printLogEventPretty(event: {
3
7
  id: string;
4
8
  timestamp: number;
@@ -13,6 +13,9 @@ export declare class RemoteFirefox {
13
13
  constructor(configOptions: PluginInterface & {
14
14
  browserVersionLine?: string;
15
15
  resolvedRdpPort?: number;
16
+ launchProfilePath?: string;
17
+ launchBinaryPath?: string;
18
+ launchBinaryProvenance?: 'managed' | 'pinned' | 'system' | 'snapshot';
16
19
  });
17
20
  private resolveRdpPort;
18
21
  private connectClient;
@@ -16,6 +16,8 @@ export interface SafariPackagerOverrides {
16
16
  export interface SafariPackagerOptions extends SafariPackagerOverrides {
17
17
  /** 'safari' or 'webkit-based'. Defaults to 'safari'. */
18
18
  browser?: 'safari' | 'webkit-based';
19
+ /** Print the dev identity card and ready line after the first full package. */
20
+ announceDevReady?: boolean;
19
21
  /** Skip opening the packaged app. Defaults to true: packaging is not launching. */
20
22
  noOpen?: boolean;
21
23
  /** Print the commands instead of running them. */
@@ -6,6 +6,7 @@ export type SafariPluginLike = Pick<PluginInterface, 'extension' | 'noOpen' | 'i
6
6
  bundleId?: string;
7
7
  macOsOnly?: boolean;
8
8
  forceRegenerate?: boolean;
9
+ announceDevReady?: boolean;
9
10
  };
10
11
  export interface SafariBuildConfig {
11
12
  extensionDir: string;
@@ -1,2 +1,16 @@
1
1
  import type { Command } from 'commander';
2
+ export interface LogEventLike {
3
+ type?: unknown;
4
+ eventType?: unknown;
5
+ context?: unknown;
6
+ level?: unknown;
7
+ seq?: unknown;
8
+ url?: unknown;
9
+ hostname?: unknown;
10
+ tabId?: unknown;
11
+ messageParts?: unknown;
12
+ code?: unknown;
13
+ remediation?: unknown;
14
+ }
15
+ export declare function formatPrettyLogLine(event: LogEventLike): string;
2
16
  export declare function registerLogsCommand(program: Command): void;
@@ -1,5 +1,12 @@
1
1
  import type { Command } from 'commander';
2
+ export interface StoredLogin {
3
+ token: string;
4
+ projectSlug: string;
5
+ workspaceSlug: string;
6
+ }
7
+ export declare function readStoredLogin(): StoredLogin | null;
2
8
  export declare function readStoredLoginToken(): string;
9
+ export declare function readLocalProjectName(projectPath: string): string;
3
10
  export interface PublishRequest {
4
11
  url: string;
5
12
  headers: Record<string, string>;
@@ -10,7 +17,24 @@ export interface PublishInput {
10
17
  api?: string;
11
18
  ttl?: string | number;
12
19
  buildSha?: string;
20
+ projectPath?: string;
21
+ project?: string;
22
+ }
23
+ /**
24
+ * The project this publish will act for, and where that answer came from. A
25
+ * stored login is scoped to one project, so a publish run anywhere else answers
26
+ * for the wrong one; `actsFor` is what the command prints and refuses on.
27
+ */
28
+ export interface PublishScope {
29
+ source: 'flag' | 'env' | 'stored-login';
30
+ actsFor: string;
31
+ workspace: string;
32
+ localName: string;
13
33
  }
14
34
  /** Build the HTTP request (pure, unit-testable, no network). */
15
35
  export declare function buildPublishRequest(opts: PublishInput): PublishRequest;
36
+ export declare function buildPublishPlan(opts: PublishInput): {
37
+ request: PublishRequest;
38
+ scope: PublishScope;
39
+ };
16
40
  export declare function registerPublishCommand(program: Command): void;
@@ -231,7 +231,7 @@ export type ProgramAIHelpJSON = {
231
231
  summary: string;
232
232
  templates: string[];
233
233
  }>;
234
- aliases: Array<{
234
+ aliases?: Array<{
235
235
  name: string;
236
236
  resolvesTo: string;
237
237
  note: string;
@@ -1,5 +1,8 @@
1
1
  export type Browser = 'chrome' | 'edge' | 'firefox' | 'chromium' | 'chromium-based' | 'gecko-based' | 'firefox-based' | 'safari' | 'webkit-based';
2
2
  export declare function isSafariVendor(value: string): boolean;
3
+ export declare const SUPPORTED_BROWSER_TARGETS: string[];
4
+ export declare const BROWSER_TARGETS_HELP: string;
5
+ export declare const NO_SAFARI_BROWSER_TARGETS_HELP: string;
3
6
  export declare function parseOptionalBoolean(value?: string): boolean;
4
7
  export declare const vendors: (browser?: Browser | 'all') => string[];
5
8
  export declare const installTargets: (browser?: Browser | 'all') => string[];
package/package.json CHANGED
@@ -38,7 +38,7 @@
38
38
  "extension": "./bin/extension.cjs"
39
39
  },
40
40
  "name": "extension",
41
- "version": "4.0.27",
41
+ "version": "4.0.29-next.0",
42
42
  "description": "The cross-browser extension framework. Build Chrome, Edge, Firefox, and Safari extensions with no build configuration.",
43
43
  "homepage": "https://extension.js.org/",
44
44
  "bugs": {
@@ -106,9 +106,9 @@
106
106
  "vivaldi-location2": "2.1.1",
107
107
  "waterfox-location": "2.1.1",
108
108
  "yandex-location": "2.1.1",
109
- "extension-create": "4.0.27",
110
- "extension-develop": "4.0.27",
111
- "extension-install": "4.0.27",
109
+ "extension-create": "4.0.29-next.0",
110
+ "extension-develop": "4.0.29-next.0",
111
+ "extension-install": "4.0.29-next.0",
112
112
  "commander": "^15.0.0",
113
113
  "pintor": "0.3.0",
114
114
  "semver": "^7.7.3",