@vmz/vmz 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -13
- package/dist/application-cmd.d.ts +1 -2
- package/dist/application-cmd.js +9 -10
- package/dist/bundler-adapter.d.ts +2 -3
- package/dist/bundler-adapter.js +2 -3
- package/dist/cdn-policy.d.ts +178 -0
- package/dist/cdn-policy.js +344 -0
- package/dist/cli.d.ts +10 -2
- package/dist/cli.js +179 -16
- package/dist/content-addressed-assets.d.ts +69 -0
- package/dist/content-addressed-assets.js +206 -0
- package/dist/dev-session.d.ts +2 -2
- package/dist/dev-session.js +51 -16
- package/dist/document-build.js +1 -2
- package/dist/document-check.d.ts +1 -1
- package/dist/document-check.js +4 -4
- package/dist/document-cmd.d.ts +1 -2
- package/dist/document-cmd.js +2 -3
- package/dist/document-designs.js +31 -3
- package/dist/document-enrich.js +2 -3
- package/dist/document-evidence.d.ts +4 -4
- package/dist/document-evidence.js +20 -12
- package/dist/document-integrate.d.ts +0 -1
- package/dist/document-integrate.js +0 -1
- package/dist/document-interactive.d.ts +11 -11
- package/dist/document-interactive.js +12 -13
- package/dist/document-locale.d.ts +1 -1
- package/dist/document-locale.js +1 -1
- package/dist/document-markdown.d.ts +1 -2
- package/dist/document-markdown.js +13 -7
- package/dist/document-scan.d.ts +4 -4
- package/dist/document-scan.js +4 -4
- package/dist/document-schema.d.ts +28 -29
- package/dist/document-schema.js +28 -29
- package/dist/explain-cmd.js +3 -3
- package/dist/index.d.ts +349 -789
- package/dist/index.js +109 -85
- package/dist/invocation.d.ts +68 -0
- package/dist/invocation.js +169 -0
- package/dist/locale-check.d.ts +19 -3
- package/dist/locale-check.js +130 -6
- package/dist/locale-cmd.js +6 -7
- package/dist/locale-delivery.d.ts +38 -38
- package/dist/locale-delivery.js +39 -40
- package/dist/locale-router.d.ts +39 -40
- package/dist/locale-router.js +39 -40
- package/dist/locale-runtime.d.ts +39 -39
- package/dist/locale-runtime.js +44 -45
- package/dist/locale-schema.d.ts +1 -2
- package/dist/locale-schema.js +1 -2
- package/dist/locale-tooling.d.ts +13 -13
- package/dist/locale-tooling.js +14 -15
- package/dist/log.d.ts +1 -1
- package/dist/log.js +1 -1
- package/dist/packages.d.ts +1 -2
- package/dist/packages.js +1 -2
- package/dist/plugin-host.d.ts +11 -3
- package/dist/plugin-host.js +21 -5
- package/dist/port.d.ts +10 -0
- package/dist/port.js +46 -0
- package/dist/production-observability.d.ts +286 -0
- package/dist/production-observability.js +469 -0
- package/dist/production-test-pack.d.ts +158 -0
- package/dist/production-test-pack.js +452 -0
- package/dist/refactor-cmd.d.ts +1 -1
- package/dist/refactor-cmd.js +6 -6
- package/dist/release-cmd.d.ts +8 -0
- package/dist/release-cmd.js +126 -0
- package/dist/release-pack.d.ts +96 -0
- package/dist/release-pack.js +337 -0
- package/dist/resolve-native-cli.d.ts +14 -0
- package/dist/resolve-native-cli.js +84 -0
- package/dist/resolve.d.ts +1 -2
- package/dist/resolve.js +1 -2
- package/dist/site-delivery.d.ts +134 -0
- package/dist/site-delivery.js +345 -0
- package/dist/static-emit.d.ts +136 -0
- package/dist/static-emit.js +463 -0
- package/dist/test-cmd.d.ts +2 -2
- package/dist/test-cmd.js +37 -17
- package/dist/watch-diff.d.ts +1 -1
- package/dist/watch-diff.js +1 -1
- package/package.json +32 -17
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A3 filesystem release packaging — digests, atomic pointer, rollback, artifact diff.
|
|
3
|
+
*
|
|
4
|
+
* Not the full CDN/StaticDelivery matrix. Same VPG build (`dist/`) is packed into
|
|
5
|
+
* `_vmz/*` manifests + content digests; publish retains previous release for rollback
|
|
6
|
+
* without rebuild.
|
|
7
|
+
*/
|
|
8
|
+
export declare const RELEASE_ENVELOPE_SCHEMA = "vmz.release.envelope.v0";
|
|
9
|
+
export declare const APPLICATION_ARTIFACT_SCHEMA = "vmz.application.artifact.v0";
|
|
10
|
+
export declare const DELIVERY_ARTIFACT_MANIFEST_SCHEMA = "vmz.profile.delivery_artifact_manifest.v0";
|
|
11
|
+
export declare const ROUTE_REALIZATION_TABLE_SCHEMA = "vmz.profile.route_realization_table.v0";
|
|
12
|
+
export declare const ARTIFACT_DIFF_SCHEMA = "vmz.artifact.diff.v0";
|
|
13
|
+
/**
|
|
14
|
+
* @param {Buffer | string} data
|
|
15
|
+
*/
|
|
16
|
+
export declare function sha256Hex(data: any): string;
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} filePath
|
|
19
|
+
*/
|
|
20
|
+
export declare function sha256File(filePath: any): string;
|
|
21
|
+
/**
|
|
22
|
+
* @param {unknown} value
|
|
23
|
+
*/
|
|
24
|
+
export declare function canonicalJson(value: any): string;
|
|
25
|
+
/**
|
|
26
|
+
* Pack `dist/` into `_vmz` manifests + release envelope (filesystem Delivery Profile).
|
|
27
|
+
* @param {string} distDir
|
|
28
|
+
* @param {{ applicationId?: string }} [opts]
|
|
29
|
+
*/
|
|
30
|
+
export declare function packRelease(distDir: any, opts?: {}): {
|
|
31
|
+
artifactDigest: string;
|
|
32
|
+
schema: string;
|
|
33
|
+
applicationId: any;
|
|
34
|
+
deliveryProfile: string;
|
|
35
|
+
applicationIntegrity: any;
|
|
36
|
+
deploymentDigest: any;
|
|
37
|
+
programDigest: string;
|
|
38
|
+
styleDigest: any;
|
|
39
|
+
routeDigest: string;
|
|
40
|
+
fileDigests: {};
|
|
41
|
+
manifests: {
|
|
42
|
+
applicationArtifact: string;
|
|
43
|
+
deliveryArtifactManifest: string;
|
|
44
|
+
routeRealization: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} pointerPath
|
|
49
|
+
* @param {string} digest
|
|
50
|
+
*/
|
|
51
|
+
export declare function atomicWritePointer(pointerPath: any, digest: any): void;
|
|
52
|
+
/**
|
|
53
|
+
* @param {string} pointerPath
|
|
54
|
+
* @returns {string | null}
|
|
55
|
+
*/
|
|
56
|
+
export declare function readPointer(pointerPath: any): string;
|
|
57
|
+
/**
|
|
58
|
+
* Publish packed dist into releases root; retain previous pointer for rollback.
|
|
59
|
+
* @param {string} releasesRoot
|
|
60
|
+
* @param {string} distDir
|
|
61
|
+
* @param {ReturnType<typeof packRelease>} envelope
|
|
62
|
+
*/
|
|
63
|
+
export declare function publishRelease(releasesRoot: any, distDir: any, envelope: any): {
|
|
64
|
+
digest: any;
|
|
65
|
+
previous: string;
|
|
66
|
+
currentPath: string;
|
|
67
|
+
releaseDir: string;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Rollback CURRENT → PREVIOUS without rebuild.
|
|
71
|
+
* @param {string} releasesRoot
|
|
72
|
+
*/
|
|
73
|
+
export declare function rollbackRelease(releasesRoot: any): {
|
|
74
|
+
restored: string;
|
|
75
|
+
demoted: string;
|
|
76
|
+
releaseDir: string;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Structured diff between two release envelopes / digest maps.
|
|
80
|
+
* @param {{ fileDigests?: Record<string, string>, artifactDigest?: string }} a
|
|
81
|
+
* @param {{ fileDigests?: Record<string, string>, artifactDigest?: string }} b
|
|
82
|
+
*/
|
|
83
|
+
export declare function diffArtifacts(a: any, b: any): {
|
|
84
|
+
schema: string;
|
|
85
|
+
beforeDigest: any;
|
|
86
|
+
afterDigest: any;
|
|
87
|
+
added: any[];
|
|
88
|
+
removed: any[];
|
|
89
|
+
changed: any[];
|
|
90
|
+
identical: boolean;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* @param {string} releasesRoot
|
|
94
|
+
* @param {string} digest
|
|
95
|
+
*/
|
|
96
|
+
export declare function loadReleaseEnvelope(releasesRoot: any, digest: any): any;
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A3 filesystem release packaging — digests, atomic pointer, rollback, artifact diff.
|
|
3
|
+
*
|
|
4
|
+
* Not the full CDN/StaticDelivery matrix. Same VPG build (`dist/`) is packed into
|
|
5
|
+
* `_vmz/*` manifests + content digests; publish retains previous release for rollback
|
|
6
|
+
* without rebuild.
|
|
7
|
+
*/
|
|
8
|
+
// @ts-nocheck
|
|
9
|
+
import crypto from 'node:crypto';
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
export const RELEASE_ENVELOPE_SCHEMA = 'vmz.release.envelope.v0';
|
|
13
|
+
export const APPLICATION_ARTIFACT_SCHEMA = 'vmz.application.artifact.v0';
|
|
14
|
+
export const DELIVERY_ARTIFACT_MANIFEST_SCHEMA = 'vmz.profile.delivery_artifact_manifest.v0';
|
|
15
|
+
export const ROUTE_REALIZATION_TABLE_SCHEMA = 'vmz.profile.route_realization_table.v0';
|
|
16
|
+
export const ARTIFACT_DIFF_SCHEMA = 'vmz.artifact.diff.v0';
|
|
17
|
+
/**
|
|
18
|
+
* @param {Buffer | string} data
|
|
19
|
+
*/
|
|
20
|
+
export function sha256Hex(data) {
|
|
21
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} filePath
|
|
25
|
+
*/
|
|
26
|
+
export function sha256File(filePath) {
|
|
27
|
+
return sha256Hex(fs.readFileSync(filePath));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* @param {unknown} value
|
|
31
|
+
*/
|
|
32
|
+
export function canonicalJson(value) {
|
|
33
|
+
return JSON.stringify(sortKeys(value));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @param {unknown} value
|
|
37
|
+
* @returns {unknown}
|
|
38
|
+
*/
|
|
39
|
+
function sortKeys(value) {
|
|
40
|
+
if (Array.isArray(value))
|
|
41
|
+
return value.map(sortKeys);
|
|
42
|
+
if (value && typeof value === 'object') {
|
|
43
|
+
/** @type {Record<string, unknown>} */
|
|
44
|
+
const out = {};
|
|
45
|
+
for (const k of Object.keys(value).sort()) {
|
|
46
|
+
out[k] = sortKeys(/** @type {Record<string, unknown>} */ (value)[k]);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* @param {string} distDir
|
|
54
|
+
* @returns {string[]}
|
|
55
|
+
*/
|
|
56
|
+
function listContentFiles(distDir) {
|
|
57
|
+
/** @type {string[]} */
|
|
58
|
+
const out = [];
|
|
59
|
+
const skipDir = new Set(['_vmz', 'node_modules']);
|
|
60
|
+
const skipName = new Set(['vmz-serve-host.mjs', 'vmz-serve-host.js']);
|
|
61
|
+
/** @param {string} abs */
|
|
62
|
+
/** @param {string} rel */
|
|
63
|
+
function walk(abs, rel) {
|
|
64
|
+
let ents;
|
|
65
|
+
try {
|
|
66
|
+
ents = fs.readdirSync(abs, { withFileTypes: true });
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
for (const e of ents) {
|
|
72
|
+
if (e.name.startsWith('.'))
|
|
73
|
+
continue;
|
|
74
|
+
const nextAbs = path.join(abs, e.name);
|
|
75
|
+
const nextRel = rel ? `${rel}/${e.name}` : e.name;
|
|
76
|
+
if (e.isDirectory()) {
|
|
77
|
+
if (skipDir.has(e.name))
|
|
78
|
+
continue;
|
|
79
|
+
walk(nextAbs, nextRel);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (skipName.has(e.name))
|
|
83
|
+
continue;
|
|
84
|
+
out.push(nextRel.replace(/\\/g, '/'));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
walk(distDir, '');
|
|
88
|
+
out.sort();
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* @param {string} chunkId
|
|
93
|
+
*/
|
|
94
|
+
function pathPatternFromChunk(chunkId) {
|
|
95
|
+
const rel = chunkId.replace(/^pages\//, '');
|
|
96
|
+
const parts = rel.split('/').filter(Boolean);
|
|
97
|
+
const segs = [];
|
|
98
|
+
for (let i = 0; i < parts.length; i++) {
|
|
99
|
+
const p = parts[i];
|
|
100
|
+
if (p === 'index' && i === parts.length - 1)
|
|
101
|
+
continue;
|
|
102
|
+
segs.push(p);
|
|
103
|
+
}
|
|
104
|
+
return segs.length ? `/${segs.join('/')}` : '/';
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Pack `dist/` into `_vmz` manifests + release envelope (filesystem Delivery Profile).
|
|
108
|
+
* @param {string} distDir
|
|
109
|
+
* @param {{ applicationId?: string }} [opts]
|
|
110
|
+
*/
|
|
111
|
+
export function packRelease(distDir, opts = {}) {
|
|
112
|
+
const abs = path.resolve(distDir);
|
|
113
|
+
if (!fs.existsSync(abs)) {
|
|
114
|
+
throw new Error(`packRelease: missing dist ${abs}`);
|
|
115
|
+
}
|
|
116
|
+
const deploymentPath = path.join(abs, 'vmz-deployment.json');
|
|
117
|
+
if (!fs.existsSync(deploymentPath)) {
|
|
118
|
+
throw new Error(`packRelease: missing ${deploymentPath}`);
|
|
119
|
+
}
|
|
120
|
+
const deployment = JSON.parse(fs.readFileSync(deploymentPath, 'utf8'));
|
|
121
|
+
const files = listContentFiles(abs);
|
|
122
|
+
/** @type {Record<string, string>} */
|
|
123
|
+
const fileDigests = {};
|
|
124
|
+
for (const rel of files) {
|
|
125
|
+
fileDigests[rel] = sha256File(path.join(abs, ...rel.split('/')));
|
|
126
|
+
}
|
|
127
|
+
const pages = (deployment.units || []).filter((u) => u.kind === 'page');
|
|
128
|
+
const routeRealization = {
|
|
129
|
+
schema: ROUTE_REALIZATION_TABLE_SCHEMA,
|
|
130
|
+
routes: pages.map((u) => ({
|
|
131
|
+
routeId: String(u.chunkId),
|
|
132
|
+
chunkId: String(u.chunkId),
|
|
133
|
+
pathPattern: pathPatternFromChunk(String(u.chunkId)),
|
|
134
|
+
clientEntry: u.clientEntry || null,
|
|
135
|
+
programIr: u.programIr || null,
|
|
136
|
+
})),
|
|
137
|
+
};
|
|
138
|
+
const programParts = pages
|
|
139
|
+
.map((u) => u.programIr)
|
|
140
|
+
.filter(Boolean)
|
|
141
|
+
.map((rel) => fileDigests[String(rel).replace(/\\/g, '/')] || '')
|
|
142
|
+
.filter(Boolean)
|
|
143
|
+
.sort();
|
|
144
|
+
const programDigest = sha256Hex(programParts.join('|'));
|
|
145
|
+
const styleDigest = typeof deployment.styleBundleHash === 'string' && deployment.styleBundleHash ? deployment.styleBundleHash : null;
|
|
146
|
+
const deploymentDigest = fileDigests['vmz-deployment.json'] || sha256File(deploymentPath);
|
|
147
|
+
const applicationId = opts.applicationId || 'production-router';
|
|
148
|
+
const applicationArtifact = {
|
|
149
|
+
schema: APPLICATION_ARTIFACT_SCHEMA,
|
|
150
|
+
applicationId,
|
|
151
|
+
deliveryProfile: 'filesystem',
|
|
152
|
+
programDigest,
|
|
153
|
+
planDigest: programDigest,
|
|
154
|
+
deploymentDigest,
|
|
155
|
+
styleDigest,
|
|
156
|
+
routeDigest: sha256Hex(canonicalJson(routeRealization)),
|
|
157
|
+
fileDigests,
|
|
158
|
+
publicRouteContracts: routeRealization.routes.map((r) => r.routeId),
|
|
159
|
+
};
|
|
160
|
+
applicationArtifact.integrity = sha256Hex(canonicalJson({ ...applicationArtifact, integrity: undefined }));
|
|
161
|
+
const deliveryManifest = {
|
|
162
|
+
schema: DELIVERY_ARTIFACT_MANIFEST_SCHEMA,
|
|
163
|
+
deliveryProfile: 'filesystem',
|
|
164
|
+
copiesSemanticIr: false,
|
|
165
|
+
applicationId,
|
|
166
|
+
applicationIntegrity: applicationArtifact.integrity,
|
|
167
|
+
routeCount: routeRealization.routes.length,
|
|
168
|
+
assetCount: Object.keys(fileDigests).length,
|
|
169
|
+
styleDigest,
|
|
170
|
+
deploymentDigest,
|
|
171
|
+
};
|
|
172
|
+
const envelopeBody = {
|
|
173
|
+
schema: RELEASE_ENVELOPE_SCHEMA,
|
|
174
|
+
applicationId,
|
|
175
|
+
deliveryProfile: 'filesystem',
|
|
176
|
+
applicationIntegrity: applicationArtifact.integrity,
|
|
177
|
+
deploymentDigest,
|
|
178
|
+
programDigest,
|
|
179
|
+
styleDigest,
|
|
180
|
+
routeDigest: applicationArtifact.routeDigest,
|
|
181
|
+
fileDigests,
|
|
182
|
+
manifests: {
|
|
183
|
+
applicationArtifact: '_vmz/application-artifact.json',
|
|
184
|
+
deliveryArtifactManifest: '_vmz/delivery-artifact-manifest.json',
|
|
185
|
+
routeRealization: '_vmz/route-realization.json',
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
const artifactDigest = sha256Hex(canonicalJson(envelopeBody));
|
|
189
|
+
const envelope = { ...envelopeBody, artifactDigest };
|
|
190
|
+
const vmzDir = path.join(abs, '_vmz');
|
|
191
|
+
fs.mkdirSync(vmzDir, { recursive: true });
|
|
192
|
+
writeJson(path.join(vmzDir, 'application-artifact.json'), applicationArtifact);
|
|
193
|
+
writeJson(path.join(vmzDir, 'delivery-artifact-manifest.json'), deliveryManifest);
|
|
194
|
+
writeJson(path.join(vmzDir, 'route-realization.json'), routeRealization);
|
|
195
|
+
writeJson(path.join(vmzDir, 'release-envelope.json'), envelope);
|
|
196
|
+
return envelope;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* @param {string} file
|
|
200
|
+
* @param {unknown} value
|
|
201
|
+
*/
|
|
202
|
+
function writeJson(file, value) {
|
|
203
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* @param {string} pointerPath
|
|
207
|
+
* @param {string} digest
|
|
208
|
+
*/
|
|
209
|
+
export function atomicWritePointer(pointerPath, digest) {
|
|
210
|
+
const dir = path.dirname(pointerPath);
|
|
211
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
212
|
+
const tmp = `${pointerPath}.${process.pid}.${Date.now()}.tmp`;
|
|
213
|
+
fs.writeFileSync(tmp, `${digest.trim()}\n`, 'utf8');
|
|
214
|
+
try {
|
|
215
|
+
fs.renameSync(tmp, pointerPath);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
if (fs.existsSync(pointerPath))
|
|
219
|
+
fs.unlinkSync(pointerPath);
|
|
220
|
+
fs.renameSync(tmp, pointerPath);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* @param {string} pointerPath
|
|
225
|
+
* @returns {string | null}
|
|
226
|
+
*/
|
|
227
|
+
export function readPointer(pointerPath) {
|
|
228
|
+
if (!fs.existsSync(pointerPath))
|
|
229
|
+
return null;
|
|
230
|
+
const t = fs.readFileSync(pointerPath, 'utf8').trim();
|
|
231
|
+
return t || null;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Publish packed dist into releases root; retain previous pointer for rollback.
|
|
235
|
+
* @param {string} releasesRoot
|
|
236
|
+
* @param {string} distDir
|
|
237
|
+
* @param {ReturnType<typeof packRelease>} envelope
|
|
238
|
+
*/
|
|
239
|
+
export function publishRelease(releasesRoot, distDir, envelope) {
|
|
240
|
+
const digest = envelope.artifactDigest;
|
|
241
|
+
if (!digest)
|
|
242
|
+
throw new Error('publishRelease: envelope missing artifactDigest');
|
|
243
|
+
const root = path.resolve(releasesRoot);
|
|
244
|
+
const dest = path.join(root, digest);
|
|
245
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
246
|
+
// Immutable snapshot of packed dist (exclude prior releases nesting).
|
|
247
|
+
const destDist = path.join(dest, 'dist');
|
|
248
|
+
fs.rmSync(destDist, { recursive: true, force: true });
|
|
249
|
+
fs.cpSync(path.resolve(distDir), destDist, {
|
|
250
|
+
recursive: true,
|
|
251
|
+
filter: (src) => {
|
|
252
|
+
const n = src.replace(/\\/g, '/');
|
|
253
|
+
return !n.includes('/.vmz-releases/') && !n.includes('/dist/releases');
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
writeJson(path.join(dest, 'envelope.json'), envelope);
|
|
257
|
+
const currentPath = path.join(root, 'CURRENT');
|
|
258
|
+
const previousPath = path.join(root, 'PREVIOUS');
|
|
259
|
+
const prev = readPointer(currentPath);
|
|
260
|
+
if (prev && prev !== digest) {
|
|
261
|
+
atomicWritePointer(previousPath, prev);
|
|
262
|
+
}
|
|
263
|
+
atomicWritePointer(currentPath, digest);
|
|
264
|
+
return {
|
|
265
|
+
digest,
|
|
266
|
+
previous: prev && prev !== digest ? prev : readPointer(previousPath),
|
|
267
|
+
currentPath,
|
|
268
|
+
releaseDir: dest,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Rollback CURRENT → PREVIOUS without rebuild.
|
|
273
|
+
* @param {string} releasesRoot
|
|
274
|
+
*/
|
|
275
|
+
export function rollbackRelease(releasesRoot) {
|
|
276
|
+
const root = path.resolve(releasesRoot);
|
|
277
|
+
const currentPath = path.join(root, 'CURRENT');
|
|
278
|
+
const previousPath = path.join(root, 'PREVIOUS');
|
|
279
|
+
const current = readPointer(currentPath);
|
|
280
|
+
const previous = readPointer(previousPath);
|
|
281
|
+
if (!previous) {
|
|
282
|
+
throw new Error('rollbackRelease: no PREVIOUS pointer');
|
|
283
|
+
}
|
|
284
|
+
if (!fs.existsSync(path.join(root, previous, 'envelope.json'))) {
|
|
285
|
+
throw new Error(`rollbackRelease: missing retained release ${previous}`);
|
|
286
|
+
}
|
|
287
|
+
if (current) {
|
|
288
|
+
atomicWritePointer(previousPath, current);
|
|
289
|
+
}
|
|
290
|
+
atomicWritePointer(currentPath, previous);
|
|
291
|
+
return {
|
|
292
|
+
restored: previous,
|
|
293
|
+
demoted: current,
|
|
294
|
+
releaseDir: path.join(root, previous),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Structured diff between two release envelopes / digest maps.
|
|
299
|
+
* @param {{ fileDigests?: Record<string, string>, artifactDigest?: string }} a
|
|
300
|
+
* @param {{ fileDigests?: Record<string, string>, artifactDigest?: string }} b
|
|
301
|
+
*/
|
|
302
|
+
export function diffArtifacts(a, b) {
|
|
303
|
+
const da = a.fileDigests || {};
|
|
304
|
+
const db = b.fileDigests || {};
|
|
305
|
+
const keys = new Set([...Object.keys(da), ...Object.keys(db)]);
|
|
306
|
+
/** @type {string[]} */
|
|
307
|
+
const added = [];
|
|
308
|
+
/** @type {string[]} */
|
|
309
|
+
const removed = [];
|
|
310
|
+
/** @type {Array<{ path: string, before: string, after: string }>} */
|
|
311
|
+
const changed = [];
|
|
312
|
+
for (const k of [...keys].sort()) {
|
|
313
|
+
if (!(k in da))
|
|
314
|
+
added.push(k);
|
|
315
|
+
else if (!(k in db))
|
|
316
|
+
removed.push(k);
|
|
317
|
+
else if (da[k] !== db[k])
|
|
318
|
+
changed.push({ path: k, before: da[k], after: db[k] });
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
schema: ARTIFACT_DIFF_SCHEMA,
|
|
322
|
+
beforeDigest: a.artifactDigest || null,
|
|
323
|
+
afterDigest: b.artifactDigest || null,
|
|
324
|
+
added,
|
|
325
|
+
removed,
|
|
326
|
+
changed,
|
|
327
|
+
identical: added.length === 0 && removed.length === 0 && changed.length === 0,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* @param {string} releasesRoot
|
|
332
|
+
* @param {string} digest
|
|
333
|
+
*/
|
|
334
|
+
export function loadReleaseEnvelope(releasesRoot, digest) {
|
|
335
|
+
const p = path.join(path.resolve(releasesRoot), digest, 'envelope.json');
|
|
336
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
337
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the native `vmz` CLI binary (vmz-tools), never the Node wrapper.
|
|
3
|
+
* Used by `vmz lsp` / `vmz mcp` so stdio servers stay one binary for all hosts.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} [startDir]
|
|
7
|
+
* @returns {string | null}
|
|
8
|
+
*/
|
|
9
|
+
export declare function findRepoRoot(startDir?: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* @param {{ cwd?: string }} [opts]
|
|
12
|
+
* @returns {string | null} absolute path to native vmz binary
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveNativeVmzCli(opts?: {}): any;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the native `vmz` CLI binary (vmz-tools), never the Node wrapper.
|
|
4
|
+
* Used by `vmz lsp` / `vmz mcp` so stdio servers stay one binary for all hosts.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, statSync } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
const exe = process.platform === 'win32' ? 'vmz.exe' : 'vmz';
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} [startDir]
|
|
12
|
+
* @returns {string | null}
|
|
13
|
+
*/
|
|
14
|
+
export function findRepoRoot(startDir = process.cwd()) {
|
|
15
|
+
let dir = path.resolve(startDir);
|
|
16
|
+
for (let i = 0; i < 12; i++) {
|
|
17
|
+
if (existsSync(path.join(dir, 'Cargo.toml')) && existsSync(path.join(dir, 'package.json'))) {
|
|
18
|
+
return dir;
|
|
19
|
+
}
|
|
20
|
+
const parent = path.dirname(dir);
|
|
21
|
+
if (parent === dir)
|
|
22
|
+
break;
|
|
23
|
+
dir = parent;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Prefer the newer of release/debug so a fresh `cargo build` is not shadowed by a stale release.
|
|
29
|
+
* @param {string} repo
|
|
30
|
+
* @returns {string | null}
|
|
31
|
+
*/
|
|
32
|
+
function pickNewestProfileBinary(repo) {
|
|
33
|
+
/** @type {{ path: string, mtime: number } | null} */
|
|
34
|
+
let best = null;
|
|
35
|
+
for (const profile of ['release', 'debug']) {
|
|
36
|
+
const candidate = path.join(repo, 'target', profile, exe);
|
|
37
|
+
if (!existsSync(candidate))
|
|
38
|
+
continue;
|
|
39
|
+
let mtime = 0;
|
|
40
|
+
try {
|
|
41
|
+
mtime = statSync(candidate).mtimeMs;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!best || mtime > best.mtime)
|
|
47
|
+
best = { path: candidate, mtime };
|
|
48
|
+
}
|
|
49
|
+
return best?.path ?? null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* @param {{ cwd?: string }} [opts]
|
|
53
|
+
* @returns {string | null} absolute path to native vmz binary
|
|
54
|
+
*/
|
|
55
|
+
export function resolveNativeVmzCli(opts = {}) {
|
|
56
|
+
if (typeof process.env.VMZ_NATIVE === 'string' && process.env.VMZ_NATIVE.trim()) {
|
|
57
|
+
const p = path.resolve(process.env.VMZ_NATIVE.trim());
|
|
58
|
+
if (existsSync(p))
|
|
59
|
+
return p;
|
|
60
|
+
}
|
|
61
|
+
const roots = [];
|
|
62
|
+
if (opts.cwd)
|
|
63
|
+
roots.push(path.resolve(opts.cwd));
|
|
64
|
+
roots.push(process.cwd());
|
|
65
|
+
try {
|
|
66
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
67
|
+
// packages/runtimes/vmz/dist → repo root
|
|
68
|
+
roots.push(path.resolve(here, '../../../..'));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
/* ignore */
|
|
72
|
+
}
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
for (const start of roots) {
|
|
75
|
+
const repo = findRepoRoot(start);
|
|
76
|
+
if (!repo || seen.has(repo))
|
|
77
|
+
continue;
|
|
78
|
+
seen.add(repo);
|
|
79
|
+
const picked = pickNewestProfileBinary(repo);
|
|
80
|
+
if (picked)
|
|
81
|
+
return picked;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
package/dist/resolve.d.ts
CHANGED
package/dist/resolve.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
/**
|
|
3
|
-
* Shared project path resolution for the Node CLI host
|
|
4
|
-
* Design: `规划设计/vmz/14-Node-NAPI与插件宿主.md`
|
|
3
|
+
* Shared project path resolution for the Node CLI host .
|
|
5
4
|
*/
|
|
6
5
|
import { existsSync, readFileSync } from 'node:fs';
|
|
7
6
|
import path from 'node:path';
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A3-site: SiteDeliveryContract — embedded | filesystem | remote selection + release fallback.
|
|
3
|
+
* Authoring via defineConfig({ delivery }) / defineSite(...); pure data only.
|
|
4
|
+
*/
|
|
5
|
+
export declare const SITE_DELIVERY_CONTRACT_SCHEMA = "vmz.site.delivery_contract.v0";
|
|
6
|
+
export declare const SITE_DELIVERY_RESOLUTION_SCHEMA = "vmz.site.delivery_resolution.v0";
|
|
7
|
+
/**
|
|
8
|
+
* Pure-data helper for `defineConfig({ delivery: defineSite(...) })`.
|
|
9
|
+
* Not a second config entry — CLI never auto-discovers `vmz.site.ts`.
|
|
10
|
+
* @param {Record<string, unknown>} delivery
|
|
11
|
+
*/
|
|
12
|
+
export declare function defineSite(delivery: any): any;
|
|
13
|
+
/**
|
|
14
|
+
* Normalize authoring delivery → frozen SiteDeliveryContract.
|
|
15
|
+
* @param {unknown} raw
|
|
16
|
+
* @param {{ siteId?: string, projectRoot?: string }} [opts]
|
|
17
|
+
* @returns {{ ok: true, contract: Record<string, any> } | { ok: false, diagnostics: Array<{ code: string, message: string }> }}
|
|
18
|
+
*/
|
|
19
|
+
export declare function normalizeSiteDelivery(raw: any, opts?: {}): {
|
|
20
|
+
ok: boolean;
|
|
21
|
+
diagnostics: any[];
|
|
22
|
+
contract?: undefined;
|
|
23
|
+
} | {
|
|
24
|
+
ok: boolean;
|
|
25
|
+
contract: {
|
|
26
|
+
schema: string;
|
|
27
|
+
schemaVersion: string;
|
|
28
|
+
siteId: any;
|
|
29
|
+
artifact: string;
|
|
30
|
+
expectedCompatibility: any;
|
|
31
|
+
sources: any[];
|
|
32
|
+
resolutionPolicy: {
|
|
33
|
+
mode: string;
|
|
34
|
+
fallback: any;
|
|
35
|
+
fileLevelMix: boolean;
|
|
36
|
+
};
|
|
37
|
+
failurePolicy: any;
|
|
38
|
+
updatePolicy: any;
|
|
39
|
+
rollbackPolicy: any;
|
|
40
|
+
securityPolicy: any;
|
|
41
|
+
activation: string;
|
|
42
|
+
};
|
|
43
|
+
diagnostics?: undefined;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Probe one physical source for release-level readiness (not per-URL).
|
|
47
|
+
* @param {{
|
|
48
|
+
* available?: boolean,
|
|
49
|
+
* artifactDigest?: string | null,
|
|
50
|
+
* integrityOk?: boolean,
|
|
51
|
+
* signatureOk?: boolean,
|
|
52
|
+
* objectClosureOk?: boolean,
|
|
53
|
+
* mixedDigestObjects?: boolean,
|
|
54
|
+
* error?: string | null,
|
|
55
|
+
* }} [probe]
|
|
56
|
+
*/
|
|
57
|
+
export declare function normalizeSourceProbe(probe?: {}): {
|
|
58
|
+
available: boolean;
|
|
59
|
+
artifactDigest: any;
|
|
60
|
+
integrityOk: boolean;
|
|
61
|
+
signatureOk: boolean;
|
|
62
|
+
objectClosureOk: boolean;
|
|
63
|
+
mixedDigestObjects: boolean;
|
|
64
|
+
error: any;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Resolve one complete release per SiteDeliveryContract (no file-level mix).
|
|
68
|
+
* @param {Record<string, any>} contract
|
|
69
|
+
* @param {Record<string, ReturnType<typeof normalizeSourceProbe>>} probes by sourceId
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveSiteRelease(contract: any, probes?: {}): {
|
|
72
|
+
schema: string;
|
|
73
|
+
status: string;
|
|
74
|
+
selectedSourceId: any;
|
|
75
|
+
selectedKind: any;
|
|
76
|
+
selectedDigest: any;
|
|
77
|
+
fallbackReason: string;
|
|
78
|
+
attempted: any[];
|
|
79
|
+
fileLevelMix: boolean;
|
|
80
|
+
activation: string;
|
|
81
|
+
contractDigest: any;
|
|
82
|
+
} | {
|
|
83
|
+
schema: string;
|
|
84
|
+
status: string;
|
|
85
|
+
selectedSourceId: any;
|
|
86
|
+
selectedKind: any;
|
|
87
|
+
selectedDigest: any;
|
|
88
|
+
fallbackReason: string;
|
|
89
|
+
attempted: any[];
|
|
90
|
+
fileLevelMix: boolean;
|
|
91
|
+
activation: string;
|
|
92
|
+
contractDigest: any;
|
|
93
|
+
resolutionDigest: any;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Read a packed release directory probe (expects _vmz/release-envelope.json + object closure).
|
|
97
|
+
* @param {string} releaseDir absolute path to a release snapshot (contains dist/ or is dist/)
|
|
98
|
+
*/
|
|
99
|
+
export declare function probeReleaseDirectory(releaseDir: any): {
|
|
100
|
+
available: boolean;
|
|
101
|
+
artifactDigest: any;
|
|
102
|
+
integrityOk: boolean;
|
|
103
|
+
signatureOk: boolean;
|
|
104
|
+
objectClosureOk: boolean;
|
|
105
|
+
mixedDigestObjects: boolean;
|
|
106
|
+
error: any;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Emit normalized contract (+ optional resolution) under dist/_vmz.
|
|
110
|
+
* @param {string} outDir
|
|
111
|
+
* @param {unknown} deliveryRaw
|
|
112
|
+
* @param {{ siteId?: string, probes?: Record<string, any> }} [opts]
|
|
113
|
+
*/
|
|
114
|
+
export declare function emitSiteDelivery(outDir: any, deliveryRaw: any, opts?: {}): {
|
|
115
|
+
contract: {
|
|
116
|
+
schema: string;
|
|
117
|
+
schemaVersion: string;
|
|
118
|
+
siteId: any;
|
|
119
|
+
artifact: string;
|
|
120
|
+
expectedCompatibility: any;
|
|
121
|
+
sources: any[];
|
|
122
|
+
resolutionPolicy: {
|
|
123
|
+
mode: string;
|
|
124
|
+
fallback: any;
|
|
125
|
+
fileLevelMix: boolean;
|
|
126
|
+
};
|
|
127
|
+
failurePolicy: any;
|
|
128
|
+
updatePolicy: any;
|
|
129
|
+
rollbackPolicy: any;
|
|
130
|
+
securityPolicy: any;
|
|
131
|
+
activation: string;
|
|
132
|
+
};
|
|
133
|
+
resolution: any;
|
|
134
|
+
};
|