extension 4.1.19 → 4.1.21

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.
Files changed (30) hide show
  1. package/README.md +23 -20
  2. package/bin/extension.cjs +2 -0
  3. package/dist/84.cjs +2 -1
  4. package/dist/browsers.cjs +179 -95
  5. package/dist/cli.cjs +454 -238
  6. package/dist/extension/browsers/__spec__/safari-fake-tools.d.ts +27 -0
  7. package/dist/extension/browsers/browsers-lib/constants.d.ts +0 -2
  8. package/dist/extension/browsers/browsers-lib/messages.d.ts +2 -1
  9. package/dist/extension/browsers/browsers-lib/resolve-profile.d.ts +0 -35
  10. package/dist/extension/browsers/browsers-lib/write-json-atomic.d.ts +1 -0
  11. package/dist/extension/browsers/browsers-types.d.ts +0 -89
  12. package/dist/extension/browsers/index.d.ts +1 -29
  13. package/dist/extension/browsers/run-chromium/chromium-launch/extension-output-path.d.ts +0 -6
  14. package/dist/extension/browsers/run-chromium/chromium-launch/index.d.ts +0 -9
  15. package/dist/extension/browsers/run-chromium/chromium-types.d.ts +0 -11
  16. package/dist/extension/browsers/run-safari/safari-launch/index.d.ts +2 -7
  17. package/dist/extension/browsers/run-safari/safari-launch/tools.d.ts +17 -0
  18. package/dist/extension/browsers/run-safari/safari-packager.d.ts +2 -16
  19. package/dist/extension/browsers/run-safari/safari-types.d.ts +2 -1
  20. package/dist/extension/commands/act.d.ts +0 -6
  21. package/dist/extension/commands/doctor.d.ts +0 -7
  22. package/dist/extension/commands/publish.d.ts +0 -6
  23. package/dist/extension/config-types.d.ts +5 -2
  24. package/dist/extension/helpers/cli-explicit.d.ts +0 -15
  25. package/dist/extension/helpers/messages.d.ts +7 -0
  26. package/dist/extension/helpers/node-version-guard.d.ts +7 -2
  27. package/dist/extension/helpers/template-catalog.d.ts +1 -1
  28. package/dist/extension/helpers/template-corpus.generated.d.ts +1 -1
  29. package/dist/extension/helpers/vendors.d.ts +1 -0
  30. package/package.json +4 -6
package/README.md CHANGED
@@ -6,12 +6,10 @@
6
6
  [action-url]: https://github.com/extension-js/extension.js/actions
7
7
  [discord-image]: https://img.shields.io/discord/1253608412890271755?label=Discord&logo=discord&style=flat&color=0971fe
8
8
  [discord-url]: https://discord.gg/v9h2RgeTSN
9
- [snyk-image]: https://snyk.io/test/github/extension-js/extension.js/badge.svg?color=0971fe
10
- [snyk-url]: https://snyk.io/test/github/extension-js/extension.js
11
- [scorecard-image]: https://img.shields.io/ossf-scorecard/github.com/extension-js/extension.js?label=OpenSSF%20Scorecard&style=flat&color=0971fe
12
- [scorecard-url]: https://scorecard.dev/viewer/?uri=github.com/extension-js/extension.js
9
+ [bestpractices-image]: https://img.shields.io/cii/level/14662?label=Best%20Practices&style=flat&color=0971fe
10
+ [bestpractices-url]: https://www.bestpractices.dev/projects/14662
13
11
 
14
- # Extension.js [![Version][npm-version-image]][npm-version-url] [![Stars][stars-image]][stars-url] [![CI][action-image]][action-url] [![Discord][discord-image]][discord-url] [![OpenSSF Scorecard][scorecard-image]][scorecard-url]
12
+ # Extension.js [![Version][npm-version-image]][npm-version-url] [![Stars][stars-image]][stars-url] [![CI][action-image]][action-url] [![Discord][discord-image]][discord-url] [![Best Practices][bestpractices-image]][bestpractices-url]
15
13
 
16
14
  > The cross-browser extension framework. Build for Chrome, Edge, Firefox, and Safari with no build config required.
17
15
 
@@ -25,7 +23,9 @@ npm run dev
25
23
 
26
24
  Works with `npm`, `pnpm`, `yarn`, `bun`, and `deno`.
27
25
 
28
- [Documentation](https://extension.js.org) · [Templates](https://templates.extension.dev) · [Examples](https://github.com/extension-js/examples) · [Discord](https://discord.gg/v9h2RgeTSN)
26
+ The CLI itself runs on Node.js 22.12+, Deno 2.5+, and Bun 1.2+.
27
+
28
+ [Documentation](https://extension.js.org/docs) · [Templates](https://templates.extension.dev/?utm_source=readme&utm_campaign=nav-templates) · [YouTube](https://www.youtube.com/@BrowserExtension) · [Showcase](https://extension.js.org/showcase) · [Blog](https://extension.js.org/blog)
29
29
 
30
30
  ## Why Extension.js
31
31
 
@@ -173,13 +173,25 @@ npx extension@latest dev https://github.com/GoogleChrome/chrome-extensions-sampl
173
173
 
174
174
  ## Package managers
175
175
 
176
- <div align="center">
176
+ Add Extension.js to a project, or run it once without installing, with the package manager you already use.
177
177
 
178
- | <img alt="npm" src="https://media.extension.land/logos/package-managers/npm.svg" width="70"> | <img alt="pnpm" src="https://media.extension.land/logos/package-managers/pnpm.svg" width="70"> | <img alt="Yarn" src="https://media.extension.land/logos/package-managers/yarn.svg" width="70"> | <img alt="Bun" src="https://media.extension.land/logos/package-managers/bun.svg" width="70"> | <picture><source media="(prefers-color-scheme: dark)" srcset="https://media.extension.land/logos/package-managers/deno-dark.svg"><img alt="Deno" src="https://media.extension.land/logos/package-managers/deno.svg" width="70"></picture> |
179
- | :-: | :-: | :-: | :-: | :-: |
180
- | npm<br>✅ Supported | pnpm<br>✅ Supported | Yarn<br>✅ Supported | Bun<br>✅ Supported | Deno<br>✅ Supported |
178
+ | Package manager | Add to a project | Run without installing |
179
+ | :-- | :-- | :-- |
180
+ | npm | `npm install extension@latest --save-dev` | `npx extension@latest create my-extension` |
181
+ | pnpm | `pnpm add extension@latest --save-dev` | `pnpm dlx extension@latest create my-extension` |
182
+ | Yarn | `yarn add extension@latest --dev` | `yarn dlx extension@latest create my-extension` |
183
+ | Bun | `bun add --dev extension@latest` | `bunx extension@latest create my-extension` |
184
+ | Deno | `deno add npm:extension@latest` | `deno run -A npm:extension@latest create my-extension` |
181
185
 
182
- </div>
186
+ Which runtime the CLI then executes on is a separate question, answered below.
187
+
188
+ ## Runtimes
189
+
190
+ | | Runtime | Status | Notes |
191
+ | :-: | :-- | :-- | :-- |
192
+ | <img alt="Node.js" src="https://media.extension.land/logos/package-managers/node.svg" width="28"> | Node.js | ✅ 22.12 and newer | The default. `npx`, `pnpm dlx`, `yarn dlx` and `bunx` all land here through the published bin |
193
+ | <picture><source media="(prefers-color-scheme: dark)" srcset="https://media.extension.land/logos/package-managers/deno-dark.svg"><img alt="Deno" src="https://media.extension.land/logos/package-managers/deno.svg" width="28"></picture> | Deno | ✅ 2.5 and newer | `deno run -A npm:extension@latest`. Deno 2.8.0 is refused because it cannot load `node:querystring`, fixed in 2.8.1 |
194
+ | <img alt="Bun" src="https://media.extension.land/logos/package-managers/bun.svg" width="28"> | Bun | ✅ 1.2 and newer | `bunx --bun extension` and `bun run --bun`. Plain `bunx` runs the CLI on Node.js instead, through the published bin |
183
195
 
184
196
  ## Community
185
197
 
@@ -193,15 +205,6 @@ npx extension@latest dev https://github.com/GoogleChrome/chrome-extensions-sampl
193
205
  Sponsors help the project ship faster releases, better developer experience, and long-term reliability for extension teams.
194
206
 
195
207
  <div align="center">
196
- <p>
197
- <a href="https://www.testmuai.com/?utm_medium=sponsor&utm_source=extensionjs" target="_blank" rel="noopener noreferrer">
198
- <picture>
199
- <source media="(prefers-color-scheme: dark)" srcset="https://extension.js.org/images/sponsors/testmuai_dark.svg" />
200
- <source media="(prefers-color-scheme: light)" srcset="https://extension.js.org/images/sponsors/testmuai.svg" />
201
- <img src="https://extension.js.org/images/sponsors/testmuai.svg" width="220" alt="TestMu AI" />
202
- </picture>
203
- </a>
204
- </p>
205
208
  <p>
206
209
  <a href="https://extension.dev/?utm_medium=sponsor&utm_source=extensionjs" target="_blank" rel="noopener noreferrer">
207
210
  <picture>
package/bin/extension.cjs CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+
2
3
  // ██████╗██╗ ██╗
3
4
  // ██╔════╝██║ ██║
4
5
  // ██║ ██║ ██║
@@ -38,6 +39,7 @@ if (fs.existsSync(distCjsEntry)) {
38
39
  console.error(
39
40
  '[Extension.js] CLI not built. Run "pnpm --filter extension compile".'
40
41
  )
42
+
41
43
  process.exit(1)
42
44
  }
43
45
  } else {
package/dist/84.cjs CHANGED
@@ -197,6 +197,7 @@ exports.modules = {
197
197
  var newtab_override = __webpack_require__("./browsers/browsers-lib/newtab-override.ts");
198
198
  var ready_stamp = __webpack_require__("./browsers/browsers-lib/ready-stamp.ts");
199
199
  var shared_utils = __webpack_require__("./browsers/browsers-lib/shared-utils.ts");
200
+ var write_json_atomic = __webpack_require__("./browsers/browsers-lib/write-json-atomic.ts");
200
201
  var external_ws_ = __webpack_require__("ws");
201
202
  var external_ws_default = /*#__PURE__*/ __webpack_require__.n(external_ws_);
202
203
  var constants = __webpack_require__("./browsers/browsers-lib/constants.ts");
@@ -1116,7 +1117,7 @@ exports.modules = {
1116
1117
  const ready = JSON.parse(external_node_fs_.readFileSync(readyPath, 'utf-8'));
1117
1118
  if (ready.cdpPort !== chromeRemoteDebugPort) {
1118
1119
  ready.cdpPort = chromeRemoteDebugPort;
1119
- external_node_fs_.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1120
+ (0, write_json_atomic.Z)(readyPath, ready);
1120
1121
  }
1121
1122
  }
1122
1123
  }
package/dist/browsers.cjs CHANGED
@@ -740,16 +740,19 @@ var __webpack_modules__ = {
740
740
  const tail = outputTail.trim().length ? `\n${pintor__rspack_import_8_default().gray('── last output ──')}\n${outputTail}` : `\n${pintor__rspack_import_8_default().gray('(no output captured)')}`;
741
741
  return `${getLoggingPrefix('error')} Safari packaging tool ${pintor__rspack_import_8_default().underline(tool)} failed (${pintor__rspack_import_8_default().red(code)}).${tail}`;
742
742
  }
743
+ const SAFARI_KEPT_ON_PURPOSE = {
744
+ world: 'Safari has honored the MAIN world since Safari 18, so the key stays'
745
+ };
743
746
  function safariConverterWarnings(warnings) {
744
- return `${getLoggingPrefix('warn')} safari-web-extension-converter reported ${pintor__rspack_import_8_default().yellow(String(warnings.length))} ${1 === warnings.length ? 'warning' : 'warnings'}, some manifest keys/APIs may not be supported by Safari:\n` + warnings.map((line)=>` ${pintor__rspack_import_8_default().gray('•')} ${line}`).join('\n');
747
+ const kept = warnings.map((line)=>line.trim()).filter((line)=>line in SAFARI_KEPT_ON_PURPOSE);
748
+ const keptLines = kept.map((line)=>` ${pintor__rspack_import_8_default().yellow(line)} ${pintor__rspack_import_8_default().gray(SAFARI_KEPT_ON_PURPOSE[line])}`).join('\n');
749
+ return `${getLoggingPrefix('warn')} safari-web-extension-converter reported ${pintor__rspack_import_8_default().yellow(String(warnings.length))} ${1 === warnings.length ? 'warning' : 'warnings'}, some manifest keys/APIs may not be supported by Safari:\n` + warnings.map((line)=>` ${pintor__rspack_import_8_default().gray('•')} ${line}`).join('\n') + (kept.length ? `\nExtension.js kept ${1 === kept.length ? 'one of these keys' : 'some of these keys'} on purpose:\n${keptLines}` : '');
745
750
  }
746
751
  function safariDefaultBundleIdNote(bundleId) {
747
- return `${getLoggingPrefix('warn')} The bundle id ${pintor__rspack_import_8_default().gray(bundleId)} is generated from the app name.\nEvery project built from the same source shares this id, and the first team to register it takes it.\nSet your own with ${pintor__rspack_import_8_default().blue('--bundle-id')} before your first submission.\nAfter a submission, a new id is a new extension with none of your users.`;
752
+ return `${getLoggingPrefix('info')} Bundle id ${pintor__rspack_import_8_default().gray(bundleId)} (generated, set ${pintor__rspack_import_8_default().blue('--bundle-id')} to own it).`;
748
753
  }
749
- function safariOpenHint(appPath, appName, signed) {
750
- const launch = `${getLoggingPrefix('info')} Launch it once to register with Safari: ${pintor__rspack_import_8_default().blue('open')} ${pintor__rspack_import_8_default().underline(`"${appPath}"`)}\n`;
751
- if (signed) return launch + `Then turn on ${pintor__rspack_import_8_default().yellow(appName)} in Safari ▸ Settings ▸ Extensions.`;
752
- return launch + `Then enable ${pintor__rspack_import_8_default().yellow(appName)} via Safari ▸ Develop ▸ ` + `${pintor__rspack_import_8_default().yellow('Allow Unsigned Extensions')} and Safari ▸ Settings ▸ Extensions.`;
754
+ function safariOpenHint(appPath, appName) {
755
+ return `${getLoggingPrefix('info')} Launch it once to register with Safari: ${pintor__rspack_import_8_default().blue('open')} ${pintor__rspack_import_8_default().underline(`"${appPath}"`)}\nThen turn on ${pintor__rspack_import_8_default().yellow(appName)} in Safari ▸ Settings ▸ Extensions.`;
753
756
  }
754
757
  function safariDryRunNotBuilding() {
755
758
  return `${getLoggingPrefix('info')} Dry run: skip the Safari app build.`;
@@ -761,17 +764,21 @@ var __webpack_modules__ = {
761
764
  return `${getLoggingPrefix('info')} xcodebuild: ${pintor__rspack_import_8_default().gray(cmd)}.`;
762
765
  }
763
766
  function safariNextSteps(appName, signed) {
764
- if (signed) return `${getLoggingPrefix('info')} One-time setup to load ${pintor__rspack_import_8_default().yellow(appName)} in Safari:\n ${pintor__rspack_import_8_default().gray('1.')} Safari ▸ Settings ▸ Extensions ▸ turn on ${pintor__rspack_import_8_default().yellow(appName)}\n ${pintor__rspack_import_8_default().gray('→')} Signed with your team id, so this survives a restart.`;
765
- return `${getLoggingPrefix('info')} One-time setup to load ${pintor__rspack_import_8_default().yellow(appName)} in Safari:\n ${pintor__rspack_import_8_default().gray('1.')} Safari Settings Advanced check ${pintor__rspack_import_8_default().yellow('“Show features for web developers”')}\n ${pintor__rspack_import_8_default().gray('2.')} Safari ▸ Develop ▸ ${pintor__rspack_import_8_default().yellow('Allow Unsigned Extensions')} ${pintor__rspack_import_8_default().gray('(resets each launch)')}\n ${pintor__rspack_import_8_default().gray('3.')} Safari ▸ Settings ▸ Extensions ▸ turn on ${pintor__rspack_import_8_default().yellow(appName)}\n ${pintor__rspack_import_8_default().gray('→')} The app window that just opened can also take you there.`;
767
+ const steps = `${getLoggingPrefix('info')} One-time setup to load ${pintor__rspack_import_8_default().yellow(appName)} in Safari:\n ${pintor__rspack_import_8_default().gray('1.')} Safari ▸ Settings ▸ Extensions ▸ turn on ${pintor__rspack_import_8_default().yellow(appName)}\n`;
768
+ if (signed) return steps + ` ${pintor__rspack_import_8_default().gray('')} Signed with your team id, so the identity is stable for distribution.`;
769
+ return steps + ` ${pintor__rspack_import_8_default().gray('→')} Ad-hoc signed, so it stays enabled across restarts.`;
766
770
  }
767
771
  function safariRegistered(appName) {
768
772
  return `${getLoggingPrefix('success')} Safari recognizes ${pintor__rspack_import_8_default().yellow(appName)}, finish enabling it with the steps above.`;
769
773
  }
774
+ function safariPidUnresolved(appName) {
775
+ return `${getLoggingPrefix('warn')} Opened ${pintor__rspack_import_8_default().yellow(appName)} but could not find its process id.\nThe session's ready.json names the app and the extension without a ${pintor__rspack_import_8_default().gray('browserPid')}.`;
776
+ }
770
777
  function safariNotYetRegistered(appName) {
771
778
  return `${getLoggingPrefix('info')} Safari hasn't picked up ${pintor__rspack_import_8_default().yellow(appName)} yet.\nOpen the app once, then check Safari ▸ Settings ▸ Extensions.`;
772
779
  }
773
780
  function safariRebuilt(appName) {
774
- return `${getLoggingPrefix('success')} Rebuilt ${pintor__rspack_import_8_default().yellow(appName)}, reload the page (or toggle the extension) in Safari to see changes.`;
781
+ return `${getLoggingPrefix('success')} Rebuilt ${pintor__rspack_import_8_default().yellow(appName)}.`;
775
782
  }
776
783
  function safariProjectStale() {
777
784
  return `${getLoggingPrefix('info')} Regenerating the Xcode project, manifest.json or identity options changed since it was generated.`;
@@ -1028,6 +1035,7 @@ var __webpack_modules__ = {
1028
1035
  jR: ()=>safariBuilding,
1029
1036
  jc: ()=>safariProjectStale,
1030
1037
  jk: ()=>requireGeckoBinaryForGeckoBased,
1038
+ jn: ()=>safariPidUnresolved,
1031
1039
  kK: ()=>preferringSystemBrowserOverSnapshot,
1032
1040
  l9: ()=>parsingPacketError,
1033
1041
  lO: ()=>firefoxDryRunArgs,
@@ -1400,6 +1408,7 @@ var __webpack_modules__ = {
1400
1408
  "./browsers/browsers-lib/ready-stamp.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1401
1409
  var node_fs__rspack_import_0 = __webpack_require__("node:fs");
1402
1410
  var node_path__rspack_import_1 = __webpack_require__("node:path");
1411
+ var _write_json_atomic__rspack_import_2 = __webpack_require__("./browsers/browsers-lib/write-json-atomic.ts");
1403
1412
  function readyPathFor(extensionOutputPath) {
1404
1413
  return node_path__rspack_import_1.join(node_path__rspack_import_1.dirname(extensionOutputPath), 'extension-js', node_path__rspack_import_1.basename(extensionOutputPath), 'ready.json');
1405
1414
  }
@@ -1411,7 +1420,7 @@ var __webpack_modules__ = {
1411
1420
  const ready = JSON.parse(node_fs__rspack_import_0.readFileSync(readyPath, 'utf-8'));
1412
1421
  if (ready.rdpPort === rdpPort) return;
1413
1422
  ready.rdpPort = rdpPort;
1414
- node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1423
+ (0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
1415
1424
  } catch {}
1416
1425
  }
1417
1426
  function stampReadyBrowserLaunch(extensionOutputPath, details) {
@@ -1429,7 +1438,7 @@ var __webpack_modules__ = {
1429
1438
  if (binary) ready.binary = binary;
1430
1439
  const provenance = String(details?.binaryProvenance || '').trim();
1431
1440
  if (provenance) ready.binaryProvenance = provenance;
1432
- node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1441
+ (0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
1433
1442
  } catch {}
1434
1443
  }
1435
1444
  function stampReadyExtensionId(extensionOutputPath, extensionId) {
@@ -1441,7 +1450,7 @@ var __webpack_modules__ = {
1441
1450
  const ready = JSON.parse(node_fs__rspack_import_0.readFileSync(readyPath, 'utf-8'));
1442
1451
  if (ready.extensionId === id) return;
1443
1452
  ready.extensionId = id;
1444
- node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1453
+ (0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
1445
1454
  } catch {}
1446
1455
  }
1447
1456
  function stampReadyExtensionLoadRefused(extensionOutputPath, reason) {
@@ -1456,7 +1465,7 @@ var __webpack_modules__ = {
1456
1465
  ready.message = `${browserLabel.charAt(0).toUpperCase() + browserLabel.slice(1)} refused to load the extension at ${extensionOutputPath}${reason ? `: ${reason}` : ''}`;
1457
1466
  ready.extensionLoadRefusedAt = new Date().toISOString();
1458
1467
  if (reason) ready.extensionLoadRefusedReason = reason;
1459
- node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1468
+ (0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
1460
1469
  } catch {}
1461
1470
  }
1462
1471
  function stampReadyProfileLocked(extensionOutputPath, details) {
@@ -1470,7 +1479,7 @@ var __webpack_modules__ = {
1470
1479
  ready.message = String(details?.message || '').trim() || 'the browser profile is already in use by another session';
1471
1480
  ready.profileLockedAt = new Date().toISOString();
1472
1481
  if (details?.owner) ready.profileLockOwner = details.owner;
1473
- node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1482
+ (0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
1474
1483
  } catch {}
1475
1484
  }
1476
1485
  function stampReadyBrowserExited(extensionOutputPath, code) {
@@ -1486,7 +1495,7 @@ var __webpack_modules__ = {
1486
1495
  ready.code = 'browser_exited';
1487
1496
  ready.message = `the ${ready.browser || 'browser'} process exited (code ${code ?? 'unknown'}); nothing is running`;
1488
1497
  }
1489
- node_fs__rspack_import_0.writeFileSync(readyPath, JSON.stringify(ready, null, 2));
1498
+ (0, _write_json_atomic__rspack_import_2.Z)(readyPath, ready);
1490
1499
  } catch {}
1491
1500
  }
1492
1501
  __webpack_require__.d(__webpack_exports__, {
@@ -1949,6 +1958,27 @@ var __webpack_modules__ = {
1949
1958
  yO: ()=>probeChromiumBinaryVersion
1950
1959
  });
1951
1960
  },
1961
+ "./browsers/browsers-lib/write-json-atomic.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1962
+ var node_fs__rspack_import_0 = __webpack_require__("node:fs");
1963
+ function writeJsonAtomic(filePath, value) {
1964
+ const tmpPath = `${filePath}.tmp-${process.pid}`;
1965
+ try {
1966
+ node_fs__rspack_import_0.writeFileSync(tmpPath, JSON.stringify(value, null, 2), 'utf-8');
1967
+ node_fs__rspack_import_0.renameSync(tmpPath, filePath);
1968
+ return true;
1969
+ } catch {
1970
+ try {
1971
+ node_fs__rspack_import_0.rmSync(tmpPath, {
1972
+ force: true
1973
+ });
1974
+ } catch {}
1975
+ return false;
1976
+ }
1977
+ }
1978
+ __webpack_require__.d(__webpack_exports__, {
1979
+ Z: ()=>writeJsonAtomic
1980
+ });
1981
+ },
1952
1982
  "./browsers/browsers-lib/wsl-support.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1953
1983
  __webpack_require__.d(__webpack_exports__, {
1954
1984
  qc: ()=>external_wsl_support_namespaceObject.hasGuiDisplay,
@@ -5812,7 +5842,6 @@ var __webpack_modules__ = {
5812
5842
  QW: ()=>safariBuildPreflight,
5813
5843
  iF: ()=>safariPreflightError
5814
5844
  });
5815
- var external_node_child_process_ = __webpack_require__("node:child_process");
5816
5845
  var external_node_fs_ = __webpack_require__("node:fs");
5817
5846
  var messaging = __webpack_require__("./helpers/messaging.ts");
5818
5847
  var banner = __webpack_require__("./browsers/browsers-lib/banner.ts");
@@ -5826,6 +5855,7 @@ var __webpack_modules__ = {
5826
5855
  (0, messaging._w)(messages.Aj(xcodebuildCmd));
5827
5856
  }
5828
5857
  var safari_config = __webpack_require__("./browsers/run-safari/safari-launch/safari-config.ts");
5858
+ var external_node_child_process_ = __webpack_require__("node:child_process");
5829
5859
  function isMacOS() {
5830
5860
  return 'darwin' === process.platform;
5831
5861
  }
@@ -5883,20 +5913,6 @@ var __webpack_modules__ = {
5883
5913
  ok: Boolean(converter && xcodebuild)
5884
5914
  };
5885
5915
  }
5886
- function fallbackLogger() {
5887
- return {
5888
- info: (...a)=>(0, messaging._w)(...a),
5889
- warn: (...a)=>(0, messaging.mR)(...a),
5890
- error: (...a)=>(0, messaging.Gg)(...a),
5891
- debug: (...a)=>console?.debug?.(...a)
5892
- };
5893
- }
5894
- function isTestEnv() {
5895
- return Boolean(process.env.VITEST || process.env.VITEST_WORKER_ID);
5896
- }
5897
- function delay(ms) {
5898
- return new Promise((resolve)=>setTimeout(resolve, ms));
5899
- }
5900
5916
  const TOOL_TAIL_LINES = 50;
5901
5917
  const TOOL_TAIL_BYTES = 8192;
5902
5918
  function toolOutputTail(output) {
@@ -5935,18 +5951,85 @@ var __webpack_modules__ = {
5935
5951
  }));
5936
5952
  });
5937
5953
  }
5954
+ async function resolvePidForBundle(bundleId) {
5955
+ for(let attempt = 0; attempt < 10; attempt += 1){
5956
+ const pid = await new Promise((resolve)=>{
5957
+ const child = (0, external_node_child_process_.spawn)("osascript", [
5958
+ '-e',
5959
+ `tell application "System Events" to get unix id of first process whose bundle identifier is "${bundleId}"`
5960
+ ]);
5961
+ let out = '';
5962
+ child.stdout.on('data', (d)=>out += String(d));
5963
+ child.on('error', ()=>resolve(null));
5964
+ child.on('close', ()=>{
5965
+ const n = Number(String(out).trim());
5966
+ resolve(Number.isFinite(n) && n > 0 ? n : null);
5967
+ });
5968
+ });
5969
+ if (pid) return pid;
5970
+ await new Promise((r)=>setTimeout(r, 500));
5971
+ }
5972
+ return null;
5973
+ }
5974
+ function createSafariTools() {
5975
+ return {
5976
+ detectToolchain: ()=>detectSafariToolchain(),
5977
+ runConverter: (args)=>runTool('xcrun', args),
5978
+ runXcodebuild: (args)=>runTool('xcodebuild', args),
5979
+ openApp: (target)=>runTool('open', [
5980
+ target
5981
+ ]),
5982
+ openSafari: (binary)=>runTool('open', [
5983
+ '-a',
5984
+ binary
5985
+ ]),
5986
+ resolvePid: (bundleId)=>resolvePidForBundle(bundleId),
5987
+ pluginkitList: async ()=>{
5988
+ const { ok, output } = await runTool('pluginkit', [
5989
+ '-m'
5990
+ ], {
5991
+ quiet: true
5992
+ });
5993
+ return ok ? output : '';
5994
+ }
5995
+ };
5996
+ }
5997
+ function fallbackLogger() {
5998
+ return {
5999
+ info: (...a)=>(0, messaging._w)(...a),
6000
+ warn: (...a)=>(0, messaging.mR)(...a),
6001
+ error: (...a)=>(0, messaging.Gg)(...a),
6002
+ debug: (...a)=>console?.debug?.(...a)
6003
+ };
6004
+ }
6005
+ function delay(ms) {
6006
+ return new Promise((resolve)=>setTimeout(resolve, ms));
6007
+ }
5938
6008
  function converterWarnings(output) {
5939
- return output.split(/\r?\n/).filter((line)=>/warning/i.test(line)).map((line)=>line.trim()).filter((line)=>line.length > 0);
6009
+ const lines = output.split(/\r?\n/);
6010
+ const collected = [];
6011
+ let inWarning = false;
6012
+ for (const line of lines){
6013
+ const trimmed = line.trim();
6014
+ if (/warning/i.test(line)) {
6015
+ inWarning = true;
6016
+ if (trimmed.length > 0) collected.push(trimmed);
6017
+ continue;
6018
+ }
6019
+ const isContinuation = inWarning && /^\s/.test(line) && trimmed.length > 0;
6020
+ if (isContinuation) {
6021
+ collected.push(trimmed);
6022
+ continue;
6023
+ }
6024
+ inWarning = false;
6025
+ }
6026
+ return collected;
5940
6027
  }
5941
- async function confirmRegisteredWithSafari(bundleIdentifier) {
6028
+ async function confirmRegisteredWithSafari(tools, bundleIdentifier) {
5942
6029
  const needle = `${bundleIdentifier}.Extension`;
5943
6030
  for(let attempt = 0; attempt < 6; attempt += 1){
5944
- const { ok, output } = await runTool('pluginkit', [
5945
- '-m'
5946
- ], {
5947
- quiet: true
5948
- });
5949
- if (ok && output.includes(needle)) return true;
6031
+ const listing = await tools.pluginkitList();
6032
+ if (listing.includes(needle)) return true;
5950
6033
  await delay(800);
5951
6034
  }
5952
6035
  return false;
@@ -5961,6 +6044,10 @@ var __webpack_modules__ = {
5961
6044
  macOsOnly: config.macOsOnly
5962
6045
  };
5963
6046
  }
6047
+ function completePackage(config, logger, mode) {
6048
+ if ('full' === mode && config.bundleIdDerived) logger.info?.(messages.$q(config.bundleIdentifier));
6049
+ return describePackage(config);
6050
+ }
5964
6051
  function safariPreflightError() {
5965
6052
  const tc = detectSafariToolchain();
5966
6053
  if (!tc.platformOk) return messages.G3(process.platform);
@@ -5984,26 +6071,6 @@ var __webpack_modules__ = {
5984
6071
  severity: 'ok'
5985
6072
  };
5986
6073
  }
5987
- async function resolvePidForBundle(bundleId) {
5988
- for(let attempt = 0; attempt < 10; attempt += 1){
5989
- const pid = await new Promise((resolve)=>{
5990
- const child = (0, external_node_child_process_.spawn)("osascript", [
5991
- '-e',
5992
- `tell application "System Events" to get unix id of first process whose bundle identifier is "${bundleId}"`
5993
- ]);
5994
- let out = '';
5995
- child.stdout.on('data', (d)=>out += String(d));
5996
- child.on('error', ()=>resolve(null));
5997
- child.on('close', ()=>{
5998
- const n = Number(String(out).trim());
5999
- resolve(Number.isFinite(n) && n > 0 ? n : null);
6000
- });
6001
- });
6002
- if (pid) return pid;
6003
- await new Promise((r)=>setTimeout(r, 500));
6004
- }
6005
- return null;
6006
- }
6007
6074
  async function announceSafariDevSession(host, config, appPath) {
6008
6075
  try {
6009
6076
  await (0, banner.ai)({
@@ -6019,16 +6086,16 @@ var __webpack_modules__ = {
6019
6086
  } catch {}
6020
6087
  }
6021
6088
  async function runSafariPipeline(compilation, host, logger, mode) {
6089
+ const tools = host.tools || createSafariTools();
6022
6090
  const config = (0, safari_config.F7)(compilation, host);
6023
6091
  const converterArgs = (0, safari_config.w7)(config);
6024
6092
  const xcodebuildArgs = (0, safari_config.vx)(config);
6025
6093
  const isSigned = Boolean(config.developmentTeam);
6026
- if ('full' === mode && config.bundleIdDerived) logger.warn?.(messages.$q(config.bundleIdentifier));
6027
- if (host.dryRun || isTestEnv()) {
6094
+ if (host.dryRun) {
6028
6095
  logSafariDryRun(`xcrun ${converterArgs.join(' ')}`, `xcodebuild ${xcodebuildArgs.join(' ')}`);
6029
- return describePackage(config);
6096
+ return completePackage(config, logger, mode);
6030
6097
  }
6031
- const toolchain = detectSafariToolchain();
6098
+ const toolchain = tools.detectToolchain();
6032
6099
  if (!toolchain.platformOk) {
6033
6100
  logger.warn?.(messages.G3(process.platform));
6034
6101
  return describePackage(config);
@@ -6052,7 +6119,7 @@ var __webpack_modules__ = {
6052
6119
  }
6053
6120
  const { saved, restore } = (0, safari_config._D)(config);
6054
6121
  logger.info?.(messages.uK(config.extensionDir));
6055
- const converted = await runTool('xcrun', converterArgs);
6122
+ const converted = await tools.runConverter(converterArgs);
6056
6123
  if (!converted.ok) {
6057
6124
  const tail = toolOutputTail(converted.output);
6058
6125
  logger.error?.(messages.A('safari-web-extension-converter', converted.code, tail));
@@ -6067,9 +6134,9 @@ var __webpack_modules__ = {
6067
6134
  if (preservedKeys.length > 0) logger.info?.(messages.oB(preservedKeys));
6068
6135
  (0, safari_config.x7)(config);
6069
6136
  logger.info?.(messages.l_(config.projectLocation));
6070
- } else logger.info?.(messages.oM());
6137
+ } else if ('full' === mode) logger.info?.(messages.oM());
6071
6138
  if ('full' === mode) logger.info?.(messages.jR((0, safari_config.aD)(config)));
6072
- const built = await runTool('xcodebuild', xcodebuildArgs);
6139
+ const built = await tools.runXcodebuild(xcodebuildArgs);
6073
6140
  if (!built.ok) {
6074
6141
  const tail = toolOutputTail(built.output);
6075
6142
  logger.error?.(messages.A('xcodebuild', built.code, tail));
@@ -6082,32 +6149,28 @@ var __webpack_modules__ = {
6082
6149
  }
6083
6150
  logger.info?.(messages.Rl(appPath));
6084
6151
  if (!config.open) {
6085
- logger.info?.(messages.aO(appPath, config.appName, isSigned));
6152
+ logger.info?.(messages.aO(appPath, config.appName));
6086
6153
  if (host.announceDevReady) await announceSafariDevSession(host, config, appPath);
6087
- return describePackage(config);
6154
+ return completePackage(config, logger, mode);
6088
6155
  }
6089
6156
  const target = external_node_fs_.existsSync(appPath) ? appPath : (0, safari_config.Gi)(config);
6090
6157
  logger.info?.(messages.fw(target));
6091
- await runTool('open', [
6092
- target
6093
- ]);
6094
- if (config.safariBinary) await runTool('open', [
6095
- '-a',
6096
- config.safariBinary
6097
- ]);
6158
+ await tools.openApp(target);
6159
+ if (config.safariBinary) await tools.openSafari(config.safariBinary);
6098
6160
  const raisedBundleId = config.safariBinary ? 'com.apple.Safari' : config.bundleIdentifier;
6099
- const browserPid = await resolvePidForBundle(raisedBundleId);
6100
- if (browserPid) (0, ready_stamp.M)(config.extensionDir, {
6101
- browserPid,
6161
+ const browserPid = await tools.resolvePid(raisedBundleId);
6162
+ (0, ready_stamp.M)(config.extensionDir, {
6163
+ browserPid: browserPid || void 0,
6102
6164
  binary: config.safariBinary || appPath,
6103
6165
  binaryProvenance: config.safariBinary ? 'pinned' : 'system',
6104
6166
  extensionId: `${config.bundleIdentifier}.Extension`
6105
6167
  });
6168
+ if (!browserPid) logger.warn?.(messages.jn(config.appName));
6106
6169
  logger.info?.(messages.fO(config.appName, isSigned));
6107
- if (await confirmRegisteredWithSafari(config.bundleIdentifier)) logger.info?.(messages.fd(config.appName));
6170
+ if (await confirmRegisteredWithSafari(tools, config.bundleIdentifier)) logger.info?.(messages.fd(config.appName));
6108
6171
  else logger.info?.(messages.qY(config.appName));
6109
6172
  if (host.announceDevReady) await announceSafariDevSession(host, config, appPath);
6110
- return describePackage(config);
6173
+ return completePackage(config, logger, mode);
6111
6174
  }
6112
6175
  async function packageSafariExtension(host, outputPath, logger, mode = 'full') {
6113
6176
  const compilation = {
@@ -6227,36 +6290,56 @@ var __webpack_modules__ = {
6227
6290
  function manifestFingerprintPath(config) {
6228
6291
  return node_path__rspack_import_1.join(config.projectLocation, '.manifest-fingerprint');
6229
6292
  }
6230
- function stableStringify(obj) {
6231
- if (null == obj) return JSON.stringify(obj);
6232
- if ('object' != typeof obj) return JSON.stringify(obj);
6233
- if (Array.isArray(obj)) return `[${obj.map(stableStringify).join(',')}]`;
6234
- const keys = Object.keys(obj).sort();
6235
- return `{${keys.map((k)=>`${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(',')}}`;
6236
- }
6237
- function readManifestRaw(extensionDir) {
6293
+ const HOT_UPDATE_DIR = 'hot';
6294
+ function topLevelEntries(extensionDir) {
6238
6295
  try {
6239
- return node_fs__rspack_import_0.readFileSync(node_path__rspack_import_1.join(extensionDir, 'manifest.json'), 'utf8');
6296
+ return node_fs__rspack_import_0.readdirSync(extensionDir).filter((entry)=>entry !== HOT_UPDATE_DIR).sort();
6240
6297
  } catch {
6241
- return '';
6298
+ return [];
6242
6299
  }
6243
6300
  }
6244
- function normalizeManifest(raw) {
6301
+ function iconsFingerprint(extensionDir) {
6245
6302
  try {
6246
- return stableStringify(JSON.parse(raw));
6303
+ const raw = node_fs__rspack_import_0.readFileSync(node_path__rspack_import_1.join(extensionDir, 'manifest.json'), 'utf8');
6304
+ const icons = JSON.parse(raw).icons;
6305
+ if (!icons || 'object' != typeof icons) return '';
6306
+ return Object.keys(icons).sort().map((size)=>`${size}:${String(icons[size])}`).join(',');
6247
6307
  } catch {
6248
- return raw;
6308
+ return '';
6309
+ }
6310
+ }
6311
+ function asRecord(value) {
6312
+ return value && 'object' == typeof value && !Array.isArray(value) ? value : {};
6313
+ }
6314
+ function judgedManifestSurface(extensionDir) {
6315
+ const manifest = readManifest(extensionDir);
6316
+ const parts = [
6317
+ Object.keys(manifest).sort().join(',')
6318
+ ];
6319
+ for (const listName of [
6320
+ 'permissions',
6321
+ 'optional_permissions'
6322
+ ]){
6323
+ const list = manifest[listName];
6324
+ const names = Array.isArray(list) ? list.filter((entry)=>'string' == typeof entry).sort() : [];
6325
+ parts.push(`${listName}:${names.join(',')}`);
6249
6326
  }
6327
+ const contentScripts = manifest.content_scripts;
6328
+ if (Array.isArray(contentScripts)) parts.push(contentScripts.map((entry)=>Object.keys(asRecord(entry)).sort().join('+')).join(';'));
6329
+ parts.push(Object.keys(asRecord(manifest.options_ui)).sort().join(','));
6330
+ return parts.join('|');
6250
6331
  }
6251
6332
  function composeProjectFingerprint(config) {
6252
6333
  return JSON.stringify({
6253
- v: 2,
6334
+ v: 4,
6254
6335
  identity: {
6255
6336
  appName: config.appName,
6256
6337
  bundleId: config.bundleIdentifier,
6257
6338
  macOsOnly: config.macOsOnly
6258
6339
  },
6259
- manifest: normalizeManifest(readManifestRaw(config.extensionDir))
6340
+ entries: topLevelEntries(config.extensionDir),
6341
+ icons: iconsFingerprint(config.extensionDir),
6342
+ judged: judgedManifestSurface(config.extensionDir)
6260
6343
  });
6261
6344
  }
6262
6345
  function saveManifestFingerprint(config) {
@@ -6339,7 +6422,7 @@ var __webpack_modules__ = {
6339
6422
  return Object.fromEntries(Object.entries(input).filter(([, value])=>void 0 !== value));
6340
6423
  }
6341
6424
  function createSafariPackager(options = {}) {
6342
- const { browser = 'safari', noOpen = true, dryRun = false, logger, ...identity } = options;
6425
+ const { browser = 'safari', noOpen = true, dryRun = false, logger, tools, ...identity } = options;
6343
6426
  return async (distPath, mode = 'full', overrides)=>await (0, _safari_launch__rspack_import_0.Ah)({
6344
6427
  extension: [
6345
6428
  distPath
@@ -6347,6 +6430,7 @@ var __webpack_modules__ = {
6347
6430
  browser,
6348
6431
  noOpen,
6349
6432
  dryRun,
6433
+ tools,
6350
6434
  ...withoutUndefined(identity),
6351
6435
  ...withoutUndefined(overrides)
6352
6436
  }, distPath, logger, mode);