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/src/cli/help.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function printHelp() {
|
|
2
|
+
console.log(`
|
|
3
|
+
Addonova - browser extension toolkit
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
npx addonova init <my-extension> Scaffold a new extension project
|
|
7
|
+
npx addonova build [options] Build the extension
|
|
8
|
+
npx addonova zip Create release zip bundles
|
|
9
|
+
npx addonova tool Open the helping tools UI in a browser
|
|
10
|
+
npx addonova --help Show this help
|
|
11
|
+
|
|
12
|
+
Build options:
|
|
13
|
+
--all, --chrome, --firefox, --edge, --opera, --naver, --thunderbird
|
|
14
|
+
--release, --debug, --watch, --open, --test, --version=x.x.x
|
|
15
|
+
`);
|
|
16
|
+
}
|
package/src/cli/index.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { fork } from 'node:child_process';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
|
|
7
|
+
import { init } from '../commands/init.js';
|
|
8
|
+
import { runTool } from '../commands/tool.js';
|
|
9
|
+
import { printHelp } from './help.js';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
async function runBuildCommand(command, args) {
|
|
14
|
+
const cliPath = resolve(__dirname, '../build/cli.js');
|
|
15
|
+
|
|
16
|
+
if (!existsSync(cliPath)) {
|
|
17
|
+
console.error('[*] Addonova build CLI not found. Reinstall the package.');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const child = fork(cliPath, [command, ...args], {
|
|
22
|
+
execArgv: ['--max-old-space-size=3072'],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
process.on('SIGINT', () => {
|
|
26
|
+
child.kill('SIGKILL');
|
|
27
|
+
process.exit(130);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
await new Promise((resolve, reject) =>
|
|
31
|
+
child.on('error', reject).on('close', (code) => {
|
|
32
|
+
if (code !== 0) process.exit(code);
|
|
33
|
+
resolve();
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function runCli(argv) {
|
|
39
|
+
const [command, ...args] = argv;
|
|
40
|
+
|
|
41
|
+
switch (command) {
|
|
42
|
+
case 'init':
|
|
43
|
+
await init(args);
|
|
44
|
+
break;
|
|
45
|
+
case 'tool':
|
|
46
|
+
await runTool();
|
|
47
|
+
break;
|
|
48
|
+
case 'build':
|
|
49
|
+
case 'zip':
|
|
50
|
+
await runBuildCommand(command, args);
|
|
51
|
+
break;
|
|
52
|
+
case '--help':
|
|
53
|
+
case '-h':
|
|
54
|
+
default:
|
|
55
|
+
printHelp();
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -6,10 +6,9 @@ import process from 'node:process';
|
|
|
6
6
|
import { selectBrowsers, askQuestion } from '../utils/prompts.js';
|
|
7
7
|
|
|
8
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const TEMPLATE_DIR = path.resolve(__dirname, '../../
|
|
9
|
+
const TEMPLATE_DIR = path.resolve(__dirname, '../../templates/extension');
|
|
10
10
|
|
|
11
11
|
const VARIABLE_RE = /\{\{\s*(\w+)\s*\}\}/g;
|
|
12
|
-
const SKIP_DIRS = new Set(['workspace']);
|
|
13
12
|
|
|
14
13
|
function render(template, vars) {
|
|
15
14
|
return template.replace(VARIABLE_RE, (_, key) => vars[key] ?? `{{${key}}}`);
|
|
@@ -23,8 +22,6 @@ async function copyDir(src, dest, vars, selected = []) {
|
|
|
23
22
|
for (const entry of entries) {
|
|
24
23
|
const entryName = path.basename(entry.name, path.extname(entry.name));
|
|
25
24
|
|
|
26
|
-
if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue;
|
|
27
|
-
|
|
28
25
|
if ((dirName === 'config' || dirName === 'platform') && selected.length > 0 && !selected.includes(entryName)) {
|
|
29
26
|
continue;
|
|
30
27
|
}
|
|
@@ -90,19 +87,18 @@ export async function init(args) {
|
|
|
90
87
|
await installDependencies(projectDir);
|
|
91
88
|
}
|
|
92
89
|
|
|
90
|
+
|
|
93
91
|
console.log(`
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
└──────────────────────────────────────────────
|
|
107
|
-
`);
|
|
92
|
+
[+] "${projectName}" is ready!
|
|
93
|
+
|
|
94
|
+
📁 ${projectDir}
|
|
95
|
+
|
|
96
|
+
Next steps:
|
|
97
|
+
cd ${projectName}
|
|
98
|
+
npm install
|
|
99
|
+
npm run watch
|
|
100
|
+
|
|
101
|
+
Targets: ${selected.join(', ').padEnd(30)}
|
|
102
|
+
|
|
103
|
+
`);
|
|
108
104
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { fork } from 'node:child_process';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { resolve, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
export async function runTool() {
|
|
10
|
+
const serverPath = resolve(__dirname, '../tools/tools-server.js');
|
|
11
|
+
|
|
12
|
+
if (!existsSync(serverPath)) {
|
|
13
|
+
console.error('[*] Addonova tools server not found. Reinstall the package.');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const child = fork(serverPath, [], {
|
|
18
|
+
stdio: 'inherit',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
process.on('SIGINT', () => {
|
|
22
|
+
child.kill('SIGKILL');
|
|
23
|
+
process.exit(130);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
await new Promise((resolve, reject) =>
|
|
27
|
+
child.on('error', reject).on('close', (code) => {
|
|
28
|
+
if (code !== 0) process.exit(code);
|
|
29
|
+
resolve();
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
}
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import { exec } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
import {readFile, writeFile, httpsRequest, timeout} from '../build/utils.js';
|
|
9
|
+
|
|
10
|
+
const openBrowser = (url) => {
|
|
11
|
+
const cmd = process.platform === 'win32' ? 'start' :
|
|
12
|
+
process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
13
|
+
exec(`${cmd} ${url}`);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const LOCALES_ROOT = path.resolve(process.cwd(), 'src/_locales');
|
|
17
|
+
const PORT = process.env.PORT || 9876;
|
|
18
|
+
const MIME = {
|
|
19
|
+
'.html': 'text/html; charset=utf-8',
|
|
20
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
21
|
+
'.css': 'text/css; charset=utf-8',
|
|
22
|
+
'.json': 'application/json',
|
|
23
|
+
'.png': 'image/png',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function toMessageId(message) {
|
|
27
|
+
if (typeof message !== 'string') return '';
|
|
28
|
+
return message.trim().split(/\s+/).slice(0, 3)
|
|
29
|
+
.map(w => w.replace(/[^\w]/g, '').toLowerCase())
|
|
30
|
+
.filter(Boolean).join('_');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseLocale(content) {
|
|
34
|
+
const messages = new Map();
|
|
35
|
+
const lines = content.split('\n');
|
|
36
|
+
let id = '';
|
|
37
|
+
for (let i = 0; i < lines.length; i++) {
|
|
38
|
+
const line = lines[i];
|
|
39
|
+
if (line.startsWith('@')) {
|
|
40
|
+
id = line.substring(1);
|
|
41
|
+
} else if (line.startsWith('#')) {
|
|
42
|
+
continue;
|
|
43
|
+
} else if (messages.has(id)) {
|
|
44
|
+
messages.set(id, `${messages.get(id)}\n${line}`);
|
|
45
|
+
} else {
|
|
46
|
+
messages.set(id, line);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
messages.forEach((value, id) => messages.set(id, value.trim()));
|
|
50
|
+
return messages;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function stringifyLocale(messages) {
|
|
54
|
+
const lines = [];
|
|
55
|
+
messages.forEach((message, id) => {
|
|
56
|
+
lines.push(`@${id}`);
|
|
57
|
+
const hasDoubleNewLines = /\n\n/.test(message);
|
|
58
|
+
message.split('\n').filter(l => l.trim()).forEach((line, index, filtered) => {
|
|
59
|
+
lines.push(line);
|
|
60
|
+
if (hasDoubleNewLines && index < filtered.length - 1) lines.push('');
|
|
61
|
+
});
|
|
62
|
+
lines.push('');
|
|
63
|
+
});
|
|
64
|
+
return lines.join('\n');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function json2i18n(input) {
|
|
68
|
+
let output = '';
|
|
69
|
+
for (const key in input) {
|
|
70
|
+
output += `@${key}\n${input[key].message}\n\n`;
|
|
71
|
+
}
|
|
72
|
+
return output;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function getSupportedLocales() {
|
|
76
|
+
const entries = await fs.readdir(LOCALES_ROOT).catch(() => []);
|
|
77
|
+
return entries.filter(f => f.endsWith('.i18n')).map(f => f.replace('.i18n', ''));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function translate(text, lang) {
|
|
81
|
+
const url = new URL('https://translate.googleapis.com/translate_a/single');
|
|
82
|
+
url.search = new URLSearchParams({
|
|
83
|
+
client: 'gtx', sl: 'en-US', tl: lang, dt: 't', dj: '1', q: text,
|
|
84
|
+
}).toString();
|
|
85
|
+
const response = await httpsRequest(url.toString());
|
|
86
|
+
const data = JSON.parse(response.text());
|
|
87
|
+
return data.sentences.map(s => s.trans).join('\n').replaceAll(/\n+/g, '\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function jsonResponse(res, data, status = 200) {
|
|
91
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
92
|
+
res.end(JSON.stringify(data));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function serveStatic(req, res) {
|
|
96
|
+
let filePath = req.url === '/' ? '/tools.html' : req.url;
|
|
97
|
+
filePath = path.join(__dirname, filePath);
|
|
98
|
+
|
|
99
|
+
const ext = path.extname(filePath);
|
|
100
|
+
if (!MIME[ext]) {
|
|
101
|
+
res.writeHead(404);
|
|
102
|
+
res.end('Not found');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fs.readFile(filePath).then(content => {
|
|
107
|
+
res.writeHead(200, { 'Content-Type': MIME[ext] });
|
|
108
|
+
res.end(content);
|
|
109
|
+
}).catch(() => {
|
|
110
|
+
res.writeHead(404);
|
|
111
|
+
res.end('Not found');
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function parseBody(req) {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
let body = '';
|
|
118
|
+
req.on('data', chunk => body += chunk);
|
|
119
|
+
req.on('end', () => {
|
|
120
|
+
try { resolve(JSON.parse(body)); }
|
|
121
|
+
catch { reject(new Error('Invalid JSON')); }
|
|
122
|
+
});
|
|
123
|
+
req.on('error', reject);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function handleAPI(req, res) {
|
|
128
|
+
const url = new URL(req.url, `http://localhost:${PORT}`);
|
|
129
|
+
const parts = url.pathname.split('/').filter(Boolean);
|
|
130
|
+
|
|
131
|
+
if (parts[0] !== 'api') return false;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
switch (parts[1]) {
|
|
135
|
+
case 'locales': {
|
|
136
|
+
if (parts[2]) {
|
|
137
|
+
const filePath = path.join(LOCALES_ROOT, `${parts[2]}.i18n`);
|
|
138
|
+
const content = await readFile(filePath).catch(() => null);
|
|
139
|
+
if (!content) {
|
|
140
|
+
jsonResponse(res, { error: 'Locale not found' }, 404);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
const messages = parseLocale(content);
|
|
144
|
+
const obj = {};
|
|
145
|
+
messages.forEach((v, k) => obj[k] = v);
|
|
146
|
+
jsonResponse(res, { locale: parts[2], messages: obj });
|
|
147
|
+
} else {
|
|
148
|
+
const list = await getSupportedLocales();
|
|
149
|
+
const data = {};
|
|
150
|
+
for (const loc of list) {
|
|
151
|
+
const content = await readFile(path.join(LOCALES_ROOT, `${loc}.i18n`));
|
|
152
|
+
const messages = parseLocale(content);
|
|
153
|
+
const obj = {};
|
|
154
|
+
messages.forEach((v, k) => obj[k] = v);
|
|
155
|
+
data[loc] = obj;
|
|
156
|
+
}
|
|
157
|
+
jsonResponse(res, { locales: list, data });
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case 'translate': {
|
|
163
|
+
const { text, lang } = await parseBody(req);
|
|
164
|
+
if (!text || !lang) {
|
|
165
|
+
jsonResponse(res, { error: 'text and lang required' }, 400);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
const result = await translate(text, lang);
|
|
169
|
+
jsonResponse(res, { translated: result });
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
case 'messages': {
|
|
174
|
+
if (req.method !== 'POST') {
|
|
175
|
+
jsonResponse(res, { error: 'Method not allowed' }, 405);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const body = await parseBody(req);
|
|
180
|
+
|
|
181
|
+
if (parts[2] === 'add') {
|
|
182
|
+
const { message, customId } = body;
|
|
183
|
+
if (!message) {
|
|
184
|
+
jsonResponse(res, { error: 'message required' }, 400);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const locales = await getSupportedLocales();
|
|
189
|
+
const messageId = (customId && customId.trim()) ? customId.trim() : toMessageId(message);
|
|
190
|
+
const results = { messageId, locales: {} };
|
|
191
|
+
|
|
192
|
+
for (const locale of locales) {
|
|
193
|
+
const filePath = path.join(LOCALES_ROOT, `${locale}.i18n`);
|
|
194
|
+
const content = await readFile(filePath);
|
|
195
|
+
const messages = parseLocale(content);
|
|
196
|
+
|
|
197
|
+
if (locale === 'en') {
|
|
198
|
+
if (!messages.has(messageId)) {
|
|
199
|
+
messages.set(messageId, message);
|
|
200
|
+
await writeFile(filePath, stringifyLocale(messages));
|
|
201
|
+
results.locales[locale] = message;
|
|
202
|
+
} else {
|
|
203
|
+
results.locales[locale] = { exists: true };
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
if (messages.has(messageId)) {
|
|
207
|
+
results.locales[locale] = { exists: true };
|
|
208
|
+
} else {
|
|
209
|
+
await timeout(1000);
|
|
210
|
+
const translated = await translate(message, locale);
|
|
211
|
+
messages.set(messageId, translated);
|
|
212
|
+
await writeFile(filePath, stringifyLocale(messages));
|
|
213
|
+
results.locales[locale] = translated;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
jsonResponse(res, results);
|
|
219
|
+
} else if (parts[2] === 'delete') {
|
|
220
|
+
const { messageId, targetLocale } = body;
|
|
221
|
+
if (!messageId) {
|
|
222
|
+
jsonResponse(res, { error: 'messageId required' }, 400);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const locales = targetLocale ? [targetLocale] : await getSupportedLocales();
|
|
227
|
+
const deleted = [];
|
|
228
|
+
|
|
229
|
+
for (const locale of locales) {
|
|
230
|
+
const filePath = path.join(LOCALES_ROOT, `${locale}.i18n`);
|
|
231
|
+
try {
|
|
232
|
+
const content = await readFile(filePath);
|
|
233
|
+
const messages = parseLocale(content);
|
|
234
|
+
if (messages.has(messageId)) {
|
|
235
|
+
messages.delete(messageId);
|
|
236
|
+
await writeFile(filePath, stringifyLocale(messages));
|
|
237
|
+
deleted.push(locale);
|
|
238
|
+
}
|
|
239
|
+
} catch { /* skip missing */ }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
jsonResponse(res, { deleted });
|
|
243
|
+
} else {
|
|
244
|
+
jsonResponse(res, { error: 'Unknown action' }, 400);
|
|
245
|
+
}
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
case 'json2i18n': {
|
|
250
|
+
if (req.method !== 'POST') {
|
|
251
|
+
jsonResponse(res, { error: 'Method not allowed' }, 405);
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
const body = await parseBody(req);
|
|
255
|
+
if (!body.json) {
|
|
256
|
+
jsonResponse(res, { error: 'json field required' }, 400);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
const result = json2i18n(body.json);
|
|
260
|
+
jsonResponse(res, { i18n: result });
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
default:
|
|
265
|
+
jsonResponse(res, { error: 'Unknown endpoint' }, 404);
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
} catch (err) {
|
|
269
|
+
jsonResponse(res, { error: err.message }, 500);
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const server = http.createServer(async (req, res) => {
|
|
275
|
+
if (req.url.startsWith('/api/')) {
|
|
276
|
+
await handleAPI(req, res);
|
|
277
|
+
} else {
|
|
278
|
+
serveStatic(req, res);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
server.listen(PORT, () => {
|
|
283
|
+
const url = `http://localhost:${PORT}`;
|
|
284
|
+
console.log(`Addonova Tools UI → ${url}`);
|
|
285
|
+
console.log(`Working directory locales: ${LOCALES_ROOT}`);
|
|
286
|
+
openBrowser(url);
|
|
287
|
+
});
|