@vmz/vmz 0.1.22 → 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/cli.js +2 -0
- package/dist/content-addressed-assets.d.ts +10 -7
- package/dist/content-addressed-assets.js +30 -62
- 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/dist/github-actions-cmd.d.ts +6 -0
- package/dist/github-actions-cmd.js +120 -0
- package/dist/pack-client-packages.d.ts +4 -2
- package/dist/pack-client-packages.js +23 -33
- package/dist/static-emit.js +20 -15
- package/locales/en-US/cli.json +14 -0
- package/locales/en-US/diag.json +0 -1
- package/locales/zh-CN/diag.json +0 -1
- package/package.json +14 -14
package/dist/cli.js
CHANGED
|
@@ -21,6 +21,7 @@ import { registerArtifactCommands } from './release-cmd.js';
|
|
|
21
21
|
import { registerRefactorCommands } from './refactor-cmd.js';
|
|
22
22
|
import { registerExplainCommand } from './explain-cmd.js';
|
|
23
23
|
import { registerPlanCommands } from './plan-cmd.js';
|
|
24
|
+
import { registerGithubActionsCommand } from './github-actions-cmd.js';
|
|
24
25
|
import { loadVmzConfig } from './plugin-host.js';
|
|
25
26
|
import { normalizeDeliveryAuthoring, resolveProfileArtifactDir, selectBuildProfile } from './delivery-profile.js';
|
|
26
27
|
import { packFromDeploymentIr } from './pack.js';
|
|
@@ -76,6 +77,7 @@ function buildProductCli(opts = {}) {
|
|
|
76
77
|
registerArtifactCommands(cli.command('artifact|artifacts|release', 'cli.cmd.artifact'));
|
|
77
78
|
registerRefactorCommands(cli.command('refactor', 'cli.cmd.refactor'));
|
|
78
79
|
registerExplainCommand(cli);
|
|
80
|
+
registerGithubActionsCommand(cli);
|
|
79
81
|
cli.command('version', 'cli.cmd.version').action(() => cmdVersion());
|
|
80
82
|
return cli;
|
|
81
83
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A3: content-addressed assets/<hash> layout for immutable CDN objects.
|
|
3
|
-
* Logical paths stay available for serve/dev
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* Logical paths stay available for serve/dev. Static HTML must receive hashed
|
|
4
|
+
* `cssEntry` / `moduleScriptSrc` at shell emit time — this module does **not**
|
|
5
|
+
* post-rewrite HTML. CSS aggregators rewrite `@import` before hash. JS under
|
|
6
|
+
* `assets/` rewrites ESM `./` → `../` via oxc (plus dynamic `import("./"+)` form)
|
|
7
|
+
* so barrels resolve at dist root.
|
|
7
8
|
*/
|
|
8
9
|
export declare const CONTENT_ADDRESSED_ASSETS_SCHEMA = "vmz.content_addressed_assets.v0";
|
|
9
10
|
interface ContentAddressedAssetObject {
|
|
@@ -24,7 +25,6 @@ export interface ContentAddressedAssetsManifest {
|
|
|
24
25
|
}
|
|
25
26
|
interface EmitContentAddressedAssetsOpts {
|
|
26
27
|
candidates?: string[];
|
|
27
|
-
rewriteHtml?: boolean;
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Rewrite relative `@import "./foo.css"` to hashed paths under assets/.
|
|
@@ -46,9 +46,10 @@ export declare function rewriteCssImports(cssText: any, rewrites: any): any;
|
|
|
46
46
|
*/
|
|
47
47
|
export declare function rewriteJsEntryRelativeImports(jsText: any, _rewrites?: {}): string;
|
|
48
48
|
/**
|
|
49
|
-
* Emit `assets/<sha256>.<ext>` copies and
|
|
49
|
+
* Emit `assets/<sha256>.<ext>` copies and return logical→hashed rewrites.
|
|
50
|
+
* Callers must pass hashed URLs into `generatePageShell` — HTML is never rewritten here.
|
|
50
51
|
* @param {string} distDir
|
|
51
|
-
* @param {{ candidates?: string[]
|
|
52
|
+
* @param {{ candidates?: string[] }} [opts]
|
|
52
53
|
*/
|
|
53
54
|
export declare function emitContentAddressedAssets(distDir: string, opts?: EmitContentAddressedAssetsOpts): {
|
|
54
55
|
manifest: ContentAddressedAssetsManifest;
|
|
@@ -99,6 +100,8 @@ export declare function assertSharedAssetPath(distDir: any, a: any, b: any, ext?
|
|
|
99
100
|
digestB?: undefined;
|
|
100
101
|
rel?: undefined;
|
|
101
102
|
};
|
|
103
|
+
/** Resolve `/logical` → hashed href from emit rewrites (`/assets/<sha>.ext`). */
|
|
104
|
+
export declare function hashedAssetHref(rewrites: Record<string, string>, logical: string | null | undefined): string | null;
|
|
102
105
|
export declare function contentAddressedAssetsDigest(manifest: any): any;
|
|
103
106
|
export declare function sha256Buffer(buf: any): string;
|
|
104
107
|
export {};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A3: content-addressed assets/<hash> layout for immutable CDN objects.
|
|
3
|
-
* Logical paths stay available for serve/dev
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* Logical paths stay available for serve/dev. Static HTML must receive hashed
|
|
4
|
+
* `cssEntry` / `moduleScriptSrc` at shell emit time — this module does **not**
|
|
5
|
+
* post-rewrite HTML. CSS aggregators rewrite `@import` before hash. JS under
|
|
6
|
+
* `assets/` rewrites ESM `./` → `../` via oxc (plus dynamic `import("./"+)` form)
|
|
7
|
+
* so barrels resolve at dist root.
|
|
7
8
|
*/
|
|
8
9
|
import crypto from 'node:crypto';
|
|
9
10
|
import fs from 'node:fs';
|
|
10
11
|
import path from 'node:path';
|
|
12
|
+
import { requireNativeAddon } from './native-addon.js';
|
|
11
13
|
import { canonicalJson, sha256Hex } from './release-pack.js';
|
|
12
14
|
import { writePrettyJsonFile } from './pretty-json.js';
|
|
13
15
|
export const CONTENT_ADDRESSED_ASSETS_SCHEMA = 'vmz.content_addressed_assets.v0';
|
|
@@ -59,26 +61,25 @@ export function rewriteCssImports(cssText, rewrites) {
|
|
|
59
61
|
* @param {Record<string, string>} [_rewrites]
|
|
60
62
|
*/
|
|
61
63
|
export function rewriteJsEntryRelativeImports(jsText, _rewrites = {}) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
const native = requireNativeAddon();
|
|
65
|
+
if (typeof native.rewriteModuleSpecifiers !== 'function') {
|
|
66
|
+
throw new Error('native missing rewriteModuleSpecifiers — run `pnpm napi:build`');
|
|
67
|
+
}
|
|
68
|
+
// Static import/export specs via oxc AST (`./x` → `../x`). Dynamic
|
|
69
|
+
// `import("./"+id)` is not a literal specifier — rewrite that codegen form only.
|
|
70
|
+
let out = String(native.rewriteModuleSpecifiers(String(jsText ?? ''), JSON.stringify({
|
|
71
|
+
tsExtToJs: false,
|
|
72
|
+
dotSlashToParent: true,
|
|
73
|
+
})));
|
|
64
74
|
out = out.replace(/import\(\s*"\.\/"\s*\+/g, 'import("../"+');
|
|
65
75
|
out = out.replace(/import\(\s*'\.\/'\s*\+/g, "import('../'+");
|
|
66
|
-
const rewriteSpec = (spec) => {
|
|
67
|
-
const logical = String(spec || '').replace(/^\.\//, '');
|
|
68
|
-
if (!logical || logical.startsWith('../') || logical.startsWith('/'))
|
|
69
|
-
return spec;
|
|
70
|
-
return `../${logical}`;
|
|
71
|
-
};
|
|
72
|
-
// import/export … from "./x" | import("./x")
|
|
73
|
-
out = out.replace(/\b((?:import|export)\s+[^'"\n]*?\s+from\s+|import\s*\(\s*)(['"])(\.\/[^'"]+)\2/g, (match, prefix, quote, spec) => `${prefix}${quote}${rewriteSpec(spec)}${quote}`);
|
|
74
|
-
// side-effect: import "./x.js"
|
|
75
|
-
out = out.replace(/(^|[;\s])(import\s*)(['"])(\.\/[^'"]+)\3/gm, (match, lead, kw, quote, spec) => `${lead}${kw}${quote}${rewriteSpec(spec)}${quote}`);
|
|
76
76
|
return out;
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
|
-
* Emit `assets/<sha256>.<ext>` copies and
|
|
79
|
+
* Emit `assets/<sha256>.<ext>` copies and return logical→hashed rewrites.
|
|
80
|
+
* Callers must pass hashed URLs into `generatePageShell` — HTML is never rewritten here.
|
|
80
81
|
* @param {string} distDir
|
|
81
|
-
* @param {{ candidates?: string[]
|
|
82
|
+
* @param {{ candidates?: string[] }} [opts]
|
|
82
83
|
*/
|
|
83
84
|
export function emitContentAddressedAssets(distDir, opts = {}) {
|
|
84
85
|
const abs = path.resolve(distDir);
|
|
@@ -141,17 +142,13 @@ export function emitContentAddressedAssets(distDir, opts = {}) {
|
|
|
141
142
|
});
|
|
142
143
|
}
|
|
143
144
|
objects.sort((a, b) => (a.logicalPath < b.logicalPath ? -1 : a.logicalPath > b.logicalPath ? 1 : 0));
|
|
144
|
-
let rewrittenHtml = 0;
|
|
145
|
-
if (opts.rewriteHtml !== false) {
|
|
146
|
-
rewrittenHtml = rewriteHtmlReferences(abs, rewrites);
|
|
147
|
-
}
|
|
148
145
|
const manifest = {
|
|
149
146
|
schema: CONTENT_ADDRESSED_ASSETS_SCHEMA,
|
|
150
147
|
layout: 'assets/<sha256>.<ext>',
|
|
151
148
|
immutable: true,
|
|
152
149
|
objectCount: objects.length,
|
|
153
150
|
objects,
|
|
154
|
-
rewrittenHtml,
|
|
151
|
+
rewrittenHtml: 0,
|
|
155
152
|
};
|
|
156
153
|
manifest.manifestDigest = sha256Hex(canonicalJson({ ...manifest, manifestDigest: undefined }));
|
|
157
154
|
const vmzDir = path.join(abs, '_vmz');
|
|
@@ -299,45 +296,16 @@ function walk(root, dir, onFile) {
|
|
|
299
296
|
onFile(path.relative(root, full).replace(/\\/g, '/'));
|
|
300
297
|
}
|
|
301
298
|
}
|
|
302
|
-
/**
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
let count = 0;
|
|
313
|
-
walkHtml(distDir, (file) => {
|
|
314
|
-
let text = fs.readFileSync(file, 'utf8');
|
|
315
|
-
let next = text;
|
|
316
|
-
for (const [from, to] of pairs) {
|
|
317
|
-
next = next.split(from).join(to);
|
|
318
|
-
}
|
|
319
|
-
if (next !== text) {
|
|
320
|
-
fs.writeFileSync(file, next, 'utf8');
|
|
321
|
-
count += 1;
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
return count;
|
|
325
|
-
}
|
|
326
|
-
function walkHtml(dir, onFile) {
|
|
327
|
-
const stack = [dir];
|
|
328
|
-
while (stack.length) {
|
|
329
|
-
const cur = stack.pop();
|
|
330
|
-
for (const name of fs.readdirSync(cur)) {
|
|
331
|
-
if (name === 'assets' || name === '_vmz' || name === 'node_modules')
|
|
332
|
-
continue;
|
|
333
|
-
const full = path.join(cur, name);
|
|
334
|
-
const st = fs.statSync(full);
|
|
335
|
-
if (st.isDirectory())
|
|
336
|
-
stack.push(full);
|
|
337
|
-
else if (name.endsWith('.html'))
|
|
338
|
-
onFile(full);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
299
|
+
/** Resolve `/logical` → hashed href from emit rewrites (`/assets/<sha>.ext`). */
|
|
300
|
+
export function hashedAssetHref(rewrites, logical) {
|
|
301
|
+
if (!logical)
|
|
302
|
+
return null;
|
|
303
|
+
const raw = String(logical).replace(/\\/g, '/');
|
|
304
|
+
const key = raw.startsWith('/') ? raw : `/${raw.replace(/^\.\//, '')}`;
|
|
305
|
+
const hit = rewrites[key] || rewrites[key.slice(1)];
|
|
306
|
+
if (!hit)
|
|
307
|
+
return key.startsWith('/') ? key : `/${key}`;
|
|
308
|
+
return hit.startsWith('/') ? hit : `/${hit}`;
|
|
341
309
|
}
|
|
342
310
|
export function contentAddressedAssetsDigest(manifest) {
|
|
343
311
|
return manifest.manifestDigest || sha256Hex(canonicalJson({ ...manifest, manifestDigest: undefined }));
|
|
@@ -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)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vmz github-actions` — monitor workflow runs via N-API → Rust `vmz-github` (octocrab).
|
|
3
|
+
*/
|
|
4
|
+
import type { Cli, ParsedOptions } from '@vmz/commander';
|
|
5
|
+
export declare function registerGithubActionsCommand(cli: Cli): void;
|
|
6
|
+
export declare function cmdGithubActions(args: ParsedOptions): number;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vmz github-actions` — monitor workflow runs via N-API → Rust `vmz-github` (octocrab).
|
|
3
|
+
*/
|
|
4
|
+
import { loadNative } from './index.js';
|
|
5
|
+
import { log } from './log.js';
|
|
6
|
+
import { emitPrettyJson } from './pretty-json.js';
|
|
7
|
+
export function registerGithubActionsCommand(cli) {
|
|
8
|
+
cli.command('github-actions|gha', 'cli.cmd.github-actions')
|
|
9
|
+
.option('--mode <mode>', 'cli.opt.gha.mode')
|
|
10
|
+
.option('--workflow <id>', 'cli.opt.gha.workflow')
|
|
11
|
+
.option('--run-id <id>', 'cli.opt.gha.run-id')
|
|
12
|
+
.option('--ref <sha>', 'cli.opt.gha.ref')
|
|
13
|
+
.option('--branch <name>', 'cli.opt.gha.branch')
|
|
14
|
+
.option('--owner <owner>', 'cli.opt.gha.owner')
|
|
15
|
+
.option('--repo <repo>', 'cli.opt.gha.repo')
|
|
16
|
+
.option('--token <token>', 'cli.opt.gha.token')
|
|
17
|
+
.option('--timeout-minutes <n>', 'cli.opt.gha.timeout-minutes')
|
|
18
|
+
.option('--interval-seconds <n>', 'cli.opt.gha.interval-seconds')
|
|
19
|
+
.option('--required-conclusion <c>', 'cli.opt.gha.required-conclusion')
|
|
20
|
+
.option('--json [file]', 'cli.opt.json')
|
|
21
|
+
.action((options) => cmdGithubActions(options));
|
|
22
|
+
}
|
|
23
|
+
export function cmdGithubActions(args) {
|
|
24
|
+
const native = loadNative();
|
|
25
|
+
if (typeof native.githubActionsMonitorJson !== 'function') {
|
|
26
|
+
log.error('githubActionsMonitorJson missing — rebuild native (`pnpm napi:build`)');
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
29
|
+
const modeRaw = String(args.mode ?? 'status')
|
|
30
|
+
.trim()
|
|
31
|
+
.toLowerCase();
|
|
32
|
+
if (modeRaw !== 'status' && modeRaw !== 'wait') {
|
|
33
|
+
log.errorId('cli.err.gha.mode', { mode: modeRaw });
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
const { owner, repo } = resolveOwnerRepo(args);
|
|
37
|
+
if (!owner || !repo) {
|
|
38
|
+
log.errorId('cli.err.gha.repo');
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
const token = (typeof args.token === 'string' && args.token.trim()) || process.env.GITHUB_TOKEN || process.env.GH_TOKEN || undefined;
|
|
42
|
+
const timeoutMinutes = positiveNumber(args['timeout-minutes'], 45);
|
|
43
|
+
const intervalSeconds = positiveNumber(args['interval-seconds'], 15);
|
|
44
|
+
const request = {
|
|
45
|
+
owner,
|
|
46
|
+
repo,
|
|
47
|
+
token: token || null,
|
|
48
|
+
mode: modeRaw,
|
|
49
|
+
workflow: strOrNull(args.workflow),
|
|
50
|
+
runId: parseRunId(args['run-id']),
|
|
51
|
+
headSha: strOrNull(args.ref),
|
|
52
|
+
branch: strOrNull(args.branch),
|
|
53
|
+
timeoutSecs: Math.round(timeoutMinutes * 60),
|
|
54
|
+
intervalSecs: Math.round(intervalSeconds),
|
|
55
|
+
requiredConclusion: String(args['required-conclusion'] ?? 'success'),
|
|
56
|
+
};
|
|
57
|
+
let resultJson;
|
|
58
|
+
try {
|
|
59
|
+
resultJson = native.githubActionsMonitorJson(JSON.stringify(request));
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
63
|
+
log.error(msg);
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
let result;
|
|
67
|
+
try {
|
|
68
|
+
result = JSON.parse(resultJson);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
log.error('githubActionsMonitorJson returned invalid JSON');
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
log.info(`github-actions: run_id=${result.runId} status=${result.status} conclusion=${result.conclusion ?? 'null'} html_url=${result.htmlUrl}`);
|
|
75
|
+
if (args.json !== undefined && args.json !== false) {
|
|
76
|
+
emitPrettyJson(typeof args.json === 'string' ? args.json : true, result);
|
|
77
|
+
}
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
function resolveOwnerRepo(args) {
|
|
81
|
+
let owner = typeof args.owner === 'string' ? args.owner.trim() : '';
|
|
82
|
+
let repo = typeof args.repo === 'string' ? args.repo.trim() : '';
|
|
83
|
+
if (!owner || !repo) {
|
|
84
|
+
const fromEnv = (process.env.GITHUB_REPOSITORY || '').trim();
|
|
85
|
+
const slash = fromEnv.indexOf('/');
|
|
86
|
+
if (slash > 0) {
|
|
87
|
+
if (!owner)
|
|
88
|
+
owner = fromEnv.slice(0, slash);
|
|
89
|
+
if (!repo)
|
|
90
|
+
repo = fromEnv.slice(slash + 1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!owner || !repo) {
|
|
94
|
+
const fallback = (process.env.VMZ_GITHUB_REPO || 'doki-land/vmz-framework').trim();
|
|
95
|
+
const slash = fallback.indexOf('/');
|
|
96
|
+
if (slash > 0) {
|
|
97
|
+
if (!owner)
|
|
98
|
+
owner = fallback.slice(0, slash);
|
|
99
|
+
if (!repo)
|
|
100
|
+
repo = fallback.slice(slash + 1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { owner, repo };
|
|
104
|
+
}
|
|
105
|
+
function strOrNull(v) {
|
|
106
|
+
if (typeof v !== 'string')
|
|
107
|
+
return null;
|
|
108
|
+
const t = v.trim();
|
|
109
|
+
return t ? t : null;
|
|
110
|
+
}
|
|
111
|
+
function parseRunId(v) {
|
|
112
|
+
if (v === undefined || v === null || v === true || v === false)
|
|
113
|
+
return null;
|
|
114
|
+
const n = Number(v);
|
|
115
|
+
return Number.isFinite(n) && n > 0 ? Math.trunc(n) : null;
|
|
116
|
+
}
|
|
117
|
+
function positiveNumber(v, fallback) {
|
|
118
|
+
const n = Number(v);
|
|
119
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
120
|
+
}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Bare npm/workspace imports are legal on the author surface (01/04).
|
|
5
5
|
* Browser ESM cannot resolve them. Pack materializes reachable package
|
|
6
|
-
* modules under `dist/vendor/<pkg>/…` and rewrites importers to relative paths
|
|
6
|
+
* modules under `dist/vendor/<pkg>/…` and rewrites importers to relative paths
|
|
7
|
+
* via oxc AST (N-API `rewriteModuleSpecifiers`) — no import-text regex.
|
|
7
8
|
*
|
|
8
9
|
* Not full oxc chunk-split/minify (`oxc-pending` remains for release minify).
|
|
9
10
|
*/
|
|
@@ -25,5 +26,6 @@ export declare function packClientBareImports(outDir: string, opts?: PackClientB
|
|
|
25
26
|
};
|
|
26
27
|
/** @param {string} js */
|
|
27
28
|
export declare function collectBareSpecs(js: any): unknown[];
|
|
28
|
-
|
|
29
|
+
/** oxc AST module specifier rewrite (`.ts`→`.js` + optional exact map). */
|
|
30
|
+
export declare function rewriteRelativeTsSpecs(js: any, exactMap?: any): any;
|
|
29
31
|
export {};
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Bare npm/workspace imports are legal on the author surface (01/04).
|
|
5
5
|
* Browser ESM cannot resolve them. Pack materializes reachable package
|
|
6
|
-
* modules under `dist/vendor/<pkg>/…` and rewrites importers to relative paths
|
|
6
|
+
* modules under `dist/vendor/<pkg>/…` and rewrites importers to relative paths
|
|
7
|
+
* via oxc AST (N-API `rewriteModuleSpecifiers`) — no import-text regex.
|
|
7
8
|
*
|
|
8
9
|
* Not full oxc chunk-split/minify (`oxc-pending` remains for release minify).
|
|
9
10
|
*/
|
|
@@ -11,6 +12,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSy
|
|
|
11
12
|
import path from 'node:path';
|
|
12
13
|
import { createRequire } from 'node:module';
|
|
13
14
|
import { resolvePackageRoot } from './packages.js';
|
|
15
|
+
import { requireNativeAddon } from './native-addon.js';
|
|
14
16
|
const VENDOR_DIR = 'vendor';
|
|
15
17
|
const SKIP_PREFIXES = ['node:', 'nodejs:', 'cloudflare:', 'data:', 'http:', 'https:', 'vmz:', '#'];
|
|
16
18
|
export function packClientBareImports(outDir, opts = {}) {
|
|
@@ -327,16 +329,22 @@ function transpileTs(source, filename) {
|
|
|
327
329
|
}
|
|
328
330
|
}
|
|
329
331
|
function rewriteTsExt(p) {
|
|
332
|
+
// Path basename only (output layout) — not ESM source rewrite.
|
|
330
333
|
return String(p)
|
|
331
334
|
.replace(/\.tsx$/i, '.js')
|
|
332
335
|
.replace(/\.ts$/i, '.js');
|
|
333
336
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
337
|
+
/** oxc AST module specifier rewrite (`.ts`→`.js` + optional exact map). */
|
|
338
|
+
export function rewriteRelativeTsSpecs(js, exactMap = null) {
|
|
339
|
+
const native = requireNativeAddon();
|
|
340
|
+
if (typeof native.rewriteModuleSpecifiers !== 'function') {
|
|
341
|
+
throw new Error('native missing rewriteModuleSpecifiers — run `pnpm napi:build`');
|
|
342
|
+
}
|
|
343
|
+
const rules = {
|
|
344
|
+
tsExtToJs: true,
|
|
345
|
+
exact: exactMap && typeof exactMap === 'object' ? exactMap : {},
|
|
346
|
+
};
|
|
347
|
+
return native.rewriteModuleSpecifiers(String(js ?? ''), JSON.stringify(rules));
|
|
340
348
|
}
|
|
341
349
|
function resolveRelativeSource(fromDir, rel) {
|
|
342
350
|
const cleaned = rel.replace(/\?.*$/, '').replace(/#.*$/, '');
|
|
@@ -361,33 +369,15 @@ function resolveRelativeSource(fromDir, rel) {
|
|
|
361
369
|
return null;
|
|
362
370
|
}
|
|
363
371
|
function rewriteBareImports(js, fromFile, bareToVendor) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
372
|
+
/** @type {Record<string, string>} */
|
|
373
|
+
const exact = {};
|
|
374
|
+
for (const [spec, dest] of bareToVendor.entries()) {
|
|
375
|
+
if (isRelativeOrAbsolute(spec) || shouldSkipBare(spec))
|
|
376
|
+
continue;
|
|
368
377
|
let rel = path.relative(path.dirname(fromFile), dest).replace(/\\/g, '/');
|
|
369
378
|
if (!rel.startsWith('.'))
|
|
370
379
|
rel = `./${rel}`;
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
out = out.replace(/\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g, (full, quote, spec) => {
|
|
375
|
-
if (isRelativeOrAbsolute(spec) || shouldSkipBare(spec))
|
|
376
|
-
return full;
|
|
377
|
-
const rel = toRel(spec);
|
|
378
|
-
return rel ? `import(${quote}${rel}${quote})` : full;
|
|
379
|
-
});
|
|
380
|
-
out = out.replace(/\bfrom\s+(['"])([^'"]+)\1/g, (full, quote, spec) => {
|
|
381
|
-
if (isRelativeOrAbsolute(spec) || shouldSkipBare(spec))
|
|
382
|
-
return full;
|
|
383
|
-
const rel = toRel(spec);
|
|
384
|
-
return rel ? `from ${quote}${rel}${quote}` : full;
|
|
385
|
-
});
|
|
386
|
-
out = out.replace(/\bimport\s+(['"])([^'"]+)\1/g, (full, quote, spec) => {
|
|
387
|
-
if (isRelativeOrAbsolute(spec) || shouldSkipBare(spec))
|
|
388
|
-
return full;
|
|
389
|
-
const rel = toRel(spec);
|
|
390
|
-
return rel ? `import ${quote}${rel}${quote}` : full;
|
|
391
|
-
});
|
|
392
|
-
return out;
|
|
380
|
+
exact[spec] = rel;
|
|
381
|
+
}
|
|
382
|
+
return rewriteRelativeTsSpecs(js, exact);
|
|
393
383
|
}
|
package/dist/static-emit.js
CHANGED
|
@@ -10,7 +10,7 @@ import { createRenderHost } from '@vmz/core/render-host';
|
|
|
10
10
|
import { resolveRouteLayoutChain } from '@vmz/core/route-layout-chain';
|
|
11
11
|
import { listClientComponentsSync } from '@vmz/core/component-registry';
|
|
12
12
|
import { emitCdnPolicy } from './cdn-policy.js';
|
|
13
|
-
import { emitContentAddressedAssets } from './content-addressed-assets.js';
|
|
13
|
+
import { emitContentAddressedAssets, hashedAssetHref } from './content-addressed-assets.js';
|
|
14
14
|
import { absoluteUrl, buildLocalePageMeta, localizeBodyLinks } from './locale-router.js';
|
|
15
15
|
import { requireNativeAddon } from './native-addon.js';
|
|
16
16
|
import { writePrettyJsonFile } from './pretty-json.js';
|
|
@@ -33,6 +33,13 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
33
33
|
const pageCatalog = listPageClientFiles(distDir);
|
|
34
34
|
const generations = [];
|
|
35
35
|
const skipped = [];
|
|
36
|
+
// Hash immutable assets before any HTML write so shell slots carry final URLs
|
|
37
|
+
// (no post-emit HTML split/join rewrite).
|
|
38
|
+
emitStaticClientEntries(distDir, pageCatalog);
|
|
39
|
+
const assets = emitContentAddressedAssets(distDir);
|
|
40
|
+
const cssLogical = readCssEntry(distDir);
|
|
41
|
+
const cssEntry = hashedAssetHref(assets.rewrites, cssLogical) || hashedAssetHref(assets.rewrites, 'vmz.css');
|
|
42
|
+
const moduleScriptSrc = hashedAssetHref(assets.rewrites, 'entry-client.js') || '/entry-client.js';
|
|
36
43
|
for (const page of pageCatalog) {
|
|
37
44
|
const pattern = page.pathPattern || patternFromSegs(page.segs);
|
|
38
45
|
const routeId = guessRouteId(distDir, page.chunkId);
|
|
@@ -118,7 +125,8 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
118
125
|
layoutChain,
|
|
119
126
|
props,
|
|
120
127
|
meta: gen.meta,
|
|
121
|
-
cssEntry
|
|
128
|
+
cssEntry,
|
|
129
|
+
moduleScriptSrc,
|
|
122
130
|
headExtraHtml: faviconHead,
|
|
123
131
|
});
|
|
124
132
|
fs.writeFileSync(absHtml, html, 'utf8');
|
|
@@ -149,7 +157,8 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
149
157
|
robots: 'noindex,nofollow',
|
|
150
158
|
lang: 'en',
|
|
151
159
|
},
|
|
152
|
-
cssEntry
|
|
160
|
+
cssEntry,
|
|
161
|
+
moduleScriptSrc: '',
|
|
153
162
|
isErrorDocument: true,
|
|
154
163
|
headExtraHtml: faviconHead,
|
|
155
164
|
});
|
|
@@ -208,22 +217,16 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
208
217
|
fileCount: publicAssets.fileCount ?? 0,
|
|
209
218
|
source: 'source' in publicAssets && typeof publicAssets.source === 'string' ? publicAssets.source : null,
|
|
210
219
|
},
|
|
220
|
+
contentAddressedAssets: {
|
|
221
|
+
schema: assets.manifest.schema,
|
|
222
|
+
manifestDigest: assets.manifest.manifestDigest,
|
|
223
|
+
objectCount: assets.manifest.objectCount,
|
|
224
|
+
layout: assets.manifest.layout,
|
|
225
|
+
},
|
|
211
226
|
};
|
|
212
227
|
const digest = sha256Hex(canonicalJson(manifest));
|
|
213
228
|
manifest.manifestDigest = digest;
|
|
214
229
|
writePrettyJsonFile(path.join(vmzDir, 'static-delivery-manifest.json'), manifest);
|
|
215
|
-
emitStaticClientEntries(distDir, pageCatalog);
|
|
216
|
-
const assets = emitContentAddressedAssets(distDir);
|
|
217
|
-
manifest.contentAddressedAssets = {
|
|
218
|
-
schema: assets.manifest.schema,
|
|
219
|
-
manifestDigest: assets.manifest.manifestDigest,
|
|
220
|
-
objectCount: assets.manifest.objectCount,
|
|
221
|
-
layout: assets.manifest.layout,
|
|
222
|
-
};
|
|
223
|
-
// Re-stamp static manifest after linking asset digest (HTML already rewritten on disk).
|
|
224
|
-
delete manifest.manifestDigest;
|
|
225
|
-
manifest.manifestDigest = sha256Hex(canonicalJson(manifest));
|
|
226
|
-
writePrettyJsonFile(path.join(vmzDir, 'static-delivery-manifest.json'), manifest);
|
|
227
230
|
const cdn = emitCdnPolicy(distDir, manifest);
|
|
228
231
|
return {
|
|
229
232
|
manifest,
|
|
@@ -463,6 +466,7 @@ function readCssEntry(distDir) {
|
|
|
463
466
|
* props: Record<string, unknown>,
|
|
464
467
|
* meta: { title: string, description: string, canonical: string, robots: string, lang: string, dir?: string, alternates?: Array<{ hreflang: string, href: string }> },
|
|
465
468
|
* cssEntry: string | null,
|
|
469
|
+
* moduleScriptSrc?: string,
|
|
466
470
|
* isErrorDocument?: boolean,
|
|
467
471
|
* headExtraHtml?: string,
|
|
468
472
|
* }} input
|
|
@@ -493,6 +497,7 @@ function wrapDocument(input) {
|
|
|
493
497
|
...(input.cssEntry ? { cssEntry: String(input.cssEntry) } : {}),
|
|
494
498
|
isErrorDocument: !!input.isErrorDocument,
|
|
495
499
|
...(input.headExtraHtml ? { headExtraHtml: String(input.headExtraHtml) } : {}),
|
|
500
|
+
...(input.moduleScriptSrc !== undefined ? { moduleScriptSrc: String(input.moduleScriptSrc) } : {}),
|
|
496
501
|
});
|
|
497
502
|
}
|
|
498
503
|
/**
|
package/locales/en-US/cli.json
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"cli.cmd.refactor": "DX rename plans / apply",
|
|
44
44
|
"cli.cmd.refactor.rename": "Plan or apply a workspace rename",
|
|
45
45
|
"cli.cmd.explain": "DX causal explain",
|
|
46
|
+
"cli.cmd.github-actions": "Monitor GitHub Actions via Rust octocrab (status|wait)",
|
|
46
47
|
"cli.cmd.plan": "Dump frozen Rust plans via N-API",
|
|
47
48
|
"cli.cmd.plan.locale": "LocalePlan from locales/",
|
|
48
49
|
"cli.cmd.plan.document-route": "DocumentRoutePlan from documents/",
|
|
@@ -83,7 +84,20 @@
|
|
|
83
84
|
"cli.opt.apply": "Apply workspace edit plan",
|
|
84
85
|
"cli.opt.explain": "Include rename explain chain",
|
|
85
86
|
"cli.opt.explain.target": "Generic Workspace.explain target",
|
|
87
|
+
"cli.opt.gha.mode": "status (one-shot) or wait (poll until completed)",
|
|
88
|
+
"cli.opt.gha.workflow": "Workflow file name (e.g. ci.yml) or numeric id",
|
|
89
|
+
"cli.opt.gha.run-id": "Watch a specific workflow run id",
|
|
90
|
+
"cli.opt.gha.ref": "Commit SHA filter (head_sha)",
|
|
91
|
+
"cli.opt.gha.branch": "Branch name filter",
|
|
92
|
+
"cli.opt.gha.owner": "Repository owner (default GITHUB_REPOSITORY)",
|
|
93
|
+
"cli.opt.gha.repo": "Repository name (default GITHUB_REPOSITORY)",
|
|
94
|
+
"cli.opt.gha.token": "GitHub token (default GITHUB_TOKEN / GH_TOKEN)",
|
|
95
|
+
"cli.opt.gha.timeout-minutes": "Max wait minutes for mode=wait",
|
|
96
|
+
"cli.opt.gha.interval-seconds": "Poll interval seconds for mode=wait",
|
|
97
|
+
"cli.opt.gha.required-conclusion": "Required conclusion when mode=wait (default success)",
|
|
86
98
|
"cli.err.unknown_plan_kind": "unknown plan kind `{kind}` (locale | document-route)",
|
|
99
|
+
"cli.err.gha.mode": "unknown --mode {mode} (status | wait)",
|
|
100
|
+
"cli.err.gha.repo": "set --owner/--repo or GITHUB_REPOSITORY (owner/repo)",
|
|
87
101
|
"cli.err.unknown_target": "unknown --target {target} (browser | mini-program-wechat)",
|
|
88
102
|
"cli.err.document_build_aborted": "document build aborted due to diagnostics",
|
|
89
103
|
"cli.err.locale_rename_usage": "usage: vmz locale rename <from-message-id> <to-message-id>",
|
package/locales/en-US/diag.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"diag.message": "{message}",
|
|
3
3
|
"diag.detail": "{detail}",
|
|
4
4
|
|
|
5
|
-
"vmz::template::jsx_rejected": "JSX / single-brace expressions are rejected in `<template>`: {detail}",
|
|
6
5
|
"vmz::template::illegal_directive": "Illegal or unsupported directive in `<template>`: {detail}",
|
|
7
6
|
"vmz::template::parse_failed": "Failed to parse `<template>`: {detail}",
|
|
8
7
|
"vmz::template::invalid_expr": "Invalid template expression: {detail}",
|
package/locales/zh-CN/diag.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"diag.message": "{message}",
|
|
3
3
|
"diag.detail": "{detail}",
|
|
4
4
|
|
|
5
|
-
"vmz::template::jsx_rejected": "`<template>` 拒绝 JSX / 单花括号表达式:{detail}",
|
|
6
5
|
"vmz::template::illegal_directive": "`<template>` 中存在非法或不支持的指令:{detail}",
|
|
7
6
|
"vmz::template::parse_failed": "解析 `<template>` 失败:{detail}",
|
|
8
7
|
"vmz::template::invalid_expr": "模板表达式无效:{detail}",
|
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"
|