@vmz/vmz 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-assemble.js +4 -3
- package/dist/cdn-policy.js +5 -8
- package/dist/cli.js +20 -7
- package/dist/content-addressed-assets.js +2 -1
- package/dist/delivery-profile.d.ts +10 -0
- package/dist/delivery-profile.js +69 -0
- package/dist/dev-session.d.ts +6 -0
- package/dist/dev-session.js +166 -40
- package/dist/document-build.js +24 -28
- package/dist/document-cmd.js +2 -9
- package/dist/document-integrate.js +10 -17
- package/dist/embedded-packaging.js +3 -7
- package/dist/index.d.ts +19 -1
- package/dist/index.js +62 -2
- package/dist/locale-check.js +28 -58
- package/dist/locale-cmd.js +10 -42
- package/dist/locale-route-emit.d.ts +4 -1
- package/dist/locale-route-emit.js +7 -32
- package/dist/mini-host.d.ts +47 -0
- package/dist/mini-host.js +202 -0
- package/dist/native-addon.d.ts +9 -0
- package/dist/native-addon.js +84 -0
- package/dist/pack-client-packages.d.ts +25 -0
- package/dist/pack-client-packages.js +399 -0
- package/dist/pack.d.ts +21 -3
- package/dist/pack.js +21 -6
- package/dist/pretty-json.d.ts +19 -0
- package/dist/pretty-json.js +43 -0
- package/dist/production-observability.js +3 -2
- package/dist/production-test-pack.js +4 -3
- package/dist/release-pack.js +5 -18
- package/dist/route-path.d.ts +35 -0
- package/dist/route-path.js +77 -0
- package/dist/server-artifact.js +5 -6
- package/dist/site-delivery.js +3 -2
- package/dist/static-emit.js +67 -86
- package/dist/test-cmd.js +2 -1
- package/dist/wechat-packaging.d.ts +22 -0
- package/dist/wechat-packaging.js +59 -0
- package/package.json +12 -12
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser HTTP path projection from Route Graph / `vmz-deployment.json`.
|
|
3
|
+
* Mini pack ignores this and lowers RouteId → chunk id → page stem.
|
|
4
|
+
*/
|
|
5
|
+
export declare function isRouteBoundaryStem(stem: string): boolean;
|
|
6
|
+
export declare function isRouteGroupDir(seg: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* File-route fallback (`pages/home` → `/home`, `pages/index` → `/`).
|
|
9
|
+
* Used only when a deployment unit has no `pathPattern`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function filePathPatternFromChunk(chunkId: string): string;
|
|
12
|
+
export type DeploymentPageUnit = {
|
|
13
|
+
kind?: string;
|
|
14
|
+
chunkId?: string;
|
|
15
|
+
clientEntry?: string;
|
|
16
|
+
programIr?: string;
|
|
17
|
+
pathPattern?: string;
|
|
18
|
+
routeId?: string;
|
|
19
|
+
};
|
|
20
|
+
/** Canonical Browser HTTP pattern for a page unit. Mini must not read this. */
|
|
21
|
+
export declare function unitBrowserPathPattern(unit: DeploymentPageUnit | null | undefined): string;
|
|
22
|
+
export type PathSeg = {
|
|
23
|
+
kind: 'static';
|
|
24
|
+
value: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'param';
|
|
27
|
+
name: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: 'catch';
|
|
30
|
+
name: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function parsePathPattern(pattern: string): PathSeg[];
|
|
33
|
+
export declare function listPublicPageUnits(deployment: {
|
|
34
|
+
units?: DeploymentPageUnit[];
|
|
35
|
+
} | null | undefined): DeploymentPageUnit[];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser HTTP path projection from Route Graph / `vmz-deployment.json`.
|
|
3
|
+
* Mini pack ignores this and lowers RouteId → chunk id → page stem.
|
|
4
|
+
*/
|
|
5
|
+
export function isRouteBoundaryStem(stem) {
|
|
6
|
+
return stem === 'Layout' || stem === 'Loading' || stem === 'Error' || stem === 'NotFound';
|
|
7
|
+
}
|
|
8
|
+
export function isRouteGroupDir(seg) {
|
|
9
|
+
return typeof seg === 'string' && seg.startsWith('(') && seg.endsWith(')') && seg.length > 2;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* File-route fallback (`pages/home` → `/home`, `pages/index` → `/`).
|
|
13
|
+
* Used only when a deployment unit has no `pathPattern`.
|
|
14
|
+
*/
|
|
15
|
+
export function filePathPatternFromChunk(chunkId) {
|
|
16
|
+
const rel = String(chunkId || '').replace(/^pages\//, '');
|
|
17
|
+
const parts = rel.split('/').filter(Boolean);
|
|
18
|
+
const segs = [];
|
|
19
|
+
for (let i = 0; i < parts.length; i++) {
|
|
20
|
+
const p = parts[i];
|
|
21
|
+
if (isRouteGroupDir(p))
|
|
22
|
+
continue;
|
|
23
|
+
if (p === 'index' && i === parts.length - 1)
|
|
24
|
+
continue;
|
|
25
|
+
if (isRouteBoundaryStem(p))
|
|
26
|
+
continue;
|
|
27
|
+
segs.push(p);
|
|
28
|
+
}
|
|
29
|
+
return segs.length ? `/${segs.join('/')}` : '/';
|
|
30
|
+
}
|
|
31
|
+
/** Canonical Browser HTTP pattern for a page unit. Mini must not read this. */
|
|
32
|
+
export function unitBrowserPathPattern(unit) {
|
|
33
|
+
const explicit = String(unit?.pathPattern || '').trim();
|
|
34
|
+
if (explicit)
|
|
35
|
+
return explicit.startsWith('/') ? explicit : `/${explicit}`;
|
|
36
|
+
return filePathPatternFromChunk(String(unit?.chunkId || ''));
|
|
37
|
+
}
|
|
38
|
+
export function parsePathPattern(pattern) {
|
|
39
|
+
const raw = String(pattern || '').trim();
|
|
40
|
+
if (!raw || raw === '/')
|
|
41
|
+
return [];
|
|
42
|
+
const parts = raw.replace(/^\/+/, '').replace(/\/+$/, '').split('/').filter(Boolean);
|
|
43
|
+
const segs = [];
|
|
44
|
+
for (const p of parts) {
|
|
45
|
+
if (isRouteGroupDir(p))
|
|
46
|
+
continue;
|
|
47
|
+
segs.push(parsePathSegment(p));
|
|
48
|
+
}
|
|
49
|
+
return segs;
|
|
50
|
+
}
|
|
51
|
+
function parsePathSegment(p) {
|
|
52
|
+
const catchAll = /^\[\.\.\.([^\]]+)\]$/.exec(p);
|
|
53
|
+
const star = /^\*([A-Za-z_][\w]*)$/.exec(p);
|
|
54
|
+
const param = /^\[([^\]]+)\]$/.exec(p);
|
|
55
|
+
const colon = /^:([A-Za-z_][\w]*)$/.exec(p);
|
|
56
|
+
if (catchAll)
|
|
57
|
+
return { kind: 'catch', name: catchAll[1] };
|
|
58
|
+
if (star)
|
|
59
|
+
return { kind: 'catch', name: star[1] };
|
|
60
|
+
if (param)
|
|
61
|
+
return { kind: 'param', name: param[1] };
|
|
62
|
+
if (colon)
|
|
63
|
+
return { kind: 'param', name: colon[1] };
|
|
64
|
+
return { kind: 'static', value: p.toLowerCase() };
|
|
65
|
+
}
|
|
66
|
+
export function listPublicPageUnits(deployment) {
|
|
67
|
+
const units = Array.isArray(deployment?.units) ? deployment.units : [];
|
|
68
|
+
return units.filter((u) => {
|
|
69
|
+
if (u?.kind !== 'page')
|
|
70
|
+
return false;
|
|
71
|
+
const chunkId = String(u.chunkId || '').replace(/\\/g, '/');
|
|
72
|
+
if (!chunkId.startsWith('pages/'))
|
|
73
|
+
return false;
|
|
74
|
+
const stem = chunkId.split('/').pop() || '';
|
|
75
|
+
return !isRouteBoundaryStem(stem);
|
|
76
|
+
});
|
|
77
|
+
}
|
package/dist/server-artifact.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
// @ts-nocheck
|
|
6
6
|
import crypto from 'node:crypto';
|
|
7
|
-
import { existsSync, mkdirSync, readFileSync
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { SERVER_RUNTIMES } from './delivery-profile.js';
|
|
10
|
+
import { writePrettyJsonFile } from './pretty-json.js';
|
|
10
11
|
export const SERVER_ARTIFACT_SCHEMA = 'vmz.server.artifact.v0';
|
|
11
12
|
export const HTTP_CONTRACT_SCHEMA = 'vmz.http.contract.v0';
|
|
12
13
|
export const SERVER_RUNTIME_ADAPTER_SCHEMA = 'vmz.server.runtime_adapter.v0';
|
|
@@ -116,14 +117,14 @@ export function emitServerArtifact(outDir, opts = {}) {
|
|
|
116
117
|
const vmzDir = path.join(outDir, '_vmz');
|
|
117
118
|
mkdirSync(vmzDir, { recursive: true });
|
|
118
119
|
const file = path.join(vmzDir, 'server-artifact.json');
|
|
119
|
-
|
|
120
|
+
writePrettyJsonFile(file, artifact);
|
|
120
121
|
const adapterDir = path.join(vmzDir, 'adapters');
|
|
121
122
|
mkdirSync(adapterDir, { recursive: true });
|
|
122
123
|
for (const adapterId of ['worker', 'rust-host']) {
|
|
123
124
|
const projection = projectServerRuntimeAdapter(artifact, adapterId);
|
|
124
125
|
const dir = path.join(adapterDir, adapterId);
|
|
125
126
|
mkdirSync(dir, { recursive: true });
|
|
126
|
-
|
|
127
|
+
writePrettyJsonFile(path.join(dir, 'adapter.json'), projection);
|
|
127
128
|
}
|
|
128
129
|
return { artifact, path: file, httpContractDigest };
|
|
129
130
|
}
|
|
@@ -144,9 +145,7 @@ export function projectServerRuntimeAdapter(artifact, adapterId) {
|
|
|
144
145
|
spaFallback: false,
|
|
145
146
|
entry: artifact.entry,
|
|
146
147
|
publicRouteCount: Array.isArray(artifact.publicRoutes) ? artifact.publicRoutes.length : 0,
|
|
147
|
-
internalCapabilityCount: Array.isArray(artifact.internalCapabilities)
|
|
148
|
-
? artifact.internalCapabilities.length
|
|
149
|
-
: 0,
|
|
148
|
+
internalCapabilityCount: Array.isArray(artifact.internalCapabilities) ? artifact.internalCapabilities.length : 0,
|
|
150
149
|
};
|
|
151
150
|
if (id === 'node') {
|
|
152
151
|
return { ...base, host: 'node:http', invoke: 'handleNodeRequest', status: 'runtime' };
|
package/dist/site-delivery.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import crypto from 'node:crypto';
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
+
import { writePrettyJsonFile } from './pretty-json.js';
|
|
9
10
|
export const SITE_DELIVERY_CONTRACT_SCHEMA = 'vmz.site.delivery_contract.v0';
|
|
10
11
|
export const SITE_DELIVERY_RESOLUTION_SCHEMA = 'vmz.site.delivery_resolution.v0';
|
|
11
12
|
/**
|
|
@@ -318,11 +319,11 @@ export function emitSiteDelivery(outDir, deliveryRaw, opts = {}) {
|
|
|
318
319
|
}
|
|
319
320
|
const vmzDir = path.join(outDir, '_vmz');
|
|
320
321
|
fs.mkdirSync(vmzDir, { recursive: true });
|
|
321
|
-
|
|
322
|
+
writePrettyJsonFile(path.join(vmzDir, 'site-delivery-contract.json'), norm.contract);
|
|
322
323
|
let resolution = null;
|
|
323
324
|
if (opts.probes) {
|
|
324
325
|
resolution = resolveSiteRelease(norm.contract, opts.probes);
|
|
325
|
-
|
|
326
|
+
writePrettyJsonFile(path.join(vmzDir, 'site-delivery-resolution.json'), resolution);
|
|
326
327
|
}
|
|
327
328
|
return { contract: norm.contract, resolution };
|
|
328
329
|
}
|
package/dist/static-emit.js
CHANGED
|
@@ -10,6 +10,9 @@ import { pathToFileURL } from 'node:url';
|
|
|
10
10
|
import { emitCdnPolicy } from './cdn-policy.js';
|
|
11
11
|
import { emitContentAddressedAssets } from './content-addressed-assets.js';
|
|
12
12
|
import { absoluteUrl, buildLocalePageMeta, localizeBodyLinks } from './locale-router.js';
|
|
13
|
+
import { requireNativeAddon } from './native-addon.js';
|
|
14
|
+
import { writePrettyJsonFile } from './pretty-json.js';
|
|
15
|
+
import { filePathPatternFromChunk, isRouteBoundaryStem, listPublicPageUnits, parsePathPattern, unitBrowserPathPattern, } from './route-path.js';
|
|
13
16
|
export const STATIC_DELIVERY_MANIFEST_SCHEMA = 'vmz.static.delivery_manifest.v0';
|
|
14
17
|
/**
|
|
15
18
|
* @param {string} distDir
|
|
@@ -43,7 +46,7 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
43
46
|
/** @type {Array<{ routeId: string, path: string, chunkId: string, classification: string, reason: string }>} */
|
|
44
47
|
const skipped = [];
|
|
45
48
|
for (const page of pageCatalog) {
|
|
46
|
-
const pattern = patternFromSegs(page.segs);
|
|
49
|
+
const pattern = page.pathPattern || patternFromSegs(page.segs);
|
|
47
50
|
const routeId = guessRouteId(distDir, page.chunkId);
|
|
48
51
|
if (page.segs.some((s) => s.kind === 'param' || s.kind === 'catch')) {
|
|
49
52
|
skipped.push({
|
|
@@ -119,9 +122,7 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
119
122
|
const absHtml = path.join(distDir, gen.htmlPath);
|
|
120
123
|
fs.mkdirSync(path.dirname(absHtml), { recursive: true });
|
|
121
124
|
// Each LocaleId HTML must retain locale on same-app Links (realization authority).
|
|
122
|
-
const localizedBody = gen.localeId && localeArt
|
|
123
|
-
? localizeBodyLinks(bodyHtml, gen.localeId, localeArt)
|
|
124
|
-
: bodyHtml;
|
|
125
|
+
const localizedBody = gen.localeId && localeArt ? localizeBodyLinks(bodyHtml, gen.localeId, localeArt) : bodyHtml;
|
|
125
126
|
const html = wrapDocument({
|
|
126
127
|
bodyHtml: localizedBody,
|
|
127
128
|
chunkId: page.chunkId,
|
|
@@ -164,7 +165,11 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
164
165
|
fs.writeFileSync(path.join(distDir, '404.html'), notFoundHtml, 'utf8');
|
|
165
166
|
const sitemap = buildSitemap(origin, generations);
|
|
166
167
|
fs.writeFileSync(path.join(distDir, 'sitemap.xml'), sitemap, 'utf8');
|
|
167
|
-
const
|
|
168
|
+
const nativeRobots = requireNativeAddon();
|
|
169
|
+
if (typeof nativeRobots.generateRobotsTxt !== 'function') {
|
|
170
|
+
throw new Error('vmz native addon missing generateRobotsTxt — rebuild with `pnpm napi:build`');
|
|
171
|
+
}
|
|
172
|
+
const robots = nativeRobots.generateRobotsTxt(`${origin}/sitemap.xml`);
|
|
168
173
|
fs.writeFileSync(path.join(distDir, 'robots.txt'), robots, 'utf8');
|
|
169
174
|
// Hard rule: no SPA fallback shim in artifact.
|
|
170
175
|
for (const bad of ['_redirects', 'vercel.json', 'netlify.toml']) {
|
|
@@ -209,7 +214,7 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
209
214
|
};
|
|
210
215
|
const digest = sha256Hex(canonicalJson(manifest));
|
|
211
216
|
manifest.manifestDigest = digest;
|
|
212
|
-
|
|
217
|
+
writePrettyJsonFile(path.join(vmzDir, 'static-delivery-manifest.json'), manifest);
|
|
213
218
|
const assets = emitContentAddressedAssets(distDir);
|
|
214
219
|
manifest.contentAddressedAssets = {
|
|
215
220
|
schema: assets.manifest.schema,
|
|
@@ -220,7 +225,7 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
220
225
|
// Re-stamp static manifest after linking asset digest (HTML already rewritten on disk).
|
|
221
226
|
delete manifest.manifestDigest;
|
|
222
227
|
manifest.manifestDigest = sha256Hex(canonicalJson(manifest));
|
|
223
|
-
|
|
228
|
+
writePrettyJsonFile(path.join(vmzDir, 'static-delivery-manifest.json'), manifest);
|
|
224
229
|
const cdn = emitCdnPolicy(distDir, manifest);
|
|
225
230
|
return {
|
|
226
231
|
manifest,
|
|
@@ -260,8 +265,11 @@ function sortKeys(value) {
|
|
|
260
265
|
* @param {string} distDir
|
|
261
266
|
*/
|
|
262
267
|
function listPageClientFiles(distDir) {
|
|
268
|
+
const fromDep = listPagesFromDeployment(distDir);
|
|
269
|
+
if (fromDep.length)
|
|
270
|
+
return fromDep;
|
|
263
271
|
const root = path.join(distDir, 'pages');
|
|
264
|
-
/** @type {Array<{ chunkId: string, segs: ReturnType<typeof
|
|
272
|
+
/** @type {Array<{ chunkId: string, segs: ReturnType<typeof parsePathPattern>, pathPattern: string }>} */
|
|
265
273
|
const out = [];
|
|
266
274
|
function walk(abs, relParts) {
|
|
267
275
|
let ents;
|
|
@@ -276,10 +284,11 @@ function listPageClientFiles(distDir) {
|
|
|
276
284
|
walk(path.join(abs, e.name), [...relParts, e.name]);
|
|
277
285
|
else if (e.isFile() && e.name.endsWith('.client.js')) {
|
|
278
286
|
const stem = e.name.replace(/\.client\.js$/, '');
|
|
279
|
-
if (stem
|
|
287
|
+
if (isRouteBoundaryStem(stem))
|
|
280
288
|
continue;
|
|
281
289
|
const chunkId = ['pages', ...relParts, stem].join('/');
|
|
282
|
-
|
|
290
|
+
const pathPattern = filePathPatternFromChunk(chunkId);
|
|
291
|
+
out.push({ chunkId, pathPattern, segs: parsePathPattern(pathPattern) });
|
|
283
292
|
}
|
|
284
293
|
}
|
|
285
294
|
}
|
|
@@ -287,32 +296,26 @@ function listPageClientFiles(distDir) {
|
|
|
287
296
|
return out;
|
|
288
297
|
}
|
|
289
298
|
/**
|
|
290
|
-
* @param {string}
|
|
299
|
+
* @param {string} distDir
|
|
291
300
|
*/
|
|
292
|
-
function
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
segs.push({ kind: 'catch', name: catchAll[1] });
|
|
307
|
-
else if (param)
|
|
308
|
-
segs.push({ kind: 'param', name: param[1] });
|
|
309
|
-
else
|
|
310
|
-
segs.push({ kind: 'static', value: p.toLowerCase() });
|
|
301
|
+
function listPagesFromDeployment(distDir) {
|
|
302
|
+
const deploymentPath = path.join(distDir, 'vmz-deployment.json');
|
|
303
|
+
if (!fs.existsSync(deploymentPath))
|
|
304
|
+
return [];
|
|
305
|
+
try {
|
|
306
|
+
const deployment = JSON.parse(fs.readFileSync(deploymentPath, 'utf8'));
|
|
307
|
+
return listPublicPageUnits(deployment).map((u) => {
|
|
308
|
+
const chunkId = String(u.chunkId || '').replace(/\\/g, '/');
|
|
309
|
+
const pathPattern = unitBrowserPathPattern(u);
|
|
310
|
+
return { chunkId, pathPattern, segs: parsePathPattern(pathPattern) };
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return [];
|
|
311
315
|
}
|
|
312
|
-
return segs;
|
|
313
316
|
}
|
|
314
317
|
/**
|
|
315
|
-
* @param {ReturnType<typeof
|
|
318
|
+
* @param {ReturnType<typeof parsePathPattern>} segs
|
|
316
319
|
*/
|
|
317
320
|
function patternFromSegs(segs) {
|
|
318
321
|
if (!segs.length)
|
|
@@ -391,8 +394,8 @@ async function resolvePageMeta(Page, ctx) {
|
|
|
391
394
|
else if (Page.meta && typeof Page.meta === 'object') {
|
|
392
395
|
raw = Page.meta;
|
|
393
396
|
}
|
|
394
|
-
const title = String(raw.title ||
|
|
395
|
-
const description = String(raw.description ||
|
|
397
|
+
const title = String(raw.title || guessTitle(ctx.pathname) || 'App');
|
|
398
|
+
const description = String(raw.description || '');
|
|
396
399
|
const canonical = String(raw.canonical || `${ctx.origin}${ctx.pathname === '/' ? '/' : ctx.pathname}`);
|
|
397
400
|
const robots = String(raw.robots || 'index,follow');
|
|
398
401
|
const lang = String(raw.lang || 'en');
|
|
@@ -438,10 +441,7 @@ function expandLocaleStaticGenerations(input) {
|
|
|
438
441
|
const locales = (localeArt.locales || []).map((l) => l.id);
|
|
439
442
|
const directions = Object.fromEntries((localeArt.locales || []).map((l) => [l.id, l.direction || 'ltr']));
|
|
440
443
|
const defaultLocale = localeArt.defaultLocale || locales[0];
|
|
441
|
-
const forRoute = (localeArt.realizations || []).filter((r) => r.routeId === routeId ||
|
|
442
|
-
r.routeId === input.chunkId ||
|
|
443
|
-
r.pathPattern === pattern ||
|
|
444
|
-
(r.path === pattern && !r.prefixed));
|
|
444
|
+
const forRoute = (localeArt.realizations || []).filter((r) => r.routeId === routeId || r.routeId === input.chunkId || r.pathPattern === pattern || (r.path === pattern && !r.prefixed));
|
|
445
445
|
/** @type {any[]} */
|
|
446
446
|
const out = [];
|
|
447
447
|
for (const loc of locales) {
|
|
@@ -519,59 +519,40 @@ function readCssEntry(distDir) {
|
|
|
519
519
|
*/
|
|
520
520
|
function wrapDocument(input) {
|
|
521
521
|
const propsJson = JSON.stringify(input.props ?? {});
|
|
522
|
-
const layoutAttr = input.layoutChain.length ? ` data-vmz-layout="${escapeAttr(input.layoutChain.join(','))}"` : '';
|
|
523
|
-
const pageAttr = input.chunkId ? ` data-vmz-page="${escapeAttr(input.chunkId)}"` : '';
|
|
524
522
|
const localeId = input.meta.lang || 'en';
|
|
525
523
|
const dir = input.meta.dir || 'ltr';
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
.
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
${entry}</body>
|
|
549
|
-
</html>
|
|
550
|
-
`;
|
|
524
|
+
const native = requireNativeAddon();
|
|
525
|
+
if (typeof native.generatePageShell !== 'function') {
|
|
526
|
+
throw new Error('vmz native addon missing generatePageShell — rebuild with `pnpm napi:build`');
|
|
527
|
+
}
|
|
528
|
+
return native.generatePageShell({
|
|
529
|
+
bodyHtml: input.bodyHtml,
|
|
530
|
+
chunkId: input.chunkId || '',
|
|
531
|
+
layoutChain: input.layoutChain || [],
|
|
532
|
+
propsJson,
|
|
533
|
+
meta: {
|
|
534
|
+
title: input.meta.title,
|
|
535
|
+
description: input.meta.description,
|
|
536
|
+
canonical: input.meta.canonical,
|
|
537
|
+
robots: input.meta.robots,
|
|
538
|
+
lang: localeId,
|
|
539
|
+
dir,
|
|
540
|
+
alternates: input.meta.alternates || [],
|
|
541
|
+
},
|
|
542
|
+
// napi Option<String>: omit/undefined = None; null is rejected as String
|
|
543
|
+
...(input.cssEntry ? { cssEntry: String(input.cssEntry) } : {}),
|
|
544
|
+
isErrorDocument: !!input.isErrorDocument,
|
|
545
|
+
});
|
|
551
546
|
}
|
|
552
547
|
/**
|
|
553
548
|
* @param {string} origin
|
|
554
549
|
* @param {Array<{ canonical: string, robots: string }>} generations
|
|
555
550
|
*/
|
|
556
|
-
function buildSitemap(
|
|
557
|
-
const urls = generations
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
564
|
-
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
565
|
-
${urls}
|
|
566
|
-
</urlset>
|
|
567
|
-
`;
|
|
568
|
-
}
|
|
569
|
-
function escapeHtml(s) {
|
|
570
|
-
return String(s).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
571
|
-
}
|
|
572
|
-
function escapeAttr(s) {
|
|
573
|
-
return escapeHtml(s).replaceAll('"', '"');
|
|
574
|
-
}
|
|
575
|
-
function escapeXml(s) {
|
|
576
|
-
return escapeAttr(s).replaceAll("'", ''');
|
|
551
|
+
function buildSitemap(_origin, generations) {
|
|
552
|
+
const urls = generations.filter((g) => !String(g.robots).includes('noindex')).map((g) => ({ loc: g.canonical }));
|
|
553
|
+
const native = requireNativeAddon();
|
|
554
|
+
if (typeof native.generateSitemapXml !== 'function') {
|
|
555
|
+
throw new Error('vmz native addon missing generateSitemapXml — rebuild with `pnpm napi:build`');
|
|
556
|
+
}
|
|
557
|
+
return native.generateSitemapXml(urls);
|
|
577
558
|
}
|
package/dist/test-cmd.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { createWorkspace } from './index.js';
|
|
8
8
|
import { log } from './log.js';
|
|
9
|
+
import { generatePrettyJson } from './pretty-json.js';
|
|
9
10
|
/**
|
|
10
11
|
* @returns {Promise<typeof import('@vmz/test')>}
|
|
11
12
|
*/
|
|
@@ -317,7 +318,7 @@ export async function cmdTest(args) {
|
|
|
317
318
|
return errors.length ? 2 : 0;
|
|
318
319
|
}
|
|
319
320
|
if (wantJson) {
|
|
320
|
-
const text = `${
|
|
321
|
+
const text = `${generatePrettyJson(report)}\n`;
|
|
321
322
|
if (typeof args.json === 'string' && args.json !== 'true') {
|
|
322
323
|
const { writeFileSync } = await import('node:fs');
|
|
323
324
|
writeFileSync(path.resolve(String(args.json)), text);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Materialize `defineConfig({ delivery: { packaging: { wechat } } })` for wechat_pack.
|
|
3
|
+
* Pure data only. Writes `dist/_vmz/wechat-packaging.json` (not a second config entry).
|
|
4
|
+
*/
|
|
5
|
+
export declare const WECHAT_PACKAGING_SCHEMA = "vmz.target.wechat_packaging.v0";
|
|
6
|
+
export declare const WECHAT_PACKAGING_REL = "dist/_vmz/wechat-packaging.json";
|
|
7
|
+
/**
|
|
8
|
+
* @param {unknown} delivery
|
|
9
|
+
* @returns {{ schema: string, appId: string, projectName?: string, title?: string }}
|
|
10
|
+
*/
|
|
11
|
+
export declare function wechatPackagingFromDelivery(delivery: any): {
|
|
12
|
+
schema: string;
|
|
13
|
+
appId: any;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Load `vmz.config.*` and write the WeChat packaging contract for the Rust packer.
|
|
17
|
+
* @param {string} project
|
|
18
|
+
*/
|
|
19
|
+
export declare function materializeWechatPackaging(project: any): {
|
|
20
|
+
schema: string;
|
|
21
|
+
appId: any;
|
|
22
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Materialize `defineConfig({ delivery: { packaging: { wechat } } })` for wechat_pack.
|
|
3
|
+
* Pure data only. Writes `dist/_vmz/wechat-packaging.json` (not a second config entry).
|
|
4
|
+
*/
|
|
5
|
+
// @ts-nocheck
|
|
6
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { createJiti } from 'jiti';
|
|
9
|
+
import { pickDeliveryPackaging } from './delivery-profile.js';
|
|
10
|
+
import { writePrettyJsonFile } from './pretty-json.js';
|
|
11
|
+
export const WECHAT_PACKAGING_SCHEMA = 'vmz.target.wechat_packaging.v0';
|
|
12
|
+
export const WECHAT_PACKAGING_REL = 'dist/_vmz/wechat-packaging.json';
|
|
13
|
+
const CONFIG_NAMES = ['vmz.config.ts', 'vmz.config.mts', 'vmz.config.mjs', 'vmz.config.js'];
|
|
14
|
+
function loadConfigSync(project) {
|
|
15
|
+
for (const name of CONFIG_NAMES) {
|
|
16
|
+
const full = path.join(project, name);
|
|
17
|
+
if (!existsSync(full))
|
|
18
|
+
continue;
|
|
19
|
+
const jiti = createJiti(import.meta.url, {
|
|
20
|
+
interopDefault: true,
|
|
21
|
+
moduleCache: false,
|
|
22
|
+
});
|
|
23
|
+
return jiti(full);
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* @param {unknown} delivery
|
|
29
|
+
* @returns {{ schema: string, appId: string, projectName?: string, title?: string }}
|
|
30
|
+
*/
|
|
31
|
+
export function wechatPackagingFromDelivery(delivery) {
|
|
32
|
+
const diagnostics = [];
|
|
33
|
+
const packaging = pickDeliveryPackaging(delivery && typeof delivery === 'object' ? delivery : {}, diagnostics);
|
|
34
|
+
const wechat = packaging && packaging.wechat ? packaging.wechat : {};
|
|
35
|
+
/** @type {{ schema: string, appId: string, projectName?: string, title?: string }} */
|
|
36
|
+
const out = {
|
|
37
|
+
schema: WECHAT_PACKAGING_SCHEMA,
|
|
38
|
+
appId: typeof wechat.appId === 'string' && wechat.appId.trim() ? wechat.appId.trim() : 'touristappid',
|
|
39
|
+
};
|
|
40
|
+
if (typeof wechat.projectName === 'string' && wechat.projectName.trim()) {
|
|
41
|
+
out.projectName = wechat.projectName.trim();
|
|
42
|
+
}
|
|
43
|
+
if (typeof wechat.title === 'string' && wechat.title.trim()) {
|
|
44
|
+
out.title = wechat.title.trim();
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Load `vmz.config.*` and write the WeChat packaging contract for the Rust packer.
|
|
50
|
+
* @param {string} project
|
|
51
|
+
*/
|
|
52
|
+
export function materializeWechatPackaging(project) {
|
|
53
|
+
const cfg = loadConfigSync(project);
|
|
54
|
+
const spec = wechatPackagingFromDelivery(cfg?.delivery);
|
|
55
|
+
const abs = path.join(project, WECHAT_PACKAGING_REL);
|
|
56
|
+
mkdirSync(path.dirname(abs), { recursive: true });
|
|
57
|
+
writePrettyJsonFile(abs, spec);
|
|
58
|
+
return spec;
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vmz/vmz",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "VMZ Node toolchain — N-API workspace session + CLI (publish name @vmz/vmz)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@vmz/core": "0.1.
|
|
52
|
-
"@vmz/plugin": "0.1.
|
|
53
|
-
"@vmz/protocol": "0.1.
|
|
51
|
+
"@vmz/core": "0.1.2",
|
|
52
|
+
"@vmz/plugin": "0.1.2",
|
|
53
|
+
"@vmz/protocol": "0.1.2",
|
|
54
54
|
"jiti": "^2.6.1",
|
|
55
55
|
"json5": "^2.2.3"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@vmz/plugin-markdown-it": "0.1.
|
|
59
|
-
"@vmz/test": "0.1.
|
|
58
|
+
"@vmz/plugin-markdown-it": "0.1.2",
|
|
59
|
+
"@vmz/test": "0.1.2",
|
|
60
60
|
"typescript": "^5.8.3"
|
|
61
61
|
},
|
|
62
62
|
"peerDependenciesMeta": {
|
|
@@ -90,12 +90,12 @@
|
|
|
90
90
|
"cli"
|
|
91
91
|
],
|
|
92
92
|
"optionalDependencies": {
|
|
93
|
-
"@vmz/vmz-win32-x64": "0.1.
|
|
94
|
-
"@vmz/vmz-win32-arm64": "0.1.
|
|
95
|
-
"@vmz/vmz-darwin-x64": "0.1.
|
|
96
|
-
"@vmz/vmz-darwin-arm64": "0.1.
|
|
97
|
-
"@vmz/vmz-linux-x64": "0.1.
|
|
98
|
-
"@vmz/vmz-linux-arm64": "0.1.
|
|
93
|
+
"@vmz/vmz-win32-x64": "0.1.2",
|
|
94
|
+
"@vmz/vmz-win32-arm64": "0.1.2",
|
|
95
|
+
"@vmz/vmz-darwin-x64": "0.1.2",
|
|
96
|
+
"@vmz/vmz-darwin-arm64": "0.1.2",
|
|
97
|
+
"@vmz/vmz-linux-x64": "0.1.2",
|
|
98
|
+
"@vmz/vmz-linux-arm64": "0.1.2"
|
|
99
99
|
},
|
|
100
100
|
"publishConfig": {
|
|
101
101
|
"access": "public"
|