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.
- package/README.md +117 -48
- package/bin/addonova.js +4 -0
- package/bin/index.js +1 -60
- package/package.json +20 -29
- package/src/build/browser.js +143 -0
- package/{workspace/tasks → src/build}/build.js +16 -2
- package/src/build/bundle-html.js +120 -0
- package/{workspace/tasks → src/build}/cli.js +4 -2
- package/src/cli/help.js +16 -0
- package/src/cli/index.js +58 -0
- package/src/commands/init.js +14 -18
- package/src/commands/tool.js +32 -0
- package/src/index.js +0 -1
- package/src/tools/tools-server.js +287 -0
- package/src/tools/tools.html +388 -0
- package/templates/extension/config/firefox.js +30 -0
- package/templates/extension/config/thunderbird.js +30 -0
- package/templates/extension/package.json.tpl +36 -0
- package/{template → templates/extension}/platform/naver/platform.js +0 -15
- package/src/commands/update.js +0 -67
- package/template/config/firefox.js +0 -29
- package/template/config/thunderbird.js +0 -29
- package/template/package.json.tpl +0 -54
- package/workspace/tasks/bundle-html.js +0 -91
- package/workspace/tools/json2i18n.js +0 -37
- package/workspace/tools/translate.js +0 -299
- /package/{workspace/tasks → src/build}/bundle-css.js +0 -0
- /package/{workspace/tasks → src/build}/bundle-js.js +0 -0
- /package/{workspace/tasks → src/build}/bundle-locales.js +0 -0
- /package/{workspace/tasks → src/build}/bundle-manifest.js +0 -0
- /package/{workspace/tasks → src/build}/copy.js +0 -0
- /package/{workspace/tasks → src/build}/folder.js +0 -0
- /package/{workspace/tasks → src/build}/paths.js +0 -0
- /package/{workspace/tasks → src/build}/task.js +0 -0
- /package/{workspace/tasks → src/build}/utils.js +0 -0
- /package/{workspace/tasks → src/build}/watch.js +0 -0
- /package/{workspace/tasks → src/build}/zip.js +0 -0
- /package/{template → templates/extension}/config/chrome.js +0 -0
- /package/{template → templates/extension}/config/edge.js +0 -0
- /package/{template → templates/extension}/config/naver.js +0 -0
- /package/{template → templates/extension}/config/opera.js +0 -0
- /package/{template → templates/extension}/platform/chrome/platform.js +0 -0
- /package/{template → templates/extension}/platform/edge/platform.js +0 -0
- /package/{template → templates/extension}/platform/opera/platform.js +0 -0
- /package/{template → templates/extension}/src/_locales/en.i18n +0 -0
- /package/{template → templates/extension}/src/assets/icons/128.png +0 -0
- /package/{template → templates/extension}/src/assets/icons/32.png +0 -0
- /package/{template → templates/extension}/src/assets/icons/48.png +0 -0
- /package/{template → templates/extension}/src/assets/icons/64.png +0 -0
- /package/{template → templates/extension}/src/css/popup.css +0 -0
- /package/{template → templates/extension}/src/html/popup.html +0 -0
- /package/{template → templates/extension}/src/js/background.js +0 -0
- /package/{template → templates/extension}/src/js/lib/browser.js +0 -0
- /package/{template → templates/extension}/src/js/lib/common.js +0 -0
- /package/{template → templates/extension}/src/js/lib/config.js +0 -0
- /package/{template → templates/extension}/src/js/lib/runtime.js +0 -0
- /package/{template → templates/extension}/src/js/popup.js +0 -0
- /package/{template → templates/extension}/src/manifest/manifest-firefox.json +0 -0
- /package/{template → templates/extension}/src/manifest/manifest.json +0 -0
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Addonova
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/code-hemu/addonova)
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
##
|
|
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
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
|
|
|
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
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
158
|
+
npm pack --dry-run
|
|
91
159
|
```
|
|
92
160
|
|
|
93
161
|
## Requirements
|
|
94
162
|
|
|
95
|
-
- Node.js >=
|
|
163
|
+
- Addonova package: Node.js >= 20.19
|
|
164
|
+
- Generated extension template: Node.js >= 22 and npm >= 11
|
package/bin/addonova.js
ADDED
package/bin/index.js
CHANGED
|
@@ -1,61 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
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.
|
|
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/
|
|
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
|
-
"
|
|
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
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
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": ">=
|
|
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
|
-
|
|
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
|
|
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('
|
|
87
|
+
log.error('[X] No target platform specified.');
|
|
86
88
|
printHelp();
|
|
87
89
|
process.exit(130);
|
|
88
90
|
}
|