@mxml3gend/gloss 0.1.1 → 0.1.3
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 +74 -0
- package/dist/baseline.js +118 -0
- package/dist/cache.js +78 -0
- package/dist/cacheMetrics.js +120 -0
- package/dist/check.js +510 -0
- package/dist/config.js +214 -10
- package/dist/fs.js +105 -6
- package/dist/gitDiff.js +113 -0
- package/dist/hooks.js +101 -0
- package/dist/index.js +437 -12
- package/dist/renameKeyUsage.js +4 -12
- package/dist/server.js +163 -9
- package/dist/translationKeys.js +20 -0
- package/dist/translationTree.js +42 -0
- package/dist/typegen.js +30 -0
- package/dist/ui/Gloss_logo.png +0 -0
- package/dist/ui/assets/index-BCr07xD_.js +21 -0
- package/dist/ui/assets/index-CjmLcA1x.css +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/logo_full.png +0 -0
- package/dist/usage.js +105 -22
- package/dist/usageExtractor.js +151 -0
- package/dist/usageScanner.js +110 -28
- package/dist/xliff.js +92 -0
- package/package.json +15 -5
- package/dist/ui/assets/index-CREq9Gop.css +0 -1
- package/dist/ui/assets/index-Dhb2pVPI.js +0 -10
package/dist/index.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
4
6
|
import open from "open";
|
|
5
|
-
import {
|
|
7
|
+
import { resetIssueBaseline, updateIssueBaseline } from "./baseline.js";
|
|
8
|
+
import { clearGlossCaches, getCacheStatus } from "./cache.js";
|
|
9
|
+
import { printGlossCheck, runGlossCheck } from "./check.js";
|
|
10
|
+
import { GlossConfigError, discoverLocaleDirectoryCandidates, loadGlossConfig, } from "./config.js";
|
|
11
|
+
import { installPreCommitHooks } from "./hooks.js";
|
|
6
12
|
import { startServer } from "./server.js";
|
|
13
|
+
import { generateKeyTypes } from "./typegen.js";
|
|
7
14
|
const DEFAULT_PORT = 5179;
|
|
15
|
+
const GENERATED_CONFIG_FILE = "gloss.config.cjs";
|
|
16
|
+
const projectRoot = () => process.env.INIT_CWD || process.cwd();
|
|
8
17
|
const getVersion = async () => {
|
|
9
18
|
const packagePath = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
10
19
|
const raw = await fs.readFile(packagePath, "utf8");
|
|
@@ -16,23 +25,274 @@ const printHelp = () => {
|
|
|
16
25
|
|
|
17
26
|
Usage:
|
|
18
27
|
gloss [options]
|
|
28
|
+
gloss check [options]
|
|
29
|
+
gloss gen-types [options]
|
|
30
|
+
gloss init-hooks [options]
|
|
31
|
+
gloss baseline reset
|
|
32
|
+
gloss cache status
|
|
33
|
+
gloss cache clear
|
|
34
|
+
gloss open key <translation-key> [options]
|
|
19
35
|
|
|
20
36
|
Options:
|
|
21
|
-
-h, --help
|
|
22
|
-
-v, --version
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
-h, --help Show help
|
|
38
|
+
-v, --version Show version
|
|
39
|
+
|
|
40
|
+
Serve options:
|
|
41
|
+
--no-open Do not open browser automatically
|
|
42
|
+
--no-cache Bypass scanner caches for API responses
|
|
43
|
+
-p, --port Set server port (default: ${DEFAULT_PORT})
|
|
44
|
+
|
|
45
|
+
Check options:
|
|
46
|
+
--format <human|json|both> Output format (default: human)
|
|
47
|
+
--json Shortcut for --format json
|
|
48
|
+
--no-cache Force full rescan without reading/writing scanner cache
|
|
49
|
+
|
|
50
|
+
Type generation options:
|
|
51
|
+
--out <path> Output file for generated key types (default: i18n-keys.d.ts)
|
|
52
|
+
|
|
53
|
+
Hook options:
|
|
54
|
+
gloss init-hooks Install pre-commit hooks for gloss check
|
|
55
|
+
|
|
56
|
+
Baseline options:
|
|
57
|
+
gloss baseline reset Remove the local issue baseline (.gloss/baseline.json)
|
|
58
|
+
|
|
59
|
+
Cache options:
|
|
60
|
+
gloss cache status Show scanner cache status
|
|
61
|
+
gloss cache clear Clear in-memory scanner cache and .gloss/cache-metrics.json
|
|
62
|
+
|
|
63
|
+
Open key options:
|
|
64
|
+
gloss open key <key> Open Gloss focused on a translation key
|
|
65
|
+
--no-cache Bypass scanner caches while serving
|
|
25
66
|
`);
|
|
26
67
|
};
|
|
27
68
|
const parseArgs = (args) => {
|
|
28
|
-
const
|
|
69
|
+
const firstArg = args[0];
|
|
70
|
+
if (firstArg === "baseline") {
|
|
71
|
+
const commandArgs = args.slice(1);
|
|
72
|
+
const options = {
|
|
73
|
+
command: "baseline-reset",
|
|
74
|
+
help: false,
|
|
75
|
+
version: false,
|
|
76
|
+
};
|
|
77
|
+
for (const arg of commandArgs) {
|
|
78
|
+
if (arg === "-h" || arg === "--help") {
|
|
79
|
+
options.help = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (arg === "-v" || arg === "--version") {
|
|
83
|
+
options.version = true;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (arg === "reset") {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
throw new Error(`Unknown argument for gloss baseline: ${arg}`);
|
|
90
|
+
}
|
|
91
|
+
if (!options.help && commandArgs[0] !== "reset") {
|
|
92
|
+
throw new Error("Usage: gloss baseline reset");
|
|
93
|
+
}
|
|
94
|
+
return options;
|
|
95
|
+
}
|
|
96
|
+
if (firstArg === "cache") {
|
|
97
|
+
const commandArgs = args.slice(1);
|
|
98
|
+
const action = commandArgs[0];
|
|
99
|
+
const restArgs = commandArgs.slice(1);
|
|
100
|
+
if (action !== "status" && action !== "clear") {
|
|
101
|
+
throw new Error("Usage: gloss cache <status|clear>");
|
|
102
|
+
}
|
|
103
|
+
const options = {
|
|
104
|
+
command: action === "status" ? "cache-status" : "cache-clear",
|
|
105
|
+
help: false,
|
|
106
|
+
version: false,
|
|
107
|
+
};
|
|
108
|
+
for (const arg of restArgs) {
|
|
109
|
+
if (arg === "-h" || arg === "--help") {
|
|
110
|
+
options.help = true;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (arg === "-v" || arg === "--version") {
|
|
114
|
+
options.version = true;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`Unknown argument for gloss cache: ${arg}`);
|
|
118
|
+
}
|
|
119
|
+
return options;
|
|
120
|
+
}
|
|
121
|
+
if (firstArg === "open") {
|
|
122
|
+
const commandArgs = args.slice(1);
|
|
123
|
+
const base = { help: false, version: false };
|
|
124
|
+
const options = {
|
|
125
|
+
command: "open-key",
|
|
126
|
+
...base,
|
|
127
|
+
noOpen: false,
|
|
128
|
+
noCache: false,
|
|
129
|
+
port: DEFAULT_PORT,
|
|
130
|
+
key: "",
|
|
131
|
+
};
|
|
132
|
+
if (commandArgs.length === 0) {
|
|
133
|
+
throw new Error("Usage: gloss open key <translation-key> [--port <number>]");
|
|
134
|
+
}
|
|
135
|
+
if (commandArgs[0] === "-h" || commandArgs[0] === "--help") {
|
|
136
|
+
options.help = true;
|
|
137
|
+
return options;
|
|
138
|
+
}
|
|
139
|
+
if (commandArgs[0] === "-v" || commandArgs[0] === "--version") {
|
|
140
|
+
options.version = true;
|
|
141
|
+
return options;
|
|
142
|
+
}
|
|
143
|
+
let index = 0;
|
|
144
|
+
if (commandArgs[0] === "key") {
|
|
145
|
+
index = 1;
|
|
146
|
+
}
|
|
147
|
+
const keyValue = commandArgs[index];
|
|
148
|
+
if (!keyValue || keyValue.startsWith("-")) {
|
|
149
|
+
throw new Error("Missing translation key. Usage: gloss open key <translation-key>");
|
|
150
|
+
}
|
|
151
|
+
options.key = keyValue.trim();
|
|
152
|
+
index += 1;
|
|
153
|
+
for (; index < commandArgs.length; index += 1) {
|
|
154
|
+
const arg = commandArgs[index];
|
|
155
|
+
if (arg === "-h" || arg === "--help") {
|
|
156
|
+
options.help = true;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (arg === "-v" || arg === "--version") {
|
|
160
|
+
options.version = true;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (arg === "--no-open") {
|
|
164
|
+
options.noOpen = true;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (arg === "--no-cache") {
|
|
168
|
+
options.noCache = true;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (arg === "-p" || arg === "--port") {
|
|
172
|
+
const nextValue = commandArgs[index + 1];
|
|
173
|
+
if (!nextValue) {
|
|
174
|
+
throw new Error("Missing value for --port.");
|
|
175
|
+
}
|
|
176
|
+
const parsed = Number.parseInt(nextValue, 10);
|
|
177
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
178
|
+
throw new Error("Port must be a positive integer.");
|
|
179
|
+
}
|
|
180
|
+
options.port = parsed;
|
|
181
|
+
index += 1;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`Unknown argument for gloss open: ${arg}`);
|
|
185
|
+
}
|
|
186
|
+
return options;
|
|
187
|
+
}
|
|
188
|
+
const isCommand = firstArg && !firstArg.startsWith("-");
|
|
189
|
+
const command = firstArg === "check" || firstArg === "gen-types" || firstArg === "init-hooks"
|
|
190
|
+
? firstArg
|
|
191
|
+
: "serve";
|
|
192
|
+
const commandArgs = command === "serve" ? args : args.slice(1);
|
|
193
|
+
const base = {
|
|
29
194
|
help: false,
|
|
30
195
|
version: false,
|
|
196
|
+
};
|
|
197
|
+
if (command === "check") {
|
|
198
|
+
const options = {
|
|
199
|
+
command,
|
|
200
|
+
...base,
|
|
201
|
+
format: "human",
|
|
202
|
+
noCache: false,
|
|
203
|
+
};
|
|
204
|
+
for (let index = 0; index < commandArgs.length; index += 1) {
|
|
205
|
+
const arg = commandArgs[index];
|
|
206
|
+
if (arg === "-h" || arg === "--help") {
|
|
207
|
+
options.help = true;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (arg === "-v" || arg === "--version") {
|
|
211
|
+
options.version = true;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (arg === "--json") {
|
|
215
|
+
options.format = "json";
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (arg === "--no-cache") {
|
|
219
|
+
options.noCache = true;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (arg === "--format") {
|
|
223
|
+
const nextValue = commandArgs[index + 1];
|
|
224
|
+
if (!nextValue) {
|
|
225
|
+
throw new Error("Missing value for --format.");
|
|
226
|
+
}
|
|
227
|
+
if (nextValue !== "human" && nextValue !== "json" && nextValue !== "both") {
|
|
228
|
+
throw new Error("Invalid value for --format. Use human, json, or both.");
|
|
229
|
+
}
|
|
230
|
+
options.format = nextValue;
|
|
231
|
+
index += 1;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
throw new Error(`Unknown argument for gloss check: ${arg}`);
|
|
235
|
+
}
|
|
236
|
+
return options;
|
|
237
|
+
}
|
|
238
|
+
if (command === "gen-types") {
|
|
239
|
+
const options = {
|
|
240
|
+
command,
|
|
241
|
+
...base,
|
|
242
|
+
};
|
|
243
|
+
for (let index = 0; index < commandArgs.length; index += 1) {
|
|
244
|
+
const arg = commandArgs[index];
|
|
245
|
+
if (arg === "-h" || arg === "--help") {
|
|
246
|
+
options.help = true;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (arg === "-v" || arg === "--version") {
|
|
250
|
+
options.version = true;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (arg === "--out") {
|
|
254
|
+
const nextValue = commandArgs[index + 1];
|
|
255
|
+
if (!nextValue) {
|
|
256
|
+
throw new Error("Missing value for --out.");
|
|
257
|
+
}
|
|
258
|
+
options.outFile = nextValue;
|
|
259
|
+
index += 1;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
throw new Error(`Unknown argument for gloss gen-types: ${arg}`);
|
|
263
|
+
}
|
|
264
|
+
return options;
|
|
265
|
+
}
|
|
266
|
+
if (command === "init-hooks") {
|
|
267
|
+
const options = {
|
|
268
|
+
command,
|
|
269
|
+
...base,
|
|
270
|
+
};
|
|
271
|
+
for (const arg of commandArgs) {
|
|
272
|
+
if (arg === "-h" || arg === "--help") {
|
|
273
|
+
options.help = true;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (arg === "-v" || arg === "--version") {
|
|
277
|
+
options.version = true;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
throw new Error(`Unknown argument for gloss init-hooks: ${arg}`);
|
|
281
|
+
}
|
|
282
|
+
return options;
|
|
283
|
+
}
|
|
284
|
+
if (isCommand && command === "serve" && firstArg !== undefined) {
|
|
285
|
+
throw new Error(`Unknown command: ${firstArg}`);
|
|
286
|
+
}
|
|
287
|
+
const options = {
|
|
288
|
+
command: "serve",
|
|
289
|
+
...base,
|
|
31
290
|
noOpen: false,
|
|
291
|
+
noCache: false,
|
|
32
292
|
port: DEFAULT_PORT,
|
|
33
293
|
};
|
|
34
|
-
for (let index = 0; index <
|
|
35
|
-
const arg =
|
|
294
|
+
for (let index = 0; index < commandArgs.length; index += 1) {
|
|
295
|
+
const arg = commandArgs[index];
|
|
36
296
|
if (arg === "-h" || arg === "--help") {
|
|
37
297
|
options.help = true;
|
|
38
298
|
continue;
|
|
@@ -45,8 +305,12 @@ const parseArgs = (args) => {
|
|
|
45
305
|
options.noOpen = true;
|
|
46
306
|
continue;
|
|
47
307
|
}
|
|
308
|
+
if (arg === "--no-cache") {
|
|
309
|
+
options.noCache = true;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
48
312
|
if (arg === "-p" || arg === "--port") {
|
|
49
|
-
const nextValue =
|
|
313
|
+
const nextValue = commandArgs[index + 1];
|
|
50
314
|
if (!nextValue) {
|
|
51
315
|
throw new Error("Missing value for --port.");
|
|
52
316
|
}
|
|
@@ -77,6 +341,87 @@ const printConfigError = (error) => {
|
|
|
77
341
|
console.error("Gloss could not start: invalid gloss.config.ts.");
|
|
78
342
|
console.error(error.message);
|
|
79
343
|
};
|
|
344
|
+
const renderGeneratedConfig = (candidatePath, locales) => {
|
|
345
|
+
const defaultLocale = locales.includes("en") ? "en" : locales[0] ?? "en";
|
|
346
|
+
const localeList = locales.map((locale) => JSON.stringify(locale)).join(", ");
|
|
347
|
+
const pathLiteral = JSON.stringify(candidatePath);
|
|
348
|
+
return `module.exports = {
|
|
349
|
+
locales: [${localeList}],
|
|
350
|
+
defaultLocale: ${JSON.stringify(defaultLocale)},
|
|
351
|
+
path: ${pathLiteral},
|
|
352
|
+
format: "json",
|
|
353
|
+
};
|
|
354
|
+
`;
|
|
355
|
+
};
|
|
356
|
+
const chooseCandidateInteractive = async (candidates) => {
|
|
357
|
+
if (candidates.length === 1) {
|
|
358
|
+
return candidates[0];
|
|
359
|
+
}
|
|
360
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
361
|
+
return candidates[0];
|
|
362
|
+
}
|
|
363
|
+
const rl = createInterface({
|
|
364
|
+
input: process.stdin,
|
|
365
|
+
output: process.stdout,
|
|
366
|
+
});
|
|
367
|
+
try {
|
|
368
|
+
console.log("Gloss setup: multiple locale directories were found.");
|
|
369
|
+
candidates.forEach((candidate, index) => {
|
|
370
|
+
const marker = index === 0 ? " (recommended)" : "";
|
|
371
|
+
console.log(` ${index + 1}. ${candidate.path} -> [${candidate.locales.join(", ")}]${marker}`);
|
|
372
|
+
});
|
|
373
|
+
while (true) {
|
|
374
|
+
const answer = (await rl.question(`Choose a locale directory [1-${candidates.length}] (default 1): `)).trim();
|
|
375
|
+
if (!answer) {
|
|
376
|
+
return candidates[0];
|
|
377
|
+
}
|
|
378
|
+
const selection = Number.parseInt(answer, 10);
|
|
379
|
+
if (Number.isFinite(selection) && selection >= 1 && selection <= candidates.length) {
|
|
380
|
+
return candidates[selection - 1];
|
|
381
|
+
}
|
|
382
|
+
console.log(`Please enter a number between 1 and ${candidates.length}.`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
finally {
|
|
386
|
+
rl.close();
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
const bootstrapConfigIfMissing = async () => {
|
|
390
|
+
const cwd = projectRoot();
|
|
391
|
+
const candidates = await discoverLocaleDirectoryCandidates(cwd);
|
|
392
|
+
if (candidates.length === 0) {
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
console.log("No Gloss config found. Starting first-run setup.");
|
|
396
|
+
const selected = await chooseCandidateInteractive(candidates);
|
|
397
|
+
const configFilePath = path.join(cwd, GENERATED_CONFIG_FILE);
|
|
398
|
+
const content = renderGeneratedConfig(selected.path, selected.locales);
|
|
399
|
+
await fs.writeFile(configFilePath, content, "utf8");
|
|
400
|
+
console.log(`Created ${GENERATED_CONFIG_FILE} using ${selected.path}.`);
|
|
401
|
+
console.log("Starting Gloss with the generated config.");
|
|
402
|
+
return true;
|
|
403
|
+
};
|
|
404
|
+
const formatBytes = (value) => {
|
|
405
|
+
if (value < 1024) {
|
|
406
|
+
return `${value} B`;
|
|
407
|
+
}
|
|
408
|
+
if (value < 1024 * 1024) {
|
|
409
|
+
return `${(value / 1024).toFixed(1)} KB`;
|
|
410
|
+
}
|
|
411
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
412
|
+
};
|
|
413
|
+
const formatAge = (ageMs) => {
|
|
414
|
+
if (ageMs === null) {
|
|
415
|
+
return "n/a";
|
|
416
|
+
}
|
|
417
|
+
const totalSeconds = Math.max(0, Math.floor(ageMs / 1000));
|
|
418
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
419
|
+
const seconds = totalSeconds % 60;
|
|
420
|
+
if (minutes === 0) {
|
|
421
|
+
return `${seconds}s`;
|
|
422
|
+
}
|
|
423
|
+
return `${minutes}m ${seconds}s`;
|
|
424
|
+
};
|
|
80
425
|
async function main() {
|
|
81
426
|
const options = parseArgs(process.argv.slice(2));
|
|
82
427
|
if (options.help) {
|
|
@@ -87,9 +432,89 @@ async function main() {
|
|
|
87
432
|
console.log(await getVersion());
|
|
88
433
|
return;
|
|
89
434
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
435
|
+
if (options.command === "init-hooks") {
|
|
436
|
+
const result = await installPreCommitHooks(projectRoot());
|
|
437
|
+
console.log("Gloss hook installation");
|
|
438
|
+
for (const message of result.messages) {
|
|
439
|
+
console.log(`- ${message}`);
|
|
440
|
+
}
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (options.command === "baseline-reset") {
|
|
444
|
+
const result = await resetIssueBaseline(projectRoot());
|
|
445
|
+
if (result.existed) {
|
|
446
|
+
console.log(`Removed baseline at ${result.baselinePath}`);
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
console.log(`No baseline found at ${result.baselinePath}`);
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (options.command === "cache-clear") {
|
|
454
|
+
const result = await clearGlossCaches(projectRoot());
|
|
455
|
+
console.log("Gloss cache clear");
|
|
456
|
+
console.log(`- Usage scanner cache: ${result.usage.fileCount} files across ${result.usage.bucketCount} buckets removed`);
|
|
457
|
+
console.log(`- Key usage cache: ${result.keyUsage.fileCount} files across ${result.keyUsage.bucketCount} buckets removed`);
|
|
458
|
+
console.log(`- Cache metrics file: ${result.metrics.existed ? "removed" : "not found"} (${result.metrics.path})`);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
let cfg;
|
|
462
|
+
try {
|
|
463
|
+
cfg = await loadGlossConfig();
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
if (error instanceof GlossConfigError && error.code === "MISSING_CONFIG") {
|
|
467
|
+
const generated = await bootstrapConfigIfMissing();
|
|
468
|
+
if (generated) {
|
|
469
|
+
cfg = await loadGlossConfig();
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
throw error;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (options.command === "check") {
|
|
480
|
+
const result = await runGlossCheck(cfg, {
|
|
481
|
+
useCache: !options.noCache,
|
|
482
|
+
});
|
|
483
|
+
let baseline;
|
|
484
|
+
try {
|
|
485
|
+
baseline = await updateIssueBaseline(projectRoot(), result.summary);
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
console.error(`Warning: failed to update issue baseline: ${error.message}`);
|
|
489
|
+
}
|
|
490
|
+
printGlossCheck(result, options.format, baseline);
|
|
491
|
+
if (!result.ok) {
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (options.command === "cache-status") {
|
|
497
|
+
const status = await getCacheStatus(projectRoot(), cfg);
|
|
498
|
+
console.log("Gloss cache status");
|
|
499
|
+
console.log(`- Metrics file: ${status.metricsFileFound ? "found" : "not found"}${status.metricsUpdatedAt ? ` (updated ${status.metricsUpdatedAt})` : ""}`);
|
|
500
|
+
console.log(`- Total cached files: ${status.totalCachedFiles} (${formatBytes(status.totalCachedSizeBytes)})`);
|
|
501
|
+
console.log(`- Oldest cache entry age: ${formatAge(status.oldestEntryAgeMs)}`);
|
|
502
|
+
console.log(`- Stale relative to config: ${status.staleRelativeToConfig ? "yes" : "no"}`);
|
|
503
|
+
console.log(`- Usage scanner: ${status.usageScanner.fileCount} files, ${formatBytes(status.usageScanner.totalSizeBytes)}, source=${status.usageScanner.source}`);
|
|
504
|
+
console.log(`- Key usage: ${status.keyUsage.fileCount} files, ${formatBytes(status.keyUsage.totalSizeBytes)}, source=${status.keyUsage.source}`);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (options.command === "gen-types") {
|
|
508
|
+
const result = await generateKeyTypes(cfg, { outFile: options.outFile });
|
|
509
|
+
console.log(`Generated ${result.keyCount} keys in ${result.outFile}`);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const { port } = await startServer(cfg, options.port, {
|
|
513
|
+
useCache: !options.noCache,
|
|
514
|
+
});
|
|
515
|
+
const url = options.command === "open-key"
|
|
516
|
+
? `http://localhost:${port}/?key=${encodeURIComponent(options.key)}`
|
|
517
|
+
: `http://localhost:${port}`;
|
|
93
518
|
console.log(`Gloss running at ${url}`);
|
|
94
519
|
if (options.noOpen || process.env.CI) {
|
|
95
520
|
return;
|
package/dist/renameKeyUsage.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { replaceTranslationKeyLiterals } from "./usageExtractor.js";
|
|
3
4
|
const IGNORED_DIRECTORIES = new Set([
|
|
4
5
|
"node_modules",
|
|
5
6
|
"dist",
|
|
@@ -8,11 +9,10 @@ const IGNORED_DIRECTORIES = new Set([
|
|
|
8
9
|
"coverage",
|
|
9
10
|
]);
|
|
10
11
|
const SCANNED_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
11
|
-
const TRANSLATION_CALL_REGEX = /(\b(?:t|translate)\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
12
12
|
const projectRoot = () => process.env.INIT_CWD || process.cwd();
|
|
13
13
|
const normalizePath = (filePath) => filePath.split(path.sep).join("/");
|
|
14
14
|
const isScannableFile = (fileName) => SCANNED_EXTENSIONS.has(path.extname(fileName));
|
|
15
|
-
export async function renameKeyUsage(oldKey, newKey, rootDir = projectRoot()) {
|
|
15
|
+
export async function renameKeyUsage(oldKey, newKey, rootDir = projectRoot(), mode) {
|
|
16
16
|
if (!oldKey || !newKey || oldKey === newKey) {
|
|
17
17
|
return {
|
|
18
18
|
changedFiles: [],
|
|
@@ -39,19 +39,11 @@ export async function renameKeyUsage(oldKey, newKey, rootDir = projectRoot()) {
|
|
|
39
39
|
}
|
|
40
40
|
filesScanned += 1;
|
|
41
41
|
const source = await fs.readFile(fullPath, "utf8");
|
|
42
|
-
|
|
43
|
-
const updated = source.replace(TRANSLATION_CALL_REGEX, (match, prefix, quote, key) => {
|
|
44
|
-
if (key !== oldKey) {
|
|
45
|
-
return match;
|
|
46
|
-
}
|
|
47
|
-
fileReplacements += 1;
|
|
48
|
-
replacements += 1;
|
|
49
|
-
return `${prefix}${quote}${newKey}${quote}`;
|
|
50
|
-
});
|
|
51
|
-
TRANSLATION_CALL_REGEX.lastIndex = 0;
|
|
42
|
+
const { updated, replacements: fileReplacements } = replaceTranslationKeyLiterals(source, fullPath, oldKey, newKey, mode);
|
|
52
43
|
if (fileReplacements === 0 || updated === source) {
|
|
53
44
|
continue;
|
|
54
45
|
}
|
|
46
|
+
replacements += fileReplacements;
|
|
55
47
|
await fs.writeFile(fullPath, updated, "utf8");
|
|
56
48
|
changedFiles.push(normalizePath(path.relative(rootDir, fullPath)));
|
|
57
49
|
}
|