extension 4.1.14 → 4.1.16

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
@@ -891,6 +891,20 @@ var __webpack_modules__ = {
891
891
  function rdpInvalidRequestPayload() {
892
892
  return `${getLoggingPrefix('error')} Received an unreadable Firefox remote debugging message.\nThe debugging connection is out of sync with the browser, usually after a crash or an abrupt reload.\nRestart the dev session.\nIf it repeats, report it with your Firefox version.`;
893
893
  }
894
+ function firstRunInstallOffer(browser) {
895
+ const name = managedBrowserDisplayName(browser);
896
+ return `${getLoggingPrefix('info')} ${name} is not installed yet.\nExtension.js runs your extension in a version-pinned browser with an isolated profile, so it never touches the browser you use every day.\n${pintor__rspack_import_8_default().gray('NO DOWNLOAD')} ${pintor__rspack_import_8_default().blue('--browser=edge')} or ${pintor__rspack_import_8_default().blue('--browser=brave')} use a browser already on this machine, and ${pintor__rspack_import_8_default().blue('--chromium-binary')} ${pintor__rspack_import_8_default().gray('<abs-path>')} pins any binary, including your own Chrome.`;
897
+ }
898
+ function firstRunInstallQuestion(browser) {
899
+ const name = managedBrowserDisplayName(browser);
900
+ return `${getLoggingPrefix('info')} Download ${name} now? ${pintor__rspack_import_8_default().gray('[Y/n]')} `;
901
+ }
902
+ function firstRunInstallDeclined(browser) {
903
+ return `${getLoggingPrefix('info')} Skipped the download.\nRun ${pintor__rspack_import_8_default().blue(`npx extension install ${browser}`)} when you want it, or pass one of the no-download options above.`;
904
+ }
905
+ function firstRunInstallFailed(browser, reason) {
906
+ return `${getLoggingPrefix('warn')} The download did not finish.\n${pintor__rspack_import_8_default().gray('REASON')} ${reason}\nRun ${pintor__rspack_import_8_default().blue(`npx extension install ${browser}`)} to see the full output.`;
907
+ }
894
908
  __webpack_require__.d(__webpack_exports__, {
895
909
  $q: ()=>safariDefaultBundleIdNote,
896
910
  $w: ()=>bestEffortBannerPrintFailed,
@@ -909,6 +923,7 @@ var __webpack_modules__ = {
909
923
  F1: ()=>cdpClientBrowserConnectionEstablished,
910
924
  F4: ()=>enhancedProcessManagementUncaughtException,
911
925
  FF: ()=>cdpClientFoundTargets,
926
+ FW: ()=>firstRunInstallQuestion,
912
927
  Fm: ()=>connectionClosedError,
913
928
  G3: ()=>safariRequiresMacOS,
914
929
  GB: ()=>waitingForBrowserDebugger,
@@ -928,6 +943,7 @@ var __webpack_modules__ = {
928
943
  MS: ()=>errorConnectingToBrowser,
929
944
  N4: ()=>enhancedProcessManagementUnhandledRejection,
930
945
  NO: ()=>mv3BackgroundScriptsNotSupportedByChromium,
946
+ NT: ()=>firstRunInstallDeclined,
931
947
  Nk: ()=>chromiumDryRunNotLaunching,
932
948
  Q0: ()=>chromiumDryRunBinary,
933
949
  Rl: ()=>safariBuilt,
@@ -959,6 +975,7 @@ var __webpack_modules__ = {
959
975
  fR: ()=>cdpClientConnectionError,
960
976
  fd: ()=>safariRegistered,
961
977
  fw: ()=>safariOpening,
978
+ gX: ()=>firstRunInstallFailed,
962
979
  hE: ()=>cdpClientConnectionClosed,
963
980
  io: ()=>emptyLine,
964
981
  jP: ()=>rdpInvalidRequestPayload,
@@ -969,6 +986,7 @@ var __webpack_modules__ = {
969
986
  l9: ()=>parsingPacketError,
970
987
  lO: ()=>firefoxDryRunArgs,
971
988
  l_: ()=>safariConverted,
989
+ lk: ()=>firstRunInstallOffer,
972
990
  ne: ()=>requireChromiumBinaryForChromiumBased,
973
991
  nn: ()=>chromeProcessExited,
974
992
  oB: ()=>safariSettingsPreserved,
@@ -2196,6 +2214,63 @@ var __webpack_modules__ = {
2196
2214
  var external_yandex_location_default = /*#__PURE__*/ __webpack_require__.n(external_yandex_location_namespaceObject);
2197
2215
  var messaging = __webpack_require__("./helpers/messaging.ts");
2198
2216
  var banner = __webpack_require__("./browsers/browsers-lib/banner.ts");
2217
+ const external_node_readline_namespaceObject = require("node:readline");
2218
+ var external_node_readline_default = /*#__PURE__*/ __webpack_require__.n(external_node_readline_namespaceObject);
2219
+ var browser_install_outcome = __webpack_require__("./helpers/browser-install-outcome.ts");
2220
+ var messages = __webpack_require__("./browsers/browsers-lib/messages.ts");
2221
+ function isCI() {
2222
+ const v = process.env;
2223
+ return Boolean(v.CI || v.GITHUB_ACTIONS || v.GITLAB_CI || v.BUILDKITE || v.CIRCLECI || v.TRAVIS);
2224
+ }
2225
+ function canPromptForInstall() {
2226
+ if (process.env.VITEST || process.env.VITEST_WORKER_ID) return false;
2227
+ if (process.env.EXTENSION_NO_INSTALL_PROMPT) return false;
2228
+ if (isCI()) return false;
2229
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
2230
+ }
2231
+ function askToInstall(question) {
2232
+ return new Promise((resolve)=>{
2233
+ const rl = external_node_readline_default().createInterface({
2234
+ input: process.stdin,
2235
+ output: process.stdout
2236
+ });
2237
+ const finish = (answer)=>{
2238
+ try {
2239
+ rl.close();
2240
+ } catch {}
2241
+ resolve(answer);
2242
+ };
2243
+ rl.question(question, (answer)=>{
2244
+ const normalized = String(answer || '').trim().toLowerCase();
2245
+ finish('n' !== normalized && 'no' !== normalized);
2246
+ });
2247
+ rl.on('SIGINT', ()=>finish(false));
2248
+ });
2249
+ }
2250
+ async function offerManagedInstall(target) {
2251
+ if (!canPromptForInstall()) return false;
2252
+ (0, messaging._w)(messages.lk(target));
2253
+ (0, browser_install_outcome.rQ)('offered', target);
2254
+ const accepted = await askToInstall(messages.FW(target));
2255
+ if (!accepted) {
2256
+ (0, browser_install_outcome.rQ)('declined', target);
2257
+ (0, messaging._w)(messages.NT(target));
2258
+ return false;
2259
+ }
2260
+ const startedAt = Date.now();
2261
+ try {
2262
+ const { extensionInstall } = await import("extension-install");
2263
+ await extensionInstall({
2264
+ browser: target
2265
+ });
2266
+ (0, browser_install_outcome.rQ)('accepted', target, (Date.now() - startedAt) / 1000);
2267
+ return true;
2268
+ } catch (error) {
2269
+ (0, browser_install_outcome.rQ)('failed', target, (Date.now() - startedAt) / 1000);
2270
+ (0, messaging._w)(messages.gX(target, error instanceof Error ? error.message : String(error)));
2271
+ return false;
2272
+ }
2273
+ }
2199
2274
  var instance_registry = __webpack_require__("./browsers/browsers-lib/instance-registry.ts");
2200
2275
  const MSG_REFERENCE = /__MSG_([A-Za-z0-9_@]+?)__/g;
2201
2276
  function collectMsgReferences(value) {
@@ -2416,7 +2491,6 @@ var __webpack_modules__ = {
2416
2491
  return false;
2417
2492
  }
2418
2493
  }
2419
- var messages = __webpack_require__("./browsers/browsers-lib/messages.ts");
2420
2494
  var output_binaries_resolver = __webpack_require__("./browsers/browsers-lib/output-binaries-resolver.ts");
2421
2495
  var process_teardown = __webpack_require__("./browsers/browsers-lib/process-teardown.ts");
2422
2496
  var ready_message = __webpack_require__("./browsers/browsers-lib/ready-message.ts");
@@ -3208,6 +3282,26 @@ var __webpack_modules__ = {
3208
3282
  browserBinaryLocation = normalized;
3209
3283
  }
3210
3284
  }
3285
+ if (!browserBinaryLocation || !external_node_fs_.existsSync(browserBinaryLocation)) {
3286
+ const offerTarget = 'chromium' === browser ? 'chrome' : 'chrome' === browser || 'edge' === browser ? browser : null;
3287
+ if (offerTarget && await offerManagedInstall(offerTarget)) {
3288
+ const justInstalled = ()=>{
3289
+ try {
3290
+ const env = managedEnvFor(offerTarget);
3291
+ const located = 'edge' === offerTarget ? external_edge_location_default()({
3292
+ env
3293
+ }) : external_chrome_location2_default()(true, {
3294
+ env
3295
+ });
3296
+ return normalizePath(located || null);
3297
+ } catch {
3298
+ return null;
3299
+ }
3300
+ };
3301
+ const resolved = resolveManagedBinary() || justInstalled();
3302
+ if (isUsableBinary(resolved)) browserBinaryLocation = resolved;
3303
+ }
3304
+ }
3211
3305
  if (!browserBinaryLocation || !external_node_fs_.existsSync(browserBinaryLocation)) {
3212
3306
  if ('chromium' === browser || 'chromium-based' === browser) printInstallGuidance(getInstallGuidanceText('chromium'), 'chromium');
3213
3307
  if (!printedGuidance && 'chromium' !== browser && 'chromium-based' !== browser) this.logger.error(messages.Ih(browser, browserBinaryLocation || ''));
@@ -6186,6 +6280,18 @@ var __webpack_modules__ = {
6186
6280
  P: ()=>createSafariPackager
6187
6281
  });
6188
6282
  },
6283
+ "./helpers/browser-install-outcome.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
6284
+ function recordBrowserInstall(outcome, browser, seconds) {
6285
+ ({
6286
+ ...'number' == typeof seconds && Number.isFinite(seconds) && seconds >= 0 ? {
6287
+ seconds: Math.round(seconds)
6288
+ } : {}
6289
+ });
6290
+ }
6291
+ __webpack_require__.d(__webpack_exports__, {
6292
+ rQ: ()=>recordBrowserInstall
6293
+ });
6294
+ },
6189
6295
  "./helpers/messaging.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
6190
6296
  var pintor__rspack_import_0 = __webpack_require__("pintor");
6191
6297
  var pintor__rspack_import_0_default = /*#__PURE__*/ __webpack_require__.n(pintor__rspack_import_0);
package/dist/cli.cjs CHANGED
@@ -892,6 +892,20 @@ var __webpack_modules__ = {
892
892
  function rdpInvalidRequestPayload() {
893
893
  return `${getLoggingPrefix('error')} Received an unreadable Firefox remote debugging message.\nThe debugging connection is out of sync with the browser, usually after a crash or an abrupt reload.\nRestart the dev session.\nIf it repeats, report it with your Firefox version.`;
894
894
  }
895
+ function firstRunInstallOffer(browser) {
896
+ const name = managedBrowserDisplayName(browser);
897
+ return `${getLoggingPrefix('info')} ${name} is not installed yet.\nExtension.js runs your extension in a version-pinned browser with an isolated profile, so it never touches the browser you use every day.\n${pintor__rspack_import_8_default().gray('NO DOWNLOAD')} ${pintor__rspack_import_8_default().blue('--browser=edge')} or ${pintor__rspack_import_8_default().blue('--browser=brave')} use a browser already on this machine, and ${pintor__rspack_import_8_default().blue('--chromium-binary')} ${pintor__rspack_import_8_default().gray('<abs-path>')} pins any binary, including your own Chrome.`;
898
+ }
899
+ function firstRunInstallQuestion(browser) {
900
+ const name = managedBrowserDisplayName(browser);
901
+ return `${getLoggingPrefix('info')} Download ${name} now? ${pintor__rspack_import_8_default().gray('[Y/n]')} `;
902
+ }
903
+ function firstRunInstallDeclined(browser) {
904
+ return `${getLoggingPrefix('info')} Skipped the download.\nRun ${pintor__rspack_import_8_default().blue(`npx extension install ${browser}`)} when you want it, or pass one of the no-download options above.`;
905
+ }
906
+ function firstRunInstallFailed(browser, reason) {
907
+ return `${getLoggingPrefix('warn')} The download did not finish.\n${pintor__rspack_import_8_default().gray('REASON')} ${reason}\nRun ${pintor__rspack_import_8_default().blue(`npx extension install ${browser}`)} to see the full output.`;
908
+ }
895
909
  __webpack_require__.d(__webpack_exports__, {
896
910
  $q: ()=>safariDefaultBundleIdNote,
897
911
  $w: ()=>bestEffortBannerPrintFailed,
@@ -910,6 +924,7 @@ var __webpack_modules__ = {
910
924
  F1: ()=>cdpClientBrowserConnectionEstablished,
911
925
  F4: ()=>enhancedProcessManagementUncaughtException,
912
926
  FF: ()=>cdpClientFoundTargets,
927
+ FW: ()=>firstRunInstallQuestion,
913
928
  Fm: ()=>connectionClosedError,
914
929
  G3: ()=>safariRequiresMacOS,
915
930
  GB: ()=>waitingForBrowserDebugger,
@@ -929,6 +944,7 @@ var __webpack_modules__ = {
929
944
  MS: ()=>errorConnectingToBrowser,
930
945
  N4: ()=>enhancedProcessManagementUnhandledRejection,
931
946
  NO: ()=>mv3BackgroundScriptsNotSupportedByChromium,
947
+ NT: ()=>firstRunInstallDeclined,
932
948
  Nk: ()=>chromiumDryRunNotLaunching,
933
949
  Q0: ()=>chromiumDryRunBinary,
934
950
  Rl: ()=>safariBuilt,
@@ -960,6 +976,7 @@ var __webpack_modules__ = {
960
976
  fR: ()=>cdpClientConnectionError,
961
977
  fd: ()=>safariRegistered,
962
978
  fw: ()=>safariOpening,
979
+ gX: ()=>firstRunInstallFailed,
963
980
  hE: ()=>cdpClientConnectionClosed,
964
981
  io: ()=>emptyLine,
965
982
  jP: ()=>rdpInvalidRequestPayload,
@@ -970,6 +987,7 @@ var __webpack_modules__ = {
970
987
  l9: ()=>parsingPacketError,
971
988
  lO: ()=>firefoxDryRunArgs,
972
989
  l_: ()=>safariConverted,
990
+ lk: ()=>firstRunInstallOffer,
973
991
  ne: ()=>requireChromiumBinaryForChromiumBased,
974
992
  nn: ()=>chromeProcessExited,
975
993
  oB: ()=>safariSettingsPreserved,
@@ -2187,6 +2205,63 @@ var __webpack_modules__ = {
2187
2205
  var external_yandex_location_default = /*#__PURE__*/ __webpack_require__.n(external_yandex_location_namespaceObject);
2188
2206
  var messaging = __webpack_require__("./helpers/messaging.ts");
2189
2207
  var banner = __webpack_require__("./browsers/browsers-lib/banner.ts");
2208
+ const external_node_readline_namespaceObject = require("node:readline");
2209
+ var external_node_readline_default = /*#__PURE__*/ __webpack_require__.n(external_node_readline_namespaceObject);
2210
+ var browser_install_outcome = __webpack_require__("./helpers/browser-install-outcome.ts");
2211
+ var messages = __webpack_require__("./browsers/browsers-lib/messages.ts");
2212
+ function isCI() {
2213
+ const v = process.env;
2214
+ return Boolean(v.CI || v.GITHUB_ACTIONS || v.GITLAB_CI || v.BUILDKITE || v.CIRCLECI || v.TRAVIS);
2215
+ }
2216
+ function canPromptForInstall() {
2217
+ if (process.env.VITEST || process.env.VITEST_WORKER_ID) return false;
2218
+ if (process.env.EXTENSION_NO_INSTALL_PROMPT) return false;
2219
+ if (isCI()) return false;
2220
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
2221
+ }
2222
+ function askToInstall(question) {
2223
+ return new Promise((resolve)=>{
2224
+ const rl = external_node_readline_default().createInterface({
2225
+ input: process.stdin,
2226
+ output: process.stdout
2227
+ });
2228
+ const finish = (answer)=>{
2229
+ try {
2230
+ rl.close();
2231
+ } catch {}
2232
+ resolve(answer);
2233
+ };
2234
+ rl.question(question, (answer)=>{
2235
+ const normalized = String(answer || '').trim().toLowerCase();
2236
+ finish('n' !== normalized && 'no' !== normalized);
2237
+ });
2238
+ rl.on('SIGINT', ()=>finish(false));
2239
+ });
2240
+ }
2241
+ async function offerManagedInstall(target) {
2242
+ if (!canPromptForInstall()) return false;
2243
+ (0, messaging._w)(messages.lk(target));
2244
+ (0, browser_install_outcome.rQ)('offered', target);
2245
+ const accepted = await askToInstall(messages.FW(target));
2246
+ if (!accepted) {
2247
+ (0, browser_install_outcome.rQ)('declined', target);
2248
+ (0, messaging._w)(messages.NT(target));
2249
+ return false;
2250
+ }
2251
+ const startedAt = Date.now();
2252
+ try {
2253
+ const { extensionInstall } = await import("extension-install");
2254
+ await extensionInstall({
2255
+ browser: target
2256
+ });
2257
+ (0, browser_install_outcome.rQ)('accepted', target, (Date.now() - startedAt) / 1000);
2258
+ return true;
2259
+ } catch (error) {
2260
+ (0, browser_install_outcome.rQ)('failed', target, (Date.now() - startedAt) / 1000);
2261
+ (0, messaging._w)(messages.gX(target, error instanceof Error ? error.message : String(error)));
2262
+ return false;
2263
+ }
2264
+ }
2190
2265
  var instance_registry = __webpack_require__("./browsers/browsers-lib/instance-registry.ts");
2191
2266
  const MSG_REFERENCE = /__MSG_([A-Za-z0-9_@]+?)__/g;
2192
2267
  function collectMsgReferences(value) {
@@ -2407,7 +2482,6 @@ var __webpack_modules__ = {
2407
2482
  return false;
2408
2483
  }
2409
2484
  }
2410
- var messages = __webpack_require__("./browsers/browsers-lib/messages.ts");
2411
2485
  var output_binaries_resolver = __webpack_require__("./browsers/browsers-lib/output-binaries-resolver.ts");
2412
2486
  var process_teardown = __webpack_require__("./browsers/browsers-lib/process-teardown.ts");
2413
2487
  var ready_message = __webpack_require__("./browsers/browsers-lib/ready-message.ts");
@@ -3199,6 +3273,26 @@ var __webpack_modules__ = {
3199
3273
  browserBinaryLocation = normalized;
3200
3274
  }
3201
3275
  }
3276
+ if (!browserBinaryLocation || !external_node_fs_.existsSync(browserBinaryLocation)) {
3277
+ const offerTarget = 'chromium' === browser ? 'chrome' : 'chrome' === browser || 'edge' === browser ? browser : null;
3278
+ if (offerTarget && await offerManagedInstall(offerTarget)) {
3279
+ const justInstalled = ()=>{
3280
+ try {
3281
+ const env = managedEnvFor(offerTarget);
3282
+ const located = 'edge' === offerTarget ? external_edge_location_default()({
3283
+ env
3284
+ }) : external_chrome_location2_default()(true, {
3285
+ env
3286
+ });
3287
+ return normalizePath(located || null);
3288
+ } catch {
3289
+ return null;
3290
+ }
3291
+ };
3292
+ const resolved = resolveManagedBinary() || justInstalled();
3293
+ if (isUsableBinary(resolved)) browserBinaryLocation = resolved;
3294
+ }
3295
+ }
3202
3296
  if (!browserBinaryLocation || !external_node_fs_.existsSync(browserBinaryLocation)) {
3203
3297
  if ('chromium' === browser || 'chromium-based' === browser) printInstallGuidance(getInstallGuidanceText('chromium'), 'chromium');
3204
3298
  if (!printedGuidance && 'chromium' !== browser && 'chromium-based' !== browser) this.logger.error(messages.Ih(browser, browserBinaryLocation || ''));
@@ -6177,6 +6271,25 @@ var __webpack_modules__ = {
6177
6271
  P: ()=>createSafariPackager
6178
6272
  });
6179
6273
  },
6274
+ "./helpers/browser-install-outcome.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
6275
+ let record = null;
6276
+ function recordBrowserInstall(outcome, browser, seconds) {
6277
+ record = {
6278
+ outcome,
6279
+ browser,
6280
+ ...'number' == typeof seconds && Number.isFinite(seconds) && seconds >= 0 ? {
6281
+ seconds: Math.round(seconds)
6282
+ } : {}
6283
+ };
6284
+ }
6285
+ function readBrowserInstall() {
6286
+ return record;
6287
+ }
6288
+ __webpack_require__.d(__webpack_exports__, {
6289
+ rQ: ()=>recordBrowserInstall,
6290
+ w6: ()=>readBrowserInstall
6291
+ });
6292
+ },
6180
6293
  "./helpers/messaging.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
6181
6294
  var pintor__rspack_import_0 = __webpack_require__("pintor");
6182
6295
  var pintor__rspack_import_0_default = /*#__PURE__*/ __webpack_require__.n(pintor__rspack_import_0);
@@ -6497,6 +6610,7 @@ var __webpack_modules__ = {
6497
6610
  var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_);
6498
6611
  var external_pintor_ = __webpack_require__("pintor");
6499
6612
  var external_pintor_default = /*#__PURE__*/ __webpack_require__.n(external_pintor_);
6613
+ var browser_install_outcome = __webpack_require__("./helpers/browser-install-outcome.ts");
6500
6614
  let cachedPackageJson = null;
6501
6615
  function getCliPackageJson() {
6502
6616
  if (cachedPackageJson) return cachedPackageJson;
@@ -6512,6 +6626,7 @@ var __webpack_modules__ = {
6512
6626
  }
6513
6627
  throw new Error('Extension.js CLI package.json not found.');
6514
6628
  }
6629
+ var messaging = __webpack_require__("./helpers/messaging.ts");
6515
6630
  var external_node_crypto_ = __webpack_require__("node:crypto");
6516
6631
  var external_node_crypto_default = /*#__PURE__*/ __webpack_require__.n(external_node_crypto_);
6517
6632
  var external_node_os_ = __webpack_require__("node:os");
@@ -6527,6 +6642,15 @@ var __webpack_modules__ = {
6527
6642
  return obj;
6528
6643
  }
6529
6644
  const VERSION_MAX_LENGTH = 64;
6645
+ const CATALOG_CODE = /^E_[A-Z0-9_]{1,48}$/;
6646
+ function catalogCode(value) {
6647
+ if ('string' != typeof value) return;
6648
+ return CATALOG_CODE.test(value) ? value : void 0;
6649
+ }
6650
+ function smallExitCode(value) {
6651
+ if ('number' != typeof value || !Number.isInteger(value)) return;
6652
+ return value >= 0 && value <= 255 ? value : void 0;
6653
+ }
6530
6654
  function sanitizeTag(value) {
6531
6655
  return String(value).trim().replace(/[^a-zA-Z0-9._-]/g, '').slice(0, 64);
6532
6656
  }
@@ -6731,6 +6855,10 @@ var __webpack_modules__ = {
6731
6855
  get isEnabled() {
6732
6856
  return !this.disabled;
6733
6857
  }
6858
+ disable() {
6859
+ this.disabled = true;
6860
+ this.buffer.length = 0;
6861
+ }
6734
6862
  track(event, props) {
6735
6863
  try {
6736
6864
  if (this.disabled) return;
@@ -6747,6 +6875,11 @@ var __webpack_modules__ = {
6747
6875
  };
6748
6876
  if (props.template) enforcedProps.template = sanitizeTag(props.template);
6749
6877
  if (props.source) enforcedProps.source = sanitizeTag(props.source);
6878
+ if ('started' === props.session) enforcedProps.session = 'started';
6879
+ const code = catalogCode(props.code);
6880
+ if (code) enforcedProps.code = code;
6881
+ const exitCode = smallExitCode(props.exit_code);
6882
+ if (void 0 !== exitCode) enforcedProps.exit_code = exitCode;
6750
6883
  const payload = {
6751
6884
  event,
6752
6885
  properties: {
@@ -6758,7 +6891,7 @@ var __webpack_modules__ = {
6758
6891
  distinct_id: this.anonId
6759
6892
  };
6760
6893
  this.writeAudit(payload);
6761
- if ('command_executed' === event && 'create' !== props.command && Math.random() > this.sampleRate) return;
6894
+ if ('command_executed' === event && 'create' !== props.command && 'started' !== props.session && Math.random() > this.sampleRate) return;
6762
6895
  if (!this.apiKey || !this.host) return;
6763
6896
  this.buffer.push(payload);
6764
6897
  this.sent += 1;
@@ -6862,7 +6995,7 @@ var __webpack_modules__ = {
6862
6995
  return Math.min(Math.max(n, min), max);
6863
6996
  }
6864
6997
  const TEMPLATE_CORPUS_REPO = 'extension-js/examples';
6865
- const TEMPLATE_CORPUS_REF = 'cb6a25377bd9516a1e55447a2010537019851ab2';
6998
+ const TEMPLATE_CORPUS_REF = 'd951b735ee5fbe904bba0c47d688527300d10e32';
6866
6999
  const TEMPLATE_CORPUS_SLUGS = [
6867
7000
  'action',
6868
7001
  'action-locales',
@@ -7147,14 +7280,23 @@ var __webpack_modules__ = {
7147
7280
  ].join('\n');
7148
7281
  }
7149
7282
  const KNOWN_COMMANDS = new Set([
7283
+ 'build',
7284
+ 'capabilities',
7150
7285
  'create',
7151
7286
  'dev',
7152
- 'start',
7153
- 'preview',
7154
- 'build',
7287
+ 'doctor',
7288
+ 'eval',
7289
+ 'inspect',
7155
7290
  'install',
7156
- 'uninstall',
7291
+ 'logs',
7292
+ 'open',
7293
+ 'preview',
7294
+ 'publish',
7295
+ 'reload',
7296
+ 'start',
7297
+ 'storage',
7157
7298
  'telemetry',
7299
+ 'uninstall',
7158
7300
  'unknown'
7159
7301
  ]);
7160
7302
  function detectInvokedCommand(argv) {
@@ -7187,8 +7329,17 @@ var __webpack_modules__ = {
7187
7329
  if (templateAliasFor(name)) return name;
7188
7330
  }
7189
7331
  function telemetryCommandContext(command, argv = process.argv) {
7190
- if ('create' !== command) return {};
7332
+ const install = (0, browser_install_outcome.w6)();
7333
+ const installContext = install ? {
7334
+ browser_install: install.outcome,
7335
+ browser_install_browser: install.browser,
7336
+ ...void 0 === install.seconds ? {} : {
7337
+ browser_install_seconds: install.seconds
7338
+ }
7339
+ } : {};
7340
+ if ('create' !== command) return installContext;
7191
7341
  return {
7342
+ ...installContext,
7192
7343
  template: advertisedTemplateName(readArgValue(argv, [
7193
7344
  '--template',
7194
7345
  '-t'
@@ -7209,8 +7360,12 @@ var __webpack_modules__ = {
7209
7360
  function getTelemetryConsent() {
7210
7361
  return consent;
7211
7362
  }
7363
+ function invokedCommand() {
7364
+ return invoked;
7365
+ }
7212
7366
  function setTelemetryConsent(value) {
7213
7367
  const ok = writeConsent(value);
7368
+ if ('disabled' === value) telemetry.disable();
7214
7369
  const storage = resolveTelemetryStorage();
7215
7370
  return {
7216
7371
  ok,
@@ -7218,12 +7373,29 @@ var __webpack_modules__ = {
7218
7373
  };
7219
7374
  }
7220
7375
  let tracked = false;
7376
+ let sessionStarted = false;
7221
7377
  function markTracked() {
7222
7378
  if (tracked) return false;
7223
7379
  tracked = true;
7224
7380
  return true;
7225
7381
  }
7382
+ function markCommandSessionStart(command = invoked) {
7383
+ if (tracked || sessionStarted) return;
7384
+ sessionStarted = true;
7385
+ telemetry.track('command_executed', {
7386
+ command,
7387
+ success: true,
7388
+ version: telemetry_cli_version,
7389
+ session: 'started',
7390
+ ...telemetryCommandContext(command)
7391
+ });
7392
+ telemetry.flush();
7393
+ }
7394
+ function hasTrackedSessionStart() {
7395
+ return sessionStarted;
7396
+ }
7226
7397
  function markCommandSuccess(command = invoked) {
7398
+ if (sessionStarted) return;
7227
7399
  if (!markTracked()) return;
7228
7400
  telemetry.track('command_executed', {
7229
7401
  command,
@@ -7232,12 +7404,29 @@ var __webpack_modules__ = {
7232
7404
  ...telemetryCommandContext(command)
7233
7405
  });
7234
7406
  }
7235
- function markCommandFailure(command = invoked) {
7407
+ function telemetryFailureCode(code) {
7408
+ if ('string' != typeof code) return;
7409
+ return Object.prototype.hasOwnProperty.call(messaging.Lp, code) ? code : void 0;
7410
+ }
7411
+ function telemetryExitCode(exitCode) {
7412
+ if ('number' != typeof exitCode) return;
7413
+ if (!Number.isInteger(exitCode) || exitCode < 0 || exitCode > 255) return;
7414
+ return exitCode;
7415
+ }
7416
+ function markCommandFailure(command = invoked, details = {}) {
7236
7417
  if (!markTracked()) return;
7418
+ const code = telemetryFailureCode(details.code);
7419
+ const exitCode = telemetryExitCode(details.exitCode);
7237
7420
  telemetry.track('command_failed', {
7238
7421
  command,
7239
7422
  success: false,
7240
7423
  version: telemetry_cli_version,
7424
+ ...code ? {
7425
+ code
7426
+ } : {},
7427
+ ...void 0 === exitCode ? {} : {
7428
+ exit_code: exitCode
7429
+ },
7241
7430
  ...telemetryCommandContext(command)
7242
7431
  });
7243
7432
  }
@@ -7264,7 +7453,17 @@ var __webpack_modules__ = {
7264
7453
  });
7265
7454
  }
7266
7455
  const TELEMETRY_FLUSH_TIMEOUT_MS = 500;
7456
+ function markOutcomeForExit(code) {
7457
+ try {
7458
+ if ('unknown' === invokedCommand()) return;
7459
+ if (0 === code) return void markCommandSuccess();
7460
+ markCommandFailure(void 0, {
7461
+ exitCode: code
7462
+ });
7463
+ } catch {}
7464
+ }
7267
7465
  async function exitAfterDrain(code) {
7466
+ markOutcomeForExit(code);
7268
7467
  try {
7269
7468
  await Promise.race([
7270
7469
  telemetry.flush(),
@@ -7403,7 +7602,6 @@ var __webpack_modules__ = {
7403
7602
  const bridgeSpecifier = 'extension-develop/bridge';
7404
7603
  return await import(bridgeSpecifier);
7405
7604
  }
7406
- var messaging = __webpack_require__("./helpers/messaging.ts");
7407
7605
  function getLoggingPrefix(type) {
7408
7606
  return (0, messaging.Pl)(type);
7409
7607
  }
@@ -9108,7 +9306,10 @@ Cross-browser compatibility
9108
9306
  cliVersion: getCliPackageJson().version
9109
9307
  });
9110
9308
  } catch (error) {
9111
- markCommandFailure();
9309
+ markCommandFailure(void 0, {
9310
+ code: createErrorCode(error),
9311
+ exitCode: 1
9312
+ });
9112
9313
  if (!asJson) throw error;
9113
9314
  console.log(JSON.stringify(messaging.Pr.fail('create', 'failed', {
9114
9315
  code: createErrorCode(error),
@@ -9321,6 +9522,8 @@ Cross-browser compatibility
9321
9522
  function cliGeckoBinary(opts) {
9322
9523
  return opts.geckoBinary ?? opts.firefoxBinary;
9323
9524
  }
9525
+ const BROWSER_LAUNCH_HELP_FOOTER = "\nTo stop the browser launch, use --no-browser.\nTo launch the browser without opening a tab for your extension, use --no-open.\n";
9526
+ const NO_OPEN_FLAG_DESCRIPTION = 'launch the browser but do not open a tab for your extension. To stop the browser launch itself, use --no-browser';
9324
9527
  async function resolveNoBrowser(projectPath, command) {
9325
9528
  if ('1' === process.env.EXTENSION_CLI_NO_BROWSER) return true;
9326
9529
  try {
@@ -9544,7 +9747,7 @@ Cross-browser compatibility
9544
9747
  return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 8080;
9545
9748
  }
9546
9749
  function registerDevCommand(program) {
9547
- program.command('dev').arguments('[project-path|remote-url]').usage('[project-path|remote-url] [options]').description(commandDescriptions.dev).addHelpText('after', "\nAdditional options:\n --no-browser do not launch the browser (dev server still starts)\n --no-reload emit a dev-mode dist without the content-script reload runtime; tabs need manual reload to see changes\n --wait wait for ready contract and exit; pair with --output json for machine output\n").option('--profile <path-to-file | boolean>', 'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile').option(`-b, --browser <${BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds and opens a Safari app via Xcode (macOS only; no live reload)').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').addOption(geckoBinaryOption()).addOption(firefoxBinaryAliasOption()).option('--safari-binary <path-to-binary>', 'specify the Safari binary to open after packaging (safari targets only)').option('--app-name <name>', 'override the Safari app name (safari targets only). Defaults to the manifest `name`').option('--bundle-id <reverse.dns>', 'set a user-owned Safari bundle identifier (safari targets only). Defaults to a generated dev.extensionjs.* id').option('--development-team <id>', 'sign the Safari app with an Apple Developer team id (safari targets only). Without it the build is ad-hoc signed, which Safari treats as unsigned: the extension then needs Develop \u25b8 Allow Unsigned Extensions re-ticked on every launch. A signed build is listed and stays enabled across restarts').option('--macos-only [boolean]', 'generate a macOS-only Safari Xcode project (safari targets only). Pass `false` for a universal macOS + iOS project. Defaults to `true`', parseOptionalBoolean).option('--force-regenerate', 'regenerate the Safari Xcode project even when up to date (safari targets only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `true`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--no-open', 'do not open the browser automatically (default: open)').option('--starting-url <url>', 'specify the starting URL for the browser. Defaults to `undefined`').option('--port <port>', 'specify the port to use for the development server. Defaults to `8080`').option('--host <host>', 'specify the host to bind the dev server to. Use 0.0.0.0 for Docker/devcontainers. Defaults to `127.0.0.1`').option('--public-host <host>', 'connectable host the browser (HMR + reload bridge) dials when it differs from the bind host (e.g. a remote/devcontainer). Defaults to the bind host, or 127.0.0.1 when bound to 0.0.0.0').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--wait [boolean]', 'wait for dist/extension-js/<browser>/ready.json and exit', parseOptionalBoolean).option('--wait-timeout <ms>', 'timeout in milliseconds when using --wait (default: 60000)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--wait-format <pretty|json>').hideHelp()).addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).option('--allow-control', 'enable the agent-bridge control channel for bounded act (storage/reload/open): see `extension reload|storage|open`').option('--allow-eval', 'additionally enable `extension eval` (implies --allow-control, runs arbitrary code in a context, writes a 0600 session token)').option('--parent-pid <pid>', 'exit when the given process dies. For wrappers that spawn `extension dev`, so a leaked dev server can never outlive its owner').action(async (pathOrRemoteUrl, options, command)=>{
9750
+ program.command('dev').arguments('[project-path|remote-url]').usage('[project-path|remote-url] [options]').description(commandDescriptions.dev).addHelpText('after', "\nAdditional options:\n --no-browser stop the browser launch, the dev server still starts\n --no-reload emit a dev-mode dist without the content-script reload runtime, tabs need a manual reload to see changes\n --wait wait for ready contract and exit, pair with --output json for machine output\n" + BROWSER_LAUNCH_HELP_FOOTER).option('--profile <path-to-file | boolean>', 'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile').option(`-b, --browser <${BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`. `safari` builds and opens a Safari app via Xcode (macOS only; no live reload)').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').addOption(geckoBinaryOption()).addOption(firefoxBinaryAliasOption()).option('--safari-binary <path-to-binary>', 'specify the Safari binary to open after packaging (safari targets only)').option('--app-name <name>', 'override the Safari app name (safari targets only). Defaults to the manifest `name`').option('--bundle-id <reverse.dns>', 'set a user-owned Safari bundle identifier (safari targets only). Defaults to a generated dev.extensionjs.* id').option('--development-team <id>', 'sign the Safari app with an Apple Developer team id (safari targets only). Without it the build is ad-hoc signed, which Safari treats as unsigned: the extension then needs Develop \u25b8 Allow Unsigned Extensions re-ticked on every launch. A signed build is listed and stays enabled across restarts').option('--macos-only [boolean]', 'generate a macOS-only Safari Xcode project (safari targets only). Pass `false` for a universal macOS + iOS project. Defaults to `true`', parseOptionalBoolean).option('--force-regenerate', 'regenerate the Safari Xcode project even when up to date (safari targets only)').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `true`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--no-open', NO_OPEN_FLAG_DESCRIPTION).option('--starting-url <url>', 'specify the starting URL for the browser. Defaults to `undefined`').option('--port <port>', 'specify the port to use for the development server. Defaults to `8080`').option('--host <host>', 'specify the host to bind the dev server to. Use 0.0.0.0 for Docker/devcontainers. Defaults to `127.0.0.1`').option('--public-host <host>', 'connectable host the browser (HMR + reload bridge) dials when it differs from the bind host (e.g. a remote/devcontainer). Defaults to the bind host, or 127.0.0.1 when bound to 0.0.0.0').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--wait [boolean]', 'wait for dist/extension-js/<browser>/ready.json and exit', parseOptionalBoolean).option('--wait-timeout <ms>', 'timeout in milliseconds when using --wait (default: 60000)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--wait-format <pretty|json>').hideHelp()).addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).option('--allow-control', 'enable the agent-bridge control channel for bounded act (storage/reload/open): see `extension reload|storage|open`').option('--allow-eval', 'additionally enable `extension eval` (implies --allow-control, runs arbitrary code in a context, writes a 0600 session token)').option('--parent-pid <pid>', 'exit when the given process dies. For wrappers that spawn `extension dev`, so a leaked dev server can never outlive its owner').action(async (pathOrRemoteUrl, options, command)=>{
9548
9751
  const { browser: cliBrowser, ...devOptions } = options;
9549
9752
  const browser = cliBrowser ?? await resolveConfigBrowser(pathOrRemoteUrl || process.cwd(), 'dev') ?? 'chromium';
9550
9753
  if (devOptions.debug || devOptions.author || devOptions.authorMode) {
@@ -9665,6 +9868,7 @@ Cross-browser compatibility
9665
9868
  pid: process.pid,
9666
9869
  noBrowser
9667
9870
  }));
9871
+ markCommandSessionStart('dev');
9668
9872
  const { extensionDev } = await loadExtensionDevelopModule();
9669
9873
  for (const vendor of list){
9670
9874
  const logsOption = devOptions.logs;
@@ -10209,7 +10413,7 @@ Cross-browser compatibility
10209
10413
  'Manifest file not found'
10210
10414
  ];
10211
10415
  function registerPreviewCommand(program) {
10212
- program.command('preview').arguments('[project-name]').usage('[path-to-remote-extension] [options]').description(commandDescriptions.preview).addHelpText('after', '\nAdditional option:\n --no-browser do not launch the browser\n').option('--profile <path-to-file | boolean>', 'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile').option(`--browser <${NO_SAFARI_BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').addOption(geckoBinaryOption()).addOption(firefoxBinaryAliasOption()).option('--starting-url <url>', 'specify the starting URL for the browser. Defaults to `undefined`').option('--port <port>', 'specify the port to use for the development server. Defaults to `8080`').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--output-path <dir>', 'path to an existing unpacked extension directory. Defaults to dist/<browser> when available').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).action(async (pathOrRemoteUrl, options, command)=>{
10416
+ program.command('preview').arguments('[project-name]').usage('[path-to-remote-extension] [options]').description(commandDescriptions.preview).addHelpText('after', "\nAdditional option:\n --no-browser stop the browser launch\n" + BROWSER_LAUNCH_HELP_FOOTER).option('--profile <path-to-file | boolean>', 'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile').option(`--browser <${NO_SAFARI_BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').addOption(geckoBinaryOption()).addOption(firefoxBinaryAliasOption()).option('--no-open', NO_OPEN_FLAG_DESCRIPTION).option('--starting-url <url>', 'specify the starting URL for the browser. Defaults to `undefined`').option('--port <port>', 'specify the port to use for the development server. Defaults to `8080`').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--output-path <dir>', 'path to an existing unpacked extension directory. Defaults to dist/<browser> when available').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).action(async (pathOrRemoteUrl, options, command)=>{
10213
10417
  const { browser: cliBrowser, ...previewOptions } = options;
10214
10418
  const browser = cliBrowser ?? await resolveConfigBrowser(pathOrRemoteUrl || process.cwd(), 'preview') ?? 'chromium';
10215
10419
  if (previewOptions.debug || previewOptions.author || previewOptions.authorMode) {
@@ -10250,6 +10454,7 @@ Cross-browser compatibility
10250
10454
  const isRemote = 'string' == typeof pathOrRemoteUrl && /^https?:/i.test(pathOrRemoteUrl);
10251
10455
  if (isRemote) process.env.EXTJS_LIGHT = '1';
10252
10456
  }
10457
+ markCommandSessionStart('preview');
10253
10458
  const { extensionPreview } = await loadExtensionDevelopPreviewModule();
10254
10459
  const previewed = [];
10255
10460
  for (const vendor of list){
@@ -10263,6 +10468,7 @@ Cross-browser compatibility
10263
10468
  browser: vendor,
10264
10469
  chromiumBinary: previewOptions.chromiumBinary,
10265
10470
  geckoBinary: cliGeckoBinary(previewOptions),
10471
+ noOpen: explicitCliValue(command, 'open', false === previewOptions.open),
10266
10472
  startingUrl: previewOptions.startingUrl,
10267
10473
  port: previewOptions.port,
10268
10474
  noBrowser: await resolveNoBrowser(pathOrRemoteUrl || process.cwd(), 'preview'),
@@ -10474,7 +10680,7 @@ Cross-browser compatibility
10474
10680
  return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 8080;
10475
10681
  }
10476
10682
  function registerStartCommand(program) {
10477
- program.command('start').arguments('[project-path|remote-url]').usage('[project-path|remote-url] [options]').description(commandDescriptions.start).addHelpText('after', '\nAdditional options:\n --no-browser do not launch the browser (build still runs)\n --wait wait for ready contract and exit; pair with --output json for machine output\n').option('--profile <path-to-file | boolean>', 'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile').option(`--browser <${NO_SAFARI_BROWSER_TARGETS_HELP}>`, 'specify a browser/engine to run. Defaults to `chromium`').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `true`', parseOptionalBoolean).option('--no-polyfill', 'disable the cross-browser polyfill').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').addOption(geckoBinaryOption()).addOption(firefoxBinaryAliasOption()).option('--starting-url <url>', 'specify the starting URL for the browser. Defaults to `undefined`').option('--port <port>', 'specify the port to use for the development server. Defaults to `8080`').option('--host <host>', 'specify the host to bind the dev server to. Use 0.0.0.0 for Docker/devcontainers. Defaults to `127.0.0.1`').option('--public-host <host>', 'connectable host the browser (HMR + reload bridge) dials when it differs from the bind host (e.g. a remote/devcontainer). Defaults to the bind host, or 127.0.0.1 when bound to 0.0.0.0').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--wait [boolean]', 'wait for dist/extension-js/<browser>/ready.json and exit', parseOptionalBoolean).option('--wait-timeout <ms>', 'timeout in milliseconds when using --wait (default: 60000)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--wait-format <pretty|json>').hideHelp()).addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).action(async (pathOrRemoteUrl, options, command)=>{
10683
+ program.command('start').arguments('[project-path|remote-url]').usage('[project-path|remote-url] [options]').description(commandDescriptions.start).addHelpText('after', "\nAdditional options:\n --no-browser stop the browser launch, the build still runs\n --wait wait for ready contract and exit, pair with --output json for machine output\n" + BROWSER_LAUNCH_HELP_FOOTER).option('--profile <path-to-file | boolean>', 'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile').option(`--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('--no-open', NO_OPEN_FLAG_DESCRIPTION).option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').addOption(geckoBinaryOption()).addOption(firefoxBinaryAliasOption()).option('--starting-url <url>', 'specify the starting URL for the browser. Defaults to `undefined`').option('--port <port>', 'specify the port to use for the development server. Defaults to `8080`').option('--host <host>', 'specify the host to bind the dev server to. Use 0.0.0.0 for Docker/devcontainers. Defaults to `127.0.0.1`').option('--public-host <host>', 'connectable host the browser (HMR + reload bridge) dials when it differs from the bind host (e.g. a remote/devcontainer). Defaults to the bind host, or 127.0.0.1 when bound to 0.0.0.0').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--wait [boolean]', 'wait for dist/extension-js/<browser>/ready.json and exit', parseOptionalBoolean).option('--wait-timeout <ms>', 'timeout in milliseconds when using --wait (default: 60000)').option('--output <pretty|json>', 'result format. Use json for a schema-1 envelope on stdout').addOption(new external_commander_namespaceObject.Option('--wait-format <pretty|json>').hideHelp()).addOption(new external_commander_namespaceObject.Option('--debug', 'print maintainer diagnostics alongside normal output')).addOption(new external_commander_namespaceObject.Option('--author, --author-mode', 'deprecated alias for --debug').hideHelp()).action(async (pathOrRemoteUrl, options, command)=>{
10478
10684
  const { browser: cliBrowser, ...startOptions } = options;
10479
10685
  const browser = cliBrowser ?? await resolveConfigBrowser(pathOrRemoteUrl || process.cwd(), 'start') ?? 'chromium';
10480
10686
  if (startOptions.debug || startOptions.author || startOptions.authorMode) {
@@ -10542,6 +10748,7 @@ Cross-browser compatibility
10542
10748
  pid: process.pid,
10543
10749
  noBrowser: await resolveNoBrowser(pathOrRemoteUrl || process.cwd(), 'start')
10544
10750
  }));
10751
+ markCommandSessionStart('start');
10545
10752
  const { extensionBuild } = await loadExtensionDevelopModule();
10546
10753
  for (const vendor of list){
10547
10754
  const logsOption = startOptions.logs;
@@ -10581,6 +10788,7 @@ Cross-browser compatibility
10581
10788
  port: startOptions.port,
10582
10789
  host: startOptions.host,
10583
10790
  noBrowser: false,
10791
+ noOpen: explicitCliValue(command, 'open', false === startOptions.open),
10584
10792
  extensions: parseExtensionsList(startOptions.extensions),
10585
10793
  metadataCommand: 'start',
10586
10794
  logLevel,
@@ -10667,6 +10875,63 @@ Cross-browser compatibility
10667
10875
  }
10668
10876
  return null;
10669
10877
  }
10878
+ const TERMINATION_SIGNALS = [
10879
+ 'SIGINT',
10880
+ 'SIGTERM'
10881
+ ];
10882
+ const FLUSH_DEADLINE_MS = 500;
10883
+ const SIGNAL_NUMBERS = {
10884
+ SIGINT: 2,
10885
+ SIGTERM: 15
10886
+ };
10887
+ function signalExitCode(signal) {
10888
+ return 128 + SIGNAL_NUMBERS[signal];
10889
+ }
10890
+ let handled = false;
10891
+ const ownListeners = new Set();
10892
+ let telemetry_signals_installed = false;
10893
+ async function flushTelemetryWithin(ms) {
10894
+ try {
10895
+ await Promise.race([
10896
+ telemetry.flush(),
10897
+ new Promise((resolve)=>setTimeout(resolve, ms).unref?.())
10898
+ ]);
10899
+ } catch {}
10900
+ }
10901
+ async function handleTerminationSignal(signal, deps) {
10902
+ if (handled) return;
10903
+ handled = true;
10904
+ const counted = deps.sessionStarted();
10905
+ const othersOwn = deps.othersOwnTermination();
10906
+ if (!counted && !othersOwn) deps.markInterrupted(signalExitCode(signal));
10907
+ await deps.flush();
10908
+ if (!othersOwn) await deps.exit(counted ? 0 : signalExitCode(signal));
10909
+ }
10910
+ function defaultDeps() {
10911
+ return {
10912
+ sessionStarted: hasTrackedSessionStart,
10913
+ markInterrupted: (exitCode)=>markCommandFailure(void 0, {
10914
+ code: messaging.Lp.E_INTERRUPTED,
10915
+ exitCode
10916
+ }),
10917
+ flush: ()=>flushTelemetryWithin(FLUSH_DEADLINE_MS),
10918
+ othersOwnTermination: ()=>TERMINATION_SIGNALS.some((signal)=>process.listeners(signal).some((listener)=>!ownListeners.has(listener))),
10919
+ exit: (code)=>exitAfterDrain(code)
10920
+ };
10921
+ }
10922
+ function installTelemetrySignalHandlers() {
10923
+ if (telemetry_signals_installed) return;
10924
+ if (!getTelemetryConsent().enabled) return;
10925
+ telemetry_signals_installed = true;
10926
+ for (const signal of TERMINATION_SIGNALS){
10927
+ const listener = ()=>{
10928
+ handleTerminationSignal(signal, defaultDeps());
10929
+ };
10930
+ ownListeners.add(listener);
10931
+ process.once(signal, listener);
10932
+ }
10933
+ }
10934
+ installTelemetrySignalHandlers();
10670
10935
  const index_cliPackageJson = getCliPackageJson();
10671
10936
  function developVersion() {
10672
10937
  return resolveExtensionDevelopVersion(__dirname, index_cliPackageJson.version);
@@ -10775,13 +11040,19 @@ Cross-browser compatibility
10775
11040
  if (isCommanderError(err)) {
10776
11041
  const exitCode = commanderExitCode(err);
10777
11042
  if (0 === exitCode) process.exit(0);
10778
- markCommandFailure();
11043
+ markCommandFailure(void 0, {
11044
+ code: messaging.Lp.E_ARGS,
11045
+ exitCode
11046
+ });
10779
11047
  console.error(commanderHumanError(err, commandName));
10780
11048
  if (asJson) writeStdoutFrame(commanderErrorEnvelope(err, commandName));
10781
11049
  await exitAfterDrain(exitCode);
10782
11050
  return;
10783
11051
  }
10784
- markCommandFailure();
11052
+ markCommandFailure(void 0, {
11053
+ code: telemetryFailureCode(err?.code) || messaging.Lp.E_INTERNAL,
11054
+ exitCode: 1
11055
+ });
10785
11056
  console.error(unhandledError(err));
10786
11057
  if (asJson && !isErrorFramed(err)) writeStdoutFrame(internalErrorEnvelope(err, commandName));
10787
11058
  await exitAfterDrain(1);
@@ -0,0 +1,4 @@
1
+ export type InstallableTarget = 'chrome' | 'chromium' | 'edge' | 'firefox';
2
+ export declare function canPromptForInstall(): boolean;
3
+ export declare function askToInstall(question: string): Promise<boolean>;
4
+ export declare function offerManagedInstall(target: InstallableTarget): Promise<boolean>;
@@ -140,4 +140,8 @@ export declare function requireGeckoBinaryForGeckoBased(): string;
140
140
  export declare function invalidChromiumBinaryPath(p: string): string;
141
141
  export declare function invalidGeckoBinaryPath(p: string): string;
142
142
  export declare function rdpInvalidRequestPayload(): string;
143
+ export declare function firstRunInstallOffer(browser: string): string;
144
+ export declare function firstRunInstallQuestion(browser: string): string;
145
+ export declare function firstRunInstallDeclined(browser: string): string;
146
+ export declare function firstRunInstallFailed(browser: string, reason: string): string;
143
147
  export {};
@@ -0,0 +1,10 @@
1
+ export type BrowserInstallOutcome = 'offered' | 'accepted' | 'declined' | 'failed';
2
+ interface BrowserInstallRecord {
3
+ outcome: BrowserInstallOutcome;
4
+ browser: string;
5
+ seconds?: number;
6
+ }
7
+ export declare function recordBrowserInstall(outcome: BrowserInstallOutcome, browser: string, seconds?: number): void;
8
+ export declare function readBrowserInstall(): BrowserInstallRecord | null;
9
+ export declare function resetBrowserInstall(): void;
10
+ export {};
@@ -1 +1,3 @@
1
+ export declare const BROWSER_LAUNCH_HELP_FOOTER: string;
2
+ export declare const NO_OPEN_FLAG_DESCRIPTION = "launch the browser but do not open a tab for your extension. To stop the browser launch itself, use --no-browser";
1
3
  export declare function resolveNoBrowser(projectPath: string, command: 'dev' | 'start' | 'preview'): Promise<boolean>;
@@ -1,20 +1,33 @@
1
1
  import { Telemetry, type TelemetrySource } from './telemetry';
2
- type KnownCommand = 'create' | 'dev' | 'start' | 'preview' | 'build' | 'install' | 'uninstall' | 'telemetry' | 'unknown';
2
+ type KnownCommand = 'build' | 'capabilities' | 'create' | 'dev' | 'doctor' | 'eval' | 'inspect' | 'install' | 'logs' | 'open' | 'preview' | 'publish' | 'reload' | 'start' | 'storage' | 'telemetry' | 'uninstall' | 'unknown';
3
+ export declare const KNOWN_COMMANDS: ReadonlySet<KnownCommand>;
3
4
  export declare function detectInvokedCommand(argv: string[]): KnownCommand;
4
5
  export declare function advertisedTemplateName(value: string | undefined): string | undefined;
5
6
  export declare function telemetryCommandContext(command: string, argv?: string[]): {
6
7
  template?: string;
7
8
  source?: string;
9
+ browser_install?: string;
10
+ browser_install_browser?: string;
11
+ browser_install_seconds?: number;
8
12
  };
9
13
  export declare const telemetry: Telemetry;
10
14
  export declare function getTelemetryConsent(): {
11
15
  enabled: boolean;
12
16
  source: TelemetrySource;
13
17
  };
18
+ export declare function invokedCommand(): string;
14
19
  export declare function setTelemetryConsent(value: 'enabled' | 'disabled'): {
15
20
  ok: boolean;
16
21
  path: string | null;
17
22
  };
23
+ export declare function markCommandSessionStart(command?: KnownCommand): void;
24
+ export declare function hasTrackedSessionStart(): boolean;
18
25
  export declare function markCommandSuccess(command?: KnownCommand): void;
19
- export declare function markCommandFailure(command?: KnownCommand): void;
26
+ export interface CommandFailureDetails {
27
+ code?: unknown;
28
+ exitCode?: unknown;
29
+ }
30
+ export declare function telemetryFailureCode(code: unknown): string | undefined;
31
+ export declare function telemetryExitCode(exitCode: unknown): number | undefined;
32
+ export declare function markCommandFailure(command?: KnownCommand, details?: CommandFailureDetails): void;
20
33
  export {};
@@ -0,0 +1,14 @@
1
+ export type TerminationSignal = 'SIGINT' | 'SIGTERM';
2
+ export declare const TERMINATION_SIGNALS: readonly TerminationSignal[];
3
+ export declare function signalExitCode(signal: TerminationSignal): number;
4
+ export interface TerminationSignalDeps {
5
+ sessionStarted: () => boolean;
6
+ markInterrupted: (exitCode: number) => void;
7
+ flush: () => Promise<void>;
8
+ othersOwnTermination: () => boolean;
9
+ exit: (code: number) => void | Promise<void>;
10
+ }
11
+ export declare function __resetTelemetrySignalsForTest(): void;
12
+ export declare function flushTelemetryWithin(ms: number): Promise<void>;
13
+ export declare function handleTerminationSignal(signal: TerminationSignal, deps: TerminationSignalDeps): Promise<void>;
14
+ export declare function installTelemetrySignalHandlers(): void;
@@ -5,6 +5,9 @@ export type TelemetryProps = {
5
5
  version: string;
6
6
  template?: string;
7
7
  source?: string;
8
+ session?: 'started';
9
+ code?: string;
10
+ exit_code?: number;
8
11
  };
9
12
  export type TelemetrySource = 'env' | 'flag' | 'config' | 'ci' | 'default';
10
13
  type TelemetryInit = {
@@ -53,6 +56,7 @@ export declare class Telemetry {
53
56
  private buffer;
54
57
  constructor(init: TelemetryInit);
55
58
  get isEnabled(): boolean;
59
+ disable(): void;
56
60
  track(event: TelemetryEvent, props: TelemetryProps): void;
57
61
  flush(): Promise<void>;
58
62
  shutdown(): void;
@@ -1,6 +1,6 @@
1
1
  export declare const DEFAULT_TEMPLATE = "typescript";
2
2
  export declare const BUNDLED_TEMPLATES: readonly string[];
3
- export declare const TEMPLATE_CATALOG_URL = "https://github.com/extension-js/examples/tree/cb6a25377bd9516a1e55447a2010537019851ab2/examples";
3
+ export declare const TEMPLATE_CATALOG_URL = "https://github.com/extension-js/examples/tree/d951b735ee5fbe904bba0c47d688527300d10e32/examples";
4
4
  export interface TemplateGroup {
5
5
  title: string;
6
6
  summary: string;
@@ -1,3 +1,3 @@
1
1
  export declare const TEMPLATE_CORPUS_REPO = "extension-js/examples";
2
- export declare const TEMPLATE_CORPUS_REF = "cb6a25377bd9516a1e55447a2010537019851ab2";
2
+ export declare const TEMPLATE_CORPUS_REF = "d951b735ee5fbe904bba0c47d688527300d10e32";
3
3
  export declare const TEMPLATE_CORPUS_SLUGS: readonly string[];
package/package.json CHANGED
@@ -38,7 +38,7 @@
38
38
  "extension": "./bin/extension.cjs"
39
39
  },
40
40
  "name": "extension",
41
- "version": "4.1.14",
41
+ "version": "4.1.16",
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.1.14",
110
- "extension-develop": "4.1.14",
111
- "extension-install": "4.1.14",
109
+ "extension-create": "4.1.16",
110
+ "extension-develop": "4.1.16",
111
+ "extension-install": "4.1.16",
112
112
  "commander": "^15.0.0",
113
113
  "pintor": "0.3.0",
114
114
  "semver": "^7.7.3",