@lark-apaas/fullstack-cli 1.1.59 → 1.1.61-alpha.20260818172555

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.
@@ -0,0 +1,1346 @@
1
+ // Canonical preflight source. Template repositories receive this file verbatim.
2
+ import crypto from 'node:crypto';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+
6
+ function sha256(file) {
7
+ return crypto
8
+ .createHash('sha256')
9
+ .update(fs.readFileSync(file))
10
+ .digest('hex');
11
+ }
12
+
13
+ function readJson(file) {
14
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
15
+ }
16
+
17
+ function runtimeAbiHashes(runtime) {
18
+ if (
19
+ typeof runtime?.runtimeAbiHash !== 'string' ||
20
+ runtime.runtimeAbiHash.length === 0
21
+ ) {
22
+ throw new Error('MIAODA_CACHE_RUNTIME_MANIFEST_INVALID');
23
+ }
24
+ const compatible = runtime.compatibleRuntimeAbiHashes ?? [];
25
+ if (
26
+ !Array.isArray(compatible) ||
27
+ compatible.some(hash => typeof hash !== 'string' || hash.length === 0)
28
+ ) {
29
+ throw new Error('MIAODA_CACHE_RUNTIME_MANIFEST_INVALID');
30
+ }
31
+ return new Set([runtime.runtimeAbiHash, ...compatible]);
32
+ }
33
+
34
+ function listRegularFiles(root, cacheRoot) {
35
+ const files = [];
36
+ const visit = directory => {
37
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
38
+ const file = path.join(directory, entry.name);
39
+ if (entry.isSymbolicLink()) {
40
+ throw new Error(`MIAODA_CACHE_GENERATION_SYMLINK_REJECTED: ${file}`);
41
+ }
42
+ if (entry.isDirectory()) visit(file);
43
+ else if (entry.isFile()) files.push(path.relative(cacheRoot, file));
44
+ }
45
+ };
46
+ visit(root);
47
+ return files.sort();
48
+ }
49
+
50
+ const WORKSPACE_SOURCE_ROOTS = ['client', 'server', 'shared', 'src'];
51
+ const WORKSPACE_SOURCE_FILES = [
52
+ '.env',
53
+ '.env.development',
54
+ '.env.development.local',
55
+ 'drizzle.config.ts',
56
+ 'nest-cli.json',
57
+ 'postcss.config.js',
58
+ 'postcss.config.cjs',
59
+ 'postcss.config.mjs',
60
+ 'tailwind.config.ts',
61
+ 'tailwind.config.js',
62
+ 'tailwind.config.cjs',
63
+ 'tailwind.config.mjs',
64
+ 'tsconfig.json',
65
+ 'tsconfig.app.json',
66
+ 'tsconfig.node.json',
67
+ 'vite.config.ts',
68
+ 'vite.config.mts',
69
+ 'vite.config.js',
70
+ 'vite.config.mjs',
71
+ 'vite.config.cjs',
72
+ ];
73
+
74
+ function listWorkspaceSourceFilePaths(projectRoot) {
75
+ const files = WORKSPACE_SOURCE_ROOTS.flatMap(relativeRoot => {
76
+ const root = path.join(projectRoot, relativeRoot);
77
+ return fs.existsSync(root) ? listRegularFiles(root, projectRoot) : [];
78
+ });
79
+ for (const relativeFile of WORKSPACE_SOURCE_FILES) {
80
+ const file = path.join(projectRoot, relativeFile);
81
+ if (!fs.existsSync(file)) continue;
82
+ if (fs.lstatSync(file).isSymbolicLink() || !fs.statSync(file).isFile()) {
83
+ throw new Error(`MIAODA_WORKSPACE_SOURCE_FILE_REJECTED: ${file}`);
84
+ }
85
+ files.push(relativeFile);
86
+ }
87
+ return [...new Set(files)].sort();
88
+ }
89
+
90
+ function listWorkspaceSourceFiles(projectRoot) {
91
+ return listWorkspaceSourceFilePaths(projectRoot).map(relativeFile => {
92
+ const file = path.join(projectRoot, ...relativeFile.split('/'));
93
+ return {
94
+ path: relativeFile.split(path.sep).join('/'),
95
+ sha256: sha256(file),
96
+ size: fs.statSync(file).size,
97
+ };
98
+ });
99
+ }
100
+
101
+ function workspaceSourceFileInScope(relativePath, artifactScope) {
102
+ if (artifactScope === 'all') return true;
103
+ if (relativePath === 'client' || relativePath.startsWith('client/')) {
104
+ return artifactScope === 'vite';
105
+ }
106
+ if (relativePath === 'server' || relativePath.startsWith('server/')) {
107
+ return artifactScope === 'server';
108
+ }
109
+ // shared/, src/ and root config files may be consumed by either build.
110
+ return true;
111
+ }
112
+
113
+ function canonicalJson(value) {
114
+ if (Array.isArray(value)) return value.map(canonicalJson);
115
+ if (value && typeof value === 'object') {
116
+ return Object.fromEntries(
117
+ Object.entries(value)
118
+ .sort(([left], [right]) => left.localeCompare(right))
119
+ .map(([key, child]) => [key, canonicalJson(child)])
120
+ );
121
+ }
122
+ return value;
123
+ }
124
+
125
+ function stableServerMetadataIdentity(serverMetadata) {
126
+ const availabilityProbe = serverMetadata?.availabilityProbe ?? {};
127
+ const checks = Array.isArray(availabilityProbe.checks)
128
+ ? availabilityProbe.checks.map(check => {
129
+ const { elapsedMs: _elapsedMs, ...stableCheck } = check;
130
+ return stableCheck;
131
+ })
132
+ : availabilityProbe.checks;
133
+ const {
134
+ startupMs: _startupMs,
135
+ stdout: _stdout,
136
+ stderr: _stderr,
137
+ ...stableProbe
138
+ } = availabilityProbe;
139
+ return canonicalJson({
140
+ ...serverMetadata,
141
+ availabilityProbe: { ...stableProbe, checks },
142
+ });
143
+ }
144
+
145
+ function jsonIdentity(value) {
146
+ const content = JSON.stringify(value);
147
+ return {
148
+ sha256: crypto.createHash('sha256').update(content).digest('hex'),
149
+ size: Buffer.byteLength(content),
150
+ };
151
+ }
152
+
153
+ function serverDependenciesAreConsumable(serverDependencies) {
154
+ if (
155
+ serverDependencies?.schemaVersion !== 1 ||
156
+ typeof serverDependencies?.sourceEntry !== 'string' ||
157
+ !serverDependencies.sourceEntry ||
158
+ !Array.isArray(serverDependencies?.bootstrapModules) ||
159
+ serverDependencies.bootstrapModules.length === 0 ||
160
+ !serverDependencies?.transitionCompilerOptions ||
161
+ typeof serverDependencies.transitionCompilerOptions !== 'object' ||
162
+ Array.isArray(serverDependencies.transitionCompilerOptions) ||
163
+ !Array.isArray(serverDependencies?.packages) ||
164
+ serverDependencies.packages.length === 0 ||
165
+ !Array.isArray(serverDependencies?.actionPlugins) ||
166
+ !Array.isArray(serverDependencies?.files) ||
167
+ serverDependencies.files.length === 0 ||
168
+ !/^[a-f0-9]{64}$/.test(serverDependencies?.treeSha256 || '') ||
169
+ serverDependencies.fileCount !== serverDependencies.files.length ||
170
+ !Number.isSafeInteger(serverDependencies.totalBytes) ||
171
+ serverDependencies.totalBytes <= 0
172
+ ) {
173
+ return false;
174
+ }
175
+ const validPackages = packages =>
176
+ packages.every(
177
+ pkg =>
178
+ typeof pkg?.name === 'string' &&
179
+ pkg.name &&
180
+ typeof pkg?.version === 'string' &&
181
+ pkg.version
182
+ );
183
+ const paths = new Set();
184
+ const validFiles = serverDependencies.files.every(file => {
185
+ if (
186
+ typeof file?.path !== 'string' ||
187
+ !file.path ||
188
+ file.path !== path.posix.normalize(file.path) ||
189
+ file.path === '.' ||
190
+ file.path.startsWith('../') ||
191
+ path.posix.isAbsolute(file.path) ||
192
+ file.path.includes('\\') ||
193
+ paths.has(file.path) ||
194
+ !/^[a-f0-9]{64}$/.test(file?.sha256 || '') ||
195
+ !Number.isSafeInteger(file?.size) ||
196
+ file.size < 0
197
+ ) {
198
+ return false;
199
+ }
200
+ paths.add(file.path);
201
+ return true;
202
+ });
203
+ return (
204
+ validPackages(serverDependencies.packages) &&
205
+ validPackages(serverDependencies.actionPlugins) &&
206
+ validFiles &&
207
+ crypto
208
+ .createHash('sha256')
209
+ .update(JSON.stringify(serverDependencies.files))
210
+ .digest('hex') === serverDependencies.treeSha256 &&
211
+ serverDependencies.files.reduce((total, file) => total + file.size, 0) ===
212
+ serverDependencies.totalBytes
213
+ );
214
+ }
215
+
216
+ function compactServerDependencies(serverDependencies) {
217
+ if (!serverDependencies) return undefined;
218
+ const { files: _files, ...compact } = serverDependencies;
219
+ return compact;
220
+ }
221
+
222
+ function compactServerDependenciesAreConsumable(serverDependencies) {
223
+ return (
224
+ serverDependencies?.schemaVersion === 1 &&
225
+ typeof serverDependencies?.sourceEntry === 'string' &&
226
+ serverDependencies.sourceEntry.length > 0 &&
227
+ Array.isArray(serverDependencies?.bootstrapModules) &&
228
+ serverDependencies.bootstrapModules.length > 0 &&
229
+ serverDependencies?.transitionCompilerOptions &&
230
+ typeof serverDependencies.transitionCompilerOptions === 'object' &&
231
+ !Array.isArray(serverDependencies.transitionCompilerOptions) &&
232
+ Array.isArray(serverDependencies?.packages) &&
233
+ serverDependencies.packages.length > 0 &&
234
+ Array.isArray(serverDependencies?.actionPlugins) &&
235
+ /^[a-f0-9]{64}$/.test(serverDependencies?.treeSha256 || '') &&
236
+ Number.isSafeInteger(serverDependencies.fileCount) &&
237
+ serverDependencies.fileCount > 0 &&
238
+ Number.isSafeInteger(serverDependencies.totalBytes) &&
239
+ serverDependencies.totalBytes > 0
240
+ );
241
+ }
242
+
243
+ function workspaceBuildInputs(projectRoot) {
244
+ const packageJsonFile = path.join(projectRoot, 'package.json');
245
+ const packageJson = fs.existsSync(packageJsonFile)
246
+ ? readJson(packageJsonFile)
247
+ : {};
248
+ return {
249
+ actionPlugins: canonicalJson(packageJson.actionPlugins ?? {}),
250
+ };
251
+ }
252
+
253
+ function packageNameFromOptimizerId(id) {
254
+ const specifier = id.split(' > ').at(-1) || id;
255
+ if (
256
+ !specifier ||
257
+ specifier.startsWith('.') ||
258
+ specifier.startsWith('/') ||
259
+ specifier.startsWith('\0') ||
260
+ specifier.includes('://')
261
+ ) {
262
+ return undefined;
263
+ }
264
+ const parts = specifier.split('/');
265
+ return specifier.startsWith('@')
266
+ ? parts.length >= 2
267
+ ? `${parts[0]}/${parts[1]}`
268
+ : undefined
269
+ : parts[0];
270
+ }
271
+
272
+ function isPathInside(root, candidate) {
273
+ const relative = path.relative(root, candidate);
274
+ return (
275
+ relative === '' ||
276
+ (!relative.startsWith('..') && !path.isAbsolute(relative))
277
+ );
278
+ }
279
+
280
+ function optimizerEntryUsesRuntimePackage({
281
+ projectRoot,
282
+ runtimeManifestFile,
283
+ runtime,
284
+ packageName,
285
+ entry,
286
+ }) {
287
+ const variants = runtime.packages?.[packageName];
288
+ if (!Array.isArray(variants) || variants.length === 0) return false;
289
+ if (typeof entry?.src !== 'string' || !entry.src) return false;
290
+
291
+ const source = path.isAbsolute(entry.src)
292
+ ? entry.src
293
+ : path.resolve(projectRoot, entry.src);
294
+ if (!fs.existsSync(source)) return false;
295
+ const realSource = fs.realpathSync(source);
296
+ const runtimeRoot = path.dirname(path.resolve(runtimeManifestFile));
297
+ return variants.some(variant => {
298
+ if (typeof variant.relativePath !== 'string' || !variant.relativePath) {
299
+ return false;
300
+ }
301
+ const packageRoot = path.resolve(runtimeRoot, variant.relativePath);
302
+ if (
303
+ !isPathInside(runtimeRoot, packageRoot) ||
304
+ !fs.existsSync(packageRoot)
305
+ ) {
306
+ return false;
307
+ }
308
+ return isPathInside(fs.realpathSync(packageRoot), realSource);
309
+ });
310
+ }
311
+
312
+ function resolveArtifact(cacheRoot, artifact) {
313
+ if (
314
+ !artifact?.path ||
315
+ !artifact?.sha256 ||
316
+ !Number.isSafeInteger(artifact?.size) ||
317
+ artifact.size < 0
318
+ ) {
319
+ throw new Error('MIAODA_CACHE_GENERATION_ARTIFACT_INVALID');
320
+ }
321
+ const file = path.resolve(cacheRoot, artifact.path);
322
+ if (file !== cacheRoot && !file.startsWith(`${cacheRoot}${path.sep}`)) {
323
+ throw new Error(`MIAODA_CACHE_GENERATION_PATH_ESCAPE: ${artifact.path}`);
324
+ }
325
+ if (
326
+ !fs.existsSync(file) ||
327
+ fs.lstatSync(file).isSymbolicLink() ||
328
+ fs.statSync(file).size !== artifact.size ||
329
+ sha256(file) !== artifact.sha256
330
+ ) {
331
+ throw new Error(`MIAODA_CACHE_GENERATION_SHA_MISMATCH: ${file}`);
332
+ }
333
+ const realFile = fs.realpathSync(file);
334
+ if (!isPathInside(cacheRoot, realFile)) {
335
+ throw new Error(`MIAODA_CACHE_GENERATION_PATH_ESCAPE: ${artifact.path}`);
336
+ }
337
+ return realFile;
338
+ }
339
+
340
+ function validateClientConfigFiles(projectRoot, appRuntime) {
341
+ const configFiles = appRuntime.clientDependencyGraph?.configFiles;
342
+ if (!Array.isArray(configFiles)) {
343
+ throw new Error('MIAODA_CACHE_CLIENT_CONFIG_FILES_INVALID');
344
+ }
345
+ const realProjectRoot = fs.realpathSync(projectRoot);
346
+ const sealedPaths = new Set();
347
+ for (const configFile of configFiles) {
348
+ const relativePath = configFile?.path;
349
+ if (
350
+ typeof relativePath !== 'string' ||
351
+ !relativePath ||
352
+ relativePath !== path.posix.normalize(relativePath) ||
353
+ relativePath === '.' ||
354
+ relativePath.startsWith('../') ||
355
+ path.posix.isAbsolute(relativePath) ||
356
+ relativePath.includes('\\') ||
357
+ sealedPaths.has(relativePath) ||
358
+ !/^[a-f0-9]{64}$/.test(configFile?.sha256 || '')
359
+ ) {
360
+ throw new Error('MIAODA_CACHE_CLIENT_CONFIG_FILES_INVALID');
361
+ }
362
+ sealedPaths.add(relativePath);
363
+ const absolutePath = path.join(projectRoot, ...relativePath.split('/'));
364
+ let currentPath = projectRoot;
365
+ for (const segment of relativePath.split('/')) {
366
+ currentPath = path.join(currentPath, segment);
367
+ if (
368
+ !fs.existsSync(currentPath) ||
369
+ fs.lstatSync(currentPath).isSymbolicLink()
370
+ ) {
371
+ throw new Error('MIAODA_CACHE_CLIENT_CONFIG_FILE_MISMATCH');
372
+ }
373
+ }
374
+ if (!fs.statSync(absolutePath).isFile()) {
375
+ throw new Error('MIAODA_CACHE_CLIENT_CONFIG_FILE_MISMATCH');
376
+ }
377
+ const realFile = fs.realpathSync(absolutePath);
378
+ if (!isPathInside(realProjectRoot, realFile)) {
379
+ throw new Error('MIAODA_CACHE_CLIENT_CONFIG_FILE_MISMATCH');
380
+ }
381
+ const actualHash = crypto
382
+ .createHash('sha256')
383
+ .update(fs.readFileSync(realFile))
384
+ .digest('hex');
385
+ if (actualHash !== configFile.sha256) {
386
+ throw new Error('MIAODA_CACHE_CLIENT_CONFIG_FILE_MISMATCH');
387
+ }
388
+ }
389
+ const fixedNames = [
390
+ 'vite.config.ts',
391
+ 'vite.config.mts',
392
+ 'vite.config.js',
393
+ 'vite.config.mjs',
394
+ 'vite.config.cjs',
395
+ 'postcss.config.js',
396
+ 'postcss.config.cjs',
397
+ 'postcss.config.mjs',
398
+ 'tailwind.config.ts',
399
+ 'tailwind.config.js',
400
+ 'tailwind.config.cjs',
401
+ 'tailwind.config.mjs',
402
+ 'tsconfig.json',
403
+ 'tsconfig.app.json',
404
+ 'tsconfig.node.json',
405
+ '.env',
406
+ '.env.development',
407
+ '.env.development.local',
408
+ ];
409
+ const currentEntrypoints = fixedNames.filter(name =>
410
+ fs.existsSync(path.join(projectRoot, name))
411
+ );
412
+ const visitPatchFiles = relativeDirectory => {
413
+ const absoluteDirectory = path.join(projectRoot, relativeDirectory);
414
+ if (!fs.existsSync(absoluteDirectory)) return;
415
+ for (const entry of fs.readdirSync(absoluteDirectory, {
416
+ withFileTypes: true,
417
+ })) {
418
+ if (entry.isSymbolicLink()) {
419
+ throw new Error('MIAODA_CACHE_CLIENT_CONFIG_FILE_MISMATCH');
420
+ }
421
+ const child = path.posix.join(relativeDirectory, entry.name);
422
+ if (entry.isDirectory()) visitPatchFiles(child);
423
+ else if (entry.isFile()) currentEntrypoints.push(child);
424
+ }
425
+ };
426
+ visitPatchFiles('patches');
427
+ visitPatchFiles('.yarn/patches');
428
+ if (currentEntrypoints.some(relativePath => !sealedPaths.has(relativePath))) {
429
+ throw new Error('MIAODA_CACHE_CLIENT_CONFIG_FILE_SET_MISMATCH');
430
+ }
431
+ }
432
+
433
+ function validateClientConfigEnvironment(appRuntime) {
434
+ for (const [name, expectedHash] of Object.entries(
435
+ appRuntime.clientDependencyGraph.configEnvironment || {}
436
+ )) {
437
+ const actualHash = crypto
438
+ .createHash('sha256')
439
+ .update(process.env[name] || '')
440
+ .digest('hex');
441
+ if (actualHash !== expectedHash) {
442
+ throw new Error(
443
+ `MIAODA_CACHE_CLIENT_CONFIG_ENVIRONMENT_MISMATCH: ${name}`
444
+ );
445
+ }
446
+ }
447
+ }
448
+
449
+ const VALIDATION_RECEIPT_SCHEMA_VERSION = 3;
450
+ const VALIDATION_RECEIPT_MAX_BYTES = 1024 * 1024;
451
+ const VALIDATION_RECEIPT_MAX_AGE_MS = 5 * 60 * 1000;
452
+
453
+ function statIdentity(file) {
454
+ const stat = fs.lstatSync(file, { bigint: true });
455
+ if (stat.isSymbolicLink() || !stat.isFile()) {
456
+ throw new Error(`MIAODA_CACHE_VALIDATION_RECEIPT_FILE_REJECTED: ${file}`);
457
+ }
458
+ return {
459
+ dev: String(stat.dev),
460
+ ino: String(stat.ino),
461
+ mode: String(stat.mode),
462
+ size: String(stat.size),
463
+ mtimeNs: String(stat.mtimeNs),
464
+ ctimeNs: String(stat.ctimeNs),
465
+ };
466
+ }
467
+
468
+ function receiptFileDescriptor(
469
+ { projectRoot, cacheRoot, runtimeManifestFile },
470
+ file
471
+ ) {
472
+ const resolvedFile = path.resolve(file);
473
+ if (isPathInside(cacheRoot, resolvedFile)) {
474
+ return { root: 'cache', path: path.relative(cacheRoot, resolvedFile) };
475
+ }
476
+ if (isPathInside(projectRoot, resolvedFile)) {
477
+ return { root: 'project', path: path.relative(projectRoot, resolvedFile) };
478
+ }
479
+ if (resolvedFile === runtimeManifestFile) {
480
+ return { root: 'runtime-manifest', path: '' };
481
+ }
482
+ throw new Error(`MIAODA_CACHE_VALIDATION_RECEIPT_PATH_REJECTED: ${file}`);
483
+ }
484
+
485
+ function resolveReceiptFile(
486
+ { projectRoot, cacheRoot, runtimeManifestFile },
487
+ descriptor
488
+ ) {
489
+ let root;
490
+ if (descriptor?.root === 'cache') root = cacheRoot;
491
+ else if (descriptor?.root === 'project') root = projectRoot;
492
+ else if (descriptor?.root === 'runtime-manifest') {
493
+ if (descriptor.path !== '') {
494
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_INVALID');
495
+ }
496
+ return runtimeManifestFile;
497
+ } else {
498
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_INVALID');
499
+ }
500
+ if (
501
+ typeof descriptor.path !== 'string' ||
502
+ descriptor.path !== path.normalize(descriptor.path) ||
503
+ descriptor.path === '..' ||
504
+ descriptor.path.startsWith(`..${path.sep}`) ||
505
+ path.isAbsolute(descriptor.path)
506
+ ) {
507
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_INVALID');
508
+ }
509
+ const file = path.resolve(root, descriptor.path);
510
+ if (!isPathInside(root, file)) {
511
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_INVALID');
512
+ }
513
+ return file;
514
+ }
515
+
516
+ function receiptId(receiptWithoutId) {
517
+ return crypto
518
+ .createHash('sha256')
519
+ .update(JSON.stringify(canonicalJson(receiptWithoutId)))
520
+ .digest('hex');
521
+ }
522
+
523
+ function compactReceiptGeneration(generation) {
524
+ return {
525
+ schemaVersion: generation.schemaVersion,
526
+ generationId: generation.generationId,
527
+ runtimeAbiHash: generation.runtimeAbiHash,
528
+ applicationKind: generation.applicationKind,
529
+ serverRuntime: generation.serverRuntime,
530
+ serverRuntimeMode: generation.serverRuntimeMode,
531
+ artifacts: generation.artifacts,
532
+ };
533
+ }
534
+
535
+ function resolveReceiptCacheArtifact(cacheRoot, artifact) {
536
+ if (typeof artifact?.path !== 'string' || !artifact.path) {
537
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_INVALID');
538
+ }
539
+ const file = path.resolve(cacheRoot, artifact.path);
540
+ if (!isPathInside(cacheRoot, file)) {
541
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_PATH_REJECTED');
542
+ }
543
+ const realFile = fs.realpathSync(file);
544
+ if (!isPathInside(cacheRoot, realFile)) {
545
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_PATH_REJECTED');
546
+ }
547
+ return realFile;
548
+ }
549
+
550
+ export function writeCacheGenerationValidationReceipt({
551
+ validated,
552
+ receiptFile,
553
+ }) {
554
+ const artifactScope = validated?.validation?.artifactScope;
555
+ if (
556
+ !['all', 'server', 'vite'].includes(artifactScope) ||
557
+ !validated?.generation?.generationId
558
+ ) {
559
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_SCOPE_REJECTED');
560
+ }
561
+ const roots = {
562
+ projectRoot: fs.realpathSync(validated.projectRoot),
563
+ cacheRoot: fs.realpathSync(validated.cacheRoot),
564
+ runtimeManifestFile: fs.realpathSync(validated.runtimeManifestFile),
565
+ };
566
+ const controlFiles = new Map();
567
+ const addControlFile = file => {
568
+ const resolvedFile = path.resolve(file);
569
+ controlFiles.set(resolvedFile, true);
570
+ };
571
+ addControlFile(validated.generationFile);
572
+ addControlFile(roots.runtimeManifestFile);
573
+ for (const file of Object.values(validated.artifacts)) addControlFile(file);
574
+ const controls = Array.from(controlFiles.keys())
575
+ .sort((left, right) => left.localeCompare(right))
576
+ .map(file => ({
577
+ ...receiptFileDescriptor(roots, file),
578
+ stat: statIdentity(file),
579
+ }));
580
+ const receiptWithoutId = {
581
+ schemaVersion: VALIDATION_RECEIPT_SCHEMA_VERSION,
582
+ validatedAtMs: Date.now(),
583
+ artifactScope,
584
+ roots,
585
+ generationId: validated.generation.generationId,
586
+ controls,
587
+ validated: {
588
+ generation: compactReceiptGeneration(validated.generation),
589
+ runtime: validated.runtime,
590
+ serverMetadata: validated.serverMetadata,
591
+ serverDependencies: compactServerDependencies(
592
+ validated.serverDependencies
593
+ ),
594
+ appRuntime: validated.appRuntime,
595
+ },
596
+ };
597
+ const receipt = {
598
+ ...receiptWithoutId,
599
+ receiptId: receiptId(receiptWithoutId),
600
+ };
601
+ const resolvedReceiptFile = path.resolve(receiptFile);
602
+ fs.mkdirSync(path.dirname(resolvedReceiptFile), { recursive: true });
603
+ const temporaryFile = `${resolvedReceiptFile}.${process.pid}.${crypto.randomUUID()}.tmp`;
604
+ try {
605
+ fs.writeFileSync(temporaryFile, `${JSON.stringify(receipt)}\n`, {
606
+ mode: 0o600,
607
+ flag: 'wx',
608
+ });
609
+ fs.renameSync(temporaryFile, resolvedReceiptFile);
610
+ } finally {
611
+ fs.rmSync(temporaryFile, { force: true });
612
+ }
613
+ return receipt;
614
+ }
615
+
616
+ // The image-owned preflight hashes every artifact and source once. Serving
617
+ // runtimes consume this short-lived receipt without walking the workspace or
618
+ // sealed dependency tree again. Only the generation control plane (manifest,
619
+ // runtime ABI metadata and named artifact descriptors) is identity-checked.
620
+ // Publication of a new generation, control-file mutation, or stale/malformed
621
+ // receipt is a cache miss and callers fall back to the original SHA validation.
622
+ export function validateCacheGenerationReceipt({
623
+ projectRoot,
624
+ cacheRoot,
625
+ runtimeManifestFile,
626
+ receiptFile,
627
+ validateConfigEnvironment = true,
628
+ artifactScope = 'all',
629
+ }) {
630
+ if (!['all', 'server', 'vite'].includes(artifactScope)) {
631
+ throw new Error('MIAODA_CACHE_VALIDATION_SCOPE_INVALID');
632
+ }
633
+ const roots = {
634
+ projectRoot: fs.realpathSync(path.resolve(projectRoot)),
635
+ cacheRoot: fs.realpathSync(path.resolve(cacheRoot)),
636
+ runtimeManifestFile: fs.realpathSync(path.resolve(runtimeManifestFile)),
637
+ };
638
+ const resolvedReceiptFile = path.resolve(receiptFile);
639
+ const receiptStat = fs.lstatSync(resolvedReceiptFile);
640
+ if (
641
+ receiptStat.isSymbolicLink() ||
642
+ !receiptStat.isFile() ||
643
+ (receiptStat.mode & 0o022) !== 0 ||
644
+ receiptStat.size > VALIDATION_RECEIPT_MAX_BYTES
645
+ ) {
646
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_INVALID');
647
+ }
648
+ const receipt = readJson(resolvedReceiptFile);
649
+ const { receiptId: actualReceiptId, ...receiptWithoutId } = receipt;
650
+ // Receipts produced before scoped validation existed covered the full
651
+ // generation. A scoped receipt may only be consumed by the same artifact
652
+ // runtime; otherwise a server-only check could accidentally authorize Vite
653
+ // files (or vice versa) that were never validated.
654
+ const receiptArtifactScope = receipt.artifactScope || 'all';
655
+ const receiptCoversRequestedScope =
656
+ receiptArtifactScope === 'all' || receiptArtifactScope === artifactScope;
657
+ if (
658
+ receipt.schemaVersion !== VALIDATION_RECEIPT_SCHEMA_VERSION ||
659
+ !/^[a-f0-9]{64}$/.test(actualReceiptId || '') ||
660
+ receiptId(receiptWithoutId) !== actualReceiptId ||
661
+ !Number.isSafeInteger(receipt.validatedAtMs) ||
662
+ receipt.validatedAtMs > Date.now() + 5_000 ||
663
+ Date.now() - receipt.validatedAtMs > VALIDATION_RECEIPT_MAX_AGE_MS ||
664
+ JSON.stringify(receipt.roots) !== JSON.stringify(roots) ||
665
+ !['all', 'server', 'vite'].includes(receiptArtifactScope) ||
666
+ !receiptCoversRequestedScope
667
+ ) {
668
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_INVALID');
669
+ }
670
+ const {
671
+ generation,
672
+ runtime,
673
+ serverMetadata,
674
+ serverDependencies,
675
+ appRuntime,
676
+ } = receipt.validated || {};
677
+ const applicationKind =
678
+ generation?.applicationKind === 'frontend-only'
679
+ ? 'frontend-only'
680
+ : 'fullstack';
681
+ if (
682
+ ![3, 4].includes(generation?.schemaVersion) ||
683
+ generation.generationId !== receipt.generationId ||
684
+ !runtimeAbiHashes(runtime).has(generation.runtimeAbiHash)
685
+ ) {
686
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_MISMATCH');
687
+ }
688
+ if (artifactScope === 'server' && applicationKind === 'frontend-only') {
689
+ throw new Error('MIAODA_CACHE_SERVER_ARTIFACT_NOT_AVAILABLE');
690
+ }
691
+ if (
692
+ /^[a-f0-9]{64}$/.test(path.basename(roots.cacheRoot)) &&
693
+ path.basename(roots.cacheRoot) !== generation.generationId
694
+ ) {
695
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_MISMATCH');
696
+ }
697
+ if (
698
+ !Array.isArray(receipt.controls) ||
699
+ receipt.controls.length === 0 ||
700
+ receipt.controls.length > 64
701
+ ) {
702
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_INVALID');
703
+ }
704
+ for (const descriptor of receipt.controls) {
705
+ const file = resolveReceiptFile(roots, descriptor);
706
+ if (
707
+ JSON.stringify(statIdentity(file)) !== JSON.stringify(descriptor.stat)
708
+ ) {
709
+ throw new Error(`MIAODA_CACHE_VALIDATION_RECEIPT_FILE_CHANGED: ${file}`);
710
+ }
711
+ const realFile = fs.realpathSync(file);
712
+ const expectedRoot =
713
+ descriptor.root === 'cache'
714
+ ? roots.cacheRoot
715
+ : descriptor.root === 'project'
716
+ ? roots.projectRoot
717
+ : path.dirname(roots.runtimeManifestFile);
718
+ if (
719
+ descriptor.root !== 'runtime-manifest' &&
720
+ !isPathInside(expectedRoot, realFile)
721
+ ) {
722
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_PATH_REJECTED');
723
+ }
724
+ }
725
+ if (artifactScope !== 'vite' && applicationKind === 'fullstack') {
726
+ const expectedRuntime = generation.serverRuntime || {};
727
+ if (
728
+ String(expectedRuntime.nodeModuleAbi) !== process.versions.modules ||
729
+ expectedRuntime.platform !== process.platform ||
730
+ expectedRuntime.arch !== process.arch ||
731
+ String(expectedRuntime.nodeVersion || '').replace(/^v/, '') !==
732
+ process.versions.node
733
+ ) {
734
+ throw new Error('MIAODA_SERVER_CACHE_ABI_MISMATCH');
735
+ }
736
+ if (
737
+ generation.schemaVersion === 4 &&
738
+ (generation.serverRuntimeMode !== 'workspace-source-dependencies' ||
739
+ !compactServerDependenciesAreConsumable(serverDependencies))
740
+ ) {
741
+ throw new Error('MIAODA_SERVER_RUNTIME_DEPENDENCIES_REJECTED');
742
+ }
743
+ }
744
+ if (artifactScope !== 'server') {
745
+ if (
746
+ appRuntime?.schemaVersion !== 1 ||
747
+ appRuntime.runtimeAbiHash !== generation.runtimeAbiHash
748
+ ) {
749
+ throw new Error('MIAODA_APP_RUNTIME_ABI_MISMATCH');
750
+ }
751
+ if (validateConfigEnvironment) validateClientConfigEnvironment(appRuntime);
752
+ }
753
+ const shouldResolveArtifact = name =>
754
+ (applicationKind === 'fullstack' &&
755
+ artifactScope !== 'vite' &&
756
+ (generation.schemaVersion === 4
757
+ ? name === 'serverDependencies'
758
+ : name === 'serverBundle' ||
759
+ name === 'serverMetadata' ||
760
+ name.startsWith('serverRuntimeAsset:'))) ||
761
+ (artifactScope !== 'server' &&
762
+ (name === 'appRuntime' || name === 'viteMetadata'));
763
+ const artifacts = Object.fromEntries(
764
+ Object.entries(generation.artifacts || {})
765
+ .filter(([name]) => shouldResolveArtifact(name))
766
+ .map(([name, artifact]) => [
767
+ name,
768
+ resolveReceiptCacheArtifact(roots.cacheRoot, artifact),
769
+ ])
770
+ );
771
+ if (
772
+ generation.schemaVersion === 4 &&
773
+ applicationKind === 'fullstack' &&
774
+ artifactScope !== 'vite'
775
+ ) {
776
+ const transitionArtifacts = {
777
+ serverBundle: path.join(
778
+ roots.cacheRoot,
779
+ 'server',
780
+ 'bundle',
781
+ 'server.bundle.cjs'
782
+ ),
783
+ serverMetadata: path.join(
784
+ roots.cacheRoot,
785
+ 'server',
786
+ 'bundle',
787
+ 'server.bundle.cjs.meta.json'
788
+ ),
789
+ };
790
+ const receiptControls = new Set(
791
+ receipt.controls
792
+ .filter(descriptor => descriptor?.root === 'cache')
793
+ .map(descriptor => path.resolve(roots.cacheRoot, descriptor.path))
794
+ );
795
+ const exists = Object.values(transitionArtifacts).map(file =>
796
+ fs.existsSync(file)
797
+ );
798
+ if (exists.some(Boolean) && !exists.every(Boolean)) {
799
+ throw new Error('MIAODA_SERVER_TRANSITION_BUNDLE_INCOMPLETE');
800
+ }
801
+ if (exists.every(Boolean)) {
802
+ for (const [name, file] of Object.entries(transitionArtifacts)) {
803
+ if (!receiptControls.has(file)) {
804
+ throw new Error('MIAODA_CACHE_VALIDATION_RECEIPT_INVALID');
805
+ }
806
+ artifacts[name] = fs.realpathSync(file);
807
+ }
808
+ }
809
+ }
810
+ return {
811
+ projectRoot: roots.projectRoot,
812
+ cacheRoot: roots.cacheRoot,
813
+ runtimeManifestFile: roots.runtimeManifestFile,
814
+ generationFile: path.join(roots.cacheRoot, 'generation.json'),
815
+ generation,
816
+ runtime,
817
+ artifacts,
818
+ serverMetadata:
819
+ artifactScope === 'vite' || applicationKind === 'frontend-only'
820
+ ? undefined
821
+ : serverMetadata,
822
+ serverDependencies:
823
+ artifactScope === 'vite' || applicationKind === 'frontend-only'
824
+ ? undefined
825
+ : serverDependencies,
826
+ appRuntime: artifactScope === 'server' ? undefined : appRuntime,
827
+ // The serving runtime copies the sealed Vite directory as a whole and
828
+ // never consumes the per-file list. Returning an empty list keeps receipt
829
+ // consumption independent of projects with thousands of optimized files.
830
+ viteFiles: [],
831
+ validation: {
832
+ artifactScope,
833
+ validateConfigEnvironment,
834
+ validatedBy: 'platform-receipt',
835
+ },
836
+ };
837
+ }
838
+
839
+ export function validateCacheGeneration({
840
+ projectRoot,
841
+ cacheRoot,
842
+ runtimeManifestFile,
843
+ validateConfigEnvironment = true,
844
+ artifactScope = 'all',
845
+ validateWorkspaceInputs = true,
846
+ }) {
847
+ if (!['all', 'server', 'vite'].includes(artifactScope)) {
848
+ throw new Error('MIAODA_CACHE_VALIDATION_SCOPE_INVALID');
849
+ }
850
+ if (!validateWorkspaceInputs && artifactScope !== 'vite') {
851
+ throw new Error('MIAODA_CACHE_WORKSPACE_INPUT_SKIP_REQUIRES_VITE_SCOPE');
852
+ }
853
+ const requestedServerArtifacts =
854
+ artifactScope === 'all' || artifactScope === 'server';
855
+ const validateViteArtifacts =
856
+ artifactScope === 'all' || artifactScope === 'vite';
857
+ const resolvedProjectRoot = path.resolve(projectRoot);
858
+ const realProjectRoot = fs.realpathSync(resolvedProjectRoot);
859
+ const requestedCacheRoot = path.resolve(
860
+ cacheRoot || path.join(resolvedProjectRoot, '.miaoda-cache', 'current')
861
+ );
862
+ const resolvedCacheRoot = fs.existsSync(requestedCacheRoot)
863
+ ? fs.realpathSync(requestedCacheRoot)
864
+ : requestedCacheRoot;
865
+ const cacheRelative = path.relative(realProjectRoot, resolvedCacheRoot);
866
+ if (
867
+ cacheRelative === '..' ||
868
+ cacheRelative.startsWith(`..${path.sep}`) ||
869
+ path.isAbsolute(cacheRelative)
870
+ ) {
871
+ throw new Error('MIAODA_CACHE_GENERATION_ROOT_ESCAPE');
872
+ }
873
+ const resolvedRuntimeManifest = path.resolve(runtimeManifestFile);
874
+ const generationFile = path.join(resolvedCacheRoot, 'generation.json');
875
+ for (const file of [generationFile, resolvedRuntimeManifest]) {
876
+ if (!fs.existsSync(file)) {
877
+ throw new Error(`MIAODA_CACHE_BOOTSTRAP_INPUT_MISSING: ${file}`);
878
+ }
879
+ }
880
+ const generation = readJson(generationFile);
881
+ const runtime = readJson(resolvedRuntimeManifest);
882
+ if (![3, 4].includes(generation.schemaVersion) || !generation.generationId) {
883
+ throw new Error('MIAODA_CACHE_GENERATION_SCHEMA_UNSUPPORTED');
884
+ }
885
+ if (
886
+ generation.applicationKind !== undefined &&
887
+ !['fullstack', 'frontend-only'].includes(generation.applicationKind)
888
+ ) {
889
+ throw new Error('MIAODA_CACHE_APPLICATION_KIND_INVALID');
890
+ }
891
+ const applicationKind =
892
+ generation.applicationKind === 'frontend-only'
893
+ ? 'frontend-only'
894
+ : 'fullstack';
895
+ if (artifactScope === 'server' && applicationKind === 'frontend-only') {
896
+ throw new Error('MIAODA_CACHE_SERVER_ARTIFACT_NOT_AVAILABLE');
897
+ }
898
+ const validateServerArtifacts =
899
+ requestedServerArtifacts && applicationKind === 'fullstack';
900
+ if (
901
+ validateServerArtifacts &&
902
+ generation.schemaVersion === 3 &&
903
+ (!/^[a-f0-9]{64}$/.test(generation.serverMetadataIdentity?.sha256 || '') ||
904
+ !Number.isSafeInteger(generation.serverMetadataIdentity?.size) ||
905
+ generation.serverMetadataIdentity.size < 0)
906
+ ) {
907
+ throw new Error('MIAODA_CACHE_SERVER_METADATA_IDENTITY_INVALID');
908
+ }
909
+ const { generationId, ...generationIdentity } = generation;
910
+ const generationIdIdentity =
911
+ applicationKind === 'fullstack' && generation.schemaVersion === 3
912
+ ? {
913
+ ...generationIdentity,
914
+ artifacts: {
915
+ ...generationIdentity.artifacts,
916
+ serverMetadata: {
917
+ ...generationIdentity.artifacts?.serverMetadata,
918
+ ...generation.serverMetadataIdentity,
919
+ },
920
+ },
921
+ }
922
+ : generationIdentity;
923
+ const expectedGenerationId = crypto
924
+ .createHash('sha256')
925
+ .update(JSON.stringify(generationIdIdentity))
926
+ .digest('hex');
927
+ if (generationId !== expectedGenerationId) {
928
+ throw new Error('MIAODA_CACHE_GENERATION_ID_MISMATCH');
929
+ }
930
+ if (
931
+ runtime.schemaVersion !== 1 ||
932
+ !runtimeAbiHashes(runtime).has(generation.runtimeAbiHash)
933
+ ) {
934
+ throw new Error('MIAODA_CACHE_RUNTIME_ABI_MISMATCH');
935
+ }
936
+ const shouldResolveArtifact = name =>
937
+ (validateServerArtifacts &&
938
+ (generation.schemaVersion === 4
939
+ ? name === 'serverDependencies'
940
+ : name === 'serverBundle' ||
941
+ name === 'serverMetadata' ||
942
+ name.startsWith('serverRuntimeAsset:'))) ||
943
+ (validateViteArtifacts &&
944
+ (name === 'appRuntime' || name === 'viteMetadata'));
945
+ const artifacts = Object.fromEntries(
946
+ Object.entries(generation.artifacts || {})
947
+ .filter(([name]) => shouldResolveArtifact(name))
948
+ .map(([name, artifact]) => [
949
+ name,
950
+ resolveArtifact(resolvedCacheRoot, artifact),
951
+ ])
952
+ );
953
+ const requiredArtifacts = [
954
+ ...(validateServerArtifacts
955
+ ? generation.schemaVersion === 4
956
+ ? ['serverDependencies']
957
+ : ['serverBundle', 'serverMetadata']
958
+ : []),
959
+ ...(validateViteArtifacts ? ['appRuntime', 'viteMetadata'] : []),
960
+ ];
961
+ for (const name of requiredArtifacts) {
962
+ if (!artifacts[name]) {
963
+ throw new Error(`MIAODA_CACHE_GENERATION_ARTIFACT_MISSING: ${name}`);
964
+ }
965
+ }
966
+ if (
967
+ (validateViteArtifacts && !Array.isArray(generation.viteFiles)) ||
968
+ (validateViteArtifacts && generation.viteFiles.length === 0)
969
+ ) {
970
+ throw new Error('MIAODA_CACHE_GENERATION_VITE_FILES_MISSING');
971
+ }
972
+ if (
973
+ generation.schemaVersion === 3 &&
974
+ !Array.isArray(generation.sourceFiles)
975
+ ) {
976
+ throw new Error('MIAODA_CACHE_GENERATION_SOURCE_FILES_MISSING');
977
+ }
978
+ if (validateWorkspaceInputs && generation.schemaVersion === 3) {
979
+ const currentSourceFiles = listWorkspaceSourceFiles(
980
+ resolvedProjectRoot
981
+ ).filter(file => workspaceSourceFileInScope(file.path, artifactScope));
982
+ const sealedSourceFiles = generation.sourceFiles.filter(file =>
983
+ workspaceSourceFileInScope(file.path, artifactScope)
984
+ );
985
+ if (
986
+ JSON.stringify(currentSourceFiles) !== JSON.stringify(sealedSourceFiles)
987
+ ) {
988
+ throw new Error('MIAODA_CACHE_GENERATION_SOURCE_FILE_SET_MISMATCH');
989
+ }
990
+ if (
991
+ JSON.stringify(workspaceBuildInputs(resolvedProjectRoot)) !==
992
+ JSON.stringify(generation.workspaceBuildInputs)
993
+ ) {
994
+ throw new Error('MIAODA_CACHE_GENERATION_BUILD_INPUT_MISMATCH');
995
+ }
996
+ }
997
+ let viteFiles = [];
998
+ let serverMetadata;
999
+ let serverDependencies;
1000
+ let appRuntime;
1001
+ let viteMetadata;
1002
+ if (validateViteArtifacts) {
1003
+ const viteFilePaths = new Set();
1004
+ viteFiles = generation.viteFiles.map(artifact => {
1005
+ if (viteFilePaths.has(artifact?.path)) {
1006
+ throw new Error(
1007
+ `MIAODA_CACHE_GENERATION_VITE_FILE_DUPLICATE: ${artifact?.path}`
1008
+ );
1009
+ }
1010
+ viteFilePaths.add(artifact?.path);
1011
+ return resolveArtifact(resolvedCacheRoot, artifact);
1012
+ });
1013
+ if (!viteFilePaths.has(generation.artifacts.viteMetadata.path)) {
1014
+ throw new Error('MIAODA_CACHE_GENERATION_VITE_METADATA_UNSEALED');
1015
+ }
1016
+ const actualViteFiles = listRegularFiles(
1017
+ path.join(resolvedCacheRoot, 'vite'),
1018
+ resolvedCacheRoot
1019
+ );
1020
+ const sealedViteFiles = Array.from(viteFilePaths).sort();
1021
+ if (JSON.stringify(actualViteFiles) !== JSON.stringify(sealedViteFiles)) {
1022
+ throw new Error('MIAODA_CACHE_GENERATION_VITE_FILE_SET_MISMATCH');
1023
+ }
1024
+ appRuntime = readJson(artifacts.appRuntime);
1025
+ viteMetadata = readJson(artifacts.viteMetadata);
1026
+ }
1027
+ if (validateServerArtifacts && generation.schemaVersion === 4) {
1028
+ serverDependencies = readJson(artifacts.serverDependencies);
1029
+ if (!serverDependenciesAreConsumable(serverDependencies)) {
1030
+ throw new Error('MIAODA_SERVER_RUNTIME_DEPENDENCIES_REJECTED');
1031
+ }
1032
+ if (generation.serverRuntimeMode !== 'workspace-source-dependencies') {
1033
+ throw new Error('MIAODA_SERVER_RUNTIME_MODE_REJECTED');
1034
+ }
1035
+ if (
1036
+ !Array.isArray(generation.serverFiles) ||
1037
+ generation.serverFiles.length < 2
1038
+ ) {
1039
+ throw new Error('MIAODA_SERVER_RUNTIME_FILES_MISSING');
1040
+ }
1041
+ const sealedServerFiles = generation.serverFiles.map(artifact =>
1042
+ resolveArtifact(resolvedCacheRoot, artifact)
1043
+ );
1044
+ const actualServerFiles = listRegularFiles(
1045
+ path.join(resolvedCacheRoot, 'server'),
1046
+ resolvedCacheRoot
1047
+ );
1048
+ const sealedServerPaths = generation.serverFiles
1049
+ .map(artifact => artifact?.path)
1050
+ .sort();
1051
+ if (
1052
+ JSON.stringify(actualServerFiles) !== JSON.stringify(sealedServerPaths)
1053
+ ) {
1054
+ throw new Error('MIAODA_SERVER_RUNTIME_FILE_SET_MISMATCH');
1055
+ }
1056
+ const dependencyFilePaths = serverDependencies.files.map(
1057
+ file => `server/node_modules/${file.path}`
1058
+ );
1059
+ if (
1060
+ JSON.stringify(dependencyFilePaths.sort()) !==
1061
+ JSON.stringify(
1062
+ sealedServerPaths
1063
+ .filter(file => file.startsWith('server/node_modules/'))
1064
+ .sort()
1065
+ )
1066
+ ) {
1067
+ throw new Error('MIAODA_SERVER_RUNTIME_DEPENDENCY_SET_MISMATCH');
1068
+ }
1069
+ const bundleFile = path.join(
1070
+ resolvedCacheRoot,
1071
+ 'server',
1072
+ 'bundle',
1073
+ 'server.bundle.cjs'
1074
+ );
1075
+ const bundleMetadataFile = `${bundleFile}.meta.json`;
1076
+ const bundleExists = fs.existsSync(bundleFile);
1077
+ const bundleMetadataExists = fs.existsSync(bundleMetadataFile);
1078
+ if (bundleExists !== bundleMetadataExists) {
1079
+ throw new Error('MIAODA_SERVER_TRANSITION_BUNDLE_INCOMPLETE');
1080
+ }
1081
+ if (bundleExists) {
1082
+ const bundleMetadata = readJson(bundleMetadataFile);
1083
+ if (
1084
+ bundleMetadata?.schemaVersion !== 2 ||
1085
+ bundleMetadata?.consumable !== true ||
1086
+ !/^[a-f0-9]{64}$/.test(bundleMetadata?.workspaceSourceSha256 || '') ||
1087
+ sha256(bundleFile) !== bundleMetadata.bundleSha256
1088
+ ) {
1089
+ throw new Error('MIAODA_SERVER_TRANSITION_BUNDLE_REJECTED');
1090
+ }
1091
+ // Schema v4 seals the optional transition bundle through `serverFiles`
1092
+ // instead of the legacy `artifacts.serverBundle` field. Expose the
1093
+ // validated paths under the stable logical names used by the runtime.
1094
+ // Validation receipts will consequently protect these files as controls.
1095
+ artifacts.serverBundle = bundleFile;
1096
+ artifacts.serverMetadata = bundleMetadataFile;
1097
+ serverMetadata = bundleMetadata;
1098
+ }
1099
+ const cachedActionPlugins = Object.fromEntries(
1100
+ serverDependencies.actionPlugins.map(plugin => [
1101
+ plugin.name,
1102
+ plugin.version,
1103
+ ])
1104
+ );
1105
+ if (
1106
+ JSON.stringify(
1107
+ workspaceBuildInputs(resolvedProjectRoot).actionPlugins
1108
+ ) !== JSON.stringify(canonicalJson(cachedActionPlugins))
1109
+ ) {
1110
+ throw new Error('MIAODA_SERVER_RUNTIME_ACTION_PLUGIN_MISMATCH');
1111
+ }
1112
+ if (sealedServerFiles.length !== generation.serverFiles.length) {
1113
+ throw new Error('MIAODA_SERVER_RUNTIME_FILE_SET_MISMATCH');
1114
+ }
1115
+ const expectedRuntime = generation.serverRuntime || {};
1116
+ if (
1117
+ String(expectedRuntime.nodeModuleAbi) !== process.versions.modules ||
1118
+ expectedRuntime.platform !== process.platform ||
1119
+ expectedRuntime.arch !== process.arch ||
1120
+ String(expectedRuntime.nodeVersion || '').replace(/^v/, '') !==
1121
+ process.versions.node
1122
+ ) {
1123
+ throw new Error('MIAODA_SERVER_CACHE_ABI_MISMATCH');
1124
+ }
1125
+ }
1126
+ if (validateServerArtifacts && generation.schemaVersion === 3) {
1127
+ serverMetadata = readJson(artifacts.serverMetadata);
1128
+ if (
1129
+ JSON.stringify(
1130
+ jsonIdentity(stableServerMetadataIdentity(serverMetadata))
1131
+ ) !== JSON.stringify(generation.serverMetadataIdentity)
1132
+ ) {
1133
+ throw new Error('MIAODA_CACHE_SERVER_METADATA_IDENTITY_MISMATCH');
1134
+ }
1135
+ const expectedRuntime = generation.serverRuntime || {};
1136
+ const expectedNodeVersion = String(
1137
+ expectedRuntime.nodeVersion || ''
1138
+ ).replace(/^v/, '');
1139
+ const requiredProbeChecks = [
1140
+ 'isolated-node-modules',
1141
+ 'runtime-assets',
1142
+ 'http-readiness',
1143
+ 'business-api',
1144
+ 'process-alive',
1145
+ 'module-resolution',
1146
+ ];
1147
+ const probeChecks = serverMetadata.availabilityProbe?.checks;
1148
+ const probeCheckNames = Array.isArray(probeChecks)
1149
+ ? probeChecks.map(check => check?.name)
1150
+ : [];
1151
+ if (
1152
+ serverMetadata.schemaVersion !== 2 ||
1153
+ serverMetadata.consumable !== true ||
1154
+ !Array.isArray(serverMetadata.runtimeAssets) ||
1155
+ !Array.isArray(serverMetadata.externalImports) ||
1156
+ serverMetadata.externalImports.length > 0 ||
1157
+ !Array.isArray(serverMetadata.runtimeAssetWarnings) ||
1158
+ serverMetadata.runtimeAssetWarnings.some(
1159
+ warning =>
1160
+ typeof warning?.kind !== 'string' ||
1161
+ typeof warning?.source !== 'string' ||
1162
+ !/^[a-f0-9]{16}$/.test(warning?.expressionHash || '') ||
1163
+ typeof warning?.message !== 'string'
1164
+ ) ||
1165
+ !Array.isArray(serverMetadata.actionPlugins) ||
1166
+ serverMetadata.actionPlugins.some(
1167
+ plugin =>
1168
+ typeof plugin?.name !== 'string' ||
1169
+ !plugin.name ||
1170
+ typeof plugin?.version !== 'string' ||
1171
+ !plugin.version
1172
+ ) ||
1173
+ !Array.isArray(serverMetadata.dynamicImportWarnings) ||
1174
+ serverMetadata.dynamicImportWarnings.some(
1175
+ warning =>
1176
+ typeof warning?.kind !== 'string' ||
1177
+ typeof warning?.source !== 'string' ||
1178
+ !/^[a-f0-9]{16}$/.test(warning?.expressionHash || '') ||
1179
+ typeof warning?.message !== 'string'
1180
+ ) ||
1181
+ serverMetadata.bundleSha256 !==
1182
+ generation.artifacts.serverBundle.sha256 ||
1183
+ serverMetadata.availabilityProbe?.success !== true ||
1184
+ serverMetadata.availabilityProbe?.nodeModulesPresent !== false ||
1185
+ !Array.isArray(probeChecks) ||
1186
+ probeChecks.some(
1187
+ check => typeof check?.name !== 'string' || check?.success !== true
1188
+ ) ||
1189
+ new Set(probeCheckNames).size !== probeCheckNames.length ||
1190
+ requiredProbeChecks.some(name => !probeCheckNames.includes(name)) ||
1191
+ (serverMetadata.actionPlugins.length > 0 &&
1192
+ !probeCheckNames.includes('action-plugin-registry'))
1193
+ ) {
1194
+ throw new Error('MIAODA_SERVER_CACHE_METADATA_REJECTED');
1195
+ }
1196
+ const expectedRuntimeAssetKeys = [];
1197
+ const runtimeAssetPaths = new Set();
1198
+ for (const asset of serverMetadata.runtimeAssets) {
1199
+ const assetPath = asset?.path;
1200
+ if (
1201
+ typeof assetPath !== 'string' ||
1202
+ !assetPath ||
1203
+ assetPath !== path.posix.normalize(assetPath) ||
1204
+ assetPath === '.' ||
1205
+ assetPath.startsWith('../') ||
1206
+ path.posix.isAbsolute(assetPath) ||
1207
+ assetPath.includes('\\') ||
1208
+ runtimeAssetPaths.has(assetPath)
1209
+ ) {
1210
+ throw new Error('MIAODA_SERVER_CACHE_RUNTIME_ASSET_REJECTED');
1211
+ }
1212
+ runtimeAssetPaths.add(assetPath);
1213
+ const key = `serverRuntimeAsset:${assetPath}`;
1214
+ const generationArtifact = generation.artifacts[key];
1215
+ if (
1216
+ !generationArtifact ||
1217
+ generationArtifact.path !== path.posix.join('server', assetPath) ||
1218
+ generationArtifact.sha256 !== asset.sha256 ||
1219
+ generationArtifact.size !== asset.size ||
1220
+ !artifacts[key]
1221
+ ) {
1222
+ throw new Error('MIAODA_SERVER_CACHE_RUNTIME_ASSET_REJECTED');
1223
+ }
1224
+ expectedRuntimeAssetKeys.push(key);
1225
+ }
1226
+ const sealedRuntimeAssetKeys = Object.keys(generation.artifacts)
1227
+ .filter(key => key.startsWith('serverRuntimeAsset:'))
1228
+ .sort();
1229
+ if (
1230
+ JSON.stringify(expectedRuntimeAssetKeys.sort()) !==
1231
+ JSON.stringify(sealedRuntimeAssetKeys)
1232
+ ) {
1233
+ throw new Error('MIAODA_SERVER_CACHE_RUNTIME_ASSET_SET_MISMATCH');
1234
+ }
1235
+ const sealedServerFiles = Object.values(generation.artifacts)
1236
+ .map(artifact => artifact?.path)
1237
+ .filter(
1238
+ artifactPath =>
1239
+ typeof artifactPath === 'string' && artifactPath.startsWith('server/')
1240
+ )
1241
+ .sort();
1242
+ const actualServerFiles = listRegularFiles(
1243
+ path.join(resolvedCacheRoot, 'server'),
1244
+ resolvedCacheRoot
1245
+ );
1246
+ if (
1247
+ JSON.stringify(actualServerFiles) !== JSON.stringify(sealedServerFiles)
1248
+ ) {
1249
+ throw new Error('MIAODA_SERVER_CACHE_FILE_SET_MISMATCH');
1250
+ }
1251
+ if (
1252
+ expectedRuntime.nodeModuleAbi !== process.versions.modules ||
1253
+ expectedRuntime.platform !== process.platform ||
1254
+ expectedRuntime.arch !== process.arch ||
1255
+ expectedNodeVersion !== process.versions.node
1256
+ ) {
1257
+ throw new Error('MIAODA_SERVER_CACHE_ABI_MISMATCH');
1258
+ }
1259
+ }
1260
+ if (
1261
+ (validateViteArtifacts && appRuntime.schemaVersion !== 1) ||
1262
+ (validateViteArtifacts &&
1263
+ appRuntime.runtimeAbiHash !== generation.runtimeAbiHash)
1264
+ ) {
1265
+ throw new Error('MIAODA_APP_RUNTIME_ABI_MISMATCH');
1266
+ }
1267
+ if (validateViteArtifacts) {
1268
+ validateClientConfigFiles(resolvedProjectRoot, appRuntime);
1269
+ }
1270
+ if (
1271
+ (validateViteArtifacts &&
1272
+ !/^[a-f0-9]{64}$/.test(appRuntime.clientDependencyGraphHash || '')) ||
1273
+ (validateViteArtifacts &&
1274
+ (appRuntime.clientDependencyGraph?.schemaVersion !== 1 ||
1275
+ appRuntime.clientDependencyGraph?.runtimeAbiHash !==
1276
+ generation.runtimeAbiHash))
1277
+ ) {
1278
+ throw new Error('MIAODA_CLIENT_DEPENDENCY_GRAPH_INVALID');
1279
+ }
1280
+ if (validateViteArtifacts && validateConfigEnvironment) {
1281
+ validateClientConfigEnvironment(appRuntime);
1282
+ }
1283
+ if (validateViteArtifacts) {
1284
+ const optimizedDependencies = new Set(
1285
+ Object.keys(viteMetadata.optimized || {})
1286
+ );
1287
+ const missingViteDependencies = Array.from(
1288
+ new Set(appRuntime.optimizeDependencies || [])
1289
+ ).filter(specifier => !optimizedDependencies.has(specifier));
1290
+ if (missingViteDependencies.length > 0) {
1291
+ throw new Error(
1292
+ `MIAODA_VITE_CACHE_DEPENDENCY_COVERAGE_MISSING: ${missingViteDependencies.join(', ')}`
1293
+ );
1294
+ }
1295
+ const graphPackageNames = new Set(
1296
+ (appRuntime.clientDependencyGraph?.entries || [])
1297
+ .map(entry => packageNameFromOptimizerId(entry.specifier || ''))
1298
+ .filter(Boolean)
1299
+ );
1300
+ const optimizerEntries = [
1301
+ ...Object.entries(viteMetadata.optimized || {}),
1302
+ ...Object.entries(viteMetadata.discovered || {}),
1303
+ ];
1304
+ const uncoveredOptimizerPackages = Array.from(
1305
+ new Set(
1306
+ optimizerEntries.flatMap(([id, entry]) => {
1307
+ const name = packageNameFromOptimizerId(id);
1308
+ if (!name || graphPackageNames.has(name)) return [];
1309
+ return optimizerEntryUsesRuntimePackage({
1310
+ projectRoot: resolvedProjectRoot,
1311
+ runtimeManifestFile: resolvedRuntimeManifest,
1312
+ runtime,
1313
+ packageName: name,
1314
+ entry,
1315
+ })
1316
+ ? []
1317
+ : [name];
1318
+ })
1319
+ )
1320
+ ).sort();
1321
+ if (uncoveredOptimizerPackages.length > 0) {
1322
+ throw new Error(
1323
+ `MIAODA_VITE_OPTIMIZER_GRAPH_TOOLCHAIN_COVERAGE_MISSING: ${uncoveredOptimizerPackages.join(', ')}`
1324
+ );
1325
+ }
1326
+ }
1327
+ return {
1328
+ projectRoot: resolvedProjectRoot,
1329
+ cacheRoot: resolvedCacheRoot,
1330
+ runtimeManifestFile: resolvedRuntimeManifest,
1331
+ generationFile,
1332
+ generation,
1333
+ runtime,
1334
+ artifacts,
1335
+ serverMetadata,
1336
+ serverDependencies,
1337
+ appRuntime,
1338
+ viteFiles,
1339
+ validation: {
1340
+ artifactScope,
1341
+ validateConfigEnvironment,
1342
+ validateWorkspaceInputs,
1343
+ validatedBy: 'full-sha',
1344
+ },
1345
+ };
1346
+ }