extension 4.1.19 → 4.1.20

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
@@ -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=OpenSSF%20Best%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] [![OpenSSF 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+ and Deno 2.5+. Bun installs and launches it, and it runs on Node.js from there.
27
+
28
+ [Documentation](https://extension.js.org/docs) · [Templates](https://templates.extension.dev/?utm_source=readme&utm_campaign=nav-templates) · [Showcase](https://extension.js.org/showcase) · [Blog](https://extension.js.org/blog)
29
29
 
30
30
  ## Why Extension.js
31
31
 
@@ -181,6 +181,16 @@ npx extension@latest dev https://github.com/GoogleChrome/chrome-extensions-sampl
181
181
 
182
182
  </div>
183
183
 
184
+ Each of these installs Extension.js, launches it, and runs its scripts. Which runtime the CLI then executes on is a separate question.
185
+
186
+ ## Runtimes
187
+
188
+ | Runtime | Status | Notes |
189
+ | :-- | :-- | :-- |
190
+ | Node.js | ✅ 22.12 and newer | The default. `npx`, `pnpm dlx`, `yarn dlx` and `bunx` all land here through the published bin |
191
+ | 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 |
192
+ | Bun | ❌ Not a runtime for the CLI | Bun installs and launches Extension.js, which then runs on Node.js. `bunx --bun` and `bun run --bun` are refused |
193
+
184
194
  ## Community
185
195
 
186
196
  - Star the repo if Extension.js helped you ship faster
@@ -193,15 +203,6 @@ npx extension@latest dev https://github.com/GoogleChrome/chrome-extensions-sampl
193
203
  Sponsors help the project ship faster releases, better developer experience, and long-term reliability for extension teams.
194
204
 
195
205
  <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
206
  <p>
206
207
  <a href="https://extension.dev/?utm_medium=sponsor&utm_source=extensionjs" target="_blank" rel="noopener noreferrer">
207
208
  <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/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
752
  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.`;
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,8 +764,9 @@ 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.`;
@@ -771,7 +775,7 @@ var __webpack_modules__ = {
771
775
  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
776
  }
773
777
  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.`;
778
+ return `${getLoggingPrefix('success')} Rebuilt ${pintor__rspack_import_8_default().yellow(appName)}.`;
775
779
  }
776
780
  function safariProjectStale() {
777
781
  return `${getLoggingPrefix('info')} Regenerating the Xcode project, manifest.json or identity options changed since it was generated.`;
@@ -5936,7 +5940,24 @@ var __webpack_modules__ = {
5936
5940
  });
5937
5941
  }
5938
5942
  function converterWarnings(output) {
5939
- return output.split(/\r?\n/).filter((line)=>/warning/i.test(line)).map((line)=>line.trim()).filter((line)=>line.length > 0);
5943
+ const lines = output.split(/\r?\n/);
5944
+ const collected = [];
5945
+ let inWarning = false;
5946
+ for (const line of lines){
5947
+ const trimmed = line.trim();
5948
+ if (/warning/i.test(line)) {
5949
+ inWarning = true;
5950
+ if (trimmed.length > 0) collected.push(trimmed);
5951
+ continue;
5952
+ }
5953
+ const isContinuation = inWarning && /^\s/.test(line) && trimmed.length > 0;
5954
+ if (isContinuation) {
5955
+ collected.push(trimmed);
5956
+ continue;
5957
+ }
5958
+ inWarning = false;
5959
+ }
5960
+ return collected;
5940
5961
  }
5941
5962
  async function confirmRegisteredWithSafari(bundleIdentifier) {
5942
5963
  const needle = `${bundleIdentifier}.Extension`;
@@ -6067,7 +6088,7 @@ var __webpack_modules__ = {
6067
6088
  if (preservedKeys.length > 0) logger.info?.(messages.oB(preservedKeys));
6068
6089
  (0, safari_config.x7)(config);
6069
6090
  logger.info?.(messages.l_(config.projectLocation));
6070
- } else logger.info?.(messages.oM());
6091
+ } else if ('full' === mode) logger.info?.(messages.oM());
6071
6092
  if ('full' === mode) logger.info?.(messages.jR((0, safari_config.aD)(config)));
6072
6093
  const built = await runTool('xcodebuild', xcodebuildArgs);
6073
6094
  if (!built.ok) {
@@ -6082,7 +6103,7 @@ var __webpack_modules__ = {
6082
6103
  }
6083
6104
  logger.info?.(messages.Rl(appPath));
6084
6105
  if (!config.open) {
6085
- logger.info?.(messages.aO(appPath, config.appName, isSigned));
6106
+ logger.info?.(messages.aO(appPath, config.appName));
6086
6107
  if (host.announceDevReady) await announceSafariDevSession(host, config, appPath);
6087
6108
  return describePackage(config);
6088
6109
  }
@@ -6227,36 +6248,55 @@ var __webpack_modules__ = {
6227
6248
  function manifestFingerprintPath(config) {
6228
6249
  return node_path__rspack_import_1.join(config.projectLocation, '.manifest-fingerprint');
6229
6250
  }
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) {
6251
+ function topLevelEntries(extensionDir) {
6238
6252
  try {
6239
- return node_fs__rspack_import_0.readFileSync(node_path__rspack_import_1.join(extensionDir, 'manifest.json'), 'utf8');
6253
+ return node_fs__rspack_import_0.readdirSync(extensionDir).sort();
6240
6254
  } catch {
6241
- return '';
6255
+ return [];
6242
6256
  }
6243
6257
  }
6244
- function normalizeManifest(raw) {
6258
+ function iconsFingerprint(extensionDir) {
6245
6259
  try {
6246
- return stableStringify(JSON.parse(raw));
6260
+ const raw = node_fs__rspack_import_0.readFileSync(node_path__rspack_import_1.join(extensionDir, 'manifest.json'), 'utf8');
6261
+ const icons = JSON.parse(raw).icons;
6262
+ if (!icons || 'object' != typeof icons) return '';
6263
+ return Object.keys(icons).sort().map((size)=>`${size}:${String(icons[size])}`).join(',');
6247
6264
  } catch {
6248
- return raw;
6265
+ return '';
6266
+ }
6267
+ }
6268
+ function asRecord(value) {
6269
+ return value && 'object' == typeof value && !Array.isArray(value) ? value : {};
6270
+ }
6271
+ function judgedManifestSurface(extensionDir) {
6272
+ const manifest = readManifest(extensionDir);
6273
+ const parts = [
6274
+ Object.keys(manifest).sort().join(',')
6275
+ ];
6276
+ for (const listName of [
6277
+ 'permissions',
6278
+ 'optional_permissions'
6279
+ ]){
6280
+ const list = manifest[listName];
6281
+ const names = Array.isArray(list) ? list.filter((entry)=>'string' == typeof entry).sort() : [];
6282
+ parts.push(`${listName}:${names.join(',')}`);
6249
6283
  }
6284
+ const contentScripts = manifest.content_scripts;
6285
+ if (Array.isArray(contentScripts)) parts.push(contentScripts.map((entry)=>Object.keys(asRecord(entry)).sort().join('+')).join(';'));
6286
+ parts.push(Object.keys(asRecord(manifest.options_ui)).sort().join(','));
6287
+ return parts.join('|');
6250
6288
  }
6251
6289
  function composeProjectFingerprint(config) {
6252
6290
  return JSON.stringify({
6253
- v: 2,
6291
+ v: 4,
6254
6292
  identity: {
6255
6293
  appName: config.appName,
6256
6294
  bundleId: config.bundleIdentifier,
6257
6295
  macOsOnly: config.macOsOnly
6258
6296
  },
6259
- manifest: normalizeManifest(readManifestRaw(config.extensionDir))
6297
+ entries: topLevelEntries(config.extensionDir),
6298
+ icons: iconsFingerprint(config.extensionDir),
6299
+ judged: judgedManifestSurface(config.extensionDir)
6260
6300
  });
6261
6301
  }
6262
6302
  function saveManifestFingerprint(config) {