@vmz/vmz 0.1.22 → 0.1.23
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/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,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.23",
|
|
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.23",
|
|
52
|
+
"@vmz/core": "0.1.23",
|
|
53
|
+
"@vmz/diagnostic": "0.1.23",
|
|
54
|
+
"@vmz/plugin": "0.1.23",
|
|
55
|
+
"@vmz/protocol": "0.1.23",
|
|
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.23",
|
|
60
|
+
"@vmz/test": "0.1.23",
|
|
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.23",
|
|
96
|
+
"@vmz/vmz-win32-arm64": "0.1.23",
|
|
97
|
+
"@vmz/vmz-darwin-x64": "0.1.23",
|
|
98
|
+
"@vmz/vmz-darwin-arm64": "0.1.23",
|
|
99
|
+
"@vmz/vmz-linux-x64": "0.1.23",
|
|
100
|
+
"@vmz/vmz-linux-arm64": "0.1.23"
|
|
101
101
|
},
|
|
102
102
|
"publishConfig": {
|
|
103
103
|
"access": "public"
|