addonova 1.0.2 → 1.0.4

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 (59) hide show
  1. package/README.md +117 -48
  2. package/bin/addonova.js +4 -0
  3. package/bin/index.js +1 -60
  4. package/package.json +20 -29
  5. package/src/build/browser.js +143 -0
  6. package/{workspace/tasks → src/build}/build.js +16 -2
  7. package/src/build/bundle-html.js +120 -0
  8. package/{workspace/tasks → src/build}/cli.js +4 -2
  9. package/src/cli/help.js +16 -0
  10. package/src/cli/index.js +58 -0
  11. package/src/commands/init.js +14 -18
  12. package/src/commands/tool.js +32 -0
  13. package/src/index.js +0 -1
  14. package/src/tools/tools-server.js +287 -0
  15. package/src/tools/tools.html +388 -0
  16. package/templates/extension/config/firefox.js +30 -0
  17. package/templates/extension/config/thunderbird.js +30 -0
  18. package/templates/extension/package.json.tpl +36 -0
  19. package/{template → templates/extension}/platform/naver/platform.js +0 -15
  20. package/src/commands/update.js +0 -67
  21. package/template/config/firefox.js +0 -29
  22. package/template/config/thunderbird.js +0 -29
  23. package/template/package.json.tpl +0 -54
  24. package/workspace/tasks/bundle-html.js +0 -91
  25. package/workspace/tools/json2i18n.js +0 -37
  26. package/workspace/tools/translate.js +0 -299
  27. /package/{workspace/tasks → src/build}/bundle-css.js +0 -0
  28. /package/{workspace/tasks → src/build}/bundle-js.js +0 -0
  29. /package/{workspace/tasks → src/build}/bundle-locales.js +0 -0
  30. /package/{workspace/tasks → src/build}/bundle-manifest.js +0 -0
  31. /package/{workspace/tasks → src/build}/copy.js +0 -0
  32. /package/{workspace/tasks → src/build}/folder.js +0 -0
  33. /package/{workspace/tasks → src/build}/paths.js +0 -0
  34. /package/{workspace/tasks → src/build}/task.js +0 -0
  35. /package/{workspace/tasks → src/build}/utils.js +0 -0
  36. /package/{workspace/tasks → src/build}/watch.js +0 -0
  37. /package/{workspace/tasks → src/build}/zip.js +0 -0
  38. /package/{template → templates/extension}/config/chrome.js +0 -0
  39. /package/{template → templates/extension}/config/edge.js +0 -0
  40. /package/{template → templates/extension}/config/naver.js +0 -0
  41. /package/{template → templates/extension}/config/opera.js +0 -0
  42. /package/{template → templates/extension}/platform/chrome/platform.js +0 -0
  43. /package/{template → templates/extension}/platform/edge/platform.js +0 -0
  44. /package/{template → templates/extension}/platform/opera/platform.js +0 -0
  45. /package/{template → templates/extension}/src/_locales/en.i18n +0 -0
  46. /package/{template → templates/extension}/src/assets/icons/128.png +0 -0
  47. /package/{template → templates/extension}/src/assets/icons/32.png +0 -0
  48. /package/{template → templates/extension}/src/assets/icons/48.png +0 -0
  49. /package/{template → templates/extension}/src/assets/icons/64.png +0 -0
  50. /package/{template → templates/extension}/src/css/popup.css +0 -0
  51. /package/{template → templates/extension}/src/html/popup.html +0 -0
  52. /package/{template → templates/extension}/src/js/background.js +0 -0
  53. /package/{template → templates/extension}/src/js/lib/browser.js +0 -0
  54. /package/{template → templates/extension}/src/js/lib/common.js +0 -0
  55. /package/{template → templates/extension}/src/js/lib/config.js +0 -0
  56. /package/{template → templates/extension}/src/js/lib/runtime.js +0 -0
  57. /package/{template → templates/extension}/src/js/popup.js +0 -0
  58. /package/{template → templates/extension}/src/manifest/manifest-firefox.json +0 -0
  59. /package/{template → templates/extension}/src/manifest/manifest.json +0 -0
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # addonova
1
+ # Addonova
2
2
 
3
- Next-Generation Web Extension Framework
3
+ [![Addonova Social Banner](https://raw.githubusercontent.com/code-hemu/addonova/refs/heads/main/docs/assets/capture.jpg)](https://github.com/code-hemu/addonova)
4
4
 
5
- Scaffold and build cross-browser WebExtensions with a modern toolchain.
5
+ Addonova is a framework that allows developers to build extensions for multiple browsers. Developers can easily build, test, and manage extensions.
6
6
 
7
- ## Usage
7
+ ## Quick Start
8
8
 
9
9
  ```bash
10
10
  npx addonova init my-extension
@@ -21,75 +21,144 @@ npm run release
21
21
  - Naver Whale
22
22
  - Thunderbird
23
23
 
24
- ## Commands
24
+ ## CLI Commands
25
25
 
26
26
  | Command | Description |
27
- |---------|-------------|
28
- | `init <name>` | Scaffold a new extension project |
29
- | `update` | Update workspace/build files to latest template |
30
- | `build [options]` | Build the extension |
31
- | `zip` | Create zip bundles |
32
- | `--help` | Show help |
27
+ | --- | --- |
28
+ | `addonova init <name>` | Scaffold a new extension project |
29
+ | `addonova build [options]` | Build the current extension project |
30
+ | `addonova zip` | Create release zip bundles |
31
+ | `addonova tool` | Open the i18n tools UI in a browser |
32
+ | `addonova --help` | Show help |
33
33
 
34
- ## Build Targets
34
+ ## Build Scripts
35
+
36
+ Generated projects include these npm scripts:
37
+
38
+ ```bash
39
+ npm run release
40
+ npm run release:chrome
41
+ npm run release:edge
42
+ npm run release:opera
43
+ npm run release:firefox
44
+ npm run release:thunderbird
45
+ npm run release:naver
46
+
47
+ npm run debug
48
+ npm run debug:chrome
49
+ npm run debug:edge
50
+ npm run debug:opera
51
+ npm run debug:firefox
52
+ npm run debug:thunderbird
53
+ npm run debug:naver
54
+
55
+ npm run dev
56
+ npm run zip
57
+ npm test
58
+ ```
59
+
60
+ You can also call the CLI directly:
35
61
 
36
62
  ```bash
37
- npm run release # Release build (all)
38
- npm run release:chrome # Release (Chrome)
39
- npm run release:firefox # Release (Firefox)
40
- npm run debug # Debug build (all)
41
- npm run debug:chrome # Debug (Chrome)
42
- npm run watch # Watch mode
43
- npm run zip # Create zip bundles
44
- npx addonova build --all --release # Build via CLI
45
- npx addonova --help # Show help
63
+ npx addonova build --all --release
64
+ npx addonova build --chrome --debug
65
+ npx addonova build --all --debug --watch
66
+ npx addonova zip
67
+ npx addonova tool
46
68
  ```
47
69
 
48
- ## Project Structure
70
+ ## Build Options
71
+
72
+ | Option | Description |
73
+ | --- | --- |
74
+ | `--all` | Build all configured browsers |
75
+ | `--chrome` | Build Chrome target |
76
+ | `--edge` | Build Microsoft Edge target |
77
+ | `--firefox` | Build Firefox target |
78
+ | `--opera` | Build Opera target |
79
+ | `--naver` | Build Naver Whale target |
80
+ | `--thunderbird` | Build Thunderbird target |
81
+ | `--release` | Create release build |
82
+ | `--debug` | Create debug build |
83
+ | `--watch` | Rebuild when files change and reload opened extensions |
84
+ | `--open` | Open a browser with the debug extension loaded |
85
+ | `--test` | Build test version |
86
+ | `--version=x.x.x` | Append version to output zip names |
87
+
88
+ ## Dev Mode
49
89
 
90
+ ```bash
91
+ npm run dev
50
92
  ```
93
+
94
+ Dev mode runs:
95
+
96
+ ```bash
97
+ addonova build --all --debug --watch
98
+ ```
99
+
100
+ Addonova opens an isolated browser profile, loads the unpacked debug extension from `.output/debug/<browser>`, watches source files, rebuilds changed assets, and reloads the extension when the built files change.
101
+
102
+ ## Generated Extension Structure
103
+
104
+ ```txt
51
105
  my-extension/
52
- ├── config/ # Build config per browser
53
- │ ├── chrome.js
54
- │ ├── firefox.js
55
- └── ...
56
- ├── platform/ # Platform-specific polyfills
57
- ├── chrome/platform.js
58
- ├── firefox/platform.js
59
- └── ...
60
- ├── src/
61
- │ ├── _locales/ # i18n locale files (.i18n)
62
- │ ├── assets/ # Static assets (icons, etc.)
63
- │ ├── css/ # Stylesheets
64
- │ ├── html/ # HTML pages
65
- │ ├── js/ # JavaScript source
66
- │ │ ├── background.js
67
- │ │ ├── popup.js
68
- │ │ └── lib/ # Shared runtime libs
69
- │ └── manifest/ # Manifest JSON templates
70
- ├── .output/ # Build output
71
- │ ├── release/ # Release builds
72
- │ └── debug/ # Debug builds
73
- └── package.json
106
+ |-- config/
107
+ |-- platform/
108
+ |-- src/
109
+ | |-- _locales/
110
+ | |-- assets/
111
+ | |-- css/
112
+ | |-- html/
113
+ | |-- js/
114
+ | `-- manifest/
115
+ |-- .output/
116
+ `-- package.json
74
117
  ```
75
118
 
76
119
  ## Locales / i18n
77
120
 
78
121
  Locale files use the `.i18n` format:
79
122
 
80
- ```
123
+ ```txt
81
124
  @extensionName
82
125
  My Extension
126
+
83
127
  @extensionDescription
84
128
  This is my extension description.
85
129
  ```
86
130
 
87
- Run the interactive message manager:
131
+ Run the interactive message manager from a generated project:
132
+
133
+ ```bash
134
+ npm run tool
135
+ ```
136
+
137
+ Or use the i18n tools UI:
138
+
139
+ ```bash
140
+ npx addonova tool
141
+ ```
142
+
143
+ This opens a full UI at `http://localhost:9876` with:
144
+ - **Translate tab** — view all locale messages, add new messages with auto-translation, delete messages
145
+ - **JSON → i18n tab** — drag-and-drop a `messages.json` file to convert to `.i18n` format
146
+
147
+ ## Development
148
+
149
+ Run the test suite:
150
+
151
+ ```bash
152
+ npm test
153
+ ```
154
+
155
+ Check what will be published:
88
156
 
89
157
  ```bash
90
- node node_modules/addonova/workspace/tools/translate.js
158
+ npm pack --dry-run
91
159
  ```
92
160
 
93
161
  ## Requirements
94
162
 
95
- - Node.js >= 18
163
+ - Addonova package: Node.js >= 20.19
164
+ - Generated extension template: Node.js >= 22 and npm >= 11
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from '../src/cli/index.js';
3
+
4
+ await runCli(process.argv.slice(2));
package/bin/index.js CHANGED
@@ -1,61 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import { fork } from 'node:child_process';
3
- import process from 'node:process';
4
- import { dirname, resolve } from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
- import { existsSync } from 'node:fs';
7
-
8
- const __dirname = dirname(fileURLToPath(import.meta.url));
9
- import { init } from '../src/commands/init.js';
10
- import { update } from '../src/commands/update.js';
11
-
12
- const [command, ...args] = process.argv.slice(2);
13
-
14
- switch (command) {
15
- case 'init':
16
- await init(args);
17
- break;
18
- case 'update':
19
- await update();
20
- break;
21
- case 'build':
22
- case 'zip': {
23
- const cliPath = resolve(__dirname, '../workspace/tasks/cli.js');
24
- if (!existsSync(cliPath)) {
25
- console.error('[*] Addonova workspace not found. Reinstall the package.');
26
- process.exit(1);
27
- }
28
- const child = fork(cliPath, [command, ...args], {
29
- execArgv: ['--max-old-space-size=3072']
30
- });
31
- process.on('SIGINT', () => {
32
- child.kill('SIGKILL');
33
- process.exit(130);
34
- });
35
- await new Promise((resolve, reject) =>
36
- child.on('error', reject).on('close', (code) => {
37
- if (code !== 0) process.exit(code);
38
- resolve();
39
- })
40
- );
41
- break;
42
- }
43
- case '--help':
44
- case '-h':
45
- default:
46
- console.log(`
47
- Addonova — browser extension toolkit
48
-
49
- Usage:
50
- npx addonova init <my-extension> Scaffold a new extension project
51
- npx addonova update Update workspace/build files in existing project
52
- npx addonova build [options] Build the extension
53
- npx addonova zip Create zip bundles
54
- npx addonova --help Show this help
55
-
56
- Build options:
57
- --all, --chrome, --firefox, --edge, --opera, --naver, --thunderbird
58
- --release, --debug, --watch, --test, --version=x.x.x
59
- `);
60
- break;
61
- }
2
+ import './addonova.js';
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "addonova",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Web Extension Framework",
5
5
  "type": "module",
6
+ "scripts": {
7
+ "test": "node --test"
8
+ },
6
9
  "bin": {
7
- "addonova": "./bin/index.js"
10
+ "addonova": "./bin/addonova.js"
8
11
  },
9
12
  "exports": {
10
13
  ".": "./src/index.js",
@@ -13,40 +16,28 @@
13
16
  "files": [
14
17
  "bin/",
15
18
  "src/",
16
- "workspace/",
17
- "template/"
19
+ "templates/"
18
20
  ],
19
- "scripts": {
20
- "addonova-release": "node --max-old-space-size=3072 workspace/tasks/cli.js build --all --release",
21
- "addonova-release:chrome": "node --max-old-space-size=3072 workspace/tasks/cli.js build --chrome --release",
22
- "addonova-release:edge": "node --max-old-space-size=3072 workspace/tasks/cli.js build --edge --release",
23
- "addonova-release:opera": "node --max-old-space-size=3072 workspace/tasks/cli.js build --opera --release",
24
- "addonova-release:firefox": "node --max-old-space-size=3072 workspace/tasks/cli.js build --firefox --release",
25
- "addonova-release:thunderbird": "node --max-old-space-size=3072 workspace/tasks/cli.js build --thunderbird --release",
26
- "addonova-release:naver": "node --max-old-space-size=3072 workspace/tasks/cli.js build --naver --release",
27
- "addonova-debug": "node --max-old-space-size=3072 workspace/tasks/cli.js build --all --debug",
28
- "addonova-debug:chrome": "node --max-old-space-size=3072 workspace/tasks/cli.js build --chrome --debug",
29
- "addonova-debug:edge": "node --max-old-space-size=3072 workspace/tasks/cli.js build --edge --debug",
30
- "addonova-debug:opera": "node --max-old-space-size=3072 workspace/tasks/cli.js build --opera --debug",
31
- "addonova-debug:firefox": "node --max-old-space-size=3072 workspace/tasks/cli.js build --firefox --debug",
32
- "addonova-debug:thunderbird": "node --max-old-space-size=3072 workspace/tasks/cli.js build --thunderbird --debug",
33
- "addonova-debug:naver": "node --max-old-space-size=3072 workspace/tasks/cli.js build --naver --debug",
34
- "addonova-watch": "node --max-old-space-size=3072 workspace/tasks/cli.js build --all --debug --watch"
35
- },
36
21
  "dependencies": {
37
- "@craftamap/esbuild-plugin-html": "^0.9.0",
38
22
  "chokidar": "^5.0.0",
39
23
  "esbuild": "^0.27.3",
40
24
  "globby": "^16.1.0",
25
+ "html-minifier-terser": "^7.2.0",
26
+ "htmlhint": "^1.9.2",
27
+ "web-ext": "^10.3.0",
41
28
  "yazl": "^3.3.1"
42
29
  },
30
+ "overrides": {
31
+ "encoding-sniffer": "^1.0.2",
32
+ "jsdom": "^29.1.1"
33
+ },
43
34
  "keywords": [
44
- "webextension",
45
- "browser-extension",
46
- "chrome-extension",
47
- "firefox-addon",
48
- "scaffold",
49
- "cli"
35
+ "chrome",
36
+ "web",
37
+ "extension",
38
+ "browser",
39
+ "bundler",
40
+ "framework"
50
41
  ],
51
42
  "license": "ISC",
52
43
  "author": "Hemanta gayen",
@@ -59,6 +50,6 @@
59
50
  "url": "https://github.com/code-hemu/addonova/issues"
60
51
  },
61
52
  "engines": {
62
- "node": ">=18"
53
+ "node": ">=20.19"
63
54
  }
64
55
  }
@@ -0,0 +1,143 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdir } from 'node:fs/promises';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { spawn } from 'node:child_process';
5
+ import process from 'node:process';
6
+ import { createRequire } from 'node:module';
7
+
8
+ import { getDestDir } from './paths.js';
9
+ import { log } from './utils.js';
10
+
11
+ const WEB_EXT_TARGETS = new Set(['chrome', 'edge', 'firefox', 'opera', 'naver']);
12
+ const require = createRequire(import.meta.url);
13
+
14
+ const chromiumBinaries = {
15
+ edge: {
16
+ win32: [
17
+ 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
18
+ 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
19
+ ],
20
+ darwin: ['/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'],
21
+ linux: ['microsoft-edge', 'microsoft-edge-stable'],
22
+ },
23
+ opera: {
24
+ win32: [
25
+ 'C:\\Users\\%USERNAME%\\AppData\\Local\\Programs\\Opera\\opera.exe',
26
+ 'C:\\Program Files\\Opera\\opera.exe',
27
+ ],
28
+ darwin: ['/Applications/Opera.app/Contents/MacOS/Opera'],
29
+ linux: ['opera'],
30
+ },
31
+ naver: {
32
+ win32: [
33
+ 'C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe',
34
+ 'C:\\Program Files (x86)\\Naver\\Naver Whale\\Application\\whale.exe',
35
+ ],
36
+ darwin: ['/Applications/Whale.app/Contents/MacOS/Whale'],
37
+ linux: ['whale'],
38
+ },
39
+ };
40
+
41
+ function expandEnv(value) {
42
+ return value.replace(/%([^%]+)%/g, (_, name) => process.env[name] ?? '');
43
+ }
44
+
45
+ function resolveBinary(platform) {
46
+ const candidates = chromiumBinaries[platform]?.[process.platform] ?? [];
47
+
48
+ for (const candidate of candidates) {
49
+ const binary = expandEnv(candidate);
50
+ if (process.platform === 'linux' || existsSync(binary)) {
51
+ return binary;
52
+ }
53
+ }
54
+
55
+ return null;
56
+ }
57
+
58
+ function getWebExtBin() {
59
+ try {
60
+ const webExtIndex = require.resolve('web-ext');
61
+ return join(dirname(webExtIndex), 'bin', 'web-ext.js');
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ function createWebExtArgs(platform, settings) {
68
+ const sourceDir = resolve(getDestDir({ isDebug: settings.isDebug, platform }));
69
+ const profileDir = resolve(join('.output', 'profiles', platform));
70
+ const args = [
71
+ 'run',
72
+ '--source-dir',
73
+ sourceDir,
74
+ '--no-input',
75
+ '--start-url',
76
+ 'about:blank',
77
+ ];
78
+
79
+ if (platform === 'firefox') {
80
+ args.push('--target', 'firefox-desktop', '--firefox-profile', profileDir);
81
+ return { args, sourceDir, profileDir };
82
+ }
83
+
84
+ args.push('--target', 'chromium', '--chromium-profile', profileDir, '--profile-create-if-missing');
85
+
86
+ if (platform !== 'chrome') {
87
+ const binary = resolveBinary(platform);
88
+ if (binary) {
89
+ args.push('--chromium-binary', binary);
90
+ } else {
91
+ log.warn(`[!] ${platform} binary not found. Trying the default Chromium browser.`);
92
+ }
93
+ }
94
+
95
+ return { args, sourceDir, profileDir };
96
+ }
97
+
98
+ export async function startBrowserRunners(settings) {
99
+ const webExtBin = getWebExtBin();
100
+ const platforms = settings.platforms.filter((platform) => WEB_EXT_TARGETS.has(platform));
101
+
102
+ if (!webExtBin || !existsSync(webExtBin)) {
103
+ log.warn('[!] Browser dev runner not found. Run npm install before dev mode.');
104
+ return;
105
+ }
106
+
107
+ if (platforms.length === 0) {
108
+ log.warn('[!] Browser dev mode supports Chrome, Edge, Firefox, Opera, and Naver Whale.');
109
+ return;
110
+ }
111
+
112
+ settings.browserRunners = [];
113
+
114
+ for (const platform of platforms) {
115
+ const { args, sourceDir, profileDir } = createWebExtArgs(platform, settings);
116
+
117
+ if (!existsSync(sourceDir)) {
118
+ log.warn(`[!] Extension output missing for ${platform}: ${sourceDir}`);
119
+ continue;
120
+ }
121
+
122
+ await mkdir(profileDir, { recursive: true });
123
+
124
+ const child = spawn(process.execPath, [webExtBin, ...args], {
125
+ stdio: 'inherit',
126
+ });
127
+
128
+ child.on('exit', (code) => {
129
+ if (code !== null && code !== 0) {
130
+ log.warn(`[!] Browser runner for ${platform} exited with code ${code}`);
131
+ }
132
+ });
133
+
134
+ settings.browserRunners.push(child);
135
+ log.ok(`[+] Started browser runner for ${platform}`);
136
+ }
137
+ }
138
+
139
+ export function stopBrowserRunners(settings) {
140
+ for (const child of settings.browserRunners ?? []) {
141
+ if (!child.killed) child.kill('SIGTERM');
142
+ }
143
+ }
@@ -8,6 +8,7 @@ import bundleLocales from './bundle-locales.js';
8
8
  import bundleManifest from './bundle-manifest.js';
9
9
  import assetsCopy from './copy.js';
10
10
  import zip from './zip.js';
11
+ import { startBrowserRunners, stopBrowserRunners } from './browser.js';
11
12
  import {log} from './utils.js';
12
13
  import {runTasks} from './task.js';
13
14
 
@@ -53,7 +54,9 @@ const settings = {
53
54
  isDebug: args.includes('--debug'),
54
55
  isTest: args.includes('--test'),
55
56
  logInfo: args.includes('--log-info'),
56
- logWarn: args.includes('--log-warn')
57
+ logWarn: args.includes('--log-warn'),
58
+ shouldRunBrowser: args.includes('--open') || (args.includes('--watch') && args.includes('--debug')),
59
+ args,
57
60
  }
58
61
 
59
62
  const standardTask = [
@@ -77,7 +80,18 @@ const buildTask = [
77
80
  try {
78
81
  await runTasks(settings.isDebug ? standardTask : buildTask, settings);
79
82
  if (settings.isWatch) {
80
- standardTask.forEach((task) => task.watch(settings.platforms, settings.isDebug));
83
+ if (settings.shouldRunBrowser) {
84
+ await startBrowserRunners(settings);
85
+ process.on('exit', () => stopBrowserRunners(settings));
86
+ process.on('SIGINT', () => {
87
+ stopBrowserRunners(settings);
88
+ process.exit(130);
89
+ });
90
+ }
91
+ standardTask.forEach((task) => task.watch(
92
+ settings.platforms,
93
+ settings.isDebug
94
+ ));
81
95
  log.ok('[^_^] Watching...');
82
96
  } else {
83
97
  log.ok('MISSION PASSED! RESPECT +');
@@ -0,0 +1,120 @@
1
+ import path from 'node:path';
2
+ import { minify } from 'html-minifier-terser';
3
+ import htmlhint from 'htmlhint';
4
+
5
+ import { getDestDir } from './paths.js';
6
+ import { readFile, writeFile, getConfig, getAllFiles, log, fileExistsInConfig } from './utils.js';
7
+ import { createTask } from './task.js';
8
+
9
+ const srcHTMLDir = 'src/html';
10
+ const { HTMLHint } = htmlhint;
11
+
12
+ const htmlHintRules = {
13
+ 'attr-lowercase': true,
14
+ 'attr-no-duplication': true,
15
+ 'doctype-first': false,
16
+ 'id-unique': true,
17
+ 'tag-pair': true,
18
+ 'tagname-lowercase': true,
19
+ };
20
+
21
+ const htmlMinifyOptions = {
22
+ collapseBooleanAttributes: true,
23
+ collapseWhitespace: true,
24
+ conservativeCollapse: true,
25
+ keepClosingSlash: true,
26
+ minifyCSS: true,
27
+ minifyJS: true,
28
+ removeComments: true,
29
+ removeEmptyAttributes: false,
30
+ };
31
+
32
+ function getDestinationFile(destDir, dest, src, filename) {
33
+ const sourceName = path.basename(src, path.extname(src));
34
+ const outputName = `${filename.replace('[name]', sourceName)}.html`;
35
+ return path.join(destDir, dest, outputName);
36
+ }
37
+
38
+ function formatHtmlIssue(file, issue) {
39
+ return `${file}:${issue.line}:${issue.col} ${issue.type.toUpperCase()} ${issue.message}`;
40
+ }
41
+
42
+ function validateHTML(file, html) {
43
+ const issues = HTMLHint.verify(html, htmlHintRules);
44
+ const errors = issues.filter((issue) => issue.type === 'error');
45
+
46
+ if (errors.length > 0) {
47
+ throw new Error(errors.map((issue) => formatHtmlIssue(file, issue)).join('\n'));
48
+ }
49
+
50
+ for (const issue of issues) {
51
+ log.warn(`[HTML] ${formatHtmlIssue(file, issue)}`);
52
+ }
53
+ }
54
+
55
+ async function bundleHTML(config, isDebug, platform) {
56
+ const dir = getDestDir({ isDebug, platform });
57
+
58
+ for (const [dest, sources] of Object.entries(config.entry)) {
59
+ for (const src of sources) {
60
+ const html = await readFile(src, 'utf8');
61
+ validateHTML(src, html);
62
+
63
+ const output = isDebug
64
+ ? html
65
+ : await minify(html, htmlMinifyOptions);
66
+
67
+ await writeFile(
68
+ getDestinationFile(dir, dest, src, config.filename),
69
+ output,
70
+ 'utf8'
71
+ );
72
+ }
73
+ }
74
+ }
75
+
76
+ export function createBundleHTMLTask(srcHTMLDir) {
77
+ const runBundleHTML = async ({ platforms, isDebug, logInfo, logWarn }) => {
78
+ for (const platform of platforms) {
79
+ const config = await getConfig(platform);
80
+ if (config.html) {
81
+ await bundleHTML(config.html, isDebug, platform);
82
+ if (logInfo) log.ok(`Bundling HTML for ${platform}...`);
83
+ } else if (logWarn) {
84
+ log.warn(`No HTML config found for ${platform}, skipping HTML bundling.`);
85
+ }
86
+ }
87
+ };
88
+
89
+ const onChange = async (changedFiles, watcher, platforms, isDebug) => {
90
+ for (const platform of platforms) {
91
+ const config = await getConfig(platform);
92
+
93
+ if (!config.html) continue;
94
+ const exists = await fileExistsInConfig(
95
+ config.html.entry,
96
+ changedFiles[0]
97
+ );
98
+
99
+ if (exists) {
100
+ const newConfig = {
101
+ html: {
102
+ entry: exists,
103
+ filename: config.html.filename,
104
+ },
105
+ };
106
+ await bundleHTML(newConfig.html, isDebug, platform);
107
+ }
108
+ }
109
+ };
110
+
111
+ return createTask(
112
+ 'bundle HTML',
113
+ runBundleHTML
114
+ ).addWatcher(
115
+ () => getAllFiles(srcHTMLDir),
116
+ onChange
117
+ );
118
+ }
119
+
120
+ export default createBundleHTMLTask(srcHTMLDir);
@@ -21,10 +21,11 @@ function printHelp() {
21
21
  'Other parameters:',
22
22
  ' --release Build release version (default)',
23
23
  ' --debug Build debug version',
24
- ' --watch Watch for changes and rebuild automatically',
24
+ ' --watch Watch for changes and reload opened extensions',
25
25
  ' --log-info Log info messages',
26
26
  ' --log-warn Log warning messages',
27
27
  ' --test Build test version (for testing in development environment)',
28
+ ' --open Open a browser with the debug extension loaded',
28
29
  ' --version=1.2.3 Append version to output file name (e.g. extension-name-chrome-1.2.3.zip)',
29
30
  ' -h, --help Show this help message',
30
31
  ].join('\n'));
@@ -57,6 +58,7 @@ function validateArguments(args) {
57
58
  '--log-info',
58
59
  '--log-warn',
59
60
  '--test',
61
+ '--open',
60
62
  '--version=*',
61
63
  '--help',
62
64
  '-h'
@@ -82,7 +84,7 @@ async function run() {
82
84
  const validationErrors = validateArguments(args);
83
85
  if (validationErrors.length > 0) {
84
86
  validationErrors.forEach(log.error);
85
- log.error(' No target platform specified.');
87
+ log.error('[X] No target platform specified.');
86
88
  printHelp();
87
89
  process.exit(130);
88
90
  }