addonova 1.0.0 → 1.0.2

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 +93 -0
  2. package/bin/index.js +42 -3
  3. package/package.json +24 -2
  4. package/src/commands/init.js +31 -31
  5. package/src/commands/update.js +67 -0
  6. package/src/index.js +1 -0
  7. package/template/config/chrome.js +13 -5
  8. package/template/config/edge.js +14 -6
  9. package/template/config/firefox.js +2 -2
  10. package/template/config/naver.js +14 -6
  11. package/template/config/opera.js +14 -6
  12. package/template/config/thunderbird.js +2 -2
  13. package/template/package.json.tpl +38 -23
  14. package/template/platform/chrome/platform.js +13 -0
  15. package/template/platform/edge/platform.js +14 -0
  16. package/template/platform/naver/platform.js +28 -0
  17. package/template/platform/opera/platform.js +14 -0
  18. package/template/src/_locales/en.i18n +3 -2
  19. package/template/src/assets/icons/128.png +0 -0
  20. package/template/src/assets/icons/32.png +0 -0
  21. package/template/src/assets/icons/48.png +0 -0
  22. package/template/src/assets/icons/64.png +0 -0
  23. package/template/src/js/background.js +6 -1
  24. package/template/src/js/lib/browser.js +263 -0
  25. package/template/src/js/lib/common.js +14 -0
  26. package/template/src/js/lib/config.js +21 -0
  27. package/template/src/js/lib/runtime.js +83 -0
  28. package/template/src/manifest/manifest-firefox.json +10 -7
  29. package/template/src/manifest/manifest.json +10 -7
  30. package/{template → workspace}/tasks/build.js +18 -10
  31. package/{template → workspace}/tasks/bundle-css.js +27 -34
  32. package/{template → workspace}/tasks/bundle-js.js +0 -1
  33. package/{template → workspace}/tasks/cli.js +12 -6
  34. package/{template → workspace}/tasks/copy.js +1 -2
  35. package/{template → workspace}/tasks/paths.js +2 -3
  36. package/{template → workspace}/tasks/zip.js +1 -1
  37. package/{template → workspace}/tools/json2i18n.js +7 -3
  38. package/template/tasks/translate.js +0 -255
  39. /package/template/src/{scss/popup.scss → css/popup.css} +0 -0
  40. /package/{template → workspace}/tasks/bundle-html.js +0 -0
  41. /package/{template → workspace}/tasks/bundle-locales.js +0 -0
  42. /package/{template → workspace}/tasks/bundle-manifest.js +0 -0
  43. /package/{template → workspace}/tasks/folder.js +0 -0
  44. /package/{template → workspace}/tasks/task.js +0 -0
  45. /package/{template → workspace}/tasks/utils.js +0 -0
  46. /package/{template → workspace}/tasks/watch.js +0 -0
  47. /package/{template → workspace}/tools/translate.js +0 -0
@@ -1,4 +1,5 @@
1
1
  import process from 'node:process';
2
+ import fs from 'node:fs';
2
3
  import createFolder from './folder.js';
3
4
  import bundleCSS from './bundle-css.js';
4
5
  import bundleJS from './bundle-js.js';
@@ -10,16 +11,23 @@ import zip from './zip.js';
10
11
  import {log} from './utils.js';
11
12
  import {runTasks} from './task.js';
12
13
 
14
+ const SUPPORTED_BROWSERS = ['chrome', 'edge', 'firefox', 'opera', 'naver', 'thunderbird'];
13
15
  const args = process.argv.slice(2);
14
16
 
15
- const platforms = [
16
- 'chrome',
17
- 'edge',
18
- 'naver',
19
- 'opera',
20
- 'firefox',
21
- 'thunderbird'
22
- ];
17
+ function getConfiguredBrowsers() {
18
+ const configDir = 'config';
19
+ try {
20
+ const files = fs.readdirSync(configDir);
21
+ return files
22
+ .filter((f) => f.endsWith('.js'))
23
+ .map((f) => f.replace(/\.js$/, ''))
24
+ .filter((b) => SUPPORTED_BROWSERS.includes(b));
25
+ } catch {
26
+ return [];
27
+ }
28
+ }
29
+
30
+ const platforms = getConfiguredBrowsers();
23
31
 
24
32
  let platform = [];
25
33
 
@@ -65,12 +73,12 @@ const buildTask = [
65
73
 
66
74
  (async () => {
67
75
  log.ok('--------------------------------');
68
- log.ok('🚀 Build started');
76
+ log.ok('[+] Build started');
69
77
  try {
70
78
  await runTasks(settings.isDebug ? standardTask : buildTask, settings);
71
79
  if (settings.isWatch) {
72
80
  standardTask.forEach((task) => task.watch(settings.platforms, settings.isDebug));
73
- log.ok('👀 Watching...');
81
+ log.ok('[^_^] Watching...');
74
82
  } else {
75
83
  log.ok('MISSION PASSED! RESPECT +');
76
84
  }
@@ -1,11 +1,10 @@
1
1
  import path from 'node:path';
2
2
  import { build } from 'esbuild';
3
- import { sassPlugin } from 'esbuild-sass-plugin';
4
3
  import {getDestDir} from './paths.js';
5
4
  import {readFile, writeFile, getConfig, getAllFiles, log, fileExistsInConfig} from './utils.js';
6
5
  import {createTask} from './task.js';
7
6
 
8
- const srcSCSSDir = 'src/scss';
7
+ const srcCSSDir = 'src/css';
9
8
 
10
9
  async function removeTopSourceComment(filePath) {
11
10
  const code = await readFile(filePath, 'utf8');
@@ -14,24 +13,17 @@ async function removeTopSourceComment(filePath) {
14
13
  }
15
14
 
16
15
  async function esbuildCSS(config, isDebug, platform) {
17
- let buildResult;
18
- let outputs;
16
+ let buildResult, outputs;
19
17
  const dir = getDestDir({isDebug, platform});
20
-
18
+
21
19
  for (const [dest, src] of Object.entries(config.entry)) {
22
20
  buildResult = await build({
23
21
  entryPoints: src,
24
22
  outdir: path.join(dir, dest),
25
23
  entryNames: config.filename,
26
24
  loader: {
27
- '.scss': 'css'
25
+ '.css': 'css'
28
26
  },
29
- plugins: [
30
- sassPlugin({
31
- type: 'css',
32
- sourceMap: config.sourcemap,
33
- })
34
- ],
35
27
  minify: config.minify,
36
28
  sourcemap: config.sourcemap,
37
29
  metafile: true,
@@ -49,60 +41,61 @@ async function esbuildCSS(config, isDebug, platform) {
49
41
  }
50
42
  }
51
43
 
52
- export function createBundleCSSTask(srcSCSSDir){
44
+ function createBundleCSSTask(srcCSSDir) {
53
45
  let currentWatchFiles;
46
+
54
47
  const bundleCSS = async ({platforms, isDebug, logInfo, logWarn}) => {
55
- for(const platform of platforms){
56
- const config = (await getConfig(platform));
57
- const scssConfig = config.scss;
58
- if (scssConfig){
59
- await esbuildCSS(scssConfig, isDebug, platform);
48
+ for (const platform of platforms) {
49
+ const config = await getConfig(platform);
50
+ const cssConfig = config.css;
51
+ if (cssConfig) {
52
+ await esbuildCSS(cssConfig, isDebug, platform);
60
53
  if (logInfo) log.ok(`Bundling CSS for ${platform}...`);
61
54
  } else {
62
- if(logWarn) log.warn(`No SCSS config found for ${platform}, skipping CSS bundling.`);
63
- }
55
+ if (logWarn) log.warn(`No CSS config found for ${platform}, skipping CSS bundling.`);
56
+ }
64
57
  }
65
58
  }
66
59
 
67
60
  const onChange = async (changedFiles, watcher, platforms, isDebug) => {
68
- for(const platform of platforms){
61
+ for (const platform of platforms) {
69
62
  const config = await getConfig(platform);
70
- if (config.scss){
63
+ if (config.css) {
71
64
  const exists = await fileExistsInConfig(
72
- config.scss.entry,
65
+ config.css.entry,
73
66
  changedFiles[0]
74
67
  );
75
- if (exists){
68
+ if (exists) {
76
69
  const newConfig = {
77
- scss: {
70
+ css: {
78
71
  entry: exists,
79
- filename: config.scss.filename,
80
- minify: config.scss.minify,
81
- sourcemap: config.scss.sourcemap
72
+ filename: config.css.filename,
73
+ minify: config.css.minify,
74
+ sourcemap: config.css.sourcemap
82
75
  }
83
76
  }
84
- await esbuildCSS(newConfig.scss, isDebug, platform);
77
+ await esbuildCSS(newConfig.css, isDebug, platform);
85
78
  }
86
-
87
79
  }
88
80
  }
89
81
 
90
- const newWatchFiles = await getAllFiles(srcSCSSDir);
82
+ const newWatchFiles = await getAllFiles(srcCSSDir);
91
83
  watcher.unwatch(
92
84
  currentWatchFiles.filter((oldFile) => !newWatchFiles.includes(oldFile))
93
85
  );
94
86
  watcher.add(
95
- newWatchFiles.filter((newFile) => currentWatchFiles.includes(newFile))
87
+ newWatchFiles.filter((newFile) => !currentWatchFiles.includes(newFile))
96
88
  );
97
89
  }
90
+
98
91
  return createTask(
99
92
  'bundle CSS',
100
93
  bundleCSS,
101
94
  ).addWatcher(
102
95
  async () => {
103
- currentWatchFiles = await getAllFiles(srcSCSSDir);
96
+ currentWatchFiles = await getAllFiles(srcCSSDir);
104
97
  return currentWatchFiles;
105
98
  }, onChange);
106
99
  }
107
100
 
108
- export default createBundleCSSTask(srcSCSSDir);
101
+ export default createBundleCSSTask(srcCSSDir);
@@ -13,7 +13,6 @@ async function removeTopSourceComment(filePath) {
13
13
  }
14
14
 
15
15
  async function esbuildJS(config, isDebug, platform){
16
- // console.log(`Starting JS build for ${config}...`);
17
16
  let buildResult, outputs;
18
17
  const dir = getDestDir({isDebug, platform});
19
18
  for (const [dest, src] of Object.entries(config.entry)) {
@@ -1,4 +1,4 @@
1
- import {join} from 'node:path';
1
+ import {join, dirname} from 'node:path';
2
2
  import {fileURLToPath} from 'node:url';
3
3
  import process from 'node:process';
4
4
  import {fork} from 'node:child_process';
@@ -6,7 +6,7 @@ import {log} from './utils.js';
6
6
 
7
7
  function printHelp() {
8
8
  console.log([
9
- 'Volume booster build utility',
9
+ 'WebExtension build utility',
10
10
  'Usage: npm run build -- [options]',
11
11
  '',
12
12
  'To narrow down the list of build targets (for efficiency):',
@@ -30,7 +30,7 @@ function printHelp() {
30
30
  ].join('\n'));
31
31
  }
32
32
 
33
- const __filename = join(fileURLToPath(import.meta.url), '../build.js');
33
+ const __filename = join(dirname(fileURLToPath(import.meta.url)), 'build.js');
34
34
 
35
35
  async function executeChildProcess(args) {
36
36
  const child = fork(__filename, args);
@@ -61,14 +61,20 @@ function validateArguments(args) {
61
61
  '--help',
62
62
  '-h'
63
63
  ];
64
- const invalidFlags = args.filter((flag) => !validFlags.includes(flag));
64
+ const invalidFlags = args.filter((flag) => !validFlags.includes(flag) && !flag.startsWith('--version='));
65
65
  invalidFlags.forEach((flag) => validationErrors.push(`Invalid flag ${flag}`));
66
66
  return validationErrors;
67
67
  }
68
68
 
69
69
  async function run() {
70
- const args = process.argv.slice(3);
71
- const shouldPrintHelp = args.length === 0 || process.argv[2] !== 'build' || args.includes('-h') || args.includes('--help');
70
+ const subcommand = process.argv[2];
71
+ let args = process.argv.slice(3);
72
+
73
+ if (subcommand === 'zip') {
74
+ args = ['--all', '--release'];
75
+ }
76
+
77
+ const shouldPrintHelp = args.length === 0 || (subcommand !== 'build' && subcommand !== 'zip') || args.includes('-h') || args.includes('--help');
72
78
  if (shouldPrintHelp) {
73
79
  printHelp();
74
80
  process.exit(0);
@@ -6,7 +6,6 @@ import path from 'node:path';
6
6
  import { mkdir } from 'node:fs/promises';
7
7
 
8
8
  export function createCopyTask() {
9
- const paths = [];
10
9
  const assetsCopy = async ({platforms, isDebug, logInfo, logWarn}) => {
11
10
  for(const platform of platforms){
12
11
  const config = await getConfig(platform);
@@ -40,7 +39,7 @@ export function createCopyTask() {
40
39
  'assets copy',
41
40
  assetsCopy,
42
41
  ).addWatcher(
43
- paths,
42
+ [],
44
43
  onChange,
45
44
  );
46
45
  }
@@ -1,7 +1,6 @@
1
- import {createRequire} from 'node:module';
2
1
  import {dirname, join} from 'node:path';
3
2
 
4
- let rootDir = dirname(createRequire(import.meta.url).resolve('../../package.json'));
3
+ let rootDir = process.cwd();
5
4
 
6
5
  export const absolutePath = (path) => {
7
6
  return join(rootDir, path);
@@ -10,7 +9,7 @@ export function setRootDir(dir) {
10
9
  rootDir = dir;
11
10
  }
12
11
  export function getDestDir({isDebug, platform}) {
13
- const buildTypeDir = `build/${isDebug ? 'debug':'release'}`;
12
+ const buildTypeDir = `.output/${isDebug ? 'debug':'release'}`;
14
13
  return `${buildTypeDir}/${platform}`;
15
14
  }
16
15
  export default {
@@ -41,7 +41,7 @@ async function zip({platforms, isDebug, version}) {
41
41
  throw new Error('zip task does not support debug builds');
42
42
  }
43
43
  version = version ? `-${version}` : '';
44
- const releaseDir = 'build/release';
44
+ const releaseDir = '.output/release';
45
45
  const promises = [];
46
46
  const date = await getLastCommitTime();
47
47
  const xpiPlatforms = ["firefox", "thunderbird"];
@@ -1,8 +1,12 @@
1
1
  import { mkdirSync, readdirSync, existsSync, readFileSync, writeFileSync } from "fs";
2
- import { join } from "path";
2
+ import { join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
3
4
 
4
- const localesDir = join(__dirname, "_locales");
5
- const outputDir = join(__dirname, "output", "_locales");
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+
8
+ const localesDir = join(__dirname, "../", "_locales");
9
+ const outputDir = join(__dirname, "../", "output", "_locales");
6
10
 
7
11
  mkdirSync(outputDir, { recursive: true });
8
12
 
@@ -1,255 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import * as readline from 'node:readline/promises';
3
- import { stdin as input, stdout as output } from 'node:process';
4
- import {readFile, writeFile, httpsRequest, timeout} from './utils.js';
5
-
6
-
7
- const LOCALES_ROOT = 'src/_locales';
8
-
9
- function toMessageId(message) {
10
- if (typeof message !== 'string') return '';
11
-
12
- return message
13
- .trim()
14
- .split(/\s+/) // split by any whitespace
15
- .slice(0, 3) // only first 3 words
16
- .map(word =>
17
- word
18
- .replace(/[^\w]/g, '') // remove special characters
19
- .toLowerCase()
20
- )
21
- .filter(Boolean) // remove empty results
22
- .join('_');
23
- }
24
-
25
- async function getSupportedLocales() {
26
- const fileList = await fs.readdir(LOCALES_ROOT);
27
-
28
- const locales = [];
29
-
30
- for (const file of fileList) {
31
- if (file.endsWith('.i18n')) {
32
- const locale = file.substring(0, file.lastIndexOf('.i18n'));
33
- locales.push(locale);
34
- }
35
- }
36
-
37
- return locales;
38
- }
39
-
40
- function stringifyLocale(messages) {
41
- const lines = [];
42
- messages.forEach((message, id) => {
43
- lines.push(`@${id}`);
44
- const hasDoubleNewLines = /\n\n/.test(message);
45
- message.split('\n')
46
- .filter((line) => line.trim())
47
- .forEach((line, index, filtered) => {
48
- lines.push(line);
49
- if (hasDoubleNewLines && index < filtered.length - 1) {
50
- lines.push('');
51
- }
52
- });
53
- lines.push('');
54
- });
55
- return lines.join('\n');
56
- }
57
-
58
- function parseLocale(content) {
59
- const messages = new Map();
60
- const lines = content.split('\n');
61
- let id = '';
62
- for (let i = 0; i < lines.length; i++) {
63
- const line = lines[i];
64
- if (line.startsWith('@')) {
65
- id = line.substring(1);
66
- } else if (line.startsWith('#')) {
67
- // Ignore
68
- } else if (messages.has(id)) {
69
- const message = messages.get(id);
70
- messages.set(id, `${message}\n${line}`);
71
- } else {
72
- messages.set(id, line);
73
- }
74
- }
75
- messages.forEach((value, id) => {
76
- messages.set(id, value.trim());
77
- });
78
- return messages;
79
- }
80
-
81
- async function translate(text, lang) {
82
- const url = new URL('https://translate.googleapis.com/translate_a/single');
83
- url.search = (new URLSearchParams({
84
- client: 'gtx',
85
- sl: 'en-US',
86
- tl: lang,
87
- dt: 't',
88
- dj: '1',
89
- q: text,
90
- })).toString();
91
- const response = await httpsRequest(url.toString());
92
- const translation = JSON.parse(response.text());
93
- return translation.sentences.map((s) => s.trans).join('\n').replaceAll(/\n+/g, '\n');
94
- }
95
-
96
- async function deleteMessage(messageId) {
97
- const supportedLocales = await getSupportedLocales();
98
-
99
- if (!messageId || !messageId.trim()) {
100
- console.log('⚠ Message key cannot be empty.');
101
- return;
102
- }
103
-
104
- const id = messageId.trim();
105
- let foundAnywhere = false;
106
-
107
- for (const locale of supportedLocales) {
108
- const locFile = `${LOCALES_ROOT}/${locale}.i18n`;
109
- const locContent = await readFile(locFile);
110
- const locMessages = parseLocale(locContent);
111
-
112
- if (!locMessages.has(id)) {
113
- continue;
114
- }
115
-
116
- locMessages.delete(id);
117
- const output = stringifyLocale(locMessages);
118
- await writeFile(locFile, output);
119
-
120
- console.log(`✔ Removed from ${locale}.i18n`);
121
- foundAnywhere = true;
122
- }
123
-
124
- if (!foundAnywhere) {
125
- console.log('⚠ Message key not found in any locale.');
126
- } else {
127
- console.log('🗑 Deletion completed.');
128
- }
129
- }
130
-
131
-
132
- async function translateEnMessage(message, customId) {
133
- console.log(`Translating message: ${message}`);
134
-
135
- const supportedLocales = await getSupportedLocales();
136
- const messageId = customId && customId.trim()
137
- ? customId.trim()
138
- : toMessageId(message);
139
-
140
- // 1️⃣ Update English first
141
- const enFile = `${LOCALES_ROOT}/en.i18n`;
142
- const enContent = await readFile(enFile);
143
- const enMessages = parseLocale(enContent);
144
-
145
- if (!enMessages.has(messageId)) {
146
- enMessages.set(messageId, message);
147
- const output = stringifyLocale(enMessages);
148
- await writeFile(enFile, output);
149
- console.log(`en: ${message}`);
150
- } else {
151
- console.log('Message already exists in en.i18n');
152
- }
153
-
154
- // 2️⃣ Translate other locales
155
- for (const locale of supportedLocales) {
156
- if (locale === 'en') continue;
157
-
158
- await timeout(1000);
159
-
160
- const locFile = `${LOCALES_ROOT}/${locale}.i18n`;
161
- const locContent = await readFile(locFile);
162
- const locMessages = parseLocale(locContent);
163
-
164
- if (locMessages.has(messageId)) {
165
- console.log(`Already exists in: ${locFile}`);
166
- continue;
167
- }
168
-
169
- const translated = await translate(message, locale);
170
- locMessages.set(messageId, translated);
171
-
172
- const output = stringifyLocale(locMessages);
173
- await writeFile(locFile, output);
174
-
175
- console.log(`${locale}: ${translated}`);
176
- }
177
- }
178
-
179
- async function main() {
180
- const rl = readline.createInterface({ input, output });
181
-
182
- try {
183
- while (true) {
184
- console.log('\n=== Message Manager ===');
185
- console.log('[1] Add new message');
186
- console.log('[2] Delete message');
187
- console.log('[0] Exit');
188
-
189
- const choice = (await rl.question('Select option: '))
190
- .trim()
191
- .toLowerCase();
192
-
193
- switch (choice) {
194
- case '1': {
195
- const newMessage = (await rl.question('Enter new message: '))
196
- .trim();
197
-
198
- if (!newMessage) {
199
- console.log('⚠ Message cannot be empty.');
200
- break;
201
- }
202
-
203
- const newMessageId = (await rl.question(
204
- 'Enter new message key (leave empty for auto-generation): '
205
- )).trim();
206
-
207
- await translateEnMessage(
208
- newMessage,
209
- newMessageId || undefined
210
- );
211
-
212
- console.log('✔ Message processed successfully.');
213
- break;
214
- }
215
-
216
- case '2': {
217
- const messageId = (await rl.question(
218
- 'Enter message key to delete: '
219
- )).trim();
220
-
221
- if (!messageId) {
222
- console.log('⚠ Message key cannot be empty.');
223
- break;
224
- }
225
-
226
- const confirm = (await rl.question(
227
- `Are you sure you want to delete "${messageId}" from all locales? (y/n): `
228
- ))
229
- .trim()
230
- .toLowerCase();
231
-
232
- if (confirm !== 'y') {
233
- console.log('Deletion cancelled.');
234
- break;
235
- }
236
-
237
- await deleteMessage(messageId);
238
- break;
239
- }
240
-
241
- case '0':
242
- case 'exit': {
243
- console.log('Exiting...');
244
- return;
245
- }
246
-
247
- default: {
248
- console.log('Invalid selection. Please try again.');
249
- }
250
- }
251
- }
252
- } finally {
253
- rl.close();
254
- }
255
- }main();
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes