@cloudflare/sandbox 0.13.0-next.651.1 → 0.13.0-next.681.1
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/Dockerfile +6 -11
- package/README.md +2 -2
- package/dist/bridge/index.js +68 -49
- package/dist/bridge/index.js.map +1 -1
- package/dist/codes-y-U76vnQ.d.ts +79 -0
- package/dist/codes-y-U76vnQ.d.ts.map +1 -0
- package/dist/contexts-DY1LHU1v.d.ts +327 -0
- package/dist/contexts-DY1LHU1v.d.ts.map +1 -0
- package/dist/{dist-DF8sudAg.js → dist-BStBkGIC.js} +33 -93
- package/dist/dist-BStBkGIC.js.map +1 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.js +4 -0
- package/dist/errors-ewgSNicb.js +741 -0
- package/dist/errors-ewgSNicb.js.map +1 -0
- package/dist/{errors-BG6NZiPD.js → errors-k3B8orjH.js} +41 -4
- package/dist/errors-k3B8orjH.js.map +1 -0
- package/dist/extensions/index.d.ts +2 -74
- package/dist/extensions/index.js +3 -152
- package/dist/extensions-CepYdzro.js +191 -0
- package/dist/extensions-CepYdzro.js.map +1 -0
- package/dist/git/index.d.ts +74 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +345 -0
- package/dist/git/index.js.map +1 -0
- package/dist/{rpc-types-PBUY-xXM.d.ts → index-B7QgIs0N.d.ts} +571 -409
- package/dist/index-B7QgIs0N.d.ts.map +1 -0
- package/dist/index-Dy6u9r60.d.ts +390 -0
- package/dist/index-Dy6u9r60.d.ts.map +1 -0
- package/dist/index.d.ts +27 -190
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -43
- package/dist/index.js.map +1 -1
- package/dist/interpreter/index.d.ts +309 -0
- package/dist/interpreter/index.d.ts.map +1 -0
- package/dist/interpreter/index.js +264 -0
- package/dist/interpreter/index.js.map +1 -0
- package/dist/openai/index.d.ts +3 -2
- package/dist/openai/index.d.ts.map +1 -1
- package/dist/openai/index.js +2 -2
- package/dist/openai/index.js.map +1 -1
- package/dist/opencode/index.d.ts +137 -161
- package/dist/opencode/index.d.ts.map +1 -1
- package/dist/opencode/index.js +269 -159
- package/dist/opencode/index.js.map +1 -1
- package/dist/{sandbox-D_MMqExx.d.ts → sandbox-DHNO89IF.d.ts} +145 -394
- package/dist/sandbox-DHNO89IF.d.ts.map +1 -0
- package/dist/{sandbox-BgwMBQ7S.js → sandbox-boKWPIcd.js} +8212 -7268
- package/dist/sandbox-boKWPIcd.js.map +1 -0
- package/dist/xterm/index.d.ts +6 -6
- package/dist/xterm/index.d.ts.map +1 -1
- package/dist/xterm/index.js +6 -6
- package/dist/xterm/index.js.map +1 -1
- package/package.json +26 -3
- package/dist/contexts-BS0Bs6IU.d.ts +0 -238
- package/dist/contexts-BS0Bs6IU.d.ts.map +0 -1
- package/dist/dist-DF8sudAg.js.map +0 -1
- package/dist/errors-BG6NZiPD.js.map +0 -1
- package/dist/extensions/index.d.ts.map +0 -1
- package/dist/extensions/index.js.map +0 -1
- package/dist/rpc-types-PBUY-xXM.d.ts.map +0 -1
- package/dist/sandbox-BgwMBQ7S.js.map +0 -1
- package/dist/sandbox-D_MMqExx.d.ts.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions-CepYdzro.js","names":["#sandbox","#pkg","#connect","#requirePackage","#hashOnce","#hashPromise"],"sources":["../src/extensions/index.ts"],"sourcesContent":["/**\n * Unified extension surface for the Cloudflare Sandbox SDK.\n *\n * Every extension \\u2014 whether it just drives existing control sub-APIs or\n * ships its own container sidecar \\u2014 shares one shape: a class extending\n * {@link SandboxExtension}, captured lazily via a `withX(this)` factory.\n *\n * - No sidecar? Extend {@link SandboxExtension} and use `this.exec()` or\n * `this.client.<subApi>` (`commands`, `files`, `interpreter`, \\u2026). Don't pass\n * a package.\n * - Need a container sidecar? Pass an {@link ExtensionPackage} to `super()`.\n * Then call {@link SandboxExtension.sidecar} to obtain the sidecar's typed\n * capnweb remote main. Calls on that stub stream through capnweb \\u2014 callback\n * parameters round-trip across both the DO\\u2192container and container\\u2192sidecar\n * hops.\n *\n * `ExtensionPackage` carries tarball bytes produced by an extension build.\n * The SDK sends those bytes only when the container has not provisioned the\n * package hash yet.\n */\n\nimport { RpcTarget } from 'cloudflare:workers';\nimport type {\n ExtensionHealth,\n ExtensionPackage,\n SandboxAPI,\n SandboxExecOptions,\n SandboxProcessPromise\n} from '@repo/shared';\nimport { EXTENSION_TARBALL_REQUIRED } from '@repo/shared';\n\n// Re-export the wire types so consumers can author extensions from this\n// subpath without reaching into `@repo/shared` directly.\nexport type { ExtensionHealth, ExtensionPackage } from '@repo/shared';\n\nexport interface HTTPAuthHostConfig {\n token: string;\n username?: string;\n type?: 'basic' | 'bearer';\n}\n\nexport interface HTTPAuthInterceptorParams {\n hosts: Record<string, HTTPAuthHostConfig>;\n}\n\n/**\n * The slice of the Sandbox an extension captures: its control `client`, unified\n * `exec()` surface, and sandbox-level environment variables. Narrow on purpose\n * \\u2014 an extension never holds the whole instance.\n *\n * `envVars` mirrors what the Sandbox applies to sessionless execution, so an\n * extension that drives commands through `client` directly can still honour\n * sandbox-level env (tokens, proxy settings) without an explicit session.\n */\nexport type SandboxLike = {\n readonly client: SandboxAPI;\n readonly exec?: (\n command: string | string[],\n options?: SandboxExecOptions\n ) => SandboxProcessPromise;\n readonly envVars?: Record<string, string>;\n registerGitAuthInterceptor?: (\n params: HTTPAuthInterceptorParams\n ) => Promise<void>;\n};\n\n/**\n * The one base class for *every* extension.\n *\n * - SDK-only: just drives existing sub-APIs.\n * - Sidecar: pass an {@link ExtensionPackage} to `super(sandbox, pkg)` and\n * call `this.sidecar<T>()` to get a typed capnweb stub of the sidecar.\n *\n * The sidecar accessor throws a clear error if no package was supplied, so an\n * extension only \"becomes\" a sidecar extension when it opts in.\n *\n * ```ts\n * // SDK-only\n * class Git extends SandboxExtension {\n * constructor(s: SandboxLike) { super(s); }\n * async status(sid: string) {\n * return this.exec('git status', { sessionId: sid }).output();\n * }\n * }\n *\n * // Sidecar\n * import sidecarTarballBytes from './sidecar-package.tgz';\n * import type { MyAPI } from './shared';\n *\n * class MyExt extends SandboxExtension {\n * constructor(s: SandboxLike) { super(s, { tarball: new Uint8Array(sidecarTarballBytes) }); }\n * async run(input: string): Promise<string> {\n * const api = await this.sidecar<MyAPI>();\n * return api.run(input);\n * }\n * }\n * ```\n *\n * RPC-safety: the sandbox lives in `#sandbox` and is reached only through the\n * `protected` `client` getter (a prototype accessor, not an own property),\n * so it is never serialised across RPC. Only the public methods you add form\n * the extension's RPC surface.\n */\nexport abstract class SandboxExtension extends RpcTarget {\n readonly #sandbox: SandboxLike;\n readonly #pkg: ExtensionPackage | undefined;\n #hashPromise: Promise<string> | undefined;\n\n protected constructor(sandbox: SandboxLike, pkg?: ExtensionPackage) {\n super();\n this.#sandbox = sandbox;\n this.#pkg = pkg;\n }\n\n /** The container control client. Use inside your own methods, lazily. */\n protected get client(): SandboxAPI {\n return this.#sandbox.client;\n }\n\n /** Unified process-handle exec surface from the owning Sandbox. */\n protected exec(\n command: string | string[],\n options?: SandboxExecOptions\n ): SandboxProcessPromise {\n const exec = this.#sandbox.exec;\n if (!exec) {\n throw new Error(\n 'Sandbox extension requires the unified exec surface from the owning Sandbox'\n );\n }\n return exec.call(this.#sandbox, command, options);\n }\n\n /**\n * Sandbox-level environment variables. Apply these to sessionless execution\n * so commands driven straight through `client` still inherit sandbox env\n * (tokens, proxy settings) the way the Sandbox's own sessionless path does.\n */\n protected get envVars(): Record<string, string> {\n return this.#sandbox.envVars ?? {};\n }\n\n protected get httpAuthInterceptor():\n | ((params: HTTPAuthInterceptorParams) => Promise<void>)\n | undefined {\n const register = this.#sandbox.registerGitAuthInterceptor;\n return register\n ? (params) => register.call(this.#sandbox, params)\n : undefined;\n }\n\n /**\n * Return the sidecar's capnweb remote main, provisioning + spawning on\n * demand. `T` is the typed interface the sidecar exposes (its\n * `SandboxSidecar` subclass shape). Each call reconnects through the host\n * so a crashed sidecar can be restarted transparently on the next use.\n *\n * Streaming is just a method that takes a callback parameter: capnweb\n * stubs the callback and routes invocations back through the SDK\\u2192container\n * \\u2192sidecar hops.\n */\n protected sidecar<T extends object>(): Promise<T> {\n // Wrap the synchronous `#requirePackage` check in an async closure so a\n // missing-package error surfaces as a rejected promise, not a sync throw\n // -- callers always treat `sidecar()` as awaitable.\n return (async () => this.#connect(this.#requirePackage()))() as Promise<T>;\n }\n\n /** Health snapshot for this extension's sidecar. */\n protected async sidecarHealth(): Promise<ExtensionHealth> {\n const hash = await this.#hashOnce();\n return this.#sandbox.client.extensions.health(hash);\n }\n\n /**\n * Stop this extension's sidecar. The next `sidecar()` call will respawn on\n * demand.\n */\n protected async stopSidecar(): Promise<void> {\n const hash = await this.#hashOnce();\n await this.#sandbox.client.extensions.stop(hash);\n }\n\n // --- internals -----------------------------------------------------------\n\n async #connect(pkg: ExtensionPackage): Promise<object> {\n const packageHash = await this.#hashOnce();\n const api = this.#sandbox.client.extensions;\n\n // Hash-first: ask the host whether this process already has the package.\n // If it doesn't, retry once with the tarball bytes attached.\n try {\n return (await api.connect({\n packageHash,\n bin: pkg.bin,\n readinessTimeoutMs: pkg.readinessTimeoutMs,\n allowInstallScripts: pkg.allowInstallScripts\n })) as object;\n } catch (error) {\n if (!isTarballRequiredError(error)) throw error;\n try {\n return (await api.connect({\n packageHash,\n tarball: pkg.tarball,\n bin: pkg.bin,\n readinessTimeoutMs: pkg.readinessTimeoutMs,\n allowInstallScripts: pkg.allowInstallScripts\n })) as object;\n } catch (retryError) {\n throw createSidecarProvisioningError(packageHash, retryError);\n }\n }\n }\n\n #hashOnce(): Promise<string> {\n const pkg = this.#requirePackage();\n this.#hashPromise ??= sha256Hex(pkg.tarball);\n return this.#hashPromise;\n }\n\n #requirePackage(): ExtensionPackage {\n if (!this.#pkg) {\n throw new Error(\n 'This extension has no sidecar package. Pass one to `super(sandbox, pkg)` to use sidecar/sidecarHealth/stopSidecar.'\n );\n }\n return this.#pkg;\n }\n}\n\n/**\n * Recognise the host's `ExtensionTarballRequired` error across the capnweb\n * boundary. There are two cases:\n *\n * 1. capnweb preserves `Error.name`, so the primary wire-safe contract is the\n * string constant `EXTENSION_TARBALL_REQUIRED`.\n * 2. capnweb sometimes re-wraps the error as an `RPCTransportError`, discarding\n * `name` but preserving the message text. We fall back to matching the\n * message so the tarball retry still fires in that case.\n */\nfunction isTarballRequiredError(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) return false;\n const candidate = error as {\n name?: unknown;\n message?: unknown;\n code?: unknown;\n };\n if (candidate.name === EXTENSION_TARBALL_REQUIRED) return true;\n if (candidate.code === EXTENSION_TARBALL_REQUIRED) return true;\n if (typeof candidate.message !== 'string') return false;\n return (\n candidate.message.includes(EXTENSION_TARBALL_REQUIRED) ||\n /Extension package '[0-9a-f]{64}' is not provisioned/.test(\n candidate.message\n )\n );\n}\n\nfunction createSidecarProvisioningError(\n packageHash: string,\n cause: unknown\n): Error {\n const causeMessage =\n cause instanceof Error\n ? cause.message\n : typeof cause === 'string'\n ? cause\n : String(cause);\n return new Error(\n `Failed to provision sandbox sidecar package '${packageHash}'. The sidecar tarball was sent to the container, but the container could not install or start it. Check that the extension build generated a valid npm-style .tgz, that the .tgz is included in the Worker bundle, and that the sidecar package declares a valid package.json bin/sandboxExtension entry. Cause: ${causeMessage}`,\n { cause }\n );\n}\n\n/**\n * Hex sha256 of a tarball via Web Crypto, which is available in workerd and\n * every modern runtime. Avoids a Node-only path so this module stays\n * Worker-bundle-safe.\n */\nasync function sha256Hex(bytes: Uint8Array): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', bytes as BufferSource);\n const view = new Uint8Array(digest);\n let hex = '';\n for (const byte of view) {\n hex += byte.toString(16).padStart(2, '0');\n }\n return hex;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuGA,IAAsB,mBAAtB,cAA+C,UAAU;CACvD,CAASA;CACT,CAASC;CACT;CAEA,AAAU,YAAY,SAAsB,KAAwB;AAClE,SAAO;AACP,QAAKD,UAAW;AAChB,QAAKC,MAAO;;;CAId,IAAc,SAAqB;AACjC,SAAO,MAAKD,QAAS;;;CAIvB,AAAU,KACR,SACA,SACuB;EACvB,MAAM,OAAO,MAAKA,QAAS;AAC3B,MAAI,CAAC,KACH,OAAM,IAAI,MACR,8EACD;AAEH,SAAO,KAAK,KAAK,MAAKA,SAAU,SAAS,QAAQ;;;;;;;CAQnD,IAAc,UAAkC;AAC9C,SAAO,MAAKA,QAAS,WAAW,EAAE;;CAGpC,IAAc,sBAEA;EACZ,MAAM,WAAW,MAAKA,QAAS;AAC/B,SAAO,YACF,WAAW,SAAS,KAAK,MAAKA,SAAU,OAAO,GAChD;;;;;;;;;;;;CAaN,AAAU,UAAwC;AAIhD,UAAQ,YAAY,MAAKE,QAAS,MAAKC,gBAAiB,CAAC,GAAG;;;CAI9D,MAAgB,gBAA0C;EACxD,MAAM,OAAO,MAAM,MAAKC,UAAW;AACnC,SAAO,MAAKJ,QAAS,OAAO,WAAW,OAAO,KAAK;;;;;;CAOrD,MAAgB,cAA6B;EAC3C,MAAM,OAAO,MAAM,MAAKI,UAAW;AACnC,QAAM,MAAKJ,QAAS,OAAO,WAAW,KAAK,KAAK;;CAKlD,OAAME,QAAS,KAAwC;EACrD,MAAM,cAAc,MAAM,MAAKE,UAAW;EAC1C,MAAM,MAAM,MAAKJ,QAAS,OAAO;AAIjC,MAAI;AACF,UAAQ,MAAM,IAAI,QAAQ;IACxB;IACA,KAAK,IAAI;IACT,oBAAoB,IAAI;IACxB,qBAAqB,IAAI;IAC1B,CAAC;WACK,OAAO;AACd,OAAI,CAAC,uBAAuB,MAAM,CAAE,OAAM;AAC1C,OAAI;AACF,WAAQ,MAAM,IAAI,QAAQ;KACxB;KACA,SAAS,IAAI;KACb,KAAK,IAAI;KACT,oBAAoB,IAAI;KACxB,qBAAqB,IAAI;KAC1B,CAAC;YACK,YAAY;AACnB,UAAM,+BAA+B,aAAa,WAAW;;;;CAKnE,YAA6B;EAC3B,MAAM,MAAM,MAAKG,gBAAiB;AAClC,QAAKE,gBAAiB,UAAU,IAAI,QAAQ;AAC5C,SAAO,MAAKA;;CAGd,kBAAoC;AAClC,MAAI,CAAC,MAAKJ,IACR,OAAM,IAAI,MACR,qHACD;AAEH,SAAO,MAAKA;;;;;;;;;;;;;AAchB,SAAS,uBAAuB,OAAyB;AACvD,KAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;CACxD,MAAM,YAAY;AAKlB,KAAI,UAAU,SAAS,2BAA4B,QAAO;AAC1D,KAAI,UAAU,SAAS,2BAA4B,QAAO;AAC1D,KAAI,OAAO,UAAU,YAAY,SAAU,QAAO;AAClD,QACE,UAAU,QAAQ,SAAS,2BAA2B,IACtD,sDAAsD,KACpD,UAAU,QACX;;AAIL,SAAS,+BACP,aACA,OACO;CACP,MAAM,eACJ,iBAAiB,QACb,MAAM,UACN,OAAO,UAAU,WACf,QACA,OAAO,MAAM;AACrB,QAAO,IAAI,MACT,gDAAgD,YAAY,oTAAoT,gBAChX,EAAE,OAAO,CACV;;;;;;;AAQH,eAAe,UAAU,OAAoC;CAC3D,MAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,MAAsB;CAC3E,MAAM,OAAO,IAAI,WAAW,OAAO;CACnC,IAAI,MAAM;AACV,MAAK,MAAM,QAAQ,KACjB,QAAO,KAAK,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;AAE3C,QAAO"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { i as SandboxLike, r as SandboxExtension, t as HTTPAuthHostConfig } from "../index-B7QgIs0N.js";
|
|
2
|
+
|
|
3
|
+
//#region ../../extensions/git/src/types.d.ts
|
|
4
|
+
|
|
5
|
+
/** Result of a git clone (`checkout`) operation. */
|
|
6
|
+
interface GitCheckoutResult {
|
|
7
|
+
success: boolean;
|
|
8
|
+
repoUrl: string;
|
|
9
|
+
branch: string;
|
|
10
|
+
targetDir: string;
|
|
11
|
+
timestamp: string;
|
|
12
|
+
exitCode?: number;
|
|
13
|
+
}
|
|
14
|
+
type GitHostAuth = HTTPAuthHostConfig;
|
|
15
|
+
interface GitAuthConfig {
|
|
16
|
+
github?: GitHostAuth;
|
|
17
|
+
gitlab?: GitHostAuth;
|
|
18
|
+
bitbucket?: GitHostAuth;
|
|
19
|
+
hosts?: Record<string, GitHostAuth>;
|
|
20
|
+
}
|
|
21
|
+
interface GitExtensionOptions {
|
|
22
|
+
auth?: GitAuthConfig;
|
|
23
|
+
}
|
|
24
|
+
/** Options shared by every git extension method. */
|
|
25
|
+
interface GitSessionOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Session to run the git command in. Omit to run sessionless (the command
|
|
28
|
+
* runs in a fresh, non-persistent shell). Sessionless commands still inherit
|
|
29
|
+
* the sandbox-level environment variables (e.g. tokens, proxy settings), so
|
|
30
|
+
* auth and egress configured on the sandbox keep working. Pass a session id
|
|
31
|
+
* to run inside an existing session so its working directory and environment
|
|
32
|
+
* carry over instead.
|
|
33
|
+
*/
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Options for cloning a repository. */
|
|
37
|
+
interface GitCheckoutOptions extends GitSessionOptions {
|
|
38
|
+
/** Branch (or tag) to check out after cloning. */
|
|
39
|
+
branch?: string;
|
|
40
|
+
/** Directory to clone into. Defaults to `/workspace/<repoName>`. */
|
|
41
|
+
targetDir?: string;
|
|
42
|
+
/** Clone depth for shallow clones (e.g. 1 for the latest commit only). */
|
|
43
|
+
depth?: number;
|
|
44
|
+
/** Maximum wall-clock time for the git clone subprocess, in milliseconds. */
|
|
45
|
+
cloneTimeoutMs?: number;
|
|
46
|
+
auth?: GitAuthConfig | false;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region ../../extensions/git/src/index.d.ts
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The git extension. Drives `git` in the container through the commands
|
|
53
|
+
* control sub-API and translates failures into the SDK's typed git errors.
|
|
54
|
+
*/
|
|
55
|
+
declare class Git extends SandboxExtension {
|
|
56
|
+
#private;
|
|
57
|
+
constructor(sandbox: SandboxLike, options?: GitExtensionOptions);
|
|
58
|
+
/**
|
|
59
|
+
* Clone a repository. Returns the resolved target directory and the branch
|
|
60
|
+
* actually checked out (queried from git, not assumed).
|
|
61
|
+
*/
|
|
62
|
+
checkout(repoUrl: string, options?: GitCheckoutOptions): Promise<GitCheckoutResult>;
|
|
63
|
+
/** Check out an existing branch in a cloned repository. */
|
|
64
|
+
checkoutBranch(repoPath: string, branch: string, options?: GitSessionOptions): Promise<void>;
|
|
65
|
+
/** Return the current branch of a cloned repository. */
|
|
66
|
+
getCurrentBranch(repoPath: string, options?: GitSessionOptions): Promise<string>;
|
|
67
|
+
/** List local and remote branches of a cloned repository. */
|
|
68
|
+
listBranches(repoPath: string, options?: GitSessionOptions): Promise<string[]>;
|
|
69
|
+
}
|
|
70
|
+
/** Factory — the consumer-facing API. */
|
|
71
|
+
declare function withGit(sandbox: SandboxLike, options?: GitExtensionOptions): Git;
|
|
72
|
+
//#endregion
|
|
73
|
+
export { Git, type GitAuthConfig, type GitCheckoutOptions, type GitCheckoutResult, type GitExtensionOptions, type GitHostAuth, type GitSessionOptions, withGit as default, withGit };
|
|
74
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../../extensions/git/src/types.ts","../../../../extensions/git/src/index.ts"],"sourcesContent":[],"mappings":";;;;AAmBA;AAEiB,UAXA,iBAAA,CAWa;EACnB,OAAA,EAAA,OAAA;EACA,OAAA,EAAA,MAAA;EACG,MAAA,EAAA,MAAA;EACW,SAAA,EAAA,MAAA;EAAf,SAAA,EAAA,MAAA;EAAM,QAAA,CAAA,EAAA,MAAA;AAKhB;AAKiB,KAhBL,WAAA,GAAc,kBAgBQ;AAajB,UA3BA,aAAA,CA2BmB;WA1BzB;WACA;cACG;EC4DD,KAAA,CAAI,ED3DP,MC2DO,CAAA,MAAA,ED3DQ,WC2DR,CAAA;;AAcJ,UDpEI,mBAAA,CCoEJ;EACA,IAAA,CAAA,EDpEJ,aCoEI;;;AAuGR,UDvKY,iBAAA,CCuKZ;EAqDQ;;;;;;AA0Lb;;EAEW,SAAA,CAAA,EAAA,MAAA;;;UD3YM,kBAAA,SAA2B;;;;;;;;;SASnC;;;;;;ACgYT;;;AAGG,cAxWU,GAAA,SAAY,gBAAA,CAwWtB;EAAG,CAAA,OAAA;uBArWiB,uBAAsB;;;;;sCAWhC,qBACR,QAAQ;;6DAsGA,oBACR;;+CAqDQ,oBACR;;2CAoCQ,oBACR;;;iBAoJW,OAAA,UACL,uBACA,sBACR"}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { d as DEFAULT_GIT_CLONE_TIMEOUT_MS, f as extractRepoName, p as redactCommand } from "../dist-BStBkGIC.js";
|
|
2
|
+
import { i as ErrorCode, r as getHttpStatus } from "../errors-k3B8orjH.js";
|
|
3
|
+
import { t as createErrorFromResponse } from "../errors-ewgSNicb.js";
|
|
4
|
+
import { t as SandboxExtension } from "../extensions-CepYdzro.js";
|
|
5
|
+
|
|
6
|
+
//#region ../../extensions/git/src/manager.ts
|
|
7
|
+
/**
|
|
8
|
+
* Pure git business logic for the git extension.
|
|
9
|
+
*
|
|
10
|
+
* No I/O — just command-argument building, output parsing, validation, and
|
|
11
|
+
* error-code classification. Ported from the former container-side
|
|
12
|
+
* `GitManager` so the extension can drive everything through `exec`.
|
|
13
|
+
*/
|
|
14
|
+
const GIT_CLONE_KILL_GRACE_SECONDS = 5;
|
|
15
|
+
/** Format a millisecond timeout as a `timeout(1)`-friendly seconds string. */
|
|
16
|
+
function gitCloneTimeoutSeconds(timeoutMs) {
|
|
17
|
+
const timeoutSeconds = timeoutMs / 1e3;
|
|
18
|
+
return Number.isInteger(timeoutSeconds) ? String(timeoutSeconds) : timeoutSeconds.toFixed(3).replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Generate the default target directory for a clone: `/workspace/<repoName>`.
|
|
22
|
+
*/
|
|
23
|
+
function generateTargetDirectory(repoUrl) {
|
|
24
|
+
return `/workspace/${extractRepoName(repoUrl)}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build git clone command arguments.
|
|
28
|
+
*
|
|
29
|
+
* Wraps the command with `timeout -k 5 <seconds>` to enforce a wall-clock
|
|
30
|
+
* limit, and configures git's own stalled-transfer detection via
|
|
31
|
+
* `http.lowSpeedLimit` and `http.lowSpeedTime`.
|
|
32
|
+
*/
|
|
33
|
+
function buildCloneArgs(repoUrl, targetDir, options = {}) {
|
|
34
|
+
const timeoutSeconds = gitCloneTimeoutSeconds(options.cloneTimeoutMs ?? DEFAULT_GIT_CLONE_TIMEOUT_MS);
|
|
35
|
+
const args = [
|
|
36
|
+
"timeout",
|
|
37
|
+
"-k",
|
|
38
|
+
String(GIT_CLONE_KILL_GRACE_SECONDS),
|
|
39
|
+
String(timeoutSeconds),
|
|
40
|
+
"git",
|
|
41
|
+
"-c",
|
|
42
|
+
"http.lowSpeedLimit=1024",
|
|
43
|
+
"-c",
|
|
44
|
+
"http.lowSpeedTime=30",
|
|
45
|
+
"clone",
|
|
46
|
+
"--filter=blob:none"
|
|
47
|
+
];
|
|
48
|
+
if (options.branch) args.push("--branch", options.branch);
|
|
49
|
+
if (options.depth !== void 0) args.push("--depth", String(options.depth));
|
|
50
|
+
args.push(repoUrl, targetDir);
|
|
51
|
+
return args;
|
|
52
|
+
}
|
|
53
|
+
function buildCheckoutArgs(branch) {
|
|
54
|
+
return [
|
|
55
|
+
"git",
|
|
56
|
+
"checkout",
|
|
57
|
+
branch
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
function buildGetCurrentBranchArgs() {
|
|
61
|
+
return [
|
|
62
|
+
"git",
|
|
63
|
+
"branch",
|
|
64
|
+
"--show-current"
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
function buildListBranchesArgs() {
|
|
68
|
+
return [
|
|
69
|
+
"git",
|
|
70
|
+
"branch",
|
|
71
|
+
"-a"
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parse `git branch -a` output into a deduplicated array of branch names.
|
|
76
|
+
* Strips the current-branch marker, the `remotes/origin/` prefix, and the
|
|
77
|
+
* `HEAD` reference.
|
|
78
|
+
*/
|
|
79
|
+
function parseBranchList(stdout) {
|
|
80
|
+
return stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => line.replace(/^\*\s*/, "")).map((line) => line.replace(/^remotes\/origin\//, "")).filter((branch) => branch !== "HEAD" && !branch.includes("->")).filter((branch, index, array) => array.indexOf(branch) === index);
|
|
81
|
+
}
|
|
82
|
+
/** Validate a branch name (format only). */
|
|
83
|
+
function validateBranchName(branch) {
|
|
84
|
+
if (!branch || branch.trim().length === 0) return {
|
|
85
|
+
isValid: false,
|
|
86
|
+
error: "Branch name cannot be empty"
|
|
87
|
+
};
|
|
88
|
+
return { isValid: true };
|
|
89
|
+
}
|
|
90
|
+
/** Validate a git URL (format only — no allowlist). */
|
|
91
|
+
function validateGitUrl(url) {
|
|
92
|
+
const errors = [];
|
|
93
|
+
if (!url || typeof url !== "string") return {
|
|
94
|
+
isValid: false,
|
|
95
|
+
errors: ["Git URL must be a non-empty string"]
|
|
96
|
+
};
|
|
97
|
+
const trimmedUrl = url.trim();
|
|
98
|
+
if (trimmedUrl.length === 0) errors.push("Git URL cannot be empty");
|
|
99
|
+
if (trimmedUrl.length > 2048) errors.push("Git URL too long (max 2048 characters)");
|
|
100
|
+
if (trimmedUrl.includes("\0")) errors.push("Git URL contains null bytes");
|
|
101
|
+
return {
|
|
102
|
+
isValid: errors.length === 0,
|
|
103
|
+
errors
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** Validate a filesystem path (format only). */
|
|
107
|
+
function validatePath(path) {
|
|
108
|
+
const errors = [];
|
|
109
|
+
if (!path || typeof path !== "string") return {
|
|
110
|
+
isValid: false,
|
|
111
|
+
errors: ["Path must be a non-empty string"]
|
|
112
|
+
};
|
|
113
|
+
if (path.includes("\0")) errors.push("Path contains null bytes");
|
|
114
|
+
if (path.length > 4096) errors.push("Path too long (max 4096 characters)");
|
|
115
|
+
return {
|
|
116
|
+
isValid: errors.length === 0,
|
|
117
|
+
errors
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Determine the appropriate {@link ErrorCode} for a failed git operation,
|
|
122
|
+
* based on the operation, the stderr/error text, and the process exit code.
|
|
123
|
+
*/
|
|
124
|
+
function determineErrorCode(operation, error, exitCode) {
|
|
125
|
+
const lowerMessage = (typeof error === "string" ? error : error.message).toLowerCase();
|
|
126
|
+
if (exitCode === 124) return ErrorCode.GIT_NETWORK_ERROR;
|
|
127
|
+
if (exitCode === 128) {
|
|
128
|
+
if (lowerMessage.includes("not a git repository")) return ErrorCode.GIT_OPERATION_FAILED;
|
|
129
|
+
if (lowerMessage.includes("repository not found")) return ErrorCode.GIT_REPOSITORY_NOT_FOUND;
|
|
130
|
+
return ErrorCode.GIT_OPERATION_FAILED;
|
|
131
|
+
}
|
|
132
|
+
if (lowerMessage.includes("permission denied") || lowerMessage.includes("access denied")) return ErrorCode.GIT_AUTH_FAILED;
|
|
133
|
+
if (lowerMessage.includes("not found") || lowerMessage.includes("does not exist")) return ErrorCode.GIT_REPOSITORY_NOT_FOUND;
|
|
134
|
+
if (lowerMessage.includes("already exists")) return ErrorCode.GIT_CLONE_FAILED;
|
|
135
|
+
if (lowerMessage.includes("did not match") || lowerMessage.includes("pathspec")) return ErrorCode.GIT_BRANCH_NOT_FOUND;
|
|
136
|
+
if (lowerMessage.includes("authentication") || lowerMessage.includes("credentials")) return ErrorCode.GIT_AUTH_FAILED;
|
|
137
|
+
switch (operation) {
|
|
138
|
+
case "clone": return ErrorCode.GIT_CLONE_FAILED;
|
|
139
|
+
case "checkout": return ErrorCode.GIT_CHECKOUT_FAILED;
|
|
140
|
+
default: return ErrorCode.GIT_OPERATION_FAILED;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region ../../extensions/git/src/index.ts
|
|
146
|
+
/**
|
|
147
|
+
* Git extension for the Cloudflare Sandbox SDK.
|
|
148
|
+
*
|
|
149
|
+
* Git is just a sequence of shell commands, so this is an **SDK-only**
|
|
150
|
+
* extension: it drives the owning sandbox's unified `exec()` process surface
|
|
151
|
+
* over the existing control channel and needs no sidecar. All the git
|
|
152
|
+
* business logic that used to live in the container (argument building,
|
|
153
|
+
* branch parsing, error
|
|
154
|
+
* classification) is ported into `./manager` and runs in the Worker; only the
|
|
155
|
+
* `git` process itself runs in the container.
|
|
156
|
+
*
|
|
157
|
+
* Usage — attach it to a Sandbox subclass and expose delegate methods:
|
|
158
|
+
*
|
|
159
|
+
* ```ts
|
|
160
|
+
* import { Sandbox as BaseSandbox } from '@cloudflare/sandbox';
|
|
161
|
+
* import { withGit } from '@cloudflare/sandbox/git';
|
|
162
|
+
*
|
|
163
|
+
* export class Sandbox extends BaseSandbox<Env> {
|
|
164
|
+
* git = withGit(this);
|
|
165
|
+
* gitCheckout(repoUrl: string, options?: GitCheckoutOptions) {
|
|
166
|
+
* return this.git.checkout(repoUrl, options);
|
|
167
|
+
* }
|
|
168
|
+
* }
|
|
169
|
+
*
|
|
170
|
+
* await sandbox.gitCheckout('https://github.com/owner/repo.git');
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
const CLONE_PROCESS_TIMEOUT_BUFFER_MS = 1e4;
|
|
174
|
+
/**
|
|
175
|
+
* The git extension. Drives `git` in the container through the commands
|
|
176
|
+
* control sub-API and translates failures into the SDK's typed git errors.
|
|
177
|
+
*/
|
|
178
|
+
var Git = class extends SandboxExtension {
|
|
179
|
+
#options;
|
|
180
|
+
constructor(sandbox, options = {}) {
|
|
181
|
+
super(sandbox);
|
|
182
|
+
this.#options = options;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Clone a repository. Returns the resolved target directory and the branch
|
|
186
|
+
* actually checked out (queried from git, not assumed).
|
|
187
|
+
*/
|
|
188
|
+
async checkout(repoUrl, options = {}) {
|
|
189
|
+
const urlValidation = validateGitUrl(repoUrl);
|
|
190
|
+
if (!urlValidation.isValid) this.#throwValidation("repoUrl", urlValidation.errors, "INVALID_GIT_URL");
|
|
191
|
+
const targetDir = options.targetDir || generateTargetDirectory(repoUrl);
|
|
192
|
+
const cloneTimeoutMs = options.cloneTimeoutMs ?? DEFAULT_GIT_CLONE_TIMEOUT_MS;
|
|
193
|
+
if (!Number.isInteger(cloneTimeoutMs) || cloneTimeoutMs <= 0) this.#throwError(ErrorCode.VALIDATION_FAILED, `Invalid clone timeout '${options.cloneTimeoutMs}'. Must be a positive integer representing milliseconds.`, { validationErrors: [{
|
|
194
|
+
field: "cloneTimeoutMs",
|
|
195
|
+
message: "Clone timeout must be a positive integer representing milliseconds",
|
|
196
|
+
code: "INVALID_TIMEOUT"
|
|
197
|
+
}] });
|
|
198
|
+
const pathValidation = validatePath(targetDir);
|
|
199
|
+
if (!pathValidation.isValid) this.#throwValidation("targetDir", pathValidation.errors, "INVALID_PATH");
|
|
200
|
+
await this.#configureAuth(repoUrl, options.auth);
|
|
201
|
+
const sessionId = this.#sessionId(options);
|
|
202
|
+
const cloneResult = await this.#exec(buildCloneArgs(repoUrl, targetDir, options), sessionId, void 0, cloneTimeoutMs + CLONE_PROCESS_TIMEOUT_BUFFER_MS);
|
|
203
|
+
if (cloneResult.exitCode !== 0) {
|
|
204
|
+
if ([
|
|
205
|
+
124,
|
|
206
|
+
143,
|
|
207
|
+
-15
|
|
208
|
+
].includes(cloneResult.exitCode)) this.#throwError(ErrorCode.GIT_NETWORK_ERROR, `Git clone timed out after ${gitCloneTimeoutSeconds(cloneTimeoutMs)} seconds for '${redactCommand(repoUrl)}'`, {
|
|
209
|
+
repository: redactCommand(repoUrl),
|
|
210
|
+
targetDir,
|
|
211
|
+
exitCode: 124,
|
|
212
|
+
stderr: "Operation timed out"
|
|
213
|
+
});
|
|
214
|
+
const code = determineErrorCode("clone", cloneResult.stderr || "Unknown error", cloneResult.exitCode);
|
|
215
|
+
this.#throwError(code, `Failed to clone repository '${redactCommand(repoUrl)}': ${redactCommand(cloneResult.stderr || "") || `exit code ${cloneResult.exitCode}`}`, {
|
|
216
|
+
repository: redactCommand(repoUrl),
|
|
217
|
+
targetDir,
|
|
218
|
+
exitCode: cloneResult.exitCode,
|
|
219
|
+
stderr: redactCommand(cloneResult.stderr || "")
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
const branchResult = await this.#exec(buildGetCurrentBranchArgs(), sessionId, targetDir);
|
|
223
|
+
return {
|
|
224
|
+
success: true,
|
|
225
|
+
repoUrl,
|
|
226
|
+
branch: branchResult.exitCode === 0 && branchResult.stdout.trim() ? branchResult.stdout.trim() : options.branch || "unknown",
|
|
227
|
+
targetDir,
|
|
228
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/** Check out an existing branch in a cloned repository. */
|
|
232
|
+
async checkoutBranch(repoPath, branch, options = {}) {
|
|
233
|
+
const pathValidation = validatePath(repoPath);
|
|
234
|
+
if (!pathValidation.isValid) this.#throwValidation("repoPath", pathValidation.errors, "INVALID_PATH");
|
|
235
|
+
const branchValidation = validateBranchName(branch);
|
|
236
|
+
if (!branchValidation.isValid) this.#throwError(ErrorCode.VALIDATION_FAILED, `Invalid branch name '${branch}': ${branchValidation.error || "Invalid format"}`, { validationErrors: [{
|
|
237
|
+
field: "branch",
|
|
238
|
+
message: branchValidation.error || "Invalid branch name format",
|
|
239
|
+
code: "INVALID_BRANCH"
|
|
240
|
+
}] });
|
|
241
|
+
const result = await this.#exec(buildCheckoutArgs(branch), this.#sessionId(options), repoPath);
|
|
242
|
+
if (result.exitCode !== 0) {
|
|
243
|
+
const code = determineErrorCode("checkout", result.stderr || "Unknown error", result.exitCode);
|
|
244
|
+
this.#throwError(code, `Failed to checkout branch '${branch}' in '${repoPath}': ${result.stderr || `exit code ${result.exitCode}`}`, {
|
|
245
|
+
branch,
|
|
246
|
+
targetDir: repoPath,
|
|
247
|
+
exitCode: result.exitCode,
|
|
248
|
+
stderr: result.stderr
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/** Return the current branch of a cloned repository. */
|
|
253
|
+
async getCurrentBranch(repoPath, options = {}) {
|
|
254
|
+
const pathValidation = validatePath(repoPath);
|
|
255
|
+
if (!pathValidation.isValid) this.#throwValidation("repoPath", pathValidation.errors, "INVALID_PATH");
|
|
256
|
+
const result = await this.#exec(buildGetCurrentBranchArgs(), this.#sessionId(options), repoPath);
|
|
257
|
+
if (result.exitCode !== 0) this.#throwError(determineErrorCode("getCurrentBranch", result.stderr || "Unknown error", result.exitCode), `Failed to get current branch in '${repoPath}': ${result.stderr || `exit code ${result.exitCode}`}`, {
|
|
258
|
+
targetDir: repoPath,
|
|
259
|
+
exitCode: result.exitCode,
|
|
260
|
+
stderr: result.stderr
|
|
261
|
+
});
|
|
262
|
+
return result.stdout.trim();
|
|
263
|
+
}
|
|
264
|
+
/** List local and remote branches of a cloned repository. */
|
|
265
|
+
async listBranches(repoPath, options = {}) {
|
|
266
|
+
const pathValidation = validatePath(repoPath);
|
|
267
|
+
if (!pathValidation.isValid) this.#throwValidation("repoPath", pathValidation.errors, "INVALID_PATH");
|
|
268
|
+
const result = await this.#exec(buildListBranchesArgs(), this.#sessionId(options), repoPath);
|
|
269
|
+
if (result.exitCode !== 0) this.#throwError(determineErrorCode("listBranches", result.stderr || "Unknown error", result.exitCode), `Failed to list branches in '${repoPath}': ${result.stderr || `exit code ${result.exitCode}`}`, {
|
|
270
|
+
targetDir: repoPath,
|
|
271
|
+
exitCode: result.exitCode,
|
|
272
|
+
stderr: result.stderr
|
|
273
|
+
});
|
|
274
|
+
return parseBranchList(result.stdout);
|
|
275
|
+
}
|
|
276
|
+
async #configureAuth(repoUrl, authOverride) {
|
|
277
|
+
const hosts = this.#authHosts(authOverride);
|
|
278
|
+
if (Object.keys(hosts).length === 0) return;
|
|
279
|
+
const hostname = this.#authHostname(repoUrl);
|
|
280
|
+
if (hostname === void 0 || !hosts[hostname]) return;
|
|
281
|
+
if (!this.httpAuthInterceptor) this.#throwError(ErrorCode.VALIDATION_FAILED, "Git extension authentication requires exporting ContainerProxy from the Worker entrypoint. Import ContainerProxy from @cloudflare/sandbox and export it from your Worker to use git auth interception.", {
|
|
282
|
+
repository: redactCommand(repoUrl),
|
|
283
|
+
host: hostname
|
|
284
|
+
});
|
|
285
|
+
await this.httpAuthInterceptor({ hosts: { [hostname]: hosts[hostname] } });
|
|
286
|
+
}
|
|
287
|
+
#authHostname(repoUrl) {
|
|
288
|
+
try {
|
|
289
|
+
return new URL(repoUrl).hostname;
|
|
290
|
+
} catch {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
#authHosts(authOverride) {
|
|
295
|
+
if (authOverride === false) return {};
|
|
296
|
+
const config = authOverride ?? this.#options.auth;
|
|
297
|
+
if (!config) return {};
|
|
298
|
+
const hosts = { ...config.hosts ?? {} };
|
|
299
|
+
if (config.github) hosts["github.com"] = config.github;
|
|
300
|
+
if (config.gitlab) hosts["gitlab.com"] = config.gitlab;
|
|
301
|
+
if (config.bitbucket) hosts["bitbucket.org"] = config.bitbucket;
|
|
302
|
+
return hosts;
|
|
303
|
+
}
|
|
304
|
+
#sessionId(options) {
|
|
305
|
+
return options.sessionId;
|
|
306
|
+
}
|
|
307
|
+
async #exec(command, sessionId, cwd, timeout) {
|
|
308
|
+
const result = await this.exec(command, {
|
|
309
|
+
...sessionId !== void 0 && { sessionId },
|
|
310
|
+
...cwd !== void 0 && { cwd },
|
|
311
|
+
...timeout !== void 0 && { timeout },
|
|
312
|
+
stdout: "pipe",
|
|
313
|
+
stderr: "pipe"
|
|
314
|
+
}).output({ encoding: "utf8" });
|
|
315
|
+
return {
|
|
316
|
+
stdout: result.stdout,
|
|
317
|
+
stderr: result.stderr,
|
|
318
|
+
exitCode: result.exitCode
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
#throwValidation(field, errors, code) {
|
|
322
|
+
this.#throwError(code === "INVALID_GIT_URL" ? ErrorCode.INVALID_GIT_URL : ErrorCode.VALIDATION_FAILED, `Invalid ${field}: ${errors.join(", ")}`, { validationErrors: errors.map((message) => ({
|
|
323
|
+
field,
|
|
324
|
+
message,
|
|
325
|
+
code
|
|
326
|
+
})) });
|
|
327
|
+
}
|
|
328
|
+
#throwError(code, message, context) {
|
|
329
|
+
throw createErrorFromResponse({
|
|
330
|
+
code,
|
|
331
|
+
message,
|
|
332
|
+
context,
|
|
333
|
+
httpStatus: getHttpStatus(code),
|
|
334
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
/** Factory — the consumer-facing API. */
|
|
339
|
+
function withGit(sandbox, options = {}) {
|
|
340
|
+
return new Git(sandbox, options);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
//#endregion
|
|
344
|
+
export { Git, withGit as default, withGit };
|
|
345
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["errors: string[]","#options","#throwValidation","#throwError","#configureAuth","#sessionId","#exec","#authHosts","#authHostname","hosts: Record<string, GitHostAuth>"],"sources":["../../../../extensions/git/src/manager.ts","../../../../extensions/git/src/index.ts"],"sourcesContent":["/**\n * Pure git business logic for the git extension.\n *\n * No I/O — just command-argument building, output parsing, validation, and\n * error-code classification. Ported from the former container-side\n * `GitManager` so the extension can drive everything through `exec`.\n */\n\nimport { DEFAULT_GIT_CLONE_TIMEOUT_MS, extractRepoName } from '@repo/shared';\nimport { ErrorCode } from '@repo/shared/errors';\nimport type { GitCheckoutOptions } from './types.js';\n\nexport { DEFAULT_GIT_CLONE_TIMEOUT_MS };\n\nconst GIT_CLONE_KILL_GRACE_SECONDS = 5;\n\n/** Format a millisecond timeout as a `timeout(1)`-friendly seconds string. */\nexport function gitCloneTimeoutSeconds(timeoutMs: number): string {\n const timeoutSeconds = timeoutMs / 1000;\n return Number.isInteger(timeoutSeconds)\n ? String(timeoutSeconds)\n : timeoutSeconds\n .toFixed(3)\n .replace(/\\.0+$/, '')\n .replace(/(\\.\\d*?)0+$/, '$1');\n}\n\n/**\n * Generate the default target directory for a clone: `/workspace/<repoName>`.\n */\nexport function generateTargetDirectory(repoUrl: string): string {\n return `/workspace/${extractRepoName(repoUrl)}`;\n}\n\n/**\n * Build git clone command arguments.\n *\n * Wraps the command with `timeout -k 5 <seconds>` to enforce a wall-clock\n * limit, and configures git's own stalled-transfer detection via\n * `http.lowSpeedLimit` and `http.lowSpeedTime`.\n */\nexport function buildCloneArgs(\n repoUrl: string,\n targetDir: string,\n options: GitCheckoutOptions = {}\n): string[] {\n const timeoutMs = options.cloneTimeoutMs ?? DEFAULT_GIT_CLONE_TIMEOUT_MS;\n const timeoutSeconds = gitCloneTimeoutSeconds(timeoutMs);\n const args = [\n 'timeout',\n '-k',\n String(GIT_CLONE_KILL_GRACE_SECONDS),\n String(timeoutSeconds),\n 'git',\n '-c',\n 'http.lowSpeedLimit=1024',\n '-c',\n 'http.lowSpeedTime=30',\n 'clone',\n '--filter=blob:none'\n ];\n\n if (options.branch) {\n args.push('--branch', options.branch);\n }\n\n if (options.depth !== undefined) {\n args.push('--depth', String(options.depth));\n }\n\n args.push(repoUrl, targetDir);\n\n return args;\n}\n\nexport function buildCheckoutArgs(branch: string): string[] {\n return ['git', 'checkout', branch];\n}\n\nexport function buildGetCurrentBranchArgs(): string[] {\n return ['git', 'branch', '--show-current'];\n}\n\nexport function buildListBranchesArgs(): string[] {\n return ['git', 'branch', '-a'];\n}\n\n/**\n * Parse `git branch -a` output into a deduplicated array of branch names.\n * Strips the current-branch marker, the `remotes/origin/` prefix, and the\n * `HEAD` reference.\n */\nexport function parseBranchList(stdout: string): string[] {\n return stdout\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line.length > 0)\n .map((line) => line.replace(/^\\*\\s*/, ''))\n .map((line) => line.replace(/^remotes\\/origin\\//, ''))\n .filter((branch) => branch !== 'HEAD' && !branch.includes('->'))\n .filter((branch, index, array) => array.indexOf(branch) === index);\n}\n\n/** Validate a branch name (format only). */\nexport function validateBranchName(branch: string): {\n isValid: boolean;\n error?: string;\n} {\n if (!branch || branch.trim().length === 0) {\n return { isValid: false, error: 'Branch name cannot be empty' };\n }\n return { isValid: true };\n}\n\n/** Validate a git URL (format only — no allowlist). */\nexport function validateGitUrl(url: string): {\n isValid: boolean;\n errors: string[];\n} {\n const errors: string[] = [];\n\n if (!url || typeof url !== 'string') {\n return { isValid: false, errors: ['Git URL must be a non-empty string'] };\n }\n\n const trimmedUrl = url.trim();\n\n if (trimmedUrl.length === 0) {\n errors.push('Git URL cannot be empty');\n }\n if (trimmedUrl.length > 2048) {\n errors.push('Git URL too long (max 2048 characters)');\n }\n if (trimmedUrl.includes('\\0')) {\n errors.push('Git URL contains null bytes');\n }\n\n return { isValid: errors.length === 0, errors };\n}\n\n/** Validate a filesystem path (format only). */\nexport function validatePath(path: string): {\n isValid: boolean;\n errors: string[];\n} {\n const errors: string[] = [];\n\n if (!path || typeof path !== 'string') {\n return { isValid: false, errors: ['Path must be a non-empty string'] };\n }\n if (path.includes('\\0')) {\n errors.push('Path contains null bytes');\n }\n if (path.length > 4096) {\n errors.push('Path too long (max 4096 characters)');\n }\n\n return { isValid: errors.length === 0, errors };\n}\n\n/**\n * Determine the appropriate {@link ErrorCode} for a failed git operation,\n * based on the operation, the stderr/error text, and the process exit code.\n */\nexport function determineErrorCode(\n operation: string,\n error: Error | string,\n exitCode?: number\n): ErrorCode {\n const errorMessage = typeof error === 'string' ? error : error.message;\n const lowerMessage = errorMessage.toLowerCase();\n\n // Exit code 124: timeout command killed the process.\n if (exitCode === 124) {\n return ErrorCode.GIT_NETWORK_ERROR;\n }\n\n // Exit code 128: git-specific fatal errors.\n if (exitCode === 128) {\n if (lowerMessage.includes('not a git repository')) {\n return ErrorCode.GIT_OPERATION_FAILED;\n }\n if (lowerMessage.includes('repository not found')) {\n return ErrorCode.GIT_REPOSITORY_NOT_FOUND;\n }\n return ErrorCode.GIT_OPERATION_FAILED;\n }\n\n if (\n lowerMessage.includes('permission denied') ||\n lowerMessage.includes('access denied')\n ) {\n return ErrorCode.GIT_AUTH_FAILED;\n }\n if (\n lowerMessage.includes('not found') ||\n lowerMessage.includes('does not exist')\n ) {\n return ErrorCode.GIT_REPOSITORY_NOT_FOUND;\n }\n if (lowerMessage.includes('already exists')) {\n return ErrorCode.GIT_CLONE_FAILED;\n }\n if (\n lowerMessage.includes('did not match') ||\n lowerMessage.includes('pathspec')\n ) {\n return ErrorCode.GIT_BRANCH_NOT_FOUND;\n }\n if (\n lowerMessage.includes('authentication') ||\n lowerMessage.includes('credentials')\n ) {\n return ErrorCode.GIT_AUTH_FAILED;\n }\n\n switch (operation) {\n case 'clone':\n return ErrorCode.GIT_CLONE_FAILED;\n case 'checkout':\n return ErrorCode.GIT_CHECKOUT_FAILED;\n default:\n return ErrorCode.GIT_OPERATION_FAILED;\n }\n}\n","/**\n * Git extension for the Cloudflare Sandbox SDK.\n *\n * Git is just a sequence of shell commands, so this is an **SDK-only**\n * extension: it drives the owning sandbox's unified `exec()` process surface\n * over the existing control channel and needs no sidecar. All the git\n * business logic that used to live in the container (argument building,\n * branch parsing, error\n * classification) is ported into `./manager` and runs in the Worker; only the\n * `git` process itself runs in the container.\n *\n * Usage — attach it to a Sandbox subclass and expose delegate methods:\n *\n * ```ts\n * import { Sandbox as BaseSandbox } from '@cloudflare/sandbox';\n * import { withGit } from '@cloudflare/sandbox/git';\n *\n * export class Sandbox extends BaseSandbox<Env> {\n * git = withGit(this);\n * gitCheckout(repoUrl: string, options?: GitCheckoutOptions) {\n * return this.git.checkout(repoUrl, options);\n * }\n * }\n *\n * await sandbox.gitCheckout('https://github.com/owner/repo.git');\n * ```\n */\n\nimport { createErrorFromResponse } from '@cloudflare/sandbox/errors';\nimport {\n SandboxExtension,\n type SandboxLike\n} from '@cloudflare/sandbox/extensions';\nimport { redactCommand } from '@repo/shared';\nimport {\n ErrorCode,\n type ErrorResponse,\n getHttpStatus\n} from '@repo/shared/errors';\nimport {\n buildCheckoutArgs,\n buildCloneArgs,\n buildGetCurrentBranchArgs,\n buildListBranchesArgs,\n DEFAULT_GIT_CLONE_TIMEOUT_MS,\n determineErrorCode,\n generateTargetDirectory,\n gitCloneTimeoutSeconds,\n parseBranchList,\n validateBranchName,\n validateGitUrl,\n validatePath\n} from './manager.js';\nimport type {\n GitCheckoutOptions,\n GitCheckoutResult,\n GitExtensionOptions,\n GitHostAuth,\n GitSessionOptions\n} from './types.js';\n\nexport type {\n GitAuthConfig,\n GitCheckoutOptions,\n GitCheckoutResult,\n GitExtensionOptions,\n GitHostAuth,\n GitSessionOptions\n} from './types.js';\nexport { withGit as default };\n\nconst CLONE_PROCESS_TIMEOUT_BUFFER_MS = 10_000;\n\n/** Shape returned by a completed git process. */\ninterface ExecOutcome {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * The git extension. Drives `git` in the container through the commands\n * control sub-API and translates failures into the SDK's typed git errors.\n */\nexport class Git extends SandboxExtension {\n readonly #options: GitExtensionOptions;\n\n constructor(sandbox: SandboxLike, options: GitExtensionOptions = {}) {\n super(sandbox);\n this.#options = options;\n }\n\n /**\n * Clone a repository. Returns the resolved target directory and the branch\n * actually checked out (queried from git, not assumed).\n */\n async checkout(\n repoUrl: string,\n options: GitCheckoutOptions = {}\n ): Promise<GitCheckoutResult> {\n const urlValidation = validateGitUrl(repoUrl);\n if (!urlValidation.isValid) {\n this.#throwValidation('repoUrl', urlValidation.errors, 'INVALID_GIT_URL');\n }\n\n const targetDir = options.targetDir || generateTargetDirectory(repoUrl);\n const cloneTimeoutMs =\n options.cloneTimeoutMs ?? DEFAULT_GIT_CLONE_TIMEOUT_MS;\n\n if (!Number.isInteger(cloneTimeoutMs) || cloneTimeoutMs <= 0) {\n this.#throwError(\n ErrorCode.VALIDATION_FAILED,\n `Invalid clone timeout '${options.cloneTimeoutMs}'. Must be a positive integer representing milliseconds.`,\n {\n validationErrors: [\n {\n field: 'cloneTimeoutMs',\n message:\n 'Clone timeout must be a positive integer representing milliseconds',\n code: 'INVALID_TIMEOUT'\n }\n ]\n }\n );\n }\n\n const pathValidation = validatePath(targetDir);\n if (!pathValidation.isValid) {\n this.#throwValidation('targetDir', pathValidation.errors, 'INVALID_PATH');\n }\n\n await this.#configureAuth(repoUrl, options.auth);\n\n const sessionId = this.#sessionId(options);\n const cloneResult = await this.#exec(\n buildCloneArgs(repoUrl, targetDir, options),\n sessionId,\n undefined,\n cloneTimeoutMs + CLONE_PROCESS_TIMEOUT_BUFFER_MS\n );\n\n if (cloneResult.exitCode !== 0) {\n if ([124, 143, -15].includes(cloneResult.exitCode)) {\n this.#throwError(\n ErrorCode.GIT_NETWORK_ERROR,\n `Git clone timed out after ${gitCloneTimeoutSeconds(\n cloneTimeoutMs\n )} seconds for '${redactCommand(repoUrl)}'`,\n {\n repository: redactCommand(repoUrl),\n targetDir,\n exitCode: 124,\n stderr: 'Operation timed out'\n }\n );\n }\n\n const code = determineErrorCode(\n 'clone',\n cloneResult.stderr || 'Unknown error',\n cloneResult.exitCode\n );\n this.#throwError(\n code,\n `Failed to clone repository '${redactCommand(repoUrl)}': ${\n redactCommand(cloneResult.stderr || '') ||\n `exit code ${cloneResult.exitCode}`\n }`,\n {\n repository: redactCommand(repoUrl),\n targetDir,\n exitCode: cloneResult.exitCode,\n stderr: redactCommand(cloneResult.stderr || '')\n }\n );\n }\n\n // Query the branch actually checked out rather than assuming.\n const branchResult = await this.#exec(\n buildGetCurrentBranchArgs(),\n sessionId,\n targetDir\n );\n const branch =\n branchResult.exitCode === 0 && branchResult.stdout.trim()\n ? branchResult.stdout.trim()\n : options.branch || 'unknown';\n\n return {\n success: true,\n repoUrl,\n branch,\n targetDir,\n timestamp: new Date().toISOString()\n };\n }\n\n /** Check out an existing branch in a cloned repository. */\n async checkoutBranch(\n repoPath: string,\n branch: string,\n options: GitSessionOptions = {}\n ): Promise<void> {\n const pathValidation = validatePath(repoPath);\n if (!pathValidation.isValid) {\n this.#throwValidation('repoPath', pathValidation.errors, 'INVALID_PATH');\n }\n\n const branchValidation = validateBranchName(branch);\n if (!branchValidation.isValid) {\n this.#throwError(\n ErrorCode.VALIDATION_FAILED,\n `Invalid branch name '${branch}': ${branchValidation.error || 'Invalid format'}`,\n {\n validationErrors: [\n {\n field: 'branch',\n message: branchValidation.error || 'Invalid branch name format',\n code: 'INVALID_BRANCH'\n }\n ]\n }\n );\n }\n\n const result = await this.#exec(\n buildCheckoutArgs(branch),\n this.#sessionId(options),\n repoPath\n );\n\n if (result.exitCode !== 0) {\n const code = determineErrorCode(\n 'checkout',\n result.stderr || 'Unknown error',\n result.exitCode\n );\n this.#throwError(\n code,\n `Failed to checkout branch '${branch}' in '${repoPath}': ${\n result.stderr || `exit code ${result.exitCode}`\n }`,\n {\n branch,\n targetDir: repoPath,\n exitCode: result.exitCode,\n stderr: result.stderr\n }\n );\n }\n }\n\n /** Return the current branch of a cloned repository. */\n async getCurrentBranch(\n repoPath: string,\n options: GitSessionOptions = {}\n ): Promise<string> {\n const pathValidation = validatePath(repoPath);\n if (!pathValidation.isValid) {\n this.#throwValidation('repoPath', pathValidation.errors, 'INVALID_PATH');\n }\n\n const result = await this.#exec(\n buildGetCurrentBranchArgs(),\n this.#sessionId(options),\n repoPath\n );\n\n if (result.exitCode !== 0) {\n this.#throwError(\n determineErrorCode(\n 'getCurrentBranch',\n result.stderr || 'Unknown error',\n result.exitCode\n ),\n `Failed to get current branch in '${repoPath}': ${\n result.stderr || `exit code ${result.exitCode}`\n }`,\n {\n targetDir: repoPath,\n exitCode: result.exitCode,\n stderr: result.stderr\n }\n );\n }\n\n return result.stdout.trim();\n }\n\n /** List local and remote branches of a cloned repository. */\n async listBranches(\n repoPath: string,\n options: GitSessionOptions = {}\n ): Promise<string[]> {\n const pathValidation = validatePath(repoPath);\n if (!pathValidation.isValid) {\n this.#throwValidation('repoPath', pathValidation.errors, 'INVALID_PATH');\n }\n\n const result = await this.#exec(\n buildListBranchesArgs(),\n this.#sessionId(options),\n repoPath\n );\n\n if (result.exitCode !== 0) {\n this.#throwError(\n determineErrorCode(\n 'listBranches',\n result.stderr || 'Unknown error',\n result.exitCode\n ),\n `Failed to list branches in '${repoPath}': ${\n result.stderr || `exit code ${result.exitCode}`\n }`,\n {\n targetDir: repoPath,\n exitCode: result.exitCode,\n stderr: result.stderr\n }\n );\n }\n\n return parseBranchList(result.stdout);\n }\n\n // --- internals -----------------------------------------------------------\n\n async #configureAuth(\n repoUrl: string,\n authOverride: GitCheckoutOptions['auth']\n ): Promise<void> {\n const hosts = this.#authHosts(authOverride);\n if (Object.keys(hosts).length === 0) {\n return;\n }\n\n const hostname = this.#authHostname(repoUrl);\n if (hostname === undefined || !hosts[hostname]) {\n return;\n }\n\n if (!this.httpAuthInterceptor) {\n this.#throwError(\n ErrorCode.VALIDATION_FAILED,\n 'Git extension authentication requires exporting ContainerProxy from the Worker entrypoint. Import ContainerProxy from @cloudflare/sandbox and export it from your Worker to use git auth interception.',\n {\n repository: redactCommand(repoUrl),\n host: hostname\n }\n );\n }\n\n await this.httpAuthInterceptor({ hosts: { [hostname]: hosts[hostname] } });\n }\n\n #authHostname(repoUrl: string): string | undefined {\n try {\n return new URL(repoUrl).hostname;\n } catch {\n return undefined;\n }\n }\n\n #authHosts(\n authOverride: GitCheckoutOptions['auth']\n ): Record<string, GitHostAuth> {\n if (authOverride === false) {\n return {};\n }\n const config = authOverride ?? this.#options.auth;\n if (!config) {\n return {};\n }\n\n const hosts: Record<string, GitHostAuth> = { ...(config.hosts ?? {}) };\n if (config.github) {\n hosts['github.com'] = config.github;\n }\n if (config.gitlab) {\n hosts['gitlab.com'] = config.gitlab;\n }\n if (config.bitbucket) {\n hosts['bitbucket.org'] = config.bitbucket;\n }\n return hosts;\n }\n\n #sessionId(options: GitSessionOptions): string | undefined {\n return options.sessionId;\n }\n\n async #exec(\n command: string[],\n sessionId: string | undefined,\n cwd?: string,\n timeout?: number\n ): Promise<ExecOutcome> {\n const result = await this.exec(command, {\n ...(sessionId !== undefined && { sessionId }),\n ...(cwd !== undefined && { cwd }),\n ...(timeout !== undefined && { timeout }),\n stdout: 'pipe',\n stderr: 'pipe'\n }).output({ encoding: 'utf8' });\n return {\n stdout: result.stdout,\n stderr: result.stderr,\n exitCode: result.exitCode\n };\n }\n\n #throwValidation(field: string, errors: string[], code: string): never {\n this.#throwError(\n code === 'INVALID_GIT_URL'\n ? ErrorCode.INVALID_GIT_URL\n : ErrorCode.VALIDATION_FAILED,\n `Invalid ${field}: ${errors.join(', ')}`,\n {\n validationErrors: errors.map((message) => ({ field, message, code }))\n }\n );\n }\n\n #throwError(\n code: ErrorCode,\n message: string,\n context: Record<string, unknown>\n ): never {\n const response: ErrorResponse = {\n code,\n message,\n context,\n httpStatus: getHttpStatus(code),\n timestamp: new Date().toISOString()\n };\n throw createErrorFromResponse(response);\n }\n}\n\n/** Factory — the consumer-facing API. */\nexport function withGit(\n sandbox: SandboxLike,\n options: GitExtensionOptions = {}\n): Git {\n return new Git(sandbox, options);\n}\n"],"mappings":"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"}
|