@polang233/kiro-language-pack 0.0.0-stage → 1.2.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.
- package/LICENSE +21 -0
- package/NOTICE +57 -0
- package/README.en.md +61 -0
- package/README.md +60 -2
- package/README.zh-CN.md +61 -0
- package/assets/language-pack.vsix +0 -0
- package/bin/cli.mjs +3 -0
- package/config.json +185 -0
- package/package.json +74 -4
- package/scripts/lib/argv.mjs +50 -0
- package/scripts/lib/cli.mjs +101 -0
- package/scripts/lib/kiro-paths.mjs +151 -0
- package/scripts/lib/markers.mjs +43 -0
- package/scripts/lib/npm-tooling.mjs +19 -0
- package/scripts/lib/patch-state.mjs +304 -0
- package/scripts/lib/system-locale.mjs +37 -0
- package/scripts/lib/upgrade-diff.mjs +244 -0
- package/scripts/lib/util.mjs +146 -0
- package/scripts/patch-install.mjs +763 -0
- package/src/extension/main.cjs +287 -0
- package/src/i18n/zh-cn/patch/kiro.kiroAgent.json +125 -0
- package/src/i18n/zh-tw/patch/kiro.kiroAgent.json +125 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/** Pure snapshot comparison and translation review. No filesystem or app access. */
|
|
2
|
+
const IGNORED_MODULE = /(^|\/)tests?\//;
|
|
3
|
+
const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
4
|
+
const coreOrder = (a, b) => compare(a.moduleId, b.moduleId) || compare(a.key, b.key);
|
|
5
|
+
const sourceText = (value) => typeof value === 'string' ? value : value?.message ?? '';
|
|
6
|
+
const recorded = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
+
|
|
8
|
+
export function flattenCore(core = {}) {
|
|
9
|
+
const rows = [];
|
|
10
|
+
for (const [moduleId, keys] of Object.entries(core)) {
|
|
11
|
+
if (IGNORED_MODULE.test(moduleId)) continue;
|
|
12
|
+
for (const [key, english] of Object.entries(keys ?? {})) {
|
|
13
|
+
rows.push({ moduleId, key, english: sourceText(english) });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return new Map(rows.sort(coreOrder).map((row) => [`${row.moduleId}\0${row.key}`, row]));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function diffRows(before, after) {
|
|
20
|
+
const addedKeys = [], removedKeys = [], changedKeys = [];
|
|
21
|
+
for (const [id, row] of after) {
|
|
22
|
+
if (!before.has(id)) addedKeys.push({ ...row });
|
|
23
|
+
else if (before.get(id).english !== row.english) {
|
|
24
|
+
changedKeys.push({ ...row, previousEnglish: before.get(id).english });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const [id, row] of before) {
|
|
28
|
+
if (!after.has(id)) removedKeys.push({ ...row });
|
|
29
|
+
}
|
|
30
|
+
return { addedKeys, removedKeys, changedKeys };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extensionRows(extensionId, entries = {}) {
|
|
34
|
+
return new Map(Object.entries(entries ?? {}).sort(([a], [b]) => compare(a, b))
|
|
35
|
+
.map(([key, value]) => [key, { extensionId, key, english: sourceText(value) }]));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const counts = (diff) => ({
|
|
39
|
+
added: diff.addedKeys.length, removed: diff.removedKeys.length, changed: diff.changedKeys.length
|
|
40
|
+
});
|
|
41
|
+
const emptyDiff = () => ({ addedKeys: [], removedKeys: [], changedKeys: [] });
|
|
42
|
+
|
|
43
|
+
/** A move requires the same key and English unique in BOTH complete snapshots. */
|
|
44
|
+
function findMoves(before, after, added, removed) {
|
|
45
|
+
const identity = (row) => row.english;
|
|
46
|
+
const index = (rows) => {
|
|
47
|
+
const map = new Map();
|
|
48
|
+
for (const row of rows) {
|
|
49
|
+
const id = identity(row);
|
|
50
|
+
if (!map.has(id)) map.set(id, []);
|
|
51
|
+
map.get(id).push(row);
|
|
52
|
+
}
|
|
53
|
+
return map;
|
|
54
|
+
};
|
|
55
|
+
const beforeIndex = index(before.values()), afterIndex = index(after.values());
|
|
56
|
+
const removedIndex = index(removed);
|
|
57
|
+
return added.flatMap((row) => {
|
|
58
|
+
const id = identity(row), candidates = removedIndex.get(id) ?? [];
|
|
59
|
+
if (!row.english || candidates.length !== 1 ||
|
|
60
|
+
beforeIndex.get(id)?.length !== 1 || afterIndex.get(id)?.length !== 1) return [];
|
|
61
|
+
const old = candidates[0];
|
|
62
|
+
if (old.moduleId === row.moduleId || old.key !== row.key) return [];
|
|
63
|
+
return [{ fromModuleId: old.moduleId, toModuleId: row.moduleId, key: row.key, english: row.english }];
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function diffUpgrade(before, after) {
|
|
68
|
+
const beforeCore = flattenCore(before?.core), afterCore = flattenCore(after.core);
|
|
69
|
+
const core = diffRows(beforeCore, afterCore);
|
|
70
|
+
const movedKeys = findMoves(beforeCore, afterCore, core.addedKeys, core.removedKeys);
|
|
71
|
+
const previous = before?.extensions ?? {}, current = after.extensions ?? {};
|
|
72
|
+
const addedExtensions = [], removedExtensions = [];
|
|
73
|
+
const packageNls = emptyDiff(), l10nBundle = emptyDiff();
|
|
74
|
+
const comparedExtensions = [], uncomparedExtensions = [];
|
|
75
|
+
const ids = [...new Set([...Object.keys(previous), ...Object.keys(current)])].sort(compare);
|
|
76
|
+
for (const extensionId of ids) {
|
|
77
|
+
const old = previous[extensionId], next = current[extensionId];
|
|
78
|
+
if (!old) addedExtensions.push(extensionId);
|
|
79
|
+
if (!next) removedExtensions.push(extensionId);
|
|
80
|
+
const packageDiff = diffRows(
|
|
81
|
+
extensionRows(extensionId, old?.packageNls), extensionRows(extensionId, next?.packageNls)
|
|
82
|
+
);
|
|
83
|
+
for (const key of Object.keys(packageNls)) packageNls[key].push(...packageDiff[key]);
|
|
84
|
+
|
|
85
|
+
// An absent extension is known empty; an omitted bundle field is NOT a baseline.
|
|
86
|
+
const beforeKnown = !old || recorded(old.l10nBundle);
|
|
87
|
+
const afterKnown = !next || recorded(next.l10nBundle);
|
|
88
|
+
if (!beforeKnown || !afterKnown) {
|
|
89
|
+
uncomparedExtensions.push({
|
|
90
|
+
extensionId,
|
|
91
|
+
reason: !beforeKnown && !afterKnown ? 'baseline-and-current-not-recorded'
|
|
92
|
+
: !beforeKnown ? 'baseline-not-recorded' : 'current-not-recorded'
|
|
93
|
+
});
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
comparedExtensions.push(extensionId);
|
|
97
|
+
const bundleDiff = diffRows(
|
|
98
|
+
extensionRows(extensionId, old?.l10nBundle), extensionRows(extensionId, next?.l10nBundle)
|
|
99
|
+
);
|
|
100
|
+
for (const key of Object.keys(l10nBundle)) l10nBundle[key].push(...bundleDiff[key]);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
baselineAvailable: before != null,
|
|
104
|
+
...core,
|
|
105
|
+
movedKeys,
|
|
106
|
+
counts: { ...counts(core), moved: movedKeys.length },
|
|
107
|
+
extensionDrift: {
|
|
108
|
+
addedExtensions, removedExtensions,
|
|
109
|
+
counts: { addedExtensions: addedExtensions.length, removedExtensions: removedExtensions.length },
|
|
110
|
+
packageNls: { ...packageNls, counts: counts(packageNls) },
|
|
111
|
+
l10nBundle: {
|
|
112
|
+
...l10nBundle, counts: counts(l10nBundle), comparedExtensions, uncomparedExtensions,
|
|
113
|
+
coverage: { comparedExtensions: comparedExtensions.length, uncomparedExtensions: uncomparedExtensions.length }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function translationState(english, own, upstream) {
|
|
120
|
+
const candidate = (value) => typeof value === 'string' && value !== '' ? value : null;
|
|
121
|
+
const ownTranslation = candidate(own), upstreamTranslation = candidate(upstream);
|
|
122
|
+
const effectiveSource = ownTranslation !== null ? 'own' : upstreamTranslation !== null ? 'upstream' : null;
|
|
123
|
+
const effective = effectiveSource === 'own' ? ownTranslation : upstreamTranslation;
|
|
124
|
+
return {
|
|
125
|
+
own: ownTranslation, upstream: upstreamTranslation, effectiveSource,
|
|
126
|
+
translated: effective !== null,
|
|
127
|
+
identicalToEnglish: effective !== null && effective === english
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function review(english, own, upstream) {
|
|
132
|
+
const { translated, ...state } = translationState(english, own, upstream);
|
|
133
|
+
return {
|
|
134
|
+
...state, hasCandidateTranslation: translated, required: true,
|
|
135
|
+
sourcesToReview: ['own', 'upstream'].filter((source) => state[source] !== null)
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Candidate translations for changed English always require review, even if present. */
|
|
140
|
+
export function auditUpgrade(metadata, drift, {
|
|
141
|
+
ownCore = {}, authoredCore = ownCore, upstreamCore = {}, ownExtensions = {}, upstreamExtensions = {}
|
|
142
|
+
} = {}) {
|
|
143
|
+
const live = flattenCore(metadata.core);
|
|
144
|
+
const missingTranslations = [], identicalToEnglish = [];
|
|
145
|
+
for (const row of live.values()) {
|
|
146
|
+
const state = translationState(row.english, ownCore[row.moduleId]?.[row.key], upstreamCore[row.moduleId]?.[row.key]);
|
|
147
|
+
if (row.english !== '' && !state.translated) missingTranslations.push(row);
|
|
148
|
+
if (state.identicalToEnglish) identicalToEnglish.push({ ...row, translationSource: state.effectiveSource });
|
|
149
|
+
}
|
|
150
|
+
const missingIds = new Set(missingTranslations.map((row) => `${row.moduleId}\0${row.key}`));
|
|
151
|
+
const addedUntranslated = drift.addedKeys.filter((row) => missingIds.has(`${row.moduleId}\0${row.key}`));
|
|
152
|
+
const changedKeys = drift.changedKeys.map((row) => ({
|
|
153
|
+
...row, translationReview: review(row.english, ownCore[row.moduleId]?.[row.key], upstreamCore[row.moduleId]?.[row.key])
|
|
154
|
+
}));
|
|
155
|
+
const movedKeys = drift.movedKeys.map((row) => ({
|
|
156
|
+
...row,
|
|
157
|
+
translationReview: review(row.english, ownCore[row.toModuleId]?.[row.key], upstreamCore[row.toModuleId]?.[row.key]),
|
|
158
|
+
previousTranslations: translationState(row.english, ownCore[row.fromModuleId]?.[row.key], upstreamCore[row.fromModuleId]?.[row.key])
|
|
159
|
+
}));
|
|
160
|
+
const orphanedAuthored = [...flattenCore(authoredCore).entries()]
|
|
161
|
+
.filter(([id]) => !live.has(id)).map(([, { moduleId, key }]) => ({ moduleId, key }));
|
|
162
|
+
const removedIds = new Set(drift.removedKeys.map((row) => `${row.moduleId}\0${row.key}`));
|
|
163
|
+
const newlyOrphanedAuthored = orphanedAuthored.filter((row) => removedIds.has(`${row.moduleId}\0${row.key}`));
|
|
164
|
+
const preExistingOrphanedAuthored = drift.baselineAvailable
|
|
165
|
+
? orphanedAuthored.filter((row) => !removedIds.has(`${row.moduleId}\0${row.key}`)) : null;
|
|
166
|
+
const missingModules = new Map();
|
|
167
|
+
for (const { moduleId, key, english } of missingTranslations) {
|
|
168
|
+
if (!missingModules.has(moduleId)) missingModules.set(moduleId, []);
|
|
169
|
+
missingModules.get(moduleId).push({ key, english });
|
|
170
|
+
}
|
|
171
|
+
const missingByModule = Object.fromEntries([...missingModules]
|
|
172
|
+
.sort((a, b) => b[1].length - a[1].length || compare(a[0], b[0])));
|
|
173
|
+
|
|
174
|
+
const extensionMissingTranslations = [], extensionIdenticalToEnglish = [];
|
|
175
|
+
for (const [extensionId, ext] of Object.entries(metadata.extensions ?? {}).sort(([a], [b]) => compare(a, b))) {
|
|
176
|
+
for (const [section, translationSection] of [['packageNls', 'package'], ['l10nBundle', 'bundle']]) {
|
|
177
|
+
for (const row of extensionRows(extensionId, ext[section]).values()) {
|
|
178
|
+
const state = translationState(row.english,
|
|
179
|
+
ownExtensions[extensionId]?.[translationSection]?.[row.key],
|
|
180
|
+
upstreamExtensions[extensionId]?.[translationSection]?.[row.key]);
|
|
181
|
+
if (row.english !== '' && !state.translated) {
|
|
182
|
+
extensionMissingTranslations.push({ ...row, section });
|
|
183
|
+
}
|
|
184
|
+
if (state.identicalToEnglish) {
|
|
185
|
+
extensionIdenticalToEnglish.push({ ...row, section, translationSource: state.effectiveSource });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const extensionDrift = { ...drift.extensionDrift };
|
|
191
|
+
for (const [section, translationSection] of [['packageNls', 'package'], ['l10nBundle', 'bundle']]) {
|
|
192
|
+
const diff = drift.extensionDrift[section];
|
|
193
|
+
extensionDrift[section] = { ...diff, changedKeys: diff.changedKeys.map((row) => ({
|
|
194
|
+
...row, translationReview: review(row.english,
|
|
195
|
+
ownExtensions[row.extensionId]?.[translationSection]?.[row.key],
|
|
196
|
+
upstreamExtensions[row.extensionId]?.[translationSection]?.[row.key])
|
|
197
|
+
})) };
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
changedKeys, movedKeys, extensionDrift,
|
|
201
|
+
missingTranslations, addedUntranslated, missingByModule, identicalToEnglish,
|
|
202
|
+
orphanedAuthored, newlyOrphanedAuthored, preExistingOrphanedAuthored,
|
|
203
|
+
extensionMissingTranslations, extensionIdenticalToEnglish
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** English work list. Existing candidate translations never suppress changed sources. */
|
|
208
|
+
export function buildUpgradeSkeleton(audits) {
|
|
209
|
+
const core = new Map(), extensions = new Map();
|
|
210
|
+
const coreWork = new Map(), extensionWork = new Map();
|
|
211
|
+
for (const audit of audits) {
|
|
212
|
+
for (const row of [...audit.missingTranslations, ...audit.changedKeys]) {
|
|
213
|
+
if (!core.has(row.moduleId)) core.set(row.moduleId, new Map());
|
|
214
|
+
core.get(row.moduleId).set(row.key, row.english);
|
|
215
|
+
const id = `${row.moduleId}\0${row.key}`;
|
|
216
|
+
if (!coreWork.has(id) || row.translationReview?.required) {
|
|
217
|
+
coreWork.set(id, { moduleId: row.moduleId, key: row.key,
|
|
218
|
+
reason: row.translationReview?.required ? 'source-changed' : 'missing-translation' });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const changed = ['packageNls', 'l10nBundle'].flatMap((section) =>
|
|
222
|
+
audit.extensionDrift[section].changedKeys.map((row) => ({ ...row, section })));
|
|
223
|
+
for (const row of [...audit.extensionMissingTranslations, ...changed]) {
|
|
224
|
+
if (!extensions.has(row.extensionId)) extensions.set(row.extensionId, { package: new Map(), bundle: new Map() });
|
|
225
|
+
const section = row.section === 'packageNls' ? 'package' : 'bundle';
|
|
226
|
+
extensions.get(row.extensionId)[section].set(row.key, row.english);
|
|
227
|
+
const id = `${row.extensionId}\0${row.section}\0${row.key}`;
|
|
228
|
+
if (!extensionWork.has(id) || row.translationReview?.required) {
|
|
229
|
+
extensionWork.set(id, { extensionId: row.extensionId, section: row.section, key: row.key,
|
|
230
|
+
reason: row.translationReview?.required ? 'source-changed' : 'missing-translation' });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
version: '1.0.0',
|
|
236
|
+
$comment: 'English source work list. Entries in $reviewRequired need translation or source review before use in src/i18n/.',
|
|
237
|
+
contents: Object.fromEntries([...core].map(([id, keys]) => [id, Object.fromEntries(keys)])),
|
|
238
|
+
extensions: Object.fromEntries([...extensions].map(([id, sections]) => [id, {
|
|
239
|
+
version: '1.0.0', contents: Object.fromEntries(Object.entries(sections)
|
|
240
|
+
.filter(([, keys]) => keys.size).map(([section, keys]) => [section, Object.fromEntries(keys)]))
|
|
241
|
+
}])),
|
|
242
|
+
$reviewRequired: { core: [...coreWork.values()], extensions: [...extensionWork.values()] }
|
|
243
|
+
};
|
|
244
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
export const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
6
|
+
|
|
7
|
+
/** Resolve a path relative to the repository root. */
|
|
8
|
+
export const p = (...segments) => path.join(ROOT, ...segments);
|
|
9
|
+
|
|
10
|
+
export function readJson(file, fallback = undefined) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(stripJsonComments(fs.readFileSync(file, 'utf8')));
|
|
13
|
+
} catch (err) {
|
|
14
|
+
if (err.code === 'ENOENT') {
|
|
15
|
+
if (fallback !== undefined) return fallback;
|
|
16
|
+
throw new Error(`File not found: ${file}`);
|
|
17
|
+
}
|
|
18
|
+
throw new Error(`Failed to parse JSON: ${file}\n${err.message}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function writeJson(file, data, { pretty = true } = {}) {
|
|
23
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
24
|
+
fs.writeFileSync(file, JSON.stringify(data, null, pretty ? 2 : 0) + '\n', 'utf8');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* VS Code allows comments in product.json and argv.json, and this project allows
|
|
29
|
+
* them in config.json and in translation sources. Only comments outside string
|
|
30
|
+
* literals are removed.
|
|
31
|
+
*/
|
|
32
|
+
export function stripJsonComments(text) {
|
|
33
|
+
let out = '';
|
|
34
|
+
let inString = false;
|
|
35
|
+
let inLineComment = false;
|
|
36
|
+
let inBlockComment = false;
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < text.length; i++) {
|
|
39
|
+
const c = text[i];
|
|
40
|
+
const next = text[i + 1];
|
|
41
|
+
|
|
42
|
+
if (inLineComment) {
|
|
43
|
+
if (c === '\n') { inLineComment = false; out += c; }
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (inBlockComment) {
|
|
47
|
+
if (c === '*' && next === '/') { inBlockComment = false; i++; }
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (inString) {
|
|
51
|
+
out += c;
|
|
52
|
+
if (c === '\\') { out += text[++i] ?? ''; continue; }
|
|
53
|
+
if (c === '"') inString = false;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (c === '"') { inString = true; out += c; continue; }
|
|
57
|
+
if (c === '/' && next === '/') { inLineComment = true; i++; continue; }
|
|
58
|
+
if (c === '/' && next === '*') { inBlockComment = true; i++; continue; }
|
|
59
|
+
out += c;
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Deep merge where `override` wins. Arrays are replaced, not concatenated. */
|
|
65
|
+
export function deepMerge(base, override) {
|
|
66
|
+
if (!isPlainObject(base) || !isPlainObject(override)) {
|
|
67
|
+
return structuredClone(override ?? base);
|
|
68
|
+
}
|
|
69
|
+
const out = { ...base };
|
|
70
|
+
for (const [key, value] of Object.entries(override)) {
|
|
71
|
+
out[key] = isPlainObject(value) && isPlainObject(base[key])
|
|
72
|
+
? deepMerge(base[key], value)
|
|
73
|
+
: structuredClone(value);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const isPlainObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
79
|
+
|
|
80
|
+
/** Files directly inside `dir` with the given extension, sorted by name. */
|
|
81
|
+
export function listFiles(dir, ext) {
|
|
82
|
+
if (!fs.existsSync(dir)) return [];
|
|
83
|
+
return fs.readdirSync(dir)
|
|
84
|
+
.filter((f) => f.endsWith(ext))
|
|
85
|
+
.sort()
|
|
86
|
+
.map((f) => path.join(dir, f));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function rmrf(target) {
|
|
90
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const USE_COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
94
|
+
const paint = (code, s) => (USE_COLOR ? `\u001b[${code}m${s}\u001b[0m` : s);
|
|
95
|
+
|
|
96
|
+
export const log = {
|
|
97
|
+
info: (...a) => console.log(paint(36, '·'), ...a),
|
|
98
|
+
ok: (...a) => console.log(paint(32, '+'), ...a),
|
|
99
|
+
warn: (...a) => console.warn(paint(33, '!'), ...a),
|
|
100
|
+
err: (...a) => console.error(paint(31, 'x'), ...a),
|
|
101
|
+
step: (title) => console.log('\n' + paint(1, title)),
|
|
102
|
+
plain: (...a) => console.log(...a)
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export function fail(message) {
|
|
106
|
+
log.err(message);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function loadConfig() {
|
|
111
|
+
const config = readJson(p('config.json'));
|
|
112
|
+
|
|
113
|
+
const required = ['publisher', 'namePrefix', 'version', 'engines', 'locales', 'modes', 'upstream', 'build', 'target'];
|
|
114
|
+
for (const key of required) {
|
|
115
|
+
if (config[key] === undefined) fail(`config.json is missing the required "${key}" field.`);
|
|
116
|
+
}
|
|
117
|
+
if (!Array.isArray(config.locales) || config.locales.length === 0) {
|
|
118
|
+
fail('config.json: "locales" must be a non-empty array.');
|
|
119
|
+
}
|
|
120
|
+
for (const locale of config.locales) {
|
|
121
|
+
for (const key of ['id', 'languageName', 'localizedLanguageName']) {
|
|
122
|
+
if (!locale[key]) fail(`config.json: locale entry ${JSON.stringify(locale.id ?? locale)} is missing "${key}".`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (String(config.publisher).startsWith('CHANGE-ME')) {
|
|
126
|
+
log.warn('config.json still has the placeholder "publisher". Packaging works, but set a real publisher id before publishing.');
|
|
127
|
+
}
|
|
128
|
+
return config;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Minimal `--flag`, `--key=value` and positional argument parser. */
|
|
132
|
+
export function parseArgs(argv = process.argv.slice(2)) {
|
|
133
|
+
const flags = {};
|
|
134
|
+
const positional = [];
|
|
135
|
+
for (const arg of argv) {
|
|
136
|
+
if (arg.startsWith('--')) {
|
|
137
|
+
const equal = arg.indexOf('=');
|
|
138
|
+
const key = arg.slice(2, equal < 0 ? undefined : equal);
|
|
139
|
+
const value = equal < 0 ? undefined : arg.slice(equal + 1);
|
|
140
|
+
flags[key] = value === undefined ? true : value === 'false' ? false : value;
|
|
141
|
+
} else {
|
|
142
|
+
positional.push(arg);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { flags, positional };
|
|
146
|
+
}
|