extension 3.8.11 → 3.8.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -137,45 +137,6 @@ npx extension@latest dev --chromium-binary "/Applications/Google Chrome.app/Cont
137
137
  npx extension@latest dev --gecko-binary "/Applications/Firefox.app/Contents/MacOS/firefox"
138
138
  ```
139
139
 
140
- ### Managed browser binaries
141
-
142
- Use Extension.js commands to install/remove managed browser binaries:
143
-
144
- ```bash
145
- # Install into Extension.js managed cache
146
- npx extension@latest install --browser=chromium
147
- npx extension@latest install --browser=firefox
148
-
149
- # Show managed cache root
150
- npx extension@latest install --where
151
-
152
- # Show managed path for a specific browser
153
- npx extension@latest install --where --browser=firefox
154
-
155
- # Remove one browser binary
156
- npx extension@latest uninstall chromium
157
-
158
- # Remove all managed browser binaries
159
- npx extension@latest uninstall --all
160
-
161
- # Show managed cache root
162
- npx extension@latest uninstall --where
163
-
164
- # Show managed path(s) for target browser(s)
165
- npx extension@latest uninstall --where --browser=chromium
166
- ```
167
-
168
- `uninstall` only removes binaries from the Extension.js managed cache root (or `EXT_BROWSERS_CACHE_DIR` when set). It does not remove browser installations that were installed elsewhere (for example, system browsers or custom paths outside the managed cache).
169
-
170
- `edge` note for Linux: Playwright channel installs may require a privileged interactive session (`sudo` prompt). If channel install cannot proceed but a system Edge binary is already present, Extension.js will use that existing binary. Otherwise, install Edge system-wide first and then run Extension.js with `--browser=edge` (or use `chromium`).
171
-
172
- Default managed cache locations are stable and human-readable:
173
- - macOS: `~/Library/Caches/extension.js/browsers`
174
- - Linux: `~/.cache/extension.js/browsers` (or `$XDG_CACHE_HOME/extension.js/browsers`)
175
- - Windows: `%LOCALAPPDATA%\\extension.js\\browsers`
176
-
177
- If you want a custom path, set `EXT_BROWSERS_CACHE_DIR` to a clean location (for example, `EXT_BROWSERS_CACHE_DIR=/tmp/extension.js/browsers-dev`).
178
-
179
140
  ## Sponsors
180
141
 
181
142
  <div align="center">
package/dist/cli.cjs CHANGED
@@ -160,6 +160,7 @@ Source Inspection (dev command)
160
160
  - When provided without a URL, falls back to ${messages_arg('--starting-url')} or ${messages_arg('https://example.com')}
161
161
  - For ${code('extension dev')}, watch mode is enabled by default when ${code('--source')} is present
162
162
  - ${messages_arg('Note:')} ${code('extension preview')} and ${code('extension start')} do not run source inspection in run-only preview mode.
163
+ - ${messages_arg('Automation sync:')} when using ${code('extension dev --no-browser')} or ${code('extension start --no-browser')}, run ${code('extension <dev|start> --wait --browser=<browser>')} in a second process to gate on ${code('ready.json')} (add ${code('--wait-format=json')} for machine-readable output).
163
164
  - ${code('--watch-source')} ${messages_arg('[boolean]')} Re-print HTML on rebuilds or file changes
164
165
  - ${code('--source-format')} ${messages_arg('<pretty|json|ndjson>')} Output format for page HTML (defaults to ${code('--log-format')} when present)
165
166
  - ${code('--source-summary')} ${messages_arg('[boolean]')} Output a compact summary instead of full HTML
@@ -336,6 +337,7 @@ Source Inspection & Real-Time Monitoring
336
337
  - Useful for debugging content script behavior and style injection
337
338
  - Example: ${code('extension dev --source=' + messages_arg('https://example.com'))}
338
339
  - ${messages_arg('Note:')} ${code('preview/start')} run in run-only mode and do not perform source inspection.
340
+ - For machine synchronization with ${code('--no-browser')}, use ${code('extension dev --wait --browser=<browser>')} or ${code('extension start --wait --browser=<browser>')} in a second process (use ${code('--wait-format=json')} when a parser consumes stdout).
339
341
 
340
342
  Non-Destructive Testing in CI
341
343
  - Prefer ${code('EXTENSION_AUTHOR_MODE=development')} to copy local templates and avoid network.
@@ -470,6 +472,8 @@ Cross-Browser Compatibility
470
472
  'extension --ai-help --format json',
471
473
  'extension dev ./my-ext --source https://example.com --source-format json',
472
474
  'extension dev ./my-ext --logs=info --log-format=json',
475
+ 'extension dev ./my-ext --wait --browser=chromium --wait-format=json',
476
+ 'extension start ./my-ext --wait --browser=chromium --wait-format=json',
473
477
  'extension install chromium',
474
478
  'extension install --where',
475
479
  'extension uninstall --where',
@@ -905,6 +909,105 @@ Cross-Browser Compatibility
905
909
  }
906
910
  });
907
911
  }
912
+ function isHttpUrl(value) {
913
+ if (!value) return false;
914
+ return /^https?:\/\//i.test(value);
915
+ }
916
+ function resolveProjectPath(pathOrRemoteUrl) {
917
+ if (!pathOrRemoteUrl) return process.cwd();
918
+ return external_node_path_namespaceObject.isAbsolute(pathOrRemoteUrl) ? pathOrRemoteUrl : external_node_path_namespaceObject.join(process.cwd(), pathOrRemoteUrl);
919
+ }
920
+ function parseWaitTimeoutMs(value) {
921
+ const fallback = 60000;
922
+ if ('number' == typeof value && Number.isFinite(value) && value > 0) return Math.floor(value);
923
+ if ('string' == typeof value && value.trim()) {
924
+ const parsed = parseInt(value, 10);
925
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
926
+ }
927
+ return fallback;
928
+ }
929
+ function parseWaitFormat(value) {
930
+ return 'json' === value ? 'json' : 'pretty';
931
+ }
932
+ function isProcessLikelyAlive(pid) {
933
+ if ('number' != typeof pid || !Number.isInteger(pid) || pid <= 0) return true;
934
+ try {
935
+ process.kill(pid, 0);
936
+ return true;
937
+ } catch {
938
+ return false;
939
+ }
940
+ }
941
+ function isFreshContractPayload(payload, maxAgeMs) {
942
+ const candidates = [
943
+ payload.ts,
944
+ payload.compiledAt,
945
+ payload.startedAt
946
+ ];
947
+ for (const candidate of candidates){
948
+ if (!candidate) continue;
949
+ const stamp = Date.parse(candidate);
950
+ if (Number.isFinite(stamp)) return Date.now() - stamp <= maxAgeMs;
951
+ }
952
+ return false;
953
+ }
954
+ async function waitForReadyContract(options) {
955
+ const readyPath = external_node_path_namespaceObject.join(options.projectPath, 'dist', 'extension-js', options.browser, 'ready.json');
956
+ const start = Date.now();
957
+ while(Date.now() - start < options.timeoutMs){
958
+ if (external_node_fs_namespaceObject.existsSync(readyPath)) try {
959
+ const payload = JSON.parse(external_node_fs_namespaceObject.readFileSync(readyPath, 'utf8'));
960
+ const isLive = isProcessLikelyAlive(payload.pid);
961
+ if (payload.command !== options.command) {
962
+ await new Promise((resolve)=>setTimeout(resolve, 250));
963
+ continue;
964
+ }
965
+ if (!isLive) {
966
+ if ('start' !== options.command || !isFreshContractPayload(payload, options.timeoutMs)) {
967
+ await new Promise((resolve)=>setTimeout(resolve, 250));
968
+ continue;
969
+ }
970
+ }
971
+ if ('ready' === payload.status) return payload;
972
+ if ('error' === payload.status) {
973
+ const detail = payload.message || payload.errors?.[0] || 'unknown error';
974
+ throw new Error(String(detail));
975
+ }
976
+ } catch (error) {
977
+ if (error instanceof Error) throw error;
978
+ throw new Error(String(error));
979
+ }
980
+ await new Promise((resolve)=>setTimeout(resolve, 250));
981
+ }
982
+ throw new Error(`Timed out waiting for ready contract at ${readyPath} (${options.timeoutMs} ms)`);
983
+ }
984
+ async function runWaitMode(options) {
985
+ if (isHttpUrl(options.pathOrRemoteUrl)) throw new Error('--wait requires a local project path (remote URLs are not supported)');
986
+ const projectPath = resolveProjectPath(options.pathOrRemoteUrl);
987
+ const timeoutMs = parseWaitTimeoutMs(options.waitTimeout);
988
+ const format = parseWaitFormat(options.waitFormat);
989
+ const results = [];
990
+ for (const browser of options.browsers){
991
+ const payload = await waitForReadyContract({
992
+ command: options.command,
993
+ projectPath,
994
+ browser,
995
+ timeoutMs
996
+ });
997
+ results.push({
998
+ ...payload,
999
+ browser
1000
+ });
1001
+ }
1002
+ return {
1003
+ projectPath,
1004
+ command: options.command,
1005
+ timeoutMs,
1006
+ format,
1007
+ browsers: options.browsers,
1008
+ results
1009
+ };
1010
+ }
908
1011
  function normalizeSourceOption(source, startingUrl) {
909
1012
  if (!source) return;
910
1013
  const hasExplicitSourceString = 'string' == typeof source && 'true' !== String(source).trim().toLowerCase();
@@ -1012,7 +1115,7 @@ Cross-Browser Compatibility
1012
1115
  }
1013
1116
  const dev_require = (0, external_module_namespaceObject.createRequire)(__rslib_import_meta_url__);
1014
1117
  function registerDevCommand(program, telemetry) {
1015
- program.command('dev').arguments('[project-path|remote-url]').usage('dev [project-path|remote-url] [options]').description(commandDescriptions.dev).addHelpText('after', '\nAdditional option:\n --no-browser do not launch the browser (dev server still starts)\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('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `false`').option('--no-open', 'do not open the browser automatically (default: open)').option('--starting-url <url>', 'specify the starting URL for the browser. Defaults to `undefined`').option('--port <port>', 'specify the port to use for the development server. Defaults to `8080`').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--source [url]', "[experimental] opens the provided URL in Chrome and prints the full, live HTML of the page after content scripts are injected").option('--watch-source [boolean]', '[experimental] re-print HTML on rebuilds or file changes (defaults to true when --source is present)', parseOptionalBoolean).option('--source-format <pretty|json|ndjson>', '[experimental] output format for source HTML (defaults to --log-format when present, otherwise JSON when --source is used)').option('--source-summary [boolean]', '[experimental] output a compact summary instead of full HTML', parseOptionalBoolean).option('--source-meta [boolean]', '[experimental] output page metadata (readyState, viewport, frames)', parseOptionalBoolean).option('--source-probe <selectors>', '[experimental] comma-separated CSS selectors to probe').option('--source-tree <off|root-only>', '[experimental] output a compact extension root tree').option('--source-console [boolean]', '[experimental] output console summary (best-effort)', parseOptionalBoolean).option('--source-dom [boolean]', '[experimental] output DOM snapshots and diffs (default: true when watch is enabled)', parseOptionalBoolean).option('--source-max-bytes <bytes>', '[experimental] limit HTML output size in bytes (0 disables truncation)').option('--source-redact <off|safe|strict>', '[experimental] redact sensitive content in HTML output (default: safe for JSON/NDJSON)').option('--source-include-shadow <off|open-only|all>', '[experimental] control Shadow DOM inclusion in HTML output (default: open-only)').option('--source-diff [boolean]', '[experimental] include diff metadata on watch updates (default: true when watch is enabled)', parseOptionalBoolean).option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--author, --author-mode', '[internal] enable maintainer diagnostics (does not affect user runtime logs)').action(async function(pathOrRemoteUrl, { browser = 'chromium', ...devOptions }) {
1118
+ program.command('dev').arguments('[project-path|remote-url]').usage('dev [project-path|remote-url] [options]').description(commandDescriptions.dev).addHelpText('after', '\nAdditional options:\n --no-browser do not launch the browser (dev server still starts)\n --wait wait for ready contract and exit\n --wait-format pretty|json output for wait mode\n').option('--profile <path-to-file | boolean>', 'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile').option('--browser <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based>', 'specify a browser/engine to run. Defaults to `chromium`').option('--chromium-binary <path-to-binary>', 'specify a path to the Chromium binary. This option overrides the --browser setting. Defaults to the system default').option('--gecko-binary, --firefox-binary <path-to-binary>', 'specify a path to the Gecko binary. This option overrides the --browser setting. Defaults to the system default').option('--polyfill [boolean]', 'whether or not to apply the cross-browser polyfill. Defaults to `false`').option('--no-open', 'do not open the browser automatically (default: open)').option('--starting-url <url>', 'specify the starting URL for the browser. Defaults to `undefined`').option('--port <port>', 'specify the port to use for the development server. Defaults to `8080`').option('--log-context <list>', '[experimental] comma-separated contexts to include (background,content,page,sidebar,popup,options,devtools). Use `all` to include all contexts (default)').option('--logs <off|error|warn|info|debug|trace|all>', '[experimental] minimum centralized logger level to display in terminal (default: off)').option('--log-format <pretty|json|ndjson>', '[experimental] output format for logger events. Defaults to `pretty`').option('--no-log-timestamps', 'disable ISO timestamps in pretty output').option('--no-log-color', 'disable color in pretty output').option('--log-url <pattern>', '[experimental] only show logs where event.url matches this substring or regex (/re/i)').option('--log-tab <id>', 'only show logs for a specific tabId (number)').option('--source [url]', "[experimental] opens the provided URL in Chrome and prints the full, live HTML of the page after content scripts are injected").option('--watch-source [boolean]', '[experimental] re-print HTML on rebuilds or file changes (defaults to true when --source is present)', parseOptionalBoolean).option('--source-format <pretty|json|ndjson>', '[experimental] output format for source HTML (defaults to --log-format when present, otherwise JSON when --source is used)').option('--source-summary [boolean]', '[experimental] output a compact summary instead of full HTML', parseOptionalBoolean).option('--source-meta [boolean]', '[experimental] output page metadata (readyState, viewport, frames)', parseOptionalBoolean).option('--source-probe <selectors>', '[experimental] comma-separated CSS selectors to probe').option('--source-tree <off|root-only>', '[experimental] output a compact extension root tree').option('--source-console [boolean]', '[experimental] output console summary (best-effort)', parseOptionalBoolean).option('--source-dom [boolean]', '[experimental] output DOM snapshots and diffs (default: true when watch is enabled)', parseOptionalBoolean).option('--source-max-bytes <bytes>', '[experimental] limit HTML output size in bytes (0 disables truncation)').option('--source-redact <off|safe|strict>', '[experimental] redact sensitive content in HTML output (default: safe for JSON/NDJSON)').option('--source-include-shadow <off|open-only|all>', '[experimental] control Shadow DOM inclusion in HTML output (default: open-only)').option('--source-diff [boolean]', '[experimental] include diff metadata on watch updates (default: true when watch is enabled)', parseOptionalBoolean).option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[internal] install project dependencies when missing', parseOptionalBoolean).option('--wait [boolean]', 'wait for dist/extension-js/<browser>/ready.json and exit', parseOptionalBoolean).option('--wait-timeout <ms>', 'timeout in milliseconds when using --wait (default: 60000)').option('--wait-format <pretty|json>', 'output format for --wait results (default: pretty)').option('--author, --author-mode', '[internal] enable maintainer diagnostics (does not affect user runtime logs)').action(async function(pathOrRemoteUrl, { browser = 'chromium', ...devOptions }) {
1016
1119
  if (devOptions.author || devOptions['authorMode']) {
1017
1120
  process.env.EXTENSION_AUTHOR_MODE = 'true';
1018
1121
  if (!process.env.EXTENSION_VERBOSE) process.env.EXTENSION_VERBOSE = '1';
@@ -1030,6 +1133,29 @@ Cross-Browser Compatibility
1030
1133
  validateVendorsOrExit(list, (invalid, supported)=>{
1031
1134
  console.error(unsupportedBrowserFlag(invalid, supported));
1032
1135
  });
1136
+ if (devOptions.wait) {
1137
+ const waitResult = await runWaitMode({
1138
+ command: 'dev',
1139
+ pathOrRemoteUrl,
1140
+ browsers: list,
1141
+ waitTimeout: devOptions.waitTimeout,
1142
+ waitFormat: devOptions.waitFormat
1143
+ });
1144
+ if ('json' === waitResult.format) console.log(JSON.stringify({
1145
+ ok: true,
1146
+ mode: 'wait',
1147
+ command: 'dev',
1148
+ browsers: waitResult.browsers,
1149
+ results: waitResult.results
1150
+ }));
1151
+ telemetry.track('cli_command_finish', {
1152
+ command: 'dev',
1153
+ duration_ms: Date.now() - cmdStart,
1154
+ success: true,
1155
+ exit_code: 0
1156
+ });
1157
+ return;
1158
+ }
1033
1159
  const normalizedSource = normalizeSourceOption(devOptions.source, devOptions.startingUrl);
1034
1160
  if (normalizedSource) {
1035
1161
  devOptions.source = normalizedSource;
@@ -1111,7 +1237,7 @@ Cross-Browser Compatibility
1111
1237
  }
1112
1238
  const start_require = (0, external_module_namespaceObject.createRequire)(__rslib_import_meta_url__);
1113
1239
  function registerStartCommand(program, telemetry) {
1114
- program.command('start').arguments('[project-path|remote-url]').usage('start [project-path|remote-url] [options]').description(commandDescriptions.start).addHelpText('after', '\nAdditional option:\n --no-browser do not launch the browser (build still runs)\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`').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('--source [url]', "[experimental] opens the provided URL in Chrome and prints the full, live HTML of the page after content scripts are injected").option('--watch-source [boolean]', '[experimental] re-print HTML on rebuilds or file changes', parseOptionalBoolean).option('--source-format <pretty|json|ndjson>', '[experimental] output format for source HTML (defaults to --log-format when present, otherwise JSON when --source is used)').option('--source-summary [boolean]', '[experimental] output a compact summary instead of full HTML', parseOptionalBoolean).option('--source-meta [boolean]', '[experimental] output page metadata (readyState, viewport, frames)', parseOptionalBoolean).option('--source-probe <selectors>', '[experimental] comma-separated CSS selectors to probe').option('--source-tree <off|root-only>', '[experimental] output a compact extension root tree').option('--source-console [boolean]', '[experimental] output console summary (best-effort)', parseOptionalBoolean).option('--source-dom [boolean]', '[experimental] output DOM snapshots and diffs (default: true when watch is enabled)', parseOptionalBoolean).option('--source-max-bytes <bytes>', '[experimental] limit HTML output size in bytes (0 disables truncation)').option('--source-redact <off|safe|strict>', '[experimental] redact sensitive content in HTML output (default: safe for JSON/NDJSON)').option('--source-include-shadow <off|open-only|all>', '[experimental] control Shadow DOM inclusion in HTML output (default: open-only)').option('--source-diff [boolean]', '[experimental] include diff metadata on watch updates (default: true when watch is enabled)', parseOptionalBoolean).option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[experimental] install project dependencies when missing', parseOptionalBoolean).option('--author, --author-mode', '[experimental] enable maintainer diagnostics (does not affect user runtime logs)').action(async function(pathOrRemoteUrl, { browser = 'chromium', ...startOptions }) {
1240
+ program.command('start').arguments('[project-path|remote-url]').usage('start [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\n --wait-format pretty|json output for wait mode\n').option('--profile <path-to-file | boolean>', 'what path to use for the browser profile. A boolean value of false sets the profile to the default user profile. Defaults to a fresh profile').option('--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`').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('--source [url]', "[experimental] opens the provided URL in Chrome and prints the full, live HTML of the page after content scripts are injected").option('--watch-source [boolean]', '[experimental] re-print HTML on rebuilds or file changes', parseOptionalBoolean).option('--source-format <pretty|json|ndjson>', '[experimental] output format for source HTML (defaults to --log-format when present, otherwise JSON when --source is used)').option('--source-summary [boolean]', '[experimental] output a compact summary instead of full HTML', parseOptionalBoolean).option('--source-meta [boolean]', '[experimental] output page metadata (readyState, viewport, frames)', parseOptionalBoolean).option('--source-probe <selectors>', '[experimental] comma-separated CSS selectors to probe').option('--source-tree <off|root-only>', '[experimental] output a compact extension root tree').option('--source-console [boolean]', '[experimental] output console summary (best-effort)', parseOptionalBoolean).option('--source-dom [boolean]', '[experimental] output DOM snapshots and diffs (default: true when watch is enabled)', parseOptionalBoolean).option('--source-max-bytes <bytes>', '[experimental] limit HTML output size in bytes (0 disables truncation)').option('--source-redact <off|safe|strict>', '[experimental] redact sensitive content in HTML output (default: safe for JSON/NDJSON)').option('--source-include-shadow <off|open-only|all>', '[experimental] control Shadow DOM inclusion in HTML output (default: open-only)').option('--source-diff [boolean]', '[experimental] include diff metadata on watch updates (default: true when watch is enabled)', parseOptionalBoolean).option('--extensions <list>', 'comma-separated list of companion extensions or store URLs to load').option('--install [boolean]', '[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('--wait-format <pretty|json>', 'output format for --wait results (default: pretty)').option('--author, --author-mode', '[experimental] enable maintainer diagnostics (does not affect user runtime logs)').action(async function(pathOrRemoteUrl, { browser = 'chromium', ...startOptions }) {
1115
1241
  const hasSourceInspectionFlags = void 0 !== startOptions.source || void 0 !== startOptions.watchSource || void 0 !== startOptions.sourceFormat || void 0 !== startOptions.sourceSummary || void 0 !== startOptions.sourceMeta || void 0 !== startOptions.sourceProbe || void 0 !== startOptions.sourceTree || void 0 !== startOptions.sourceConsole || void 0 !== startOptions.sourceDom || void 0 !== startOptions.sourceMaxBytes || void 0 !== startOptions.sourceRedact || void 0 !== startOptions.sourceIncludeShadow || void 0 !== startOptions.sourceDiff;
1116
1242
  if (hasSourceInspectionFlags) {
1117
1243
  console.error(sourceInspectionNotSupported('start'));
@@ -1131,6 +1257,29 @@ Cross-Browser Compatibility
1131
1257
  validateVendorsOrExit(list, (invalid, supported)=>{
1132
1258
  console.error(unsupportedBrowserFlag(invalid, supported));
1133
1259
  });
1260
+ if (startOptions.wait) {
1261
+ const waitResult = await runWaitMode({
1262
+ command: 'start',
1263
+ pathOrRemoteUrl,
1264
+ browsers: list,
1265
+ waitTimeout: startOptions.waitTimeout,
1266
+ waitFormat: startOptions.waitFormat
1267
+ });
1268
+ if ('json' === waitResult.format) console.log(JSON.stringify({
1269
+ ok: true,
1270
+ mode: 'wait',
1271
+ command: 'start',
1272
+ browsers: waitResult.browsers,
1273
+ results: waitResult.results
1274
+ }));
1275
+ telemetry.track('cli_command_finish', {
1276
+ command: 'start',
1277
+ duration_ms: Date.now() - cmdStart,
1278
+ success: true,
1279
+ exit_code: 0
1280
+ });
1281
+ return;
1282
+ }
1134
1283
  const normalizedSource = normalizeSourceOption(startOptions.source, startOptions.startingUrl);
1135
1284
  if (normalizedSource) startOptions.source = normalizedSource;
1136
1285
  const sourceEnabled = Boolean(startOptions.source || startOptions.watchSource);
@@ -0,0 +1,37 @@
1
+ export type WaitFormat = 'pretty' | 'json';
2
+ export type WaitCommand = 'dev' | 'start';
3
+ export type ReadyContractPayload = {
4
+ status?: string;
5
+ message?: string;
6
+ errors?: string[];
7
+ code?: string;
8
+ command?: string;
9
+ browser?: string;
10
+ runId?: string;
11
+ startedAt?: string;
12
+ distPath?: string;
13
+ manifestPath?: string;
14
+ port?: number | null;
15
+ pid?: number;
16
+ ts?: string;
17
+ compiledAt?: string | null;
18
+ };
19
+ export type RunWaitModeOptions = {
20
+ command: WaitCommand;
21
+ pathOrRemoteUrl?: string;
22
+ browsers: string[];
23
+ waitTimeout?: string | number;
24
+ waitFormat?: string;
25
+ };
26
+ export type RunWaitModeResult = {
27
+ projectPath: string;
28
+ command: WaitCommand;
29
+ timeoutMs: number;
30
+ format: WaitFormat;
31
+ browsers: string[];
32
+ results: Array<ReadyContractPayload & {
33
+ browser: string;
34
+ }>;
35
+ };
36
+ export declare function runWaitMode(options: RunWaitModeOptions): Promise<RunWaitModeResult>;
37
+ export declare function runDevWaitMode(options: Omit<RunWaitModeOptions, 'command'>): Promise<RunWaitModeResult>;
package/package.json CHANGED
@@ -33,7 +33,7 @@
33
33
  "extension": "./bin/extension.cjs"
34
34
  },
35
35
  "name": "extension",
36
- "version": "3.8.11",
36
+ "version": "3.8.13",
37
37
  "description": "Create cross-browser extensions with no build configuration.",
38
38
  "homepage": "https://extension.js.org/",
39
39
  "bugs": {
@@ -90,9 +90,9 @@
90
90
  "@types/chrome": "^0.1.33",
91
91
  "@types/node": "^25.2.0",
92
92
  "@types/webextension-polyfill": "0.12.4",
93
- "extension-create": "3.8.11",
94
- "extension-develop": "3.8.11",
95
- "extension-install": "3.8.11",
93
+ "extension-create": "3.8.13",
94
+ "extension-develop": "3.8.13",
95
+ "extension-install": "3.8.13",
96
96
  "commander": "^14.0.3",
97
97
  "pintor": "0.3.0",
98
98
  "semver": "^7.7.3",