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
@@ -0,0 +1,388 @@
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
+ <title>Addonova Tools</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+ body { font-family: system-ui, -apple-system, sans-serif; background: #f5f5f5; color: #222; min-height: 100vh; }
10
+ .header { background: #1a73e8; color: #fff; padding: 16px 24px; display: flex; align-items: center; gap: 12px; }
11
+ .header h1 { font-size: 20px; font-weight: 600; }
12
+ .header span { font-size: 13px; opacity: 0.85; }
13
+ .tabs { display: flex; background: #e8e8e8; border-bottom: 1px solid #ccc; }
14
+ .tab { padding: 10px 24px; cursor: pointer; border: none; background: none; font-size: 14px; font-weight: 500; color: #555; border-bottom: 2px solid transparent; }
15
+ .tab:hover { background: #ddd; }
16
+ .tab.active { background: #fff; color: #1a73e8; border-bottom-color: #1a73e8; }
17
+ .container { max-width: 1100px; margin: 0 auto; padding: 24px; display: none; }
18
+ .container.active { display: block; }
19
+ .card { background: #fff; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,.1); padding: 20px; margin-bottom: 16px; }
20
+ .card h2 { font-size: 16px; margin-bottom: 12px; color: #333; }
21
+ .form-row { display: flex; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; align-items: flex-start; }
22
+ label { font-size: 13px; font-weight: 500; color: #555; display: block; margin-bottom: 4px; }
23
+ input[type=text], textarea, select { width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 6px; font-size: 14px; font-family: inherit; }
24
+ input[type=text]:focus, textarea:focus, select:focus { outline: none; border-color: #1a73e8; box-shadow: 0 0 0 2px rgba(26,115,232,.2); }
25
+ textarea { min-height: 80px; resize: vertical; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 13px; }
26
+ button { padding: 8px 16px; border: none; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; background: #1a73e8; color: #fff; }
27
+ button:hover { background: #1557b0; }
28
+ button:disabled { opacity: .5; cursor: not-allowed; }
29
+ button.danger { background: #d93025; }
30
+ button.danger:hover { background: #b3261e; }
31
+ button.outline { background: transparent; border: 1px solid #ccc; color: #333; }
32
+ button.outline:hover { background: #f0f0f0; }
33
+ .locale-list { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
34
+ .locale-tag { padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; background: #e8f0fe; color: #1a73e8; }
35
+ .msg-table { width: 100%; border-collapse: collapse; font-size: 13px; }
36
+ .msg-table th, .msg-table td { padding: 6px 10px; text-align: left; border-bottom: 1px solid #eee; }
37
+ .msg-table th { font-weight: 600; color: #555; position: sticky; top: 0; background: #fafafa; }
38
+ .msg-table tr:hover td { background: #f8f9ff; }
39
+ .msg-table .key { font-family: monospace; font-weight: 600; color: #1a73e8; white-space: nowrap; }
40
+ .msg-table .val { max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
41
+ .msg-table .del-btn { color: #d93025; cursor: pointer; font-size: 12px; padding: 2px 6px; border-radius: 4px; }
42
+ .msg-table .del-btn:hover { background: #fce8e6; }
43
+ .file-drop { border: 2px dashed #ccc; border-radius: 8px; padding: 32px; text-align: center; cursor: pointer; transition: .2s; }
44
+ .file-drop:hover, .file-drop.dragover { border-color: #1a73e8; background: #f0f6ff; }
45
+ .file-drop p { color: #888; font-size: 14px; }
46
+ .file-drop .icon { font-size: 36px; margin-bottom: 8px; }
47
+ .output-area { background: #fafafa; border: 1px solid #eee; border-radius: 6px; padding: 12px; font-family: monospace; font-size: 13px; white-space: pre-wrap; max-height: 300px; overflow: auto; margin-top: 10px; }
48
+ .loading { opacity: .6; pointer-events: none; }
49
+ .spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid #ccc; border-top-color: #1a73e8; border-radius: 50%; animation: spin .6s linear infinite; margin-right: 6px; vertical-align: middle; }
50
+ @keyframes spin { to { transform: rotate(360deg); } }
51
+ .toast { position: fixed; bottom: 20px; right: 20px; padding: 10px 16px; border-radius: 6px; color: #fff; font-size: 13px; opacity: 0; transition: .3s; z-index: 100; }
52
+ .toast.show { opacity: 1; }
53
+ .toast.ok { background: #188038; }
54
+ .toast.err { background: #d93025; }
55
+ .col { flex: 1; min-width: 200px; }
56
+ .grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
57
+ @media (max-width: 700px) { .grid2 { grid-template-columns: 1fr; } }
58
+ </style>
59
+ </head>
60
+ <body>
61
+
62
+ <div class="header">
63
+ <h1>🛠 Addonova Tools</h1>
64
+ <span id="status">disconnected</span>
65
+ </div>
66
+
67
+ <div class="tabs">
68
+ <button class="tab active" data-tab="translate">Translate</button>
69
+ <button class="tab" data-tab="json2i18n">JSON → i18n</button>
70
+ </div>
71
+
72
+ <div class="container active" id="tab-translate">
73
+ <div class="card">
74
+ <h2>Locales</h2>
75
+ <div class="locale-list" id="localeList"><span class="spinner"></span> loading...</div>
76
+ </div>
77
+
78
+ <div class="grid2">
79
+ <div class="card">
80
+ <h2>Add Message</h2>
81
+ <div class="form-row">
82
+ <div class="col">
83
+ <label>English text</label>
84
+ <input type="text" id="addMsgText" placeholder="Enter English message">
85
+ </div>
86
+ <div class="col">
87
+ <label>Message key (leave blank for auto)</label>
88
+ <input type="text" id="addMsgKey" placeholder="auto-generated">
89
+ </div>
90
+ </div>
91
+ <button id="addMsgBtn">Add & Translate</button>
92
+ </div>
93
+
94
+ <div class="card">
95
+ <h2>Delete Message</h2>
96
+ <div class="form-row">
97
+ <div class="col">
98
+ <label>Message key</label>
99
+ <input type="text" id="delMsgKey" placeholder="message_key">
100
+ </div>
101
+ <div class="col">
102
+ <label>Scope</label>
103
+ <select id="delMsgScope">
104
+ <option value="all">All locales</option>
105
+ </select>
106
+ </div>
107
+ </div>
108
+ <button class="danger" id="delMsgBtn">Delete</button>
109
+ </div>
110
+ </div>
111
+
112
+ <div class="card">
113
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
114
+ <h2 style="margin:0">Messages</h2>
115
+ <button class="outline" id="refreshMsgBtn" style="font-size:12px">Refresh</button>
116
+ </div>
117
+ <div id="msgViewer" style="max-height:400px;overflow:auto">
118
+ <div class="spinner"></div> loading...
119
+ </div>
120
+ </div>
121
+ </div>
122
+
123
+ <div class="container" id="tab-json2i18n">
124
+ <div class="grid2">
125
+ <div class="card">
126
+ <h2>Upload messages.json</h2>
127
+ <div class="file-drop" id="fileDrop">
128
+ <div class="icon">📄</div>
129
+ <p>Drop a <strong>messages.json</strong> file here or click to browse</p>
130
+ </div>
131
+ <input type="file" id="fileInput" accept=".json" style="display:none">
132
+ <div id="jsonPreview" class="output-area" style="display:none;margin-top:12px"></div>
133
+ </div>
134
+
135
+ <div class="card">
136
+ <h2>i18n Output</h2>
137
+ <button id="copyI18nBtn" style="margin-bottom:8px;font-size:12px" class="outline">📋 Copy</button>
138
+ <div id="i18nOutput" class="output-area">Upload a file to see the result</div>
139
+ </div>
140
+ </div>
141
+ </div>
142
+
143
+ <div class="toast" id="toast"></div>
144
+
145
+ <script>
146
+ const api = (path, opts = {}) =>
147
+ fetch(path, { headers: { 'Content-Type': 'application/json' }, ...opts })
148
+ .then(r => r.json())
149
+ .catch(() => ({ error: 'Network error' }));
150
+
151
+ function toast(msg, type = 'ok') {
152
+ const el = document.getElementById('toast');
153
+ el.textContent = msg;
154
+ el.className = `toast ${type} show`;
155
+ clearTimeout(el._t);
156
+ el._t = setTimeout(() => el.classList.remove('show'), 3000);
157
+ }
158
+
159
+ // ── Tabs ──
160
+ document.querySelectorAll('.tab').forEach(tab => {
161
+ tab.addEventListener('click', () => {
162
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
163
+ document.querySelectorAll('.container').forEach(c => c.classList.remove('active'));
164
+ tab.classList.add('active');
165
+ document.getElementById('tab-' + tab.dataset.tab).classList.add('active');
166
+ });
167
+ });
168
+
169
+ // ── Locales ──
170
+ async function loadLocales() {
171
+ const data = await api('/api/locales');
172
+ const list = document.getElementById('localeList');
173
+ const scope = document.getElementById('delMsgScope');
174
+
175
+ if (data.error) {
176
+ list.innerHTML = `<span style="color:#d93025">⚠ ${data.error}</span>`;
177
+ return;
178
+ }
179
+
180
+ list.innerHTML = (data.locales || []).map(l => `<span class="locale-tag">${l}</span>`).join('');
181
+
182
+ const current = scope.value;
183
+ scope.innerHTML = '<option value="all">All locales</option>'
184
+ + (data.locales || []).map(l => `<option value="${l}">${l}</option>`).join('');
185
+ if (current) scope.value = current;
186
+
187
+ return data;
188
+ }
189
+
190
+ // ── Messages table ──
191
+ async function loadMessages() {
192
+ const el = document.getElementById('msgViewer');
193
+ const data = await api('/api/locales');
194
+
195
+ if (data.error) {
196
+ el.innerHTML = `<span style="color:#d93025">⚠ ${data.error}</span>`;
197
+ return;
198
+ }
199
+
200
+ if (!data.locales || !data.locales.length) {
201
+ el.innerHTML = '<span style="color:#888">No locale files found in src/_locales/</span>';
202
+ return;
203
+ }
204
+
205
+ const en = data.data['en'] || {};
206
+ const keys = Object.keys(en);
207
+
208
+ if (!keys.length) {
209
+ el.innerHTML = '<span style="color:#888">No messages found</span>';
210
+ return;
211
+ }
212
+
213
+ let html = '<table class="msg-table"><thead><tr><th>Key</th><th>English</th>';
214
+ for (const loc of data.locales) html += `<th>${loc}</th>`;
215
+ html += '<th></th></tr></thead><tbody>';
216
+
217
+ for (const key of keys) {
218
+ html += `<tr><td class="key">${key}</td>`;
219
+ html += `<td class="val">${esc(data.data.en[key] || '')}</td>`;
220
+ for (const loc of data.locales) {
221
+ html += `<td class="val">${esc(data.data[loc]?.[key] || '')}</td>`;
222
+ }
223
+ html += `<td><span class="del-btn" data-key="${key}">✕</span></td>`;
224
+ html += '</tr>';
225
+ }
226
+
227
+ html += '</tbody></table>';
228
+ el.innerHTML = html;
229
+
230
+ el.querySelectorAll('.del-btn').forEach(btn => {
231
+ btn.addEventListener('click', async () => {
232
+ const key = btn.dataset.key;
233
+ if (!confirm(`Delete "${key}" from all locales?`)) return;
234
+ const res = await api('/api/messages/delete', {
235
+ method: 'POST',
236
+ body: JSON.stringify({ messageId: key }),
237
+ });
238
+ if (res.error) { toast(res.error, 'err'); return; }
239
+ toast(`Deleted from ${res.deleted.length} locale(s)`);
240
+ loadMessages();
241
+ });
242
+ });
243
+ }
244
+
245
+ function esc(s) {
246
+ const d = document.createElement('div');
247
+ d.textContent = s;
248
+ return d.innerHTML;
249
+ }
250
+
251
+ // ── Add message ──
252
+ document.getElementById('addMsgBtn').addEventListener('click', async function () {
253
+ const message = document.getElementById('addMsgText').value.trim();
254
+ if (!message) { toast('Enter a message', 'err'); return; }
255
+ const customId = document.getElementById('addMsgKey').value.trim();
256
+
257
+ this.disabled = true;
258
+ this.innerHTML = '<span class="spinner"></span> Translating...';
259
+
260
+ const res = await api('/api/messages/add', {
261
+ method: 'POST',
262
+ body: JSON.stringify({ message, customId: customId || undefined }),
263
+ });
264
+
265
+ this.disabled = false;
266
+ this.textContent = 'Add & Translate';
267
+
268
+ if (res.error) { toast(res.error, 'err'); return; }
269
+ toast(`Added as "${res.messageId}" — translated to ${Object.keys(res.locales).length} locales`);
270
+ document.getElementById('addMsgText').value = '';
271
+ document.getElementById('addMsgKey').value = '';
272
+ loadMessages();
273
+ loadLocales();
274
+ });
275
+
276
+ // ── Delete message ──
277
+ document.getElementById('delMsgBtn').addEventListener('click', async function () {
278
+ const messageId = document.getElementById('delMsgKey').value.trim();
279
+ if (!messageId) { toast('Enter message key', 'err'); return; }
280
+
281
+ const scope = document.getElementById('delMsgScope').value;
282
+ const targetLocale = scope === 'all' ? null : scope;
283
+ const label = targetLocale ? `"${targetLocale}"` : 'ALL locales';
284
+
285
+ if (!confirm(`Delete "${messageId}" from ${label}?`)) return;
286
+
287
+ this.disabled = true;
288
+ this.innerHTML = 'Deleting...';
289
+
290
+ const res = await api('/api/messages/delete', {
291
+ method: 'POST',
292
+ body: JSON.stringify({ messageId, targetLocale }),
293
+ });
294
+
295
+ this.disabled = false;
296
+ this.textContent = 'Delete';
297
+
298
+ if (res.error) { toast(res.error, 'err'); return; }
299
+ toast(`Deleted from ${res.deleted.length} locale(s)`);
300
+ document.getElementById('delMsgKey').value = '';
301
+ loadMessages();
302
+ loadLocales();
303
+ });
304
+
305
+ // ── Refresh ──
306
+ document.getElementById('refreshMsgBtn').addEventListener('click', () => {
307
+ loadMessages();
308
+ loadLocales();
309
+ });
310
+
311
+ // ── JSON → i18n ──
312
+ const fileDrop = document.getElementById('fileDrop');
313
+ const fileInput = document.getElementById('fileInput');
314
+ const jsonPreview = document.getElementById('jsonPreview');
315
+ const i18nOutput = document.getElementById('i18nOutput');
316
+
317
+ fileDrop.addEventListener('click', () => fileInput.click());
318
+
319
+ fileDrop.addEventListener('dragover', e => {
320
+ e.preventDefault();
321
+ fileDrop.classList.add('dragover');
322
+ });
323
+ fileDrop.addEventListener('dragleave', () => fileDrop.classList.remove('dragover'));
324
+ fileDrop.addEventListener('drop', e => {
325
+ e.preventDefault();
326
+ fileDrop.classList.remove('dragover');
327
+ if (e.dataTransfer.files.length) handleFile(e.dataTransfer.files[0]);
328
+ });
329
+
330
+ fileInput.addEventListener('change', () => {
331
+ if (fileInput.files.length) handleFile(fileInput.files[0]);
332
+ });
333
+
334
+ function handleFile(file) {
335
+ if (!file.name.endsWith('.json')) {
336
+ toast('Please select a .json file', 'err');
337
+ return;
338
+ }
339
+
340
+ const reader = new FileReader();
341
+ reader.onload = async (e) => {
342
+ try {
343
+ const json = JSON.parse(e.target.result);
344
+ jsonPreview.textContent = JSON.stringify(json, null, 2);
345
+ jsonPreview.style.display = 'block';
346
+
347
+ const res = await api('/api/json2i18n', {
348
+ method: 'POST',
349
+ body: JSON.stringify({ json }),
350
+ });
351
+
352
+ if (res.error) { toast(res.error, 'err'); return; }
353
+ i18nOutput.textContent = res.i18n;
354
+ } catch {
355
+ toast('Invalid JSON file', 'err');
356
+ }
357
+ };
358
+ reader.readAsText(file);
359
+ }
360
+
361
+ document.getElementById('copyI18nBtn').addEventListener('click', () => {
362
+ const text = i18nOutput.textContent;
363
+ if (!text || text === 'Upload a file to see the result') return;
364
+ navigator.clipboard.writeText(text).then(() => toast('Copied!'));
365
+ });
366
+
367
+ // ── Status ──
368
+ (async function init() {
369
+ const data = await api('/api/locales');
370
+ const status = document.getElementById('status');
371
+ if (data.error) {
372
+ status.textContent = '⚠ server error';
373
+ status.style.color = '#ffbb00';
374
+ return;
375
+ }
376
+ status.textContent = `✔ ${data.locales.length} locales`;
377
+ status.style.color = '#a8e6a3';
378
+
379
+ if (data.locales.length) {
380
+ loadMessages();
381
+ } else {
382
+ document.getElementById('msgViewer').innerHTML =
383
+ '<span style="color:#888">No locale files found in src/_locales/</span>';
384
+ }
385
+ })();
386
+ </script>
387
+ </body>
388
+ </html>
@@ -0,0 +1,30 @@
1
+ export default {
2
+ js: {
3
+ entry: {
4
+ '': ['src/js/background.js'],
5
+ 'data/interface': ['src/js/popup.js'],
6
+ },
7
+ filename: '[name]',
8
+ minify: true,
9
+ sourcemap: false,
10
+ },
11
+ css: {
12
+ entry: {
13
+ 'data/interface': ['src/css/popup.css'],
14
+ },
15
+ filename: '[name]',
16
+ minify: true,
17
+ sourcemap: false,
18
+ },
19
+ html: {
20
+ entry: {
21
+ 'data/interface': ['src/html/popup.html'],
22
+ },
23
+ filename: '[name]',
24
+ },
25
+ assets: {
26
+ entry: {
27
+ 'data/icons': ['src/assets/icons'],
28
+ },
29
+ },
30
+ };
@@ -0,0 +1,30 @@
1
+ export default {
2
+ js: {
3
+ entry: {
4
+ '': ['src/js/background.js'],
5
+ 'data/interface': ['src/js/popup.js'],
6
+ },
7
+ filename: '[name]',
8
+ minify: true,
9
+ sourcemap: false,
10
+ },
11
+ css: {
12
+ entry: {
13
+ 'data/interface': ['src/css/popup.css'],
14
+ },
15
+ filename: '[name]',
16
+ minify: true,
17
+ sourcemap: false,
18
+ },
19
+ html: {
20
+ entry: {
21
+ 'data/interface': ['src/html/popup.html'],
22
+ },
23
+ filename: '[name]',
24
+ },
25
+ assets: {
26
+ entry: {
27
+ 'data/icons': ['src/assets/icons'],
28
+ },
29
+ },
30
+ };
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "{{version}}",
4
+ "description": "{{description}}",
5
+ "json.schemaValidation": "off",
6
+ "license": "GPLv3",
7
+ "author": "Addonova",
8
+ "type": "module",
9
+ "engines": {
10
+ "node": ">=22",
11
+ "npm": ">=11"
12
+ },
13
+ "scripts": {
14
+ "release": "addonova build --all --release",
15
+ "release:chrome": "addonova build --chrome --release",
16
+ "release:edge": "addonova build --edge --release",
17
+ "release:opera": "addonova build --opera --release",
18
+ "release:firefox": "addonova build --firefox --release",
19
+ "release:thunderbird": "addonova build --thunderbird --release",
20
+ "release:naver": "addonova build --naver --release",
21
+ "debug": "addonova build --all --debug",
22
+ "debug:chrome": "addonova build --chrome --debug",
23
+ "debug:edge": "addonova build --edge --debug",
24
+ "debug:opera": "addonova build --opera --debug",
25
+ "debug:firefox": "addonova build --firefox --debug",
26
+ "debug:thunderbird": "addonova build --thunderbird --debug",
27
+ "debug:naver": "addonova build --naver --debug",
28
+ "dev": "addonova build --all --debug --watch",
29
+ "tool": "addonova tool",
30
+ "zip": "addonova zip",
31
+ "test": "node --test"
32
+ },
33
+ "devDependencies": {
34
+ "addonova": "^1.0.4"
35
+ }
36
+ }
@@ -10,19 +10,4 @@ const LINKS = {
10
10
  "facebook": "https://www.facebook.com/codehemu/",
11
11
  "youtube": "https://www.youtube.com/@CodeHemu",
12
12
  "twitter": "https://x.com/CodeHemu"
13
- };
14
-
15
- const DEBOUNCE = (callback, delay) => {
16
- if (typeof callback !== "function") {
17
- return false;
18
- }
19
-
20
- let timer;
21
- return function(...args) {
22
- const context = this;
23
- clearTimeout(timer);
24
- timer = setTimeout(function() {
25
- callback.apply(context, args);
26
- }, delay);
27
- };
28
13
  };
@@ -1,67 +0,0 @@
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
-
7
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
- const TEMPLATE_DIR = path.resolve(__dirname, '../../template');
9
- const VARIABLE_RE = /\{\{\s*(\w+)\s*\}\}/g;
10
- const SKIP_DIRS = new Set(['config', 'src', 'platform', 'workspace']);
11
-
12
- function render(template, vars) {
13
- return template.replace(VARIABLE_RE, (_, key) => vars[key] ?? `{{${key}}}`);
14
- }
15
-
16
- async function copyDir(src, dest, vars) {
17
- await fs.mkdir(dest, { recursive: true });
18
- const entries = await fs.readdir(src, { withFileTypes: true });
19
-
20
- for (const entry of entries) {
21
- if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue;
22
-
23
- const srcPath = path.join(src, entry.name);
24
- const destPath = path.join(dest, entry.name.replace(/\.tpl$/, ''));
25
-
26
- if (entry.isDirectory()) {
27
- await copyDir(srcPath, destPath, vars);
28
- } else if (entry.name.endsWith('.tpl')) {
29
- const content = await fs.readFile(srcPath, 'utf-8');
30
- await fs.writeFile(destPath, render(content, vars), 'utf-8');
31
- } else {
32
- await fs.copyFile(srcPath, destPath);
33
- }
34
- }
35
- }
36
-
37
- export async function update() {
38
- const cwd = process.cwd();
39
- const pkgPath = path.join(cwd, 'package.json');
40
-
41
- if (!existsSync(pkgPath)) {
42
- console.error('[*] No package.json found. Run this command inside an addonova project.');
43
- process.exit(1);
44
- }
45
-
46
- let pkg;
47
- try {
48
- pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
49
- } catch {
50
- console.error('[*] Invalid package.json.');
51
- process.exit(1);
52
- }
53
-
54
- const vars = {
55
- name: pkg.name || 'extension',
56
- description: pkg.description || 'A cross-browser WebExtension',
57
- version: pkg.version || '1.0.0',
58
- browsers: JSON.stringify([]),
59
- browsersList: '',
60
- year: new Date().getFullYear().toString(),
61
- };
62
-
63
- console.log(`[+] Updating "${pkg.name}" (keeping config/, src/, platform/)`);
64
- await copyDir(TEMPLATE_DIR, cwd, vars);
65
-
66
- console.log('[+] Update complete.');
67
- }
@@ -1,29 +0,0 @@
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
- css: {
11
- entry: {
12
- '': ['src/css/popup.css'],
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
- };
@@ -1,29 +0,0 @@
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
- css: {
11
- entry: {
12
- '': ['src/css/popup.css'],
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
- };