addonova 1.0.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 (38) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +2 -0
  3. package/bin/index.js +22 -0
  4. package/package.json +42 -0
  5. package/src/commands/init.js +108 -0
  6. package/src/index.js +1 -0
  7. package/src/utils/prompts.js +32 -0
  8. package/template/config/chrome.js +29 -0
  9. package/template/config/edge.js +29 -0
  10. package/template/config/firefox.js +29 -0
  11. package/template/config/naver.js +29 -0
  12. package/template/config/opera.js +29 -0
  13. package/template/config/thunderbird.js +29 -0
  14. package/template/package.json.tpl +39 -0
  15. package/template/src/_locales/en.i18n +4 -0
  16. package/template/src/html/popup.html +15 -0
  17. package/template/src/js/background.js +1 -0
  18. package/template/src/js/popup.js +6 -0
  19. package/template/src/manifest/manifest-firefox.json +29 -0
  20. package/template/src/manifest/manifest.json +24 -0
  21. package/template/src/scss/popup.scss +25 -0
  22. package/template/tasks/build.js +82 -0
  23. package/template/tasks/bundle-css.js +108 -0
  24. package/template/tasks/bundle-html.js +91 -0
  25. package/template/tasks/bundle-js.js +95 -0
  26. package/template/tasks/bundle-locales.js +139 -0
  27. package/template/tasks/bundle-manifest.js +54 -0
  28. package/template/tasks/cli.js +85 -0
  29. package/template/tasks/copy.js +49 -0
  30. package/template/tasks/folder.js +25 -0
  31. package/template/tasks/paths.js +21 -0
  32. package/template/tasks/task.js +60 -0
  33. package/template/tasks/translate.js +255 -0
  34. package/template/tasks/utils.js +134 -0
  35. package/template/tasks/watch.js +46 -0
  36. package/template/tasks/zip.js +66 -0
  37. package/template/tools/json2i18n.js +33 -0
  38. package/template/tools/translate.js +299 -0
package/bin/index.js ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import process from 'node:process';
3
+ import { init } from '../src/commands/init.js';
4
+
5
+ const [command, ...args] = process.argv.slice(2);
6
+
7
+ switch (command) {
8
+ case 'init':
9
+ await init(args);
10
+ break;
11
+ case '--help':
12
+ case '-h':
13
+ default:
14
+ console.log(`
15
+ addonova — Cross-browser WebExtension toolkit
16
+
17
+ Usage:
18
+ npx addonova init <project-name> Scaffold a new extension project
19
+ npx addonova --help Show this help
20
+ `);
21
+ break;
22
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "addonova",
3
+ "version": "1.0.0",
4
+ "description": "Web Extension Framework",
5
+ "type": "module",
6
+ "bin": {
7
+ "addonova": "./bin/index.js"
8
+ },
9
+ "exports": {
10
+ ".": "./src/index.js",
11
+ "./*": "./src/*.js"
12
+ },
13
+ "files": [
14
+ "bin/",
15
+ "src/",
16
+ "template/"
17
+ ],
18
+ "scripts": {
19
+ "test": "echo \"Error: no test specified\" && exit 1"
20
+ },
21
+ "keywords": [
22
+ "webextension",
23
+ "browser-extension",
24
+ "chrome-extension",
25
+ "firefox-addon",
26
+ "scaffold",
27
+ "cli"
28
+ ],
29
+ "license": "ISC",
30
+ "author": "Hemanta gayen",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/code-hemu/addonova.git"
34
+ },
35
+ "homepage": "https://github.com/code-hemu/addonova#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/code-hemu/addonova/issues"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ }
42
+ }
@@ -0,0 +1,108 @@
1
+ import { existsSync } from 'node:fs';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import process from 'node:process';
6
+ import { selectBrowsers, askQuestion } from '../utils/prompts.js';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const TEMPLATE_DIR = path.resolve(__dirname, '../../template');
10
+
11
+ const VARIABLE_RE = /\{\{\s*(\w+)\s*\}\}/g;
12
+
13
+ function render(template, vars) {
14
+ return template.replace(VARIABLE_RE, (_, key) => vars[key] ?? `{{${key}}}`);
15
+ }
16
+
17
+ async function copyDir(src, dest, vars) {
18
+ await fs.mkdir(dest, { recursive: true });
19
+ const entries = await fs.readdir(src, { withFileTypes: true });
20
+
21
+ for (const entry of entries) {
22
+ const srcPath = path.join(src, entry.name);
23
+ const destPath = path.join(dest, entry.name.replace(/\.tpl$/, ''));
24
+
25
+ if (entry.isDirectory()) {
26
+ await copyDir(srcPath, destPath, vars);
27
+ } else if (entry.name.endsWith('.tpl')) {
28
+ const content = await fs.readFile(srcPath, 'utf-8');
29
+ await fs.writeFile(destPath, render(content, vars), 'utf-8');
30
+ } else {
31
+ await fs.copyFile(srcPath, destPath);
32
+ }
33
+ }
34
+ }
35
+
36
+ async function installDependencies(projectDir) {
37
+ console.log('\n⚡ Installing dependencies...');
38
+ const { execSync } = await import('node:child_process');
39
+ execSync('npm install', { cwd: projectDir, stdio: 'inherit' });
40
+ }
41
+
42
+ async function detectPackageManager() {
43
+ const hasLock = f => existsSync(path.join(process.cwd(), f));
44
+
45
+ if (hasLock('pnpm-lock.yaml')) return 'pnpm';
46
+ if (hasLock('yarn.lock')) return 'yarn';
47
+ if (hasLock('package-lock.json') || hasLock('bun.lock')) return 'npm';
48
+ return 'npm';
49
+ }
50
+
51
+ export async function init(args) {
52
+ let projectName = args[0];
53
+
54
+ if (!projectName) {
55
+ projectName = await askQuestion('Project name: ');
56
+ }
57
+
58
+ if (!projectName) {
59
+ console.error('❌ Project name is required.');
60
+ process.exit(1);
61
+ }
62
+
63
+ const projectDir = path.resolve(process.cwd(), projectName);
64
+
65
+ if (existsSync(projectDir)) {
66
+ console.error(`❌ Directory "${projectName}" already exists.`);
67
+ process.exit(1);
68
+ }
69
+
70
+ const browsers = await selectBrowsers();
71
+ const selected = browsers.length > 0 ? browsers : ['chrome', 'firefox'];
72
+
73
+ const vars = {
74
+ name: projectName,
75
+ description: 'A cross-browser WebExtension',
76
+ version: '1.0.0',
77
+ browsers: JSON.stringify(selected),
78
+ browsersList: selected.join(', '),
79
+ year: new Date().getFullYear().toString(),
80
+ };
81
+
82
+ console.log(`\n📁 Scaffolding "${projectName}" for: ${selected.join(', ')}`);
83
+ await copyDir(TEMPLATE_DIR, projectDir, vars);
84
+
85
+ console.log('✔ Template files created.');
86
+
87
+ const pm = detectPackageManager();
88
+ const shouldInstall = await askQuestion('\nRun npm install now? (Y/n): ');
89
+ if (shouldInstall.toLowerCase() !== 'n') {
90
+ await installDependencies(projectDir);
91
+ }
92
+
93
+ console.log(`
94
+ ┌──────────────────────────────────────────────┐
95
+ │ ✅ "${projectName}" is ready! │
96
+ │ │
97
+ │ ${projectDir} │
98
+ │ │
99
+ │ Next steps: │
100
+ │ cd ${projectName} │
101
+ │ npm run build -- --all │
102
+ │ npm run build -- --chrome --debug │
103
+ │ npm run build -- --firefox --watch │
104
+ │ │
105
+ │ Targets: ${selected.join(', ').padEnd(30)}│
106
+ └──────────────────────────────────────────────┘
107
+ `);
108
+ }
package/src/index.js ADDED
@@ -0,0 +1 @@
1
+ export { init } from './commands/init.js';
@@ -0,0 +1,32 @@
1
+ import * as readline from 'node:readline/promises';
2
+ import { stdin as input, stdout as output } from 'node:process';
3
+
4
+ export async function askQuestion(query) {
5
+ const rl = readline.createInterface({ input, output });
6
+ const answer = await rl.question(query);
7
+ rl.close();
8
+ return answer.trim();
9
+ }
10
+
11
+ export async function confirm(query) {
12
+ const answer = await askQuestion(`${query} (Y/n): `);
13
+ return answer.toLowerCase() !== 'n';
14
+ }
15
+
16
+ export async function selectBrowsers() {
17
+ const allBrowsers = [
18
+ { value: 'chrome', label: 'Chrome (MV3)' },
19
+ { value: 'edge', label: 'Edge' },
20
+ { value: 'firefox', label: 'Firefox' },
21
+ { value: 'opera', label: 'Opera' },
22
+ { value: 'naver', label: 'Naver Whale' },
23
+ { value: 'thunderbird', label: 'Thunderbird' },
24
+ ];
25
+
26
+ console.log('\nSelect target browsers (comma-separated, e.g. 1,3,5):');
27
+ allBrowsers.forEach((b, i) => console.log(` [${i + 1}] ${b.label} (${b.value})`));
28
+
29
+ const answer = await askQuestion('Browsers: ');
30
+ const indices = answer.split(',').map(s => parseInt(s.trim(), 10) - 1);
31
+ return indices.filter(i => i >= 0 && i < allBrowsers.length).map(i => allBrowsers[i].value);
32
+ }
@@ -0,0 +1,29 @@
1
+ export default {
2
+ js: {
3
+ entry: {
4
+ '': ['src/js/background.js', 'src/js/popup.js'],
5
+ },
6
+ filename: '[name]',
7
+ minify: true,
8
+ sourcemap: false,
9
+ },
10
+ scss: {
11
+ entry: {
12
+ '': ['src/scss/popup.scss'],
13
+ },
14
+ filename: '[name]',
15
+ minify: true,
16
+ sourcemap: false,
17
+ },
18
+ html: {
19
+ entry: {
20
+ '': ['src/html/popup.html'],
21
+ },
22
+ filename: '[name]',
23
+ },
24
+ assets: {
25
+ entry: {
26
+ '': ['src/assets/**/*'],
27
+ },
28
+ },
29
+ };
@@ -0,0 +1,29 @@
1
+ export default {
2
+ js: {
3
+ entry: {
4
+ '': ['src/js/background.js', 'src/js/popup.js'],
5
+ },
6
+ filename: '[name]',
7
+ minify: true,
8
+ sourcemap: false,
9
+ },
10
+ scss: {
11
+ entry: {
12
+ '': ['src/scss/popup.scss'],
13
+ },
14
+ filename: '[name]',
15
+ minify: true,
16
+ sourcemap: false,
17
+ },
18
+ html: {
19
+ entry: {
20
+ '': ['src/html/popup.html'],
21
+ },
22
+ filename: '[name]',
23
+ },
24
+ assets: {
25
+ entry: {
26
+ '': ['src/assets/**/*'],
27
+ },
28
+ },
29
+ };
@@ -0,0 +1,29 @@
1
+ export default {
2
+ js: {
3
+ entry: {
4
+ '': ['src/js/background.js', 'src/js/popup.js'],
5
+ },
6
+ filename: '[name]',
7
+ minify: true,
8
+ sourcemap: false,
9
+ },
10
+ scss: {
11
+ entry: {
12
+ '': ['src/scss/popup.scss'],
13
+ },
14
+ filename: '[name]',
15
+ minify: true,
16
+ sourcemap: false,
17
+ },
18
+ html: {
19
+ entry: {
20
+ '': ['src/html/popup.html'],
21
+ },
22
+ filename: '[name]',
23
+ },
24
+ assets: {
25
+ entry: {
26
+ '': ['src/assets/**/*'],
27
+ },
28
+ },
29
+ };
@@ -0,0 +1,29 @@
1
+ export default {
2
+ js: {
3
+ entry: {
4
+ '': ['src/js/background.js', 'src/js/popup.js'],
5
+ },
6
+ filename: '[name]',
7
+ minify: true,
8
+ sourcemap: false,
9
+ },
10
+ scss: {
11
+ entry: {
12
+ '': ['src/scss/popup.scss'],
13
+ },
14
+ filename: '[name]',
15
+ minify: true,
16
+ sourcemap: false,
17
+ },
18
+ html: {
19
+ entry: {
20
+ '': ['src/html/popup.html'],
21
+ },
22
+ filename: '[name]',
23
+ },
24
+ assets: {
25
+ entry: {
26
+ '': ['src/assets/**/*'],
27
+ },
28
+ },
29
+ };
@@ -0,0 +1,29 @@
1
+ export default {
2
+ js: {
3
+ entry: {
4
+ '': ['src/js/background.js', 'src/js/popup.js'],
5
+ },
6
+ filename: '[name]',
7
+ minify: true,
8
+ sourcemap: false,
9
+ },
10
+ scss: {
11
+ entry: {
12
+ '': ['src/scss/popup.scss'],
13
+ },
14
+ filename: '[name]',
15
+ minify: true,
16
+ sourcemap: false,
17
+ },
18
+ html: {
19
+ entry: {
20
+ '': ['src/html/popup.html'],
21
+ },
22
+ filename: '[name]',
23
+ },
24
+ assets: {
25
+ entry: {
26
+ '': ['src/assets/**/*'],
27
+ },
28
+ },
29
+ };
@@ -0,0 +1,29 @@
1
+ export default {
2
+ js: {
3
+ entry: {
4
+ '': ['src/js/background.js', 'src/js/popup.js'],
5
+ },
6
+ filename: '[name]',
7
+ minify: true,
8
+ sourcemap: false,
9
+ },
10
+ scss: {
11
+ entry: {
12
+ '': ['src/scss/popup.scss'],
13
+ },
14
+ filename: '[name]',
15
+ minify: true,
16
+ sourcemap: false,
17
+ },
18
+ html: {
19
+ entry: {
20
+ '': ['src/html/popup.html'],
21
+ },
22
+ filename: '[name]',
23
+ },
24
+ assets: {
25
+ entry: {
26
+ '': ['src/assets/**/*'],
27
+ },
28
+ },
29
+ };
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "{{version}}",
4
+ "description": "{{description}}",
5
+ "json.schemaValidation": "off",
6
+ "license": "GPLv3",
7
+ "author": "{{name}}",
8
+ "type": "module",
9
+ "engines": {
10
+ "node": ">=22",
11
+ "npm": ">=11"
12
+ },
13
+ "scripts": {
14
+ "build": "node --max-old-space-size=3072 workspace/tasks/cli.js build --all --release",
15
+ "release": "node workspace/tasks/cli.js build --all --release",
16
+ "release:chrome": "node --max-old-space-size=3072 workspace/tasks/cli.js build --chrome --release",
17
+ "release:edge": "node --max-old-space-size=3072 workspace/tasks/cli.js build --edge --release",
18
+ "release:opera": "node --max-old-space-size=3072 workspace/tasks/cli.js build --opera --release",
19
+ "release:naver": "node --max-old-space-size=3072 workspace/tasks/cli.js build --naver --release",
20
+ "edge": "node --max-old-space-size=3072 workspace/tasks/cli.js build --edge --release",
21
+ "help": "node --max-old-space-size=3072 workspace/tasks/cli.js --help",
22
+ "debug": "node --max-old-space-size=3072 workspace/tasks/cli.js build --all --debug",
23
+ "debug:chrome": "node --max-old-space-size=3072 workspace/tasks/cli.js build --chrome --debug",
24
+ "watch": "node --max-old-space-size=3072 workspace/tasks/cli.js build --all --debug --watch",
25
+ "zip": "node --max-old-space-size=3072 workspace/tasks/cli.js zip",
26
+ "chrome:watch": "node --max-old-space-size=3072 workspace/tasks/cli.js build --chrome --release --watch",
27
+ "locales": "node --max-old-space-size=3072 workspace/tools/translate.js"
28
+ },
29
+ "devDependencies": {
30
+ "@craftamap/esbuild-plugin-html": "^0.9.0",
31
+ "chokidar": "^5.0.0",
32
+ "esbuild": "^0.27.3",
33
+ "esbuild-sass-plugin": "^3.6.0",
34
+ "globals": "^17.4.0",
35
+ "globby": "^16.1.0",
36
+ "sass": "^1.97.3",
37
+ "yazl": "^3.3.1"
38
+ }
39
+ }
@@ -0,0 +1,4 @@
1
+ @extensionName
2
+ {{name}}
3
+ @extensionDescription
4
+ {{description}}
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="stylesheet" href="popup.css" />
7
+ </head>
8
+ <body>
9
+ <div id="app">
10
+ <h1 id="heading"></h1>
11
+ <p id="description"></p>
12
+ </div>
13
+ <script src="popup.js"></script>
14
+ </body>
15
+ </html>
@@ -0,0 +1 @@
1
+ console.log('Background service worker started');
@@ -0,0 +1,6 @@
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ document.getElementById('heading').textContent =
3
+ chrome.i18n.getMessage('extensionName');
4
+ document.getElementById('description').textContent =
5
+ chrome.i18n.getMessage('extensionDescription');
6
+ });
@@ -0,0 +1,29 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "__MSG_extensionName__",
4
+ "version": "{{version}}",
5
+ "description": "__MSG_extensionDescription__",
6
+ "default_locale": "en",
7
+ "icons": {
8
+ "16": "assets/icon-16.png",
9
+ "48": "assets/icon-48.png",
10
+ "128": "assets/icon-128.png"
11
+ },
12
+ "action": {
13
+ "default_popup": "popup.html",
14
+ "default_icon": {
15
+ "16": "assets/icon-16.png",
16
+ "48": "assets/icon-48.png",
17
+ "128": "assets/icon-128.png"
18
+ }
19
+ },
20
+ "background": {
21
+ "scripts": ["background.js"]
22
+ },
23
+ "browser_specific_settings": {
24
+ "gecko": {
25
+ "id": "{{name}}@example.org"
26
+ }
27
+ },
28
+ "permissions": []
29
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "__MSG_extensionName__",
4
+ "version": "{{version}}",
5
+ "description": "__MSG_extensionDescription__",
6
+ "default_locale": "en",
7
+ "icons": {
8
+ "16": "assets/icon-16.png",
9
+ "48": "assets/icon-48.png",
10
+ "128": "assets/icon-128.png"
11
+ },
12
+ "action": {
13
+ "default_popup": "popup.html",
14
+ "default_icon": {
15
+ "16": "assets/icon-16.png",
16
+ "48": "assets/icon-48.png",
17
+ "128": "assets/icon-128.png"
18
+ }
19
+ },
20
+ "background": {
21
+ "service_worker": "background.js"
22
+ },
23
+ "permissions": []
24
+ }
@@ -0,0 +1,25 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ width: 320px;
9
+ padding: 16px;
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+ }
12
+
13
+ #app {
14
+ text-align: center;
15
+ }
16
+
17
+ h1 {
18
+ font-size: 18px;
19
+ margin-bottom: 8px;
20
+ }
21
+
22
+ p {
23
+ font-size: 14px;
24
+ color: #666;
25
+ }
@@ -0,0 +1,82 @@
1
+ import process from 'node:process';
2
+ import createFolder from './folder.js';
3
+ import bundleCSS from './bundle-css.js';
4
+ import bundleJS from './bundle-js.js';
5
+ import bundleHTML from './bundle-html.js';
6
+ import bundleLocales from './bundle-locales.js';
7
+ import bundleManifest from './bundle-manifest.js';
8
+ import assetsCopy from './copy.js';
9
+ import zip from './zip.js';
10
+ import {log} from './utils.js';
11
+ import {runTasks} from './task.js';
12
+
13
+ const args = process.argv.slice(2);
14
+
15
+ const platforms = [
16
+ 'chrome',
17
+ 'edge',
18
+ 'naver',
19
+ 'opera',
20
+ 'firefox',
21
+ 'thunderbird'
22
+ ];
23
+
24
+ let platform = [];
25
+
26
+ if (args.includes('--all')) {
27
+ platform = platforms;
28
+ } else {
29
+ if (args.includes('--chrome')) platform.push('chrome');
30
+ if (args.includes('--edge')) platform.push('edge');
31
+ if (args.includes('--opera')) platform.push('opera');
32
+ if (args.includes('--naver')) platform.push('naver');
33
+ if (args.includes('--firefox')) platform.push('firefox');
34
+ if (args.includes('--thunderbird')) platform.push('thunderbird');
35
+ }
36
+
37
+ const versionArg = args.find((a) => a.startsWith('--version='));
38
+ const version = versionArg ? versionArg.split('=')[1] : null;
39
+
40
+ const settings = {
41
+ platforms: platform,
42
+ version: version,
43
+ isWatch: args.includes('--watch'),
44
+ isRelease: args.includes('--release'),
45
+ isDebug: args.includes('--debug'),
46
+ isTest: args.includes('--test'),
47
+ logInfo: args.includes('--log-info'),
48
+ logWarn: args.includes('--log-warn')
49
+ }
50
+
51
+ const standardTask = [
52
+ createFolder,
53
+ bundleHTML,
54
+ bundleCSS,
55
+ bundleJS,
56
+ bundleLocales,
57
+ bundleManifest,
58
+ assetsCopy
59
+ ];
60
+
61
+ const buildTask = [
62
+ ...standardTask,
63
+ zip,
64
+ ];
65
+
66
+ (async () => {
67
+ log.ok('--------------------------------');
68
+ log.ok('🚀 Build started');
69
+ try {
70
+ await runTasks(settings.isDebug ? standardTask : buildTask, settings);
71
+ if (settings.isWatch) {
72
+ standardTask.forEach((task) => task.watch(settings.platforms, settings.isDebug));
73
+ log.ok('👀 Watching...');
74
+ } else {
75
+ log.ok('MISSION PASSED! RESPECT +');
76
+ }
77
+ }catch (err) {
78
+ console.log(err);
79
+ log.error(`MISSION FAILED!`);
80
+ process.exit(13);
81
+ }
82
+ })();