@tiangong-lca/cli 0.1.8 → 0.1.10
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 +18 -5
- package/assets/runtime/cli-runtime-descriptor.schema.json +138 -0
- package/assets/runtime/cli-runtime-expectation.schema.json +38 -0
- package/assets/runtime/runtime-bootstrap-lock.schema.json +254 -0
- package/assets/runtime/runtime-command-result.schema.json +95 -0
- package/assets/runtime/runtime-manifest.schema.json +380 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +48 -2
- package/dist/src/cli.js.map +1 -1
- package/dist/src/lib/dataset-import-lca.js +2 -2
- package/dist/src/lib/dataset-import-lca.js.map +1 -1
- package/dist/src/lib/dataset-support-cache.d.ts +48 -0
- package/dist/src/lib/dataset-support-cache.js +265 -0
- package/dist/src/lib/dataset-support-cache.js.map +1 -0
- package/dist/src/lib/runtime/archive-writer.d.ts +7 -0
- package/dist/src/lib/runtime/archive-writer.js +89 -0
- package/dist/src/lib/runtime/archive-writer.js.map +1 -0
- package/dist/src/lib/runtime/archive.d.ts +2 -0
- package/dist/src/lib/runtime/archive.js +122 -0
- package/dist/src/lib/runtime/archive.js.map +1 -0
- package/dist/src/lib/runtime/command.d.ts +8 -0
- package/dist/src/lib/runtime/command.js +57 -0
- package/dist/src/lib/runtime/command.js.map +1 -0
- package/dist/src/lib/runtime/descriptor.d.ts +11 -0
- package/dist/src/lib/runtime/descriptor.js +126 -0
- package/dist/src/lib/runtime/descriptor.js.map +1 -0
- package/dist/src/lib/runtime/download.d.ts +9 -0
- package/dist/src/lib/runtime/download.js +118 -0
- package/dist/src/lib/runtime/download.js.map +1 -0
- package/dist/src/lib/runtime/exec-command.d.ts +7 -0
- package/dist/src/lib/runtime/exec-command.js +83 -0
- package/dist/src/lib/runtime/exec-command.js.map +1 -0
- package/dist/src/lib/runtime/execute.d.ts +11 -0
- package/dist/src/lib/runtime/execute.js +125 -0
- package/dist/src/lib/runtime/execute.js.map +1 -0
- package/dist/src/lib/runtime/files.d.ts +7 -0
- package/dist/src/lib/runtime/files.js +91 -0
- package/dist/src/lib/runtime/files.js.map +1 -0
- package/dist/src/lib/runtime/host.d.ts +8 -0
- package/dist/src/lib/runtime/host.js +37 -0
- package/dist/src/lib/runtime/host.js.map +1 -0
- package/dist/src/lib/runtime/leases.d.ts +12 -0
- package/dist/src/lib/runtime/leases.js +72 -0
- package/dist/src/lib/runtime/leases.js.map +1 -0
- package/dist/src/lib/runtime/managed-command.d.ts +6 -0
- package/dist/src/lib/runtime/managed-command.js +99 -0
- package/dist/src/lib/runtime/managed-command.js.map +1 -0
- package/dist/src/lib/runtime/manager.d.ts +33 -0
- package/dist/src/lib/runtime/manager.js +196 -0
- package/dist/src/lib/runtime/manager.js.map +1 -0
- package/dist/src/lib/runtime/manifest-types.d.ts +73 -0
- package/dist/src/lib/runtime/manifest-types.js +4 -0
- package/dist/src/lib/runtime/manifest-types.js.map +1 -0
- package/dist/src/lib/runtime/manifest-values.d.ts +15 -0
- package/dist/src/lib/runtime/manifest-values.js +124 -0
- package/dist/src/lib/runtime/manifest-values.js.map +1 -0
- package/dist/src/lib/runtime/manifest.d.ts +7 -0
- package/dist/src/lib/runtime/manifest.js +220 -0
- package/dist/src/lib/runtime/manifest.js.map +1 -0
- package/dist/src/lib/runtime/process.d.ts +3 -0
- package/dist/src/lib/runtime/process.js +55 -0
- package/dist/src/lib/runtime/process.js.map +1 -0
- package/dist/src/lib/runtime/storage.d.ts +8 -0
- package/dist/src/lib/runtime/storage.js +142 -0
- package/dist/src/lib/runtime/storage.js.map +1 -0
- package/dist/src/lib/runtime/types.d.ts +45 -0
- package/dist/src/lib/runtime/types.js +9 -0
- package/dist/src/lib/runtime/types.js.map +1 -0
- package/dist/src/lib/supabase-data-api-contract.d.ts +4 -4
- package/dist/src/lib/supabase-data-api-contract.js +2 -0
- package/dist/src/lib/supabase-data-api-contract.js.map +1 -1
- package/dist/src/main.js +13 -1
- package/dist/src/main.js.map +1 -1
- package/dist/src/runtime.d.ts +15 -0
- package/dist/src/runtime.js +22 -0
- package/dist/src/runtime.js.map +1 -0
- package/package.json +8 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-writer.js","sourceRoot":"","sources":["../../../../src/lib/runtime/archive-writer.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { createGzip } from 'node:zlib';\nimport { pipeline } from 'node:stream/promises';\nimport { createHash } from 'node:crypto';\nimport { cachePath } from './storage.js';\nimport { hashRuntimeFile, runtimeError } from './files.js';\nimport { array, integer, relativeFile, sha, unique } from './manifest-values.js';\nimport type { ComponentFile } from './manifest-types.js';\n\nexport function runtimeTarHeader(file: ComponentFile): Buffer {\n const parts = file.path.split('/');\n let name = file.path,\n prefix = '';\n if (Buffer.byteLength(name) > 100) {\n for (let split = parts.length - 1; split > 0; split--) {\n const head = parts.slice(0, split).join('/'),\n tail = parts.slice(split).join('/');\n if (Buffer.byteLength(head) <= 155 && Buffer.byteLength(tail) <= 100) {\n prefix = head;\n name = tail;\n break;\n }\n }\n }\n const header = Buffer.alloc(512);\n header.write(name, 0, 100, 'utf8');\n header.write(prefix, 345, 155, 'utf8');\n for (const [offset, length, value] of [\n [100, 8, file.mode],\n [108, 8, 0],\n [116, 8, 0],\n [124, 12, file.bytes],\n [136, 12, 0],\n ] as const)\n header.write(value.toString(8).padStart(length - 1, '0') + '\\0', offset, length, 'ascii');\n header.fill(32, 148, 156);\n header[156] = 48;\n header.write('ustar\\0', 257, 6, 'ascii');\n header.write('00', 263, 2, 'ascii');\n const checksum = header.reduce((sum, value) => sum + value, 0);\n header.write(checksum.toString(8).padStart(6, '0') + '\\0 ', 148, 8, 'ascii');\n return header;\n}\n/** Release-side writer. The producer supplies an explicit, already materialized production file inventory. */\nexport async function writeRuntimeComponentArchive(\n root: string,\n files: readonly ComponentFile[],\n target: string,\n): Promise<{ bytes: number; sha256: string }> {\n array(files, 50_000, 1);\n unique(\n files.map((file) => relativeFile(file.path).toLowerCase()),\n 'archive file',\n );\n if (\n JSON.stringify(files.map((file) => file.path)) !==\n JSON.stringify(files.map((file) => file.path).sort())\n )\n runtimeError(\n 'RUNTIME_ARCHIVE_ORDER',\n 'Component archive files must be sorted by portable path.',\n );\n let total = 0;\n for (const file of files) {\n integer(file.bytes, 512 * 1024 * 1024);\n sha(file.sha256);\n if (file.mode !== 420 && file.mode !== 493)\n runtimeError('RUNTIME_ARCHIVE_MODE', 'Component file modes must be 0644 or 0755.');\n total += file.bytes;\n if (total > 2 * 1024 * 1024 * 1024)\n runtimeError('RUNTIME_ARCHIVE_SIZE', 'Component files exceed the unpacked size limit.');\n }\n for (const file of files) {\n const actual = hashRuntimeFile(cachePath(root, file.path), file.path);\n if (actual.bytes !== file.bytes || actual.sha256 !== file.sha256)\n runtimeError('RUNTIME_ARCHIVE_SOURCE_CHANGED', 'Component source changed before packaging.');\n }\n async function* records() {\n for (const file of files) {\n yield runtimeTarHeader(file);\n const hash = createHash('sha256');\n let bytes = 0;\n for await (const chunk of fs.createReadStream(cachePath(root, file.path))) {\n const buffer = chunk as Buffer;\n bytes += buffer.length;\n if (bytes > file.bytes)\n runtimeError('RUNTIME_ARCHIVE_SOURCE_CHANGED', 'Component source grew while packaging.');\n hash.update(buffer);\n yield buffer;\n }\n if (bytes !== file.bytes || hash.digest('hex') !== file.sha256)\n runtimeError(\n 'RUNTIME_ARCHIVE_SOURCE_CHANGED',\n 'Component source bytes changed while packaging.',\n );\n const padding = (512 - (file.bytes % 512)) % 512;\n if (padding) yield Buffer.alloc(padding);\n }\n yield Buffer.alloc(1024);\n }\n fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });\n await pipeline(\n records(),\n createGzip({ level: 9 }),\n fs.createWriteStream(target, { flags: 'wx', mode: 0o600 }),\n );\n const fact = hashRuntimeFile(target, 'archive');\n return { bytes: fact.bytes, sha256: fact.sha256 };\n}\n"]}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { createGunzip } from 'node:zlib';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
import { cachePath, writeAll } from './storage.js';
|
|
7
|
+
import { runtimeError } from './files.js';
|
|
8
|
+
function field(header, start, length) {
|
|
9
|
+
const bytes = header.subarray(start, start + length);
|
|
10
|
+
const zero = bytes.indexOf(0);
|
|
11
|
+
if (zero >= 0 && bytes.subarray(zero).some((value) => value !== 0))
|
|
12
|
+
runtimeError('RUNTIME_ARCHIVE_HEADER', 'USTAR text padding is invalid.');
|
|
13
|
+
try {
|
|
14
|
+
return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(zero < 0 ? bytes : bytes.subarray(0, zero));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
runtimeError('RUNTIME_ARCHIVE_HEADER', 'USTAR paths must be valid UTF-8.');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function octal(bytes) {
|
|
21
|
+
const raw = bytes.toString('ascii');
|
|
22
|
+
let end = raw.length;
|
|
23
|
+
while (end > 0 && (raw[end - 1] === '\0' || raw[end - 1] === ' '))
|
|
24
|
+
end--;
|
|
25
|
+
const digits = raw.slice(0, end);
|
|
26
|
+
if (!/^[0-7]+$/u.test(digits))
|
|
27
|
+
runtimeError('RUNTIME_ARCHIVE_HEADER', 'USTAR numeric fields must use bounded octal.');
|
|
28
|
+
// All callers supply at most twelve octets; the value is within exact integer range.
|
|
29
|
+
return Number.parseInt(digits, 8);
|
|
30
|
+
}
|
|
31
|
+
function readExact(fd, buffer, position) {
|
|
32
|
+
let offset = 0;
|
|
33
|
+
while (offset < buffer.length) {
|
|
34
|
+
const count = fs.readSync(fd, buffer, offset, buffer.length - offset, position + offset);
|
|
35
|
+
if (count === 0)
|
|
36
|
+
runtimeError('RUNTIME_ARCHIVE_TRUNCATED', 'Runtime archive ended before its declared content.');
|
|
37
|
+
offset += count;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function extractRuntimeArchive(archive, root, component, signal) {
|
|
41
|
+
const decoded = `${root}.tar`;
|
|
42
|
+
const expectedBytes = 1024 + component.files.reduce((sum, file) => sum + 512 + Math.ceil(file.bytes / 512) * 512, 0);
|
|
43
|
+
let decodedBytes = 0;
|
|
44
|
+
await pipeline(fs.createReadStream(archive), createGunzip(), async function* (source) {
|
|
45
|
+
for await (const chunk of source) {
|
|
46
|
+
decodedBytes += chunk.length;
|
|
47
|
+
if (decodedBytes > expectedBytes)
|
|
48
|
+
runtimeError('RUNTIME_ARCHIVE_SIZE', 'Decoded archive exceeds its exact inventory size.');
|
|
49
|
+
yield chunk;
|
|
50
|
+
}
|
|
51
|
+
}, fs.createWriteStream(decoded, { flags: 'wx', mode: 0o600 }), { signal });
|
|
52
|
+
if (decodedBytes !== expectedBytes)
|
|
53
|
+
runtimeError('RUNTIME_ARCHIVE_SIZE', 'Decoded archive size does not match its inventory.');
|
|
54
|
+
fs.mkdirSync(root, { mode: 0o700 });
|
|
55
|
+
const fd = fs.openSync(decoded, 'r');
|
|
56
|
+
let position = 0;
|
|
57
|
+
try {
|
|
58
|
+
for (const file of component.files) {
|
|
59
|
+
signal?.throwIfAborted();
|
|
60
|
+
const header = Buffer.alloc(512);
|
|
61
|
+
readExact(fd, header, position);
|
|
62
|
+
position += 512;
|
|
63
|
+
const checksum = octal(header.subarray(148, 156));
|
|
64
|
+
const unsigned = Buffer.from(header);
|
|
65
|
+
unsigned.fill(32, 148, 156);
|
|
66
|
+
if (unsigned.reduce((sum, value) => sum + value, 0) !== checksum ||
|
|
67
|
+
header.toString('ascii', 257, 263) !== 'ustar\0' ||
|
|
68
|
+
header.toString('ascii', 263, 265) !== '00' ||
|
|
69
|
+
![0, 48].includes(header[156]))
|
|
70
|
+
runtimeError('RUNTIME_ARCHIVE_HEADER', 'Runtime archives require regular-file USTAR entries only.');
|
|
71
|
+
const prefix = field(header, 345, 155), name = field(header, 0, 100);
|
|
72
|
+
const relative = prefix ? `${prefix}/${name}` : name;
|
|
73
|
+
if (relative !== file.path ||
|
|
74
|
+
octal(header.subarray(100, 108)) !== file.mode ||
|
|
75
|
+
octal(header.subarray(124, 136)) !== file.bytes ||
|
|
76
|
+
field(header, 157, 100) !== '')
|
|
77
|
+
runtimeError('RUNTIME_ARCHIVE_ENTRY', 'Archive path, mode or size differs from its trusted inventory.');
|
|
78
|
+
const output = cachePath(root, file.path);
|
|
79
|
+
fs.mkdirSync(path.dirname(output), { recursive: true, mode: 0o700 });
|
|
80
|
+
cachePath(root, file.path);
|
|
81
|
+
const out = fs.openSync(output, 'wx', 0o600);
|
|
82
|
+
const hash = createHash('sha256');
|
|
83
|
+
let remaining = file.bytes;
|
|
84
|
+
const buffer = Buffer.alloc(1024 * 1024);
|
|
85
|
+
try {
|
|
86
|
+
while (remaining > 0) {
|
|
87
|
+
signal?.throwIfAborted();
|
|
88
|
+
const chunk = buffer.subarray(0, Math.min(buffer.length, remaining));
|
|
89
|
+
readExact(fd, chunk, position);
|
|
90
|
+
position += chunk.length;
|
|
91
|
+
remaining -= chunk.length;
|
|
92
|
+
hash.update(chunk);
|
|
93
|
+
writeAll(out, chunk);
|
|
94
|
+
}
|
|
95
|
+
fs.fsyncSync(out);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
fs.closeSync(out);
|
|
99
|
+
}
|
|
100
|
+
if (hash.digest('hex') !== file.sha256)
|
|
101
|
+
runtimeError('RUNTIME_ARCHIVE_INTEGRITY', 'An extracted file does not match its trusted SHA-256.');
|
|
102
|
+
fs.chmodSync(output, file.mode);
|
|
103
|
+
const padding = (512 - (file.bytes % 512)) % 512;
|
|
104
|
+
if (padding) {
|
|
105
|
+
const bytes = Buffer.alloc(padding);
|
|
106
|
+
readExact(fd, bytes, position);
|
|
107
|
+
if (bytes.some((value) => value !== 0))
|
|
108
|
+
runtimeError('RUNTIME_ARCHIVE_PADDING', 'USTAR file padding must be empty.');
|
|
109
|
+
position += padding;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const end = Buffer.alloc(1024);
|
|
113
|
+
readExact(fd, end, position);
|
|
114
|
+
if (end.some((value) => value !== 0))
|
|
115
|
+
runtimeError('RUNTIME_ARCHIVE_TRAILING', 'Runtime archive has unexpected entries or trailing content.');
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
fs.closeSync(fd);
|
|
119
|
+
fs.unlinkSync(decoded);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../../../../src/lib/runtime/archive.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { createGunzip } from 'node:zlib';\nimport { pipeline } from 'node:stream/promises';\nimport { cachePath, writeAll } from './storage.js';\nimport { runtimeError } from './files.js';\nimport type { RuntimeComponent } from './manifest-types.js';\n\nfunction field(header: Buffer, start: number, length: number): string {\n const bytes = header.subarray(start, start + length);\n const zero = bytes.indexOf(0);\n if (zero >= 0 && bytes.subarray(zero).some((value) => value !== 0))\n runtimeError('RUNTIME_ARCHIVE_HEADER', 'USTAR text padding is invalid.');\n try {\n return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(\n zero < 0 ? bytes : bytes.subarray(0, zero),\n );\n } catch {\n runtimeError('RUNTIME_ARCHIVE_HEADER', 'USTAR paths must be valid UTF-8.');\n }\n}\nfunction octal(bytes: Buffer): number {\n const raw = bytes.toString('ascii');\n let end = raw.length;\n while (end > 0 && (raw[end - 1] === '\\0' || raw[end - 1] === ' ')) end--;\n const digits = raw.slice(0, end);\n if (!/^[0-7]+$/u.test(digits))\n runtimeError('RUNTIME_ARCHIVE_HEADER', 'USTAR numeric fields must use bounded octal.');\n // All callers supply at most twelve octets; the value is within exact integer range.\n return Number.parseInt(digits, 8);\n}\nfunction readExact(fd: number, buffer: Buffer, position: number): void {\n let offset = 0;\n while (offset < buffer.length) {\n const count = fs.readSync(fd, buffer, offset, buffer.length - offset, position + offset);\n if (count === 0)\n runtimeError(\n 'RUNTIME_ARCHIVE_TRUNCATED',\n 'Runtime archive ended before its declared content.',\n );\n offset += count;\n }\n}\nexport async function extractRuntimeArchive(\n archive: string,\n root: string,\n component: RuntimeComponent,\n signal?: AbortSignal,\n): Promise<void> {\n const decoded = `${root}.tar`;\n const expectedBytes =\n 1024 + component.files.reduce((sum, file) => sum + 512 + Math.ceil(file.bytes / 512) * 512, 0);\n let decodedBytes = 0;\n await pipeline(\n fs.createReadStream(archive),\n createGunzip(),\n async function* (source: AsyncIterable<Buffer>) {\n for await (const chunk of source) {\n decodedBytes += chunk.length;\n if (decodedBytes > expectedBytes)\n runtimeError('RUNTIME_ARCHIVE_SIZE', 'Decoded archive exceeds its exact inventory size.');\n yield chunk;\n }\n },\n fs.createWriteStream(decoded, { flags: 'wx', mode: 0o600 }),\n { signal },\n );\n if (decodedBytes !== expectedBytes)\n runtimeError('RUNTIME_ARCHIVE_SIZE', 'Decoded archive size does not match its inventory.');\n fs.mkdirSync(root, { mode: 0o700 });\n const fd = fs.openSync(decoded, 'r');\n let position = 0;\n try {\n for (const file of component.files) {\n signal?.throwIfAborted();\n const header = Buffer.alloc(512);\n readExact(fd, header, position);\n position += 512;\n const checksum = octal(header.subarray(148, 156));\n const unsigned = Buffer.from(header);\n unsigned.fill(32, 148, 156);\n if (\n unsigned.reduce((sum, value) => sum + value, 0) !== checksum ||\n header.toString('ascii', 257, 263) !== 'ustar\\0' ||\n header.toString('ascii', 263, 265) !== '00' ||\n ![0, 48].includes(header[156]!)\n )\n runtimeError(\n 'RUNTIME_ARCHIVE_HEADER',\n 'Runtime archives require regular-file USTAR entries only.',\n );\n const prefix = field(header, 345, 155),\n name = field(header, 0, 100);\n const relative = prefix ? `${prefix}/${name}` : name;\n if (\n relative !== file.path ||\n octal(header.subarray(100, 108)) !== file.mode ||\n octal(header.subarray(124, 136)) !== file.bytes ||\n field(header, 157, 100) !== ''\n )\n runtimeError(\n 'RUNTIME_ARCHIVE_ENTRY',\n 'Archive path, mode or size differs from its trusted inventory.',\n );\n const output = cachePath(root, file.path);\n fs.mkdirSync(path.dirname(output), { recursive: true, mode: 0o700 });\n cachePath(root, file.path);\n const out = fs.openSync(output, 'wx', 0o600);\n const hash = createHash('sha256');\n let remaining = file.bytes;\n const buffer = Buffer.alloc(1024 * 1024);\n try {\n while (remaining > 0) {\n signal?.throwIfAborted();\n const chunk = buffer.subarray(0, Math.min(buffer.length, remaining));\n readExact(fd, chunk, position);\n position += chunk.length;\n remaining -= chunk.length;\n hash.update(chunk);\n writeAll(out, chunk);\n }\n fs.fsyncSync(out);\n } finally {\n fs.closeSync(out);\n }\n if (hash.digest('hex') !== file.sha256)\n runtimeError(\n 'RUNTIME_ARCHIVE_INTEGRITY',\n 'An extracted file does not match its trusted SHA-256.',\n );\n fs.chmodSync(output, file.mode);\n const padding = (512 - (file.bytes % 512)) % 512;\n if (padding) {\n const bytes = Buffer.alloc(padding);\n readExact(fd, bytes, position);\n if (bytes.some((value) => value !== 0))\n runtimeError('RUNTIME_ARCHIVE_PADDING', 'USTAR file padding must be empty.');\n position += padding;\n }\n }\n const end = Buffer.alloc(1024);\n readExact(fd, end, position);\n if (end.some((value) => value !== 0))\n runtimeError(\n 'RUNTIME_ARCHIVE_TRAILING',\n 'Runtime archive has unexpected entries or trailing content.',\n );\n } finally {\n fs.closeSync(fd);\n fs.unlinkSync(decoded);\n }\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FetchLike } from '../http.js';
|
|
2
|
+
import type { CliRuntimeDescriptor } from './types.js';
|
|
3
|
+
export declare function isRuntimeCommand(argv: readonly string[]): boolean;
|
|
4
|
+
export declare function runRuntimeCommand(subcommand: string | null, args: string[], describe?: () => CliRuntimeDescriptor, fetchImpl?: FetchLike, env?: NodeJS.ProcessEnv): Promise<{
|
|
5
|
+
exitCode: number;
|
|
6
|
+
stdout: string;
|
|
7
|
+
stderr: string;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { runRuntimeExecCommand } from './exec-command.js';
|
|
3
|
+
import { runManagedRuntimeCommand } from './managed-command.js';
|
|
4
|
+
import { CliError } from '../errors.js';
|
|
5
|
+
import { describeCliRuntime } from '../../runtime.js';
|
|
6
|
+
export function isRuntimeCommand(argv) {
|
|
7
|
+
let index = 0;
|
|
8
|
+
while (['--help', '-h', '--version', '-v'].includes(argv[index] ?? ''))
|
|
9
|
+
index += 1;
|
|
10
|
+
if (argv[index] === '--')
|
|
11
|
+
index += 1;
|
|
12
|
+
return argv[index] === 'runtime';
|
|
13
|
+
}
|
|
14
|
+
export async function runRuntimeCommand(subcommand, args, describe = describeCliRuntime, fetchImpl, env) {
|
|
15
|
+
if (subcommand === 'exec')
|
|
16
|
+
return runRuntimeExecCommand(args, fetchImpl, env);
|
|
17
|
+
if (subcommand && ['ensure', 'status', 'prune', 'lease-release'].includes(subcommand))
|
|
18
|
+
return runManagedRuntimeCommand(subcommand, args, fetchImpl);
|
|
19
|
+
let flags;
|
|
20
|
+
try {
|
|
21
|
+
flags = parseArgs({
|
|
22
|
+
args,
|
|
23
|
+
strict: true,
|
|
24
|
+
allowPositionals: false,
|
|
25
|
+
options: {
|
|
26
|
+
help: { type: 'boolean', short: 'h' },
|
|
27
|
+
json: { type: 'boolean' },
|
|
28
|
+
},
|
|
29
|
+
}).values;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new CliError('Runtime commands accept only their declared flags.', {
|
|
33
|
+
code: 'RUNTIME_ARGUMENT_INVALID',
|
|
34
|
+
exitCode: 2,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (!subcommand || flags.help)
|
|
38
|
+
return {
|
|
39
|
+
exitCode: 0,
|
|
40
|
+
stdout: 'Usage: tiangong-lca runtime describe [--json] | ensure | status | prune | lease-release | exec\n\nDescribe the installed CLI package, assets and Node executable. No authentication, user env file or download.\n',
|
|
41
|
+
stderr: '',
|
|
42
|
+
};
|
|
43
|
+
if (subcommand !== 'describe')
|
|
44
|
+
throw new CliError('Unknown runtime operation.', {
|
|
45
|
+
code: 'RUNTIME_OPERATION_UNKNOWN',
|
|
46
|
+
exitCode: 2,
|
|
47
|
+
});
|
|
48
|
+
const descriptor = describe();
|
|
49
|
+
return {
|
|
50
|
+
exitCode: 0,
|
|
51
|
+
stdout: flags.json
|
|
52
|
+
? `${JSON.stringify(descriptor)}\n`
|
|
53
|
+
: `CLI ${descriptor.package.version} (${descriptor.platform})\nNode ${descriptor.node.version}\nContent ${descriptor.content_sha256}\n`,
|
|
54
|
+
stderr: '',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../../../src/lib/runtime/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAGtD,MAAM,UAAU,gBAAgB,CAAC,IAAuB;IACtD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAAE,KAAK,IAAI,CAAC,CAAC;IACnF,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI;QAAE,KAAK,IAAI,CAAC,CAAC;IACrC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAyB,EACzB,IAAc,EACd,QAAQ,GAA+B,kBAAkB,EACzD,SAAqB,EACrB,GAAuB;IAEvB,IAAI,UAAU,KAAK,MAAM;QAAE,OAAO,qBAAqB,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;IAC9E,IAAI,UAAU,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC;QACnF,OAAO,wBAAwB,CAAC,UAAU,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAC/D,IAAI,KAAyC,CAAC;IAC9C,IAAI,CAAC;QACH,KAAK,GAAG,SAAS,CAAC;YAChB,IAAI;YACJ,MAAM,EAAE,IAAI;YACZ,gBAAgB,EAAE,KAAK;YACvB,OAAO,EAAE;gBACP,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;gBACrC,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;aAC1B;SACF,CAAC,CAAC,MAAM,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,QAAQ,CAAC,oDAAoD,EAAE;YACvE,IAAI,EAAE,0BAA0B;YAChC,QAAQ,EAAE,CAAC;SACZ,CAAC,CAAC;IACL,CAAC;IACD,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC,IAAI;QAC3B,OAAO;YACL,QAAQ,EAAE,CAAC;YACX,MAAM,EACJ,mNAAmN;YACrN,MAAM,EAAE,EAAE;SACX,CAAC;IACJ,IAAI,UAAU,KAAK,UAAU;QAC3B,MAAM,IAAI,QAAQ,CAAC,4BAA4B,EAAE;YAC/C,IAAI,EAAE,2BAA2B;YACjC,QAAQ,EAAE,CAAC;SACZ,CAAC,CAAC;IACL,MAAM,UAAU,GAAG,QAAQ,EAAE,CAAC;IAC9B,OAAO;QACL,QAAQ,EAAE,CAAC;QACX,MAAM,EAAE,KAAK,CAAC,IAAI;YAChB,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI;YACnC,CAAC,CAAC,OAAO,UAAU,CAAC,OAAO,CAAC,OAAO,KAAK,UAAU,CAAC,QAAQ,WAAW,UAAU,CAAC,IAAI,CAAC,OAAO,aAAa,UAAU,CAAC,cAAc,IAAI;QACzI,MAAM,EAAE,EAAE;KACX,CAAC;AACJ,CAAC","sourcesContent":["import { parseArgs } from 'node:util';\nimport type { FetchLike } from '../http.js';\nimport { runRuntimeExecCommand } from './exec-command.js';\nimport { runManagedRuntimeCommand } from './managed-command.js';\nimport { CliError } from '../errors.js';\nimport { describeCliRuntime } from '../../runtime.js';\nimport type { CliRuntimeDescriptor } from './types.js';\n\nexport function isRuntimeCommand(argv: readonly string[]): boolean {\n let index = 0;\n while (['--help', '-h', '--version', '-v'].includes(argv[index] ?? '')) index += 1;\n if (argv[index] === '--') index += 1;\n return argv[index] === 'runtime';\n}\n\nexport async function runRuntimeCommand(\n subcommand: string | null,\n args: string[],\n describe: () => CliRuntimeDescriptor = describeCliRuntime,\n fetchImpl?: FetchLike,\n env?: NodeJS.ProcessEnv,\n): Promise<{ exitCode: number; stdout: string; stderr: string }> {\n if (subcommand === 'exec') return runRuntimeExecCommand(args, fetchImpl, env);\n if (subcommand && ['ensure', 'status', 'prune', 'lease-release'].includes(subcommand))\n return runManagedRuntimeCommand(subcommand, args, fetchImpl);\n let flags: { help?: boolean; json?: boolean };\n try {\n flags = parseArgs({\n args,\n strict: true,\n allowPositionals: false,\n options: {\n help: { type: 'boolean', short: 'h' },\n json: { type: 'boolean' },\n },\n }).values;\n } catch {\n throw new CliError('Runtime commands accept only their declared flags.', {\n code: 'RUNTIME_ARGUMENT_INVALID',\n exitCode: 2,\n });\n }\n if (!subcommand || flags.help)\n return {\n exitCode: 0,\n stdout:\n 'Usage: tiangong-lca runtime describe [--json] | ensure | status | prune | lease-release | exec\\n\\nDescribe the installed CLI package, assets and Node executable. No authentication, user env file or download.\\n',\n stderr: '',\n };\n if (subcommand !== 'describe')\n throw new CliError('Unknown runtime operation.', {\n code: 'RUNTIME_OPERATION_UNKNOWN',\n exitCode: 2,\n });\n const descriptor = describe();\n return {\n exitCode: 0,\n stdout: flags.json\n ? `${JSON.stringify(descriptor)}\\n`\n : `CLI ${descriptor.package.version} (${descriptor.platform})\\nNode ${descriptor.node.version}\\nContent ${descriptor.content_sha256}\\n`,\n stderr: '',\n };\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CliRuntimeDescriptor, CliRuntimeExpectation, RuntimePlatform } from './types.js';
|
|
2
|
+
export declare function runtimePlatform(platform: string, arch: string): RuntimePlatform;
|
|
3
|
+
export declare function runtimePackageRoot(moduleUrl: string): string;
|
|
4
|
+
export declare function inspectCliRuntime(root: string, node: {
|
|
5
|
+
executable: string;
|
|
6
|
+
version: string;
|
|
7
|
+
platform: string;
|
|
8
|
+
arch: string;
|
|
9
|
+
}): CliRuntimeDescriptor;
|
|
10
|
+
export declare function validateRuntimeExpectation(value: unknown): asserts value is CliRuntimeExpectation;
|
|
11
|
+
export declare function assertRuntimeObservationMatches(actual: CliRuntimeDescriptor, expected: unknown): CliRuntimeDescriptor;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { contentHash, hashRuntimeFile, listRuntimeFiles, runtimeError } from './files.js';
|
|
6
|
+
import { CLI_RUNTIME_DESCRIPTOR_SCHEMA, CLI_RUNTIME_EXPECTATION_SCHEMA, RUNTIME_PLATFORMS, } from './types.js';
|
|
7
|
+
const supportedNodeVersion = /^24\.(?:19|[2-9][0-9]|[1-9][0-9]{2,})\.(?:0|[1-9][0-9]*)$/u;
|
|
8
|
+
export function runtimePlatform(platform, arch) {
|
|
9
|
+
const value = `${platform}-${arch}`;
|
|
10
|
+
if (!RUNTIME_PLATFORMS.includes(value)) {
|
|
11
|
+
runtimeError('RUNTIME_PLATFORM_UNSUPPORTED', 'Supported platforms are macOS arm64, Linux x64/arm64 and Windows x64.');
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
export function runtimePackageRoot(moduleUrl) {
|
|
16
|
+
const modulePath = fileURLToPath(moduleUrl);
|
|
17
|
+
const extension = path.extname(modulePath);
|
|
18
|
+
if (!['.js', '.ts'].includes(extension) || path.basename(modulePath) !== `runtime${extension}`) {
|
|
19
|
+
runtimeError('RUNTIME_LAYOUT_INVALID', 'The runtime API must use its declared source or emitted entry.');
|
|
20
|
+
}
|
|
21
|
+
const root = extension === '.ts' ? path.resolve(modulePath, '../..') : path.resolve(modulePath, '../../..');
|
|
22
|
+
const expected = path.join(root, ...(extension === '.ts' ? ['src', 'runtime.ts'] : ['dist', 'src', 'runtime.js']));
|
|
23
|
+
if (modulePath !== expected)
|
|
24
|
+
runtimeError('RUNTIME_LAYOUT_INVALID', 'Runtime entry is outside the declared package layout.');
|
|
25
|
+
return fs.realpathSync(root);
|
|
26
|
+
}
|
|
27
|
+
export function inspectCliRuntime(root, node) {
|
|
28
|
+
const platform = runtimePlatform(node.platform, node.arch);
|
|
29
|
+
if (!supportedNodeVersion.test(node.version)) {
|
|
30
|
+
runtimeError('RUNTIME_NODE_UNSUPPORTED', 'CLI runtime requires stable Node 24.19 or later in the Node 24 line.');
|
|
31
|
+
}
|
|
32
|
+
const packageRoot = fs.realpathSync(root);
|
|
33
|
+
const files = listRuntimeFiles(packageRoot);
|
|
34
|
+
const manifestBytes = fs.readFileSync(path.join(packageRoot, 'package.json'));
|
|
35
|
+
const manifestFact = files.find((file) => file.path === 'package.json');
|
|
36
|
+
if (createHash('sha256').update(manifestBytes).digest('hex') !== manifestFact.sha256)
|
|
37
|
+
runtimeError('RUNTIME_FILE_CHANGED', 'Package manifest changed before parsing.');
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(manifestBytes.toString('utf8'));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
runtimeError('RUNTIME_PACKAGE_INVALID', 'Runtime package manifest is not valid JSON.');
|
|
44
|
+
}
|
|
45
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
46
|
+
runtimeError('RUNTIME_PACKAGE_INVALID', 'Runtime package manifest must be an object.');
|
|
47
|
+
const manifest = parsed;
|
|
48
|
+
const bin = manifest.bin;
|
|
49
|
+
if (manifest.name !== '@tiangong-lca/cli' ||
|
|
50
|
+
typeof manifest.version !== 'string' ||
|
|
51
|
+
!/^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)$/u.test(manifest.version) ||
|
|
52
|
+
!bin ||
|
|
53
|
+
bin['tiangong-lca'] !== './bin/tiangong-lca.js') {
|
|
54
|
+
runtimeError('RUNTIME_PACKAGE_INVALID', 'Runtime package name, stable version or launcher contract is invalid.');
|
|
55
|
+
}
|
|
56
|
+
for (const required of ['bin/tiangong-lca.js', 'dist/src/main.js', 'dist/src/runtime.js']) {
|
|
57
|
+
if (!files.some((file) => file.path === required))
|
|
58
|
+
runtimeError('RUNTIME_NOT_BUILT', 'Installed runtime is missing a required emitted entry.');
|
|
59
|
+
}
|
|
60
|
+
const assets = files.filter((file) => file.path.startsWith('assets/tidas-schemas/'));
|
|
61
|
+
if (assets.length === 0)
|
|
62
|
+
runtimeError('RUNTIME_ASSETS_MISSING', 'Installed runtime contains no TIDAS schema assets.');
|
|
63
|
+
const nodePath = fs.realpathSync(node.executable);
|
|
64
|
+
const nodeFact = hashRuntimeFile(nodePath, nodePath);
|
|
65
|
+
const currentManifest = hashRuntimeFile(path.join(packageRoot, 'package.json'), 'package.json');
|
|
66
|
+
if (manifestFact.sha256 !== currentManifest.sha256)
|
|
67
|
+
runtimeError('RUNTIME_FILE_CHANGED', 'Package manifest changed during inspection.');
|
|
68
|
+
return Object.freeze({
|
|
69
|
+
schema: CLI_RUNTIME_DESCRIPTOR_SCHEMA,
|
|
70
|
+
scope: 'cli-package',
|
|
71
|
+
package: Object.freeze({
|
|
72
|
+
name: '@tiangong-lca/cli',
|
|
73
|
+
version: manifest.version,
|
|
74
|
+
root: packageRoot,
|
|
75
|
+
manifest_sha256: manifestFact.sha256,
|
|
76
|
+
}),
|
|
77
|
+
platform,
|
|
78
|
+
node: Object.freeze({
|
|
79
|
+
version: node.version,
|
|
80
|
+
executable: nodePath,
|
|
81
|
+
bytes: nodeFact.bytes,
|
|
82
|
+
sha256: nodeFact.sha256,
|
|
83
|
+
}),
|
|
84
|
+
command: Object.freeze({
|
|
85
|
+
executable: nodePath,
|
|
86
|
+
argv: Object.freeze([path.join(packageRoot, 'bin', 'tiangong-lca.js')]),
|
|
87
|
+
}),
|
|
88
|
+
assets: Object.freeze({
|
|
89
|
+
tidas_schema_root: path.join(packageRoot, 'assets', 'tidas-schemas'),
|
|
90
|
+
sha256: contentHash(assets),
|
|
91
|
+
}),
|
|
92
|
+
files,
|
|
93
|
+
content_sha256: contentHash(files),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
export function validateRuntimeExpectation(value) {
|
|
97
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
98
|
+
runtimeError('RUNTIME_EXPECTATION_INVALID', 'Select exact runtime facts from a trusted release manifest.');
|
|
99
|
+
const expected = value;
|
|
100
|
+
if (Object.keys(expected).sort().join(',') !==
|
|
101
|
+
'content_sha256,node_sha256,node_version,package_version,platform,schema' ||
|
|
102
|
+
expected.schema !== CLI_RUNTIME_EXPECTATION_SCHEMA ||
|
|
103
|
+
typeof expected.package_version !== 'string' ||
|
|
104
|
+
!/^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)$/u.test(expected.package_version) ||
|
|
105
|
+
!RUNTIME_PLATFORMS.includes(expected.platform) ||
|
|
106
|
+
typeof expected.content_sha256 !== 'string' ||
|
|
107
|
+
!/^[0-9a-f]{64}$/u.test(expected.content_sha256) ||
|
|
108
|
+
typeof expected.node_version !== 'string' ||
|
|
109
|
+
!supportedNodeVersion.test(expected.node_version) ||
|
|
110
|
+
typeof expected.node_sha256 !== 'string' ||
|
|
111
|
+
!/^[0-9a-f]{64}$/u.test(expected.node_sha256)) {
|
|
112
|
+
runtimeError('RUNTIME_EXPECTATION_INVALID', 'Select exact runtime facts from a trusted release manifest.');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export function assertRuntimeObservationMatches(actual, expected) {
|
|
116
|
+
validateRuntimeExpectation(expected);
|
|
117
|
+
if (actual.package.version !== expected.package_version ||
|
|
118
|
+
actual.platform !== expected.platform ||
|
|
119
|
+
actual.content_sha256 !== expected.content_sha256 ||
|
|
120
|
+
actual.node.version !== expected.node_version ||
|
|
121
|
+
actual.node.sha256 !== expected.node_sha256) {
|
|
122
|
+
runtimeError('RUNTIME_IDENTITY_MISMATCH', 'Installed CLI/Node content does not match the selected release.');
|
|
123
|
+
}
|
|
124
|
+
return actual;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=descriptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"descriptor.js","sourceRoot":"","sources":["../../../../src/lib/runtime/descriptor.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport { createHash } from 'node:crypto';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { contentHash, hashRuntimeFile, listRuntimeFiles, runtimeError } from './files.js';\nimport {\n CLI_RUNTIME_DESCRIPTOR_SCHEMA,\n CLI_RUNTIME_EXPECTATION_SCHEMA,\n RUNTIME_PLATFORMS,\n} from './types.js';\nimport type { CliRuntimeDescriptor, CliRuntimeExpectation, RuntimePlatform } from './types.js';\n\nconst supportedNodeVersion = /^24\\.(?:19|[2-9][0-9]|[1-9][0-9]{2,})\\.(?:0|[1-9][0-9]*)$/u;\n\nexport function runtimePlatform(platform: string, arch: string): RuntimePlatform {\n const value = `${platform}-${arch}`;\n if (!RUNTIME_PLATFORMS.includes(value as RuntimePlatform)) {\n runtimeError(\n 'RUNTIME_PLATFORM_UNSUPPORTED',\n 'Supported platforms are macOS arm64, Linux x64/arm64 and Windows x64.',\n );\n }\n return value as RuntimePlatform;\n}\n\nexport function runtimePackageRoot(moduleUrl: string): string {\n const modulePath = fileURLToPath(moduleUrl);\n const extension = path.extname(modulePath);\n if (!['.js', '.ts'].includes(extension) || path.basename(modulePath) !== `runtime${extension}`) {\n runtimeError(\n 'RUNTIME_LAYOUT_INVALID',\n 'The runtime API must use its declared source or emitted entry.',\n );\n }\n const root =\n extension === '.ts' ? path.resolve(modulePath, '../..') : path.resolve(modulePath, '../../..');\n const expected = path.join(\n root,\n ...(extension === '.ts' ? ['src', 'runtime.ts'] : ['dist', 'src', 'runtime.js']),\n );\n if (modulePath !== expected)\n runtimeError('RUNTIME_LAYOUT_INVALID', 'Runtime entry is outside the declared package layout.');\n return fs.realpathSync(root);\n}\n\nexport function inspectCliRuntime(\n root: string,\n node: { executable: string; version: string; platform: string; arch: string },\n): CliRuntimeDescriptor {\n const platform = runtimePlatform(node.platform, node.arch);\n if (!supportedNodeVersion.test(node.version)) {\n runtimeError(\n 'RUNTIME_NODE_UNSUPPORTED',\n 'CLI runtime requires stable Node 24.19 or later in the Node 24 line.',\n );\n }\n const packageRoot = fs.realpathSync(root);\n const files = listRuntimeFiles(packageRoot);\n const manifestBytes = fs.readFileSync(path.join(packageRoot, 'package.json'));\n const manifestFact = files.find((file) => file.path === 'package.json')!;\n if (createHash('sha256').update(manifestBytes).digest('hex') !== manifestFact.sha256)\n runtimeError('RUNTIME_FILE_CHANGED', 'Package manifest changed before parsing.');\n let parsed: unknown;\n try {\n parsed = JSON.parse(manifestBytes.toString('utf8'));\n } catch {\n runtimeError('RUNTIME_PACKAGE_INVALID', 'Runtime package manifest is not valid JSON.');\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n runtimeError('RUNTIME_PACKAGE_INVALID', 'Runtime package manifest must be an object.');\n const manifest = parsed as Record<string, unknown>;\n const bin = manifest.bin as Record<string, unknown> | undefined;\n if (\n manifest.name !== '@tiangong-lca/cli' ||\n typeof manifest.version !== 'string' ||\n !/^(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)$/u.test(manifest.version) ||\n !bin ||\n bin['tiangong-lca'] !== './bin/tiangong-lca.js'\n ) {\n runtimeError(\n 'RUNTIME_PACKAGE_INVALID',\n 'Runtime package name, stable version or launcher contract is invalid.',\n );\n }\n for (const required of ['bin/tiangong-lca.js', 'dist/src/main.js', 'dist/src/runtime.js']) {\n if (!files.some((file) => file.path === required))\n runtimeError('RUNTIME_NOT_BUILT', 'Installed runtime is missing a required emitted entry.');\n }\n const assets = files.filter((file) => file.path.startsWith('assets/tidas-schemas/'));\n if (assets.length === 0)\n runtimeError('RUNTIME_ASSETS_MISSING', 'Installed runtime contains no TIDAS schema assets.');\n const nodePath = fs.realpathSync(node.executable);\n const nodeFact = hashRuntimeFile(nodePath, nodePath);\n const currentManifest = hashRuntimeFile(path.join(packageRoot, 'package.json'), 'package.json');\n if (manifestFact.sha256 !== currentManifest.sha256)\n runtimeError('RUNTIME_FILE_CHANGED', 'Package manifest changed during inspection.');\n return Object.freeze({\n schema: CLI_RUNTIME_DESCRIPTOR_SCHEMA,\n scope: 'cli-package',\n package: Object.freeze({\n name: '@tiangong-lca/cli',\n version: manifest.version,\n root: packageRoot,\n manifest_sha256: manifestFact.sha256,\n }),\n platform,\n node: Object.freeze({\n version: node.version,\n executable: nodePath,\n bytes: nodeFact.bytes,\n sha256: nodeFact.sha256,\n }),\n command: Object.freeze({\n executable: nodePath,\n argv: Object.freeze([path.join(packageRoot, 'bin', 'tiangong-lca.js')]),\n }),\n assets: Object.freeze({\n tidas_schema_root: path.join(packageRoot, 'assets', 'tidas-schemas'),\n sha256: contentHash(assets),\n }),\n files,\n content_sha256: contentHash(files),\n });\n}\n\nexport function validateRuntimeExpectation(value: unknown): asserts value is CliRuntimeExpectation {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n runtimeError(\n 'RUNTIME_EXPECTATION_INVALID',\n 'Select exact runtime facts from a trusted release manifest.',\n );\n const expected = value as Record<string, unknown>;\n if (\n Object.keys(expected).sort().join(',') !==\n 'content_sha256,node_sha256,node_version,package_version,platform,schema' ||\n expected.schema !== CLI_RUNTIME_EXPECTATION_SCHEMA ||\n typeof expected.package_version !== 'string' ||\n !/^(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)$/u.test(expected.package_version) ||\n !RUNTIME_PLATFORMS.includes(expected.platform as RuntimePlatform) ||\n typeof expected.content_sha256 !== 'string' ||\n !/^[0-9a-f]{64}$/u.test(expected.content_sha256) ||\n typeof expected.node_version !== 'string' ||\n !supportedNodeVersion.test(expected.node_version) ||\n typeof expected.node_sha256 !== 'string' ||\n !/^[0-9a-f]{64}$/u.test(expected.node_sha256)\n ) {\n runtimeError(\n 'RUNTIME_EXPECTATION_INVALID',\n 'Select exact runtime facts from a trusted release manifest.',\n );\n }\n}\n\nexport function assertRuntimeObservationMatches(\n actual: CliRuntimeDescriptor,\n expected: unknown,\n): CliRuntimeDescriptor {\n validateRuntimeExpectation(expected);\n if (\n actual.package.version !== expected.package_version ||\n actual.platform !== expected.platform ||\n actual.content_sha256 !== expected.content_sha256 ||\n actual.node.version !== expected.node_version ||\n actual.node.sha256 !== expected.node_sha256\n ) {\n runtimeError(\n 'RUNTIME_IDENTITY_MISMATCH',\n 'Installed CLI/Node content does not match the selected release.',\n );\n }\n return actual;\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FetchLike } from '../http.js';
|
|
2
|
+
import type { RuntimeComponent } from './manifest-types.js';
|
|
3
|
+
export type DownloadOptions = {
|
|
4
|
+
fetchImpl?: FetchLike;
|
|
5
|
+
signal?: AbortSignal;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
export declare function downloadRuntimeArchive(archive: RuntimeComponent['archive'], file: string, options?: DownloadOptions): Promise<void>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { runtimeError } from './files.js';
|
|
4
|
+
import { distributionUrl } from './manifest-values.js';
|
|
5
|
+
import { writeAll } from './storage.js';
|
|
6
|
+
class RetryableDownload extends Error {
|
|
7
|
+
}
|
|
8
|
+
async function attempt(archive, file, options) {
|
|
9
|
+
const timeout = AbortSignal.timeout(options.timeoutMs ?? 30_000);
|
|
10
|
+
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
11
|
+
signal.throwIfAborted();
|
|
12
|
+
let url = distributionUrl(archive.url);
|
|
13
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
14
|
+
let redirects = 0;
|
|
15
|
+
while (true) {
|
|
16
|
+
let response;
|
|
17
|
+
try {
|
|
18
|
+
response = await fetchImpl(url, {
|
|
19
|
+
method: 'GET',
|
|
20
|
+
redirect: 'manual',
|
|
21
|
+
headers: { Accept: 'application/octet-stream', 'Accept-Encoding': 'identity' },
|
|
22
|
+
signal,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (options.signal?.aborted)
|
|
27
|
+
throw error;
|
|
28
|
+
throw new RetryableDownload('Runtime download connection failed.');
|
|
29
|
+
}
|
|
30
|
+
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
31
|
+
await response.body?.cancel();
|
|
32
|
+
const location = response.headers.get('location');
|
|
33
|
+
if (!location || redirects++ >= 5)
|
|
34
|
+
runtimeError('RUNTIME_DOWNLOAD_REDIRECT', 'Runtime download exceeded its redirect contract.');
|
|
35
|
+
url = distributionUrl(new URL(location, url).href, true);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (response.status === 408 || response.status === 429 || response.status >= 500) {
|
|
39
|
+
await response.body?.cancel();
|
|
40
|
+
throw new RetryableDownload('Runtime release source is temporarily unavailable.');
|
|
41
|
+
}
|
|
42
|
+
if (response.status !== 200) {
|
|
43
|
+
await response.body?.cancel();
|
|
44
|
+
runtimeError('RUNTIME_DOWNLOAD_HTTP', 'Runtime release source did not return a complete artifact.');
|
|
45
|
+
}
|
|
46
|
+
const length = response.headers.get('content-length');
|
|
47
|
+
if (length !== null && (!/^[0-9]+$/u.test(length) || Number(length) !== archive.bytes)) {
|
|
48
|
+
await response.body?.cancel();
|
|
49
|
+
runtimeError('RUNTIME_DOWNLOAD_SIZE', 'Runtime archive Content-Length does not match its manifest.');
|
|
50
|
+
}
|
|
51
|
+
if (!response.body)
|
|
52
|
+
runtimeError('RUNTIME_DOWNLOAD_BODY', 'Runtime release source returned no bounded byte stream.');
|
|
53
|
+
const reader = response.body.getReader();
|
|
54
|
+
const hash = createHash('sha256');
|
|
55
|
+
let bytes = 0;
|
|
56
|
+
let fd;
|
|
57
|
+
let complete = false;
|
|
58
|
+
try {
|
|
59
|
+
fd = fs.openSync(file, 'wx', 0o600);
|
|
60
|
+
while (true) {
|
|
61
|
+
signal.throwIfAborted();
|
|
62
|
+
let result;
|
|
63
|
+
try {
|
|
64
|
+
result = await reader.read();
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (options.signal?.aborted)
|
|
68
|
+
throw error;
|
|
69
|
+
throw new RetryableDownload('Runtime download stream was interrupted.');
|
|
70
|
+
}
|
|
71
|
+
if (result.done)
|
|
72
|
+
break;
|
|
73
|
+
bytes += result.value.byteLength;
|
|
74
|
+
if (bytes > archive.bytes)
|
|
75
|
+
runtimeError('RUNTIME_DOWNLOAD_SIZE', 'Runtime archive exceeds its declared byte count.');
|
|
76
|
+
hash.update(result.value);
|
|
77
|
+
writeAll(fd, result.value);
|
|
78
|
+
}
|
|
79
|
+
if (bytes !== archive.bytes || hash.digest('hex') !== archive.sha256)
|
|
80
|
+
runtimeError('RUNTIME_DOWNLOAD_INTEGRITY', 'Runtime archive bytes or digest do not match the trusted manifest.');
|
|
81
|
+
fs.fsyncSync(fd);
|
|
82
|
+
complete = true;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
if (fd !== undefined) {
|
|
87
|
+
const own = fs.fstatSync(fd, { bigint: true });
|
|
88
|
+
fs.closeSync(fd);
|
|
89
|
+
if (!complete && fs.existsSync(file)) {
|
|
90
|
+
const current = fs.lstatSync(file, { bigint: true });
|
|
91
|
+
if (current.dev === own.dev && current.ino === own.ino)
|
|
92
|
+
fs.unlinkSync(file);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
await reader.cancel().catch(() => undefined);
|
|
96
|
+
reader.releaseLock();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export async function downloadRuntimeArchive(archive, file, options = {}) {
|
|
101
|
+
if (options.timeoutMs !== undefined &&
|
|
102
|
+
(!Number.isSafeInteger(options.timeoutMs) ||
|
|
103
|
+
options.timeoutMs < 1 ||
|
|
104
|
+
options.timeoutMs > 120_000))
|
|
105
|
+
runtimeError('RUNTIME_DOWNLOAD_TIMEOUT', 'Runtime download timeout must be between 1 and 120000 ms.');
|
|
106
|
+
for (let index = 0; index < 2; index++) {
|
|
107
|
+
try {
|
|
108
|
+
await attempt(archive, file, options);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (!(error instanceof RetryableDownload) || index === 1 || options.signal?.aborted)
|
|
113
|
+
throw error;
|
|
114
|
+
await (options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))))(100);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=download.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download.js","sourceRoot":"","sources":["../../../../src/lib/runtime/download.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport { createHash } from 'node:crypto';\nimport type { FetchLike } from '../http.js';\nimport { runtimeError } from './files.js';\nimport { distributionUrl } from './manifest-values.js';\nimport { writeAll } from './storage.js';\nimport type { RuntimeComponent } from './manifest-types.js';\n\nclass RetryableDownload extends Error {}\nexport type DownloadOptions = {\n fetchImpl?: FetchLike;\n signal?: AbortSignal;\n timeoutMs?: number;\n sleep?: (milliseconds: number) => Promise<void>;\n};\nasync function attempt(\n archive: RuntimeComponent['archive'],\n file: string,\n options: DownloadOptions,\n): Promise<void> {\n const timeout = AbortSignal.timeout(options.timeoutMs ?? 30_000);\n const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;\n signal.throwIfAborted();\n let url = distributionUrl(archive.url);\n const fetchImpl = options.fetchImpl ?? fetch;\n let redirects = 0;\n while (true) {\n let response;\n try {\n response = await fetchImpl(url, {\n method: 'GET',\n redirect: 'manual',\n headers: { Accept: 'application/octet-stream', 'Accept-Encoding': 'identity' },\n signal,\n });\n } catch (error) {\n if (options.signal?.aborted) throw error;\n throw new RetryableDownload('Runtime download connection failed.');\n }\n if ([301, 302, 303, 307, 308].includes(response.status)) {\n await response.body?.cancel();\n const location = response.headers.get('location');\n if (!location || redirects++ >= 5)\n runtimeError(\n 'RUNTIME_DOWNLOAD_REDIRECT',\n 'Runtime download exceeded its redirect contract.',\n );\n url = distributionUrl(new URL(location, url).href, true);\n continue;\n }\n if (response.status === 408 || response.status === 429 || response.status >= 500) {\n await response.body?.cancel();\n throw new RetryableDownload('Runtime release source is temporarily unavailable.');\n }\n if (response.status !== 200) {\n await response.body?.cancel();\n runtimeError(\n 'RUNTIME_DOWNLOAD_HTTP',\n 'Runtime release source did not return a complete artifact.',\n );\n }\n const length = response.headers.get('content-length');\n if (length !== null && (!/^[0-9]+$/u.test(length) || Number(length) !== archive.bytes)) {\n await response.body?.cancel();\n runtimeError(\n 'RUNTIME_DOWNLOAD_SIZE',\n 'Runtime archive Content-Length does not match its manifest.',\n );\n }\n if (!response.body)\n runtimeError(\n 'RUNTIME_DOWNLOAD_BODY',\n 'Runtime release source returned no bounded byte stream.',\n );\n const reader = response.body.getReader();\n const hash = createHash('sha256');\n let bytes = 0;\n let fd: number | undefined;\n let complete = false;\n try {\n fd = fs.openSync(file, 'wx', 0o600);\n while (true) {\n signal.throwIfAborted();\n let result;\n try {\n result = await reader.read();\n } catch (error) {\n if (options.signal?.aborted) throw error;\n throw new RetryableDownload('Runtime download stream was interrupted.');\n }\n if (result.done) break;\n bytes += result.value.byteLength;\n if (bytes > archive.bytes)\n runtimeError('RUNTIME_DOWNLOAD_SIZE', 'Runtime archive exceeds its declared byte count.');\n hash.update(result.value);\n writeAll(fd, result.value);\n }\n if (bytes !== archive.bytes || hash.digest('hex') !== archive.sha256)\n runtimeError(\n 'RUNTIME_DOWNLOAD_INTEGRITY',\n 'Runtime archive bytes or digest do not match the trusted manifest.',\n );\n fs.fsyncSync(fd);\n complete = true;\n return;\n } finally {\n if (fd !== undefined) {\n const own = fs.fstatSync(fd, { bigint: true });\n fs.closeSync(fd);\n if (!complete && fs.existsSync(file)) {\n const current = fs.lstatSync(file, { bigint: true });\n if (current.dev === own.dev && current.ino === own.ino) fs.unlinkSync(file);\n }\n }\n await reader.cancel().catch(() => undefined);\n reader.releaseLock();\n }\n }\n}\nexport async function downloadRuntimeArchive(\n archive: RuntimeComponent['archive'],\n file: string,\n options: DownloadOptions = {},\n): Promise<void> {\n if (\n options.timeoutMs !== undefined &&\n (!Number.isSafeInteger(options.timeoutMs) ||\n options.timeoutMs < 1 ||\n options.timeoutMs > 120_000)\n )\n runtimeError(\n 'RUNTIME_DOWNLOAD_TIMEOUT',\n 'Runtime download timeout must be between 1 and 120000 ms.',\n );\n for (let index = 0; index < 2; index++) {\n try {\n await attempt(archive, file, options);\n return;\n } catch (error) {\n if (!(error instanceof RetryableDownload) || index === 1 || options.signal?.aborted)\n throw error;\n await (options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))))(100);\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FetchLike } from '../http.js';
|
|
2
|
+
import { executeRuntimeLaunch } from './execute.js';
|
|
3
|
+
export declare function runRuntimeExecCommand(args: string[], fetchImpl?: FetchLike, env?: NodeJS.ProcessEnv, run?: typeof executeRuntimeLaunch): Promise<{
|
|
4
|
+
exitCode: number;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
}>;
|