@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,387 @@
|
|
|
1
|
+
import {
|
|
2
|
+
constants,
|
|
3
|
+
closeSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
fstatSync,
|
|
6
|
+
openSync,
|
|
7
|
+
readSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { safePath } from "./bundle.js";
|
|
11
|
+
import { canonicalize } from "./canonical.js";
|
|
12
|
+
import { descriptorIsFile } from "./fs-descriptor.js";
|
|
13
|
+
import { snapshotJsonValue } from "./json-value.js";
|
|
14
|
+
import { PluginError } from "./plugin-manifest.js";
|
|
15
|
+
import { appendIntrinsicArray } from "./intrinsic-array.js";
|
|
16
|
+
import {
|
|
17
|
+
PLUGIN_LOCK_FILE,
|
|
18
|
+
PLUGIN_LOCK_VERSION,
|
|
19
|
+
type PluginLockEntry,
|
|
20
|
+
} from "./plugin-lock.js";
|
|
21
|
+
import { canonicalPluginRef, type PluginSourceRef } from "./plugin-source.js";
|
|
22
|
+
|
|
23
|
+
const CLOSE_SYNC = closeSync;
|
|
24
|
+
const EXISTS_SYNC = existsSync;
|
|
25
|
+
const FSTAT_SYNC = fstatSync;
|
|
26
|
+
const OPEN_SYNC = openSync;
|
|
27
|
+
const READ_SYNC = readSync;
|
|
28
|
+
const PATH_JOIN = join;
|
|
29
|
+
const ERROR = Error;
|
|
30
|
+
const PLUGIN_ERROR_IS_INSTANCE = Function.prototype.call.bind(
|
|
31
|
+
Function.prototype[Symbol.hasInstance],
|
|
32
|
+
PluginError,
|
|
33
|
+
) as (value: unknown) => boolean;
|
|
34
|
+
const MAX_DECLARATION_BYTES = 1024 * 1024;
|
|
35
|
+
const BIG_INT = BigInt;
|
|
36
|
+
const BUFFER_ALLOC_UNSAFE = Buffer.allocUnsafe;
|
|
37
|
+
const BUFFER_TO_STRING = Function.prototype.call.bind(
|
|
38
|
+
Buffer.prototype.toString,
|
|
39
|
+
) as (value: Buffer, encoding: BufferEncoding) => string;
|
|
40
|
+
const NUMBER = Number;
|
|
41
|
+
const DECLARATION_READ_FLAGS =
|
|
42
|
+
constants.O_RDONLY |
|
|
43
|
+
(constants.O_NOFOLLOW ?? 0) |
|
|
44
|
+
(constants.O_NONBLOCK ?? 0);
|
|
45
|
+
const JSON_PARSE = JSON.parse;
|
|
46
|
+
const ARRAY_IS_ARRAY = Array.isArray;
|
|
47
|
+
const DATE_PARSE = Date.parse;
|
|
48
|
+
const NUMBER_IS_NAN = Number.isNaN;
|
|
49
|
+
const OBJECT_FREEZE = Object.freeze;
|
|
50
|
+
const OBJECT_HAS_OWN = Object.hasOwn;
|
|
51
|
+
const OBJECT_KEYS = Object.keys;
|
|
52
|
+
const REGEXP_TEST = Function.prototype.call.bind(RegExp.prototype.test) as (
|
|
53
|
+
regexp: RegExp,
|
|
54
|
+
value: string,
|
|
55
|
+
) => boolean;
|
|
56
|
+
const SET = Set;
|
|
57
|
+
const SET_ADD = Function.prototype.call.bind(Set.prototype.add) as <T>(
|
|
58
|
+
set: Set<T>,
|
|
59
|
+
value: T,
|
|
60
|
+
) => Set<T>;
|
|
61
|
+
const SET_HAS = Function.prototype.call.bind(Set.prototype.has) as <T>(
|
|
62
|
+
set: Set<T>,
|
|
63
|
+
value: T,
|
|
64
|
+
) => boolean;
|
|
65
|
+
const STRING_STARTS_WITH = Function.prototype.call.bind(
|
|
66
|
+
String.prototype.startsWith,
|
|
67
|
+
) as (value: string, search: string) => boolean;
|
|
68
|
+
const LOCK_HEX64 = /^[0-9a-f]{64}$/;
|
|
69
|
+
const LOCK_SHA = /^[0-9a-f]{40}$/;
|
|
70
|
+
const LOCK_OWNER = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})$/;
|
|
71
|
+
const LOCK_REPO = /^[A-Za-z0-9_.-]{1,100}$/;
|
|
72
|
+
const HTTPS_GITHUB = /^https:\/\/github\.com\//;
|
|
73
|
+
|
|
74
|
+
export interface HostedDeclaredExtension {
|
|
75
|
+
readonly ref: string;
|
|
76
|
+
readonly entry: PluginLockEntry;
|
|
77
|
+
readonly source: PluginSourceRef;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function declarationSignature(
|
|
81
|
+
declared: readonly HostedDeclaredExtension[],
|
|
82
|
+
): string {
|
|
83
|
+
const records: Array<{
|
|
84
|
+
ref: string;
|
|
85
|
+
name: string;
|
|
86
|
+
version: string;
|
|
87
|
+
digest: string;
|
|
88
|
+
manifestSha256: string;
|
|
89
|
+
}> = [];
|
|
90
|
+
for (let index = 0; index < declared.length; index += 1) {
|
|
91
|
+
const { ref, entry } = declared[index]!;
|
|
92
|
+
appendIntrinsicArray(records, {
|
|
93
|
+
ref,
|
|
94
|
+
name: entry.name,
|
|
95
|
+
version: entry.version,
|
|
96
|
+
digest: entry.digest,
|
|
97
|
+
manifestSha256: entry.manifestSha256,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return canonicalize(records);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function hostedDeclaredExtensions(
|
|
104
|
+
root: string,
|
|
105
|
+
): readonly HostedDeclaredExtension[] {
|
|
106
|
+
if (
|
|
107
|
+
EXISTS_SYNC(PATH_JOIN(root, "flows.json.tmp")) ||
|
|
108
|
+
EXISTS_SYNC(PATH_JOIN(root, `${PLUGIN_LOCK_FILE}.tmp`))
|
|
109
|
+
) {
|
|
110
|
+
throw new PluginError(
|
|
111
|
+
"plugin_lock_invalid",
|
|
112
|
+
"Hosted extension declarations have a pending transaction.",
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
let config: unknown;
|
|
116
|
+
try {
|
|
117
|
+
config = JSON_PARSE(
|
|
118
|
+
BUFFER_TO_STRING(
|
|
119
|
+
readBoundedDeclaration(PATH_JOIN(root, "flows.json")),
|
|
120
|
+
"utf8",
|
|
121
|
+
),
|
|
122
|
+
);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (PLUGIN_ERROR_IS_INSTANCE(error)) throw error;
|
|
125
|
+
throw new PluginError(
|
|
126
|
+
"plugin_manifest_invalid",
|
|
127
|
+
"Invalid or oversized flows.json.",
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
if (typeof config !== "object" || config === null || ARRAY_IS_ARRAY(config)) {
|
|
131
|
+
throw new PluginError(
|
|
132
|
+
"plugin_manifest_invalid",
|
|
133
|
+
"flows.json plugins must be strings.",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const plugins = OBJECT_HAS_OWN(config, "plugins")
|
|
137
|
+
? (config as { plugins?: unknown }).plugins
|
|
138
|
+
: undefined;
|
|
139
|
+
if (plugins !== undefined && !ARRAY_IS_ARRAY(plugins)) {
|
|
140
|
+
throw new PluginError(
|
|
141
|
+
"plugin_manifest_invalid",
|
|
142
|
+
"flows.json plugins must be strings.",
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
const declared: string[] = [];
|
|
146
|
+
if (plugins !== undefined) {
|
|
147
|
+
for (let index = 0; index < plugins.length; index += 1) {
|
|
148
|
+
const ref = plugins[index];
|
|
149
|
+
if (typeof ref !== "string") {
|
|
150
|
+
throw new PluginError(
|
|
151
|
+
"plugin_manifest_invalid",
|
|
152
|
+
"flows.json plugins must be strings.",
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (isHostedGithubPluginRef(ref)) appendIntrinsicArray(declared, ref);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
let lock: {
|
|
159
|
+
readonly version: 2;
|
|
160
|
+
readonly plugins: readonly PluginLockEntry[];
|
|
161
|
+
} = OBJECT_FREEZE({
|
|
162
|
+
version: PLUGIN_LOCK_VERSION,
|
|
163
|
+
plugins: OBJECT_FREEZE([]),
|
|
164
|
+
});
|
|
165
|
+
const lockPath = PATH_JOIN(root, PLUGIN_LOCK_FILE);
|
|
166
|
+
if (EXISTS_SYNC(lockPath)) {
|
|
167
|
+
let value: unknown;
|
|
168
|
+
try {
|
|
169
|
+
value = JSON_PARSE(
|
|
170
|
+
BUFFER_TO_STRING(readBoundedDeclaration(lockPath), "utf8"),
|
|
171
|
+
);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (PLUGIN_ERROR_IS_INSTANCE(error)) throw error;
|
|
174
|
+
throw new PluginError(
|
|
175
|
+
"plugin_lock_invalid",
|
|
176
|
+
`${PLUGIN_LOCK_FILE}: not valid or exceeds the hosted size limit.`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
lock = parseHostedPluginLock(value);
|
|
180
|
+
}
|
|
181
|
+
if (declared.length !== lock.plugins.length) {
|
|
182
|
+
throw new PluginError(
|
|
183
|
+
"plugin_lock_invalid",
|
|
184
|
+
"Hosted flows.json and flows.lock.json declarations differ.",
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
const result: HostedDeclaredExtension[] = [];
|
|
188
|
+
for (let index = 0; index < lock.plugins.length; index += 1) {
|
|
189
|
+
const entry = lock.plugins[index]!;
|
|
190
|
+
const source = OBJECT_FREEZE({ ...entry.source, ref: entry.source.sha });
|
|
191
|
+
const ref = canonicalPluginRef(source);
|
|
192
|
+
if (declared[index] !== ref) {
|
|
193
|
+
throw new PluginError(
|
|
194
|
+
"plugin_lock_invalid",
|
|
195
|
+
"Hosted flows.lock.json order differs from flows.json.plugins.",
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
appendIntrinsicArray(result, OBJECT_FREEZE({ ref, entry, source }));
|
|
199
|
+
}
|
|
200
|
+
return OBJECT_FREEZE(result);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function parseHostedPluginLock(input: unknown): {
|
|
204
|
+
readonly version: 2;
|
|
205
|
+
readonly plugins: readonly PluginLockEntry[];
|
|
206
|
+
} {
|
|
207
|
+
let value: unknown;
|
|
208
|
+
try {
|
|
209
|
+
value = snapshotJsonValue(input, "hosted plugin lock");
|
|
210
|
+
} catch {
|
|
211
|
+
return invalidHostedLock(
|
|
212
|
+
`expected { version: ${PLUGIN_LOCK_VERSION}, plugins: [] }.`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (
|
|
216
|
+
!hostedRecord(value) ||
|
|
217
|
+
value.version !== PLUGIN_LOCK_VERSION ||
|
|
218
|
+
!ARRAY_IS_ARRAY(value.plugins) ||
|
|
219
|
+
!hostedHasOnlyKeys(value, ["version", "plugins"])
|
|
220
|
+
) {
|
|
221
|
+
return invalidHostedLock(
|
|
222
|
+
`expected { version: ${PLUGIN_LOCK_VERSION}, plugins: [] }.`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
const names = new SET<string>();
|
|
226
|
+
const plugins: PluginLockEntry[] = [];
|
|
227
|
+
for (let index = 0; index < value.plugins.length; index += 1) {
|
|
228
|
+
const entry = value.plugins[index];
|
|
229
|
+
if (
|
|
230
|
+
!hostedRecord(entry) ||
|
|
231
|
+
!hostedHasOnlyKeys(entry, [
|
|
232
|
+
"digest",
|
|
233
|
+
"kind",
|
|
234
|
+
"manifestSha256",
|
|
235
|
+
"name",
|
|
236
|
+
"order",
|
|
237
|
+
"resolvedAt",
|
|
238
|
+
"source",
|
|
239
|
+
"version",
|
|
240
|
+
]) ||
|
|
241
|
+
entry.kind !== "flow-extension" ||
|
|
242
|
+
typeof entry.name !== "string" ||
|
|
243
|
+
typeof entry.version !== "string" ||
|
|
244
|
+
typeof entry.digest !== "string" ||
|
|
245
|
+
!REGEXP_TEST(LOCK_HEX64, entry.digest) ||
|
|
246
|
+
typeof entry.manifestSha256 !== "string" ||
|
|
247
|
+
!REGEXP_TEST(LOCK_HEX64, entry.manifestSha256) ||
|
|
248
|
+
entry.order !== index + 1 ||
|
|
249
|
+
typeof entry.resolvedAt !== "string" ||
|
|
250
|
+
NUMBER_IS_NAN(DATE_PARSE(entry.resolvedAt)) ||
|
|
251
|
+
!hostedRecord(entry.source) ||
|
|
252
|
+
!hostedHasOnlyKeys(entry.source, [
|
|
253
|
+
"host",
|
|
254
|
+
"owner",
|
|
255
|
+
"repo",
|
|
256
|
+
"sha",
|
|
257
|
+
"path",
|
|
258
|
+
]) ||
|
|
259
|
+
entry.source.host !== "github" ||
|
|
260
|
+
typeof entry.source.owner !== "string" ||
|
|
261
|
+
!REGEXP_TEST(LOCK_OWNER, entry.source.owner) ||
|
|
262
|
+
typeof entry.source.repo !== "string" ||
|
|
263
|
+
!REGEXP_TEST(LOCK_REPO, entry.source.repo) ||
|
|
264
|
+
entry.source.repo === "." ||
|
|
265
|
+
entry.source.repo === ".." ||
|
|
266
|
+
typeof entry.source.sha !== "string" ||
|
|
267
|
+
!REGEXP_TEST(LOCK_SHA, entry.source.sha) ||
|
|
268
|
+
typeof entry.source.path !== "string" ||
|
|
269
|
+
(entry.source.path !== "" && !safePath(entry.source.path))
|
|
270
|
+
) {
|
|
271
|
+
return invalidHostedLock(`plugins[${index}] is malformed.`);
|
|
272
|
+
}
|
|
273
|
+
if (SET_HAS(names, entry.name))
|
|
274
|
+
return invalidHostedLock(`plugin ${entry.name} is listed twice.`);
|
|
275
|
+
SET_ADD(names, entry.name);
|
|
276
|
+
appendIntrinsicArray(plugins, OBJECT_FREEZE({
|
|
277
|
+
name: entry.name,
|
|
278
|
+
kind: "flow-extension",
|
|
279
|
+
version: entry.version,
|
|
280
|
+
source: OBJECT_FREEZE({
|
|
281
|
+
host: "github",
|
|
282
|
+
owner: entry.source.owner,
|
|
283
|
+
repo: entry.source.repo,
|
|
284
|
+
sha: entry.source.sha,
|
|
285
|
+
path: entry.source.path,
|
|
286
|
+
}),
|
|
287
|
+
digest: entry.digest,
|
|
288
|
+
manifestSha256: entry.manifestSha256,
|
|
289
|
+
order: entry.order,
|
|
290
|
+
resolvedAt: entry.resolvedAt,
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
return OBJECT_FREEZE({
|
|
294
|
+
version: PLUGIN_LOCK_VERSION,
|
|
295
|
+
plugins: OBJECT_FREEZE(plugins),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function isHostedGithubPluginRef(value: string): boolean {
|
|
300
|
+
return (
|
|
301
|
+
STRING_STARTS_WITH(value, "github:") || REGEXP_TEST(HTTPS_GITHUB, value)
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function hostedRecord(value: unknown): value is Record<string, unknown> {
|
|
306
|
+
return typeof value === "object" && value !== null && !ARRAY_IS_ARRAY(value);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function hostedHasOnlyKeys(
|
|
310
|
+
value: Record<string, unknown>,
|
|
311
|
+
allowed: readonly string[],
|
|
312
|
+
): boolean {
|
|
313
|
+
const keys = OBJECT_KEYS(value);
|
|
314
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
315
|
+
let found = false;
|
|
316
|
+
for (
|
|
317
|
+
let allowedIndex = 0;
|
|
318
|
+
allowedIndex < allowed.length;
|
|
319
|
+
allowedIndex += 1
|
|
320
|
+
) {
|
|
321
|
+
if (keys[index] === allowed[allowedIndex]) {
|
|
322
|
+
found = true;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (!found) return false;
|
|
327
|
+
}
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function invalidHostedLock(message: string): never {
|
|
332
|
+
throw new PluginError(
|
|
333
|
+
"plugin_lock_invalid",
|
|
334
|
+
`${PLUGIN_LOCK_FILE}: ${message}`,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function readBoundedDeclaration(path: string): Buffer {
|
|
339
|
+
let descriptor: number | undefined;
|
|
340
|
+
try {
|
|
341
|
+
descriptor = OPEN_SYNC(path, DECLARATION_READ_FLAGS);
|
|
342
|
+
const before = FSTAT_SYNC(descriptor, { bigint: true });
|
|
343
|
+
if (
|
|
344
|
+
!descriptorIsFile(before) ||
|
|
345
|
+
before.size < 0n ||
|
|
346
|
+
before.size > BIG_INT(MAX_DECLARATION_BYTES)
|
|
347
|
+
) {
|
|
348
|
+
throw new PluginError(
|
|
349
|
+
"plugin_source_invalid",
|
|
350
|
+
"Hosted extension declaration is not a bounded regular file.",
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
const expected = NUMBER(before.size);
|
|
354
|
+
const bytes = BUFFER_ALLOC_UNSAFE(expected);
|
|
355
|
+
let offset = 0;
|
|
356
|
+
while (offset < expected) {
|
|
357
|
+
const count = READ_SYNC(
|
|
358
|
+
descriptor,
|
|
359
|
+
bytes,
|
|
360
|
+
offset,
|
|
361
|
+
expected - offset,
|
|
362
|
+
offset,
|
|
363
|
+
);
|
|
364
|
+
if (count === 0) throw new ERROR("short read");
|
|
365
|
+
offset += count;
|
|
366
|
+
}
|
|
367
|
+
if (READ_SYNC(descriptor, BUFFER_ALLOC_UNSAFE(1), 0, 1, expected) !== 0)
|
|
368
|
+
throw new ERROR("grew");
|
|
369
|
+
const after = FSTAT_SYNC(descriptor, { bigint: true });
|
|
370
|
+
if (
|
|
371
|
+
after.size !== before.size ||
|
|
372
|
+
after.mtimeNs !== before.mtimeNs ||
|
|
373
|
+
after.ctimeNs !== before.ctimeNs
|
|
374
|
+
) {
|
|
375
|
+
throw new ERROR("changed");
|
|
376
|
+
}
|
|
377
|
+
return bytes;
|
|
378
|
+
} catch (error) {
|
|
379
|
+
if (PLUGIN_ERROR_IS_INSTANCE(error)) throw error;
|
|
380
|
+
throw new PluginError(
|
|
381
|
+
"plugin_source_invalid",
|
|
382
|
+
"Hosted extension declaration is unreadable or changed while reading.",
|
|
383
|
+
);
|
|
384
|
+
} finally {
|
|
385
|
+
if (descriptor !== undefined) CLOSE_SYNC(descriptor);
|
|
386
|
+
}
|
|
387
|
+
}
|