@vmz/vmz 0.1.23 → 0.1.24
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/dist/dev-incremental.d.ts +60 -0
- package/dist/dev-incremental.js +86 -0
- package/dist/dev-session.js +139 -49
- package/dist/dev-watch-roots.d.ts +15 -1
- package/dist/dev-watch-roots.js +49 -2
- package/package.json +14 -14
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev incremental state helpers (0.1.24): HMR plan parsing, reload decisions, static revision.
|
|
3
|
+
*/
|
|
4
|
+
export interface HmrPlanWire {
|
|
5
|
+
mode?: string;
|
|
6
|
+
islandOnly?: boolean;
|
|
7
|
+
island_only?: boolean;
|
|
8
|
+
rerunLoaders?: string[];
|
|
9
|
+
rerun_loaders?: string[];
|
|
10
|
+
affectedChunks?: string[];
|
|
11
|
+
affected_chunks?: string[];
|
|
12
|
+
seedChunks?: string[];
|
|
13
|
+
seed_chunks?: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface BuildReportWire {
|
|
16
|
+
full?: boolean;
|
|
17
|
+
islandHmr?: boolean;
|
|
18
|
+
island_hmr?: boolean;
|
|
19
|
+
affectedChunks?: string[];
|
|
20
|
+
affected_chunks?: string[];
|
|
21
|
+
seedChunks?: string[];
|
|
22
|
+
seed_chunks?: string[];
|
|
23
|
+
emitted?: string[];
|
|
24
|
+
outputRevision?: string;
|
|
25
|
+
output_revision?: string;
|
|
26
|
+
writtenOutputs?: string[];
|
|
27
|
+
written_outputs?: string[];
|
|
28
|
+
reloadRequired?: boolean;
|
|
29
|
+
reload_required?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface DevReloadPayload {
|
|
32
|
+
affectedChunks?: string[];
|
|
33
|
+
seedChunks?: string[];
|
|
34
|
+
emitted?: string[];
|
|
35
|
+
full?: boolean;
|
|
36
|
+
islandHmr?: boolean;
|
|
37
|
+
rerunLoaders?: string[];
|
|
38
|
+
staticRevision?: string;
|
|
39
|
+
skipEntryRewrite?: boolean;
|
|
40
|
+
outputRevision?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare function parseHmrPlan(raw: string | object | null | undefined): HmrPlanWire | null;
|
|
43
|
+
export declare function hmrRerunLoaders(plan: HmrPlanWire | null | undefined): string[];
|
|
44
|
+
export declare function buildReloadPayload(report: BuildReportWire, hmrPlan: HmrPlanWire | null, opts?: {
|
|
45
|
+
staticRevision?: string;
|
|
46
|
+
skipEntryRewrite?: boolean;
|
|
47
|
+
}): DevReloadPayload;
|
|
48
|
+
export declare function shouldSoftReload(report: BuildReportWire, lastOutputRevision: string | null, opts?: {
|
|
49
|
+
force?: boolean;
|
|
50
|
+
}): boolean;
|
|
51
|
+
export declare function staticRevisionFromArtifact(artifact: {
|
|
52
|
+
files?: Array<{
|
|
53
|
+
path: string;
|
|
54
|
+
bytes?: number;
|
|
55
|
+
}>;
|
|
56
|
+
fileCount?: number;
|
|
57
|
+
} | null): string;
|
|
58
|
+
export declare function filterGenerationIgnore(changed: string[], ignore: Set<string>): string[];
|
|
59
|
+
export declare function devPathNormalize(p: string): string;
|
|
60
|
+
export declare function registerWrittenOutputsIgnore(writtenOutputs: string[] | undefined, outDir: string, projectRoot: string, ignore: Set<string>): void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev incremental state helpers (0.1.24): HMR plan parsing, reload decisions, static revision.
|
|
3
|
+
*/
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
export function parseHmrPlan(raw) {
|
|
6
|
+
if (!raw)
|
|
7
|
+
return null;
|
|
8
|
+
try {
|
|
9
|
+
const doc = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
10
|
+
return doc && typeof doc === 'object' ? doc : null;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function hmrRerunLoaders(plan) {
|
|
17
|
+
if (!plan)
|
|
18
|
+
return [];
|
|
19
|
+
const list = plan.rerunLoaders ?? plan.rerun_loaders ?? [];
|
|
20
|
+
return Array.isArray(list) ? list.map(String) : [];
|
|
21
|
+
}
|
|
22
|
+
export function buildReloadPayload(report, hmrPlan, opts = {}) {
|
|
23
|
+
const rerunLoaders = hmrRerunLoaders(hmrPlan);
|
|
24
|
+
const affectedChunks = report.affectedChunks ?? report.affected_chunks ?? [];
|
|
25
|
+
const islandHmr = Boolean(report.islandHmr ?? report.island_hmr);
|
|
26
|
+
const mode = String(hmrPlan?.mode ?? '').toLowerCase();
|
|
27
|
+
let full = Boolean(report.full);
|
|
28
|
+
if (mode === 'full')
|
|
29
|
+
full = true;
|
|
30
|
+
if (islandHmr && rerunLoaders.length === 0 && affectedChunks.length > 0) {
|
|
31
|
+
full = true;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
affectedChunks,
|
|
35
|
+
seedChunks: report.seedChunks ?? report.seed_chunks ?? [],
|
|
36
|
+
emitted: report.emitted ?? [],
|
|
37
|
+
full,
|
|
38
|
+
islandHmr: islandHmr && !full,
|
|
39
|
+
rerunLoaders,
|
|
40
|
+
staticRevision: opts.staticRevision,
|
|
41
|
+
skipEntryRewrite: opts.skipEntryRewrite,
|
|
42
|
+
outputRevision: report.outputRevision ?? report.output_revision,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function shouldSoftReload(report, lastOutputRevision, opts = {}) {
|
|
46
|
+
if (opts.force)
|
|
47
|
+
return true;
|
|
48
|
+
const reloadRequired = report.reloadRequired ?? report.reload_required;
|
|
49
|
+
if (reloadRequired === false)
|
|
50
|
+
return false;
|
|
51
|
+
const rev = report.outputRevision ?? report.output_revision ?? '';
|
|
52
|
+
if (lastOutputRevision && rev && rev === lastOutputRevision)
|
|
53
|
+
return false;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
export function staticRevisionFromArtifact(artifact) {
|
|
57
|
+
if (!artifact || artifact.fileCount === 0)
|
|
58
|
+
return 'empty';
|
|
59
|
+
const parts = (artifact.files ?? []).map((f) => `${f.path}:${f.bytes ?? 0}`);
|
|
60
|
+
parts.sort();
|
|
61
|
+
let h = 0xcbf29ce484222325n;
|
|
62
|
+
for (const p of parts) {
|
|
63
|
+
for (const b of Buffer.from(p)) {
|
|
64
|
+
h = (h * 0x100000001b3n + BigInt(b)) & 0xffffffffffffffffn;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return h.toString(16);
|
|
68
|
+
}
|
|
69
|
+
export function filterGenerationIgnore(changed, ignore) {
|
|
70
|
+
if (!ignore.size)
|
|
71
|
+
return changed;
|
|
72
|
+
return changed.filter((p) => !ignore.has(devPathNormalize(p)));
|
|
73
|
+
}
|
|
74
|
+
export function devPathNormalize(p) {
|
|
75
|
+
return String(p).replace(/\\/g, '/');
|
|
76
|
+
}
|
|
77
|
+
export function registerWrittenOutputsIgnore(writtenOutputs, outDir, projectRoot, ignore) {
|
|
78
|
+
for (const rel of writtenOutputs ?? []) {
|
|
79
|
+
const abs = path.isAbsolute(rel) ? rel : path.join(outDir, rel);
|
|
80
|
+
ignore.add(devPathNormalize(abs));
|
|
81
|
+
const underProject = devPathNormalize(abs);
|
|
82
|
+
if (underProject.startsWith(devPathNormalize(projectRoot))) {
|
|
83
|
+
ignore.add(underProject);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
package/dist/dev-session.js
CHANGED
|
@@ -16,7 +16,8 @@ import { buildIntegratedDocuments, projectHasDocuments } from './document-integr
|
|
|
16
16
|
import { createWorkspace, resolveNativePath } from './index.js';
|
|
17
17
|
import { emitLocaleRuntimeModules, localeHasErrors } from './locale-check.js';
|
|
18
18
|
import { log } from './log.js';
|
|
19
|
-
import { coalesceRootBurst, collectDevWatchRoots, classifyWatchRoot, isDependencyPath, mergeDirtySets } from './dev-watch-roots.js';
|
|
19
|
+
import { coalesceRootBurst, collectDevWatchRoots, classifyWatchRoot, isDependencyPath, mergeDirtySets, collectDevConfigPaths, configFingerprintMap, } from './dev-watch-roots.js';
|
|
20
|
+
import { buildReloadPayload, filterGenerationIgnore, parseHmrPlan, registerWrittenOutputsIgnore, shouldSoftReload, staticRevisionFromArtifact, } from './dev-incremental.js';
|
|
20
21
|
import { diffFingerprints, fileFingerprintMap } from './watch-diff.js';
|
|
21
22
|
/** Track serve-host so a failed/killed launcher cannot leave an orphan port. */
|
|
22
23
|
const DEV_HOST_PID = '.vmz-dev-host.pid';
|
|
@@ -33,10 +34,57 @@ export function createDevSession(options) {
|
|
|
33
34
|
const ws = createWs({ root: project, outDir });
|
|
34
35
|
let child = null;
|
|
35
36
|
let stopped = false;
|
|
36
|
-
|
|
37
|
+
let lastOutputRevision = null;
|
|
38
|
+
let lastStaticRevision = null;
|
|
39
|
+
const generationIgnore = new Set();
|
|
40
|
+
function planAndRebuild(changes) {
|
|
37
41
|
if (changes?.length)
|
|
38
42
|
ws.updateFiles(changes);
|
|
39
|
-
|
|
43
|
+
let hmrPlan = null;
|
|
44
|
+
try {
|
|
45
|
+
if (typeof ws.queryHmrPlan === 'function') {
|
|
46
|
+
hmrPlan = parseHmrPlan(ws.queryHmrPlan());
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
hmrPlan = null;
|
|
51
|
+
}
|
|
52
|
+
const report = ws.build();
|
|
53
|
+
return { report, hmrPlan };
|
|
54
|
+
}
|
|
55
|
+
function rebuild(changes) {
|
|
56
|
+
return planAndRebuild(changes).report;
|
|
57
|
+
}
|
|
58
|
+
async function emitPublicAndTrack() {
|
|
59
|
+
const { emitPublicStaticAssets } = await import('./public-static-assets.js');
|
|
60
|
+
const artifact = emitPublicStaticAssets(outDir, { projectRoot: project });
|
|
61
|
+
lastStaticRevision = staticRevisionFromArtifact(artifact);
|
|
62
|
+
return artifact;
|
|
63
|
+
}
|
|
64
|
+
async function maybeReloadAfterBuild(report, hmrPlan, opts = {}) {
|
|
65
|
+
const rev = report.outputRevision ?? report.output_revision ?? null;
|
|
66
|
+
const skipEntry = Boolean(rev && rev === lastOutputRevision);
|
|
67
|
+
if (!opts.force && !opts.staticOnly && !shouldSoftReload(report, lastOutputRevision)) {
|
|
68
|
+
log.info('rebuild ok — artifact unchanged, skip soft reload');
|
|
69
|
+
if (rev)
|
|
70
|
+
lastOutputRevision = rev;
|
|
71
|
+
registerWrittenOutputsIgnore(report.writtenOutputs ?? report.written_outputs, outDir, project, generationIgnore);
|
|
72
|
+
return 'skip';
|
|
73
|
+
}
|
|
74
|
+
const payload = buildReloadPayload(report, hmrPlan, {
|
|
75
|
+
staticRevision: lastStaticRevision ?? undefined,
|
|
76
|
+
skipEntryRewrite: skipEntry && !opts.force,
|
|
77
|
+
});
|
|
78
|
+
if (opts.staticOnly) {
|
|
79
|
+
payload.full = false;
|
|
80
|
+
payload.islandHmr = false;
|
|
81
|
+
payload.skipEntryRewrite = true;
|
|
82
|
+
}
|
|
83
|
+
const kind = await reloadAfterBuild(payload);
|
|
84
|
+
if (rev)
|
|
85
|
+
lastOutputRevision = rev;
|
|
86
|
+
registerWrittenOutputsIgnore(report.writtenOutputs ?? report.written_outputs, outDir, project, generationIgnore);
|
|
87
|
+
return kind;
|
|
40
88
|
}
|
|
41
89
|
function emitLocales() {
|
|
42
90
|
const localeEmit = emitLocaleRuntimeModules(project, outDir);
|
|
@@ -154,16 +202,14 @@ export function createDevSession(options) {
|
|
|
154
202
|
}
|
|
155
203
|
}
|
|
156
204
|
log.info('initial build (N-API workspace, full)…');
|
|
157
|
-
const
|
|
205
|
+
const initialPair = planAndRebuild();
|
|
206
|
+
const initial = initialPair.report;
|
|
158
207
|
if (!printReport(initial, 'build')) {
|
|
159
208
|
throw new Error('vmz dev: initial build failed');
|
|
160
209
|
}
|
|
210
|
+
lastOutputRevision = initial.outputRevision ?? initial.output_revision ?? null;
|
|
161
211
|
// `vmz build` assemble copies `public/**` → outDir; soft `ws.build()` does not.
|
|
162
|
-
|
|
163
|
-
{
|
|
164
|
-
const { emitPublicStaticAssets } = await import('./public-static-assets.js');
|
|
165
|
-
emitPublicStaticAssets(outDir, { projectRoot: project });
|
|
166
|
-
}
|
|
212
|
+
await emitPublicAndTrack();
|
|
167
213
|
// Reclaim orphan serve-host from a previous crashed launcher (same outDir).
|
|
168
214
|
reclaimStaleDevHost(outDir);
|
|
169
215
|
if (projectHasDocuments(project)) {
|
|
@@ -191,6 +237,8 @@ export function createDevSession(options) {
|
|
|
191
237
|
const localesRoot = path.join(project, 'locales');
|
|
192
238
|
const designsRoot = path.join(project, 'designs');
|
|
193
239
|
const watched = collectDevWatchRoots({ project, outDir });
|
|
240
|
+
const publicRoot = watched.publicRoot;
|
|
241
|
+
const configPaths = watched.configPaths.length ? watched.configPaths : collectDevConfigPaths(project);
|
|
194
242
|
/** @type {string[]} */
|
|
195
243
|
const watchRoots = [...watched.roots];
|
|
196
244
|
/** @type {string[]} */
|
|
@@ -209,6 +257,7 @@ export function createDevSession(options) {
|
|
|
209
257
|
for (const root of watchRoots) {
|
|
210
258
|
fingerprints.set(root, fileFingerprintMap(root));
|
|
211
259
|
}
|
|
260
|
+
let configFingerprints = configFingerprintMap(configPaths);
|
|
212
261
|
const signal = options.signal;
|
|
213
262
|
const onAbort = () => {
|
|
214
263
|
void stop();
|
|
@@ -218,7 +267,7 @@ export function createDevSession(options) {
|
|
|
218
267
|
* Scan all watch roots into a batch. Does not update fingerprints.
|
|
219
268
|
*/
|
|
220
269
|
function scanBatch() {
|
|
221
|
-
/** @type {{ srcChanged: string[], srcDeleted: string[], depChanged: string[], depDeleted: string[], designsChanged: string[], designsDeleted: string[], docsDirty: boolean, localesDirty: boolean, designsDirty: boolean }} */
|
|
270
|
+
/** @type {{ srcChanged: string[], srcDeleted: string[], depChanged: string[], depDeleted: string[], designsChanged: string[], designsDeleted: string[], publicChanged: string[], docsDirty: boolean, localesDirty: boolean, designsDirty: boolean, configDirty: boolean }} */
|
|
222
271
|
const batch = {
|
|
223
272
|
srcChanged: [],
|
|
224
273
|
srcDeleted: [],
|
|
@@ -226,11 +275,13 @@ export function createDevSession(options) {
|
|
|
226
275
|
depDeleted: [],
|
|
227
276
|
designsChanged: [],
|
|
228
277
|
designsDeleted: [],
|
|
278
|
+
publicChanged: [],
|
|
229
279
|
docsDirty: false,
|
|
230
280
|
localesDirty: false,
|
|
231
281
|
designsDirty: false,
|
|
282
|
+
configDirty: false,
|
|
232
283
|
};
|
|
233
|
-
const watchCtx = { src, docsRoot, localesRoot, designsRoot, dependencyRoots };
|
|
284
|
+
const watchCtx = { src, docsRoot, localesRoot, designsRoot, publicRoot, dependencyRoots };
|
|
234
285
|
for (const root of watchRoots) {
|
|
235
286
|
const prev = fingerprints.get(root) || new Map();
|
|
236
287
|
const next = fileFingerprintMap(root);
|
|
@@ -242,6 +293,9 @@ export function createDevSession(options) {
|
|
|
242
293
|
batch.srcChanged.push(...diff.changed);
|
|
243
294
|
batch.srcDeleted.push(...diff.deleted);
|
|
244
295
|
}
|
|
296
|
+
else if (kind === 'public') {
|
|
297
|
+
batch.publicChanged.push(...diff.changed);
|
|
298
|
+
}
|
|
245
299
|
else if (kind === 'locales') {
|
|
246
300
|
batch.localesDirty = true;
|
|
247
301
|
}
|
|
@@ -282,16 +336,51 @@ export function createDevSession(options) {
|
|
|
282
336
|
let batch;
|
|
283
337
|
try {
|
|
284
338
|
batch = scanBatch();
|
|
339
|
+
const nextConfigFp = configFingerprintMap(configPaths);
|
|
340
|
+
const configDiff = diffFingerprints(configFingerprints, nextConfigFp);
|
|
341
|
+
if (configDiff.changed.length || configDiff.deleted.length) {
|
|
342
|
+
batch.configDirty = true;
|
|
343
|
+
}
|
|
285
344
|
}
|
|
286
345
|
catch (err) {
|
|
287
346
|
log.warn('watch error:', err);
|
|
288
347
|
continue;
|
|
289
348
|
}
|
|
349
|
+
if (generationIgnore.size) {
|
|
350
|
+
batch.srcChanged = filterGenerationIgnore(batch.srcChanged, generationIgnore);
|
|
351
|
+
batch.srcDeleted = filterGenerationIgnore(batch.srcDeleted, generationIgnore);
|
|
352
|
+
generationIgnore.clear();
|
|
353
|
+
}
|
|
290
354
|
const srcDirty = batch.srcChanged.length + batch.srcDeleted.length;
|
|
291
355
|
const depDirty = batch.depChanged.length + batch.depDeleted.length;
|
|
292
356
|
const designsDirty = batch.designsDirty || batch.designsChanged.length + batch.designsDeleted.length;
|
|
293
|
-
|
|
357
|
+
const publicDirty = batch.publicChanged.length;
|
|
358
|
+
if (!srcDirty && !depDirty && !batch.docsDirty && !batch.localesDirty && !designsDirty && !publicDirty && !batch.configDirty) {
|
|
294
359
|
continue;
|
|
360
|
+
}
|
|
361
|
+
if (batch.configDirty) {
|
|
362
|
+
log.info('vmz.config change detected — respawn required (devControl)');
|
|
363
|
+
configFingerprints = configFingerprintMap(configPaths);
|
|
364
|
+
commitFingerprints();
|
|
365
|
+
await respawnHost('config-changed');
|
|
366
|
+
const report = rebuild();
|
|
367
|
+
if (printReport(report, 'rebuild')) {
|
|
368
|
+
lastOutputRevision = report.outputRevision ?? report.output_revision ?? null;
|
|
369
|
+
await emitPublicAndTrack();
|
|
370
|
+
}
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (publicDirty && !srcDirty && !depDirty && !batch.docsDirty && !batch.localesDirty && !designsDirty) {
|
|
374
|
+
log.info(`public change detected (${publicDirty} file(s)) — re-emit static assets…`);
|
|
375
|
+
await emitPublicAndTrack();
|
|
376
|
+
commitFingerprints();
|
|
377
|
+
if (!wechatPreview) {
|
|
378
|
+
const kind = await maybeReloadAfterBuild({ reloadRequired: true }, null, { force: true, staticOnly: true });
|
|
379
|
+
if (kind !== 'skip')
|
|
380
|
+
log.info('soft reload ok (public assets)');
|
|
381
|
+
}
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
295
384
|
// Coalesce multi-file bursts without dropping the initial dirty set.
|
|
296
385
|
if (srcDirty > 1) {
|
|
297
386
|
const coalesced = await coalesceRootBurst(src, fingerprints, {
|
|
@@ -344,7 +433,6 @@ export function createDevSession(options) {
|
|
|
344
433
|
batch.designsChanged = designsMerged.changed;
|
|
345
434
|
batch.designsDeleted = designsMerged.deleted;
|
|
346
435
|
}
|
|
347
|
-
commitFingerprints();
|
|
348
436
|
if (!batch.srcChanged.length &&
|
|
349
437
|
!batch.srcDeleted.length &&
|
|
350
438
|
!batch.depChanged.length &&
|
|
@@ -357,11 +445,12 @@ export function createDevSession(options) {
|
|
|
357
445
|
// Dependency changes: conservative full rebuild + full reload (v0.1.5).
|
|
358
446
|
if (batch.depChanged.length || batch.depDeleted.length) {
|
|
359
447
|
log.info(`dependency change detected (${batch.depChanged.length} update, ${batch.depDeleted.length} delete) — full rebuild…`);
|
|
360
|
-
const report =
|
|
448
|
+
const { report, hmrPlan } = planAndRebuild();
|
|
361
449
|
if (!printReport(report, 'rebuild')) {
|
|
362
450
|
log.warn('rebuild failed — keeping previous server');
|
|
363
451
|
continue;
|
|
364
452
|
}
|
|
453
|
+
await emitPublicAndTrack();
|
|
365
454
|
// Refresh watch roots in case the graph gained new packages.
|
|
366
455
|
const refreshed = collectDevWatchRoots({ project, outDir });
|
|
367
456
|
for (const r of refreshed.roots) {
|
|
@@ -381,14 +470,11 @@ export function createDevSession(options) {
|
|
|
381
470
|
log.warn('wechat pack failed — keeping previous dist/wechat');
|
|
382
471
|
continue;
|
|
383
472
|
}
|
|
384
|
-
const kind = await
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
islandHmr: false,
|
|
390
|
-
});
|
|
391
|
-
log.info(kind === 'respawn' ? 'reload ok (respawned; deps)' : 'soft reload ok (full page; deps)');
|
|
473
|
+
const kind = await maybeReloadAfterBuild(report, hmrPlan, { force: true });
|
|
474
|
+
commitFingerprints();
|
|
475
|
+
if (kind !== 'skip') {
|
|
476
|
+
log.info(kind === 'respawn' ? 'reload ok (respawned; deps)' : 'soft reload ok (full page; deps)');
|
|
477
|
+
}
|
|
392
478
|
continue;
|
|
393
479
|
}
|
|
394
480
|
let needFullReload = batch.docsDirty || batch.designsDirty;
|
|
@@ -398,11 +484,12 @@ export function createDevSession(options) {
|
|
|
398
484
|
...batch.srcChanged.map((p) => ({ path: p, kind: /** @type {'update'} */ ('update') })),
|
|
399
485
|
...batch.srcDeleted.map((p) => ({ path: p, kind: /** @type {'delete'} */ ('delete') })),
|
|
400
486
|
];
|
|
401
|
-
const report =
|
|
487
|
+
const { report, hmrPlan } = planAndRebuild(changes);
|
|
402
488
|
if (!printReport(report, 'rebuild')) {
|
|
403
489
|
log.warn('rebuild failed — keeping previous server');
|
|
404
490
|
continue;
|
|
405
491
|
}
|
|
492
|
+
await emitPublicAndTrack();
|
|
406
493
|
if (batch.docsDirty || projectHasDocuments(project)) {
|
|
407
494
|
const docs = await buildIntegratedDocuments({ projectRoot: project, outDir });
|
|
408
495
|
if (!docs.ok) {
|
|
@@ -418,20 +505,19 @@ export function createDevSession(options) {
|
|
|
418
505
|
}
|
|
419
506
|
continue;
|
|
420
507
|
}
|
|
421
|
-
const kind = await
|
|
422
|
-
|
|
423
|
-
seedChunks: report.seedChunks ?? [],
|
|
424
|
-
emitted: report.emitted ?? [],
|
|
425
|
-
full: Boolean(report.full) || needFullReload,
|
|
426
|
-
islandHmr: Boolean(report.islandHmr) && !needFullReload,
|
|
508
|
+
const kind = await maybeReloadAfterBuild(report, hmrPlan, {
|
|
509
|
+
force: needFullReload,
|
|
427
510
|
});
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
? '
|
|
432
|
-
:
|
|
433
|
-
? 'soft reload ok (
|
|
434
|
-
:
|
|
511
|
+
commitFingerprints();
|
|
512
|
+
if (kind !== 'skip') {
|
|
513
|
+
log.info(kind === 'respawn'
|
|
514
|
+
? 'reload ok (respawned serve-host)'
|
|
515
|
+
: needFullReload
|
|
516
|
+
? 'soft reload ok (full page; docs)'
|
|
517
|
+
: report.islandHmr
|
|
518
|
+
? 'soft reload ok (island HMR)'
|
|
519
|
+
: 'soft reload ok');
|
|
520
|
+
}
|
|
435
521
|
continue;
|
|
436
522
|
}
|
|
437
523
|
if (batch.designsChanged.length || batch.designsDeleted.length || batch.designsDirty) {
|
|
@@ -440,11 +526,12 @@ export function createDevSession(options) {
|
|
|
440
526
|
...batch.designsChanged.map((p) => ({ path: p, kind: /** @type {'update'} */ ('update') })),
|
|
441
527
|
...batch.designsDeleted.map((p) => ({ path: p, kind: /** @type {'delete'} */ ('delete') })),
|
|
442
528
|
];
|
|
443
|
-
const report =
|
|
529
|
+
const { report, hmrPlan } = planAndRebuild(changes.length ? changes : undefined);
|
|
444
530
|
if (!printReport(report, 'rebuild')) {
|
|
445
531
|
log.warn('designs rebuild failed — keeping previous styles');
|
|
446
532
|
continue;
|
|
447
533
|
}
|
|
534
|
+
await emitPublicAndTrack();
|
|
448
535
|
if (batch.docsDirty || projectHasDocuments(project)) {
|
|
449
536
|
const docs = await buildIntegratedDocuments({ projectRoot: project, outDir });
|
|
450
537
|
if (!docs.ok)
|
|
@@ -455,14 +542,11 @@ export function createDevSession(options) {
|
|
|
455
542
|
log.warn('wechat pack failed — keeping previous dist/wechat');
|
|
456
543
|
continue;
|
|
457
544
|
}
|
|
458
|
-
const kind = await
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
islandHmr: false,
|
|
464
|
-
});
|
|
465
|
-
log.info(kind === 'respawn' ? 'reload ok (respawned; designs)' : 'soft reload ok (full page; designs)');
|
|
545
|
+
const kind = await maybeReloadAfterBuild(report, hmrPlan, { force: true });
|
|
546
|
+
commitFingerprints();
|
|
547
|
+
if (kind !== 'skip') {
|
|
548
|
+
log.info(kind === 'respawn' ? 'reload ok (respawned; designs)' : 'soft reload ok (full page; designs)');
|
|
549
|
+
}
|
|
466
550
|
continue;
|
|
467
551
|
}
|
|
468
552
|
if (batch.localesDirty) {
|
|
@@ -479,8 +563,11 @@ export function createDevSession(options) {
|
|
|
479
563
|
continue;
|
|
480
564
|
}
|
|
481
565
|
else {
|
|
482
|
-
const kind = await
|
|
483
|
-
|
|
566
|
+
const kind = await maybeReloadAfterBuild({ reloadRequired: true, full: true }, null, { force: true });
|
|
567
|
+
commitFingerprints();
|
|
568
|
+
if (kind !== 'skip') {
|
|
569
|
+
log.info(kind === 'respawn' ? 'reload ok (respawned; locales)' : 'soft reload ok (full page; locales)');
|
|
570
|
+
}
|
|
484
571
|
if (!batch.docsDirty)
|
|
485
572
|
continue;
|
|
486
573
|
}
|
|
@@ -498,8 +585,11 @@ export function createDevSession(options) {
|
|
|
498
585
|
}
|
|
499
586
|
}
|
|
500
587
|
else {
|
|
501
|
-
const kind = await
|
|
502
|
-
|
|
588
|
+
const kind = await maybeReloadAfterBuild({ reloadRequired: true, full: true }, null, { force: true });
|
|
589
|
+
commitFingerprints();
|
|
590
|
+
if (kind !== 'skip') {
|
|
591
|
+
log.info(kind === 'respawn' ? 'reload ok (respawned; docs)' : 'soft reload ok (full page; docs)');
|
|
592
|
+
}
|
|
503
593
|
}
|
|
504
594
|
}
|
|
505
595
|
}
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dev watch helpers: coalesce multi-file bursts without dropping dirty set,
|
|
3
3
|
* and derive extra watch roots from the compile graph (deployment unit sources).
|
|
4
|
+
*
|
|
5
|
+
* DevInput layers (0.1.24): author · dependency · public · devControl.
|
|
6
|
+
* Never watch `dist/`, generated indexes, or generation write-set paths.
|
|
4
7
|
*/
|
|
8
|
+
/** Author-facing vs generated dev input bucket. */
|
|
9
|
+
export type DevInputKind = 'author' | 'dependency' | 'public' | 'devControl' | 'locales' | 'docs' | 'designs' | 'other';
|
|
10
|
+
/** `vmz.config.*` at project root (devControl). */
|
|
11
|
+
export declare function collectDevConfigPaths(project: string): string[];
|
|
12
|
+
/** Fingerprint map for explicit config files (not directory walks). */
|
|
13
|
+
export declare function configFingerprintMap(configPaths: string[]): Map<string, string>;
|
|
5
14
|
export interface DirtySet {
|
|
6
15
|
changed: string[];
|
|
7
16
|
deleted: string[];
|
|
@@ -59,7 +68,11 @@ export declare function collectDevWatchRoots(opts: CollectDevWatchRootsOpts): {
|
|
|
59
68
|
roots: string[];
|
|
60
69
|
dependencyRoots: string[];
|
|
61
70
|
applicationRoots: string[];
|
|
71
|
+
publicRoot: string | null;
|
|
72
|
+
configPaths: string[];
|
|
62
73
|
};
|
|
74
|
+
/** Map watch bucket to DevInput layer. */
|
|
75
|
+
export declare function devInputKindForWatchBucket(bucket: ReturnType<typeof classifyWatchRoot>): DevInputKind;
|
|
63
76
|
/**
|
|
64
77
|
* Classify which watch bucket a root belongs to.
|
|
65
78
|
* @param {string} root
|
|
@@ -77,9 +90,10 @@ export interface ClassifyWatchRootCtx {
|
|
|
77
90
|
docsRoot: string;
|
|
78
91
|
localesRoot: string;
|
|
79
92
|
designsRoot: string;
|
|
93
|
+
publicRoot?: string | null;
|
|
80
94
|
dependencyRoots: string[];
|
|
81
95
|
}
|
|
82
|
-
export declare function classifyWatchRoot(root: string, ctx: ClassifyWatchRootCtx): 'src' | 'locales' | 'docs' | 'designs' | 'dep' | 'other';
|
|
96
|
+
export declare function classifyWatchRoot(root: string, ctx: ClassifyWatchRootCtx): 'src' | 'locales' | 'docs' | 'designs' | 'dep' | 'public' | 'other';
|
|
83
97
|
/**
|
|
84
98
|
* Classify whether a changed file lives under a dependency watch root (not app src).
|
|
85
99
|
* @param {string} file
|
package/dist/dev-watch-roots.js
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dev watch helpers: coalesce multi-file bursts without dropping dirty set,
|
|
3
3
|
* and derive extra watch roots from the compile graph (deployment unit sources).
|
|
4
|
+
*
|
|
5
|
+
* DevInput layers (0.1.24): author · dependency · public · devControl.
|
|
6
|
+
* Never watch `dist/`, generated indexes, or generation write-set paths.
|
|
4
7
|
*/
|
|
5
|
-
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
8
|
+
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
6
9
|
import path from 'node:path';
|
|
7
10
|
import { diffFingerprints, fileFingerprintMap } from './watch-diff.js';
|
|
11
|
+
const CONFIG_BASENAMES = ['vmz.config.ts', 'vmz.config.js', 'vmz.config.mjs', 'vmz.config.cjs'];
|
|
12
|
+
/** `vmz.config.*` at project root (devControl). */
|
|
13
|
+
export function collectDevConfigPaths(project) {
|
|
14
|
+
const root = path.resolve(project);
|
|
15
|
+
return CONFIG_BASENAMES.map((name) => path.join(root, name)).filter((p) => existsSync(p));
|
|
16
|
+
}
|
|
17
|
+
/** Fingerprint map for explicit config files (not directory walks). */
|
|
18
|
+
export function configFingerprintMap(configPaths) {
|
|
19
|
+
const map = new Map();
|
|
20
|
+
for (const p of configPaths) {
|
|
21
|
+
try {
|
|
22
|
+
const st = statSync(p);
|
|
23
|
+
map.set(p, `${st.mtimeMs}:${st.size}`);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
/* ignore */
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return map;
|
|
30
|
+
}
|
|
8
31
|
export function mergeDirtySets(a, b) {
|
|
9
32
|
const changed = new Set(a?.changed || []);
|
|
10
33
|
const deleted = new Set(a?.deleted || []);
|
|
@@ -135,10 +158,14 @@ export function localLinkDependencyRoots(project) {
|
|
|
135
158
|
export function collectDevWatchRoots(opts) {
|
|
136
159
|
const project = path.resolve(opts.project);
|
|
137
160
|
const outDir = path.resolve(opts.outDir);
|
|
161
|
+
void outDir;
|
|
138
162
|
const src = path.join(project, 'src');
|
|
139
163
|
const docsRoot = path.join(project, 'documents');
|
|
140
164
|
const localesRoot = path.join(project, 'locales');
|
|
141
165
|
const designsRoot = path.join(project, 'designs');
|
|
166
|
+
const publicRootPath = path.join(project, 'public');
|
|
167
|
+
const publicRoot = existsSync(publicRootPath) && statSync(publicRootPath).isDirectory() ? publicRootPath : null;
|
|
168
|
+
const configPaths = collectDevConfigPaths(project);
|
|
142
169
|
const applicationRoots = [];
|
|
143
170
|
if (existsSync(src))
|
|
144
171
|
applicationRoots.push(src);
|
|
@@ -148,6 +175,8 @@ export function collectDevWatchRoots(opts) {
|
|
|
148
175
|
applicationRoots.push(localesRoot);
|
|
149
176
|
if (existsSync(designsRoot))
|
|
150
177
|
applicationRoots.push(designsRoot);
|
|
178
|
+
if (publicRoot)
|
|
179
|
+
applicationRoots.push(publicRoot);
|
|
151
180
|
const depSet = new Set();
|
|
152
181
|
const depJson = path.join(outDir, 'vmz-deployment.json');
|
|
153
182
|
if (existsSync(depJson)) {
|
|
@@ -188,11 +217,29 @@ export function collectDevWatchRoots(opts) {
|
|
|
188
217
|
if (!roots.includes(r))
|
|
189
218
|
roots.push(r);
|
|
190
219
|
}
|
|
191
|
-
return { roots, dependencyRoots, applicationRoots };
|
|
220
|
+
return { roots, dependencyRoots, applicationRoots, publicRoot, configPaths };
|
|
221
|
+
}
|
|
222
|
+
/** Map watch bucket to DevInput layer. */
|
|
223
|
+
export function devInputKindForWatchBucket(bucket) {
|
|
224
|
+
if (bucket === 'src')
|
|
225
|
+
return 'author';
|
|
226
|
+
if (bucket === 'dep')
|
|
227
|
+
return 'dependency';
|
|
228
|
+
if (bucket === 'public')
|
|
229
|
+
return 'public';
|
|
230
|
+
if (bucket === 'locales')
|
|
231
|
+
return 'locales';
|
|
232
|
+
if (bucket === 'docs')
|
|
233
|
+
return 'docs';
|
|
234
|
+
if (bucket === 'designs')
|
|
235
|
+
return 'designs';
|
|
236
|
+
return 'other';
|
|
192
237
|
}
|
|
193
238
|
export function classifyWatchRoot(root, ctx) {
|
|
194
239
|
if (root === ctx.src)
|
|
195
240
|
return 'src';
|
|
241
|
+
if (ctx.publicRoot && root === ctx.publicRoot)
|
|
242
|
+
return 'public';
|
|
196
243
|
if (root === ctx.localesRoot)
|
|
197
244
|
return 'locales';
|
|
198
245
|
if (root === ctx.docsRoot)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vmz/vmz",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "VMZ Node toolchain — N-API workspace session + CLI (publish name @vmz/vmz)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,16 +48,16 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@vmz/commander": "0.1.
|
|
52
|
-
"@vmz/core": "0.1.
|
|
53
|
-
"@vmz/diagnostic": "0.1.
|
|
54
|
-
"@vmz/plugin": "0.1.
|
|
55
|
-
"@vmz/protocol": "0.1.
|
|
51
|
+
"@vmz/commander": "0.1.24",
|
|
52
|
+
"@vmz/core": "0.1.24",
|
|
53
|
+
"@vmz/diagnostic": "0.1.24",
|
|
54
|
+
"@vmz/plugin": "0.1.24",
|
|
55
|
+
"@vmz/protocol": "0.1.24",
|
|
56
56
|
"jiti": "^2.6.1"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@vmz/plugin-markdown-it": "0.1.
|
|
60
|
-
"@vmz/test": "0.1.
|
|
59
|
+
"@vmz/plugin-markdown-it": "0.1.24",
|
|
60
|
+
"@vmz/test": "0.1.24",
|
|
61
61
|
"typescript": "^5.8.3"
|
|
62
62
|
},
|
|
63
63
|
"peerDependenciesMeta": {
|
|
@@ -92,12 +92,12 @@
|
|
|
92
92
|
"cli"
|
|
93
93
|
],
|
|
94
94
|
"optionalDependencies": {
|
|
95
|
-
"@vmz/vmz-win32-x64": "0.1.
|
|
96
|
-
"@vmz/vmz-win32-arm64": "0.1.
|
|
97
|
-
"@vmz/vmz-darwin-x64": "0.1.
|
|
98
|
-
"@vmz/vmz-darwin-arm64": "0.1.
|
|
99
|
-
"@vmz/vmz-linux-x64": "0.1.
|
|
100
|
-
"@vmz/vmz-linux-arm64": "0.1.
|
|
95
|
+
"@vmz/vmz-win32-x64": "0.1.24",
|
|
96
|
+
"@vmz/vmz-win32-arm64": "0.1.24",
|
|
97
|
+
"@vmz/vmz-darwin-x64": "0.1.24",
|
|
98
|
+
"@vmz/vmz-darwin-arm64": "0.1.24",
|
|
99
|
+
"@vmz/vmz-linux-x64": "0.1.24",
|
|
100
|
+
"@vmz/vmz-linux-arm64": "0.1.24"
|
|
101
101
|
},
|
|
102
102
|
"publishConfig": {
|
|
103
103
|
"access": "public"
|