browser-extension-manager 1.3.48 → 1.4.0

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 (47) hide show
  1. package/README.md +109 -70
  2. package/dist/background.js +4 -0
  3. package/dist/build.js +3 -0
  4. package/dist/cli.js +1 -0
  5. package/dist/commands/test.js +55 -0
  6. package/dist/content.js +4 -0
  7. package/dist/defaults/CLAUDE.md +66 -6
  8. package/dist/gulp/tasks/defaults.js +20 -4
  9. package/dist/gulp/tasks/distribute.js +36 -33
  10. package/dist/gulp/tasks/html.js +82 -79
  11. package/dist/gulp/tasks/utils/template-transform.js +35 -35
  12. package/dist/offscreen.js +4 -0
  13. package/dist/options.js +4 -0
  14. package/dist/page.js +4 -0
  15. package/dist/popup.js +4 -0
  16. package/dist/sidepanel.js +4 -0
  17. package/dist/test/assert.js +120 -0
  18. package/dist/test/fixtures/consumer-extension/dist/background.js +15 -0
  19. package/dist/test/fixtures/consumer-extension/dist/manifest.json +20 -0
  20. package/dist/test/fixtures/consumer-extension/dist/options.html +10 -0
  21. package/dist/test/fixtures/consumer-extension/dist/popup.html +11 -0
  22. package/dist/test/harness/extension/background.js +26 -0
  23. package/dist/test/harness/extension/manifest.json +27 -0
  24. package/dist/test/harness/extension/options.html +12 -0
  25. package/dist/test/harness/extension/popup.html +12 -0
  26. package/dist/test/harness/extension/sidepanel.html +12 -0
  27. package/dist/test/index.js +63 -0
  28. package/dist/test/runner.js +407 -0
  29. package/dist/test/runners/boot.js +201 -0
  30. package/dist/test/runners/chromium.js +399 -0
  31. package/dist/test/suites/background/messaging.test.js +42 -0
  32. package/dist/test/suites/background/storage.test.js +44 -0
  33. package/dist/test/suites/background/sw-context.test.js +47 -0
  34. package/dist/test/suites/boot/extension-loads.test.js +56 -0
  35. package/dist/test/suites/build/affiliatizer.test.js +63 -0
  36. package/dist/test/suites/build/cli.test.js +123 -0
  37. package/dist/test/suites/build/expect.test.js +47 -0
  38. package/dist/test/suites/build/exports.test.js +52 -0
  39. package/dist/test/suites/build/extension-fallback.test.js +41 -0
  40. package/dist/test/suites/build/logger-lite.test.js +59 -0
  41. package/dist/test/suites/build/manager.test.js +162 -0
  42. package/dist/test/suites/build/mode-helpers.test.js +96 -0
  43. package/dist/test/suites/view/options-and-sidepanel.test.js +14 -0
  44. package/dist/test/suites/view/popup-context.test.js +51 -0
  45. package/dist/test/suites/view/sidepanel.test.js +14 -0
  46. package/dist/utils/mode-helpers.js +92 -0
  47. package/package.json +16 -13
package/README.md CHANGED
@@ -5,129 +5,168 @@
5
5
  </p>
6
6
 
7
7
  <p align="center">
8
- <img src="https://img.shields.io/github/package-json/v/itw-creative-works/ultimate-browser-extension.svg">
8
+ <img src="https://img.shields.io/github/package-json/v/itw-creative-works/browser-extension-manager.svg">
9
9
  <br>
10
- <img src="https://img.shields.io/librariesio/release/npm/ultimate-browser-extension.svg">
11
- <img src="https://img.shields.io/bundlephobia/min/ultimate-browser-extension.svg">
12
- <img src="https://img.shields.io/codeclimate/maintainability-percentage/itw-creative-works/ultimate-browser-extension.svg">
13
- <img src="https://img.shields.io/npm/dm/ultimate-browser-extension.svg">
14
- <img src="https://img.shields.io/node/v/ultimate-browser-extension.svg">
15
- <img src="https://img.shields.io/website/https/itwcreativeworks.com.svg">
16
- <img src="https://img.shields.io/github/license/itw-creative-works/ultimate-browser-extension.svg">
17
- <img src="https://img.shields.io/github/contributors/itw-creative-works/ultimate-browser-extension.svg">
18
- <img src="https://img.shields.io/github/last-commit/itw-creative-works/ultimate-browser-extension.svg">
10
+ <img src="https://img.shields.io/librariesio/release/npm/browser-extension-manager.svg">
11
+ <img src="https://img.shields.io/bundlephobia/min/browser-extension-manager.svg">
12
+ <img src="https://img.shields.io/npm/dm/browser-extension-manager.svg">
13
+ <img src="https://img.shields.io/node/v/browser-extension-manager.svg">
14
+ <img src="https://img.shields.io/github/license/itw-creative-works/browser-extension-manager.svg">
15
+ <img src="https://img.shields.io/github/contributors/itw-creative-works/browser-extension-manager.svg">
16
+ <img src="https://img.shields.io/github/last-commit/itw-creative-works/browser-extension-manager.svg">
19
17
  <br>
20
18
  <br>
21
- <a href="https://itwcreativeworks.com">Site</a> | <a href="https://www.npmjs.com/package/ultimate-browser-extension">NPM Module</a> | <a href="https://github.com/itw-creative-works/ultimate-browser-extension">GitHub Repo</a>
19
+ <a href="https://itwcreativeworks.com">Site</a> | <a href="https://www.npmjs.com/package/browser-extension-manager">NPM Module</a> | <a href="https://github.com/itw-creative-works/browser-extension-manager">GitHub Repo</a>
22
20
  <br>
23
21
  <br>
24
- <strong>Ultimate Browser Extension</strong> is a template that helps you jumpstart your Jekyll sites and is fueled by an intuitive incorporation of npm, gulp, and is fully SEO optimized and blazingly fast.
22
+ <strong>Browser Extension Manager</strong> is a framework for building modern cross-browser extensions. One-line bootstrap per context, component-based architecture, multi-browser build pipeline, cross-context auth, auto-translation across 16 languages, and a four-layer test framework.
25
23
  </p>
26
24
 
27
25
  ## 🦄 Features
28
- * **Build for Any Browser**: Export to Chrome, Firefox, Edge, and Opera.
29
- * **NPM & Gulp**: Fueled by an intuitive incorporation of npm and gulp.
26
+
27
+ - **Build for any browser**: Chrome, Firefox, Edge, Opera, Brave
28
+ - **Component architecture**: seven contexts (background / popup / options / sidepanel / content / pages / offscreen) each with view + styles + script
29
+ - **One-line bootstrap per context** with cross-browser API wrapper
30
+ - **Cross-context auth sync**: sign-in in one tab is reflected in all open contexts (no `chrome.storage` needed)
31
+ - **Auto-translation** to 16 languages via Claude CLI on every build
32
+ - **Four-layer test framework**: build / background / view / boot — real Chromium, real MV3 service worker, real consumer extensions
33
+ - **Multi-browser packaging + auto-publish** to Chrome / Firefox / Edge stores from one command
34
+ - **Theme system**: Bootstrap 5 + Classy (custom design system), or roll your own
35
+ - **SCSS load paths**: `@use 'browser-extension-manager'` / `@use 'theme'` Just Work — no relative-path hell
30
36
 
31
37
  ## 🚀 Getting started
32
- 1. [Create a repo](https://github.com/itw-creative-works/ultimate-browser-extension/generate) from the **Ultimate Browser Extension** template.
38
+
39
+ 1. [Create a repo](https://github.com/itw-creative-works/ultimate-browser-extension/generate) from the **Ultimate Browser Extension** template (or `npm i browser-extension-manager` in an existing project).
33
40
  2. Clone the repo to your local machine.
34
- 3. Run these command to get everything setup and sync'd!
41
+ 3. Set up + run:
42
+ ```bash
43
+ npm install
44
+ npx bxm setup
45
+ npm start
46
+ ```
47
+ 4. Open Chrome and navigate to `chrome://extensions`.
48
+ 5. Enable **Developer mode**.
49
+ 6. Click **Load unpacked** and select the `packaged/chromium/raw` folder in your project.
50
+ 7. Your extension is loaded and live-reloads on source changes.
51
+
52
+ ## 📦 Sync with the template
53
+
54
+ Run `npx bxm setup` again to pull the latest framework defaults. Files you've edited are preserved; only missing or framework-owned files update.
55
+
56
+ ## 🧪 Testing
57
+
58
+ BXM ships a built-in four-layer test framework. Write tests under `test/<layer>/*.test.js` and run with:
59
+
35
60
  ```bash
36
- npm install
37
- npx bxm setup
38
- npm start
61
+ npx bxm test # all layers
62
+ npx bxm test --layer build # build layer only (plain Node, fast)
63
+ npx bxm test --layer boot # real-Chromium end-to-end test
39
64
  ```
40
- 4. Open your browser and navigate to `chrome://extensions` (or the equivalent for your browser).
41
- 5. Enable **Developer mode**.
42
- 6. Click on **Load unpacked** and select the `dist` folder in your project directory.
43
- 7. Your extension should now be loaded and ready to use!
44
65
 
45
- ## 📦 How to sync with the template
46
- 1. Simply run `npx bxm setup` in Terminal to get all the latest updates from the **Ultimate Browser Extension template**.
66
+ Test files use Jest-compatible matchers:
67
+
68
+ ```js
69
+ // test/build/manifest.test.js
70
+ const Manager = require('browser-extension-manager/build');
71
+
72
+ module.exports = {
73
+ layer: 'build',
74
+ description: 'manifest is valid MV3',
75
+ run: (ctx) => {
76
+ const m = Manager.getManifest();
77
+ ctx.expect(m.manifest_version).toBe(3);
78
+ ctx.expect(m.permissions).toContain('storage');
79
+ },
80
+ };
81
+ ```
82
+
83
+ Full guide: [docs/test-framework.md](docs/test-framework.md). End-to-end "did my packaged extension actually boot in Chrome?" tests: [docs/test-boot-layer.md](docs/test-boot-layer.md).
84
+
85
+ ## 🌐 Auto-translation
86
+
87
+ When you run `npm run build`, BXM auto-translates `src/_locales/en/messages.json` to 16 languages via Claude CLI:
47
88
 
48
- ## 🌐 Automatic Translation
49
- When you run `npm run build`, BXM automatically translates your `src/_locales/en/messages.json` to 16 languages using Claude CLI:
50
89
  `zh`, `es`, `hi`, `ar`, `pt`, `ru`, `ja`, `de`, `fr`, `ko`, `ur`, `id`, `bn`, `tl`, `vi`, `it`
51
90
 
52
- Only missing translations are generated - existing translations are preserved.
91
+ Only missing translations are generated existing translations are preserved. Full guide: [docs/translations.md](docs/translations.md).
53
92
 
54
93
  ## 🌎 Publishing your extension
55
94
 
56
- ### Manual Upload
57
- 1. Run `npm run build` in Terminal to build your extension for production.
58
- 2. Upload the `.zip` file to the browser's extension store.
95
+ ### Manual upload
96
+
97
+ ```bash
98
+ npm run build
99
+ ```
100
+
101
+ Upload the `.zip` files under `packaged/<browser>/` to each browser's extension store.
59
102
 
60
- ### Automatic Publishing
61
- BXM can automatically publish to Chrome, Firefox, and Edge stores when `BXM_IS_PUBLISH=true`:
103
+ ### Automatic publishing
62
104
 
63
105
  ```bash
64
106
  BXM_IS_PUBLISH=true npm run build
65
107
  ```
66
108
 
67
- **Setup:** Add store credentials to your `.env` file:
109
+ Add store credentials to your `.env`:
68
110
 
69
111
  ```bash
70
112
  # Chrome Web Store
71
- CHROME_EXTENSION_ID="your-extension-id"
72
- CHROME_CLIENT_ID="your-client-id"
73
- CHROME_CLIENT_SECRET="your-client-secret"
74
- CHROME_REFRESH_TOKEN="your-refresh-token"
113
+ CHROME_EXTENSION_ID="..."
114
+ CHROME_CLIENT_ID="..."
115
+ CHROME_CLIENT_SECRET="..."
116
+ CHROME_REFRESH_TOKEN="..."
75
117
 
76
118
  # Firefox Add-ons
77
- FIREFOX_EXTENSION_ID="your-extension-id"
78
- FIREFOX_API_KEY="your-api-key"
79
- FIREFOX_API_SECRET="your-api-secret"
119
+ FIREFOX_EXTENSION_ID="..."
120
+ FIREFOX_API_KEY="..."
121
+ FIREFOX_API_SECRET="..."
80
122
 
81
123
  # Microsoft Edge Add-ons
82
- EDGE_PRODUCT_ID="your-product-id"
83
- EDGE_CLIENT_ID="your-client-id"
84
- EDGE_API_KEY="your-api-key"
124
+ EDGE_PRODUCT_ID="..."
125
+ EDGE_CLIENT_ID="..."
126
+ EDGE_API_KEY="..."
85
127
  ```
86
128
 
87
- Only stores with configured credentials will be published to.
129
+ Only stores with configured credentials get published to. Full guide: [docs/publishing.md](docs/publishing.md).
88
130
 
89
131
  ## 🔐 Authentication
90
132
 
91
- BXM provides built-in authentication that syncs across all extension contexts (popup, options, pages, sidepanel, background).
92
-
93
- ### How It Works
133
+ BXM provides built-in cross-context authentication that syncs across all extension contexts (popup, options, sidepanel, pages, background) without using `chrome.storage`.
94
134
 
95
- **Background.js is the source of truth.** Auth syncs via messaging (no storage).
135
+ **Background.js is the source of truth.** Auth syncs via messaging sign-in / sign-out events propagate across all open contexts, and new contexts handshake with background on load.
96
136
 
97
- - **Sign-in**: User clicks `.auth-signin-btn` → opens `/token` page on website → website authenticates and redirects with token → background.js signs in and broadcasts to all open contexts
98
- - **Context load**: Each context compares its UID with background's UID on load; syncs if different
99
- - **Sign-out**: User clicks `.auth-signout-btn` → context signs out → notifies background → background broadcasts sign-out to all contexts
100
-
101
- ### Required Setup
137
+ ### Setup
102
138
 
103
139
  1. Add `authDomain` to your Firebase config in `config/browser-extension-manager.json`
104
- 2. Add `tabs` permission to `src/manifest.json` (for URL monitoring)
140
+ 2. Add `tabs` permission to `src/manifest.json`
141
+
142
+ ### Auth button classes
105
143
 
106
- ### Auth Button Classes
144
+ Add these CSS classes to HTML elements for declarative auth UI:
107
145
 
108
146
  | Class | Action |
109
- |-------|--------|
110
- | `.auth-signin-btn` | Opens `/token` page on website |
111
- | `.auth-signout-btn` | Signs out via Web Manager |
112
- | `.auth-account-btn` | Opens `/account` page on website |
147
+ |---|---|
148
+ | `.auth-signin-btn` | Opens `/token` page on your website |
149
+ | `.auth-signout-btn` | Signs out via Web Manager (broadcasts to all contexts) |
150
+ | `.auth-account-btn` | Opens `/account` page on your website |
113
151
 
114
- ### Example
115
152
  ```html
116
153
  <button class="btn auth-signin-btn" data-wm-bind="@show !auth.user">Sign In</button>
117
154
 
118
155
  <div data-wm-bind="@show auth.user" hidden>
156
+ <img data-wm-bind="@attr src auth.user.photoURL">
119
157
  <span data-wm-bind="@text auth.user.displayName">User</span>
120
158
  <button class="auth-signout-btn">Sign Out</button>
121
159
  </div>
122
160
  ```
123
161
 
124
- ### Reactive Bindings
125
- - `@show auth.user` / `@show !auth.user` - Show/hide based on auth state
126
- - `@text auth.user.displayName` / `@text auth.user.email` - Display user info
127
- - `@attr src auth.user.photoURL` - Set avatar image
162
+ Full guide: [docs/auth.md](docs/auth.md).
128
163
 
129
- <!-- ## ⛳️ Flags
130
- * `--test=false` - Coming soon
131
- ```bash
132
- npm start -- --test=false
133
- ``` -->
164
+ ## 📚 Documentation
165
+
166
+ In-depth docs for every subsystem live in [docs/](docs/). See [CLAUDE.md](CLAUDE.md) for the architecture overview + table of contents.
167
+
168
+ ## 🧰 Sister projects
169
+
170
+ - [Electron Manager (EM)](https://github.com/itw-creative-works/electron-manager) — same patterns, but for Electron desktop apps
171
+ - [Ultimate Jekyll Manager (UJM)](https://github.com/itw-creative-works/ultimate-jekyll-manager) — Jekyll static-site framework
172
+ - [Backend Manager (BEM)](https://github.com/itw-creative-works/backend-manager) — Firebase Functions backend framework
@@ -1,6 +1,7 @@
1
1
  // Libraries
2
2
  import extension from './lib/extension.js';
3
3
  import LoggerLite from './lib/logger-lite.js';
4
+ import { attachTo as attachModeHelpers } from './utils/mode-helpers.js';
4
5
 
5
6
  // Firebase (static imports - dynamic import() doesn't work in service workers with webpack chunking)
6
7
  import { initializeApp, getApp } from 'firebase/app';
@@ -660,5 +661,8 @@ function setupGlobalHandlers() {
660
661
  });
661
662
  }
662
663
 
664
+ // Cross-context helpers — Manager.isTesting() / isDevelopment() / etc.
665
+ attachModeHelpers(Manager);
666
+
663
667
  // Export
664
668
  export default Manager;
package/dist/build.js CHANGED
@@ -196,5 +196,8 @@ Manager.logMemory = function (logger, label) {
196
196
  };
197
197
  Manager.prototype.logMemory = Manager.logMemory;
198
198
 
199
+ // Cross-context helpers — Manager.isTesting() / isDevelopment() / etc.
200
+ require('./utils/mode-helpers.js').attachTo(Manager);
201
+
199
202
  // Export
200
203
  module.exports = Manager;
package/dist/cli.js CHANGED
@@ -11,6 +11,7 @@ const ALIASES = {
11
11
  clean: ['-c', '--clean'],
12
12
  install: ['-i', 'i', '--install'],
13
13
  setup: ['-s', '--setup'],
14
+ test: ['-t', '--test'],
14
15
  version: ['-v', '--version'],
15
16
  };
16
17
 
@@ -0,0 +1,55 @@
1
+ // Libraries
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const Manager = new (require('../build.js'));
5
+ const logger = Manager.logger('test');
6
+ const { run } = require('../test/runner.js');
7
+
8
+ module.exports = async function (options) {
9
+ const layer = options.layer || 'all';
10
+ const filter = options.filter || null;
11
+ const reporter = options.reporter || 'pretty';
12
+ const integration = options.integration === true || options.integration === 'true';
13
+
14
+ if (integration) {
15
+ process.env.BXM_TEST_INTEGRATION = '1';
16
+ }
17
+
18
+ // Canonical signal — every Manager picks this up via isTesting().
19
+ process.env.BXM_TEST_MODE = 'true';
20
+
21
+ // When BXM itself runs its own boot-layer tests (the cwd's package.json is
22
+ // BXM's package.json), there's no real consumer extension to target. Point
23
+ // the boot runner at the fixture under dist/test/fixtures/consumer-extension
24
+ // unless the caller has already set BXM_TEST_BOOT_PROJECT explicitly.
25
+ if (!process.env.BXM_TEST_BOOT_PROJECT) {
26
+ try {
27
+ const cwdPkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
28
+ if (cwdPkg.name === 'browser-extension-manager') {
29
+ process.env.BXM_TEST_BOOT_PROJECT = path.join(__dirname, '..', 'test', 'fixtures', 'consumer-extension');
30
+ }
31
+ } catch (_) { /* no package.json — leave unset */ }
32
+ }
33
+
34
+ if (reporter !== 'json') {
35
+ logger.log(`Running tests (layer=${layer}${filter ? ` filter="${filter}"` : ''}${integration ? ' +integration' : ''})`);
36
+ }
37
+
38
+ const result = await run({ layer, filter, reporter });
39
+
40
+ if (reporter === 'json') {
41
+ // Final machine-readable summary.
42
+ process.stdout.write(JSON.stringify({
43
+ event: 'summary',
44
+ passed: result.passed,
45
+ failed: result.failed,
46
+ skipped: result.skipped,
47
+ total: result.passed + result.failed + result.skipped,
48
+ }) + '\n');
49
+ }
50
+
51
+ if (result.failed > 0) {
52
+ process.exitCode = 1;
53
+ throw new Error(`${result.failed} test(s) failed`);
54
+ }
55
+ };
package/dist/content.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import extension from './lib/extension.js';
3
3
  import LoggerLite from './lib/logger-lite.js';
4
4
  import Affiliatizer from './lib/affiliatizer.js';
5
+ import { attachTo as attachModeHelpers } from './utils/mode-helpers.js';
5
6
 
6
7
  // Class
7
8
  class Manager {
@@ -28,5 +29,8 @@ class Manager {
28
29
  }
29
30
  }
30
31
 
32
+ // Cross-context helpers — Manager.isTesting() / isDevelopment() / etc.
33
+ attachModeHelpers(Manager);
34
+
31
35
  // Export
32
36
  export default Manager;
@@ -1,8 +1,68 @@
1
- # Identity
2
- This is a browser extension that is "consuming" a browser extension template project called Browser Extension Manager (BXM)--a collection of components that can be used to build a browser extension quickly and efficiently.
1
+ # ========== Default Values ==========
2
+ # Browser Extension Manager (BXM) consumer project
3
3
 
4
- ## BXM Documentation
5
- You should have a full understanding of Browser Extension Manager before editing this project, which can be found at: node_modules/browser-extension-manager/CLAUDE.md
4
+ > **Auto-managed file.** Everything between `# ========== Default Values ==========` and `# ========== Custom Values ==========` is owned by `browser-extension-manager` and rewritten on every `npx mgr setup`. Put your own project-specific notes BELOW the `Custom Values` marker — that section is preserved verbatim across setups.
6
5
 
7
- ## Project-specific Notes
8
- Add any notes specific to this project here.
6
+ ## Framework
7
+
8
+ This project consumes **Browser Extension Manager** (BXM) — a comprehensive framework for building modern cross-browser extensions (Chrome, Firefox, Edge, Opera, Brave). BXM provides one-line bootstrap per extension context, a component-based architecture (view + styles + script per context), a multi-browser build/release pipeline that produces store-uploadable zips, cross-context auth synchronization, and a built-in four-layer test framework.
9
+
10
+ **Framework's own docs** (read these for deep-dives; both paths point to the same files, the absolute path works regardless of working directory):
11
+ - Top-level overview: `/Users/ian/Developer/Repositories/ITW-Creative-Works/browser-extension-manager/CLAUDE.md` (or `node_modules/browser-extension-manager/CLAUDE.md`)
12
+ - Subsystem references: `/Users/ian/Developer/Repositories/ITW-Creative-Works/browser-extension-manager/docs/` (or `node_modules/browser-extension-manager/docs/`)
13
+
14
+ ## Quick start
15
+
16
+ ```bash
17
+ npm start # dev with live reload (gulp → webpack → serve)
18
+ npm run build # production build → dist/ + packaged/<browser>/raw/ + .zip per browser
19
+ BXM_IS_PUBLISH=true npm run build # build + auto-upload to Chrome / Firefox / Edge stores
20
+ npx mgr test # run framework + project test suites
21
+ ```
22
+
23
+ Load the unpacked extension in Chrome: point chrome://extensions → "Load unpacked" at `packaged/chromium/raw/`.
24
+
25
+ ## Where things live
26
+
27
+ - `config/browser-extension-manager.json` — JSON5 config: brand, manifest overrides, build settings, theme. `Manager.getConfig()` reads this.
28
+ - `config/messages.json` — i18n source. Auto-translated to 16 languages at build time via the Claude CLI (only missing keys regenerated).
29
+ - `config/description.md` — store-listing description (used by the publish step).
30
+ - `src/manifest.json` — extension manifest. BXM merges its defaults in at build time; you only need to declare what's specific to your extension.
31
+ - `src/views/<context>/index.html` — per-context HTML (popup / options / sidepanel / pages).
32
+ - `src/assets/js/components/<context>/index.js` — per-context script entry. One-line bootstrap of `browser-extension-manager/<context>`.
33
+ - `src/assets/css/components/<context>/index.scss` — per-context styles.
34
+ - `src/assets/js/components/background.js` — MV3 service worker entry. Source of truth for auth + messaging.
35
+ - `src/_locales/en/messages.json` — Chrome `__MSG_*__` placeholders (auto-translated to 16 langs at build).
36
+ - `hooks/build/{pre,post}.js` — optional lifecycle hooks.
37
+ - `test/**/*.js` — your project test suites (framework auto-runs them alongside its own).
38
+
39
+ ## Per-context imports
40
+
41
+ ```js
42
+ // src/assets/js/components/popup/index.js
43
+ import Manager from 'browser-extension-manager/popup';
44
+ await new Manager().initialize();
45
+
46
+ // src/assets/js/components/background.js (service worker)
47
+ import Manager from 'browser-extension-manager/background';
48
+ await new Manager().initialize();
49
+
50
+ // Same shape for options / sidepanel / content / page / offscreen
51
+ ```
52
+
53
+ ## Available APIs at runtime
54
+
55
+ After `initialize()`, every Manager exposes:
56
+ - `manager.extension` — cross-browser `chrome.*` / `browser.*` / `window.*` wrapper
57
+ - `manager.logger` — timestamped per-context logger
58
+ - `manager.webManager` — Web Manager singleton (Firebase, auth, analytics, reactive `data-wm-bind` directives)
59
+ - `manager.messenger` — `chrome.runtime.onMessage` listener wired automatically
60
+ - `manager.isDevelopment()` / `isProduction()` / `isTesting()` / `getVersion()` — cross-context helpers
61
+
62
+ Auth UI is declarative — add `.auth-signin-btn` / `.auth-signout-btn` / `.auth-account-btn` to buttons; BXM wires them. Show/hide based on auth state via `data-wm-bind="@show auth.user"`.
63
+
64
+ # ========== Custom Values ==========
65
+
66
+ ## Project-specific notes
67
+
68
+ Add anything specific to THIS project here. Edits below this line are preserved across `npx mgr setup` runs.
@@ -2,7 +2,7 @@
2
2
  const Manager = new (require('../../build.js'));
3
3
  const logger = Manager.logger('defaults');
4
4
  const { src, dest, watch, series } = require('gulp');
5
- const through2 = require('through2');
5
+ const { Transform } = require('node:stream');
6
6
  const jetpack = require('fs-jetpack');
7
7
  const path = require('path');
8
8
  const { minimatch } = require('minimatch');
@@ -66,6 +66,15 @@ const FILE_MAP = {
66
66
  mergeLines: true,
67
67
  },
68
68
 
69
+ // Consumer CLAUDE.md uses the same marker-based merge as .env/.gitignore.
70
+ // Framework owns the Default section; consumer owns everything below the Custom marker.
71
+ // Must come AFTER `**/*.md` (which sets overwrite: false) — `getFileOptions` does
72
+ // last-match-wins, so this rule's `mergeLines: true` activates the merge path even though
73
+ // the catch-all would otherwise skip.
74
+ 'CLAUDE.md': {
75
+ mergeLines: true,
76
+ },
77
+
69
78
  // Config files
70
79
  'config/browser-extension-manager.json': {
71
80
  overwrite: true,
@@ -270,8 +279,12 @@ function mergeLineBasedFiles(existingContent, newContent, fileName) {
270
279
  result.push(DEFAULT_SECTION_MARKER);
271
280
  result.push(...mergedDefaultSection);
272
281
 
273
- // Add custom section
274
- result.push('');
282
+ // Add custom section. Insert a single blank line before the marker, but only if the
283
+ // merged default doesn't already end with one — otherwise we'd accumulate an extra blank
284
+ // line on every merge, breaking idempotency after the first re-run.
285
+ if (mergedDefaultSection.length === 0 || mergedDefaultSection[mergedDefaultSection.length - 1].trim() !== '') {
286
+ result.push('');
287
+ }
275
288
  result.push(CUSTOM_SECTION_MARKER);
276
289
 
277
290
  // First add any user lines that were in default section (moved to custom)
@@ -347,7 +360,9 @@ function defaults(complete, changedFile) {
347
360
  }
348
361
 
349
362
  function customTransform() {
350
- return through2.obj(function (file, _, callback) {
363
+ return new Transform({
364
+ objectMode: true,
365
+ transform(file, _, callback) {
351
366
  // Skip if it's a directory
352
367
  if (file.isDirectory()) {
353
368
  return callback(null, file);
@@ -477,6 +492,7 @@ function customTransform() {
477
492
 
478
493
  // Complete
479
494
  return callback();
495
+ },
480
496
  });
481
497
  }
482
498
  function defaultsWatcher(complete) {
@@ -2,7 +2,7 @@
2
2
  const Manager = new (require('../../build.js'));
3
3
  const logger = Manager.logger('distribute');
4
4
  const { src, dest, watch, series } = require('gulp');
5
- const through2 = require('through2');
5
+ const { Transform } = require('node:stream');
6
6
  const path = require('path');
7
7
  const jetpack = require('fs-jetpack');
8
8
  const createTemplateTransform = require('./utils/template-transform');
@@ -101,38 +101,41 @@ function distribute() {
101
101
  }
102
102
 
103
103
  function customTransform() {
104
- return through2.obj(function (file, _, callback) {
105
- // Skip if it's a directory
106
- if (file.isDirectory()) {
107
- return callback(null, file);
108
- }
109
-
110
- // Get relative path from src base
111
- const relativePath = path.relative(file.base, file.path).replace(/\\/g, '/');
112
-
113
- // Log
114
- if (LOUD) {
115
- logger.log(`Processing file: ${relativePath}`);
116
- }
117
-
118
- // Change path if it starts with 'pages/'
119
- // if (relativePath.startsWith('pages/')) {
120
- // // Remove 'pages/' prefix
121
- // const newRelativePath = relativePath.replace(/^pages\//, '');
122
-
123
- // // Update file path to remove pages directory
124
- // // This will make src/pages/index.html -> dist/index.html
125
- // file.path = path.join(file.base, newRelativePath);
126
-
127
- // // Log
128
- // logger.log(` -> Moving from pages/ to root: ${newRelativePath}`);
129
- // }
130
-
131
- // Push the file
132
- this.push(file);
133
-
134
- // Continue
135
- callback();
104
+ return new Transform({
105
+ objectMode: true,
106
+ transform(file, _, callback) {
107
+ // Skip if it's a directory
108
+ if (file.isDirectory()) {
109
+ return callback(null, file);
110
+ }
111
+
112
+ // Get relative path from src base
113
+ const relativePath = path.relative(file.base, file.path).replace(/\\/g, '/');
114
+
115
+ // Log
116
+ if (LOUD) {
117
+ logger.log(`Processing file: ${relativePath}`);
118
+ }
119
+
120
+ // Change path if it starts with 'pages/'
121
+ // if (relativePath.startsWith('pages/')) {
122
+ // // Remove 'pages/' prefix
123
+ // const newRelativePath = relativePath.replace(/^pages\//, '');
124
+
125
+ // // Update file path to remove pages directory
126
+ // // This will make src/pages/index.html -> dist/index.html
127
+ // file.path = path.join(file.base, newRelativePath);
128
+
129
+ // // Log
130
+ // logger.log(` -> Moving from pages/ to root: ${newRelativePath}`);
131
+ // }
132
+
133
+ // Push the file
134
+ this.push(file);
135
+
136
+ // Continue
137
+ callback();
138
+ },
136
139
  });
137
140
  }
138
141