@hyperbrowser/sdk 0.93.0 → 1.0.0
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 +190 -0
- package/dist/client.d.ts +2 -20
- package/dist/client.js +5 -17
- package/dist/error.d.ts +27 -0
- package/dist/error.js +32 -0
- package/dist/image-builds.d.ts +6 -0
- package/dist/image-builds.js +12 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +12 -1
- package/dist/retry.d.ts +14 -0
- package/dist/retry.js +49 -0
- package/dist/sandbox/base.d.ts +27 -0
- package/dist/sandbox/base.js +298 -99
- package/dist/sandbox/files.d.ts +24 -25
- package/dist/sandbox/files.js +146 -37
- package/dist/sandbox/image-build/artifacts.d.ts +16 -0
- package/dist/sandbox/image-build/artifacts.js +16 -0
- package/dist/sandbox/image-build/blake2b.d.ts +2 -0
- package/dist/sandbox/image-build/blake2b.js +89 -0
- package/dist/sandbox/image-build/common.d.ts +19 -0
- package/dist/sandbox/image-build/common.js +109 -0
- package/dist/sandbox/image-build/context.d.ts +43 -0
- package/dist/sandbox/image-build/context.js +599 -0
- package/dist/sandbox/image-build/docker-image.d.ts +34 -0
- package/dist/sandbox/image-build/docker-image.js +472 -0
- package/dist/sandbox/image-build/dockerfile-analysis.d.ts +17 -0
- package/dist/sandbox/image-build/dockerfile-analysis.js +409 -0
- package/dist/sandbox/image-build/dockerignore.d.ts +18 -0
- package/dist/sandbox/image-build/dockerignore.js +275 -0
- package/dist/sandbox/image-build/gzip.d.ts +11 -0
- package/dist/sandbox/image-build/gzip.js +91 -0
- package/dist/sandbox/image-build/image-init.d.ts +5 -0
- package/dist/sandbox/image-build/image-init.js +97 -0
- package/dist/sandbox/image-build/index.d.ts +13 -0
- package/dist/sandbox/image-build/index.js +36 -0
- package/dist/sandbox/image-build/local-docker.d.ts +11 -0
- package/dist/sandbox/image-build/local-docker.js +72 -0
- package/dist/sandbox/image-build/resolution.d.ts +27 -0
- package/dist/sandbox/image-build/resolution.js +137 -0
- package/dist/sandbox/image-build/tar.d.ts +41 -0
- package/dist/sandbox/image-build/tar.js +378 -0
- package/dist/sandbox/image-build/upload.d.ts +10 -0
- package/dist/sandbox/image-build/upload.js +143 -0
- package/dist/sandbox/index.d.ts +2 -1
- package/dist/sandbox/index.js +4 -1
- package/dist/sandbox/process-output.d.ts +36 -0
- package/dist/sandbox/process-output.js +117 -0
- package/dist/sandbox/process.d.ts +25 -1
- package/dist/sandbox/process.js +197 -29
- package/dist/sandbox/terminal.js +9 -5
- package/dist/sandbox/ws.d.ts +4 -1
- package/dist/sandbox/ws.js +33 -7
- package/dist/services/base.d.ts +2 -0
- package/dist/services/base.js +46 -30
- package/dist/services/normalize.d.ts +3 -0
- package/dist/services/normalize.js +21 -0
- package/dist/services/sandboxes.d.ts +40 -3
- package/dist/services/sandboxes.js +569 -53
- package/dist/services/volumes.js +29 -14
- package/dist/types/index.d.ts +2 -1
- package/dist/types/sandbox.d.ts +246 -30
- package/dist/types/volume.d.ts +7 -7
- package/package.json +24 -5
package/dist/sandbox/files.js
CHANGED
|
@@ -3,12 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.SandboxFilesApi = exports.SandboxWatchDirHandle = void 0;
|
|
6
|
+
exports.SandboxFilesApi = exports.SandboxWatchDirHandle = exports.SandboxFileWatchHandle = void 0;
|
|
7
7
|
const buffer_1 = require("buffer");
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const web_1 = require("node:stream/web");
|
|
10
10
|
const ws_1 = __importDefault(require("ws"));
|
|
11
|
-
const
|
|
11
|
+
const stream_1 = require("stream");
|
|
12
|
+
const error_1 = require("../error");
|
|
12
13
|
const ws_2 = require("./ws");
|
|
13
14
|
const normalizeFileType = (value) => {
|
|
14
15
|
if (!value) {
|
|
@@ -123,12 +124,13 @@ const encodeWriteData = async (data, encoding) => {
|
|
|
123
124
|
}
|
|
124
125
|
throw new Error("Unsupported write data type");
|
|
125
126
|
};
|
|
126
|
-
class
|
|
127
|
+
class SandboxFileWatchHandle {
|
|
127
128
|
constructor(transport, getConnectionInfo, status, runtimeProxyOverride) {
|
|
128
129
|
this.transport = transport;
|
|
129
130
|
this.getConnectionInfo = getConnectionInfo;
|
|
130
131
|
this.status = status;
|
|
131
132
|
this.runtimeProxyOverride = runtimeProxyOverride;
|
|
133
|
+
this.connections = new Set();
|
|
132
134
|
}
|
|
133
135
|
get id() {
|
|
134
136
|
return this.status.id;
|
|
@@ -136,27 +138,66 @@ class RuntimeFileWatchHandle {
|
|
|
136
138
|
get path() {
|
|
137
139
|
return this.status.path;
|
|
138
140
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
get current() {
|
|
142
|
+
return this.toJSON();
|
|
143
|
+
}
|
|
144
|
+
toJSON() {
|
|
145
|
+
return {
|
|
146
|
+
...this.status,
|
|
147
|
+
...(this.status.events ? { events: this.status.events.map((event) => ({ ...event })) } : {}),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
async refresh(includeEvents = false) {
|
|
151
|
+
const response = await this.transport.requestJSON(`/sandbox/files/watch/${encodeURIComponent(this.id)}`, undefined, includeEvents ? { includeEvents: true } : undefined);
|
|
152
|
+
this.status = response.watch;
|
|
153
|
+
return this;
|
|
143
154
|
}
|
|
144
|
-
async
|
|
155
|
+
async stop() {
|
|
156
|
+
try {
|
|
157
|
+
await this.transport.requestJSON(`/sandbox/files/watch/${encodeURIComponent(this.id)}`, {
|
|
158
|
+
method: "DELETE",
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (!(error instanceof error_1.HyperbrowserError) || ![404, 409].includes(error.statusCode ?? 0))
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
this.status = { ...this.status, active: false, stoppedAt: this.status.stoppedAt || Date.now() };
|
|
166
|
+
for (const socket of this.connections)
|
|
167
|
+
socket.terminate();
|
|
168
|
+
}
|
|
169
|
+
async *events(options = {}) {
|
|
170
|
+
const { cursor, route = "ws", signal } = options;
|
|
171
|
+
if (route !== "ws" && route !== "stream")
|
|
172
|
+
throw new error_1.HyperbrowserError("Watch route must be ws or stream");
|
|
173
|
+
if (signal?.aborted)
|
|
174
|
+
throw new error_1.HyperbrowserError("Watch canceled", {
|
|
175
|
+
code: "request_aborted",
|
|
176
|
+
service: "runtime",
|
|
177
|
+
});
|
|
145
178
|
const connectionInfo = await this.getConnectionInfo();
|
|
146
|
-
const target = (0, ws_2.toWebSocketUrl)(connectionInfo.baseUrl, `/sandbox/files/watch/${this.status.id}
|
|
179
|
+
const target = (0, ws_2.toWebSocketUrl)(connectionInfo.baseUrl, `/sandbox/files/watch/${encodeURIComponent(this.status.id)}/${route}?sessionId=${encodeURIComponent(connectionInfo.sandboxId)}${cursor !== undefined ? `&cursor=${encodeURIComponent(String(cursor))}` : ""}`, this.runtimeProxyOverride);
|
|
147
180
|
const headers = {
|
|
148
181
|
Authorization: `Bearer ${connectionInfo.token}`,
|
|
149
182
|
};
|
|
150
183
|
if (target.hostHeader) {
|
|
151
184
|
headers.Host = target.hostHeader;
|
|
152
185
|
}
|
|
153
|
-
const ws = await (0, ws_2.openRuntimeWebSocket)(target, headers);
|
|
186
|
+
const ws = await (0, ws_2.openRuntimeWebSocket)(target, headers, { signal });
|
|
187
|
+
this.connections.add(ws);
|
|
154
188
|
const queue = new ws_2.AsyncEventQueue();
|
|
189
|
+
const abort = () => {
|
|
190
|
+
queue.fail(new error_1.HyperbrowserError("Watch canceled", { code: "request_aborted", service: "runtime" }));
|
|
191
|
+
ws.terminate();
|
|
192
|
+
};
|
|
193
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
194
|
+
if (signal?.aborted)
|
|
195
|
+
abort();
|
|
155
196
|
ws.on("message", (data) => {
|
|
156
197
|
try {
|
|
157
198
|
const parsed = JSON.parse(data.toString());
|
|
158
199
|
if ("error" in parsed) {
|
|
159
|
-
queue.fail(new
|
|
200
|
+
queue.fail(new error_1.HyperbrowserError(parsed.error, {
|
|
160
201
|
statusCode: 410,
|
|
161
202
|
code: parsed.code,
|
|
162
203
|
retryable: false,
|
|
@@ -165,9 +206,17 @@ class RuntimeFileWatchHandle {
|
|
|
165
206
|
return;
|
|
166
207
|
}
|
|
167
208
|
if (parsed.type === "event") {
|
|
168
|
-
|
|
209
|
+
this.status = {
|
|
210
|
+
...this.status,
|
|
211
|
+
// Receiver sequences start at 1; zero denotes an empty buffer.
|
|
212
|
+
oldestSeq: this.status.oldestSeq || parsed.event.seq,
|
|
213
|
+
lastSeq: Math.max(this.status.lastSeq ?? 0, parsed.event.seq),
|
|
214
|
+
};
|
|
215
|
+
queue.push(parsed);
|
|
169
216
|
return;
|
|
170
217
|
}
|
|
218
|
+
this.status = parsed.status;
|
|
219
|
+
queue.push({ type: "done", status: this.current });
|
|
171
220
|
queue.close();
|
|
172
221
|
}
|
|
173
222
|
catch (error) {
|
|
@@ -176,34 +225,38 @@ class RuntimeFileWatchHandle {
|
|
|
176
225
|
});
|
|
177
226
|
ws.on("close", () => queue.close());
|
|
178
227
|
ws.on("error", (error) => queue.fail(error));
|
|
228
|
+
ws.resume?.();
|
|
179
229
|
try {
|
|
180
230
|
for await (const event of queue) {
|
|
181
231
|
yield event;
|
|
182
232
|
}
|
|
183
233
|
}
|
|
184
234
|
finally {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
235
|
+
signal?.removeEventListener("abort", abort);
|
|
236
|
+
this.connections.delete(ws);
|
|
237
|
+
// terminate also releases peers which never acknowledge a close frame.
|
|
238
|
+
if (ws.readyState !== ws_1.default.CLOSED)
|
|
239
|
+
ws.terminate();
|
|
191
240
|
}
|
|
192
241
|
}
|
|
193
242
|
}
|
|
243
|
+
exports.SandboxFileWatchHandle = SandboxFileWatchHandle;
|
|
194
244
|
class SandboxWatchDirHandle {
|
|
195
245
|
constructor(watch, onEvent, onExit, timeoutMs) {
|
|
196
246
|
this.watch = watch;
|
|
197
247
|
this.onExit = onExit;
|
|
198
248
|
this.stopRequested = false;
|
|
199
249
|
this.exitNotified = false;
|
|
250
|
+
this.invokingCallback = false;
|
|
200
251
|
if (timeoutMs !== undefined && timeoutMs > 0) {
|
|
201
252
|
this.timeout = setTimeout(() => {
|
|
202
|
-
void this.stop();
|
|
253
|
+
void this.stop().catch(() => undefined);
|
|
203
254
|
}, timeoutMs);
|
|
204
255
|
this.timeout.unref?.();
|
|
205
256
|
}
|
|
206
257
|
this.runPromise = this.run(onEvent);
|
|
258
|
+
// Callback failures must not become unhandled background rejections.
|
|
259
|
+
this.runPromise.catch(() => undefined);
|
|
207
260
|
}
|
|
208
261
|
async stop() {
|
|
209
262
|
if (this.stopRequested) {
|
|
@@ -215,20 +268,27 @@ class SandboxWatchDirHandle {
|
|
|
215
268
|
this.timeout = undefined;
|
|
216
269
|
}
|
|
217
270
|
await this.watch.stop();
|
|
218
|
-
|
|
271
|
+
if (!this.invokingCallback)
|
|
272
|
+
await this.runPromise.catch(() => undefined);
|
|
219
273
|
}
|
|
220
274
|
async run(onEvent) {
|
|
221
275
|
let exitError;
|
|
222
276
|
try {
|
|
223
|
-
for await (const
|
|
277
|
+
for await (const message of this.watch.events()) {
|
|
278
|
+
if (message.type === "done")
|
|
279
|
+
break;
|
|
280
|
+
const event = message.event;
|
|
224
281
|
const type = normalizeEventType(event.op);
|
|
225
282
|
if (!type) {
|
|
226
283
|
continue;
|
|
227
284
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
name: relativeWatchName(this.watch.path, event.path)
|
|
231
|
-
}
|
|
285
|
+
this.invokingCallback = true;
|
|
286
|
+
try {
|
|
287
|
+
await onEvent({ type, name: relativeWatchName(this.watch.path, event.path) });
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
this.invokingCallback = false;
|
|
291
|
+
}
|
|
232
292
|
}
|
|
233
293
|
}
|
|
234
294
|
catch (error) {
|
|
@@ -241,7 +301,13 @@ class SandboxWatchDirHandle {
|
|
|
241
301
|
}
|
|
242
302
|
if (!this.exitNotified) {
|
|
243
303
|
this.exitNotified = true;
|
|
244
|
-
|
|
304
|
+
this.invokingCallback = true;
|
|
305
|
+
try {
|
|
306
|
+
await this.onExit?.(exitError);
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
this.invokingCallback = false;
|
|
310
|
+
}
|
|
245
311
|
}
|
|
246
312
|
}
|
|
247
313
|
}
|
|
@@ -264,7 +330,7 @@ class SandboxFilesApi {
|
|
|
264
330
|
return true;
|
|
265
331
|
}
|
|
266
332
|
catch (error) {
|
|
267
|
-
if (error instanceof
|
|
333
|
+
if (error instanceof error_1.HyperbrowserError && error.statusCode === 404) {
|
|
268
334
|
return false;
|
|
269
335
|
}
|
|
270
336
|
if (error instanceof Error && /not found|no such file|does not exist/i.test(error.message)) {
|
|
@@ -381,12 +447,13 @@ class SandboxFilesApi {
|
|
|
381
447
|
});
|
|
382
448
|
return Boolean(response.created);
|
|
383
449
|
}
|
|
384
|
-
async rename(oldPath, newPath) {
|
|
450
|
+
async rename(oldPath, newPath, options = {}) {
|
|
385
451
|
const response = await this.transport.requestJSON("/sandbox/files/move", {
|
|
386
452
|
method: "POST",
|
|
387
453
|
body: this.withRunAsBody({
|
|
388
454
|
from: oldPath,
|
|
389
455
|
to: newPath,
|
|
456
|
+
overwrite: options.overwrite,
|
|
390
457
|
}),
|
|
391
458
|
headers: {
|
|
392
459
|
"content-type": "application/json",
|
|
@@ -440,18 +507,60 @@ class SandboxFilesApi {
|
|
|
440
507
|
});
|
|
441
508
|
}
|
|
442
509
|
async watchDir(path, onEvent, options = {}) {
|
|
510
|
+
const watch = await this.watch(path, options);
|
|
511
|
+
return new SandboxWatchDirHandle(watch, onEvent, options.onExit, options.timeoutMs);
|
|
512
|
+
}
|
|
513
|
+
async watch(path, options = {}) {
|
|
443
514
|
const response = await this.transport.requestJSON("/sandbox/files/watch", {
|
|
444
515
|
method: "POST",
|
|
445
|
-
body: this.withRunAsBody({
|
|
446
|
-
|
|
447
|
-
recursive: options.recursive,
|
|
448
|
-
}),
|
|
449
|
-
headers: {
|
|
450
|
-
"content-type": "application/json",
|
|
451
|
-
},
|
|
516
|
+
body: this.withRunAsBody({ path, recursive: options.recursive }),
|
|
517
|
+
headers: { "content-type": "application/json" },
|
|
452
518
|
});
|
|
453
|
-
|
|
454
|
-
|
|
519
|
+
return new SandboxFileWatchHandle(this.transport, this.getConnectionInfo, response.watch, this.runtimeProxyOverride);
|
|
520
|
+
}
|
|
521
|
+
async getWatch(id, includeEvents = false) {
|
|
522
|
+
const response = await this.transport.requestJSON(`/sandbox/files/watch/${encodeURIComponent(id)}`, undefined, includeEvents ? { includeEvents: true } : undefined);
|
|
523
|
+
return new SandboxFileWatchHandle(this.transport, this.getConnectionInfo, response.watch, this.runtimeProxyOverride);
|
|
524
|
+
}
|
|
525
|
+
stat(path) {
|
|
526
|
+
return this.getInfo(path);
|
|
527
|
+
}
|
|
528
|
+
mkdir(path, options = {}) {
|
|
529
|
+
return this.makeDir(path, options);
|
|
530
|
+
}
|
|
531
|
+
move(params) {
|
|
532
|
+
return this.rename(params.source, params.destination, params);
|
|
533
|
+
}
|
|
534
|
+
delete(path, options = {}) {
|
|
535
|
+
return this.remove(path, options);
|
|
536
|
+
}
|
|
537
|
+
/** Upload without buffering; the source is closed on completion, rejection or cancellation. */
|
|
538
|
+
async uploadStream(path, source, options = {}) {
|
|
539
|
+
if (options.contentLength !== undefined &&
|
|
540
|
+
(!Number.isSafeInteger(options.contentLength) || options.contentLength < 0)) {
|
|
541
|
+
throw new error_1.HyperbrowserError("contentLength must be a nonnegative safe integer");
|
|
542
|
+
}
|
|
543
|
+
const body = stream_1.Readable.from(source, { objectMode: false, highWaterMark: 64 * 1024 });
|
|
544
|
+
try {
|
|
545
|
+
return await this.transport.requestJSON("/sandbox/files/upload", {
|
|
546
|
+
method: "PUT",
|
|
547
|
+
body,
|
|
548
|
+
signal: options.signal,
|
|
549
|
+
headers: {
|
|
550
|
+
"content-type": "application/octet-stream",
|
|
551
|
+
...(options.contentLength !== undefined
|
|
552
|
+
? { "content-length": String(options.contentLength) }
|
|
553
|
+
: {}),
|
|
554
|
+
},
|
|
555
|
+
}, this.withRunAsQuery({ path }));
|
|
556
|
+
}
|
|
557
|
+
finally {
|
|
558
|
+
body.destroy();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
/** Pull-based download; consume with for-await or Readable.from(). */
|
|
562
|
+
downloadStream(path, options = {}) {
|
|
563
|
+
return this.transport.streamBytes("/sandbox/files/download", { signal: options.signal }, this.withRunAsQuery({ path }));
|
|
455
564
|
}
|
|
456
565
|
async uploadUrl(path, options = {}) {
|
|
457
566
|
return this.transport.requestJSON("/sandbox/files/presign-upload", {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SandboxImageBuildInputFormat, SandboxImageInit } from "../../types/sandbox";
|
|
2
|
+
export declare const IMAGE_BUILD_INPUT_FORMAT: SandboxImageBuildInputFormat;
|
|
3
|
+
export declare const CONTEXT_MANIFEST_INPUT_FORMAT: SandboxImageBuildInputFormat;
|
|
4
|
+
export declare const DOCKER_IMAGE_MANIFEST_INPUT_FORMAT: SandboxImageBuildInputFormat;
|
|
5
|
+
export declare const IMAGE_BUILD_SOURCE_PLATFORM = "linux/amd64";
|
|
6
|
+
export interface DockerImageBuildArtifact {
|
|
7
|
+
path: string;
|
|
8
|
+
sha256Hex: string;
|
|
9
|
+
sizeBytes: number;
|
|
10
|
+
inputFormat: SandboxImageBuildInputFormat;
|
|
11
|
+
sourcePlatform: string;
|
|
12
|
+
imageConfigUser: string;
|
|
13
|
+
imageInit?: SandboxImageInit;
|
|
14
|
+
}
|
|
15
|
+
export declare const removeArtifact: (artifact: DockerImageBuildArtifact) => void;
|
|
16
|
+
export declare const removeWorkspace: (workspace: string) => void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.removeWorkspace = exports.removeArtifact = exports.IMAGE_BUILD_SOURCE_PLATFORM = exports.DOCKER_IMAGE_MANIFEST_INPUT_FORMAT = exports.CONTEXT_MANIFEST_INPUT_FORMAT = exports.IMAGE_BUILD_INPUT_FORMAT = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
exports.IMAGE_BUILD_INPUT_FORMAT = "rootfs_export_tar_gz";
|
|
6
|
+
exports.CONTEXT_MANIFEST_INPUT_FORMAT = "dockerfile_context_manifest_v1";
|
|
7
|
+
exports.DOCKER_IMAGE_MANIFEST_INPUT_FORMAT = "docker_image_manifest_v1";
|
|
8
|
+
exports.IMAGE_BUILD_SOURCE_PLATFORM = "linux/amd64";
|
|
9
|
+
const removeArtifact = (artifact) => {
|
|
10
|
+
(0, fs_1.rmSync)(artifact.path, { force: true });
|
|
11
|
+
};
|
|
12
|
+
exports.removeArtifact = removeArtifact;
|
|
13
|
+
const removeWorkspace = (workspace) => {
|
|
14
|
+
(0, fs_1.rmSync)(workspace, { recursive: true, force: true });
|
|
15
|
+
};
|
|
16
|
+
exports.removeWorkspace = removeWorkspace;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Minimal BLAKE2b (RFC 7693) supporting arbitrary digest lengths. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.blake2b = void 0;
|
|
5
|
+
const IV = [
|
|
6
|
+
0x6a09e667f3bcc908n,
|
|
7
|
+
0xbb67ae8584caa73bn,
|
|
8
|
+
0x3c6ef372fe94f82bn,
|
|
9
|
+
0xa54ff53a5f1d36f1n,
|
|
10
|
+
0x510e527fade682d1n,
|
|
11
|
+
0x9b05688c2b3e6c1fn,
|
|
12
|
+
0x1f83d9abfb41bd6bn,
|
|
13
|
+
0x5be0cd19137e2179n,
|
|
14
|
+
];
|
|
15
|
+
const SIGMA = [
|
|
16
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
17
|
+
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
|
|
18
|
+
[11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
|
|
19
|
+
[7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
|
|
20
|
+
[9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
|
|
21
|
+
[2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
|
|
22
|
+
[12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
|
|
23
|
+
[13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
|
|
24
|
+
[6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
|
|
25
|
+
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0],
|
|
26
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
27
|
+
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
|
|
28
|
+
];
|
|
29
|
+
const MASK = (1n << 64n) - 1n;
|
|
30
|
+
const rotr = (value, bits) => ((value >> bits) | (value << (64n - bits))) & MASK;
|
|
31
|
+
const compress = (h, block, counter, last) => {
|
|
32
|
+
const m = [];
|
|
33
|
+
for (let index = 0; index < 16; index += 1) {
|
|
34
|
+
m.push(block.readBigUInt64LE(index * 8));
|
|
35
|
+
}
|
|
36
|
+
const v = [...h, ...IV];
|
|
37
|
+
v[12] ^= counter & MASK;
|
|
38
|
+
v[13] ^= (counter >> 64n) & MASK;
|
|
39
|
+
if (last) {
|
|
40
|
+
v[14] ^= MASK;
|
|
41
|
+
}
|
|
42
|
+
const mix = (a, b, c, d, x, y) => {
|
|
43
|
+
v[a] = (v[a] + v[b] + x) & MASK;
|
|
44
|
+
v[d] = rotr(v[d] ^ v[a], 32n);
|
|
45
|
+
v[c] = (v[c] + v[d]) & MASK;
|
|
46
|
+
v[b] = rotr(v[b] ^ v[c], 24n);
|
|
47
|
+
v[a] = (v[a] + v[b] + y) & MASK;
|
|
48
|
+
v[d] = rotr(v[d] ^ v[a], 16n);
|
|
49
|
+
v[c] = (v[c] + v[d]) & MASK;
|
|
50
|
+
v[b] = rotr(v[b] ^ v[c], 63n);
|
|
51
|
+
};
|
|
52
|
+
for (let round = 0; round < 12; round += 1) {
|
|
53
|
+
const s = SIGMA[round];
|
|
54
|
+
mix(0, 4, 8, 12, m[s[0]], m[s[1]]);
|
|
55
|
+
mix(1, 5, 9, 13, m[s[2]], m[s[3]]);
|
|
56
|
+
mix(2, 6, 10, 14, m[s[4]], m[s[5]]);
|
|
57
|
+
mix(3, 7, 11, 15, m[s[6]], m[s[7]]);
|
|
58
|
+
mix(0, 5, 10, 15, m[s[8]], m[s[9]]);
|
|
59
|
+
mix(1, 6, 11, 12, m[s[10]], m[s[11]]);
|
|
60
|
+
mix(2, 7, 8, 13, m[s[12]], m[s[13]]);
|
|
61
|
+
mix(3, 4, 9, 14, m[s[14]], m[s[15]]);
|
|
62
|
+
}
|
|
63
|
+
for (let index = 0; index < 8; index += 1) {
|
|
64
|
+
h[index] = h[index] ^ v[index] ^ v[index + 8];
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const blake2b = (data, digestSize) => {
|
|
68
|
+
if (!Number.isInteger(digestSize) || digestSize < 1 || digestSize > 64) {
|
|
69
|
+
throw new RangeError("digestSize must be between 1 and 64");
|
|
70
|
+
}
|
|
71
|
+
const h = [...IV];
|
|
72
|
+
h[0] ^= BigInt(0x01010000 ^ digestSize);
|
|
73
|
+
let offset = 0;
|
|
74
|
+
let counter = 0n;
|
|
75
|
+
while (data.length - offset > 128) {
|
|
76
|
+
counter += 128n;
|
|
77
|
+
compress(h, data.subarray(offset, offset + 128), counter, false);
|
|
78
|
+
offset += 128;
|
|
79
|
+
}
|
|
80
|
+
const remaining = data.subarray(offset);
|
|
81
|
+
const block = Buffer.alloc(128);
|
|
82
|
+
remaining.copy(block);
|
|
83
|
+
counter += BigInt(remaining.length);
|
|
84
|
+
compress(h, block, counter, true);
|
|
85
|
+
const out = Buffer.alloc(64);
|
|
86
|
+
h.forEach((word, index) => out.writeBigUInt64LE(word, index * 8));
|
|
87
|
+
return out.subarray(0, digestSize);
|
|
88
|
+
};
|
|
89
|
+
exports.blake2b = blake2b;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Python `posixpath.normpath` semantics (keeps exactly two leading slashes). */
|
|
2
|
+
export declare const posixNormpath: (value: string) => string;
|
|
3
|
+
export declare const posixDirname: (value: string) => string;
|
|
4
|
+
export declare const posixJoin: (...parts: string[]) => string;
|
|
5
|
+
/** Compare strings by Unicode code point, like Python's default `str` ordering. */
|
|
6
|
+
export declare const compareCodePoints: (a: string, b: string) => number;
|
|
7
|
+
/**
|
|
8
|
+
* Compact JSON matching Python `json.dumps(value, separators=(",", ":"))`.
|
|
9
|
+
* `sortKeys` mirrors `sort_keys=True`; `ensureAscii` mirrors the default
|
|
10
|
+
* `ensure_ascii=True` escaping of non-ASCII characters.
|
|
11
|
+
*/
|
|
12
|
+
export declare const compactJson: (value: unknown, options?: {
|
|
13
|
+
sortKeys?: boolean;
|
|
14
|
+
ensureAscii?: boolean;
|
|
15
|
+
}) => string;
|
|
16
|
+
export declare const SHA256_HEX_PATTERN: RegExp;
|
|
17
|
+
export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
|
|
18
|
+
export declare const requireRecord: (value: unknown, label: string) => Record<string, unknown>;
|
|
19
|
+
export declare const sleep: (seconds: number) => Promise<void>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sleep = exports.requireRecord = exports.isRecord = exports.SHA256_HEX_PATTERN = exports.compactJson = exports.compareCodePoints = exports.posixJoin = exports.posixDirname = exports.posixNormpath = void 0;
|
|
4
|
+
/** Python `posixpath.normpath` semantics (keeps exactly two leading slashes). */
|
|
5
|
+
const posixNormpath = (value) => {
|
|
6
|
+
if (value === "") {
|
|
7
|
+
return ".";
|
|
8
|
+
}
|
|
9
|
+
let initialSlashes = value.startsWith("/") ? 1 : 0;
|
|
10
|
+
if (initialSlashes && value.startsWith("//") && !value.startsWith("///")) {
|
|
11
|
+
initialSlashes = 2;
|
|
12
|
+
}
|
|
13
|
+
const components = [];
|
|
14
|
+
for (const component of value.split("/")) {
|
|
15
|
+
if (component === "" || component === ".") {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (component !== ".." ||
|
|
19
|
+
(!initialSlashes && components.length === 0) ||
|
|
20
|
+
(components.length > 0 && components[components.length - 1] === "..")) {
|
|
21
|
+
components.push(component);
|
|
22
|
+
}
|
|
23
|
+
else if (components.length > 0) {
|
|
24
|
+
components.pop();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const joined = "/".repeat(initialSlashes) + components.join("/");
|
|
28
|
+
return joined || ".";
|
|
29
|
+
};
|
|
30
|
+
exports.posixNormpath = posixNormpath;
|
|
31
|
+
const posixDirname = (value) => {
|
|
32
|
+
const index = value.lastIndexOf("/") + 1;
|
|
33
|
+
let head = value.slice(0, index);
|
|
34
|
+
if (head && head !== "/".repeat(head.length)) {
|
|
35
|
+
head = head.replace(/\/+$/, "");
|
|
36
|
+
}
|
|
37
|
+
return head;
|
|
38
|
+
};
|
|
39
|
+
exports.posixDirname = posixDirname;
|
|
40
|
+
const posixJoin = (...parts) => {
|
|
41
|
+
let joined = "";
|
|
42
|
+
for (const part of parts) {
|
|
43
|
+
if (part.startsWith("/")) {
|
|
44
|
+
joined = part;
|
|
45
|
+
}
|
|
46
|
+
else if (!joined || joined.endsWith("/")) {
|
|
47
|
+
joined += part;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
joined += "/" + part;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return joined;
|
|
54
|
+
};
|
|
55
|
+
exports.posixJoin = posixJoin;
|
|
56
|
+
/** Compare strings by Unicode code point, like Python's default `str` ordering. */
|
|
57
|
+
const compareCodePoints = (a, b) => {
|
|
58
|
+
const left = Array.from(a);
|
|
59
|
+
const right = Array.from(b);
|
|
60
|
+
const length = Math.min(left.length, right.length);
|
|
61
|
+
for (let index = 0; index < length; index += 1) {
|
|
62
|
+
const difference = (left[index].codePointAt(0) ?? 0) - (right[index].codePointAt(0) ?? 0);
|
|
63
|
+
if (difference !== 0) {
|
|
64
|
+
return difference;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return left.length - right.length;
|
|
68
|
+
};
|
|
69
|
+
exports.compareCodePoints = compareCodePoints;
|
|
70
|
+
const escapeNonAscii = (json) => json.replace(/[\u007f-\uffff]/g, (character) => {
|
|
71
|
+
return "\\u" + character.charCodeAt(0).toString(16).padStart(4, "0");
|
|
72
|
+
});
|
|
73
|
+
// Serialize keys directly: assigning sorted keys to an object lets JavaScript
|
|
74
|
+
// reorder integer-like keys and treats __proto__ as a setter.
|
|
75
|
+
const sortedJson = (value) => {
|
|
76
|
+
if (Array.isArray(value))
|
|
77
|
+
return `[${value.map((item) => sortedJson(item) ?? "null").join(",")}]`;
|
|
78
|
+
if (value && typeof value === "object") {
|
|
79
|
+
const record = value;
|
|
80
|
+
const entries = Object.keys(record).sort(exports.compareCodePoints).flatMap((key) => {
|
|
81
|
+
const item = sortedJson(record[key]);
|
|
82
|
+
return item === undefined ? [] : [`${JSON.stringify(key)}:${item}`];
|
|
83
|
+
});
|
|
84
|
+
return `{${entries.join(",")}}`;
|
|
85
|
+
}
|
|
86
|
+
return JSON.stringify(value);
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Compact JSON matching Python `json.dumps(value, separators=(",", ":"))`.
|
|
90
|
+
* `sortKeys` mirrors `sort_keys=True`; `ensureAscii` mirrors the default
|
|
91
|
+
* `ensure_ascii=True` escaping of non-ASCII characters.
|
|
92
|
+
*/
|
|
93
|
+
const compactJson = (value, options = {}) => {
|
|
94
|
+
const json = options.sortKeys ? sortedJson(value) : JSON.stringify(value);
|
|
95
|
+
return options.ensureAscii === false ? json : escapeNonAscii(json);
|
|
96
|
+
};
|
|
97
|
+
exports.compactJson = compactJson;
|
|
98
|
+
exports.SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
|
|
99
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
100
|
+
exports.isRecord = isRecord;
|
|
101
|
+
const requireRecord = (value, label) => {
|
|
102
|
+
if (!(0, exports.isRecord)(value)) {
|
|
103
|
+
throw new Error(`${label} must be a JSON object`);
|
|
104
|
+
}
|
|
105
|
+
return value;
|
|
106
|
+
};
|
|
107
|
+
exports.requireRecord = requireRecord;
|
|
108
|
+
const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, Math.max(0, seconds) * 1000));
|
|
109
|
+
exports.sleep = sleep;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Remote Dockerfile build context selection, fingerprinting, and packaging. */
|
|
2
|
+
import { SandboxBuildContextManifest } from "../../types/sandbox";
|
|
3
|
+
import { DockerImageBuildArtifact } from "./artifacts";
|
|
4
|
+
import { DockerIgnoreMatcher } from "./dockerignore";
|
|
5
|
+
/** The packaged context no longer matches the caller's cache fingerprint. */
|
|
6
|
+
export declare class DockerBuildContextChangedError extends Error {
|
|
7
|
+
constructor(message: string);
|
|
8
|
+
}
|
|
9
|
+
export interface PackagedDockerBuildContext {
|
|
10
|
+
artifact: DockerImageBuildArtifact;
|
|
11
|
+
manifest: SandboxBuildContextManifest;
|
|
12
|
+
bundles: Record<string, DockerImageBuildArtifact>;
|
|
13
|
+
workspace: string;
|
|
14
|
+
fingerprint: string;
|
|
15
|
+
cleanup(): void;
|
|
16
|
+
}
|
|
17
|
+
export interface DockerBuildContextOptions {
|
|
18
|
+
dockerfile?: string;
|
|
19
|
+
forceFullContext?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare const loadDockerignore: (ignorePath: string) => DockerIgnoreMatcher | null;
|
|
22
|
+
export declare const collectContextEntries: (contextRoot: string, sources: string[], options: {
|
|
23
|
+
ignoreMatcher: DockerIgnoreMatcher | null;
|
|
24
|
+
required: boolean;
|
|
25
|
+
}) => Set<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Fingerprint the effective remote context without compressing or staging it.
|
|
28
|
+
*
|
|
29
|
+
* Uses the same Dockerfile source selection, ignore rules, and normalized tar
|
|
30
|
+
* entries as remote packaging. Pass the result as `expectedContextFingerprint`
|
|
31
|
+
* when building to detect context changes. Build options outside the context
|
|
32
|
+
* (e.g. platform or imageInit) must also be included in the caller's cache
|
|
33
|
+
* key. Mutable base tags and network resources are not resolved.
|
|
34
|
+
*/
|
|
35
|
+
export declare const dockerBuildContextFingerprint: (contextPath: string, options?: DockerBuildContextOptions) => Promise<string>;
|
|
36
|
+
/** Serialize a manifest exactly as the Python SDK does (field order, no nulls). */
|
|
37
|
+
export declare const canonicalManifestJson: (value: unknown) => Buffer;
|
|
38
|
+
export declare const writeManifestArtifact: (workspace: string, filename: string, data: Buffer, inputFormat: DockerImageBuildArtifact["inputFormat"], extras?: Pick<DockerImageBuildArtifact, "imageConfigUser" | "imageInit">) => DockerImageBuildArtifact;
|
|
39
|
+
export interface PackageDockerBuildContextOptions extends DockerBuildContextOptions {
|
|
40
|
+
tempDir?: string;
|
|
41
|
+
expectedContextFingerprint?: string;
|
|
42
|
+
}
|
|
43
|
+
export declare const packageDockerBuildContextManifest: (contextPath: string, options?: PackageDockerBuildContextOptions) => Promise<PackagedDockerBuildContext>;
|