@tiangong-lca/cli 0.1.9 → 0.1.11
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 +10 -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 +383 -0
- package/dist/src/cli.js +5 -1
- 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/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 +86 -0
- package/dist/src/lib/runtime/exec-command.js.map +1 -0
- package/dist/src/lib/runtime/execute.d.ts +12 -0
- package/dist/src/lib/runtime/execute.js +132 -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-context-protocol.d.ts +20 -0
- package/dist/src/lib/runtime/host-context-protocol.js +83 -0
- package/dist/src/lib/runtime/host-context-protocol.js.map +1 -0
- package/dist/src/lib/runtime/host-context-server.d.ts +6 -0
- package/dist/src/lib/runtime/host-context-server.js +81 -0
- package/dist/src/lib/runtime/host-context-server.js.map +1 -0
- package/dist/src/lib/runtime/host-context.d.ts +23 -0
- package/dist/src/lib/runtime/host-context.js +122 -0
- package/dist/src/lib/runtime/host-context.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 +82 -0
- package/dist/src/lib/runtime/manifest-types.js +5 -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 +8 -0
- package/dist/src/lib/runtime/manifest.js +239 -0
- package/dist/src/lib/runtime/manifest.js.map +1 -0
- package/dist/src/lib/runtime/process.d.ts +4 -0
- package/dist/src/lib/runtime/process.js +69 -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/runtime/work-directory.d.ts +2 -0
- package/dist/src/lib/runtime/work-directory.js +16 -0
- package/dist/src/lib/runtime/work-directory.js.map +1 -0
- package/dist/src/main.js +13 -1
- package/dist/src/main.js.map +1 -1
- package/dist/src/runtime.d.ts +16 -0
- package/dist/src/runtime.js +23 -0
- package/dist/src/runtime.js.map +1 -0
- package/package.json +5 -1
|
@@ -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
|
+
}>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { CliError } from '../errors.js';
|
|
3
|
+
import { loadTrustedRuntimeManifest } from './manifest.js';
|
|
4
|
+
import { executeRuntimeLaunch } from './execute.js';
|
|
5
|
+
export async function runRuntimeExecCommand(args, fetchImpl, env = process.env, run = executeRuntimeLaunch) {
|
|
6
|
+
const separator = args.indexOf('--'), options = separator < 0 ? args : args.slice(0, separator), argv = separator < 0 ? [] : args.slice(separator + 1);
|
|
7
|
+
let flags;
|
|
8
|
+
try {
|
|
9
|
+
flags = parseArgs({
|
|
10
|
+
args: options,
|
|
11
|
+
strict: true,
|
|
12
|
+
allowPositionals: false,
|
|
13
|
+
options: {
|
|
14
|
+
help: { type: 'boolean', short: 'h' },
|
|
15
|
+
manifest: { type: 'string' },
|
|
16
|
+
'manifest-sha256': { type: 'string' },
|
|
17
|
+
'cache-dir': { type: 'string' },
|
|
18
|
+
entry: { type: 'string' },
|
|
19
|
+
cwd: { type: 'string' },
|
|
20
|
+
lease: { type: 'string' },
|
|
21
|
+
'lease-owner': { type: 'string' },
|
|
22
|
+
},
|
|
23
|
+
}).values;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new CliError('Invalid runtime exec arguments.', {
|
|
27
|
+
code: 'RUNTIME_ARGUMENT_INVALID',
|
|
28
|
+
exitCode: 2,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (flags.help)
|
|
32
|
+
return {
|
|
33
|
+
exitCode: 0,
|
|
34
|
+
stdout: 'Usage: tiangong-lca runtime exec --manifest <file> --manifest-sha256 <trusted-sha256> --entry <id> --cwd <work-directory> [--cache-dir <dir>] [--lease <id> --lease-owner <owner>] -- <application argv>\n',
|
|
35
|
+
stderr: '',
|
|
36
|
+
};
|
|
37
|
+
if (!flags.manifest ||
|
|
38
|
+
!flags['manifest-sha256'] ||
|
|
39
|
+
!flags.entry ||
|
|
40
|
+
!flags.cwd ||
|
|
41
|
+
Boolean(flags.lease) !== Boolean(flags['lease-owner']))
|
|
42
|
+
throw new CliError('Runtime exec requires manifest, trust digest, entry and explicit work directory.', { code: 'RUNTIME_ARGUMENT_INVALID', exitCode: 2 });
|
|
43
|
+
const value = loadTrustedRuntimeManifest(flags.manifest, flags['manifest-sha256']);
|
|
44
|
+
const abort = new AbortController();
|
|
45
|
+
const stop = () => abort.abort();
|
|
46
|
+
process.once('SIGINT', stop);
|
|
47
|
+
process.once('SIGTERM', stop);
|
|
48
|
+
try {
|
|
49
|
+
const result = await run(value, {
|
|
50
|
+
cacheDir: flags['cache-dir'],
|
|
51
|
+
entry: flags.entry,
|
|
52
|
+
cwd: flags.cwd,
|
|
53
|
+
argv,
|
|
54
|
+
env,
|
|
55
|
+
fetchImpl,
|
|
56
|
+
signal: abort.signal,
|
|
57
|
+
...(flags.lease ? { lease: { id: flags.lease, owner: flags['lease-owner'] } } : {}),
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
exitCode: abort.signal.aborted
|
|
61
|
+
? 130
|
|
62
|
+
: result.error
|
|
63
|
+
? 1
|
|
64
|
+
: (result.status ?? (result.signal ? 130 : 1)),
|
|
65
|
+
stdout: result.stdout,
|
|
66
|
+
stderr: result.stderr ||
|
|
67
|
+
(result.error && 'code' in result.error && result.error.code === 'RUNTIME_HOST_CONTEXT'
|
|
68
|
+
? `${result.error.message}\n`
|
|
69
|
+
: ''),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (abort.signal.aborted)
|
|
74
|
+
return {
|
|
75
|
+
exitCode: 130,
|
|
76
|
+
stdout: '',
|
|
77
|
+
stderr: 'Runtime execution interrupted; application recovery must preserve existing attempts.\n',
|
|
78
|
+
};
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
process.removeListener('SIGINT', stop);
|
|
83
|
+
process.removeListener('SIGTERM', stop);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=exec-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec-command.js","sourceRoot":"","sources":["../../../../src/lib/runtime/exec-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { parseArgs } from 'node:util';\nimport { CliError } from '../errors.js';\nimport type { FetchLike } from '../http.js';\nimport { loadTrustedRuntimeManifest } from './manifest.js';\nimport { executeRuntimeLaunch } from './execute.js';\nexport async function runRuntimeExecCommand(\n args: string[],\n fetchImpl?: FetchLike,\n env: NodeJS.ProcessEnv = process.env,\n run: typeof executeRuntimeLaunch = executeRuntimeLaunch,\n): Promise<{ exitCode: number; stdout: string; stderr: string }> {\n const separator = args.indexOf('--'),\n options = separator < 0 ? args : args.slice(0, separator),\n argv = separator < 0 ? [] : args.slice(separator + 1);\n let flags;\n try {\n flags = parseArgs({\n args: options,\n strict: true,\n allowPositionals: false,\n options: {\n help: { type: 'boolean', short: 'h' },\n manifest: { type: 'string' },\n 'manifest-sha256': { type: 'string' },\n 'cache-dir': { type: 'string' },\n entry: { type: 'string' },\n cwd: { type: 'string' },\n lease: { type: 'string' },\n 'lease-owner': { type: 'string' },\n },\n }).values;\n } catch {\n throw new CliError('Invalid runtime exec arguments.', {\n code: 'RUNTIME_ARGUMENT_INVALID',\n exitCode: 2,\n });\n }\n if (flags.help)\n return {\n exitCode: 0,\n stdout:\n 'Usage: tiangong-lca runtime exec --manifest <file> --manifest-sha256 <trusted-sha256> --entry <id> --cwd <work-directory> [--cache-dir <dir>] [--lease <id> --lease-owner <owner>] -- <application argv>\\n',\n stderr: '',\n };\n if (\n !flags.manifest ||\n !flags['manifest-sha256'] ||\n !flags.entry ||\n !flags.cwd ||\n Boolean(flags.lease) !== Boolean(flags['lease-owner'])\n )\n throw new CliError(\n 'Runtime exec requires manifest, trust digest, entry and explicit work directory.',\n { code: 'RUNTIME_ARGUMENT_INVALID', exitCode: 2 },\n );\n const value = loadTrustedRuntimeManifest(flags.manifest, flags['manifest-sha256']);\n const abort = new AbortController();\n const stop = () => abort.abort();\n process.once('SIGINT', stop);\n process.once('SIGTERM', stop);\n try {\n const result = await run(value, {\n cacheDir: flags['cache-dir'],\n entry: flags.entry,\n cwd: flags.cwd,\n argv,\n env,\n fetchImpl,\n signal: abort.signal,\n ...(flags.lease ? { lease: { id: flags.lease, owner: flags['lease-owner']! } } : {}),\n });\n return {\n exitCode: abort.signal.aborted\n ? 130\n : result.error\n ? 1\n : (result.status ?? (result.signal ? 130 : 1)),\n stdout: result.stdout,\n stderr:\n result.stderr ||\n (result.error && 'code' in result.error && result.error.code === 'RUNTIME_HOST_CONTEXT'\n ? `${result.error.message}\\n`\n : ''),\n };\n } catch (error) {\n if (abort.signal.aborted)\n return {\n exitCode: 130,\n stdout: '',\n stderr:\n 'Runtime execution interrupted; application recovery must preserve existing attempts.\\n',\n };\n throw error;\n } finally {\n process.removeListener('SIGINT', stop);\n process.removeListener('SIGTERM', stop);\n }\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type FoundryCommandSpecSpawnResult, type FoundryCommandSpecAsyncSpawnOptions } from '../../command-spec.js';
|
|
2
|
+
import { type RuntimeManagerOptions } from './manager.js';
|
|
3
|
+
import type { TrustedRuntimeManifest, RuntimeHostContext } from './manifest-types.js';
|
|
4
|
+
export declare function runtimeChildEnvironment(source: NodeJS.ProcessEnv, mode: 'isolated' | 'cli-auth'): NodeJS.ProcessEnv;
|
|
5
|
+
export type RuntimeExecutionOptions = RuntimeManagerOptions & {
|
|
6
|
+
entry: string;
|
|
7
|
+
cwd: string;
|
|
8
|
+
argv: readonly string[];
|
|
9
|
+
env?: Record<string, string | undefined>;
|
|
10
|
+
};
|
|
11
|
+
export type RuntimeExecutionSpawn = (executable: string, argv: readonly string[], options: FoundryCommandSpecAsyncSpawnOptions, context?: RuntimeHostContext) => Promise<FoundryCommandSpecSpawnResult>;
|
|
12
|
+
export declare function executeRuntimeLaunch(value: TrustedRuntimeManifest, options: RuntimeExecutionOptions, spawnImpl?: RuntimeExecutionSpawn): Promise<FoundryCommandSpecSpawnResult>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { createFoundryCommandSpec, executeFoundryCommandSpec, } from '../../command-spec.js';
|
|
4
|
+
import { ensureRuntimeComponents, inspectRuntimeComponents, } from './manager.js';
|
|
5
|
+
import { spawnRuntimeProcess } from './process.js';
|
|
6
|
+
import { acquireRuntimeLease, releaseRuntimeLease } from './leases.js';
|
|
7
|
+
import { cachePath, defaultRuntimeCache, openRuntimeCache } from './storage.js';
|
|
8
|
+
import { assertTrustedManifest, componentKey } from './manifest.js';
|
|
9
|
+
import { inspectRuntimeHost } from './host.js';
|
|
10
|
+
import { runtimeError } from './files.js';
|
|
11
|
+
import { runtimeWorkDirectory, assertRuntimeWorkDirectoryOutsideInstall, } from './work-directory.js';
|
|
12
|
+
const systemKeys = [
|
|
13
|
+
'PATH',
|
|
14
|
+
'Path',
|
|
15
|
+
'PATHEXT',
|
|
16
|
+
'SystemRoot',
|
|
17
|
+
'SYSTEMROOT',
|
|
18
|
+
'WINDIR',
|
|
19
|
+
'ComSpec',
|
|
20
|
+
'COMSPEC',
|
|
21
|
+
'HOME',
|
|
22
|
+
'USERPROFILE',
|
|
23
|
+
'APPDATA',
|
|
24
|
+
'LOCALAPPDATA',
|
|
25
|
+
'XDG_CONFIG_HOME',
|
|
26
|
+
'XDG_CACHE_HOME',
|
|
27
|
+
'TMPDIR',
|
|
28
|
+
'TEMP',
|
|
29
|
+
'TMP',
|
|
30
|
+
'LANG',
|
|
31
|
+
'LC_ALL',
|
|
32
|
+
'TZ',
|
|
33
|
+
];
|
|
34
|
+
const authKeys = [
|
|
35
|
+
'TIANGONG_LCA_API_BASE_URL',
|
|
36
|
+
'TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY',
|
|
37
|
+
'TIANGONG_LCA_OAUTH_CLIENT_ID',
|
|
38
|
+
'TIANGONG_LCA_OAUTH_REDIRECT_URI',
|
|
39
|
+
'TIANGONG_LCA_REGION',
|
|
40
|
+
'TIANGONG_LCA_AUTH_MODE',
|
|
41
|
+
'TIANGONG_LCA_SESSION_FILE',
|
|
42
|
+
'TIANGONG_LCA_DISABLE_SESSION_CACHE',
|
|
43
|
+
'TIANGONG_LCA_FORCE_REAUTH',
|
|
44
|
+
'TIANGONG_LCA_ACCESS_TOKEN',
|
|
45
|
+
];
|
|
46
|
+
export function runtimeChildEnvironment(source, mode) {
|
|
47
|
+
const env = {};
|
|
48
|
+
for (const key of [...systemKeys, ...(mode === 'cli-auth' ? authKeys : [])])
|
|
49
|
+
if (source[key] !== undefined)
|
|
50
|
+
env[key] = source[key];
|
|
51
|
+
return env;
|
|
52
|
+
}
|
|
53
|
+
export async function executeRuntimeLaunch(value, options, spawnImpl) {
|
|
54
|
+
assertTrustedManifest(value);
|
|
55
|
+
const observedHost = options.host ?? inspectRuntimeHost();
|
|
56
|
+
const host = Object.freeze({
|
|
57
|
+
platform: observedHost.platform,
|
|
58
|
+
osRelease: observedHost.osRelease,
|
|
59
|
+
glibc: observedHost.glibc,
|
|
60
|
+
});
|
|
61
|
+
const launch = value.manifest.launches.find((item) => item.id === options.entry && item.platform === host.platform);
|
|
62
|
+
if (!launch)
|
|
63
|
+
runtimeError('RUNTIME_LAUNCH_UNKNOWN', 'Selected runtime entry is not declared for this host.');
|
|
64
|
+
if (options.argv.length > 512)
|
|
65
|
+
runtimeError('RUNTIME_LAUNCH_ARGUMENT', 'Runtime argv must be bounded and contain no credential arguments.');
|
|
66
|
+
const applicationArgv = [...options.argv];
|
|
67
|
+
if (applicationArgv.some((arg) => typeof arg !== 'string' ||
|
|
68
|
+
arg.includes('\0') ||
|
|
69
|
+
arg.length > 32768 ||
|
|
70
|
+
/^--(?:username|password|api-key|access-token|refresh-token|authorization-code|oauth-code)(?:=|$)/iu.test(arg)))
|
|
71
|
+
runtimeError('RUNTIME_LAUNCH_ARGUMENT', 'Runtime argv must be bounded and contain no credential arguments.');
|
|
72
|
+
const cwd = runtimeWorkDirectory(options.cwd), cache = openRuntimeCache(options.cacheDir ?? defaultRuntimeCache(), false);
|
|
73
|
+
assertRuntimeWorkDirectoryOutsideInstall(cwd, cache);
|
|
74
|
+
const lease = { id: `execution-${randomUUID()}`, owner: `runtime-execution:${process.pid}` };
|
|
75
|
+
let drain;
|
|
76
|
+
try {
|
|
77
|
+
await ensureRuntimeComponents(value, { ...options, cacheDir: cache, host, lease });
|
|
78
|
+
if (options.lease)
|
|
79
|
+
await acquireRuntimeLease(cache, options.lease.id, options.lease.owner, value.manifest.components
|
|
80
|
+
.filter((item) => item.platform === host.platform)
|
|
81
|
+
.map(componentKey));
|
|
82
|
+
if (inspectRuntimeComponents(value, { ...options, cacheDir: cache, host }).status !== 'ready')
|
|
83
|
+
runtimeError('RUNTIME_LAUNCH_CHANGED', 'Runtime changed before launch.');
|
|
84
|
+
const artifact = (ref) => {
|
|
85
|
+
const component = value.manifest.components.find((item) => item.id === ref.component && item.platform === host.platform);
|
|
86
|
+
const file = component.files.find((item) => item.path === ref.path);
|
|
87
|
+
const filePath = cachePath(cache, `components/${componentKey(component)}/root/${ref.path}`);
|
|
88
|
+
return {
|
|
89
|
+
role: `runtime:${ref.component}:${ref.path}`,
|
|
90
|
+
path: filePath,
|
|
91
|
+
bytes: file.bytes,
|
|
92
|
+
sha256: file.sha256,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
const program = artifact(launch.executable);
|
|
96
|
+
const references = launch.argv
|
|
97
|
+
.filter((arg) => 'component' in arg)
|
|
98
|
+
.map(artifact);
|
|
99
|
+
const argv = launch.argv
|
|
100
|
+
.map((arg) => ('literal' in arg ? arg.literal : artifact(arg).path))
|
|
101
|
+
.concat(applicationArgv);
|
|
102
|
+
const spec = createFoundryCommandSpec({
|
|
103
|
+
executable: program.path,
|
|
104
|
+
argv,
|
|
105
|
+
binding: { artifacts: [program, ...references] },
|
|
106
|
+
});
|
|
107
|
+
const result = await executeFoundryCommandSpec(spec, {
|
|
108
|
+
cwd,
|
|
109
|
+
env: runtimeChildEnvironment(options.env ?? process.env, launch.environment),
|
|
110
|
+
signal: options.signal,
|
|
111
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
112
|
+
resolveArtifactPath: (file) => file,
|
|
113
|
+
spawnImpl: (executable, argv, childOptions) => {
|
|
114
|
+
const context = launch.context_protocol
|
|
115
|
+
? Object.freeze({ manifest: value, cacheDir: cache, cwd, entry: launch.id, host })
|
|
116
|
+
: undefined;
|
|
117
|
+
const spawn = spawnImpl ?? spawnRuntimeProcess;
|
|
118
|
+
drain = context
|
|
119
|
+
? spawn(executable, argv, childOptions, context)
|
|
120
|
+
: spawn(executable, argv, childOptions);
|
|
121
|
+
return drain;
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
await drain?.catch(() => undefined);
|
|
128
|
+
if (fs.existsSync(cachePath(cache, '.runtime-cache.json')))
|
|
129
|
+
await releaseRuntimeLease(cache, lease.id, lease.owner);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=execute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute.js","sourceRoot":"","sources":["../../../../src/lib/runtime/execute.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport { randomUUID } from 'node:crypto';\nimport {\n createFoundryCommandSpec,\n executeFoundryCommandSpec,\n type FoundryCommandSpecSpawnResult,\n type FoundryCommandSpecAsyncSpawnOptions,\n} from '../../command-spec.js';\nimport {\n ensureRuntimeComponents,\n inspectRuntimeComponents,\n type RuntimeManagerOptions,\n} from './manager.js';\nimport { spawnRuntimeProcess } from './process.js';\nimport { acquireRuntimeLease, releaseRuntimeLease } from './leases.js';\nimport { cachePath, defaultRuntimeCache, openRuntimeCache } from './storage.js';\nimport { assertTrustedManifest, componentKey } from './manifest.js';\nimport { inspectRuntimeHost } from './host.js';\nimport { runtimeError } from './files.js';\nimport {\n runtimeWorkDirectory,\n assertRuntimeWorkDirectoryOutsideInstall,\n} from './work-directory.js';\nimport type {\n TrustedRuntimeManifest,\n ComponentPath,\n RuntimeHostContext,\n} from './manifest-types.js';\n\nconst systemKeys = [\n 'PATH',\n 'Path',\n 'PATHEXT',\n 'SystemRoot',\n 'SYSTEMROOT',\n 'WINDIR',\n 'ComSpec',\n 'COMSPEC',\n 'HOME',\n 'USERPROFILE',\n 'APPDATA',\n 'LOCALAPPDATA',\n 'XDG_CONFIG_HOME',\n 'XDG_CACHE_HOME',\n 'TMPDIR',\n 'TEMP',\n 'TMP',\n 'LANG',\n 'LC_ALL',\n 'TZ',\n] as const;\nconst authKeys = [\n 'TIANGONG_LCA_API_BASE_URL',\n 'TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY',\n 'TIANGONG_LCA_OAUTH_CLIENT_ID',\n 'TIANGONG_LCA_OAUTH_REDIRECT_URI',\n 'TIANGONG_LCA_REGION',\n 'TIANGONG_LCA_AUTH_MODE',\n 'TIANGONG_LCA_SESSION_FILE',\n 'TIANGONG_LCA_DISABLE_SESSION_CACHE',\n 'TIANGONG_LCA_FORCE_REAUTH',\n 'TIANGONG_LCA_ACCESS_TOKEN',\n] as const;\nexport function runtimeChildEnvironment(\n source: NodeJS.ProcessEnv,\n mode: 'isolated' | 'cli-auth',\n): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {};\n for (const key of [...systemKeys, ...(mode === 'cli-auth' ? authKeys : [])])\n if (source[key] !== undefined) env[key] = source[key];\n return env;\n}\nexport type RuntimeExecutionOptions = RuntimeManagerOptions & {\n entry: string;\n cwd: string;\n argv: readonly string[];\n env?: Record<string, string | undefined>;\n};\nexport type RuntimeExecutionSpawn = (\n executable: string,\n argv: readonly string[],\n options: FoundryCommandSpecAsyncSpawnOptions,\n context?: RuntimeHostContext,\n) => Promise<FoundryCommandSpecSpawnResult>;\nexport async function executeRuntimeLaunch(\n value: TrustedRuntimeManifest,\n options: RuntimeExecutionOptions,\n spawnImpl?: RuntimeExecutionSpawn,\n): Promise<FoundryCommandSpecSpawnResult> {\n assertTrustedManifest(value);\n const observedHost = options.host ?? inspectRuntimeHost();\n const host = Object.freeze({\n platform: observedHost.platform,\n osRelease: observedHost.osRelease,\n glibc: observedHost.glibc,\n });\n const launch = value.manifest.launches.find(\n (item) => item.id === options.entry && item.platform === host.platform,\n );\n if (!launch)\n runtimeError('RUNTIME_LAUNCH_UNKNOWN', 'Selected runtime entry is not declared for this host.');\n if (options.argv.length > 512)\n runtimeError(\n 'RUNTIME_LAUNCH_ARGUMENT',\n 'Runtime argv must be bounded and contain no credential arguments.',\n );\n const applicationArgv = [...options.argv];\n if (\n applicationArgv.some(\n (arg) =>\n typeof arg !== 'string' ||\n arg.includes('\\0') ||\n arg.length > 32768 ||\n /^--(?:username|password|api-key|access-token|refresh-token|authorization-code|oauth-code)(?:=|$)/iu.test(\n arg,\n ),\n )\n )\n runtimeError(\n 'RUNTIME_LAUNCH_ARGUMENT',\n 'Runtime argv must be bounded and contain no credential arguments.',\n );\n const cwd = runtimeWorkDirectory(options.cwd),\n cache = openRuntimeCache(options.cacheDir ?? defaultRuntimeCache(), false);\n assertRuntimeWorkDirectoryOutsideInstall(cwd, cache);\n const lease = { id: `execution-${randomUUID()}`, owner: `runtime-execution:${process.pid}` };\n let drain: Promise<FoundryCommandSpecSpawnResult> | undefined;\n try {\n await ensureRuntimeComponents(value, { ...options, cacheDir: cache, host, lease });\n if (options.lease)\n await acquireRuntimeLease(\n cache,\n options.lease.id,\n options.lease.owner,\n value.manifest.components\n .filter((item) => item.platform === host.platform)\n .map(componentKey),\n );\n if (inspectRuntimeComponents(value, { ...options, cacheDir: cache, host }).status !== 'ready')\n runtimeError('RUNTIME_LAUNCH_CHANGED', 'Runtime changed before launch.');\n const artifact = (ref: ComponentPath) => {\n const component = value.manifest.components.find(\n (item) => item.id === ref.component && item.platform === host.platform,\n )!;\n const file = component.files.find((item) => item.path === ref.path)!;\n const filePath = cachePath(cache, `components/${componentKey(component)}/root/${ref.path}`);\n return {\n role: `runtime:${ref.component}:${ref.path}`,\n path: filePath,\n bytes: file.bytes,\n sha256: file.sha256,\n };\n };\n const program = artifact(launch.executable);\n const references = launch.argv\n .filter((arg): arg is ComponentPath => 'component' in arg)\n .map(artifact);\n const argv = launch.argv\n .map((arg) => ('literal' in arg ? arg.literal : artifact(arg).path))\n .concat(applicationArgv);\n const spec = createFoundryCommandSpec({\n executable: program.path,\n argv,\n binding: { artifacts: [program, ...references] },\n });\n const result = await executeFoundryCommandSpec(spec, {\n cwd,\n env: runtimeChildEnvironment(options.env ?? process.env, launch.environment),\n signal: options.signal,\n maxBuffer: 16 * 1024 * 1024,\n resolveArtifactPath: (file) => file,\n spawnImpl: (executable, argv, childOptions) => {\n const context = launch.context_protocol\n ? Object.freeze({ manifest: value, cacheDir: cache, cwd, entry: launch.id, host })\n : undefined;\n const spawn = spawnImpl ?? spawnRuntimeProcess;\n drain = context\n ? spawn(executable, argv, childOptions, context)\n : spawn(executable, argv, childOptions);\n return drain;\n },\n });\n return result;\n } finally {\n await drain?.catch(() => undefined);\n if (fs.existsSync(cachePath(cache, '.runtime-cache.json')))\n await releaseRuntimeLease(cache, lease.id, lease.owner);\n }\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RuntimeFileFact } from './types.js';
|
|
2
|
+
export declare function runtimeError(code: string, message: string): never;
|
|
3
|
+
export declare function contentHash(value: unknown): string;
|
|
4
|
+
/** Hash one descriptor without loading the whole executable or following a selected symlink. */
|
|
5
|
+
export declare function hashRuntimeFile(file: string, label: string): RuntimeFileFact;
|
|
6
|
+
export declare function assertInventoryBudget(count: number, bytes: number): void;
|
|
7
|
+
export declare function listRuntimeFiles(root: string): readonly RuntimeFileFact[];
|