extension 4.0.28 → 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
  }
@@ -7782,7 +7958,7 @@ Cross-browser compatibility
7782
7958
  opts
7783
7959
  });
7784
7960
  });
7785
- 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)=>{
7786
7962
  if (opts.listTabs) return void await runCommand({
7787
7963
  projectPathArg,
7788
7964
  command: 'inspect',
@@ -7872,6 +8048,25 @@ Cross-browser compatibility
7872
8048
  function isSafariVendor(value) {
7873
8049
  return 'safari' === value || 'webkit-based' === value;
7874
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(' | ');
7875
8070
  function parseOptionalBoolean(value) {
7876
8071
  if (void 0 === value) return true;
7877
8072
  const normalized = String(value).trim().toLowerCase();
@@ -7897,23 +8092,7 @@ Cross-browser compatibility
7897
8092
  'firefox'
7898
8093
  ] : vendors(browser);
7899
8094
  function validateVendors(vendorsList, onInvalid) {
7900
- const supported = [
7901
- 'chrome',
7902
- 'edge',
7903
- 'firefox',
7904
- 'chromium',
7905
- 'brave',
7906
- 'opera',
7907
- 'vivaldi',
7908
- 'yandex',
7909
- 'waterfox',
7910
- 'librewolf',
7911
- 'chromium-based',
7912
- 'gecko-based',
7913
- 'firefox-based',
7914
- 'safari',
7915
- 'webkit-based'
7916
- ];
8095
+ const supported = SUPPORTED_BROWSER_TARGETS;
7917
8096
  for (const v of vendorsList)if (!supported.includes(v)) {
7918
8097
  onInvalid(v, supported);
7919
8098
  return false;
@@ -7921,7 +8100,7 @@ Cross-browser compatibility
7921
8100
  return true;
7922
8101
  }
7923
8102
  function registerBuildCommand(program) {
7924
- 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 })=>{
7925
8104
  if (buildOptions.debug || buildOptions.author || buildOptions.authorMode) {
7926
8105
  process.env.EXTENSION_DEBUG = '1';
7927
8106
  process.env.EXTENSION_AUTHOR_MODE = 'true';
@@ -8508,7 +8687,7 @@ Cross-browser compatibility
8508
8687
  return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 8080;
8509
8688
  }
8510
8689
  function registerDevCommand(program) {
8511
- 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` (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)=>{
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)=>{
8512
8691
  const { browser = 'chromium', ...devOptions } = options;
8513
8692
  if (devOptions.debug || devOptions.author || devOptions.authorMode) {
8514
8693
  process.env.EXTENSION_DEBUG = '1';
@@ -8654,7 +8833,8 @@ Cross-browser compatibility
8654
8833
  launcher: noBrowser ? void 0 : browsers.launchBrowser,
8655
8834
  safariPackager: (0, safari_packager.P)({
8656
8835
  browser: vendor,
8657
- noOpen: explicitNoOpen ?? false
8836
+ noOpen: explicitNoOpen ?? false,
8837
+ announceDevReady: true
8658
8838
  })
8659
8839
  };
8660
8840
  try {
@@ -9102,150 +9282,13 @@ Cross-browser compatibility
9102
9282
  }));
9103
9283
  });
9104
9284
  }
9105
- const LEVEL_ORDER = [
9106
- 'error',
9107
- 'warn',
9108
- 'info',
9109
- 'debug',
9110
- 'trace'
9111
- ];
9112
- function levelRank(level) {
9113
- const l = 'log' === level ? 'info' : level;
9114
- const i = LEVEL_ORDER.indexOf(l);
9115
- return -1 === i ? LEVEL_ORDER.length : i;
9116
- }
9117
- function makeUrlMatcher(pattern) {
9118
- const hasGlob = pattern.includes('*');
9119
- let re = null;
9120
- if (hasGlob) {
9121
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
9122
- re = new RegExp(escaped);
9123
- }
9124
- return (event)=>{
9125
- const candidates = [
9126
- event.url,
9127
- event.hostname
9128
- ].filter((v)=>'string' == typeof v);
9129
- if (0 === candidates.length) return false;
9130
- return candidates.some((c)=>re ? re.test(c) : c.includes(pattern));
9131
- };
9132
- }
9133
- function makeFilter(opts) {
9134
- const minLevel = String(opts.level || 'all').toLowerCase();
9135
- const contexts = opts.context && 'all' !== opts.context.toLowerCase() ? new Set(opts.context.split(',').map((c)=>c.trim())) : null;
9136
- const sinceSeq = null != opts.since ? Number(opts.since) : null;
9137
- const urlMatches = opts.url ? makeUrlMatcher(opts.url) : null;
9138
- const tabId = null != opts.tab && '' !== opts.tab ? Number(opts.tab) : null;
9139
- return (event)=>{
9140
- if (!event || 'object' != typeof event) return false;
9141
- if ('header' === event.type) return false;
9142
- if (opts.signalsOnly && 'dx.signal' !== event.eventType) return false;
9143
- if (contexts && !contexts.has(String(event.context))) return false;
9144
- if ('all' !== minLevel && 'off' !== minLevel) {
9145
- if (levelRank(String(event.level || '')) > levelRank(minLevel)) return false;
9146
- }
9147
- if (null != sinceSeq && Number.isFinite(sinceSeq) && 'number' == typeof event.seq && event.seq <= sinceSeq) return false;
9148
- if (urlMatches && !urlMatches(event)) return false;
9149
- if (null != tabId && Number.isFinite(tabId) && event.tabId !== tabId) return false;
9150
- return true;
9151
- };
9152
- }
9153
- function resolveFormat(opts) {
9154
- if (opts.output) return opts.output;
9155
- return process.stdout.isTTY ? 'pretty' : 'ndjson';
9156
- }
9157
- function printEvent(event, format) {
9158
- if ('ndjson' === format) return void console.log(JSON.stringify(event));
9159
- if ('json' === format) return void console.log(JSON.stringify(event, null, 2));
9160
- const parts = Array.isArray(event.messageParts) ? event.messageParts.map((p)=>'string' == typeof p ? p : JSON.stringify(p)).join(' ') : '';
9161
- const code = event.code ? ` ${event.code}` : '';
9162
- const remediation = event.remediation ? `\n ↳ ${event.remediation}` : '';
9163
- console.log(`[${event.seq ?? '-'}] ${String(event.level || 'log').toUpperCase()} (${event.context})${code} ${parts}${remediation}`);
9164
- }
9165
- function logs_writeFrame(frame) {
9166
- try {
9167
- external_node_fs_default().writeSync(1, `${JSON.stringify(frame)}\n`);
9168
- } catch {
9169
- console.log(JSON.stringify(frame));
9170
- }
9171
- }
9172
- function logsFilePath(projectPath, browser) {
9173
- return external_node_path_default().resolve(projectPath, 'dist', 'extension-js', browser, 'logs.ndjson');
9174
- }
9175
- function registerLogsCommand(program) {
9176
- 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)=>{
9177
- const projectPath = external_node_path_default().resolve(projectPathArg || process.cwd());
9178
- const browser = options.browser || 'chromium';
9179
- const format = resolveFormat(options);
9180
- const matches = makeFilter(options);
9181
- if (options.follow) return void await followLogs(projectPath, browser, format, matches);
9182
- const file = logsFilePath(projectPath, browser);
9183
- if (!external_node_fs_default().existsSync(file)) {
9184
- const message = `No logs found at ${file}. Start a dev session (extension dev) first, or pass --browser to match it.`;
9185
- console.error(message);
9186
- if ('pretty' !== format) logs_writeFrame(messaging.Pr.fail('logs', 'not-found', {
9187
- code: messaging.Lp.E_LOGS_NOT_FOUND,
9188
- message,
9189
- name: 'CliError'
9190
- }));
9191
- process.exit(1);
9192
- }
9193
- const lines = external_node_fs_default().readFileSync(file, 'utf-8').split('\n').filter(Boolean);
9194
- for (const line of lines){
9195
- let event;
9196
- try {
9197
- event = JSON.parse(line);
9198
- } catch {
9199
- continue;
9200
- }
9201
- if (matches(event)) printEvent(event, format);
9202
- }
9203
- });
9204
- }
9205
- async function followLogs(projectPath, browser, format, matches) {
9206
- const { BridgeConsumer, readReadyContract } = await loadExtensionDevelopBridgeModule();
9207
- const ready = readReadyContract(projectPath, browser);
9208
- if (!ready) {
9209
- const message = `No active dev session control channel found for ${browser}. Run \`extension dev --browser=${browser}\` first.`;
9210
- console.error(message);
9211
- if ('pretty' !== format) logs_writeFrame(messaging.Pr.fail('logs', 'not-found', {
9212
- code: messaging.Lp.E_SESSION_NOT_FOUND,
9213
- message,
9214
- name: 'CliError'
9215
- }));
9216
- process.exit(1);
9217
- }
9218
- const consumer = new BridgeConsumer({
9219
- controlPort: ready.controlPort,
9220
- instanceId: ready.instanceId,
9221
- reconnect: true,
9222
- onLog: (event)=>{
9223
- if (matches(event)) printEvent(event, format);
9224
- },
9225
- onGap: (gap)=>{
9226
- console.error(`… ${gap.dropped} event(s) dropped (${gap.reason}), stream is behind`);
9227
- }
9228
- });
9229
- const shutdown = (signal)=>{
9230
- consumer.close();
9231
- if ('pretty' !== format) console.log(JSON.stringify(messaging.Pr.ok('logs', 'interrupted', {
9232
- signal,
9233
- follow: true
9234
- })));
9235
- exitAfterDrain(0);
9236
- };
9237
- process.on('SIGINT', ()=>shutdown('SIGINT'));
9238
- process.on('SIGTERM', ()=>shutdown('SIGTERM'));
9239
- consumer.start();
9240
- await new Promise(()=>{});
9241
- }
9242
9285
  var run_only = __webpack_require__("./browsers/run-only.ts");
9243
9286
  const PREVIEW_NOT_FOUND_NEEDLES = [
9244
9287
  'Preview is run-only',
9245
9288
  'Manifest file not found'
9246
9289
  ];
9247
9290
  function registerPreviewCommand(program) {
9248
- 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)=>{
9249
9292
  const { browser = 'chromium', ...previewOptions } = options;
9250
9293
  if (previewOptions.debug || previewOptions.author || previewOptions.authorMode) {
9251
9294
  process.env.EXTENSION_DEBUG = '1';
@@ -9496,7 +9539,7 @@ Cross-browser compatibility
9496
9539
  return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 8080;
9497
9540
  }
9498
9541
  function registerStartCommand(program) {
9499
- 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)=>{
9500
9543
  const { browser = 'chromium', ...startOptions } = options;
9501
9544
  if (startOptions.debug || startOptions.author || startOptions.authorMode) {
9502
9545
  process.env.EXTENSION_DEBUG = '1';
@@ -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;
@@ -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.28",
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.28",
110
- "extension-develop": "4.0.28",
111
- "extension-install": "4.0.28",
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",