@vmz/vmz 0.0.1 → 0.0.2
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 +48 -2
- package/bin/vmz.js +4 -0
- package/dist/application-cmd.d.ts +22 -0
- package/dist/application-cmd.js +348 -0
- package/dist/bundler-adapter.d.ts +64 -0
- package/dist/bundler-adapter.js +111 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +370 -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 +274 -0
- package/dist/document-check.d.ts +44 -0
- package/dist/document-check.js +246 -0
- package/dist/document-cmd.d.ts +9 -0
- package/dist/document-cmd.js +147 -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 +234 -0
- package/dist/document-evidence.d.ts +49 -0
- package/dist/document-evidence.js +501 -0
- package/dist/document-integrate.d.ts +35 -0
- package/dist/document-integrate.js +89 -0
- package/dist/document-interactive.d.ts +69 -0
- package/dist/document-interactive.js +255 -0
- package/dist/document-locale.d.ts +31 -0
- package/dist/document-locale.js +59 -0
- package/dist/document-markdown.d.ts +13 -0
- package/dist/document-markdown.js +39 -0
- package/dist/document-scan.d.ts +21 -0
- package/dist/document-scan.js +151 -0
- package/dist/document-schema.d.ts +87 -0
- package/dist/document-schema.js +88 -0
- package/dist/explain-cmd.d.ts +5 -0
- package/dist/explain-cmd.js +123 -0
- package/dist/index.d.ts +808 -0
- package/dist/index.js +569 -0
- package/dist/locale-check.d.ts +106 -0
- package/dist/locale-check.js +737 -0
- package/dist/locale-cmd.d.ts +5 -0
- package/dist/locale-cmd.js +443 -0
- package/dist/locale-delivery.d.ts +298 -0
- package/dist/locale-delivery.js +444 -0
- package/dist/locale-router.d.ts +207 -0
- package/dist/locale-router.js +508 -0
- package/dist/locale-runtime.d.ts +406 -0
- package/dist/locale-runtime.js +542 -0
- package/dist/locale-schema.d.ts +9 -0
- package/dist/locale-schema.js +10 -0
- package/dist/locale-tooling.d.ts +118 -0
- package/dist/locale-tooling.js +358 -0
- package/dist/log.d.ts +19 -0
- package/dist/log.js +42 -0
- package/dist/packages.d.ts +27 -0
- package/dist/packages.js +147 -0
- package/dist/plugin-host.d.ts +30 -0
- package/dist/plugin-host.js +370 -0
- package/dist/refactor-cmd.d.ts +8 -0
- package/dist/refactor-cmd.js +156 -0
- package/dist/resolve.d.ts +25 -0
- package/dist/resolve.js +56 -0
- package/dist/test-cmd.d.ts +9 -0
- package/dist/test-cmd.js +343 -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 +81 -3
package/dist/test-cmd.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* `vmz test` command 鈥?discovery / build / filter / TestReport orchestration.
|
|
4
|
+
* Semantics live in `@vmz/test`.
|
|
5
|
+
*/
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { discoverTestManifests, buildTestReport, parseModes, buildForCompile, runCompileManifest, runLogicManifest, runSsrManifest, runResumeManifest, runBrowserManifest, runDeploymentManifest, } from '@vmz/test';
|
|
8
|
+
import { createWorkspace } from './index.js';
|
|
9
|
+
import { log } from './log.js';
|
|
10
|
+
/**
|
|
11
|
+
* @param {Record<string, string | boolean> & { _: string[] }} args
|
|
12
|
+
* @returns {Promise<number>}
|
|
13
|
+
*/
|
|
14
|
+
export async function cmdTest(args) {
|
|
15
|
+
const project = path.resolve(String(args._[0] || '.'));
|
|
16
|
+
let modes;
|
|
17
|
+
try {
|
|
18
|
+
modes = parseModes(/** @type {string|boolean|undefined} */ (args.mode));
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
log.error(e instanceof Error ? e.message : String(e));
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
const wantList = args.list === true;
|
|
25
|
+
const wantJson = args.json === true || typeof args.json === 'string';
|
|
26
|
+
const filter = typeof args.filter === 'string' && args.filter ? String(args.filter) : null;
|
|
27
|
+
const outDirArg = typeof args['out-dir'] === 'string' ? args['out-dir'] : undefined;
|
|
28
|
+
const wantAffected = args.affected === true || args.target === 'changed' || args.target === 'affected';
|
|
29
|
+
const applicationId = typeof args.application === 'string' && args.application ? String(args.application) : null;
|
|
30
|
+
const mountedId = typeof args.mounted === 'string' && args.mounted ? String(args.mounted) : null;
|
|
31
|
+
if (applicationId && mountedId) {
|
|
32
|
+
log.error('use either --application <id> or --mounted <id>, not both');
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
const { manifests, errors } = discoverTestManifests(project);
|
|
36
|
+
for (const err of errors) {
|
|
37
|
+
log.error(err);
|
|
38
|
+
}
|
|
39
|
+
let selected = manifests;
|
|
40
|
+
if (filter) {
|
|
41
|
+
const re = (() => {
|
|
42
|
+
try {
|
|
43
|
+
return new RegExp(filter);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
selected = manifests.filter((m) => {
|
|
50
|
+
const id = String(m.id || '');
|
|
51
|
+
const file = String(m.file || '');
|
|
52
|
+
if (re)
|
|
53
|
+
return re.test(id) || re.test(file);
|
|
54
|
+
return id.includes(filter) || file.includes(filter);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const scopeId = applicationId || mountedId;
|
|
58
|
+
if (scopeId) {
|
|
59
|
+
const mode = applicationId ? 'application' : 'mounted';
|
|
60
|
+
log.info(`test ${mode} scope: ApplicationId=${scopeId}` + (mountedId ? ' contracts=relocation,host_boundary' : ' scope=standalone'));
|
|
61
|
+
selected = selected.filter((m) => {
|
|
62
|
+
const app = (m.applicationId && String(m.applicationId)) || (m.application && String(m.application)) || '';
|
|
63
|
+
const file = String(m.file || '');
|
|
64
|
+
const id = String(m.id || '');
|
|
65
|
+
if (app)
|
|
66
|
+
return app === scopeId;
|
|
67
|
+
return file.includes(scopeId) || id.includes(scopeId);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/** @type {null | { schema: string, reason: string, testIds: string[], affectedChunkIds: string[], status: string }} */
|
|
71
|
+
let testSelection = null;
|
|
72
|
+
if (wantAffected) {
|
|
73
|
+
const outDir = outDirArg ? path.resolve(outDirArg) : path.join(project, 'dist');
|
|
74
|
+
const ws = createWorkspace({ root: project, outDir });
|
|
75
|
+
try {
|
|
76
|
+
if (typeof ws.selectTestsAffected !== 'function') {
|
|
77
|
+
log.error('selectTestsAffected missing on Workspace 鈥?rebuild native (`pnpm napi:build`)');
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
const raw = ws.selectTestsAffected();
|
|
81
|
+
try {
|
|
82
|
+
testSelection = JSON.parse(raw);
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
log.error(`test selection not JSON: ${e}`);
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
log.info(`affected selection: ${testSelection.status} 鈥?${testSelection.reason} (chunks=${(testSelection.affectedChunkIds || []).length})`);
|
|
89
|
+
const ids = new Set((testSelection.testIds || []).map(String));
|
|
90
|
+
const chunks = new Set((testSelection.affectedChunkIds || []).map(String));
|
|
91
|
+
if (ids.size > 0) {
|
|
92
|
+
selected = selected.filter((m) => ids.has(String(m.id || '')));
|
|
93
|
+
}
|
|
94
|
+
else if (chunks.size > 0) {
|
|
95
|
+
// Scaffold fallback: match manifest program.chunkId until graph鈫抰est edges exist.
|
|
96
|
+
selected = selected.filter((m) => {
|
|
97
|
+
const program = m.program && typeof m.program === 'object' ? m.program : {};
|
|
98
|
+
const chunk = program.chunkId ? String(program.chunkId) : '';
|
|
99
|
+
return chunk && chunks.has(chunk);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
selected = [];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
ws.dispose();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (modes.length === 1 && modes[0] !== 'all') {
|
|
111
|
+
const mode = modes[0];
|
|
112
|
+
selected = selected.filter((m) => Array.isArray(m.modes) && m.modes.includes(mode));
|
|
113
|
+
}
|
|
114
|
+
else if (!modes.includes('all')) {
|
|
115
|
+
selected = selected.filter((m) => Array.isArray(m.modes) && m.modes.some((x) => modes.includes(x)));
|
|
116
|
+
}
|
|
117
|
+
/** @type {Array<{
|
|
118
|
+
* testId: string,
|
|
119
|
+
* file: string,
|
|
120
|
+
* modes: string[],
|
|
121
|
+
* programId: string|null,
|
|
122
|
+
* planId: string|null,
|
|
123
|
+
* status: string,
|
|
124
|
+
* diagnostics: unknown[],
|
|
125
|
+
* }>} */
|
|
126
|
+
let tests;
|
|
127
|
+
if (wantList) {
|
|
128
|
+
tests = selected.map((m) => {
|
|
129
|
+
const program = m.program && typeof m.program === 'object' ? m.program : {};
|
|
130
|
+
const plan = m.plan && typeof m.plan === 'object' ? m.plan : {};
|
|
131
|
+
return {
|
|
132
|
+
testId: String(m.id),
|
|
133
|
+
file: String(m.file),
|
|
134
|
+
modes: Array.isArray(m.modes) ? m.modes.map(String) : [],
|
|
135
|
+
programId: program.chunkId ? String(program.chunkId) : null,
|
|
136
|
+
planId: plan.ref ? String(plan.ref) : plan.schema ? String(plan.schema) : null,
|
|
137
|
+
status: 'listed',
|
|
138
|
+
diagnostics: [],
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const modeActive = (name) => modes.includes('all') || modes.includes(name);
|
|
144
|
+
const needsBuild = selected.length > 0 &&
|
|
145
|
+
selected.some((m) => {
|
|
146
|
+
const mm = Array.isArray(m.modes) ? m.modes : [];
|
|
147
|
+
return ((mm.includes('compile') && modeActive('compile')) ||
|
|
148
|
+
(mm.includes('logic') && modeActive('logic')) ||
|
|
149
|
+
(mm.includes('ssr') && modeActive('ssr')) ||
|
|
150
|
+
(mm.includes('resume') && modeActive('resume')) ||
|
|
151
|
+
(mm.includes('browser') && modeActive('browser')) ||
|
|
152
|
+
(mm.includes('deployment') && modeActive('deployment')));
|
|
153
|
+
});
|
|
154
|
+
/** @type {string|null} */
|
|
155
|
+
let buildOut = null;
|
|
156
|
+
/** @type {unknown[]} */
|
|
157
|
+
let buildDiags = [];
|
|
158
|
+
/** @type {string|null} */
|
|
159
|
+
let buildError = null;
|
|
160
|
+
if (needsBuild) {
|
|
161
|
+
const built = buildForCompile(project, outDirArg ? path.resolve(outDirArg) : undefined, {
|
|
162
|
+
createWorkspace,
|
|
163
|
+
});
|
|
164
|
+
buildOut = built.outDir;
|
|
165
|
+
buildDiags = built.diagnostics || [];
|
|
166
|
+
if (!built.ok) {
|
|
167
|
+
buildError = built.error || 'build failed';
|
|
168
|
+
log.error(buildError);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
tests = [];
|
|
172
|
+
for (const m of selected) {
|
|
173
|
+
const program = m.program && typeof m.program === 'object' ? m.program : {};
|
|
174
|
+
const plan = m.plan && typeof m.plan === 'object' ? m.plan : {};
|
|
175
|
+
const mModes = Array.isArray(m.modes) ? m.modes.map(String) : [];
|
|
176
|
+
const base = {
|
|
177
|
+
testId: String(m.id),
|
|
178
|
+
file: String(m.file),
|
|
179
|
+
modes: mModes,
|
|
180
|
+
programId: program.chunkId ? String(program.chunkId) : null,
|
|
181
|
+
planId: plan.ref ? String(plan.ref) : plan.schema ? String(plan.schema) : null,
|
|
182
|
+
status: 'skipped',
|
|
183
|
+
diagnostics: /** @type {unknown[]} */ ([]),
|
|
184
|
+
};
|
|
185
|
+
const doCompile = mModes.includes('compile') && modeActive('compile');
|
|
186
|
+
const doLogic = mModes.includes('logic') && modeActive('logic');
|
|
187
|
+
const doSsr = mModes.includes('ssr') && modeActive('ssr');
|
|
188
|
+
const doResume = mModes.includes('resume') && modeActive('resume');
|
|
189
|
+
const doBrowser = mModes.includes('browser') && modeActive('browser');
|
|
190
|
+
const doDeployment = mModes.includes('deployment') && modeActive('deployment');
|
|
191
|
+
if (!doCompile && !doLogic && !doSsr && !doResume && !doBrowser && !doDeployment) {
|
|
192
|
+
base.status = 'skipped';
|
|
193
|
+
base.diagnostics = [
|
|
194
|
+
{
|
|
195
|
+
severity: 'info',
|
|
196
|
+
message: `modes [${mModes.join(',')}] not executed in this vmz test slice`,
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
tests.push(base);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (buildError || !buildOut) {
|
|
203
|
+
base.status = 'error';
|
|
204
|
+
base.diagnostics = [...buildDiags, { severity: 'error', message: buildError || 'build unavailable' }];
|
|
205
|
+
tests.push(base);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
/** @type {unknown[]} */
|
|
209
|
+
const diags = [];
|
|
210
|
+
/** @type {string[]} */
|
|
211
|
+
const statuses = [];
|
|
212
|
+
if (doCompile) {
|
|
213
|
+
const result = runCompileManifest(m, { outDir: buildOut });
|
|
214
|
+
statuses.push(result.status);
|
|
215
|
+
diags.push(...result.diagnostics);
|
|
216
|
+
base.programId = result.programId ?? base.programId;
|
|
217
|
+
base.planId = result.planId ?? base.planId;
|
|
218
|
+
}
|
|
219
|
+
if (doLogic) {
|
|
220
|
+
const result = await runLogicManifest(m, { outDir: buildOut });
|
|
221
|
+
statuses.push(result.status);
|
|
222
|
+
diags.push(...result.diagnostics);
|
|
223
|
+
base.programId = result.programId ?? base.programId;
|
|
224
|
+
base.planId = result.planId ?? base.planId;
|
|
225
|
+
}
|
|
226
|
+
if (doSsr) {
|
|
227
|
+
const result = await runSsrManifest(m, { outDir: buildOut });
|
|
228
|
+
statuses.push(result.status);
|
|
229
|
+
diags.push(...result.diagnostics);
|
|
230
|
+
base.programId = result.programId ?? base.programId;
|
|
231
|
+
base.planId = result.planId ?? base.planId;
|
|
232
|
+
}
|
|
233
|
+
if (doResume) {
|
|
234
|
+
const result = await runResumeManifest(m, { outDir: buildOut });
|
|
235
|
+
statuses.push(result.status);
|
|
236
|
+
diags.push(...result.diagnostics);
|
|
237
|
+
base.programId = result.programId ?? base.programId;
|
|
238
|
+
base.planId = result.planId ?? base.planId;
|
|
239
|
+
}
|
|
240
|
+
if (doDeployment) {
|
|
241
|
+
const result = runDeploymentManifest(m, { outDir: buildOut });
|
|
242
|
+
statuses.push(result.status);
|
|
243
|
+
diags.push(...result.diagnostics);
|
|
244
|
+
base.programId = result.programId ?? base.programId;
|
|
245
|
+
base.planId = result.planId ?? base.planId;
|
|
246
|
+
}
|
|
247
|
+
if (doBrowser) {
|
|
248
|
+
const result = await runBrowserManifest(m, { outDir: buildOut });
|
|
249
|
+
statuses.push(result.status);
|
|
250
|
+
diags.push(...result.diagnostics);
|
|
251
|
+
base.programId = result.programId ?? base.programId;
|
|
252
|
+
base.planId = result.planId ?? base.planId;
|
|
253
|
+
}
|
|
254
|
+
base.diagnostics = diags;
|
|
255
|
+
if (statuses.includes('error'))
|
|
256
|
+
base.status = 'error';
|
|
257
|
+
else if (statuses.includes('failed'))
|
|
258
|
+
base.status = 'failed';
|
|
259
|
+
else if (statuses.every((s) => s === 'passed'))
|
|
260
|
+
base.status = 'passed';
|
|
261
|
+
else
|
|
262
|
+
base.status = statuses[0] || 'skipped';
|
|
263
|
+
tests.push(base);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const failed = tests.some((t) => t.status === 'failed' || t.status === 'error');
|
|
267
|
+
const reportStatus = errors.length
|
|
268
|
+
? 'error'
|
|
269
|
+
: wantList
|
|
270
|
+
? tests.length
|
|
271
|
+
? 'listed'
|
|
272
|
+
: 'empty'
|
|
273
|
+
: failed
|
|
274
|
+
? 'failed'
|
|
275
|
+
: tests.length === 0
|
|
276
|
+
? 'empty'
|
|
277
|
+
: 'passed';
|
|
278
|
+
const report = buildTestReport({
|
|
279
|
+
project: path.relative(process.cwd(), project) || '.',
|
|
280
|
+
modes,
|
|
281
|
+
tests,
|
|
282
|
+
status: reportStatus,
|
|
283
|
+
});
|
|
284
|
+
if (wantList && !wantJson) {
|
|
285
|
+
if (tests.length === 0) {
|
|
286
|
+
console.log(`vmz test: no manifests under ${project}`);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
console.log(`vmz test: ${tests.length} test(s) under ${project}`);
|
|
290
|
+
for (const t of tests) {
|
|
291
|
+
console.log(` ${t.testId}\t${t.file}\t[${t.modes.join(',')}]`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (errors.length) {
|
|
295
|
+
console.log(`vmz test: ${errors.length} manifest error(s)`);
|
|
296
|
+
}
|
|
297
|
+
return errors.length ? 2 : 0;
|
|
298
|
+
}
|
|
299
|
+
if (wantJson) {
|
|
300
|
+
const text = `${JSON.stringify(report, null, 2)}\n`;
|
|
301
|
+
if (typeof args.json === 'string' && args.json !== 'true') {
|
|
302
|
+
const { writeFileSync } = await import('node:fs');
|
|
303
|
+
writeFileSync(path.resolve(String(args.json)), text);
|
|
304
|
+
if (!wantList) {
|
|
305
|
+
console.log(`vmz test: wrote ${args.json}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
process.stdout.write(text);
|
|
310
|
+
}
|
|
311
|
+
if (errors.length)
|
|
312
|
+
return 2;
|
|
313
|
+
if (!wantList && failed)
|
|
314
|
+
return 1;
|
|
315
|
+
return 0;
|
|
316
|
+
}
|
|
317
|
+
console.log(`vmz test: ${tests.length} test(s) — ${reportStatus}`);
|
|
318
|
+
for (const t of tests) {
|
|
319
|
+
console.log(` ${t.status}\t${t.testId}\t${t.file}`);
|
|
320
|
+
for (const d of t.diagnostics || []) {
|
|
321
|
+
if (d && typeof d === 'object' && d.severity === 'error') {
|
|
322
|
+
console.log(` ! ${d.message}`);
|
|
323
|
+
if (process.env.GITHUB_ACTIONS === 'true') {
|
|
324
|
+
const msg = String(d.message || 'error').replace(/[\r\n]+/g, ' ');
|
|
325
|
+
console.log(`::error title=vmz test ${t.testId}::${msg}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (process.env.GITHUB_ACTIONS === 'true' &&
|
|
330
|
+
(t.status === 'failed' || t.status === 'error') &&
|
|
331
|
+
!(t.diagnostics || []).some((d) => d && typeof d === 'object' && d.severity === 'error')) {
|
|
332
|
+
console.log(`::error title=vmz test ${t.testId}::status=${t.status}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (tests.length === 0) {
|
|
336
|
+
console.log(' (add *.vmz.test.json manifests)');
|
|
337
|
+
}
|
|
338
|
+
if (errors.length)
|
|
339
|
+
return 2;
|
|
340
|
+
if (failed)
|
|
341
|
+
return 1;
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track per-file fingerprints so dev rebuilds only dirty leaves (N4).
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} srcDir
|
|
6
|
+
* @returns {Map<string, string>}
|
|
7
|
+
*/
|
|
8
|
+
export declare function fileFingerprintMap(srcDir: any): Map<any, any>;
|
|
9
|
+
/**
|
|
10
|
+
* @param {Map<string, string>} prev
|
|
11
|
+
* @param {Map<string, string>} next
|
|
12
|
+
* @returns {{ changed: string[], deleted: string[] }}
|
|
13
|
+
*/
|
|
14
|
+
export declare function diffFingerprints(prev: any, next: any): {
|
|
15
|
+
changed: any[];
|
|
16
|
+
deleted: any[];
|
|
17
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Track per-file fingerprints so dev rebuilds only dirty leaves (N4).
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
const WATCH_EXT = new Set(['.vmz', '.ts', '.tsx', '.js', '.mjs', '.css', '.json']);
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} srcDir
|
|
10
|
+
* @returns {Map<string, string>}
|
|
11
|
+
*/
|
|
12
|
+
export function fileFingerprintMap(srcDir) {
|
|
13
|
+
/** @type {Map<string, string>} */
|
|
14
|
+
const map = new Map();
|
|
15
|
+
walk(srcDir, (file) => {
|
|
16
|
+
const st = statSync(file);
|
|
17
|
+
map.set(file, `${st.mtimeMs}|${st.size}`);
|
|
18
|
+
});
|
|
19
|
+
return map;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @param {Map<string, string>} prev
|
|
23
|
+
* @param {Map<string, string>} next
|
|
24
|
+
* @returns {{ changed: string[], deleted: string[] }}
|
|
25
|
+
*/
|
|
26
|
+
export function diffFingerprints(prev, next) {
|
|
27
|
+
/** @type {string[]} */
|
|
28
|
+
const changed = [];
|
|
29
|
+
/** @type {string[]} */
|
|
30
|
+
const deleted = [];
|
|
31
|
+
for (const [file, fp] of next) {
|
|
32
|
+
if (prev.get(file) !== fp)
|
|
33
|
+
changed.push(file);
|
|
34
|
+
}
|
|
35
|
+
for (const file of prev.keys()) {
|
|
36
|
+
if (!next.has(file))
|
|
37
|
+
deleted.push(file);
|
|
38
|
+
}
|
|
39
|
+
return { changed, deleted };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* @param {string} dir
|
|
43
|
+
* @param {(file: string) => void} fn
|
|
44
|
+
*/
|
|
45
|
+
function walk(dir, fn) {
|
|
46
|
+
if (!existsSync(dir))
|
|
47
|
+
return;
|
|
48
|
+
for (const name of readdirSync(dir)) {
|
|
49
|
+
const full = path.join(dir, name);
|
|
50
|
+
const st = statSync(full);
|
|
51
|
+
if (st.isDirectory())
|
|
52
|
+
walk(full, fn);
|
|
53
|
+
else if (WATCH_EXT.has(path.extname(name)))
|
|
54
|
+
fn(full);
|
|
55
|
+
}
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,90 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vmz/vmz",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "VMZ Node toolchain — N-API workspace session + CLI",
|
|
5
6
|
"license": "MIT",
|
|
6
|
-
"
|
|
7
|
+
"bin": {
|
|
8
|
+
"vmz": "./bin/vmz.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./cli": {
|
|
18
|
+
"types": "./dist/cli.d.ts",
|
|
19
|
+
"default": "./dist/cli.js"
|
|
20
|
+
},
|
|
21
|
+
"./dev-session": {
|
|
22
|
+
"types": "./dist/dev-session.d.ts",
|
|
23
|
+
"default": "./dist/dev-session.js"
|
|
24
|
+
},
|
|
25
|
+
"./plugin-host": {
|
|
26
|
+
"types": "./dist/plugin-host.d.ts",
|
|
27
|
+
"default": "./dist/plugin-host.js"
|
|
28
|
+
},
|
|
29
|
+
"./bundler-adapter": {
|
|
30
|
+
"types": "./dist/bundler-adapter.d.ts",
|
|
31
|
+
"default": "./dist/bundler-adapter.js"
|
|
32
|
+
},
|
|
33
|
+
"./test-protocol": {
|
|
34
|
+
"types": "./dist/test-protocol.d.ts",
|
|
35
|
+
"default": "./dist/test-protocol.js"
|
|
36
|
+
},
|
|
37
|
+
"./test-discover": {
|
|
38
|
+
"types": "./dist/test-discover.d.ts",
|
|
39
|
+
"default": "./dist/test-discover.js"
|
|
40
|
+
},
|
|
41
|
+
"./document-check": {
|
|
42
|
+
"types": "./dist/document-check.d.ts",
|
|
43
|
+
"default": "./dist/document-check.js"
|
|
44
|
+
},
|
|
45
|
+
"./document-schema": {
|
|
46
|
+
"types": "./dist/document-schema.d.ts",
|
|
47
|
+
"default": "./dist/document-schema.js"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@vmz/plugin": "0.0.2",
|
|
52
|
+
"@vmz/plugin-markdown-it": "0.0.2",
|
|
53
|
+
"@vmz/protocol": "0.0.2",
|
|
54
|
+
"@vmz/test": "0.0.2",
|
|
55
|
+
"jiti": "^2.6.1",
|
|
56
|
+
"json5": "^2.2.3",
|
|
57
|
+
"typescript": "^5.8.3"
|
|
58
|
+
},
|
|
7
59
|
"files": [
|
|
60
|
+
"bin",
|
|
61
|
+
"dist",
|
|
62
|
+
"*.node",
|
|
8
63
|
"README.md"
|
|
9
64
|
],
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build:native": "node ../../../scripts/build-napi.mjs",
|
|
67
|
+
"build": "pnpm run build:native && pnpm run build:js",
|
|
68
|
+
"test": "node ./tests/run-node-tests.mjs",
|
|
69
|
+
"build:js": "tsc -p tsconfig.json && node ../../../scripts/copy-vmz-public-api.mjs"
|
|
70
|
+
},
|
|
71
|
+
"keywords": [
|
|
72
|
+
"vmz",
|
|
73
|
+
"napi",
|
|
74
|
+
"compiler",
|
|
75
|
+
"cli"
|
|
76
|
+
],
|
|
77
|
+
"optionalDependencies": {
|
|
78
|
+
"@vmz/vmz-win32-x64": "0.0.2",
|
|
79
|
+
"@vmz/vmz-win32-arm64": "0.0.2",
|
|
80
|
+
"@vmz/vmz-darwin-x64": "0.0.2",
|
|
81
|
+
"@vmz/vmz-darwin-arm64": "0.0.2",
|
|
82
|
+
"@vmz/vmz-linux-x64": "0.0.2",
|
|
83
|
+
"@vmz/vmz-linux-arm64": "0.0.2"
|
|
84
|
+
},
|
|
85
|
+
"publishConfig": {
|
|
86
|
+
"access": "public"
|
|
87
|
+
},
|
|
10
88
|
"repository": {
|
|
11
89
|
"type": "git",
|
|
12
90
|
"url": "git+https://github.com/doki-land/vmz-framework.git"
|