@vmz/vmz 0.0.1 → 0.0.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 +52 -2
- package/bin/vmz.js +4 -0
- package/dist/application-cmd.d.ts +21 -0
- package/dist/application-cmd.js +347 -0
- package/dist/bundler-adapter.d.ts +63 -0
- package/dist/bundler-adapter.js +110 -0
- package/dist/cli.d.ts +23 -0
- package/dist/cli.js +474 -0
- package/dist/dev-session.d.ts +35 -0
- package/dist/dev-session.js +290 -0
- package/dist/document-build.d.ts +99 -0
- package/dist/document-build.js +273 -0
- package/dist/document-check.d.ts +44 -0
- package/dist/document-check.js +246 -0
- package/dist/document-cmd.d.ts +8 -0
- package/dist/document-cmd.js +146 -0
- package/dist/document-designs.d.ts +9 -0
- package/dist/document-designs.js +126 -0
- package/dist/document-enrich.d.ts +23 -0
- package/dist/document-enrich.js +233 -0
- package/dist/document-evidence.d.ts +49 -0
- package/dist/document-evidence.js +509 -0
- package/dist/document-integrate.d.ts +34 -0
- package/dist/document-integrate.js +88 -0
- package/dist/document-interactive.d.ts +69 -0
- package/dist/document-interactive.js +254 -0
- package/dist/document-locale.d.ts +31 -0
- package/dist/document-locale.js +59 -0
- package/dist/document-markdown.d.ts +12 -0
- package/dist/document-markdown.js +45 -0
- package/dist/document-scan.d.ts +21 -0
- package/dist/document-scan.js +151 -0
- package/dist/document-schema.d.ts +86 -0
- package/dist/document-schema.js +87 -0
- package/dist/explain-cmd.d.ts +5 -0
- package/dist/explain-cmd.js +123 -0
- package/dist/index.d.ts +359 -0
- package/dist/index.js +580 -0
- package/dist/invocation.d.ts +91 -0
- package/dist/invocation.js +190 -0
- package/dist/locale-check.d.ts +106 -0
- package/dist/locale-check.js +736 -0
- package/dist/locale-cmd.d.ts +5 -0
- package/dist/locale-cmd.js +442 -0
- package/dist/locale-delivery.d.ts +298 -0
- package/dist/locale-delivery.js +443 -0
- package/dist/locale-router.d.ts +206 -0
- package/dist/locale-router.js +507 -0
- package/dist/locale-runtime.d.ts +406 -0
- package/dist/locale-runtime.js +541 -0
- package/dist/locale-schema.d.ts +8 -0
- package/dist/locale-schema.js +9 -0
- package/dist/locale-tooling.d.ts +118 -0
- package/dist/locale-tooling.js +357 -0
- package/dist/log.d.ts +19 -0
- package/dist/log.js +42 -0
- package/dist/packages.d.ts +26 -0
- package/dist/packages.js +146 -0
- package/dist/plugin-host.d.ts +29 -0
- package/dist/plugin-host.js +369 -0
- package/dist/refactor-cmd.d.ts +8 -0
- package/dist/refactor-cmd.js +156 -0
- package/dist/resolve-native-cli.d.ts +14 -0
- package/dist/resolve-native-cli.js +84 -0
- package/dist/resolve.d.ts +24 -0
- package/dist/resolve.js +55 -0
- package/dist/test-cmd.d.ts +9 -0
- package/dist/test-cmd.js +363 -0
- package/dist/test-compile.d.ts +2 -0
- package/dist/test-compile.js +3 -0
- package/dist/test-discover.d.ts +2 -0
- package/dist/test-discover.js +3 -0
- package/dist/test-logic.d.ts +2 -0
- package/dist/test-logic.js +3 -0
- package/dist/test-protocol.d.ts +2 -0
- package/dist/test-protocol.js +3 -0
- package/dist/watch-diff.d.ts +17 -0
- package/dist/watch-diff.js +56 -0
- package/package.json +96 -3
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Locale tooling: explain · diff · extract · pseudo · cross-host conformance.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { DIAG_LOCALE_CONFORMANCE_DIVERGENCE, DIAG_LOCALE_EXPLAIN_UNKNOWN, DIAG_LOCALE_HARDCODED_TEXT, DIAG_LOCALE_PSEUDO_PRODUCTION_FORBIDDEN, DIAG_MESSAGE_DYNAMIC_ID_UNBOUNDED, FORMATTER_DATA_VERSION, LOCALE_CONFORMANCE_SCHEMA, LOCALE_DIFF_SCHEMA, LOCALE_EXPLAIN_SCHEMA, LOCALE_EXTRACT_SCHEMA, LOCALE_PSEUDO_SCHEMA, } from './locale-schema.js';
|
|
8
|
+
import { resolveMessageVariant } from './locale-runtime.js';
|
|
9
|
+
import { assertHostMessageInvariant, buildLocaleDeliveryResolution } from './locale-delivery.js';
|
|
10
|
+
/**
|
|
11
|
+
* Explain one MessageId: definition, params, variants, fallback, delivery reachability.
|
|
12
|
+
* @param {{
|
|
13
|
+
* messageId: string,
|
|
14
|
+
* locale?: string|null,
|
|
15
|
+
* deliveryId?: string|null,
|
|
16
|
+
* checkReport: any,
|
|
17
|
+
* }} input
|
|
18
|
+
*/
|
|
19
|
+
export function explainLocaleMessage(input) {
|
|
20
|
+
/** @type {any[]} */
|
|
21
|
+
const diagnostics = [];
|
|
22
|
+
const messages = input.checkReport?.messageCatalog?.messages || [];
|
|
23
|
+
const node = messages.find((m) => m.messageId === input.messageId);
|
|
24
|
+
if (!node) {
|
|
25
|
+
diagnostics.push({
|
|
26
|
+
code: DIAG_LOCALE_EXPLAIN_UNKNOWN,
|
|
27
|
+
severity: 'error',
|
|
28
|
+
message: `unknown MessageId ${input.messageId}`,
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
schema: LOCALE_EXPLAIN_SCHEMA,
|
|
32
|
+
status: 'failed',
|
|
33
|
+
messageId: input.messageId,
|
|
34
|
+
diagnostics,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const defaultLocale = input.checkReport?.manifest?.defaultLocale;
|
|
38
|
+
const requested = input.locale || defaultLocale;
|
|
39
|
+
const fallback = input.checkReport?.manifest?.fallback || {};
|
|
40
|
+
const resolution = resolveMessageVariant({
|
|
41
|
+
messageId: input.messageId,
|
|
42
|
+
requestedLocale: requested,
|
|
43
|
+
variants: node.variants,
|
|
44
|
+
fallback,
|
|
45
|
+
});
|
|
46
|
+
const base = node.variants?.[defaultLocale] || Object.values(node.variants || {})[0];
|
|
47
|
+
const deliveryId = input.deliveryId || 'delivery.web';
|
|
48
|
+
const delivery = buildLocaleDeliveryResolution({
|
|
49
|
+
host: 'web',
|
|
50
|
+
applicationId: 'app.locales',
|
|
51
|
+
deliveryId,
|
|
52
|
+
supportedLocales: (input.checkReport?.manifest?.locales || []).map((l) => l.id),
|
|
53
|
+
defaultLocale,
|
|
54
|
+
fallback,
|
|
55
|
+
messages,
|
|
56
|
+
reachableMessageIds: [input.messageId],
|
|
57
|
+
bundledLocales: [defaultLocale],
|
|
58
|
+
});
|
|
59
|
+
const inChunk = (delivery.lazyLocaleChunks || []).concat(delivery.bundledChunks || []).some((c) => c.messageIds?.includes(input.messageId));
|
|
60
|
+
return {
|
|
61
|
+
schema: LOCALE_EXPLAIN_SCHEMA,
|
|
62
|
+
status: 'ready',
|
|
63
|
+
messageId: input.messageId,
|
|
64
|
+
catalogId: node.catalogId,
|
|
65
|
+
params: base?.params || [],
|
|
66
|
+
variants: Object.fromEntries(Object.entries(node.variants || {}).map(([loc, v]) => [loc, { template: v.template, path: v.path, params: v.params }])),
|
|
67
|
+
requestedLocale: requested,
|
|
68
|
+
resolvedLocale: resolution.resolvedLocale,
|
|
69
|
+
fallbackPath: resolution.fallbackPath,
|
|
70
|
+
formatterDataVersion: FORMATTER_DATA_VERSION,
|
|
71
|
+
delivery: {
|
|
72
|
+
deliveryId,
|
|
73
|
+
reachable: inChunk,
|
|
74
|
+
catalogHash: delivery.messageCatalogHashes?.[resolution.resolvedLocale || defaultLocale] || null,
|
|
75
|
+
bundledLocales: delivery.bundledLocales,
|
|
76
|
+
},
|
|
77
|
+
diagnostics,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Diff two locales' catalogs.
|
|
82
|
+
* @param {{
|
|
83
|
+
* baseLocale: string,
|
|
84
|
+
* targetLocale: string,
|
|
85
|
+
* messages: Array<{ messageId: string, variants: Record<string, { template: string, params?: any[] }> }>,
|
|
86
|
+
* }} input
|
|
87
|
+
*/
|
|
88
|
+
export function diffLocaleCatalogs(input) {
|
|
89
|
+
const base = input.baseLocale;
|
|
90
|
+
const target = input.targetLocale;
|
|
91
|
+
/** @type {any[]} */
|
|
92
|
+
const missingInTarget = [];
|
|
93
|
+
/** @type {any[]} */
|
|
94
|
+
const missingInBase = [];
|
|
95
|
+
/** @type {any[]} */
|
|
96
|
+
const changed = [];
|
|
97
|
+
/** @type {any[]} */
|
|
98
|
+
const paramMismatches = [];
|
|
99
|
+
const ids = new Set();
|
|
100
|
+
for (const m of input.messages || [])
|
|
101
|
+
ids.add(m.messageId);
|
|
102
|
+
for (const messageId of [...ids].sort()) {
|
|
103
|
+
const node = (input.messages || []).find((m) => m.messageId === messageId);
|
|
104
|
+
const bv = node?.variants?.[base];
|
|
105
|
+
const tv = node?.variants?.[target];
|
|
106
|
+
if (bv && !tv)
|
|
107
|
+
missingInTarget.push(messageId);
|
|
108
|
+
else if (!bv && tv)
|
|
109
|
+
missingInBase.push(messageId);
|
|
110
|
+
else if (bv && tv) {
|
|
111
|
+
if (bv.template !== tv.template) {
|
|
112
|
+
changed.push({ messageId, base: bv.template, target: tv.template });
|
|
113
|
+
}
|
|
114
|
+
const bp = JSON.stringify(bv.params || []);
|
|
115
|
+
const tp = JSON.stringify(tv.params || []);
|
|
116
|
+
if (bp !== tp) {
|
|
117
|
+
paramMismatches.push({ messageId, baseParams: bv.params || [], targetParams: tv.params || [] });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
schema: LOCALE_DIFF_SCHEMA,
|
|
123
|
+
status: 'ready',
|
|
124
|
+
baseLocale: base,
|
|
125
|
+
targetLocale: target,
|
|
126
|
+
missingInTarget,
|
|
127
|
+
missingInBase,
|
|
128
|
+
changed,
|
|
129
|
+
paramMismatches,
|
|
130
|
+
summary: {
|
|
131
|
+
missingInTarget: missingInTarget.length,
|
|
132
|
+
missingInBase: missingInBase.length,
|
|
133
|
+
changed: changed.length,
|
|
134
|
+
paramMismatches: paramMismatches.length,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Scan source for likely hardcoded UI text sinks (extract --check).
|
|
140
|
+
* Does not auto-generate MessageIds.
|
|
141
|
+
* @param {string} projectRoot
|
|
142
|
+
* @param {{ check?: boolean }} [opts]
|
|
143
|
+
*/
|
|
144
|
+
export function extractHardcodedText(projectRoot, opts = {}) {
|
|
145
|
+
/** @type {any[]} */
|
|
146
|
+
const findings = [];
|
|
147
|
+
/** @type {any[]} */
|
|
148
|
+
const diagnostics = [];
|
|
149
|
+
const srcRoot = path.join(projectRoot, 'src');
|
|
150
|
+
if (!fs.existsSync(srcRoot)) {
|
|
151
|
+
return {
|
|
152
|
+
schema: LOCALE_EXTRACT_SCHEMA,
|
|
153
|
+
status: 'ready',
|
|
154
|
+
findings: [],
|
|
155
|
+
diagnostics: [],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** @type {string[]} */
|
|
159
|
+
const files = [];
|
|
160
|
+
const walk = (dir) => {
|
|
161
|
+
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
162
|
+
const p = path.join(dir, ent.name);
|
|
163
|
+
if (ent.isDirectory()) {
|
|
164
|
+
if (ent.name === 'node_modules' || ent.name === 'dist')
|
|
165
|
+
continue;
|
|
166
|
+
walk(p);
|
|
167
|
+
}
|
|
168
|
+
else if (/\.(vmz|ts|tsx|js|jsx)$/.test(ent.name)) {
|
|
169
|
+
files.push(p);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
walk(srcRoot);
|
|
174
|
+
// CJK or long quoted Latin UI-ish literals outside #locales imports.
|
|
175
|
+
const cjkRe = /['"`]([^'"`]*[\u4e00-\u9fff][^'"`]*)['"`]/g;
|
|
176
|
+
const uiLatinRe = /['"`]([A-Z][A-Za-z0-9 ,.!?]{8,})['"`]/g;
|
|
177
|
+
const dynamicIdRe = /(?<![A-Za-z0-9_$])(?:t|translate|i18n)\(\s*([^'")]+)\s*\)/g;
|
|
178
|
+
for (const fileAbs of files) {
|
|
179
|
+
const text = fs.readFileSync(fileAbs, 'utf8');
|
|
180
|
+
const rel = path.relative(projectRoot, fileAbs).replace(/\\/g, '/');
|
|
181
|
+
// Skip files that only re-export locales types.
|
|
182
|
+
if (rel.includes('locales-types'))
|
|
183
|
+
continue;
|
|
184
|
+
let m;
|
|
185
|
+
cjkRe.lastIndex = 0;
|
|
186
|
+
while ((m = cjkRe.exec(text))) {
|
|
187
|
+
const lit = m[1];
|
|
188
|
+
// Allow import paths / comments-ish short tokens
|
|
189
|
+
if (lit.includes('#locales/') || lit.includes('locales/'))
|
|
190
|
+
continue;
|
|
191
|
+
// Require a letter (Latin or CJK). Avoid `\W` without `u` — CJK is `\W` in ASCII mode.
|
|
192
|
+
if (!/\p{L}/u.test(lit))
|
|
193
|
+
continue;
|
|
194
|
+
findings.push({
|
|
195
|
+
path: rel,
|
|
196
|
+
kind: 'cjk_literal',
|
|
197
|
+
text: lit,
|
|
198
|
+
suggestion: 'Move UI copy into /locales catalog and import from #locales/*',
|
|
199
|
+
});
|
|
200
|
+
diagnostics.push({
|
|
201
|
+
code: DIAG_LOCALE_HARDCODED_TEXT,
|
|
202
|
+
severity: opts.check ? 'error' : 'warning',
|
|
203
|
+
message: `suspected hardcoded text ${JSON.stringify(lit)} in ${rel}`,
|
|
204
|
+
path: rel,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
uiLatinRe.lastIndex = 0;
|
|
208
|
+
while ((m = uiLatinRe.exec(text))) {
|
|
209
|
+
const lit = m[1];
|
|
210
|
+
if (/^(http|https|application\/|text\/)/i.test(lit))
|
|
211
|
+
continue;
|
|
212
|
+
if (lit.includes('#locales/'))
|
|
213
|
+
continue;
|
|
214
|
+
findings.push({
|
|
215
|
+
path: rel,
|
|
216
|
+
kind: 'ui_literal',
|
|
217
|
+
text: lit,
|
|
218
|
+
suggestion: 'Prefer #locales/* MessageId over hardcoded UI English',
|
|
219
|
+
});
|
|
220
|
+
diagnostics.push({
|
|
221
|
+
code: DIAG_LOCALE_HARDCODED_TEXT,
|
|
222
|
+
severity: 'warning',
|
|
223
|
+
message: `suspected hardcoded UI string ${JSON.stringify(lit)} in ${rel}`,
|
|
224
|
+
path: rel,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
dynamicIdRe.lastIndex = 0;
|
|
228
|
+
while ((m = dynamicIdRe.exec(text))) {
|
|
229
|
+
const arg = m[1].trim();
|
|
230
|
+
if (!/^['"`]/.test(arg)) {
|
|
231
|
+
diagnostics.push({
|
|
232
|
+
code: DIAG_MESSAGE_DYNAMIC_ID_UNBOUNDED,
|
|
233
|
+
severity: 'error',
|
|
234
|
+
message: `dynamic message id ${arg} is unbounded; use typed #locales/* exports`,
|
|
235
|
+
path: rel,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const hasErrors = diagnostics.some((d) => d.severity === 'error');
|
|
241
|
+
return {
|
|
242
|
+
schema: LOCALE_EXTRACT_SCHEMA,
|
|
243
|
+
status: hasErrors ? 'failed' : 'ready',
|
|
244
|
+
findings,
|
|
245
|
+
diagnostics,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Pseudo-localize a source locale for layout/overflow testing.
|
|
250
|
+
* Preserves ICU placeholders; marks provenance — never a production fallback.
|
|
251
|
+
* @param {{
|
|
252
|
+
* sourceLocale: string,
|
|
253
|
+
* messages: Array<{ messageId: string, variants: Record<string, { template: string }> }>,
|
|
254
|
+
* production?: boolean,
|
|
255
|
+
* }} input
|
|
256
|
+
*/
|
|
257
|
+
export function pseudoLocalizeCatalog(input) {
|
|
258
|
+
/** @type {any[]} */
|
|
259
|
+
const diagnostics = [];
|
|
260
|
+
if (input.production) {
|
|
261
|
+
diagnostics.push({
|
|
262
|
+
code: DIAG_LOCALE_PSEUDO_PRODUCTION_FORBIDDEN,
|
|
263
|
+
severity: 'error',
|
|
264
|
+
message: 'pseudo locale must not be used as production fallback',
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
/** @type {Record<string, string>} */
|
|
268
|
+
const catalog = {};
|
|
269
|
+
for (const m of input.messages || []) {
|
|
270
|
+
const src = m.variants?.[input.sourceLocale]?.template;
|
|
271
|
+
if (src == null)
|
|
272
|
+
continue;
|
|
273
|
+
// Expand length ~30% with accented padding while keeping {placeholders}.
|
|
274
|
+
const parts = String(src).split(/(\{[^}]+\})/g);
|
|
275
|
+
const out = parts
|
|
276
|
+
.map((p) => {
|
|
277
|
+
if (p.startsWith('{') && p.endsWith('}'))
|
|
278
|
+
return p;
|
|
279
|
+
const stretched = p.replace(/[A-Za-z]/g, (ch) => `${ch}\u0301`);
|
|
280
|
+
return stretched + (p.trim() ? '·' : '');
|
|
281
|
+
})
|
|
282
|
+
.join('');
|
|
283
|
+
catalog[m.messageId] = `[!! ${out} !!]`;
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
schema: LOCALE_PSEUDO_SCHEMA,
|
|
287
|
+
status: diagnostics.length ? 'failed' : 'ready',
|
|
288
|
+
sourceLocale: input.sourceLocale,
|
|
289
|
+
pseudoLocale: `pseudo-${input.sourceLocale}`,
|
|
290
|
+
provenance: 'dev-test-only',
|
|
291
|
+
catalog,
|
|
292
|
+
diagnostics,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Cross-host conformance: same MessageId set + catalog hashes + formatter version.
|
|
297
|
+
* @param {{
|
|
298
|
+
* manifest: any,
|
|
299
|
+
* messages: any[],
|
|
300
|
+
* routeIds?: string[],
|
|
301
|
+
* }} input
|
|
302
|
+
*/
|
|
303
|
+
export function checkLocaleConformance(input) {
|
|
304
|
+
/** @type {any[]} */
|
|
305
|
+
const diagnostics = [];
|
|
306
|
+
const supported = (input.manifest?.locales || []).map((l) => l.id);
|
|
307
|
+
const defaultLocale = input.manifest?.defaultLocale;
|
|
308
|
+
const messages = input.messages || [];
|
|
309
|
+
const common = {
|
|
310
|
+
applicationId: 'app.locales-fixture',
|
|
311
|
+
planVersion: 'plan.v0',
|
|
312
|
+
supportedLocales: supported,
|
|
313
|
+
defaultLocale,
|
|
314
|
+
fallback: input.manifest?.fallback || {},
|
|
315
|
+
messages,
|
|
316
|
+
reachableMessageIds: messages.map((m) => m.messageId),
|
|
317
|
+
bundledLocales: [defaultLocale],
|
|
318
|
+
};
|
|
319
|
+
const web = buildLocaleDeliveryResolution({ ...common, host: 'web', deliveryId: 'delivery.web' });
|
|
320
|
+
const mini = buildLocaleDeliveryResolution({ ...common, host: 'mini', deliveryId: 'delivery.mini' });
|
|
321
|
+
const native = buildLocaleDeliveryResolution({
|
|
322
|
+
...common,
|
|
323
|
+
host: 'native',
|
|
324
|
+
deliveryId: 'delivery.native',
|
|
325
|
+
});
|
|
326
|
+
diagnostics.push(...web.diagnostics, ...mini.diagnostics, ...native.diagnostics);
|
|
327
|
+
const inv = assertHostMessageInvariant([web, mini, native]);
|
|
328
|
+
if (!inv.ok) {
|
|
329
|
+
for (const d of inv.diagnostics) {
|
|
330
|
+
diagnostics.push({
|
|
331
|
+
code: DIAG_LOCALE_CONFORMANCE_DIVERGENCE,
|
|
332
|
+
severity: 'error',
|
|
333
|
+
message: d.message,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// RouteId surface: stable ids must not embed LocaleId.
|
|
338
|
+
for (const routeId of input.routeIds || []) {
|
|
339
|
+
if (supported.some((loc) => routeId.includes(loc))) {
|
|
340
|
+
diagnostics.push({
|
|
341
|
+
code: DIAG_LOCALE_CONFORMANCE_DIVERGENCE,
|
|
342
|
+
severity: 'error',
|
|
343
|
+
message: `RouteId ${routeId} must not embed LocaleId`,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const hasErrors = diagnostics.some((d) => d.severity === 'error');
|
|
348
|
+
return {
|
|
349
|
+
schema: LOCALE_CONFORMANCE_SCHEMA,
|
|
350
|
+
status: hasErrors ? 'failed' : 'ready',
|
|
351
|
+
hosts: ['web', 'mini', 'native'],
|
|
352
|
+
formatterDataVersion: FORMATTER_DATA_VERSION,
|
|
353
|
+
messageIds: messages.map((m) => m.messageId).sort(),
|
|
354
|
+
catalogHashes: web.messageCatalogHashes,
|
|
355
|
+
diagnostics,
|
|
356
|
+
};
|
|
357
|
+
}
|
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified CLI logging / diagnostics .
|
|
3
|
+
*/
|
|
4
|
+
export declare const log: {
|
|
5
|
+
/** @param {...unknown} args */
|
|
6
|
+
info(...args: any[]): void;
|
|
7
|
+
/** @param {...unknown} args */
|
|
8
|
+
warn(...args: any[]): void;
|
|
9
|
+
/** @param {...unknown} args */
|
|
10
|
+
error(...args: any[]): void;
|
|
11
|
+
/** @param {{ severity: string, path: string, message: string }} d */
|
|
12
|
+
diagnostic(d: any): void;
|
|
13
|
+
/**
|
|
14
|
+
* @param {Array<{ severity: string, path: string, message: string }>} diagnostics
|
|
15
|
+
* @param {{ denyWarnings?: boolean }} [opts]
|
|
16
|
+
* @returns {number} failing count (errors, and warnings if denyWarnings)
|
|
17
|
+
*/
|
|
18
|
+
diagnostics(diagnostics: any, opts?: {}): number;
|
|
19
|
+
};
|
package/dist/log.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Unified CLI logging / diagnostics .
|
|
4
|
+
*/
|
|
5
|
+
/** @param {string} level */
|
|
6
|
+
function stamp(level) {
|
|
7
|
+
return `vmz ${level}`;
|
|
8
|
+
}
|
|
9
|
+
export const log = {
|
|
10
|
+
/** @param {...unknown} args */
|
|
11
|
+
info(...args) {
|
|
12
|
+
console.error(stamp('info'), ...args);
|
|
13
|
+
},
|
|
14
|
+
/** @param {...unknown} args */
|
|
15
|
+
warn(...args) {
|
|
16
|
+
console.error(stamp('warn'), ...args);
|
|
17
|
+
},
|
|
18
|
+
/** @param {...unknown} args */
|
|
19
|
+
error(...args) {
|
|
20
|
+
console.error(stamp('error'), ...args);
|
|
21
|
+
},
|
|
22
|
+
/** @param {{ severity: string, path: string, message: string }} d */
|
|
23
|
+
diagnostic(d) {
|
|
24
|
+
console.error(`${d.severity}: ${d.path}: ${d.message}`);
|
|
25
|
+
},
|
|
26
|
+
/**
|
|
27
|
+
* @param {Array<{ severity: string, path: string, message: string }>} diagnostics
|
|
28
|
+
* @param {{ denyWarnings?: boolean }} [opts]
|
|
29
|
+
* @returns {number} failing count (errors, and warnings if denyWarnings)
|
|
30
|
+
*/
|
|
31
|
+
diagnostics(diagnostics, opts = {}) {
|
|
32
|
+
let failing = 0;
|
|
33
|
+
for (const d of diagnostics ?? []) {
|
|
34
|
+
this.diagnostic(d);
|
|
35
|
+
if (d.severity === 'error')
|
|
36
|
+
failing += 1;
|
|
37
|
+
else if (opts.denyWarnings && d.severity === 'warning')
|
|
38
|
+
failing += 1;
|
|
39
|
+
}
|
|
40
|
+
return failing;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm / pnpm workspace package resolution helpers (session).
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {object} ResolvedPackage
|
|
6
|
+
* @property {string} name
|
|
7
|
+
* @property {string} root
|
|
8
|
+
* @property {boolean} [private]
|
|
9
|
+
* @property {boolean} hasSrc
|
|
10
|
+
* @property {string} [version]
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Resolve workspace packages under a project (package.json workspaces or pnpm-workspace.yaml).
|
|
14
|
+
* Does not invent VMZ semantics — only filesystem / npm layout facts for plugins.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} project
|
|
17
|
+
* @returns {ResolvedPackage[]}
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveWorkspacePackages(project: any): any[];
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a package name to an absolute root (workspace first, then node_modules).
|
|
22
|
+
* @param {string} project
|
|
23
|
+
* @param {string} name
|
|
24
|
+
* @returns {string | null}
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolvePackageRoot(project: any, name: any): any;
|
package/dist/packages.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* npm / pnpm workspace package resolution helpers (session).
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} ResolvedPackage
|
|
9
|
+
* @property {string} name
|
|
10
|
+
* @property {string} root
|
|
11
|
+
* @property {boolean} [private]
|
|
12
|
+
* @property {boolean} hasSrc
|
|
13
|
+
* @property {string} [version]
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Resolve workspace packages under a project (package.json workspaces or pnpm-workspace.yaml).
|
|
17
|
+
* Does not invent VMZ semantics — only filesystem / npm layout facts for plugins.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} project
|
|
20
|
+
* @returns {ResolvedPackage[]}
|
|
21
|
+
*/
|
|
22
|
+
export function resolveWorkspacePackages(project) {
|
|
23
|
+
const root = path.resolve(project);
|
|
24
|
+
const patterns = readWorkspacePatterns(root);
|
|
25
|
+
/** @type {Map<string, ResolvedPackage>} */
|
|
26
|
+
const out = new Map();
|
|
27
|
+
// Always include the project itself when it has package.json.
|
|
28
|
+
const self = readPkg(root);
|
|
29
|
+
if (self)
|
|
30
|
+
out.set(self.root, self);
|
|
31
|
+
for (const pattern of patterns) {
|
|
32
|
+
for (const dir of expandWorkspacePattern(root, pattern)) {
|
|
33
|
+
const pkg = readPkg(dir);
|
|
34
|
+
if (pkg)
|
|
35
|
+
out.set(pkg.root, pkg);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return [...out.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a package name to an absolute root (workspace first, then node_modules).
|
|
42
|
+
* @param {string} project
|
|
43
|
+
* @param {string} name
|
|
44
|
+
* @returns {string | null}
|
|
45
|
+
*/
|
|
46
|
+
export function resolvePackageRoot(project, name) {
|
|
47
|
+
const hit = resolveWorkspacePackages(project).find((p) => p.name === name);
|
|
48
|
+
if (hit)
|
|
49
|
+
return hit.root;
|
|
50
|
+
const nm = path.join(path.resolve(project), 'node_modules', ...name.split('/'));
|
|
51
|
+
if (existsSync(path.join(nm, 'package.json')))
|
|
52
|
+
return nm;
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} root
|
|
57
|
+
* @returns {string[]}
|
|
58
|
+
*/
|
|
59
|
+
function readWorkspacePatterns(root) {
|
|
60
|
+
const patterns = [];
|
|
61
|
+
const pkgPath = path.join(root, 'package.json');
|
|
62
|
+
if (existsSync(pkgPath)) {
|
|
63
|
+
try {
|
|
64
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
65
|
+
const ws = pkg.workspaces;
|
|
66
|
+
if (Array.isArray(ws))
|
|
67
|
+
patterns.push(...ws);
|
|
68
|
+
else if (ws && Array.isArray(ws.packages))
|
|
69
|
+
patterns.push(...ws.packages);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* ignore */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const pnpm = path.join(root, 'pnpm-workspace.yaml');
|
|
76
|
+
if (existsSync(pnpm)) {
|
|
77
|
+
try {
|
|
78
|
+
const text = readFileSync(pnpm, 'utf8');
|
|
79
|
+
for (const line of text.split(/\r?\n/)) {
|
|
80
|
+
const m = line.match(/^\s*-\s*['"]?([^'"]+)['"]?\s*$/);
|
|
81
|
+
if (m)
|
|
82
|
+
patterns.push(m[1]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
/* ignore */
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return [...new Set(patterns)];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Minimal glob: supports `packages/*`, `examples/*`, exact dirs. No `**`.
|
|
93
|
+
* @param {string} root
|
|
94
|
+
* @param {string} pattern
|
|
95
|
+
*/
|
|
96
|
+
function expandWorkspacePattern(root, pattern) {
|
|
97
|
+
const cleaned = pattern.replace(/\\/g, '/').replace(/\/$/, '');
|
|
98
|
+
if (!cleaned.includes('*')) {
|
|
99
|
+
const dir = path.join(root, cleaned);
|
|
100
|
+
return existsSync(dir) ? [dir] : [];
|
|
101
|
+
}
|
|
102
|
+
const star = cleaned.indexOf('*');
|
|
103
|
+
const prefix = cleaned.slice(0, star).replace(/\/$/, '');
|
|
104
|
+
const suffix = cleaned.slice(star + 1); // e.g. "" or "/*" — we only support one *
|
|
105
|
+
if (suffix.includes('*'))
|
|
106
|
+
return [];
|
|
107
|
+
const base = path.join(root, prefix);
|
|
108
|
+
if (!existsSync(base))
|
|
109
|
+
return [];
|
|
110
|
+
/** @type {string[]} */
|
|
111
|
+
const dirs = [];
|
|
112
|
+
for (const name of readdirSync(base, { withFileTypes: true })) {
|
|
113
|
+
if (!name.isDirectory())
|
|
114
|
+
continue;
|
|
115
|
+
const dir = path.join(base, name.name);
|
|
116
|
+
if (suffix && !existsSync(path.join(dir, suffix.replace(/^\//, '')))) {
|
|
117
|
+
// suffix after * is path remainder like `/foo` — rare; skip strict check
|
|
118
|
+
}
|
|
119
|
+
dirs.push(dir);
|
|
120
|
+
}
|
|
121
|
+
return dirs;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* @param {string} dir
|
|
125
|
+
* @returns {ResolvedPackage | null}
|
|
126
|
+
*/
|
|
127
|
+
function readPkg(dir) {
|
|
128
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
129
|
+
if (!existsSync(pkgPath))
|
|
130
|
+
return null;
|
|
131
|
+
try {
|
|
132
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
133
|
+
if (!pkg.name)
|
|
134
|
+
return null;
|
|
135
|
+
return {
|
|
136
|
+
name: pkg.name,
|
|
137
|
+
root: dir,
|
|
138
|
+
private: Boolean(pkg.private),
|
|
139
|
+
hasSrc: existsSync(path.join(dir, 'src')),
|
|
140
|
+
version: typeof pkg.version === 'string' ? pkg.version : undefined,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin protocol v1 helpers + typed config loading.
|
|
3
|
+
*/
|
|
4
|
+
import { contentHash, defineConfig, definePlugin } from '@vmz/plugin';
|
|
5
|
+
export { contentHash, defineConfig, definePlugin };
|
|
6
|
+
export declare const PLUGIN_PROTOCOL = "0.1.0";
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} full
|
|
9
|
+
* @returns {Promise<any>}
|
|
10
|
+
*/
|
|
11
|
+
export declare function importMaybeTs(full: any): Promise<any>;
|
|
12
|
+
/**
|
|
13
|
+
* Load `vmz.config.*` from project root (+ optional root `vmz.plugin.*`).
|
|
14
|
+
* @param {string} project
|
|
15
|
+
* @returns {Promise<{ plugins: import('@vmz/plugin').VmzPlugin[], engines: import('@vmz/plugin').VmzEngines, path: string | null, pluginPath: string | null }>}
|
|
16
|
+
*/
|
|
17
|
+
export declare function loadVmzConfig(project: any): Promise<{
|
|
18
|
+
plugins: any[];
|
|
19
|
+
engines: {};
|
|
20
|
+
path: any;
|
|
21
|
+
pluginPath: any;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Collect + apply contribution batches for the given stages onto a Workspace.
|
|
25
|
+
* @param {import('../index.js').Workspace} workspace
|
|
26
|
+
* @param {import('@vmz/plugin').VmzPlugin[]} plugins
|
|
27
|
+
* @param {{ project: string, outDir: string, stages?: string[], engines?: import('@vmz/plugin').VmzEngines }} opts
|
|
28
|
+
*/
|
|
29
|
+
export declare function applyPlugins(workspace: any, plugins: any, opts: any): Promise<any[]>;
|