extension 3.7.0 → 3.8.1

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,6 +137,45 @@ 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
+
140
179
  ## Sponsors
141
180
 
142
181
  <div align="center">
@@ -13,6 +13,8 @@ export declare const commandDescriptions: {
13
13
  readonly start: "Builds and starts the extension in production mode";
14
14
  readonly preview: "Previews the extension in production mode without building";
15
15
  readonly build: "Builds the extension for packaging/distribution";
16
+ readonly install: "Installs a managed browser binary into Extension.js cache";
17
+ readonly uninstall: "Removes managed browser binaries from Extension.js cache";
16
18
  };
17
19
  export declare function unhandledError(err: unknown): string;
18
20
  export declare function updateFailed(err: any): string;
@@ -33,7 +35,7 @@ export declare function programAIHelp(): string;
33
35
  export type ProgramAIHelpJSON = {
34
36
  version: string;
35
37
  commands: Array<{
36
- name: 'create' | 'dev' | 'start' | 'preview' | 'build';
38
+ name: 'create' | 'dev' | 'start' | 'preview' | 'build' | 'install' | 'uninstall';
37
39
  summary: string;
38
40
  supportsSourceInspection: boolean;
39
41
  }>;
package/dist/cli.cjs CHANGED
@@ -85,7 +85,9 @@ var __webpack_exports__ = {};
85
85
  dev: 'Starts the development server with hot reloading',
86
86
  start: 'Builds and starts the extension in production mode',
87
87
  preview: 'Previews the extension in production mode without building',
88
- build: 'Builds the extension for packaging/distribution'
88
+ build: 'Builds the extension for packaging/distribution',
89
+ install: 'Installs a managed browser binary into Extension.js cache',
90
+ uninstall: 'Removes managed browser binaries from Extension.js cache'
89
91
  };
90
92
  function unhandledError(err) {
91
93
  const message = err instanceof Error ? err.stack || err.message : 'string' == typeof err ? err : fmt.truncate(err);
@@ -129,6 +131,18 @@ Available Commands
129
131
  - ${code('extension build ' + messages_arg('[project-path|remote-url]'))}
130
132
  ${commandDescriptions.build}
131
133
 
134
+ - ${code('extension install ' + messages_arg('--browser <chrome|chromium|edge|firefox|chromium-based|gecko-based|firefox-based|all>'))}
135
+ ${commandDescriptions.install}
136
+
137
+ - ${code('extension install --where')}
138
+ Prints the managed browser cache root (or browser install path(s) when --browser is provided)
139
+
140
+ - ${code('extension uninstall ' + messages_arg('<chrome|chromium|edge|firefox> | --all'))}
141
+ ${commandDescriptions.uninstall}
142
+
143
+ - ${code('extension uninstall --where')}
144
+ Prints the managed browser cache root (or browser install path(s) when --browser/--all is provided)
145
+
132
146
  Common Options
133
147
  - ${code('--browser')} ${messages_arg('<chrome|edge|firefox|chromium|chromium-based|gecko-based|firefox-based>')} Target browser/engine (default: chromium)
134
148
  - ${code('--profile')} ${messages_arg('<path|boolean>')} Browser profile configuration
@@ -376,6 +390,16 @@ Cross-Browser Compatibility
376
390
  name: 'build',
377
391
  summary: commandDescriptions.build,
378
392
  supportsSourceInspection: false
393
+ },
394
+ {
395
+ name: 'install',
396
+ summary: commandDescriptions.install,
397
+ supportsSourceInspection: false
398
+ },
399
+ {
400
+ name: 'uninstall',
401
+ summary: commandDescriptions.uninstall,
402
+ supportsSourceInspection: false
379
403
  }
380
404
  ],
381
405
  globalOptions: [
@@ -406,7 +430,9 @@ Cross-Browser Compatibility
406
430
  'start',
407
431
  'preview',
408
432
  'build',
409
- 'create'
433
+ 'create',
434
+ 'install',
435
+ 'uninstall'
410
436
  ],
411
437
  notes: [
412
438
  '--source supports URL fallback to --starting-url or https://example.com',
@@ -443,7 +469,11 @@ Cross-Browser Compatibility
443
469
  'extension --ai-help',
444
470
  'extension --ai-help --format json',
445
471
  'extension dev ./my-ext --source https://example.com --source-format json',
446
- 'extension dev ./my-ext --logs=info --log-format=json'
472
+ 'extension dev ./my-ext --logs=info --log-format=json',
473
+ 'extension install chromium',
474
+ 'extension install --where',
475
+ 'extension uninstall --where',
476
+ 'extension uninstall --all'
447
477
  ]
448
478
  };
449
479
  }
@@ -1316,6 +1346,125 @@ Cross-Browser Compatibility
1316
1346
  });
1317
1347
  });
1318
1348
  }
1349
+ function resolveManagedBrowsersCacheRoot() {
1350
+ const explicit = String(process.env.EXT_BROWSERS_CACHE_DIR || '').trim();
1351
+ if (explicit) return external_node_path_default().resolve(explicit);
1352
+ const isWin = 'win32' === process.platform;
1353
+ const isMac = 'darwin' === process.platform;
1354
+ if (isWin) {
1355
+ const local = String(process.env.LOCALAPPDATA || '').trim();
1356
+ if (local) return external_node_path_default().join(local, 'extension.js', 'browsers');
1357
+ const userProfile = String(process.env.USERPROFILE || '').trim();
1358
+ if (userProfile) return external_node_path_default().join(userProfile, 'AppData', 'Local', 'extension.js', 'browsers');
1359
+ return external_node_path_default().resolve(process.cwd(), '.cache', 'extension.js', 'browsers');
1360
+ }
1361
+ if (isMac) {
1362
+ const home = String(process.env.HOME || '').trim();
1363
+ if (home) return external_node_path_default().join(home, 'Library', 'Caches', 'extension.js', 'browsers');
1364
+ return external_node_path_default().resolve(process.cwd(), '.cache', 'extension.js', 'browsers');
1365
+ }
1366
+ const xdg = String(process.env.XDG_CACHE_HOME || '').trim();
1367
+ if (xdg) return external_node_path_default().join(xdg, 'extension.js', 'browsers');
1368
+ const home = String(process.env.HOME || '').trim();
1369
+ if (home) return external_node_path_default().join(home, '.cache', 'extension.js', 'browsers');
1370
+ return external_node_path_default().resolve(process.cwd(), '.cache', 'extension.js', 'browsers');
1371
+ }
1372
+ function normalizeInstallVendor(vendor) {
1373
+ const value = String(vendor).trim().toLowerCase();
1374
+ if ('chromium-based' === value) return 'chromium';
1375
+ if ('gecko-based' === value || 'firefox-based' === value) return 'firefox';
1376
+ if ('chrome' === value || 'chromium' === value || 'edge' === value || 'firefox' === value) return value;
1377
+ return 'chromium';
1378
+ }
1379
+ function registerInstallCommand(program, telemetry) {
1380
+ program.command('install').arguments('[browser-name]').usage('install [browser-name] [--browser <name>] [--where]').description(commandDescriptions.install).option('--browser <chrome | chromium | edge | firefox | chromium-based | gecko-based | firefox-based | all>', 'specify browser(s) to install. Supports comma-separated values and `all`.').option('--where', 'print the resolved managed browser cache root').action(async function(browserArg, options) {
1381
+ const startedAt = Date.now();
1382
+ const selectedBrowser = options.browser || browserArg || 'chromium';
1383
+ const browserList = vendors(selectedBrowser);
1384
+ validateVendorsOrExit(browserList, (invalid, supported)=>{
1385
+ console.error(unsupportedBrowserFlag(invalid, supported));
1386
+ });
1387
+ telemetry.track('cli_command_start', {
1388
+ command: 'install',
1389
+ vendors: browserList,
1390
+ where: Boolean(options.where)
1391
+ });
1392
+ try {
1393
+ if (options.where) {
1394
+ const root = resolveManagedBrowsersCacheRoot();
1395
+ if (options.browser || browserArg) for (const browser of browserList)console.log(external_node_path_default().join(root, normalizeInstallVendor(browser)));
1396
+ else console.log(root);
1397
+ } else {
1398
+ const { extensionInstall } = await import("extension-install");
1399
+ for (const browser of browserList)await extensionInstall({
1400
+ browser
1401
+ });
1402
+ }
1403
+ telemetry.track('cli_command_finish', {
1404
+ command: 'install',
1405
+ duration_ms: Date.now() - startedAt,
1406
+ success: true,
1407
+ exit_code: 0
1408
+ });
1409
+ } catch (err) {
1410
+ telemetry.track('cli_command_finish', {
1411
+ command: 'install',
1412
+ duration_ms: Date.now() - startedAt,
1413
+ success: false,
1414
+ exit_code: 1
1415
+ });
1416
+ throw err;
1417
+ }
1418
+ });
1419
+ program.command('uninstall').usage('uninstall <browser-name> | uninstall --all | uninstall --where').description(commandDescriptions.uninstall).option('--browser <browser-name>', 'browser to uninstall').option('--all', 'remove all managed browser binaries').option('--where', 'print the resolved managed browser cache root').argument('[browser-name]').action(async function(browserArg, { browser, all, where }) {
1420
+ const startedAt = Date.now();
1421
+ const target = browserArg || browser;
1422
+ telemetry.track('cli_command_start', {
1423
+ command: 'uninstall',
1424
+ browser: target,
1425
+ all: Boolean(all),
1426
+ where: Boolean(where)
1427
+ });
1428
+ try {
1429
+ if (where) {
1430
+ const root = resolveManagedBrowsersCacheRoot();
1431
+ if (all) for (const browser of [
1432
+ 'chrome',
1433
+ 'chromium',
1434
+ 'edge',
1435
+ 'firefox'
1436
+ ])console.log(external_node_path_default().join(root, browser));
1437
+ else if (target) {
1438
+ const list = vendors(target);
1439
+ validateVendorsOrExit(list, (invalid, supported)=>{
1440
+ console.error(unsupportedBrowserFlag(invalid, supported));
1441
+ });
1442
+ for (const browser of list)console.log(external_node_path_default().join(root, normalizeInstallVendor(browser)));
1443
+ } else console.log(root);
1444
+ } else {
1445
+ const { extensionUninstall } = await import("extension-install");
1446
+ await extensionUninstall({
1447
+ browser: target,
1448
+ all
1449
+ });
1450
+ }
1451
+ telemetry.track('cli_command_finish', {
1452
+ command: 'uninstall',
1453
+ duration_ms: Date.now() - startedAt,
1454
+ success: true,
1455
+ exit_code: 0
1456
+ });
1457
+ } catch (err) {
1458
+ telemetry.track('cli_command_finish', {
1459
+ command: 'uninstall',
1460
+ duration_ms: Date.now() - startedAt,
1461
+ success: false,
1462
+ exit_code: 1
1463
+ });
1464
+ throw err;
1465
+ }
1466
+ });
1467
+ }
1319
1468
  const cliPackageJson = getCliPackageJson();
1320
1469
  function developVersion() {
1321
1470
  try {
@@ -1345,6 +1494,7 @@ Cross-Browser Compatibility
1345
1494
  registerStartCommand(extensionJs, telemetry_cli_telemetry);
1346
1495
  registerPreviewCommand(extensionJs, telemetry_cli_telemetry);
1347
1496
  registerBuildCommand(extensionJs, telemetry_cli_telemetry);
1497
+ registerInstallCommand(extensionJs, telemetry_cli_telemetry);
1348
1498
  extensionJs.on('option:ai-help', function() {
1349
1499
  const format = resolveAIHelpFormatFromArgv(process.argv).trim().toLowerCase();
1350
1500
  if ('json' === format) {
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerInstallCommand(program: Command, telemetry: any): void;
package/package.json CHANGED
@@ -33,7 +33,7 @@
33
33
  "extension": "./bin/extension.cjs"
34
34
  },
35
35
  "name": "extension",
36
- "version": "3.7.0",
36
+ "version": "3.8.1",
37
37
  "description": "Create cross-browser extensions with no build configuration.",
38
38
  "homepage": "https://extension.js.org/",
39
39
  "bugs": {
@@ -90,8 +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.7.0",
94
- "extension-develop": "^3.7.0",
93
+ "extension-create": "3.8.1",
94
+ "extension-develop": "3.8.1",
95
+ "extension-install": "3.8.1",
95
96
  "commander": "^14.0.3",
96
97
  "pintor": "0.3.0",
97
98
  "semver": "^7.7.3",