@hyperbrowser/sdk 0.92.7 → 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.
Files changed (68) hide show
  1. package/README.md +190 -0
  2. package/dist/client.d.ts +4 -20
  3. package/dist/client.js +7 -17
  4. package/dist/error.d.ts +27 -0
  5. package/dist/error.js +32 -0
  6. package/dist/image-builds.d.ts +6 -0
  7. package/dist/image-builds.js +12 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.js +12 -1
  10. package/dist/retry.d.ts +14 -0
  11. package/dist/retry.js +49 -0
  12. package/dist/sandbox/base.d.ts +27 -0
  13. package/dist/sandbox/base.js +298 -99
  14. package/dist/sandbox/files.d.ts +24 -25
  15. package/dist/sandbox/files.js +146 -37
  16. package/dist/sandbox/image-build/artifacts.d.ts +16 -0
  17. package/dist/sandbox/image-build/artifacts.js +16 -0
  18. package/dist/sandbox/image-build/blake2b.d.ts +2 -0
  19. package/dist/sandbox/image-build/blake2b.js +89 -0
  20. package/dist/sandbox/image-build/common.d.ts +19 -0
  21. package/dist/sandbox/image-build/common.js +109 -0
  22. package/dist/sandbox/image-build/context.d.ts +43 -0
  23. package/dist/sandbox/image-build/context.js +599 -0
  24. package/dist/sandbox/image-build/docker-image.d.ts +34 -0
  25. package/dist/sandbox/image-build/docker-image.js +472 -0
  26. package/dist/sandbox/image-build/dockerfile-analysis.d.ts +17 -0
  27. package/dist/sandbox/image-build/dockerfile-analysis.js +409 -0
  28. package/dist/sandbox/image-build/dockerignore.d.ts +18 -0
  29. package/dist/sandbox/image-build/dockerignore.js +275 -0
  30. package/dist/sandbox/image-build/gzip.d.ts +11 -0
  31. package/dist/sandbox/image-build/gzip.js +91 -0
  32. package/dist/sandbox/image-build/image-init.d.ts +5 -0
  33. package/dist/sandbox/image-build/image-init.js +97 -0
  34. package/dist/sandbox/image-build/index.d.ts +13 -0
  35. package/dist/sandbox/image-build/index.js +36 -0
  36. package/dist/sandbox/image-build/local-docker.d.ts +11 -0
  37. package/dist/sandbox/image-build/local-docker.js +72 -0
  38. package/dist/sandbox/image-build/resolution.d.ts +27 -0
  39. package/dist/sandbox/image-build/resolution.js +137 -0
  40. package/dist/sandbox/image-build/tar.d.ts +41 -0
  41. package/dist/sandbox/image-build/tar.js +378 -0
  42. package/dist/sandbox/image-build/upload.d.ts +10 -0
  43. package/dist/sandbox/image-build/upload.js +143 -0
  44. package/dist/sandbox/index.d.ts +2 -1
  45. package/dist/sandbox/index.js +4 -1
  46. package/dist/sandbox/process-output.d.ts +36 -0
  47. package/dist/sandbox/process-output.js +117 -0
  48. package/dist/sandbox/process.d.ts +25 -1
  49. package/dist/sandbox/process.js +197 -29
  50. package/dist/sandbox/terminal.js +9 -5
  51. package/dist/sandbox/ws.d.ts +4 -1
  52. package/dist/sandbox/ws.js +33 -7
  53. package/dist/services/agents/jev-computer-use.d.ts +30 -0
  54. package/dist/services/agents/jev-computer-use.js +103 -0
  55. package/dist/services/base.d.ts +2 -0
  56. package/dist/services/base.js +46 -30
  57. package/dist/services/normalize.d.ts +3 -0
  58. package/dist/services/normalize.js +21 -0
  59. package/dist/services/sandboxes.d.ts +40 -3
  60. package/dist/services/sandboxes.js +569 -53
  61. package/dist/services/volumes.js +29 -14
  62. package/dist/types/agents/jev-computer-use.d.ts +43 -0
  63. package/dist/types/agents/jev-computer-use.js +2 -0
  64. package/dist/types/constants.d.ts +5 -2
  65. package/dist/types/index.d.ts +4 -2
  66. package/dist/types/sandbox.d.ts +246 -30
  67. package/dist/types/volume.d.ts +7 -7
  68. package/package.json +24 -5
@@ -0,0 +1,11 @@
1
+ import { PaxTarWriter } from "./tar";
2
+ export interface GzipTarResult {
3
+ sizeBytes: number;
4
+ sha256Hex: string;
5
+ uncompressedSizeBytes: number;
6
+ }
7
+ /**
8
+ * Write a deterministic gzip-compressed tar archive to `path`. The `populate`
9
+ * callback adds entries through the supplied writer.
10
+ */
11
+ export declare const writeGzipTar: (path: string, populate: (writer: PaxTarWriter) => Promise<void>) => Promise<GzipTarResult>;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writeGzipTar = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const fs_1 = require("fs");
6
+ const events_1 = require("events");
7
+ const zlib_1 = require("zlib");
8
+ const tar_1 = require("./tar");
9
+ const CRC_TABLE = (() => {
10
+ const table = new Uint32Array(256);
11
+ for (let index = 0; index < 256; index += 1) {
12
+ let value = index;
13
+ for (let bit = 0; bit < 8; bit += 1) {
14
+ value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
15
+ }
16
+ table[index] = value >>> 0;
17
+ }
18
+ return table;
19
+ })();
20
+ const updateCrc32 = (crc, chunk) => {
21
+ let value = ~crc >>> 0;
22
+ for (const byte of chunk) {
23
+ value = CRC_TABLE[(value ^ byte) & 0xff] ^ (value >>> 8);
24
+ }
25
+ return ~value >>> 0;
26
+ };
27
+ /** gzip member header matching CPython `GzipFile(filename="", mtime=0)` at compresslevel 1. */
28
+ const GZIP_HEADER = Buffer.from([0x1f, 0x8b, 0x08, 0x00, 0, 0, 0, 0, 0x00, 0xff]);
29
+ /**
30
+ * Write a deterministic gzip-compressed tar archive to `path`. The `populate`
31
+ * callback adds entries through the supplied writer.
32
+ */
33
+ const writeGzipTar = async (path, populate) => {
34
+ const output = (0, fs_1.createWriteStream)(path, { flags: "wx" });
35
+ // Observe write errors immediately, including failures before the first drain.
36
+ let outputError;
37
+ output.on("error", (error) => { outputError = error; deflate.destroy(error); });
38
+ const hasher = (0, crypto_1.createHash)("sha256");
39
+ let compressedSize = 0;
40
+ let crc = 0;
41
+ let uncompressedSize = 0;
42
+ const writeOutput = async (chunk) => {
43
+ if (outputError)
44
+ throw outputError;
45
+ hasher.update(chunk);
46
+ compressedSize += chunk.length;
47
+ if (!output.write(chunk)) {
48
+ await (0, events_1.once)(output, "drain");
49
+ }
50
+ };
51
+ const deflate = (0, zlib_1.createDeflateRaw)({ level: 1 });
52
+ const drainDeflate = (async () => {
53
+ for await (const chunk of deflate) {
54
+ await writeOutput(chunk);
55
+ }
56
+ })();
57
+ // A source failure can occur while compression is still draining.
58
+ void drainDeflate.catch(() => undefined);
59
+ try {
60
+ await writeOutput(GZIP_HEADER);
61
+ const writer = new tar_1.PaxTarWriter(async (chunk) => {
62
+ crc = updateCrc32(crc, chunk);
63
+ uncompressedSize += chunk.length;
64
+ if (!deflate.write(chunk)) {
65
+ await (0, events_1.once)(deflate, "drain");
66
+ }
67
+ });
68
+ await populate(writer);
69
+ await writer.close();
70
+ deflate.end();
71
+ await drainDeflate;
72
+ const trailer = Buffer.alloc(8);
73
+ trailer.writeUInt32LE(crc >>> 0, 0);
74
+ trailer.writeUInt32LE(uncompressedSize % 2 ** 32, 4);
75
+ await writeOutput(trailer);
76
+ output.end();
77
+ await (0, events_1.once)(output, "finish");
78
+ }
79
+ catch (error) {
80
+ deflate.destroy();
81
+ output.destroy();
82
+ await drainDeflate.catch(() => undefined);
83
+ throw error;
84
+ }
85
+ return {
86
+ sizeBytes: compressedSize,
87
+ sha256Hex: hasher.digest("hex"),
88
+ uncompressedSizeBytes: uncompressedSize,
89
+ };
90
+ };
91
+ exports.writeGzipTar = writeGzipTar;
@@ -0,0 +1,5 @@
1
+ import { SandboxImageInit } from "../../types/sandbox";
2
+ /** Derive default sandbox initialization from a Docker image config. */
3
+ export declare const deriveAutoImageInit: (config: Record<string, unknown>) => SandboxImageInit | undefined;
4
+ /** Layer explicit overrides over image-derived initialization defaults. */
5
+ export declare const mergeImageInit: (automatic: SandboxImageInit | undefined, explicit: SandboxImageInit | undefined) => SandboxImageInit | undefined;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeImageInit = exports.deriveAutoImageInit = void 0;
4
+ const IMAGE_INIT_ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
5
+ const RESERVED_IMAGE_INIT_ENV_KEYS = new Set([
6
+ "SANDBOX_ENABLED",
7
+ "SANDBOX_DEFAULT_WORKING_DIR",
8
+ "HOME",
9
+ "USER",
10
+ "LOGNAME",
11
+ "SHELL",
12
+ "PWD",
13
+ "DISPLAY",
14
+ ]);
15
+ const listStringConfig = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
16
+ const normalizeInitArgs = (values) => (values ?? []).filter((value) => value);
17
+ const deriveAutoImageEnv = (entries) => {
18
+ const env = new Map();
19
+ for (const entry of entries) {
20
+ const separator = entry.indexOf("=");
21
+ if (separator === -1) {
22
+ continue;
23
+ }
24
+ const key = entry.slice(0, separator).trim();
25
+ if (!key || !IMAGE_INIT_ENV_KEY_PATTERN.test(key) || RESERVED_IMAGE_INIT_ENV_KEYS.has(key)) {
26
+ continue;
27
+ }
28
+ env.set(key, entry.slice(separator + 1));
29
+ }
30
+ return Object.fromEntries(env);
31
+ };
32
+ const deriveAutoStartupArgs = (entrypoint, cmd) => (entrypoint.length > 0 ? [...entrypoint, ...cmd] : [...cmd]).filter((arg) => arg);
33
+ /** Derive default sandbox initialization from a Docker image config. */
34
+ const deriveAutoImageInit = (config) => {
35
+ const env = deriveAutoImageEnv(listStringConfig(config.Env));
36
+ const args = deriveAutoStartupArgs(listStringConfig(config.Entrypoint), listStringConfig(config.Cmd));
37
+ const workingDir = String(config.WorkingDir ?? "").trim();
38
+ if (Object.keys(env).length === 0 && args.length === 0 && !workingDir) {
39
+ return undefined;
40
+ }
41
+ const init = {};
42
+ if (Object.keys(env).length > 0) {
43
+ init.env = env;
44
+ }
45
+ if (args.length > 0) {
46
+ init.args = args;
47
+ }
48
+ if (workingDir) {
49
+ init.workingDir = workingDir;
50
+ }
51
+ return init;
52
+ };
53
+ exports.deriveAutoImageInit = deriveAutoImageInit;
54
+ /** Layer explicit overrides over image-derived initialization defaults. */
55
+ const mergeImageInit = (automatic, explicit) => {
56
+ if (automatic === undefined && explicit === undefined) {
57
+ return undefined;
58
+ }
59
+ const env = { ...(automatic?.env ?? {}), ...(explicit?.env ?? {}) };
60
+ let command = (automatic?.command ?? "").trim();
61
+ let args = normalizeInitArgs(automatic?.args);
62
+ let workingDir = (automatic?.workingDir ?? "").trim();
63
+ if (explicit !== undefined) {
64
+ const explicitWorkingDir = (explicit.workingDir ?? "").trim();
65
+ if (explicitWorkingDir) {
66
+ workingDir = explicitWorkingDir;
67
+ }
68
+ const explicitArgs = normalizeInitArgs(explicit.args);
69
+ if (explicitArgs.length > 0) {
70
+ args = explicitArgs;
71
+ command = "";
72
+ }
73
+ const explicitCommand = (explicit.command ?? "").trim();
74
+ if (explicitCommand) {
75
+ command = explicitCommand;
76
+ args = [];
77
+ }
78
+ }
79
+ if (Object.keys(env).length === 0 && !command && args.length === 0 && !workingDir) {
80
+ return undefined;
81
+ }
82
+ const merged = {};
83
+ if (Object.keys(env).length > 0) {
84
+ merged.env = env;
85
+ }
86
+ if (command) {
87
+ merged.command = command;
88
+ }
89
+ if (args.length > 0) {
90
+ merged.args = args;
91
+ }
92
+ if (workingDir) {
93
+ merged.workingDir = workingDir;
94
+ }
95
+ return merged;
96
+ };
97
+ exports.mergeImageInit = mergeImageInit;
@@ -0,0 +1,13 @@
1
+ export { CONTEXT_MANIFEST_INPUT_FORMAT, DOCKER_IMAGE_MANIFEST_INPUT_FORMAT, IMAGE_BUILD_INPUT_FORMAT, IMAGE_BUILD_SOURCE_PLATFORM, } from "./artifacts";
2
+ export type { DockerImageBuildArtifact } from "./artifacts";
3
+ export { DockerBuildContextChangedError, dockerBuildContextFingerprint, packageDockerBuildContextManifest, } from "./context";
4
+ export type { DockerBuildContextOptions, PackageDockerBuildContextOptions, PackagedDockerBuildContext, } from "./context";
5
+ export { DockerCommandError, dockerImageDigest, packageDockerImageManifest, prepareDockerImageManifestSource, } from "./docker-image";
6
+ export type { DockerImageManifestSource, PackagedDockerImage, PackageDockerImageManifestOptions, } from "./docker-image";
7
+ export { DockerIgnoreMatcher } from "./dockerignore";
8
+ export { analyzeDockerfileSources } from "./dockerfile-analysis";
9
+ export { deriveAutoImageInit, mergeImageInit } from "./image-init";
10
+ export { buildDockerImageFromDockerfile, makeTempDockerTag, removeDockerImage, } from "./local-docker";
11
+ export { completedImageId, imageBuildName, isTerminalImageBuildStatus, matchingImageBuild, } from "./resolution";
12
+ export type { ImageBuildNameOptions, ImageBuildSource } from "./resolution";
13
+ export { uploadImageBuildArtifact, uploadMissingImageBuildArtifacts } from "./upload";
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uploadMissingImageBuildArtifacts = exports.uploadImageBuildArtifact = exports.matchingImageBuild = exports.isTerminalImageBuildStatus = exports.imageBuildName = exports.completedImageId = exports.removeDockerImage = exports.makeTempDockerTag = exports.buildDockerImageFromDockerfile = exports.mergeImageInit = exports.deriveAutoImageInit = exports.analyzeDockerfileSources = exports.DockerIgnoreMatcher = exports.prepareDockerImageManifestSource = exports.packageDockerImageManifest = exports.dockerImageDigest = exports.DockerCommandError = exports.packageDockerBuildContextManifest = exports.dockerBuildContextFingerprint = exports.DockerBuildContextChangedError = exports.IMAGE_BUILD_SOURCE_PLATFORM = exports.IMAGE_BUILD_INPUT_FORMAT = exports.DOCKER_IMAGE_MANIFEST_INPUT_FORMAT = exports.CONTEXT_MANIFEST_INPUT_FORMAT = void 0;
4
+ var artifacts_1 = require("./artifacts");
5
+ Object.defineProperty(exports, "CONTEXT_MANIFEST_INPUT_FORMAT", { enumerable: true, get: function () { return artifacts_1.CONTEXT_MANIFEST_INPUT_FORMAT; } });
6
+ Object.defineProperty(exports, "DOCKER_IMAGE_MANIFEST_INPUT_FORMAT", { enumerable: true, get: function () { return artifacts_1.DOCKER_IMAGE_MANIFEST_INPUT_FORMAT; } });
7
+ Object.defineProperty(exports, "IMAGE_BUILD_INPUT_FORMAT", { enumerable: true, get: function () { return artifacts_1.IMAGE_BUILD_INPUT_FORMAT; } });
8
+ Object.defineProperty(exports, "IMAGE_BUILD_SOURCE_PLATFORM", { enumerable: true, get: function () { return artifacts_1.IMAGE_BUILD_SOURCE_PLATFORM; } });
9
+ var context_1 = require("./context");
10
+ Object.defineProperty(exports, "DockerBuildContextChangedError", { enumerable: true, get: function () { return context_1.DockerBuildContextChangedError; } });
11
+ Object.defineProperty(exports, "dockerBuildContextFingerprint", { enumerable: true, get: function () { return context_1.dockerBuildContextFingerprint; } });
12
+ Object.defineProperty(exports, "packageDockerBuildContextManifest", { enumerable: true, get: function () { return context_1.packageDockerBuildContextManifest; } });
13
+ var docker_image_1 = require("./docker-image");
14
+ Object.defineProperty(exports, "DockerCommandError", { enumerable: true, get: function () { return docker_image_1.DockerCommandError; } });
15
+ Object.defineProperty(exports, "dockerImageDigest", { enumerable: true, get: function () { return docker_image_1.dockerImageDigest; } });
16
+ Object.defineProperty(exports, "packageDockerImageManifest", { enumerable: true, get: function () { return docker_image_1.packageDockerImageManifest; } });
17
+ Object.defineProperty(exports, "prepareDockerImageManifestSource", { enumerable: true, get: function () { return docker_image_1.prepareDockerImageManifestSource; } });
18
+ var dockerignore_1 = require("./dockerignore");
19
+ Object.defineProperty(exports, "DockerIgnoreMatcher", { enumerable: true, get: function () { return dockerignore_1.DockerIgnoreMatcher; } });
20
+ var dockerfile_analysis_1 = require("./dockerfile-analysis");
21
+ Object.defineProperty(exports, "analyzeDockerfileSources", { enumerable: true, get: function () { return dockerfile_analysis_1.analyzeDockerfileSources; } });
22
+ var image_init_1 = require("./image-init");
23
+ Object.defineProperty(exports, "deriveAutoImageInit", { enumerable: true, get: function () { return image_init_1.deriveAutoImageInit; } });
24
+ Object.defineProperty(exports, "mergeImageInit", { enumerable: true, get: function () { return image_init_1.mergeImageInit; } });
25
+ var local_docker_1 = require("./local-docker");
26
+ Object.defineProperty(exports, "buildDockerImageFromDockerfile", { enumerable: true, get: function () { return local_docker_1.buildDockerImageFromDockerfile; } });
27
+ Object.defineProperty(exports, "makeTempDockerTag", { enumerable: true, get: function () { return local_docker_1.makeTempDockerTag; } });
28
+ Object.defineProperty(exports, "removeDockerImage", { enumerable: true, get: function () { return local_docker_1.removeDockerImage; } });
29
+ var resolution_1 = require("./resolution");
30
+ Object.defineProperty(exports, "completedImageId", { enumerable: true, get: function () { return resolution_1.completedImageId; } });
31
+ Object.defineProperty(exports, "imageBuildName", { enumerable: true, get: function () { return resolution_1.imageBuildName; } });
32
+ Object.defineProperty(exports, "isTerminalImageBuildStatus", { enumerable: true, get: function () { return resolution_1.isTerminalImageBuildStatus; } });
33
+ Object.defineProperty(exports, "matchingImageBuild", { enumerable: true, get: function () { return resolution_1.matchingImageBuild; } });
34
+ var upload_1 = require("./upload");
35
+ Object.defineProperty(exports, "uploadImageBuildArtifact", { enumerable: true, get: function () { return upload_1.uploadImageBuildArtifact; } });
36
+ Object.defineProperty(exports, "uploadMissingImageBuildArtifacts", { enumerable: true, get: function () { return upload_1.uploadMissingImageBuildArtifacts; } });
@@ -0,0 +1,11 @@
1
+ export declare const makeTempDockerTag: (prefix?: string) => string;
2
+ export declare const removeDockerImage: (image: string) => Promise<void>;
3
+ export interface LocalDockerBuildOptions {
4
+ contextPath: string;
5
+ dockerfile?: string;
6
+ tag: string;
7
+ platform?: string;
8
+ buildArgs?: Record<string, string>;
9
+ }
10
+ /** Build a Dockerfile locally with `docker buildx build --load`. */
11
+ export declare const buildDockerImageFromDockerfile: (options: LocalDockerBuildOptions) => Promise<void>;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.buildDockerImageFromDockerfile = exports.removeDockerImage = exports.makeTempDockerTag = void 0;
27
+ const crypto_1 = require("crypto");
28
+ const fs_1 = require("fs");
29
+ const path = __importStar(require("path"));
30
+ const artifacts_1 = require("./artifacts");
31
+ const docker_image_1 = require("./docker-image");
32
+ const makeTempDockerTag = (prefix = "hyperbrowser-sdk-build") => `${prefix}:${(0, crypto_1.randomUUID)().replace(/-/g, "")}`;
33
+ exports.makeTempDockerTag = makeTempDockerTag;
34
+ const removeDockerImage = async (image) => {
35
+ try {
36
+ await (0, docker_image_1.runDockerCommand)(["image", "rm", image]);
37
+ }
38
+ catch (error) {
39
+ if (!(error instanceof docker_image_1.DockerCommandError)) {
40
+ throw error;
41
+ }
42
+ }
43
+ };
44
+ exports.removeDockerImage = removeDockerImage;
45
+ /** Build a Dockerfile locally with `docker buildx build --load`. */
46
+ const buildDockerImageFromDockerfile = async (options) => {
47
+ const context = options.contextPath;
48
+ if (!(0, fs_1.existsSync)(context)) {
49
+ throw new Error(`Docker build context not found: ${context}`);
50
+ }
51
+ let dockerfilePath = options.dockerfile ?? "Dockerfile";
52
+ if (!path.isAbsolute(dockerfilePath)) {
53
+ dockerfilePath = path.join(context, dockerfilePath);
54
+ }
55
+ const args = [
56
+ "buildx",
57
+ "build",
58
+ "--platform",
59
+ options.platform ?? artifacts_1.IMAGE_BUILD_SOURCE_PLATFORM,
60
+ "-t",
61
+ options.tag,
62
+ "-f",
63
+ dockerfilePath,
64
+ "--load",
65
+ ];
66
+ for (const [key, value] of Object.entries(options.buildArgs ?? {})) {
67
+ args.push("--build-arg", `${key}=${value}`);
68
+ }
69
+ args.push(context);
70
+ await (0, docker_image_1.runDockerCommand)(args);
71
+ };
72
+ exports.buildDockerImageFromDockerfile = buildDockerImageFromDockerfile;
@@ -0,0 +1,27 @@
1
+ /** Shared identity and validation for opt-in image resolution. */
2
+ import { HyperbrowserError } from "../../error";
3
+ import { SandboxImageBuild, SandboxImageInit } from "../../types/sandbox";
4
+ export type ImageBuildSource = "dockerfile" | "prebuilt";
5
+ export interface ImageBuildNameOptions {
6
+ source: ImageBuildSource;
7
+ fingerprint: string;
8
+ namePrefix?: string;
9
+ platform?: string;
10
+ imageInit?: SandboxImageInit;
11
+ imageConfigUser?: string;
12
+ }
13
+ /** Drop undefined/null fields so identity matches `exclude_none` serialization. */
14
+ export declare const normalizeImageInit: (imageInit: SandboxImageInit | undefined) => SandboxImageInit | undefined;
15
+ /**
16
+ * Name an immutable input identity without packaging or uploading it.
17
+ *
18
+ * Fingerprints identify effective Dockerfile contexts or platform-specific
19
+ * Docker image digests. Builder resources and wait policies do not change the
20
+ * image contents and are excluded. Mutable external inputs (base tags, network
21
+ * downloads) require an explicit `forceBuild` to request another build.
22
+ */
23
+ export declare const imageBuildName: (options: ImageBuildNameOptions) => string;
24
+ /** Return the compatible in-progress build described by a 409 conflict, if any. */
25
+ export declare const matchingImageBuild: (error: HyperbrowserError, imageName: string, inputFormat: string) => SandboxImageBuild | null;
26
+ export declare const completedImageId: (build: SandboxImageBuild) => string | undefined;
27
+ export declare const isTerminalImageBuildStatus: (status: SandboxImageBuild["status"]) => boolean;
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ /** Shared identity and validation for opt-in image resolution. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.isTerminalImageBuildStatus = exports.completedImageId = exports.matchingImageBuild = exports.imageBuildName = exports.normalizeImageInit = void 0;
5
+ const blake2b_1 = require("./blake2b");
6
+ const common_1 = require("./common");
7
+ const normalizeInitEnv = (env) => env === undefined ? undefined : env;
8
+ /** Drop undefined/null fields so identity matches `exclude_none` serialization. */
9
+ const normalizeImageInit = (imageInit) => {
10
+ if (imageInit === undefined) {
11
+ return undefined;
12
+ }
13
+ const normalized = {};
14
+ const env = normalizeInitEnv(imageInit.env);
15
+ if (env !== undefined && env !== null) {
16
+ normalized.env = env;
17
+ }
18
+ if (imageInit.command !== undefined && imageInit.command !== null) {
19
+ normalized.command = imageInit.command;
20
+ }
21
+ if (imageInit.args !== undefined && imageInit.args !== null) {
22
+ normalized.args = imageInit.args;
23
+ }
24
+ if (imageInit.workingDir !== undefined && imageInit.workingDir !== null) {
25
+ normalized.workingDir = imageInit.workingDir;
26
+ }
27
+ return normalized;
28
+ };
29
+ exports.normalizeImageInit = normalizeImageInit;
30
+ /**
31
+ * Name an immutable input identity without packaging or uploading it.
32
+ *
33
+ * Fingerprints identify effective Dockerfile contexts or platform-specific
34
+ * Docker image digests. Builder resources and wait policies do not change the
35
+ * image contents and are excluded. Mutable external inputs (base tags, network
36
+ * downloads) require an explicit `forceBuild` to request another build.
37
+ */
38
+ const imageBuildName = (options) => {
39
+ const platform = (options.platform ?? "linux/amd64").trim().toLowerCase();
40
+ const namePrefix = options.namePrefix ?? "hb";
41
+ if (!/^[a-z0-9]+\/[a-z0-9]+(?:\/[a-z0-9]+)?$/.test(platform)) {
42
+ throw new Error("platform must be an OCI platform such as 'linux/amd64'");
43
+ }
44
+ if (!/^[A-Za-z0-9_-]{1,21}$/.test(namePrefix)) {
45
+ throw new Error("imageNamePrefix must be 1-21 letters, digits, '_' or '-'");
46
+ }
47
+ let payload;
48
+ if (options.source === "prebuilt") {
49
+ const fingerprint = options.fingerprint.toLowerCase();
50
+ if (!/^sha256:[0-9a-f]{64}$/.test(fingerprint)) {
51
+ throw new Error("expectedImageDigest must be a sha256: Docker digest");
52
+ }
53
+ payload = `docker_image\0${fingerprint}\0platform\0${platform}`;
54
+ }
55
+ else if (options.source === "dockerfile") {
56
+ if (!/^[0-9a-f]{64}$/.test(options.fingerprint)) {
57
+ throw new Error("expectedContextFingerprint must be a SHA-256 hex digest");
58
+ }
59
+ payload = `dockerfile-context-v3\0${options.fingerprint}\0platform\0${platform}`;
60
+ }
61
+ else {
62
+ throw new Error("source must be 'dockerfile' or 'prebuilt'");
63
+ }
64
+ const identityOptions = {};
65
+ const initialization = (0, exports.normalizeImageInit)(options.imageInit);
66
+ if (initialization !== undefined && Object.keys(initialization).length > 0) {
67
+ identityOptions.imageInit = initialization;
68
+ }
69
+ if (options.imageConfigUser !== undefined) {
70
+ identityOptions.imageConfigUser = options.imageConfigUser.trim();
71
+ }
72
+ if (Object.keys(identityOptions).length > 0) {
73
+ payload += "\0options\0" + (0, common_1.compactJson)(identityOptions, { sortKeys: true });
74
+ }
75
+ const digest = (0, blake2b_1.blake2b)(Buffer.from(payload, "utf8"), 8).toString("hex");
76
+ const name = `${namePrefix}__${options.source}__${digest}__${platform.replace(/\//g, "-")}`;
77
+ if (name.length > 64) {
78
+ throw new Error("imageNamePrefix and platform produce a name longer than 64 characters");
79
+ }
80
+ return name;
81
+ };
82
+ exports.imageBuildName = imageBuildName;
83
+ const IMAGE_BUILD_STATUSES = new Set([
84
+ "awaiting_upload",
85
+ "upload_verified",
86
+ "dispatching",
87
+ "building",
88
+ "verifying",
89
+ "completed",
90
+ "failed",
91
+ "canceled",
92
+ ]);
93
+ /** Return the compatible in-progress build described by a 409 conflict, if any. */
94
+ const matchingImageBuild = (error, imageName, inputFormat) => {
95
+ if (error.statusCode !== 409 || error.code !== "image_build_in_progress") {
96
+ return null;
97
+ }
98
+ if (!(0, common_1.isRecord)(error.details)) {
99
+ return null;
100
+ }
101
+ const data = error.details.build;
102
+ if (!(0, common_1.isRecord)(data)) {
103
+ return null;
104
+ }
105
+ const metadata = data.metadata;
106
+ if (!(0, common_1.isRecord)(metadata) || metadata.inputFormat !== inputFormat) {
107
+ return null;
108
+ }
109
+ if ((metadata.sourcePlatform ?? "linux/amd64") !== "linux/amd64") {
110
+ return null;
111
+ }
112
+ if (typeof data.id !== "string" ||
113
+ !data.id ||
114
+ typeof data.imageName !== "string" ||
115
+ typeof data.status !== "string" ||
116
+ !IMAGE_BUILD_STATUSES.has(data.status)) {
117
+ return null;
118
+ }
119
+ const build = data;
120
+ if (build.imageName !== imageName) {
121
+ return null;
122
+ }
123
+ return build;
124
+ };
125
+ exports.matchingImageBuild = matchingImageBuild;
126
+ const completedImageId = (build) => {
127
+ if (build.status !== "completed") {
128
+ return undefined;
129
+ }
130
+ if (!build.imageId) {
131
+ throw new Error("Completed image build did not return an image ID");
132
+ }
133
+ return build.imageId;
134
+ };
135
+ exports.completedImageId = completedImageId;
136
+ const isTerminalImageBuildStatus = (status) => status === "completed" || status === "failed" || status === "canceled";
137
+ exports.isTerminalImageBuildStatus = isTerminalImageBuildStatus;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Minimal tar support: a PAX-format writer that mirrors CPython's `tarfile`
3
+ * output for the normalized entries used in build contexts, and a streaming
4
+ * reader for `docker image save` archives.
5
+ */
6
+ import type { Readable } from "stream";
7
+ export type TarEntryType = "file" | "directory" | "symlink";
8
+ export interface TarEntryInfo {
9
+ /** Archive path; directories carry a trailing slash. */
10
+ name: string;
11
+ type: TarEntryType;
12
+ mode: number;
13
+ size: number;
14
+ linkname: string;
15
+ }
16
+ export declare const createTarEntryHeader: (info: TarEntryInfo) => Buffer;
17
+ export type TarSink = (chunk: Buffer) => Promise<void>;
18
+ /** Sequential PAX tar writer; `close` writes the end-of-archive blocks. */
19
+ export declare class PaxTarWriter {
20
+ private readonly sink;
21
+ private written;
22
+ constructor(sink: TarSink);
23
+ private emit;
24
+ addEntry(info: TarEntryInfo, content?: AsyncIterable<Buffer>): Promise<void>;
25
+ close(): Promise<void>;
26
+ }
27
+ export interface TarStreamEntry {
28
+ name: string;
29
+ size: number;
30
+ typeflag: string;
31
+ mode: number;
32
+ linkname: string;
33
+ isFile: boolean;
34
+ /** Stream the entry contents. Must be consumed before advancing. */
35
+ content(): AsyncGenerator<Buffer>;
36
+ }
37
+ /**
38
+ * Iterate over tar entries from a stream. Callers must fully consume an
39
+ * entry's content before advancing; unread content is skipped.
40
+ */
41
+ export declare function readTarEntries(source: Readable): AsyncGenerator<TarStreamEntry>;