@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.
Files changed (77) hide show
  1. package/README.md +18 -5
  2. package/assets/runtime/cli-runtime-descriptor.schema.json +138 -0
  3. package/assets/runtime/cli-runtime-expectation.schema.json +38 -0
  4. package/assets/runtime/runtime-bootstrap-lock.schema.json +254 -0
  5. package/assets/runtime/runtime-command-result.schema.json +95 -0
  6. package/assets/runtime/runtime-manifest.schema.json +380 -0
  7. package/dist/src/cli.d.ts +2 -0
  8. package/dist/src/cli.js +48 -2
  9. package/dist/src/cli.js.map +1 -1
  10. package/dist/src/lib/dataset-import-lca.js +2 -2
  11. package/dist/src/lib/dataset-import-lca.js.map +1 -1
  12. package/dist/src/lib/dataset-support-cache.d.ts +48 -0
  13. package/dist/src/lib/dataset-support-cache.js +265 -0
  14. package/dist/src/lib/dataset-support-cache.js.map +1 -0
  15. package/dist/src/lib/runtime/archive-writer.d.ts +7 -0
  16. package/dist/src/lib/runtime/archive-writer.js +89 -0
  17. package/dist/src/lib/runtime/archive-writer.js.map +1 -0
  18. package/dist/src/lib/runtime/archive.d.ts +2 -0
  19. package/dist/src/lib/runtime/archive.js +122 -0
  20. package/dist/src/lib/runtime/archive.js.map +1 -0
  21. package/dist/src/lib/runtime/command.d.ts +8 -0
  22. package/dist/src/lib/runtime/command.js +57 -0
  23. package/dist/src/lib/runtime/command.js.map +1 -0
  24. package/dist/src/lib/runtime/descriptor.d.ts +11 -0
  25. package/dist/src/lib/runtime/descriptor.js +126 -0
  26. package/dist/src/lib/runtime/descriptor.js.map +1 -0
  27. package/dist/src/lib/runtime/download.d.ts +9 -0
  28. package/dist/src/lib/runtime/download.js +118 -0
  29. package/dist/src/lib/runtime/download.js.map +1 -0
  30. package/dist/src/lib/runtime/exec-command.d.ts +7 -0
  31. package/dist/src/lib/runtime/exec-command.js +83 -0
  32. package/dist/src/lib/runtime/exec-command.js.map +1 -0
  33. package/dist/src/lib/runtime/execute.d.ts +11 -0
  34. package/dist/src/lib/runtime/execute.js +125 -0
  35. package/dist/src/lib/runtime/execute.js.map +1 -0
  36. package/dist/src/lib/runtime/files.d.ts +7 -0
  37. package/dist/src/lib/runtime/files.js +91 -0
  38. package/dist/src/lib/runtime/files.js.map +1 -0
  39. package/dist/src/lib/runtime/host.d.ts +8 -0
  40. package/dist/src/lib/runtime/host.js +37 -0
  41. package/dist/src/lib/runtime/host.js.map +1 -0
  42. package/dist/src/lib/runtime/leases.d.ts +12 -0
  43. package/dist/src/lib/runtime/leases.js +72 -0
  44. package/dist/src/lib/runtime/leases.js.map +1 -0
  45. package/dist/src/lib/runtime/managed-command.d.ts +6 -0
  46. package/dist/src/lib/runtime/managed-command.js +99 -0
  47. package/dist/src/lib/runtime/managed-command.js.map +1 -0
  48. package/dist/src/lib/runtime/manager.d.ts +33 -0
  49. package/dist/src/lib/runtime/manager.js +196 -0
  50. package/dist/src/lib/runtime/manager.js.map +1 -0
  51. package/dist/src/lib/runtime/manifest-types.d.ts +73 -0
  52. package/dist/src/lib/runtime/manifest-types.js +4 -0
  53. package/dist/src/lib/runtime/manifest-types.js.map +1 -0
  54. package/dist/src/lib/runtime/manifest-values.d.ts +15 -0
  55. package/dist/src/lib/runtime/manifest-values.js +124 -0
  56. package/dist/src/lib/runtime/manifest-values.js.map +1 -0
  57. package/dist/src/lib/runtime/manifest.d.ts +7 -0
  58. package/dist/src/lib/runtime/manifest.js +220 -0
  59. package/dist/src/lib/runtime/manifest.js.map +1 -0
  60. package/dist/src/lib/runtime/process.d.ts +3 -0
  61. package/dist/src/lib/runtime/process.js +55 -0
  62. package/dist/src/lib/runtime/process.js.map +1 -0
  63. package/dist/src/lib/runtime/storage.d.ts +8 -0
  64. package/dist/src/lib/runtime/storage.js +142 -0
  65. package/dist/src/lib/runtime/storage.js.map +1 -0
  66. package/dist/src/lib/runtime/types.d.ts +45 -0
  67. package/dist/src/lib/runtime/types.js +9 -0
  68. package/dist/src/lib/runtime/types.js.map +1 -0
  69. package/dist/src/lib/supabase-data-api-contract.d.ts +4 -4
  70. package/dist/src/lib/supabase-data-api-contract.js +2 -0
  71. package/dist/src/lib/supabase-data-api-contract.js.map +1 -1
  72. package/dist/src/main.js +13 -1
  73. package/dist/src/main.js.map +1 -1
  74. package/dist/src/runtime.d.ts +15 -0
  75. package/dist/src/runtime.js +22 -0
  76. package/dist/src/runtime.js.map +1 -0
  77. package/package.json +8 -2
@@ -0,0 +1,99 @@
1
+ import { parseArgs } from 'node:util';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { CliError } from '../errors.js';
5
+ import { loadTrustedRuntimeManifest } from './manifest.js';
6
+ import { ensureRuntimeComponents, inspectRuntimeComponents, pruneRuntimeComponents, } from './manager.js';
7
+ import { releaseRuntimeLease } from './leases.js';
8
+ import { defaultRuntimeCache, openRuntimeCache } from './storage.js';
9
+ export async function runManagedRuntimeCommand(operation, args, fetchImpl) {
10
+ let values;
11
+ try {
12
+ values = parseArgs({
13
+ args,
14
+ strict: true,
15
+ allowPositionals: false,
16
+ options: {
17
+ help: { type: 'boolean', short: 'h' },
18
+ json: { type: 'boolean' },
19
+ manifest: { type: 'string' },
20
+ 'manifest-sha256': { type: 'string' },
21
+ 'cache-dir': { type: 'string' },
22
+ lease: { type: 'string' },
23
+ 'lease-owner': { type: 'string' },
24
+ apply: { type: 'boolean' },
25
+ },
26
+ }).values;
27
+ }
28
+ catch {
29
+ throw new CliError('Invalid runtime management arguments.', {
30
+ code: 'RUNTIME_ARGUMENT_INVALID',
31
+ exitCode: 2,
32
+ });
33
+ }
34
+ if (values.help)
35
+ return {
36
+ exitCode: 0,
37
+ stdout: 'Usage: tiangong-lca runtime ensure|status|prune --manifest <file> --manifest-sha256 <trusted-sha256> [--cache-dir <absolute-dir>] [--json]\nensure accepts --lease <id> --lease-owner <non-secret-owner>. prune requires --apply.\nlease-release requires --lease and --lease-owner. No authentication or task state is created.\n',
38
+ stderr: '',
39
+ };
40
+ const cacheDir = values['cache-dir'] ?? defaultRuntimeCache();
41
+ if (operation === 'lease-release') {
42
+ if (!values.lease ||
43
+ !values['lease-owner'] ||
44
+ values.manifest ||
45
+ values['manifest-sha256'] ||
46
+ values.apply)
47
+ throw new CliError('Lease release requires only cache, lease id and owner.', {
48
+ code: 'RUNTIME_ARGUMENT_INVALID',
49
+ exitCode: 2,
50
+ });
51
+ const root = openRuntimeCache(cacheDir, false);
52
+ const released = fs.existsSync(path.join(root, '.runtime-cache.json'))
53
+ ? await releaseRuntimeLease(root, values.lease, values['lease-owner'])
54
+ : false;
55
+ return {
56
+ exitCode: 0,
57
+ stdout: JSON.stringify({ schema: 'tiangong-lca.runtime-lease-release.v1', released }) + '\n',
58
+ stderr: '',
59
+ };
60
+ }
61
+ if (!values.manifest ||
62
+ !values['manifest-sha256'] ||
63
+ Boolean(values.lease) !== Boolean(values['lease-owner']) ||
64
+ ((values.lease || values['lease-owner']) && operation !== 'ensure') ||
65
+ (values.apply && operation !== 'prune'))
66
+ throw new CliError('Select an explicit manifest and independent digest; lease options apply only to ensure.', { code: 'RUNTIME_ARGUMENT_INVALID', exitCode: 2 });
67
+ const trusted = loadTrustedRuntimeManifest(values.manifest, values['manifest-sha256']);
68
+ const options = {
69
+ cacheDir,
70
+ fetchImpl,
71
+ ...(values.lease ? { lease: { id: values.lease, owner: values['lease-owner'] } } : {}),
72
+ };
73
+ if (operation === 'prune') {
74
+ if (!values.apply)
75
+ throw new CliError('Pruning requires an explicit --apply for this manifest component set.', {
76
+ code: 'RUNTIME_PRUNE_APPLY_REQUIRED',
77
+ exitCode: 2,
78
+ });
79
+ return {
80
+ exitCode: 0,
81
+ stdout: JSON.stringify({
82
+ schema: 'tiangong-lca.runtime-prune.v1',
83
+ ...(await pruneRuntimeComponents(trusted, options)),
84
+ }) + '\n',
85
+ stderr: '',
86
+ };
87
+ }
88
+ const report = operation === 'ensure'
89
+ ? await ensureRuntimeComponents(trusted, options)
90
+ : inspectRuntimeComponents(trusted, options);
91
+ return {
92
+ exitCode: report.status === 'ready' ? 0 : 69,
93
+ stdout: values.json
94
+ ? JSON.stringify(report) + '\n'
95
+ : `Runtime ${report.status}: ${report.components.map((item) => `${item.id}@${item.version} ${item.status}`).join(', ')}\n`,
96
+ stderr: '',
97
+ };
98
+ }
99
+ //# sourceMappingURL=managed-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managed-command.js","sourceRoot":"","sources":["../../../../src/lib/runtime/managed-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { parseArgs } from 'node:util';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { CliError } from '../errors.js';\nimport type { FetchLike } from '../http.js';\nimport { loadTrustedRuntimeManifest } from './manifest.js';\nimport {\n ensureRuntimeComponents,\n inspectRuntimeComponents,\n pruneRuntimeComponents,\n} from './manager.js';\nimport { releaseRuntimeLease } from './leases.js';\nimport { defaultRuntimeCache, openRuntimeCache } from './storage.js';\n\nexport async function runManagedRuntimeCommand(\n operation: string,\n args: string[],\n fetchImpl?: FetchLike,\n): Promise<{ exitCode: number; stdout: string; stderr: string }> {\n let values;\n try {\n values = parseArgs({\n args,\n strict: true,\n allowPositionals: false,\n options: {\n help: { type: 'boolean', short: 'h' },\n json: { type: 'boolean' },\n manifest: { type: 'string' },\n 'manifest-sha256': { type: 'string' },\n 'cache-dir': { type: 'string' },\n lease: { type: 'string' },\n 'lease-owner': { type: 'string' },\n apply: { type: 'boolean' },\n },\n }).values;\n } catch {\n throw new CliError('Invalid runtime management arguments.', {\n code: 'RUNTIME_ARGUMENT_INVALID',\n exitCode: 2,\n });\n }\n if (values.help)\n return {\n exitCode: 0,\n stdout:\n 'Usage: tiangong-lca runtime ensure|status|prune --manifest <file> --manifest-sha256 <trusted-sha256> [--cache-dir <absolute-dir>] [--json]\\nensure accepts --lease <id> --lease-owner <non-secret-owner>. prune requires --apply.\\nlease-release requires --lease and --lease-owner. No authentication or task state is created.\\n',\n stderr: '',\n };\n const cacheDir = values['cache-dir'] ?? defaultRuntimeCache();\n if (operation === 'lease-release') {\n if (\n !values.lease ||\n !values['lease-owner'] ||\n values.manifest ||\n values['manifest-sha256'] ||\n values.apply\n )\n throw new CliError('Lease release requires only cache, lease id and owner.', {\n code: 'RUNTIME_ARGUMENT_INVALID',\n exitCode: 2,\n });\n const root = openRuntimeCache(cacheDir, false);\n const released = fs.existsSync(path.join(root, '.runtime-cache.json'))\n ? await releaseRuntimeLease(root, values.lease, values['lease-owner'])\n : false;\n return {\n exitCode: 0,\n stdout: JSON.stringify({ schema: 'tiangong-lca.runtime-lease-release.v1', released }) + '\\n',\n stderr: '',\n };\n }\n if (\n !values.manifest ||\n !values['manifest-sha256'] ||\n Boolean(values.lease) !== Boolean(values['lease-owner']) ||\n ((values.lease || values['lease-owner']) && operation !== 'ensure') ||\n (values.apply && operation !== 'prune')\n )\n throw new CliError(\n 'Select an explicit manifest and independent digest; lease options apply only to ensure.',\n { code: 'RUNTIME_ARGUMENT_INVALID', exitCode: 2 },\n );\n const trusted = loadTrustedRuntimeManifest(values.manifest, values['manifest-sha256']);\n const options = {\n cacheDir,\n fetchImpl,\n ...(values.lease ? { lease: { id: values.lease, owner: values['lease-owner']! } } : {}),\n };\n if (operation === 'prune') {\n if (!values.apply)\n throw new CliError('Pruning requires an explicit --apply for this manifest component set.', {\n code: 'RUNTIME_PRUNE_APPLY_REQUIRED',\n exitCode: 2,\n });\n return {\n exitCode: 0,\n stdout:\n JSON.stringify({\n schema: 'tiangong-lca.runtime-prune.v1',\n ...(await pruneRuntimeComponents(trusted, options)),\n }) + '\\n',\n stderr: '',\n };\n }\n const report =\n operation === 'ensure'\n ? await ensureRuntimeComponents(trusted, options)\n : inspectRuntimeComponents(trusted, options);\n return {\n exitCode: report.status === 'ready' ? 0 : 69,\n stdout: values.json\n ? JSON.stringify(report) + '\\n'\n : `Runtime ${report.status}: ${report.components.map((item) => `${item.id}@${item.version} ${item.status}`).join(', ')}\\n`,\n stderr: '',\n };\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import { type DownloadOptions } from './download.js';
2
+ import type { RuntimeComponent, RuntimeHost, TrustedRuntimeManifest } from './manifest-types.js';
3
+ export type RuntimeManagerOptions = DownloadOptions & {
4
+ cacheDir?: string;
5
+ host?: RuntimeHost;
6
+ lease?: {
7
+ id: string;
8
+ owner: string;
9
+ };
10
+ archiveSeeds?: Readonly<Record<string, string>>;
11
+ };
12
+ export type RuntimeComponentStatus = {
13
+ id: string;
14
+ version: string;
15
+ key: string;
16
+ status: 'ready' | 'missing' | 'unverified' | 'corrupt';
17
+ root: string;
18
+ reason: string | null;
19
+ };
20
+ export type RuntimeManagerReport = {
21
+ schema: 'tiangong-lca.runtime-status.v1';
22
+ manifest_sha256: string;
23
+ platform: string;
24
+ status: 'ready' | 'missing' | 'blocked';
25
+ components: RuntimeComponentStatus[];
26
+ };
27
+ export declare function verifyRuntimeComponent(root: string, component: RuntimeComponent, platform: string): void;
28
+ export declare function inspectRuntimeComponents(value: TrustedRuntimeManifest, options?: RuntimeManagerOptions): RuntimeManagerReport;
29
+ export declare function ensureRuntimeComponents(value: TrustedRuntimeManifest, options?: RuntimeManagerOptions): Promise<RuntimeManagerReport>;
30
+ export declare function pruneRuntimeComponents(value: TrustedRuntimeManifest, options?: RuntimeManagerOptions): Promise<{
31
+ removed: string[];
32
+ retained: string[];
33
+ }>;
@@ -0,0 +1,196 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { withBatchRunLock } from '../../batch.js';
5
+ import { contentHash, hashRuntimeFile, runtimeError } from './files.js';
6
+ import { assertTrustedManifest, componentKey } from './manifest.js';
7
+ import { assertRuntimeHost, inspectRuntimeHost } from './host.js';
8
+ import { cachePath, defaultRuntimeCache, ensureRuntimeCache, openRuntimeCache, readCacheJson, writeOnce, } from './storage.js';
9
+ import { downloadRuntimeArchive } from './download.js';
10
+ import { extractRuntimeArchive } from './archive.js';
11
+ import { acquireRuntimeLease, leasedRuntimeKeys, withRuntimeLeaseLock } from './leases.js';
12
+ function receipt(component) {
13
+ return {
14
+ schema: 'tiangong-lca.runtime-component.v1',
15
+ key: componentKey(component),
16
+ archive_sha256: component.archive.sha256,
17
+ content_sha256: component.content_sha256,
18
+ };
19
+ }
20
+ export function verifyRuntimeComponent(root, component, platform) {
21
+ const expected = new Map(component.files.map((file) => [file.path, file]));
22
+ let count = 0;
23
+ const walk = (relative) => {
24
+ const directory = relative ? cachePath(root, relative) : root;
25
+ const stat = fs.lstatSync(directory);
26
+ if (stat.isSymbolicLink() || !stat.isDirectory())
27
+ runtimeError('RUNTIME_COMPONENT_PATH', 'Component directories cannot be links.');
28
+ for (const name of fs.readdirSync(directory)) {
29
+ const file = relative ? `${relative}/${name}` : name;
30
+ const target = cachePath(root, file);
31
+ const item = fs.lstatSync(target);
32
+ if (item.isDirectory())
33
+ walk(file);
34
+ else {
35
+ const expectedFile = expected.get(file);
36
+ if (!expectedFile)
37
+ runtimeError('RUNTIME_COMPONENT_EXTRA', 'Component contains an unregistered file.');
38
+ const actual = hashRuntimeFile(target, file);
39
+ if (actual.bytes !== expectedFile.bytes ||
40
+ actual.sha256 !== expectedFile.sha256 ||
41
+ (platform !== 'win32-x64' && (item.mode & 0o777) !== expectedFile.mode))
42
+ runtimeError('RUNTIME_COMPONENT_CHANGED', 'Component file bytes, digest or executable mode changed.');
43
+ count++;
44
+ }
45
+ }
46
+ };
47
+ walk('');
48
+ if (count !== component.files.length)
49
+ runtimeError('RUNTIME_COMPONENT_MISSING', 'Component inventory is incomplete.');
50
+ }
51
+ function inspectComponent(cache, component, platform) {
52
+ const key = componentKey(component), relative = `components/${key}`, root = cachePath(cache, `${relative}/root`);
53
+ const base = { id: component.id, version: component.version, key, root };
54
+ if (!fs.existsSync(cachePath(cache, relative)))
55
+ return { ...base, status: 'missing', reason: null };
56
+ try {
57
+ if (fs
58
+ .readdirSync(cachePath(cache, relative))
59
+ .some((name) => !['root', 'receipt.json'].includes(name)))
60
+ runtimeError('RUNTIME_COMPONENT_EXTRA', 'Component installation directory contains unknown data.');
61
+ if (!fs.existsSync(cachePath(cache, `${relative}/receipt.json`)))
62
+ return { ...base, status: 'unverified', reason: 'runtime_install_incomplete' };
63
+ if (contentHash(readCacheJson(cache, `${relative}/receipt.json`)) !==
64
+ contentHash(receipt(component)))
65
+ runtimeError('RUNTIME_RECEIPT_CHANGED', 'Component installation receipt does not match the selected manifest.');
66
+ verifyRuntimeComponent(root, component, platform);
67
+ return { ...base, status: 'ready', reason: null };
68
+ }
69
+ catch (error) {
70
+ return {
71
+ ...base,
72
+ status: 'corrupt',
73
+ reason: typeof error.code === 'string'
74
+ ? error.code
75
+ : 'runtime_component_invalid',
76
+ };
77
+ }
78
+ }
79
+ function selected(value, options) {
80
+ assertTrustedManifest(value);
81
+ const host = options.host ?? inspectRuntimeHost();
82
+ assertRuntimeHost(value, host);
83
+ return {
84
+ host,
85
+ components: value.manifest.components.filter((component) => component.platform === host.platform),
86
+ };
87
+ }
88
+ export function inspectRuntimeComponents(value, options = {}) {
89
+ const { host, components } = selected(value, options), cache = openRuntimeCache(options.cacheDir ?? defaultRuntimeCache(), false);
90
+ const states = components.map((component) => inspectComponent(cache, component, host.platform));
91
+ return {
92
+ schema: 'tiangong-lca.runtime-status.v1',
93
+ manifest_sha256: value.sha256,
94
+ platform: host.platform,
95
+ status: states.every((item) => item.status === 'ready')
96
+ ? 'ready'
97
+ : states.some((item) => item.status === 'corrupt' || item.status === 'unverified')
98
+ ? 'blocked'
99
+ : 'missing',
100
+ components: states,
101
+ };
102
+ }
103
+ async function installComponent(cache, component, options, platform) {
104
+ const key = componentKey(component);
105
+ await withBatchRunLock({
106
+ runPath: cachePath(cache, `locks/${key}.json`),
107
+ identity: { schema: 'runtime-component-lock.v1', key },
108
+ reason: 'Runtime component installation',
109
+ }, async () => {
110
+ const state = inspectComponent(cache, component, platform);
111
+ if (state.status === 'ready')
112
+ return;
113
+ const target = cachePath(cache, `components/${key}`);
114
+ if (state.status === 'unverified') {
115
+ // Bootstrap may publish a complete tree before Node can produce the CLI receipt.
116
+ // It is cache data only: adopt after every declared byte/mode and absence of extra files is proved.
117
+ verifyRuntimeComponent(state.root, component, platform);
118
+ writeOnce(cache, `components/${key}/receipt.json`, Buffer.from(JSON.stringify(receipt(component)) + '\n'));
119
+ return;
120
+ }
121
+ if (state.status === 'corrupt')
122
+ runtimeError('RUNTIME_CACHE_CORRUPT', 'A selected cached component is corrupt; preserve active leases and prune it explicitly.');
123
+ const staging = cachePath(cache, `tmp/${key}-${randomUUID()}`);
124
+ fs.mkdirSync(staging, { recursive: true, mode: 0o700 });
125
+ try {
126
+ const archive = path.join(staging, 'component.tar.gz'), seed = options.archiveSeeds?.[key];
127
+ if (seed) {
128
+ const fact = hashRuntimeFile(seed, 'archive');
129
+ if (fact.bytes !== component.archive.bytes || fact.sha256 !== component.archive.sha256)
130
+ runtimeError('RUNTIME_ARCHIVE_SEED', 'Supplied archive seed differs from the trusted component.');
131
+ fs.copyFileSync(seed, archive, fs.constants.COPYFILE_EXCL);
132
+ }
133
+ else
134
+ await downloadRuntimeArchive(component.archive, archive, options);
135
+ await extractRuntimeArchive(archive, path.join(staging, 'root'), component, options.signal);
136
+ verifyRuntimeComponent(path.join(staging, 'root'), component, platform);
137
+ fs.unlinkSync(archive);
138
+ writeOnce(staging, 'receipt.json', Buffer.from(JSON.stringify(receipt(component)) + '\n'));
139
+ fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
140
+ cachePath(cache, `components/${key}`);
141
+ if (fs.existsSync(target))
142
+ runtimeError('RUNTIME_INSTALL_CONFLICT', 'Another component tree appeared outside the installation lock.');
143
+ fs.renameSync(staging, target);
144
+ }
145
+ finally {
146
+ if (fs.existsSync(staging))
147
+ fs.rmSync(staging, { recursive: true, force: true });
148
+ }
149
+ });
150
+ }
151
+ export async function ensureRuntimeComponents(value, options = {}) {
152
+ const { host, components } = selected(value, options);
153
+ options.signal?.throwIfAborted();
154
+ const cache = await ensureRuntimeCache(options.cacheDir ?? defaultRuntimeCache());
155
+ if (options.lease)
156
+ await acquireRuntimeLease(cache, options.lease.id, options.lease.owner, components.map(componentKey));
157
+ for (const component of components) {
158
+ options.signal?.throwIfAborted();
159
+ await installComponent(cache, component, options, host.platform);
160
+ }
161
+ return inspectRuntimeComponents(value, { ...options, cacheDir: cache, host });
162
+ }
163
+ export async function pruneRuntimeComponents(value, options = {}) {
164
+ const { components } = selected(value, options);
165
+ const cache = openRuntimeCache(options.cacheDir ?? defaultRuntimeCache(), false);
166
+ if (!fs.existsSync(cachePath(cache, '.runtime-cache.json')))
167
+ return { removed: [], retained: [] };
168
+ return withRuntimeLeaseLock(cache, async () => {
169
+ const pinned = leasedRuntimeKeys(cache), removed = [], retained = [];
170
+ for (const component of components) {
171
+ const key = componentKey(component);
172
+ if (pinned.has(key)) {
173
+ retained.push(key);
174
+ continue;
175
+ }
176
+ await withBatchRunLock({
177
+ runPath: cachePath(cache, `locks/${key}.json`),
178
+ identity: { schema: 'runtime-component-lock.v1', key },
179
+ reason: 'Explicit unused runtime pruning',
180
+ }, () => {
181
+ const target = cachePath(cache, `components/${key}`);
182
+ if (!fs.existsSync(target))
183
+ return;
184
+ if (fs.readdirSync(target).some((name) => !['root', 'receipt.json'].includes(name)))
185
+ runtimeError('RUNTIME_PRUNE_UNOWNED', 'Unknown files must be preserved instead of pruned.');
186
+ if (contentHash(readCacheJson(cache, `components/${key}/receipt.json`)) !==
187
+ contentHash(receipt(component)))
188
+ runtimeError('RUNTIME_PRUNE_UNOWNED', 'Only an installation with its exact ownership receipt can be pruned.');
189
+ fs.rmSync(target, { recursive: true, force: false });
190
+ removed.push(key);
191
+ });
192
+ }
193
+ return { removed, retained };
194
+ });
195
+ }
196
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../../src/lib/runtime/manager.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport { withBatchRunLock } from '../../batch.js';\nimport { contentHash, hashRuntimeFile, runtimeError } from './files.js';\nimport { assertTrustedManifest, componentKey } from './manifest.js';\nimport { assertRuntimeHost, inspectRuntimeHost } from './host.js';\nimport {\n cachePath,\n defaultRuntimeCache,\n ensureRuntimeCache,\n openRuntimeCache,\n readCacheJson,\n writeOnce,\n} from './storage.js';\nimport { downloadRuntimeArchive, type DownloadOptions } from './download.js';\nimport { extractRuntimeArchive } from './archive.js';\nimport { acquireRuntimeLease, leasedRuntimeKeys, withRuntimeLeaseLock } from './leases.js';\nimport type { RuntimeComponent, RuntimeHost, TrustedRuntimeManifest } from './manifest-types.js';\n\nexport type RuntimeManagerOptions = DownloadOptions & {\n cacheDir?: string;\n host?: RuntimeHost;\n lease?: { id: string; owner: string };\n archiveSeeds?: Readonly<Record<string, string>>;\n};\nexport type RuntimeComponentStatus = {\n id: string;\n version: string;\n key: string;\n status: 'ready' | 'missing' | 'unverified' | 'corrupt';\n root: string;\n reason: string | null;\n};\nexport type RuntimeManagerReport = {\n schema: 'tiangong-lca.runtime-status.v1';\n manifest_sha256: string;\n platform: string;\n status: 'ready' | 'missing' | 'blocked';\n components: RuntimeComponentStatus[];\n};\nfunction receipt(component: RuntimeComponent) {\n return {\n schema: 'tiangong-lca.runtime-component.v1',\n key: componentKey(component),\n archive_sha256: component.archive.sha256,\n content_sha256: component.content_sha256,\n };\n}\nexport function verifyRuntimeComponent(\n root: string,\n component: RuntimeComponent,\n platform: string,\n): void {\n const expected = new Map(component.files.map((file) => [file.path, file]));\n let count = 0;\n const walk = (relative: string): void => {\n const directory = relative ? cachePath(root, relative) : root;\n const stat = fs.lstatSync(directory);\n if (stat.isSymbolicLink() || !stat.isDirectory())\n runtimeError('RUNTIME_COMPONENT_PATH', 'Component directories cannot be links.');\n for (const name of fs.readdirSync(directory)) {\n const file = relative ? `${relative}/${name}` : name;\n const target = cachePath(root, file);\n const item = fs.lstatSync(target);\n if (item.isDirectory()) walk(file);\n else {\n const expectedFile = expected.get(file);\n if (!expectedFile)\n runtimeError('RUNTIME_COMPONENT_EXTRA', 'Component contains an unregistered file.');\n const actual = hashRuntimeFile(target, file);\n if (\n actual.bytes !== expectedFile.bytes ||\n actual.sha256 !== expectedFile.sha256 ||\n (platform !== 'win32-x64' && (item.mode & 0o777) !== expectedFile.mode)\n )\n runtimeError(\n 'RUNTIME_COMPONENT_CHANGED',\n 'Component file bytes, digest or executable mode changed.',\n );\n count++;\n }\n }\n };\n walk('');\n if (count !== component.files.length)\n runtimeError('RUNTIME_COMPONENT_MISSING', 'Component inventory is incomplete.');\n}\nfunction inspectComponent(\n cache: string,\n component: RuntimeComponent,\n platform: string,\n): RuntimeComponentStatus {\n const key = componentKey(component),\n relative = `components/${key}`,\n root = cachePath(cache, `${relative}/root`);\n const base = { id: component.id, version: component.version, key, root };\n if (!fs.existsSync(cachePath(cache, relative)))\n return { ...base, status: 'missing', reason: null };\n try {\n if (\n fs\n .readdirSync(cachePath(cache, relative))\n .some((name) => !['root', 'receipt.json'].includes(name))\n )\n runtimeError(\n 'RUNTIME_COMPONENT_EXTRA',\n 'Component installation directory contains unknown data.',\n );\n if (!fs.existsSync(cachePath(cache, `${relative}/receipt.json`)))\n return { ...base, status: 'unverified', reason: 'runtime_install_incomplete' };\n if (\n contentHash(readCacheJson(cache, `${relative}/receipt.json`)) !==\n contentHash(receipt(component))\n )\n runtimeError(\n 'RUNTIME_RECEIPT_CHANGED',\n 'Component installation receipt does not match the selected manifest.',\n );\n verifyRuntimeComponent(root, component, platform);\n return { ...base, status: 'ready', reason: null };\n } catch (error) {\n return {\n ...base,\n status: 'corrupt',\n reason:\n typeof (error as { code?: unknown }).code === 'string'\n ? (error as { code: string }).code\n : 'runtime_component_invalid',\n };\n }\n}\nfunction selected(value: TrustedRuntimeManifest, options: RuntimeManagerOptions) {\n assertTrustedManifest(value);\n const host = options.host ?? inspectRuntimeHost();\n assertRuntimeHost(value, host);\n return {\n host,\n components: value.manifest.components.filter(\n (component) => component.platform === host.platform,\n ),\n };\n}\nexport function inspectRuntimeComponents(\n value: TrustedRuntimeManifest,\n options: RuntimeManagerOptions = {},\n): RuntimeManagerReport {\n const { host, components } = selected(value, options),\n cache = openRuntimeCache(options.cacheDir ?? defaultRuntimeCache(), false);\n const states = components.map((component) => inspectComponent(cache, component, host.platform));\n return {\n schema: 'tiangong-lca.runtime-status.v1',\n manifest_sha256: value.sha256,\n platform: host.platform,\n status: states.every((item) => item.status === 'ready')\n ? 'ready'\n : states.some((item) => item.status === 'corrupt' || item.status === 'unverified')\n ? 'blocked'\n : 'missing',\n components: states,\n };\n}\nasync function installComponent(\n cache: string,\n component: RuntimeComponent,\n options: RuntimeManagerOptions,\n platform: string,\n): Promise<void> {\n const key = componentKey(component);\n await withBatchRunLock(\n {\n runPath: cachePath(cache, `locks/${key}.json`),\n identity: { schema: 'runtime-component-lock.v1', key },\n reason: 'Runtime component installation',\n },\n async () => {\n const state = inspectComponent(cache, component, platform);\n if (state.status === 'ready') return;\n const target = cachePath(cache, `components/${key}`);\n if (state.status === 'unverified') {\n // Bootstrap may publish a complete tree before Node can produce the CLI receipt.\n // It is cache data only: adopt after every declared byte/mode and absence of extra files is proved.\n verifyRuntimeComponent(state.root, component, platform);\n writeOnce(\n cache,\n `components/${key}/receipt.json`,\n Buffer.from(JSON.stringify(receipt(component)) + '\\n'),\n );\n return;\n }\n if (state.status === 'corrupt')\n runtimeError(\n 'RUNTIME_CACHE_CORRUPT',\n 'A selected cached component is corrupt; preserve active leases and prune it explicitly.',\n );\n const staging = cachePath(cache, `tmp/${key}-${randomUUID()}`);\n fs.mkdirSync(staging, { recursive: true, mode: 0o700 });\n try {\n const archive = path.join(staging, 'component.tar.gz'),\n seed = options.archiveSeeds?.[key];\n if (seed) {\n const fact = hashRuntimeFile(seed, 'archive');\n if (fact.bytes !== component.archive.bytes || fact.sha256 !== component.archive.sha256)\n runtimeError(\n 'RUNTIME_ARCHIVE_SEED',\n 'Supplied archive seed differs from the trusted component.',\n );\n fs.copyFileSync(seed, archive, fs.constants.COPYFILE_EXCL);\n } else await downloadRuntimeArchive(component.archive, archive, options);\n await extractRuntimeArchive(archive, path.join(staging, 'root'), component, options.signal);\n verifyRuntimeComponent(path.join(staging, 'root'), component, platform);\n fs.unlinkSync(archive);\n writeOnce(staging, 'receipt.json', Buffer.from(JSON.stringify(receipt(component)) + '\\n'));\n fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });\n cachePath(cache, `components/${key}`);\n if (fs.existsSync(target))\n runtimeError(\n 'RUNTIME_INSTALL_CONFLICT',\n 'Another component tree appeared outside the installation lock.',\n );\n fs.renameSync(staging, target);\n } finally {\n if (fs.existsSync(staging)) fs.rmSync(staging, { recursive: true, force: true });\n }\n },\n );\n}\nexport async function ensureRuntimeComponents(\n value: TrustedRuntimeManifest,\n options: RuntimeManagerOptions = {},\n): Promise<RuntimeManagerReport> {\n const { host, components } = selected(value, options);\n options.signal?.throwIfAborted();\n const cache = await ensureRuntimeCache(options.cacheDir ?? defaultRuntimeCache());\n if (options.lease)\n await acquireRuntimeLease(\n cache,\n options.lease.id,\n options.lease.owner,\n components.map(componentKey),\n );\n for (const component of components) {\n options.signal?.throwIfAborted();\n await installComponent(cache, component, options, host.platform);\n }\n return inspectRuntimeComponents(value, { ...options, cacheDir: cache, host });\n}\nexport async function pruneRuntimeComponents(\n value: TrustedRuntimeManifest,\n options: RuntimeManagerOptions = {},\n): Promise<{ removed: string[]; retained: string[] }> {\n const { components } = selected(value, options);\n const cache = openRuntimeCache(options.cacheDir ?? defaultRuntimeCache(), false);\n if (!fs.existsSync(cachePath(cache, '.runtime-cache.json'))) return { removed: [], retained: [] };\n return withRuntimeLeaseLock(cache, async () => {\n const pinned = leasedRuntimeKeys(cache),\n removed: string[] = [],\n retained: string[] = [];\n for (const component of components) {\n const key = componentKey(component);\n if (pinned.has(key)) {\n retained.push(key);\n continue;\n }\n await withBatchRunLock(\n {\n runPath: cachePath(cache, `locks/${key}.json`),\n identity: { schema: 'runtime-component-lock.v1', key },\n reason: 'Explicit unused runtime pruning',\n },\n () => {\n const target = cachePath(cache, `components/${key}`);\n if (!fs.existsSync(target)) return;\n if (fs.readdirSync(target).some((name) => !['root', 'receipt.json'].includes(name)))\n runtimeError(\n 'RUNTIME_PRUNE_UNOWNED',\n 'Unknown files must be preserved instead of pruned.',\n );\n if (\n contentHash(readCacheJson(cache, `components/${key}/receipt.json`)) !==\n contentHash(receipt(component))\n )\n runtimeError(\n 'RUNTIME_PRUNE_UNOWNED',\n 'Only an installation with its exact ownership receipt can be pruned.',\n );\n fs.rmSync(target, { recursive: true, force: false });\n removed.push(key);\n },\n );\n }\n return { removed, retained };\n });\n}\n"]}
@@ -0,0 +1,73 @@
1
+ import type { RuntimePlatform } from './types.js';
2
+ export declare const RUNTIME_MANIFEST_SCHEMA: 'tiangong-lca.runtime-manifest.v1';
3
+ export declare const RUNTIME_BOOTSTRAP_PROTOCOL: 'tiangong-lca.runtime-bootstrap.v1';
4
+ export declare const RUNTIME_ARCHIVE_FORMAT: 'tar-gzip-ustar-v1';
5
+ export type ComponentFile = Readonly<{
6
+ path: string;
7
+ bytes: number;
8
+ sha256: string;
9
+ mode: 420 | 493;
10
+ }>;
11
+ export type RuntimeComponent = Readonly<{
12
+ id: string;
13
+ version: string;
14
+ platform: RuntimePlatform;
15
+ archive: Readonly<{
16
+ format: typeof RUNTIME_ARCHIVE_FORMAT;
17
+ url: string;
18
+ bytes: number;
19
+ sha256: string;
20
+ }>;
21
+ files: readonly ComponentFile[];
22
+ content_sha256: string;
23
+ production_lock: string;
24
+ sbom: string;
25
+ licenses: readonly string[];
26
+ provenance: readonly string[];
27
+ protocols: readonly string[];
28
+ asset_fingerprints: Readonly<Record<string, string>>;
29
+ }>;
30
+ export type ComponentPath = Readonly<{
31
+ component: string;
32
+ path: string;
33
+ }>;
34
+ export type RuntimeLaunch = Readonly<{
35
+ id: string;
36
+ platform: RuntimePlatform;
37
+ executable: ComponentPath;
38
+ environment: 'isolated' | 'cli-auth';
39
+ argv: readonly (ComponentPath | Readonly<{
40
+ literal: string;
41
+ }>)[];
42
+ }>;
43
+ export type WorkspaceCompatibility = Readonly<{
44
+ schema: string;
45
+ features: readonly string[];
46
+ }>;
47
+ export type RuntimeManifest = Readonly<{
48
+ schema: typeof RUNTIME_MANIFEST_SCHEMA;
49
+ bootstrap_protocol: typeof RUNTIME_BOOTSTRAP_PROTOCOL;
50
+ product: Readonly<{
51
+ id: string;
52
+ version: string;
53
+ }>;
54
+ minimum_hosts: Readonly<Partial<Record<RuntimePlatform, Readonly<{
55
+ os_release: string;
56
+ glibc: string | null;
57
+ }>>>>;
58
+ workspace: Readonly<{
59
+ read: readonly WorkspaceCompatibility[];
60
+ write: readonly WorkspaceCompatibility[];
61
+ }>;
62
+ components: readonly RuntimeComponent[];
63
+ launches: readonly RuntimeLaunch[];
64
+ }>;
65
+ export type TrustedRuntimeManifest = Readonly<{
66
+ sha256: string;
67
+ manifest: RuntimeManifest;
68
+ }>;
69
+ export type RuntimeHost = Readonly<{
70
+ platform: RuntimePlatform;
71
+ osRelease: string;
72
+ glibc: string | null;
73
+ }>;
@@ -0,0 +1,4 @@
1
+ export const RUNTIME_MANIFEST_SCHEMA = 'tiangong-lca.runtime-manifest.v1';
2
+ export const RUNTIME_BOOTSTRAP_PROTOCOL = 'tiangong-lca.runtime-bootstrap.v1';
3
+ export const RUNTIME_ARCHIVE_FORMAT = 'tar-gzip-ustar-v1';
4
+ //# sourceMappingURL=manifest-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-types.js","sourceRoot":"","sources":["../../../../src/lib/runtime/manifest-types.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,uBAAuB,GAAG,kCAA2C,CAAC;AACnF,MAAM,CAAC,MAAM,0BAA0B,GAAG,mCAA4C,CAAC;AACvF,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAA4B,CAAC","sourcesContent":["import type { RuntimePlatform } from './types.js';\n\nexport const RUNTIME_MANIFEST_SCHEMA = 'tiangong-lca.runtime-manifest.v1' as const;\nexport const RUNTIME_BOOTSTRAP_PROTOCOL = 'tiangong-lca.runtime-bootstrap.v1' as const;\nexport const RUNTIME_ARCHIVE_FORMAT = 'tar-gzip-ustar-v1' as const;\nexport type ComponentFile = Readonly<{\n path: string;\n bytes: number;\n sha256: string;\n mode: 420 | 493;\n}>;\nexport type RuntimeComponent = Readonly<{\n id: string;\n version: string;\n platform: RuntimePlatform;\n archive: Readonly<{\n format: typeof RUNTIME_ARCHIVE_FORMAT;\n url: string;\n bytes: number;\n sha256: string;\n }>;\n files: readonly ComponentFile[];\n content_sha256: string;\n production_lock: string;\n sbom: string;\n licenses: readonly string[];\n provenance: readonly string[];\n protocols: readonly string[];\n asset_fingerprints: Readonly<Record<string, string>>;\n}>;\nexport type ComponentPath = Readonly<{ component: string; path: string }>;\nexport type RuntimeLaunch = Readonly<{\n id: string;\n platform: RuntimePlatform;\n executable: ComponentPath;\n environment: 'isolated' | 'cli-auth';\n argv: readonly (ComponentPath | Readonly<{ literal: string }>)[];\n}>;\nexport type WorkspaceCompatibility = Readonly<{ schema: string; features: readonly string[] }>;\nexport type RuntimeManifest = Readonly<{\n schema: typeof RUNTIME_MANIFEST_SCHEMA;\n bootstrap_protocol: typeof RUNTIME_BOOTSTRAP_PROTOCOL;\n product: Readonly<{ id: string; version: string }>;\n minimum_hosts: Readonly<\n Partial<Record<RuntimePlatform, Readonly<{ os_release: string; glibc: string | null }>>>\n >;\n workspace: Readonly<{\n read: readonly WorkspaceCompatibility[];\n write: readonly WorkspaceCompatibility[];\n }>;\n components: readonly RuntimeComponent[];\n launches: readonly RuntimeLaunch[];\n}>;\nexport type TrustedRuntimeManifest = Readonly<{ sha256: string; manifest: RuntimeManifest }>;\nexport type RuntimeHost = Readonly<{\n platform: RuntimePlatform;\n osRelease: string;\n glibc: string | null;\n}>;\n"]}
@@ -0,0 +1,15 @@
1
+ import { type RuntimePlatform } from './types.js';
2
+ export declare function record(value: unknown, label: string): Record<string, unknown>;
3
+ export declare function invalid(label: string): never;
4
+ export declare function exact(value: Record<string, unknown>, keys: readonly string[], label: string): void;
5
+ export declare function text(value: unknown, label: string, max?: number): string;
6
+ export declare function id(value: unknown): string;
7
+ export declare function version(value: unknown): string;
8
+ export declare function sha(value: unknown): string;
9
+ export declare function integer(value: unknown, max: number, minimum?: number): number;
10
+ export declare function array(value: unknown, max: number, minimum?: number): unknown[];
11
+ export declare function platform(value: unknown): RuntimePlatform;
12
+ export declare function relativeFile(value: unknown): string;
13
+ export declare function unique(values: readonly string[], label: string): void;
14
+ export declare function distributionUrl(value: unknown, redirected?: boolean): string;
15
+ export declare function deepFreeze<T>(value: T): T;
@@ -0,0 +1,124 @@
1
+ import { runtimeError } from './files.js';
2
+ import { RUNTIME_PLATFORMS } from './types.js';
3
+ export function record(value, label) {
4
+ if (!value || typeof value !== 'object' || Array.isArray(value))
5
+ invalid(label);
6
+ return value;
7
+ }
8
+ export function invalid(label) {
9
+ return runtimeError('RUNTIME_MANIFEST_INVALID', `Runtime manifest has invalid ${label}.`);
10
+ }
11
+ export function exact(value, keys, label) {
12
+ if (Object.keys(value).length !== keys.length || keys.some((key) => !Object.hasOwn(value, key)))
13
+ invalid(label);
14
+ }
15
+ export function text(value, label, max = 256) {
16
+ if (typeof value !== 'string' ||
17
+ !value ||
18
+ value.length > max ||
19
+ value.split('').some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
20
+ invalid(label);
21
+ return value;
22
+ }
23
+ export function id(value) {
24
+ const result = text(value, 'identifier', 64);
25
+ if (!/^[a-z0-9][a-z0-9._-]*$/u.test(result) || result === '.' || result === '..')
26
+ invalid('identifier');
27
+ return result;
28
+ }
29
+ export function version(value) {
30
+ const result = text(value, 'version', 40);
31
+ if (!/^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)$/u.test(result))
32
+ invalid('exact stable version');
33
+ return result;
34
+ }
35
+ export function sha(value) {
36
+ const result = text(value, 'SHA-256', 64);
37
+ if (!/^[0-9a-f]{64}$/u.test(result))
38
+ invalid('SHA-256');
39
+ return result;
40
+ }
41
+ export function integer(value, max, minimum = 0) {
42
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < minimum || value > max)
43
+ invalid('bounded integer');
44
+ return value;
45
+ }
46
+ export function array(value, max, minimum = 0) {
47
+ if (!Array.isArray(value) || value.length > max || value.length < minimum)
48
+ invalid('bounded array');
49
+ return value;
50
+ }
51
+ export function platform(value) {
52
+ if (!RUNTIME_PLATFORMS.includes(value))
53
+ invalid('platform');
54
+ return value;
55
+ }
56
+ export function relativeFile(value) {
57
+ const result = text(value, 'portable file path', 255);
58
+ if (Buffer.byteLength(result) > 255 ||
59
+ result.includes('\\') ||
60
+ result.includes(':') ||
61
+ result.startsWith('/') ||
62
+ result
63
+ .split('/')
64
+ .some((part) => !part ||
65
+ part === '.' ||
66
+ part === '..' ||
67
+ /^(?:\.env(?:\..*)?|\.git|\.npmrc|\.ssh)$/iu.test(part) ||
68
+ /[. ]$/u.test(part) ||
69
+ /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(part)))
70
+ invalid('portable file path');
71
+ const segments = result.split('/');
72
+ if (segments.length > 32 ||
73
+ Buffer.byteLength(segments.at(-1)) > 100 ||
74
+ (Buffer.byteLength(result) > 100 &&
75
+ !segments
76
+ .slice(0, -1)
77
+ .some((_, i) => Buffer.byteLength(segments.slice(0, i + 1).join('/')) <= 155 &&
78
+ Buffer.byteLength(segments.slice(i + 1).join('/')) <= 100)))
79
+ invalid('USTAR path');
80
+ return result;
81
+ }
82
+ export function unique(values, label) {
83
+ if (new Set(values).size !== values.length)
84
+ invalid(`duplicate ${label}`);
85
+ }
86
+ export function distributionUrl(value, redirected = false) {
87
+ const input = text(value, 'distribution URL', 4096);
88
+ let url;
89
+ try {
90
+ url = new URL(input);
91
+ }
92
+ catch {
93
+ invalid('distribution URL');
94
+ }
95
+ if (url.protocol !== 'https:' || url.username || url.password || url.hash || url.port)
96
+ invalid('distribution URL');
97
+ const primary = ['github.com', 'nodejs.org', 'registry.npmjs.org'];
98
+ const redirects = ['release-assets.githubusercontent.com', 'objects.githubusercontent.com'];
99
+ if (!primary.includes(url.hostname) && !(redirected && redirects.includes(url.hostname)))
100
+ invalid('distribution origin');
101
+ if (!redirected) {
102
+ if (url.search || /\/(?:latest|heads)(?:\/|$)/u.test(url.pathname))
103
+ invalid('immutable distribution URL');
104
+ if (url.hostname === 'github.com' &&
105
+ !/^\/[^/]+\/[^/]+\/releases\/download\/[^/]+\/[^/]+$/u.test(url.pathname))
106
+ invalid('release asset URL');
107
+ if (url.hostname === 'nodejs.org' &&
108
+ !/^\/dist\/v[0-9]+\.[0-9]+\.[0-9]+\/[^/]+$/u.test(url.pathname))
109
+ invalid('Node release URL');
110
+ if (url.hostname === 'registry.npmjs.org' &&
111
+ !/\/-\/[^/]+-[0-9]+\.[0-9]+\.[0-9]+\.tgz$/u.test(url.pathname))
112
+ invalid('registry tarball URL');
113
+ }
114
+ return url.href;
115
+ }
116
+ export function deepFreeze(value) {
117
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
118
+ for (const item of Object.values(value))
119
+ deepFreeze(item);
120
+ Object.freeze(value);
121
+ }
122
+ return value;
123
+ }
124
+ //# sourceMappingURL=manifest-values.js.map