@relayflows/sdk 2.0.29 → 2.0.30
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/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +10 -1
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/bundle-extensions.d.ts.map +1 -1
- package/dist/bundle-extensions.js +4 -7
- package/dist/bundle-extensions.js.map +1 -1
- package/dist/bundle.d.ts.map +1 -1
- package/dist/bundle.js +41 -7
- package/dist/bundle.js.map +1 -1
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +18 -6
- package/dist/canonical.js.map +1 -1
- package/dist/flow-extension-compat.d.ts.map +1 -1
- package/dist/flow-extension-compat.js +26 -8
- package/dist/flow-extension-compat.js.map +1 -1
- package/dist/flow-extension-loader.d.ts +7 -0
- package/dist/flow-extension-loader.d.ts.map +1 -1
- package/dist/flow-extension-loader.js +48 -22
- package/dist/flow-extension-loader.js.map +1 -1
- package/dist/fs-descriptor.d.ts +17 -0
- package/dist/fs-descriptor.d.ts.map +1 -0
- package/dist/fs-descriptor.js +96 -0
- package/dist/fs-descriptor.js.map +1 -0
- package/dist/hosted-base-snapshot.d.ts +25 -0
- package/dist/hosted-base-snapshot.d.ts.map +1 -0
- package/dist/hosted-base-snapshot.js +278 -0
- package/dist/hosted-base-snapshot.js.map +1 -0
- package/dist/hosted-extension-declarations.d.ts +10 -0
- package/dist/hosted-extension-declarations.d.ts.map +1 -0
- package/dist/hosted-extension-declarations.js +272 -0
- package/dist/hosted-extension-declarations.js.map +1 -0
- package/dist/hosted-extension-isolation.d.ts +82 -0
- package/dist/hosted-extension-isolation.d.ts.map +1 -0
- package/dist/hosted-extension-isolation.js +322 -0
- package/dist/hosted-extension-isolation.js.map +1 -0
- package/dist/hosted-extension-manifest.d.ts +9 -0
- package/dist/hosted-extension-manifest.d.ts.map +1 -0
- package/dist/hosted-extension-manifest.js +235 -0
- package/dist/hosted-extension-manifest.js.map +1 -0
- package/dist/hosted-extension-protocol.d.ts +16 -0
- package/dist/hosted-extension-protocol.d.ts.map +1 -0
- package/dist/hosted-extension-protocol.js +224 -0
- package/dist/hosted-extension-protocol.js.map +1 -0
- package/dist/hosted-extension-runtime.d.ts +30 -0
- package/dist/hosted-extension-runtime.d.ts.map +1 -0
- package/dist/hosted-extension-runtime.js +167 -0
- package/dist/hosted-extension-runtime.js.map +1 -0
- package/dist/hosted-extension-sandbox-source.d.ts +9 -0
- package/dist/hosted-extension-sandbox-source.d.ts.map +1 -0
- package/dist/hosted-extension-sandbox-source.js +95 -0
- package/dist/hosted-extension-sandbox-source.js.map +1 -0
- package/dist/hosted-extension-sandbox.d.ts +39 -0
- package/dist/hosted-extension-sandbox.d.ts.map +1 -0
- package/dist/hosted-extension-sandbox.js +418 -0
- package/dist/hosted-extension-sandbox.js.map +1 -0
- package/dist/hosted-project.d.ts +3 -0
- package/dist/hosted-project.d.ts.map +1 -0
- package/dist/hosted-project.js +19 -0
- package/dist/hosted-project.js.map +1 -0
- package/dist/hosted-promise-safety.d.ts +12 -0
- package/dist/hosted-promise-safety.d.ts.map +1 -0
- package/dist/hosted-promise-safety.js +63 -0
- package/dist/hosted-promise-safety.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/intrinsic-array.d.ts +3 -0
- package/dist/intrinsic-array.d.ts.map +1 -0
- package/dist/intrinsic-array.js +14 -0
- package/dist/intrinsic-array.js.map +1 -0
- package/dist/json-value.d.ts +6 -1
- package/dist/json-value.d.ts.map +1 -1
- package/dist/json-value.js +155 -36
- package/dist/json-value.js.map +1 -1
- package/dist/llm-worker.d.ts +9 -0
- package/dist/llm-worker.d.ts.map +1 -1
- package/dist/llm-worker.js +15 -1
- package/dist/llm-worker.js.map +1 -1
- package/dist/plugin-store.d.ts +8 -2
- package/dist/plugin-store.d.ts.map +1 -1
- package/dist/plugin-store.js +233 -56
- package/dist/plugin-store.js.map +1 -1
- package/dist/semver-range.d.ts.map +1 -1
- package/dist/semver-range.js +27 -15
- package/dist/semver-range.js.map +1 -1
- package/package.json +2 -2
- package/src/authored-flow-executor.ts +10 -1
- package/src/bundle-extensions.ts +6 -5
- package/src/bundle.ts +52 -7
- package/src/canonical.ts +20 -6
- package/src/flow-extension-compat.ts +32 -7
- package/src/flow-extension-loader.ts +56 -21
- package/src/fs-descriptor.ts +127 -0
- package/src/hosted-base-snapshot.ts +351 -0
- package/src/hosted-extension-declarations.ts +387 -0
- package/src/hosted-extension-isolation.ts +457 -0
- package/src/hosted-extension-manifest.ts +249 -0
- package/src/hosted-extension-protocol.ts +262 -0
- package/src/hosted-extension-runtime.ts +266 -0
- package/src/hosted-extension-sandbox-source.ts +94 -0
- package/src/hosted-extension-sandbox.ts +489 -0
- package/src/hosted-project.ts +18 -0
- package/src/hosted-promise-safety.ts +68 -0
- package/src/index.ts +18 -0
- package/src/intrinsic-array.ts +14 -0
- package/src/json-value.ts +184 -32
- package/src/llm-worker.ts +15 -1
- package/src/plugin-store.ts +323 -56
- package/src/semver-range.ts +39 -14
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { chmod, mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
|
+
import { canonicalize } from './canonical.js';
|
|
6
|
+
import { sha256 } from './bundle.js';
|
|
7
|
+
import {
|
|
8
|
+
closeDescriptor,
|
|
9
|
+
closeDirectory,
|
|
10
|
+
descriptorIsDirectory,
|
|
11
|
+
descriptorIsFile,
|
|
12
|
+
directoryEntryIsDirectory,
|
|
13
|
+
directoryEntryIsFile,
|
|
14
|
+
openDirectory,
|
|
15
|
+
openDescriptor,
|
|
16
|
+
readDescriptor,
|
|
17
|
+
readDirectory,
|
|
18
|
+
readDirectoryEntry,
|
|
19
|
+
statDescriptor,
|
|
20
|
+
} from './fs-descriptor.js';
|
|
21
|
+
import { findHostedProject } from './hosted-project.js';
|
|
22
|
+
import { appendIntrinsicArray } from './intrinsic-array.js';
|
|
23
|
+
import {
|
|
24
|
+
assertHostedPromiseSafety,
|
|
25
|
+
frozenHostedPromiseValue,
|
|
26
|
+
hostedPromiseValue,
|
|
27
|
+
} from './hosted-promise-safety.js';
|
|
28
|
+
import { PluginError } from './plugin-manifest.js';
|
|
29
|
+
|
|
30
|
+
const EXCLUDED_DIRECTORIES = new Set(['.flows', '.git', 'node_modules']);
|
|
31
|
+
const CHMOD = chmod;
|
|
32
|
+
const MKDIR = mkdir;
|
|
33
|
+
const MKDTEMP = mkdtemp;
|
|
34
|
+
const REALPATH = realpath;
|
|
35
|
+
const RM = rm;
|
|
36
|
+
const WRITE_FILE = writeFile;
|
|
37
|
+
const TMPDIR = tmpdir;
|
|
38
|
+
const PATH_DIRNAME = dirname;
|
|
39
|
+
const PATH_IS_ABSOLUTE = isAbsolute;
|
|
40
|
+
const PATH_JOIN = join;
|
|
41
|
+
const PATH_RELATIVE = relative;
|
|
42
|
+
const PATH_RESOLVE = resolve;
|
|
43
|
+
const PATH_SEPARATOR = sep;
|
|
44
|
+
const PROCESS_PLATFORM = process.platform;
|
|
45
|
+
const ARRAY_SORT = Function.prototype.call.bind(Array.prototype.sort) as <T>(
|
|
46
|
+
array: T[],
|
|
47
|
+
compare?: (left: T, right: T) => number,
|
|
48
|
+
) => T[];
|
|
49
|
+
const OBJECT_CREATE = Object.create;
|
|
50
|
+
const OBJECT_FREEZE = Object.freeze;
|
|
51
|
+
const OBJECT_GET_OWN_PROPERTY_DESCRIPTOR = Object.getOwnPropertyDescriptor;
|
|
52
|
+
const BIG_INT = BigInt;
|
|
53
|
+
const BUFFER_ALLOC_UNSAFE = Buffer.allocUnsafe;
|
|
54
|
+
const NUMBER = Number;
|
|
55
|
+
const SET_HAS = Function.prototype.call.bind(Set.prototype.has) as <T>(set: Set<T>, value: T) => boolean;
|
|
56
|
+
const STRING_LOCALE_COMPARE = Function.prototype.call.bind(String.prototype.localeCompare) as (
|
|
57
|
+
value: string,
|
|
58
|
+
other: string,
|
|
59
|
+
) => number;
|
|
60
|
+
const STRING_STARTS_WITH = Function.prototype.call.bind(String.prototype.startsWith) as (
|
|
61
|
+
value: string,
|
|
62
|
+
search: string,
|
|
63
|
+
) => boolean;
|
|
64
|
+
const MAX_ENTRIES = 10_000;
|
|
65
|
+
const MAX_BYTES = 64 * 1024 * 1024;
|
|
66
|
+
const MAX_DEPTH = 64;
|
|
67
|
+
const READ_FLAGS = constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
|
|
68
|
+
|
|
69
|
+
interface SourceFile {
|
|
70
|
+
readonly path: string;
|
|
71
|
+
readonly bytes: Uint8Array;
|
|
72
|
+
readonly sha256: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface SourceBudget {
|
|
76
|
+
entries: number;
|
|
77
|
+
bytes: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @internal Deterministic race seams for this security boundary's tests. */
|
|
81
|
+
export interface HostedBaseSnapshotTestHooks {
|
|
82
|
+
readonly beforeOpen?: (path: string) => Promise<void>;
|
|
83
|
+
readonly afterStat?: (path: string) => Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const EMPTY_HOSTED_BASE_HOOKS = OBJECT_FREEZE(
|
|
87
|
+
OBJECT_CREATE(null),
|
|
88
|
+
) as HostedBaseSnapshotTestHooks;
|
|
89
|
+
|
|
90
|
+
export interface HostedBaseSourceRoot {
|
|
91
|
+
readonly root: string;
|
|
92
|
+
readonly prefix: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface HostedBaseSnapshot {
|
|
96
|
+
readonly liveSources: readonly HostedBaseSourceRoot[];
|
|
97
|
+
readonly liveDigest: string;
|
|
98
|
+
readonly snapshotRoot: string;
|
|
99
|
+
readonly snapshotFlowPath: string;
|
|
100
|
+
readonly snapshotFlowSha256: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Materialize project source without importing tenant code. Project
|
|
105
|
+
* node_modules is never linked or read. Every admitted source byte is
|
|
106
|
+
* represented in liveDigest and copied to the private identity directory.
|
|
107
|
+
*/
|
|
108
|
+
export async function createHostedBaseSnapshot(
|
|
109
|
+
flowPath: string,
|
|
110
|
+
projectRootOverride?: string,
|
|
111
|
+
): Promise<HostedBaseSnapshot> {
|
|
112
|
+
assertHostedPromiseSafety('plugin_source_invalid');
|
|
113
|
+
const origin = await REALPATH(PATH_RESOLVE(flowPath));
|
|
114
|
+
const discovered = projectRootOverride ?? findHostedProject(PATH_DIRNAME(origin)) ?? PATH_DIRNAME(origin);
|
|
115
|
+
const projectRoot = await REALPATH(discovered);
|
|
116
|
+
const flowRelative = PATH_RELATIVE(projectRoot, origin);
|
|
117
|
+
if (
|
|
118
|
+
flowRelative === '' ||
|
|
119
|
+
PATH_IS_ABSOLUTE(flowRelative) ||
|
|
120
|
+
flowRelative === '..' ||
|
|
121
|
+
STRING_STARTS_WITH(flowRelative, `..${PATH_SEPARATOR}`)
|
|
122
|
+
) {
|
|
123
|
+
throw invalid('Hosted base flow must be a file inside its project root.');
|
|
124
|
+
}
|
|
125
|
+
const liveSources = frozenHostedPromiseValue([OBJECT_FREEZE({ root: projectRoot, prefix: '' })]);
|
|
126
|
+
const files = await readAuthorityFiles(liveSources);
|
|
127
|
+
const liveDigest = sourceDigest(files);
|
|
128
|
+
const liveFlow = sourceFileAt(files, flowRelative);
|
|
129
|
+
if (liveFlow === undefined) throw invalid('Hosted base flow is missing from its admitted source.');
|
|
130
|
+
const snapshotRoot = await MKDTEMP(PATH_JOIN(TMPDIR(), 'flows-hosted-base-'));
|
|
131
|
+
await CHMOD(snapshotRoot, 0o700);
|
|
132
|
+
try {
|
|
133
|
+
for (let index = 0; index < files.length; index += 1) {
|
|
134
|
+
const file = files[index]!;
|
|
135
|
+
const target = PATH_JOIN(snapshotRoot, file.path);
|
|
136
|
+
await MKDIR(PATH_DIRNAME(target), { recursive: true, mode: 0o700 });
|
|
137
|
+
await WRITE_FILE(target, file.bytes, { flag: 'wx', mode: 0o400 });
|
|
138
|
+
}
|
|
139
|
+
const snapshotFiles = await readSnapshotTree(snapshotRoot);
|
|
140
|
+
const snapshotDigest = sourceDigest(snapshotFiles);
|
|
141
|
+
if (snapshotDigest !== liveDigest) {
|
|
142
|
+
throw invalid('Hosted base private snapshot does not match its buffered source.');
|
|
143
|
+
}
|
|
144
|
+
const snapshotFlow = sourceFileAt(snapshotFiles, flowRelative);
|
|
145
|
+
if (snapshotFlow === undefined || snapshotFlow.sha256 !== liveFlow.sha256) {
|
|
146
|
+
throw invalid('Hosted base private snapshot does not contain its buffered flow source.');
|
|
147
|
+
}
|
|
148
|
+
return frozenHostedPromiseValue({
|
|
149
|
+
liveSources,
|
|
150
|
+
liveDigest,
|
|
151
|
+
snapshotRoot,
|
|
152
|
+
snapshotFlowPath: PATH_JOIN(snapshotRoot, flowRelative),
|
|
153
|
+
snapshotFlowSha256: snapshotFlow.sha256,
|
|
154
|
+
});
|
|
155
|
+
} catch (error) {
|
|
156
|
+
await RM(snapshotRoot, { recursive: true, force: true });
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function hostedBaseSourceDigest(
|
|
162
|
+
sources: readonly HostedBaseSourceRoot[],
|
|
163
|
+
hooks: HostedBaseSnapshotTestHooks = EMPTY_HOSTED_BASE_HOOKS,
|
|
164
|
+
): Promise<string> {
|
|
165
|
+
assertHostedPromiseSafety('plugin_source_invalid');
|
|
166
|
+
return sourceDigest(await readAuthorityFiles(sources, hooks));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function removeHostedBaseSnapshot(snapshot: HostedBaseSnapshot): Promise<void> {
|
|
170
|
+
await RM(snapshot.snapshotRoot, { recursive: true, force: true });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function readAuthorityFiles(
|
|
174
|
+
sources: readonly HostedBaseSourceRoot[],
|
|
175
|
+
hooks: HostedBaseSnapshotTestHooks = EMPTY_HOSTED_BASE_HOOKS,
|
|
176
|
+
): Promise<readonly SourceFile[]> {
|
|
177
|
+
const budget: SourceBudget = { entries: 0, bytes: 0 };
|
|
178
|
+
const files: SourceFile[] = [];
|
|
179
|
+
for (let sourceIndex = 0; sourceIndex < sources.length; sourceIndex += 1) {
|
|
180
|
+
const source = sources[sourceIndex]!;
|
|
181
|
+
const sourceFiles = await readTree(source.root, source.prefix, budget, hooks);
|
|
182
|
+
for (let fileIndex = 0; fileIndex < sourceFiles.length; fileIndex += 1) {
|
|
183
|
+
appendIntrinsicArray(files, sourceFiles[fileIndex]!);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
ARRAY_SORT(files, (left, right) => STRING_LOCALE_COMPARE(left.path, right.path));
|
|
187
|
+
return frozenHostedPromiseValue(files);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function readSnapshotTree(root: string): Promise<readonly SourceFile[]> {
|
|
191
|
+
const files: SourceFile[] = [];
|
|
192
|
+
const budget: SourceBudget = { entries: 0, bytes: 0 };
|
|
193
|
+
async function visit(directory: string, prefix: string): Promise<void> {
|
|
194
|
+
const entries = await readDirectory(directory);
|
|
195
|
+
ARRAY_SORT(entries, (left, right) => STRING_LOCALE_COMPARE(left.name, right.name));
|
|
196
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
197
|
+
const entry = entries[index]!;
|
|
198
|
+
budget.entries += 1;
|
|
199
|
+
if (budget.entries > MAX_ENTRIES) throw tooLarge();
|
|
200
|
+
const path = prefix === '' ? entry.name : PATH_JOIN(prefix, entry.name);
|
|
201
|
+
const absolute = PATH_JOIN(directory, entry.name);
|
|
202
|
+
if (directoryEntryIsDirectory(entry)) await visit(absolute, path);
|
|
203
|
+
else if (directoryEntryIsFile(entry)) {
|
|
204
|
+
const descriptor = await openDescriptor(absolute, READ_FLAGS);
|
|
205
|
+
try {
|
|
206
|
+
const before = await statDescriptor(descriptor, { bigint: true });
|
|
207
|
+
if (!descriptorIsFile(before) || before.size < 0n || before.size > BIG_INT(MAX_BYTES - budget.bytes))
|
|
208
|
+
throw tooLarge();
|
|
209
|
+
const expectedBytes = NUMBER(before.size);
|
|
210
|
+
const bytes = await readBounded(descriptor, expectedBytes, path);
|
|
211
|
+
const after = await statDescriptor(descriptor, { bigint: true });
|
|
212
|
+
if (after.size !== before.size || after.mtimeNs !== before.mtimeNs || after.ctimeNs !== before.ctimeNs) {
|
|
213
|
+
throw invalid(`Hosted base snapshot changed while reading "${path}".`);
|
|
214
|
+
}
|
|
215
|
+
budget.bytes += expectedBytes;
|
|
216
|
+
appendIntrinsicArray(files, OBJECT_FREEZE({ path, bytes, sha256: sha256(bytes) }));
|
|
217
|
+
} finally {
|
|
218
|
+
await closeDescriptor(descriptor);
|
|
219
|
+
}
|
|
220
|
+
} else throw invalid(`Hosted base snapshot contains unsupported entry "${path}".`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
await visit(root, '');
|
|
224
|
+
ARRAY_SORT(files, (left, right) => STRING_LOCALE_COMPARE(left.path, right.path));
|
|
225
|
+
return frozenHostedPromiseValue(files);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function readTree(
|
|
229
|
+
root: string,
|
|
230
|
+
prefix: string,
|
|
231
|
+
budget: SourceBudget,
|
|
232
|
+
hooks: HostedBaseSnapshotTestHooks,
|
|
233
|
+
): Promise<readonly SourceFile[]> {
|
|
234
|
+
if (PROCESS_PLATFORM !== 'linux') {
|
|
235
|
+
throw invalid('Hosted base source snapshotting requires Linux.');
|
|
236
|
+
}
|
|
237
|
+
const files: SourceFile[] = [];
|
|
238
|
+
async function visit(directory: number, relativeDirectory: string, depth: number): Promise<void> {
|
|
239
|
+
if (depth > MAX_DEPTH) throw tooLarge();
|
|
240
|
+
const entries = await openDirectory(`/proc/self/fd/${directory}`);
|
|
241
|
+
try {
|
|
242
|
+
for (;;) {
|
|
243
|
+
const entry = await readDirectoryEntry(entries);
|
|
244
|
+
if (entry === null) break;
|
|
245
|
+
if (SET_HAS(EXCLUDED_DIRECTORIES, entry.name)) continue;
|
|
246
|
+
budget.entries += 1;
|
|
247
|
+
if (budget.entries > MAX_ENTRIES) throw tooLarge();
|
|
248
|
+
const relativePath = relativeDirectory === '' ? entry.name : PATH_JOIN(relativeDirectory, entry.name);
|
|
249
|
+
const absolutePath = PATH_JOIN(root, relativePath);
|
|
250
|
+
await ownHook(hooks, 'beforeOpen')?.(absolutePath);
|
|
251
|
+
const descriptor = await openDescriptor(`/proc/self/fd/${directory}/${entry.name}`, READ_FLAGS);
|
|
252
|
+
try {
|
|
253
|
+
const before = await statDescriptor(descriptor, { bigint: true });
|
|
254
|
+
if (descriptorIsDirectory(before)) {
|
|
255
|
+
await visit(descriptor, relativePath, depth + 1);
|
|
256
|
+
} else if (descriptorIsFile(before)) {
|
|
257
|
+
if (before.size < 0n || before.size > BIG_INT(MAX_BYTES - budget.bytes)) throw tooLarge();
|
|
258
|
+
const expectedBytes = NUMBER(before.size);
|
|
259
|
+
await ownHook(hooks, 'afterStat')?.(absolutePath);
|
|
260
|
+
const bytes = await readBounded(descriptor, expectedBytes, relativePath);
|
|
261
|
+
const after = await statDescriptor(descriptor, { bigint: true });
|
|
262
|
+
if (after.size !== before.size || after.mtimeNs !== before.mtimeNs || after.ctimeNs !== before.ctimeNs) {
|
|
263
|
+
throw invalid(`Hosted base source changed while reading "${relativePath}".`);
|
|
264
|
+
}
|
|
265
|
+
budget.bytes += expectedBytes;
|
|
266
|
+
appendIntrinsicArray(
|
|
267
|
+
files,
|
|
268
|
+
OBJECT_FREEZE({
|
|
269
|
+
path: prefix === '' ? relativePath : PATH_JOIN(prefix, relativePath),
|
|
270
|
+
bytes,
|
|
271
|
+
sha256: sha256(bytes),
|
|
272
|
+
}),
|
|
273
|
+
);
|
|
274
|
+
} else {
|
|
275
|
+
throw invalid(`Hosted base authority contains unsupported entry "${relativePath}".`);
|
|
276
|
+
}
|
|
277
|
+
} finally {
|
|
278
|
+
await closeDescriptor(descriptor);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} finally {
|
|
282
|
+
await closeDirectory(entries);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const rootDescriptor = await openDescriptor(root, READ_FLAGS);
|
|
287
|
+
try {
|
|
288
|
+
if (!descriptorIsDirectory(await statDescriptor(rootDescriptor, { bigint: true }))) {
|
|
289
|
+
throw invalid('Hosted base source root is not a directory.');
|
|
290
|
+
}
|
|
291
|
+
await visit(rootDescriptor, '', 0);
|
|
292
|
+
} finally {
|
|
293
|
+
await closeDescriptor(rootDescriptor);
|
|
294
|
+
}
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (error instanceof PluginError) throw error;
|
|
297
|
+
throw invalid('Hosted base source or trusted dependency is unreadable.');
|
|
298
|
+
}
|
|
299
|
+
return frozenHostedPromiseValue(files);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function ownHook(
|
|
303
|
+
hooks: HostedBaseSnapshotTestHooks,
|
|
304
|
+
name: keyof HostedBaseSnapshotTestHooks,
|
|
305
|
+
): ((path: string) => Promise<void>) | undefined {
|
|
306
|
+
const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR(hooks, name);
|
|
307
|
+
return descriptor !== undefined && 'value' in descriptor && typeof descriptor.value === 'function'
|
|
308
|
+
? descriptor.value as (path: string) => Promise<void>
|
|
309
|
+
: undefined;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function readBounded(descriptor: number, expectedBytes: number, relativePath: string): Promise<Buffer> {
|
|
313
|
+
const bytes = BUFFER_ALLOC_UNSAFE(expectedBytes);
|
|
314
|
+
let offset = 0;
|
|
315
|
+
while (offset < expectedBytes) {
|
|
316
|
+
const bytesRead = await readDescriptor(descriptor, bytes, offset, expectedBytes - offset, offset);
|
|
317
|
+
if (bytesRead === 0) {
|
|
318
|
+
throw invalid(`Hosted base source changed while reading "${relativePath}".`);
|
|
319
|
+
}
|
|
320
|
+
offset += bytesRead;
|
|
321
|
+
}
|
|
322
|
+
const extra = BUFFER_ALLOC_UNSAFE(1);
|
|
323
|
+
if ((await readDescriptor(descriptor, extra, 0, 1, expectedBytes)) !== 0) {
|
|
324
|
+
throw invalid(`Hosted base source changed while reading "${relativePath}".`);
|
|
325
|
+
}
|
|
326
|
+
return hostedPromiseValue(bytes);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function tooLarge(): PluginError {
|
|
330
|
+
return invalid('Hosted base source exceeds the snapshot entry or byte limit.');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function sourceDigest(files: readonly SourceFile[]): string {
|
|
334
|
+
const records: Array<{ path: string; sha256: string }> = [];
|
|
335
|
+
for (let index = 0; index < files.length; index += 1) {
|
|
336
|
+
const file = files[index]!;
|
|
337
|
+
appendIntrinsicArray(records, { path: file.path, sha256: file.sha256 });
|
|
338
|
+
}
|
|
339
|
+
return sha256(canonicalize(records));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function sourceFileAt(files: readonly SourceFile[], path: string): SourceFile | undefined {
|
|
343
|
+
for (let index = 0; index < files.length; index += 1) {
|
|
344
|
+
if (files[index]!.path === path) return files[index];
|
|
345
|
+
}
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function invalid(message: string): PluginError {
|
|
350
|
+
return new PluginError('plugin_source_invalid', message);
|
|
351
|
+
}
|