@cloudflare/sandbox 0.6.10 → 0.7.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/dist/{dist-c_xYW5i_.js → dist-BpbdH8ry.js} +41 -2
- package/dist/dist-BpbdH8ry.js.map +1 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +127 -34
- package/dist/index.js.map +1 -1
- package/dist/openai/index.d.ts +1 -1
- package/dist/openai/index.js +1 -1
- package/dist/opencode/index.d.ts +46 -4
- package/dist/opencode/index.d.ts.map +1 -1
- package/dist/opencode/index.js +62 -10
- package/dist/opencode/index.js.map +1 -1
- package/dist/{sandbox-DAb6o08K.d.ts → sandbox-CEsJ1edi.d.ts} +55 -23
- package/dist/sandbox-CEsJ1edi.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/dist-c_xYW5i_.js.map +0 -1
- package/dist/sandbox-DAb6o08K.d.ts.map +0 -1
|
@@ -10,6 +10,45 @@ function getEnvString(env, key) {
|
|
|
10
10
|
const value = env?.[key];
|
|
11
11
|
return typeof value === "string" ? value : void 0;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Filter environment variables object to only include string values.
|
|
15
|
+
* Skips undefined, null, and non-string values.
|
|
16
|
+
*
|
|
17
|
+
* Use this when you only need the defined values (e.g., for per-command env
|
|
18
|
+
* where undefined means "don't override").
|
|
19
|
+
*
|
|
20
|
+
* @param envVars - Object that may contain undefined values
|
|
21
|
+
* @returns Clean object with only string values
|
|
22
|
+
*/
|
|
23
|
+
function filterEnvVars(envVars) {
|
|
24
|
+
const filtered = {};
|
|
25
|
+
for (const [key, value] of Object.entries(envVars)) if (value != null && typeof value === "string") filtered[key] = value;
|
|
26
|
+
return filtered;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Partition environment variables into values to set and keys to unset.
|
|
30
|
+
*
|
|
31
|
+
* - String values → toSet (will be exported)
|
|
32
|
+
* - undefined/null → toUnset (will be unset)
|
|
33
|
+
*
|
|
34
|
+
* This enables idiomatic JS patterns where undefined means "remove":
|
|
35
|
+
* ```typescript
|
|
36
|
+
* await sandbox.setEnvVars({
|
|
37
|
+
* API_KEY: 'new-key', // will be set
|
|
38
|
+
* OLD_VAR: undefined, // will be unset
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
function partitionEnvVars(envVars) {
|
|
43
|
+
const toSet = {};
|
|
44
|
+
const toUnset = [];
|
|
45
|
+
for (const [key, value] of Object.entries(envVars)) if (value != null && typeof value === "string") toSet[key] = value;
|
|
46
|
+
else toUnset.push(key);
|
|
47
|
+
return {
|
|
48
|
+
toSet,
|
|
49
|
+
toUnset
|
|
50
|
+
};
|
|
51
|
+
}
|
|
13
52
|
|
|
14
53
|
//#endregion
|
|
15
54
|
//#region ../shared/dist/git.js
|
|
@@ -670,5 +709,5 @@ function generateRequestId() {
|
|
|
670
709
|
}
|
|
671
710
|
|
|
672
711
|
//#endregion
|
|
673
|
-
export { isExecResult as a, shellEscape as c, TraceContext as d, Execution as f,
|
|
674
|
-
//# sourceMappingURL=dist-
|
|
712
|
+
export { getEnvString as _, isExecResult as a, shellEscape as c, TraceContext as d, Execution as f, filterEnvVars as g, extractRepoName as h, isWSStreamChunk as i, createLogger as l, GitLogger as m, isWSError as n, isProcess as o, ResultImpl as p, isWSResponse as r, isProcessStatus as s, generateRequestId as t, createNoOpLogger as u, partitionEnvVars as v };
|
|
713
|
+
//# sourceMappingURL=dist-BpbdH8ry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dist-BpbdH8ry.js","names":["LogLevelEnum","LogLevelEnum"],"sources":["../../shared/dist/env.js","../../shared/dist/git.js","../../shared/dist/interpreter-types.js","../../shared/dist/logger/types.js","../../shared/dist/logger/logger.js","../../shared/dist/logger/trace-context.js","../../shared/dist/logger/index.js","../../shared/dist/shell-escape.js","../../shared/dist/types.js","../../shared/dist/ws-types.js"],"sourcesContent":["/**\n * Safely extract a string value from an environment object\n *\n * @param env - Environment object with dynamic keys\n * @param key - The environment variable key to access\n * @returns The string value if present and is a string, undefined otherwise\n */\nexport function getEnvString(env, key) {\n const value = env?.[key];\n return typeof value === 'string' ? value : undefined;\n}\n/**\n * Filter environment variables object to only include string values.\n * Skips undefined, null, and non-string values.\n *\n * Use this when you only need the defined values (e.g., for per-command env\n * where undefined means \"don't override\").\n *\n * @param envVars - Object that may contain undefined values\n * @returns Clean object with only string values\n */\nexport function filterEnvVars(envVars) {\n const filtered = {};\n for (const [key, value] of Object.entries(envVars)) {\n if (value != null && typeof value === 'string') {\n filtered[key] = value;\n }\n }\n return filtered;\n}\n/**\n * Partition environment variables into values to set and keys to unset.\n *\n * - String values → toSet (will be exported)\n * - undefined/null → toUnset (will be unset)\n *\n * This enables idiomatic JS patterns where undefined means \"remove\":\n * ```typescript\n * await sandbox.setEnvVars({\n * API_KEY: 'new-key', // will be set\n * OLD_VAR: undefined, // will be unset\n * });\n * ```\n */\nexport function partitionEnvVars(envVars) {\n const toSet = {};\n const toUnset = [];\n for (const [key, value] of Object.entries(envVars)) {\n if (value != null && typeof value === 'string') {\n toSet[key] = value;\n }\n else {\n toUnset.push(key);\n }\n }\n return { toSet, toUnset };\n}\n","/**\n * Fallback repository name used when URL parsing fails\n */\nexport const FALLBACK_REPO_NAME = 'repository';\n/**\n * Extract repository name from a Git URL\n *\n * Supports multiple URL formats:\n * - HTTPS: https://github.com/user/repo.git → repo\n * - HTTPS without .git: https://github.com/user/repo → repo\n * - SSH: git@github.com:user/repo.git → repo\n * - GitLab/others: https://gitlab.com/org/project.git → project\n *\n * @param repoUrl - Git repository URL (HTTPS or SSH format)\n * @returns Repository name extracted from URL, or 'repository' as fallback\n */\nexport function extractRepoName(repoUrl) {\n // Try parsing as standard URL (https://, http://)\n try {\n const url = new URL(repoUrl);\n const pathParts = url.pathname.split('/');\n const lastPart = pathParts[pathParts.length - 1];\n if (lastPart) {\n return lastPart.replace(/\\.git$/, '');\n }\n }\n catch {\n // Not a standard URL, try SSH format\n }\n // For SSH URLs (git@github.com:user/repo.git), split by : and / to get last segment\n // Only process if the URL contains path delimiters\n if (repoUrl.includes(':') || repoUrl.includes('/')) {\n const segments = repoUrl.split(/[:/]/).filter(Boolean);\n const lastSegment = segments[segments.length - 1];\n if (lastSegment) {\n return lastSegment.replace(/\\.git$/, '');\n }\n }\n return FALLBACK_REPO_NAME;\n}\n/**\n * Redact credentials from URLs for secure logging\n *\n * Replaces any credentials (username:password, tokens, etc.) embedded\n * in URLs with ****** to prevent sensitive data exposure in logs.\n * Works with URLs embedded in text (e.g., \"Error: https://token@github.com/repo.git failed\")\n *\n * @param text - String that may contain URLs with credentials\n * @returns String with credentials redacted from any URLs\n */\nexport function redactCredentials(text) {\n // Scan for http(s):// URLs and redact any credentials found\n let result = text;\n let pos = 0;\n while (pos < result.length) {\n const httpPos = result.indexOf('http://', pos);\n const httpsPos = result.indexOf('https://', pos);\n let protocolPos = -1;\n let protocolLen = 0;\n if (httpPos === -1 && httpsPos === -1)\n break;\n if (httpPos !== -1 && (httpsPos === -1 || httpPos < httpsPos)) {\n protocolPos = httpPos;\n protocolLen = 7; // 'http://'.length\n }\n else {\n protocolPos = httpsPos;\n protocolLen = 8; // 'https://'.length\n }\n // Look for @ after the protocol\n const searchStart = protocolPos + protocolLen;\n const atPos = result.indexOf('@', searchStart);\n // Find where the URL ends (whitespace, quotes, or structural delimiters)\n let urlEnd = searchStart;\n while (urlEnd < result.length) {\n const char = result[urlEnd];\n if (/[\\s\"'`<>,;{}[\\]]/.test(char))\n break;\n urlEnd++;\n }\n if (atPos !== -1 && atPos < urlEnd) {\n result = `${result.substring(0, searchStart)}******${result.substring(atPos)}`;\n pos = searchStart + 6; // Move past '******'\n }\n else {\n pos = protocolPos + protocolLen;\n }\n }\n return result;\n}\n/**\n * Sanitize data by redacting credentials from any strings\n * Recursively processes objects and arrays to ensure credentials are never leaked\n */\nexport function sanitizeGitData(data) {\n // Handle primitives\n if (typeof data === 'string') {\n return redactCredentials(data);\n }\n if (data === null || data === undefined) {\n return data;\n }\n // Handle arrays\n if (Array.isArray(data)) {\n return data.map((item) => sanitizeGitData(item));\n }\n // Handle objects - recursively sanitize all fields\n if (typeof data === 'object') {\n const result = {};\n for (const [key, value] of Object.entries(data)) {\n result[key] = sanitizeGitData(value);\n }\n return result;\n }\n return data;\n}\n/**\n * Logger wrapper that automatically sanitizes git credentials\n */\nexport class GitLogger {\n baseLogger;\n constructor(baseLogger) {\n this.baseLogger = baseLogger;\n }\n sanitizeContext(context) {\n return context\n ? sanitizeGitData(context)\n : context;\n }\n sanitizeError(error) {\n if (!error)\n return error;\n // Create a new error with sanitized message and stack\n const sanitized = new Error(redactCredentials(error.message));\n sanitized.name = error.name;\n if (error.stack) {\n sanitized.stack = redactCredentials(error.stack);\n }\n // Preserve other enumerable properties\n const sanitizedRecord = sanitized;\n const errorRecord = error;\n for (const key of Object.keys(error)) {\n if (key !== 'message' && key !== 'stack' && key !== 'name') {\n sanitizedRecord[key] = sanitizeGitData(errorRecord[key]);\n }\n }\n return sanitized;\n }\n debug(message, context) {\n this.baseLogger.debug(message, this.sanitizeContext(context));\n }\n info(message, context) {\n this.baseLogger.info(message, this.sanitizeContext(context));\n }\n warn(message, context) {\n this.baseLogger.warn(message, this.sanitizeContext(context));\n }\n error(message, error, context) {\n this.baseLogger.error(message, this.sanitizeError(error), this.sanitizeContext(context));\n }\n child(context) {\n const sanitized = sanitizeGitData(context);\n const childLogger = this.baseLogger.child(sanitized);\n return new GitLogger(childLogger);\n }\n}\n","// Execution Result Container\nexport class Execution {\n code;\n context;\n /**\n * All results from the execution\n */\n results = [];\n /**\n * Accumulated stdout and stderr\n */\n logs = {\n stdout: [],\n stderr: []\n };\n /**\n * Execution error if any\n */\n error;\n /**\n * Execution count (for interpreter)\n */\n executionCount;\n constructor(code, context) {\n this.code = code;\n this.context = context;\n }\n /**\n * Convert to a plain object for serialization\n */\n toJSON() {\n return {\n code: this.code,\n logs: this.logs,\n error: this.error,\n executionCount: this.executionCount,\n results: this.results.map((result) => ({\n text: result.text,\n html: result.html,\n png: result.png,\n jpeg: result.jpeg,\n svg: result.svg,\n latex: result.latex,\n markdown: result.markdown,\n javascript: result.javascript,\n json: result.json,\n chart: result.chart,\n data: result.data\n }))\n };\n }\n}\n// Implementation of Result\nexport class ResultImpl {\n raw;\n constructor(raw) {\n this.raw = raw;\n }\n get text() {\n return this.raw.text || this.raw.data?.['text/plain'];\n }\n get html() {\n return this.raw.html || this.raw.data?.['text/html'];\n }\n get png() {\n return this.raw.png || this.raw.data?.['image/png'];\n }\n get jpeg() {\n return this.raw.jpeg || this.raw.data?.['image/jpeg'];\n }\n get svg() {\n return this.raw.svg || this.raw.data?.['image/svg+xml'];\n }\n get latex() {\n return this.raw.latex || this.raw.data?.['text/latex'];\n }\n get markdown() {\n return this.raw.markdown || this.raw.data?.['text/markdown'];\n }\n get javascript() {\n return this.raw.javascript || this.raw.data?.['application/javascript'];\n }\n get json() {\n return this.raw.json || this.raw.data?.['application/json'];\n }\n get chart() {\n return this.raw.chart;\n }\n get data() {\n return this.raw.data;\n }\n formats() {\n const formats = [];\n if (this.text)\n formats.push('text');\n if (this.html)\n formats.push('html');\n if (this.png)\n formats.push('png');\n if (this.jpeg)\n formats.push('jpeg');\n if (this.svg)\n formats.push('svg');\n if (this.latex)\n formats.push('latex');\n if (this.markdown)\n formats.push('markdown');\n if (this.javascript)\n formats.push('javascript');\n if (this.json)\n formats.push('json');\n if (this.chart)\n formats.push('chart');\n return formats;\n }\n}\n","/**\n * Logger types for Cloudflare Sandbox SDK\n *\n * Provides structured, trace-aware logging across Worker, Durable Object, and Container.\n */\n/**\n * Log levels (from most to least verbose)\n */\nexport var LogLevel;\n(function (LogLevel) {\n LogLevel[LogLevel[\"DEBUG\"] = 0] = \"DEBUG\";\n LogLevel[LogLevel[\"INFO\"] = 1] = \"INFO\";\n LogLevel[LogLevel[\"WARN\"] = 2] = \"WARN\";\n LogLevel[LogLevel[\"ERROR\"] = 3] = \"ERROR\";\n})(LogLevel || (LogLevel = {}));\n","/**\n * Logger implementation\n */\nimport { LogLevel as LogLevelEnum } from './types.js';\n/**\n * ANSI color codes for terminal output\n */\nconst COLORS = {\n reset: '\\x1b[0m',\n debug: '\\x1b[36m', // Cyan\n info: '\\x1b[32m', // Green\n warn: '\\x1b[33m', // Yellow\n error: '\\x1b[31m', // Red\n dim: '\\x1b[2m' // Dim\n};\n/**\n * CloudflareLogger implements structured logging with support for\n * both JSON output (production) and pretty printing (development).\n */\nexport class CloudflareLogger {\n baseContext;\n minLevel;\n pretty;\n /**\n * Create a new CloudflareLogger\n *\n * @param baseContext Base context included in all log entries\n * @param minLevel Minimum log level to output (default: INFO)\n * @param pretty Enable pretty printing for human-readable output (default: false)\n */\n constructor(baseContext, minLevel = LogLevelEnum.INFO, pretty = false) {\n this.baseContext = baseContext;\n this.minLevel = minLevel;\n this.pretty = pretty;\n }\n /**\n * Log debug-level message\n */\n debug(message, context) {\n if (this.shouldLog(LogLevelEnum.DEBUG)) {\n const logData = this.buildLogData('debug', message, context);\n this.output(console.log, logData);\n }\n }\n /**\n * Log info-level message\n */\n info(message, context) {\n if (this.shouldLog(LogLevelEnum.INFO)) {\n const logData = this.buildLogData('info', message, context);\n this.output(console.log, logData);\n }\n }\n /**\n * Log warning-level message\n */\n warn(message, context) {\n if (this.shouldLog(LogLevelEnum.WARN)) {\n const logData = this.buildLogData('warn', message, context);\n this.output(console.warn, logData);\n }\n }\n /**\n * Log error-level message\n */\n error(message, error, context) {\n if (this.shouldLog(LogLevelEnum.ERROR)) {\n const logData = this.buildLogData('error', message, context, error);\n this.output(console.error, logData);\n }\n }\n /**\n * Create a child logger with additional context\n */\n child(context) {\n return new CloudflareLogger({ ...this.baseContext, ...context }, this.minLevel, this.pretty);\n }\n /**\n * Check if a log level should be output\n */\n shouldLog(level) {\n return level >= this.minLevel;\n }\n /**\n * Build log data object\n */\n buildLogData(level, message, context, error) {\n const logData = {\n level,\n msg: message,\n ...this.baseContext,\n ...context,\n timestamp: new Date().toISOString()\n };\n // Add error details if provided\n if (error) {\n logData.error = {\n message: error.message,\n stack: error.stack,\n name: error.name\n };\n }\n return logData;\n }\n /**\n * Output log data to console (pretty or JSON)\n */\n output(consoleFn, data) {\n if (this.pretty) {\n this.outputPretty(consoleFn, data);\n }\n else {\n this.outputJson(consoleFn, data);\n }\n }\n /**\n * Output as JSON (production)\n */\n outputJson(consoleFn, data) {\n consoleFn(JSON.stringify(data));\n }\n /**\n * Output as pretty-printed, colored text (development)\n *\n * Format: LEVEL [component] message (trace: tr_...) {context}\n * Example: INFO [sandbox-do] Command started (trace: tr_7f3a9b2c) {commandId: \"cmd-123\"}\n */\n outputPretty(consoleFn, data) {\n const { level, msg, timestamp, traceId, component, sandboxId, sessionId, processId, commandId, operation, duration, error, ...rest } = data;\n // Build the main log line\n const levelStr = String(level || 'INFO').toUpperCase();\n const levelColor = this.getLevelColor(levelStr);\n const componentBadge = component ? `[${component}]` : '';\n const traceIdShort = traceId ? String(traceId).substring(0, 12) : '';\n // Start with level and component\n let logLine = `${levelColor}${levelStr.padEnd(5)}${COLORS.reset} ${componentBadge} ${msg}`;\n // Add trace ID if present\n if (traceIdShort) {\n logLine += ` ${COLORS.dim}(trace: ${traceIdShort})${COLORS.reset}`;\n }\n // Collect important context fields\n const contextFields = [];\n if (operation)\n contextFields.push(`operation: ${operation}`);\n if (commandId)\n contextFields.push(`commandId: ${String(commandId).substring(0, 12)}`);\n if (sandboxId)\n contextFields.push(`sandboxId: ${sandboxId}`);\n if (sessionId)\n contextFields.push(`sessionId: ${String(sessionId).substring(0, 12)}`);\n if (processId)\n contextFields.push(`processId: ${processId}`);\n if (duration !== undefined)\n contextFields.push(`duration: ${duration}ms`);\n // Add important context inline\n if (contextFields.length > 0) {\n logLine += ` ${COLORS.dim}{${contextFields.join(', ')}}${COLORS.reset}`;\n }\n // Output main log line\n consoleFn(logLine);\n // Output error details on separate lines if present\n if (error && typeof error === 'object') {\n const errorObj = error;\n if (errorObj.message) {\n consoleFn(` ${COLORS.error}Error: ${errorObj.message}${COLORS.reset}`);\n }\n if (errorObj.stack) {\n consoleFn(` ${COLORS.dim}${errorObj.stack}${COLORS.reset}`);\n }\n }\n // Output additional context if present\n if (Object.keys(rest).length > 0) {\n consoleFn(` ${COLORS.dim}${JSON.stringify(rest, null, 2)}${COLORS.reset}`);\n }\n }\n /**\n * Get ANSI color code for log level\n */\n getLevelColor(level) {\n const levelLower = level.toLowerCase();\n switch (levelLower) {\n case 'debug':\n return COLORS.debug;\n case 'info':\n return COLORS.info;\n case 'warn':\n return COLORS.warn;\n case 'error':\n return COLORS.error;\n default:\n return COLORS.reset;\n }\n }\n}\n","/**\n * Trace context utilities for request correlation\n *\n * Trace IDs enable correlating logs across distributed components:\n * Worker → Durable Object → Container → back\n *\n * The trace ID is propagated via the X-Trace-Id HTTP header.\n */\n/**\n * Utility for managing trace context across distributed components\n */\n// biome-ignore lint/complexity/noStaticOnlyClass: Keep as class for namespace grouping and discoverability\nexport class TraceContext {\n /**\n * HTTP header name for trace ID propagation\n */\n static TRACE_HEADER = 'X-Trace-Id';\n /**\n * Generate a new trace ID\n *\n * Format: \"tr_\" + 16 random hex characters\n * Example: \"tr_7f3a9b2c4e5d6f1a\"\n *\n * @returns Newly generated trace ID\n */\n static generate() {\n // Use crypto.randomUUID() for randomness, extract 16 hex chars\n const randomHex = crypto.randomUUID().replace(/-/g, '').substring(0, 16);\n return `tr_${randomHex}`;\n }\n /**\n * Extract trace ID from HTTP request headers\n *\n * @param headers Request headers\n * @returns Trace ID if present, null otherwise\n */\n static fromHeaders(headers) {\n return headers.get(TraceContext.TRACE_HEADER);\n }\n /**\n * Create headers object with trace ID for outgoing requests\n *\n * @param traceId Trace ID to include\n * @returns Headers object with X-Trace-Id set\n */\n static toHeaders(traceId) {\n return { [TraceContext.TRACE_HEADER]: traceId };\n }\n /**\n * Get the header name used for trace ID propagation\n *\n * @returns Header name (\"X-Trace-Id\")\n */\n static getHeaderName() {\n return TraceContext.TRACE_HEADER;\n }\n}\n","/**\n * Logger module\n *\n * Provides structured, trace-aware logging with:\n * - Explicit logger passing via constructor injection\n * - Pretty printing for local development\n * - JSON output for production\n * - Environment auto-detection\n * - Log level configuration\n *\n * Usage:\n *\n * ```typescript\n * // Create a logger at entry point\n * const logger = createLogger({ component: 'sandbox-do', traceId: 'tr_abc123' });\n *\n * // Pass to classes via constructor\n * const service = new MyService(logger);\n *\n * // Create child loggers for additional context\n * const execLogger = logger.child({ operation: 'exec', commandId: 'cmd-456' });\n * execLogger.info('Operation started');\n * ```\n */\nimport { CloudflareLogger } from './logger.js';\nimport { TraceContext } from './trace-context.js';\nimport { LogLevel as LogLevelEnum } from './types.js';\nexport { CloudflareLogger } from './logger.js';\nexport { TraceContext } from './trace-context.js';\nexport { LogLevel as LogLevelEnum } from './types.js';\n/**\n * Create a no-op logger for testing\n *\n * Returns a logger that implements the Logger interface but does nothing.\n * Useful for tests that don't need actual logging output.\n *\n * @returns No-op logger instance\n *\n * @example\n * ```typescript\n * // In tests\n * const client = new HttpClient({\n * baseUrl: 'http://test.com',\n * logger: createNoOpLogger() // Optional - tests can enable real logging if needed\n * });\n * ```\n */\nexport function createNoOpLogger() {\n return {\n debug: () => { },\n info: () => { },\n warn: () => { },\n error: () => { },\n child: () => createNoOpLogger()\n };\n}\n/**\n * Create a new logger instance\n *\n * @param context Base context for the logger. Must include 'component'.\n * TraceId will be auto-generated if not provided.\n * @returns New logger instance\n *\n * @example\n * ```typescript\n * // In Durable Object\n * const logger = createLogger({\n * component: 'sandbox-do',\n * traceId: TraceContext.fromHeaders(request.headers) || TraceContext.generate(),\n * sandboxId: this.id\n * });\n *\n * // In Container\n * const logger = createLogger({\n * component: 'container',\n * traceId: TraceContext.fromHeaders(request.headers)!,\n * sessionId: this.id\n * });\n * ```\n */\nexport function createLogger(context) {\n const minLevel = getLogLevelFromEnv();\n const pretty = isPrettyPrintEnabled();\n const baseContext = {\n ...context,\n traceId: context.traceId || TraceContext.generate(),\n component: context.component\n };\n return new CloudflareLogger(baseContext, minLevel, pretty);\n}\n/**\n * Get log level from environment variable\n *\n * Checks SANDBOX_LOG_LEVEL env var, falls back to default based on environment.\n * Default: 'debug' for development, 'info' for production\n */\nfunction getLogLevelFromEnv() {\n const envLevel = getEnvVar('SANDBOX_LOG_LEVEL') || 'info';\n switch (envLevel.toLowerCase()) {\n case 'debug':\n return LogLevelEnum.DEBUG;\n case 'info':\n return LogLevelEnum.INFO;\n case 'warn':\n return LogLevelEnum.WARN;\n case 'error':\n return LogLevelEnum.ERROR;\n default:\n // Invalid level, fall back to info\n return LogLevelEnum.INFO;\n }\n}\n/**\n * Check if pretty printing should be enabled\n *\n * Checks SANDBOX_LOG_FORMAT env var, falls back to auto-detection:\n * - Local development: pretty (colored, human-readable)\n * - Production: json (structured)\n */\nfunction isPrettyPrintEnabled() {\n // Check explicit SANDBOX_LOG_FORMAT env var\n const format = getEnvVar('SANDBOX_LOG_FORMAT');\n if (format) {\n return format.toLowerCase() === 'pretty';\n }\n return false;\n}\n/**\n * Get environment variable value\n *\n * Supports both Node.js (process.env) and Bun (Bun.env)\n */\nfunction getEnvVar(name) {\n // Try process.env first (Node.js / Bun)\n if (typeof process !== 'undefined' && process.env) {\n return process.env[name];\n }\n // Try Bun.env (Bun runtime)\n if (typeof Bun !== 'undefined') {\n const bunEnv = Bun.env;\n if (bunEnv) {\n return bunEnv[name];\n }\n }\n return undefined;\n}\n","/**\n * Escapes a string for safe use in shell commands using POSIX single-quote escaping.\n * Prevents command injection by wrapping the string in single quotes and escaping\n * any single quotes within the string.\n */\nexport function shellEscape(str) {\n return `'${str.replace(/'/g, \"'\\\\''\")}'`;\n}\n","/**\n * Check if a process status indicates the process has terminated\n */\nexport function isTerminalStatus(status) {\n return (status === 'completed' ||\n status === 'failed' ||\n status === 'killed' ||\n status === 'error');\n}\n// Type guards for runtime validation\nexport function isExecResult(value) {\n return (value &&\n typeof value.success === 'boolean' &&\n typeof value.exitCode === 'number' &&\n typeof value.stdout === 'string' &&\n typeof value.stderr === 'string');\n}\nexport function isProcess(value) {\n return (value &&\n typeof value.id === 'string' &&\n typeof value.command === 'string' &&\n typeof value.status === 'string');\n}\nexport function isProcessStatus(value) {\n return [\n 'starting',\n 'running',\n 'completed',\n 'failed',\n 'killed',\n 'error'\n ].includes(value);\n}\n// Re-export interpreter types for convenience\nexport { Execution, ResultImpl } from './interpreter-types';\n","/**\n * WebSocket transport protocol types\n *\n * Enables multiplexing HTTP-like requests over a single WebSocket connection.\n * This reduces sub-request count when running inside Workers/Durable Objects.\n *\n * Protocol:\n * - Client sends WSRequest messages\n * - Server responds with WSResponse messages (matched by id)\n * - For streaming endpoints, server sends multiple WSStreamChunk messages\n * followed by a final WSResponse\n */\n/**\n * Type guard for WSRequest\n *\n * Note: Only validates the discriminator field (type === 'request').\n * Does not validate other required fields (id, method, path).\n * Use for routing messages; trust TypeScript for field validation.\n */\nexport function isWSRequest(msg) {\n return (typeof msg === 'object' &&\n msg !== null &&\n 'type' in msg &&\n msg.type === 'request');\n}\n/**\n * Type guard for WSResponse\n *\n * Note: Only validates the discriminator field (type === 'response').\n */\nexport function isWSResponse(msg) {\n return (typeof msg === 'object' &&\n msg !== null &&\n 'type' in msg &&\n msg.type === 'response');\n}\n/**\n * Type guard for WSStreamChunk\n *\n * Note: Only validates the discriminator field (type === 'stream').\n */\nexport function isWSStreamChunk(msg) {\n return (typeof msg === 'object' &&\n msg !== null &&\n 'type' in msg &&\n msg.type === 'stream');\n}\n/**\n * Type guard for WSError\n *\n * Note: Only validates the discriminator field (type === 'error').\n */\nexport function isWSError(msg) {\n return (typeof msg === 'object' &&\n msg !== null &&\n 'type' in msg &&\n msg.type === 'error');\n}\n/**\n * Generate a unique request ID\n */\nexport function generateRequestId() {\n return `ws_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as ProcessKillResult, A as ResponseHandler, B as FileMetadata, C as CommandClient, D as ErrorResponse, E as ContainerStub, F as ExecEvent, G as LogEvent, H as GitCheckoutResult, I as ExecOptions, J as PortExposeResult, K as MountBucketOptions, L as ExecResult, M as BaseExecOptions, N as BucketCredentials, O as HttpClientOptions, P as BucketProvider, Q as ProcessInfoResult, R as ExecutionSession, S as WriteFileRequest, T as BaseApiResponse, U as ISandbox, V as FileStreamEvent, W as ListFilesOptions, X as Process, Y as PortListResult, Z as ProcessCleanupResult, _ as GitClient, _t as RunCodeOptions, a as CreateSessionRequest, at as SandboxOptions, b as MkdirRequest, c as DeleteSessionResponse, ct as WaitForLogResult, d as ProcessClient, dt as isProcess, et as ProcessListResult, f as PortClient, ft as isProcessStatus, g as GitCheckoutRequest, gt as ExecutionResult, h as InterpreterClient, ht as Execution, i as CommandsResponse, it as ProcessStatus, j as SessionRequest, k as RequestConfig, l as PingResponse, lt as WaitForPortOptions, m as ExecutionCallbacks, mt as CreateContextOptions, n as getSandbox, nt as ProcessOptions, o as CreateSessionResponse, ot as SessionOptions, p as UnexposePortRequest, pt as CodeContext, q as PortCloseResult, r as SandboxClient, rt as ProcessStartResult, s as DeleteSessionRequest, st as StreamOptions, t as Sandbox, tt as ProcessLogsResult, u as UtilityClient, ut as isExecResult, v as FileClient, w as ExecuteResponse, x as ReadFileRequest, y as FileOperationRequest, z as FileChunk } from "./sandbox-CEsJ1edi.js";
|
|
2
2
|
import { a as OperationType, i as ErrorResponse$1, n as ProcessExitedBeforeReadyContext, o as ErrorCode, r as ProcessReadyTimeoutContext } from "./contexts-CdrlvHWK.js";
|
|
3
3
|
|
|
4
4
|
//#region ../shared/dist/request-types.d.ts
|
|
@@ -15,7 +15,7 @@ interface ExecuteRequest {
|
|
|
15
15
|
sessionId?: string;
|
|
16
16
|
background?: boolean;
|
|
17
17
|
timeoutMs?: number;
|
|
18
|
-
env?: Record<string, string>;
|
|
18
|
+
env?: Record<string, string | undefined>;
|
|
19
19
|
cwd?: string;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
@@ -27,11 +27,18 @@ interface StartProcessRequest {
|
|
|
27
27
|
sessionId?: string;
|
|
28
28
|
processId?: string;
|
|
29
29
|
timeoutMs?: number;
|
|
30
|
-
env?: Record<string, string>;
|
|
30
|
+
env?: Record<string, string | undefined>;
|
|
31
31
|
cwd?: string;
|
|
32
32
|
encoding?: string;
|
|
33
33
|
autoCleanup?: boolean;
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Request to expose a port
|
|
37
|
+
*/
|
|
38
|
+
interface ExposePortRequest {
|
|
39
|
+
port: number;
|
|
40
|
+
name?: string;
|
|
41
|
+
}
|
|
35
42
|
//#endregion
|
|
36
43
|
//#region src/errors/classes.d.ts
|
|
37
44
|
/**
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":["ExecuteRequest","Record","StartProcessRequest","ReadFileRequest","WriteFileRequest","DeleteFileRequest","RenameFileRequest","MoveFileRequest","MkdirRequest","FileExistsRequest","ExposePortRequest","GitCheckoutRequest","ListFilesRequest","SessionCreateRequest","SessionDeleteRequest"],"sources":["../../shared/dist/request-types.d.ts","../src/errors/classes.ts","../src/file-stream.ts","../src/interpreter.ts","../src/request-handler.ts","../src/sse-parser.ts","../src/storage-mount/errors.ts"],"sourcesContent":["/**\n * Request types for API calls to the container\n * Single source of truth for the contract between SDK clients and container handlers\n */\n/**\n * Request to execute a command\n */\nexport interface ExecuteRequest {\n command: string;\n sessionId?: string;\n background?: boolean;\n timeoutMs?: number;\n env?: Record<string, string>;\n cwd?: string;\n}\n/**\n * Request to start a background process\n * Uses flat structure consistent with other endpoints\n */\nexport interface StartProcessRequest {\n command: string;\n sessionId?: string;\n processId?: string;\n timeoutMs?: number;\n env?: Record<string, string>;\n cwd?: string;\n encoding?: string;\n autoCleanup?: boolean;\n}\n/**\n * Request to read a file\n */\nexport interface ReadFileRequest {\n path: string;\n encoding?: string;\n sessionId?: string;\n}\n/**\n * Request to write a file\n */\nexport interface WriteFileRequest {\n path: string;\n content: string;\n encoding?: string;\n sessionId?: string;\n}\n/**\n * Request to delete a file\n */\nexport interface DeleteFileRequest {\n path: string;\n sessionId?: string;\n}\n/**\n * Request to rename a file\n */\nexport interface RenameFileRequest {\n oldPath: string;\n newPath: string;\n sessionId?: string;\n}\n/**\n * Request to move a file\n */\nexport interface MoveFileRequest {\n sourcePath: string;\n destinationPath: string;\n sessionId?: string;\n}\n/**\n * Request to create a directory\n */\nexport interface MkdirRequest {\n path: string;\n recursive?: boolean;\n sessionId?: string;\n}\n/**\n * Request to check if a file or directory exists\n */\nexport interface FileExistsRequest {\n path: string;\n sessionId?: string;\n}\n/**\n * Request to expose a port\n */\nexport interface ExposePortRequest {\n port: number;\n name?: string;\n}\n/**\n * Request to clone a Git repository\n */\nexport interface GitCheckoutRequest {\n repoUrl: string;\n branch?: string;\n targetDir?: string;\n sessionId?: string;\n /** Clone depth for shallow clones (e.g., 1 for latest commit only) */\n depth?: number;\n}\n/**\n * Request to list files in a directory\n */\nexport interface ListFilesRequest {\n path: string;\n options?: {\n recursive?: boolean;\n includeHidden?: boolean;\n };\n sessionId?: string;\n}\n/**\n * Request to create a session\n */\nexport interface SessionCreateRequest {\n id?: string;\n name?: string;\n env?: Record<string, string>;\n cwd?: string;\n}\n/**\n * Request to delete a session\n */\nexport interface SessionDeleteRequest {\n sessionId: string;\n}\n//# sourceMappingURL=request-types.d.ts.map"],"mappings":";;;;;;;;;AAOA;AAYA
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":["ExecuteRequest","Record","StartProcessRequest","ReadFileRequest","WriteFileRequest","DeleteFileRequest","RenameFileRequest","MoveFileRequest","MkdirRequest","FileExistsRequest","ExposePortRequest","GitCheckoutRequest","ListFilesRequest","SessionCreateRequest","SessionDeleteRequest"],"sources":["../../shared/dist/request-types.d.ts","../src/errors/classes.ts","../src/file-stream.ts","../src/interpreter.ts","../src/request-handler.ts","../src/sse-parser.ts","../src/storage-mount/errors.ts"],"sourcesContent":["/**\n * Request types for API calls to the container\n * Single source of truth for the contract between SDK clients and container handlers\n */\n/**\n * Request to execute a command\n */\nexport interface ExecuteRequest {\n command: string;\n sessionId?: string;\n background?: boolean;\n timeoutMs?: number;\n env?: Record<string, string | undefined>;\n cwd?: string;\n}\n/**\n * Request to start a background process\n * Uses flat structure consistent with other endpoints\n */\nexport interface StartProcessRequest {\n command: string;\n sessionId?: string;\n processId?: string;\n timeoutMs?: number;\n env?: Record<string, string | undefined>;\n cwd?: string;\n encoding?: string;\n autoCleanup?: boolean;\n}\n/**\n * Request to read a file\n */\nexport interface ReadFileRequest {\n path: string;\n encoding?: string;\n sessionId?: string;\n}\n/**\n * Request to write a file\n */\nexport interface WriteFileRequest {\n path: string;\n content: string;\n encoding?: string;\n sessionId?: string;\n}\n/**\n * Request to delete a file\n */\nexport interface DeleteFileRequest {\n path: string;\n sessionId?: string;\n}\n/**\n * Request to rename a file\n */\nexport interface RenameFileRequest {\n oldPath: string;\n newPath: string;\n sessionId?: string;\n}\n/**\n * Request to move a file\n */\nexport interface MoveFileRequest {\n sourcePath: string;\n destinationPath: string;\n sessionId?: string;\n}\n/**\n * Request to create a directory\n */\nexport interface MkdirRequest {\n path: string;\n recursive?: boolean;\n sessionId?: string;\n}\n/**\n * Request to check if a file or directory exists\n */\nexport interface FileExistsRequest {\n path: string;\n sessionId?: string;\n}\n/**\n * Request to expose a port\n */\nexport interface ExposePortRequest {\n port: number;\n name?: string;\n}\n/**\n * Request to clone a Git repository\n */\nexport interface GitCheckoutRequest {\n repoUrl: string;\n branch?: string;\n targetDir?: string;\n sessionId?: string;\n /** Clone depth for shallow clones (e.g., 1 for latest commit only) */\n depth?: number;\n}\n/**\n * Request to list files in a directory\n */\nexport interface ListFilesRequest {\n path: string;\n options?: {\n recursive?: boolean;\n includeHidden?: boolean;\n };\n sessionId?: string;\n}\n/**\n * Request to create a session\n */\nexport interface SessionCreateRequest {\n id?: string;\n name?: string;\n env?: Record<string, string | undefined>;\n cwd?: string;\n}\n/**\n * Request to delete a session\n */\nexport interface SessionDeleteRequest {\n sessionId: string;\n}\n//# sourceMappingURL=request-types.d.ts.map"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as isExecResult, c as shellEscape, d as TraceContext, f as Execution, g as
|
|
1
|
+
import { _ as getEnvString, a as isExecResult, c as shellEscape, d as TraceContext, f as Execution, g as filterEnvVars, h as extractRepoName, i as isWSStreamChunk, l as createLogger, m as GitLogger, n as isWSError, o as isProcess, p as ResultImpl, r as isWSResponse, s as isProcessStatus, t as generateRequestId, u as createNoOpLogger, v as partitionEnvVars } from "./dist-BpbdH8ry.js";
|
|
2
2
|
import { t as ErrorCode } from "./errors-BCXUmJUn.js";
|
|
3
3
|
import { Container, getContainer, switchPort } from "@cloudflare/containers";
|
|
4
4
|
|
|
@@ -2273,21 +2273,29 @@ async function proxyToSandbox(request, env) {
|
|
|
2273
2273
|
}
|
|
2274
2274
|
}
|
|
2275
2275
|
function extractSandboxRoute(url) {
|
|
2276
|
-
const
|
|
2277
|
-
if (
|
|
2278
|
-
const
|
|
2279
|
-
|
|
2280
|
-
const
|
|
2281
|
-
|
|
2276
|
+
const dotIndex = url.hostname.indexOf(".");
|
|
2277
|
+
if (dotIndex === -1) return null;
|
|
2278
|
+
const subdomain = url.hostname.slice(0, dotIndex);
|
|
2279
|
+
url.hostname.slice(dotIndex + 1);
|
|
2280
|
+
const firstHyphen = subdomain.indexOf("-");
|
|
2281
|
+
if (firstHyphen === -1) return null;
|
|
2282
|
+
const portStr = subdomain.slice(0, firstHyphen);
|
|
2283
|
+
if (!/^\d{4,5}$/.test(portStr)) return null;
|
|
2282
2284
|
const port = parseInt(portStr, 10);
|
|
2283
2285
|
if (!validatePort(port)) return null;
|
|
2286
|
+
const rest = subdomain.slice(firstHyphen + 1);
|
|
2287
|
+
const lastHyphen = rest.lastIndexOf("-");
|
|
2288
|
+
if (lastHyphen === -1) return null;
|
|
2289
|
+
const sandboxId = rest.slice(0, lastHyphen);
|
|
2290
|
+
const token = rest.slice(lastHyphen + 1);
|
|
2291
|
+
if (!/^[a-z0-9_]+$/.test(token) || token.length === 0 || token.length > 63) return null;
|
|
2292
|
+
if (sandboxId.length === 0 || sandboxId.length > 63) return null;
|
|
2284
2293
|
let sanitizedSandboxId;
|
|
2285
2294
|
try {
|
|
2286
2295
|
sanitizedSandboxId = sanitizeSandboxId(sandboxId);
|
|
2287
|
-
} catch
|
|
2296
|
+
} catch {
|
|
2288
2297
|
return null;
|
|
2289
2298
|
}
|
|
2290
|
-
if (sandboxId.length > 63) return null;
|
|
2291
2299
|
return {
|
|
2292
2300
|
port,
|
|
2293
2301
|
sandboxId: sanitizedSandboxId,
|
|
@@ -2509,6 +2517,30 @@ function resolveS3fsOptions(provider, userOptions) {
|
|
|
2509
2517
|
return Array.from(flagMap.values());
|
|
2510
2518
|
}
|
|
2511
2519
|
|
|
2520
|
+
//#endregion
|
|
2521
|
+
//#region src/storage-mount/validation.ts
|
|
2522
|
+
function validatePrefix(prefix) {
|
|
2523
|
+
if (!prefix.startsWith("/")) throw new InvalidMountConfigError(`Prefix must start with '/': "${prefix}"`);
|
|
2524
|
+
}
|
|
2525
|
+
function validateBucketName(bucket, mountPath) {
|
|
2526
|
+
if (bucket.includes(":")) {
|
|
2527
|
+
const [bucketName, prefixPart] = bucket.split(":");
|
|
2528
|
+
throw new InvalidMountConfigError(`Bucket name cannot contain ':'. To mount a prefix, use the 'prefix' option:\n mountBucket('${bucketName}', '${mountPath}', { ...options, prefix: '${prefixPart}' })`);
|
|
2529
|
+
}
|
|
2530
|
+
if (!/^[a-z0-9]([a-z0-9.-]{0,61}[a-z0-9])?$/.test(bucket)) throw new InvalidMountConfigError(`Invalid bucket name: "${bucket}". Bucket names must be 3-63 characters, lowercase alphanumeric, dots, or hyphens, and cannot start/end with dots or hyphens.`);
|
|
2531
|
+
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Builds the s3fs source string from bucket name and optional prefix.
|
|
2534
|
+
* Format: "bucket" or "bucket:/prefix/" for subdirectory mounts.
|
|
2535
|
+
*
|
|
2536
|
+
* @param bucket - The bucket name
|
|
2537
|
+
* @param prefix - Optional prefix/subdirectory path
|
|
2538
|
+
* @returns The s3fs source string
|
|
2539
|
+
*/
|
|
2540
|
+
function buildS3fsSource(bucket, prefix) {
|
|
2541
|
+
return prefix ? `${bucket}:${prefix}` : bucket;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2512
2544
|
//#endregion
|
|
2513
2545
|
//#region src/version.ts
|
|
2514
2546
|
/**
|
|
@@ -2516,7 +2548,7 @@ function resolveS3fsOptions(provider, userOptions) {
|
|
|
2516
2548
|
* This file is auto-updated by .github/changeset-version.ts during releases
|
|
2517
2549
|
* DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
|
|
2518
2550
|
*/
|
|
2519
|
-
const SDK_VERSION = "0.
|
|
2551
|
+
const SDK_VERSION = "0.7.0";
|
|
2520
2552
|
|
|
2521
2553
|
//#endregion
|
|
2522
2554
|
//#region src/sandbox.ts
|
|
@@ -2631,18 +2663,25 @@ var Sandbox = class extends Container {
|
|
|
2631
2663
|
async setKeepAlive(keepAlive) {
|
|
2632
2664
|
this.keepAliveEnabled = keepAlive;
|
|
2633
2665
|
await this.ctx.storage.put("keepAliveEnabled", keepAlive);
|
|
2634
|
-
if (keepAlive) this.logger.info("KeepAlive mode enabled - container will stay alive until explicitly destroyed");
|
|
2635
|
-
else this.logger.info("KeepAlive mode disabled - container will timeout normally");
|
|
2636
2666
|
}
|
|
2637
2667
|
async setEnvVars(envVars) {
|
|
2668
|
+
const { toSet, toUnset } = partitionEnvVars(envVars);
|
|
2669
|
+
for (const key of toUnset) delete this.envVars[key];
|
|
2638
2670
|
this.envVars = {
|
|
2639
2671
|
...this.envVars,
|
|
2640
|
-
...
|
|
2672
|
+
...toSet
|
|
2641
2673
|
};
|
|
2642
|
-
if (this.defaultSession)
|
|
2643
|
-
const
|
|
2644
|
-
|
|
2645
|
-
|
|
2674
|
+
if (this.defaultSession) {
|
|
2675
|
+
for (const key of toUnset) {
|
|
2676
|
+
const unsetCommand = `unset ${key}`;
|
|
2677
|
+
const result = await this.client.commands.execute(unsetCommand, this.defaultSession);
|
|
2678
|
+
if (result.exitCode !== 0) throw new Error(`Failed to unset ${key}: ${result.stderr || "Unknown error"}`);
|
|
2679
|
+
}
|
|
2680
|
+
for (const [key, value] of Object.entries(toSet)) {
|
|
2681
|
+
const exportCommand = `export ${key}=${shellEscape(value)}`;
|
|
2682
|
+
const result = await this.client.commands.execute(exportCommand, this.defaultSession);
|
|
2683
|
+
if (result.exitCode !== 0) throw new Error(`Failed to set ${key}: ${result.stderr || "Unknown error"}`);
|
|
2684
|
+
}
|
|
2646
2685
|
}
|
|
2647
2686
|
}
|
|
2648
2687
|
/**
|
|
@@ -2693,13 +2732,21 @@ var Sandbox = class extends Container {
|
|
|
2693
2732
|
}
|
|
2694
2733
|
async mountBucket(bucket, mountPath, options) {
|
|
2695
2734
|
this.logger.info(`Mounting bucket ${bucket} to ${mountPath}`);
|
|
2696
|
-
|
|
2735
|
+
const prefix = options.prefix || void 0;
|
|
2736
|
+
this.validateMountOptions(bucket, mountPath, {
|
|
2737
|
+
...options,
|
|
2738
|
+
prefix
|
|
2739
|
+
});
|
|
2740
|
+
const s3fsSource = buildS3fsSource(bucket, prefix);
|
|
2697
2741
|
const provider = options.provider || detectProviderFromUrl(options.endpoint);
|
|
2698
|
-
this.logger.debug(`Detected provider: ${provider || "unknown"}`, {
|
|
2742
|
+
this.logger.debug(`Detected provider: ${provider || "unknown"}`, {
|
|
2743
|
+
explicitProvider: options.provider,
|
|
2744
|
+
prefix
|
|
2745
|
+
});
|
|
2699
2746
|
const credentials = detectCredentials(options, this.envVars);
|
|
2700
2747
|
const passwordFilePath = this.generatePasswordFilePath();
|
|
2701
2748
|
this.activeMounts.set(mountPath, {
|
|
2702
|
-
bucket,
|
|
2749
|
+
bucket: s3fsSource,
|
|
2703
2750
|
mountPath,
|
|
2704
2751
|
endpoint: options.endpoint,
|
|
2705
2752
|
provider,
|
|
@@ -2709,9 +2756,9 @@ var Sandbox = class extends Container {
|
|
|
2709
2756
|
try {
|
|
2710
2757
|
await this.createPasswordFile(passwordFilePath, bucket, credentials);
|
|
2711
2758
|
await this.exec(`mkdir -p ${shellEscape(mountPath)}`);
|
|
2712
|
-
await this.executeS3FSMount(
|
|
2759
|
+
await this.executeS3FSMount(s3fsSource, mountPath, options, provider, passwordFilePath);
|
|
2713
2760
|
this.activeMounts.set(mountPath, {
|
|
2714
|
-
bucket,
|
|
2761
|
+
bucket: s3fsSource,
|
|
2715
2762
|
mountPath,
|
|
2716
2763
|
endpoint: options.endpoint,
|
|
2717
2764
|
provider,
|
|
@@ -2754,9 +2801,10 @@ var Sandbox = class extends Container {
|
|
|
2754
2801
|
} catch (error) {
|
|
2755
2802
|
throw new InvalidMountConfigError(`Invalid endpoint URL: "${options.endpoint}". Must be a valid HTTP(S) URL.`);
|
|
2756
2803
|
}
|
|
2757
|
-
|
|
2804
|
+
validateBucketName(bucket, mountPath);
|
|
2758
2805
|
if (!mountPath.startsWith("/")) throw new InvalidMountConfigError(`Mount path must be absolute (start with /): "${mountPath}"`);
|
|
2759
2806
|
if (this.activeMounts.has(mountPath)) throw new InvalidMountConfigError(`Mount path "${mountPath}" is already in use by bucket "${this.activeMounts.get(mountPath)?.bucket}". Unmount the existing bucket first or use a different mount path.`);
|
|
2807
|
+
if (options.prefix !== void 0) validatePrefix(options.prefix);
|
|
2760
2808
|
}
|
|
2761
2809
|
/**
|
|
2762
2810
|
* Generate unique password file path for s3fs credentials
|
|
@@ -3391,7 +3439,7 @@ var Sandbox = class extends Container {
|
|
|
3391
3439
|
const requestOptions = {
|
|
3392
3440
|
...options?.processId !== void 0 && { processId: options.processId },
|
|
3393
3441
|
...options?.timeout !== void 0 && { timeoutMs: options.timeout },
|
|
3394
|
-
...options?.env !== void 0 && { env: options.env },
|
|
3442
|
+
...options?.env !== void 0 && { env: filterEnvVars(options.env) },
|
|
3395
3443
|
...options?.cwd !== void 0 && { cwd: options.cwd },
|
|
3396
3444
|
...options?.encoding !== void 0 && { encoding: options.encoding },
|
|
3397
3445
|
...options?.autoCleanup !== void 0 && { autoCleanup: options.autoCleanup }
|
|
@@ -3559,6 +3607,30 @@ var Sandbox = class extends Container {
|
|
|
3559
3607
|
const session = sessionId ?? await this.ensureDefaultSession();
|
|
3560
3608
|
return this.client.files.exists(path, session);
|
|
3561
3609
|
}
|
|
3610
|
+
/**
|
|
3611
|
+
* Expose a port and get a preview URL for accessing services running in the sandbox
|
|
3612
|
+
*
|
|
3613
|
+
* @param port - Port number to expose (1024-65535)
|
|
3614
|
+
* @param options - Configuration options
|
|
3615
|
+
* @param options.hostname - Your Worker's domain name (required for preview URL construction)
|
|
3616
|
+
* @param options.name - Optional friendly name for the port
|
|
3617
|
+
* @param options.token - Optional custom token for the preview URL (1-16 characters: lowercase letters, numbers, hyphens, underscores)
|
|
3618
|
+
* If not provided, a random 16-character token will be generated automatically
|
|
3619
|
+
* @returns Preview URL information including the full URL, port number, and optional name
|
|
3620
|
+
*
|
|
3621
|
+
* @example
|
|
3622
|
+
* // With auto-generated token
|
|
3623
|
+
* const { url } = await sandbox.exposePort(8080, { hostname: 'example.com' });
|
|
3624
|
+
* // url: https://8080-sandbox-id-abc123random4567.example.com
|
|
3625
|
+
*
|
|
3626
|
+
* @example
|
|
3627
|
+
* // With custom token for stable URLs across deployments
|
|
3628
|
+
* const { url } = await sandbox.exposePort(8080, {
|
|
3629
|
+
* hostname: 'example.com',
|
|
3630
|
+
* token: 'my-token-v1'
|
|
3631
|
+
* });
|
|
3632
|
+
* // url: https://8080-sandbox-id-my-token-v1.example.com
|
|
3633
|
+
*/
|
|
3562
3634
|
async exposePort(port, options) {
|
|
3563
3635
|
if (options.hostname.endsWith(".workers.dev")) throw new CustomDomainRequiredError({
|
|
3564
3636
|
code: ErrorCode.CUSTOM_DOMAIN_REQUIRED,
|
|
@@ -3567,11 +3639,17 @@ var Sandbox = class extends Container {
|
|
|
3567
3639
|
httpStatus: 400,
|
|
3568
3640
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3569
3641
|
});
|
|
3570
|
-
const sessionId = await this.ensureDefaultSession();
|
|
3571
|
-
await this.client.ports.exposePort(port, sessionId, options?.name);
|
|
3572
3642
|
if (!this.sandboxName) throw new Error("Sandbox name not available. Ensure sandbox is accessed through getSandbox()");
|
|
3573
|
-
|
|
3643
|
+
let token;
|
|
3644
|
+
if (options.token !== void 0) {
|
|
3645
|
+
this.validateCustomToken(options.token);
|
|
3646
|
+
token = options.token;
|
|
3647
|
+
} else token = this.generatePortToken();
|
|
3574
3648
|
const tokens = await this.ctx.storage.get("portTokens") || {};
|
|
3649
|
+
const existingPort = Object.entries(tokens).find(([p, t]) => t === token && p !== port.toString());
|
|
3650
|
+
if (existingPort) throw new SecurityError(`Token '${token}' is already in use by port ${existingPort[0]}. Please use a different token.`);
|
|
3651
|
+
const sessionId = await this.ensureDefaultSession();
|
|
3652
|
+
await this.client.ports.exposePort(port, sessionId, options?.name);
|
|
3575
3653
|
tokens[port.toString()] = token;
|
|
3576
3654
|
await this.ctx.storage.put("portTokens", tokens);
|
|
3577
3655
|
return {
|
|
@@ -3621,12 +3699,21 @@ var Sandbox = class extends Container {
|
|
|
3621
3699
|
this.logger.error("Port is exposed but has no token - bug detected", void 0, { port });
|
|
3622
3700
|
return false;
|
|
3623
3701
|
}
|
|
3624
|
-
|
|
3702
|
+
if (storedToken.length !== token.length) return false;
|
|
3703
|
+
const encoder = new TextEncoder();
|
|
3704
|
+
const a = encoder.encode(storedToken);
|
|
3705
|
+
const b = encoder.encode(token);
|
|
3706
|
+
return crypto.subtle.timingSafeEqual(a, b);
|
|
3707
|
+
}
|
|
3708
|
+
validateCustomToken(token) {
|
|
3709
|
+
if (token.length === 0) throw new SecurityError(`Custom token cannot be empty.`);
|
|
3710
|
+
if (token.length > 16) throw new SecurityError(`Custom token too long. Maximum 16 characters allowed. Received: ${token.length} characters.`);
|
|
3711
|
+
if (!/^[a-z0-9_]+$/.test(token)) throw new SecurityError(`Custom token must contain only lowercase letters (a-z), numbers (0-9), and underscores (_). Invalid token provided.`);
|
|
3625
3712
|
}
|
|
3626
3713
|
generatePortToken() {
|
|
3627
3714
|
const array = new Uint8Array(12);
|
|
3628
3715
|
crypto.getRandomValues(array);
|
|
3629
|
-
return btoa(String.fromCharCode(...array)).replace(/\+/g, "
|
|
3716
|
+
return btoa(String.fromCharCode(...array)).replace(/\+/g, "_").replace(/\//g, "_").replace(/=/g, "").toLowerCase();
|
|
3630
3717
|
}
|
|
3631
3718
|
constructPreviewUrl(port, sandboxId, hostname, token) {
|
|
3632
3719
|
if (!validatePort(port)) throw new SecurityError(`Invalid port number: ${port}. Must be between 1024-65535 and not reserved.`);
|
|
@@ -3659,11 +3746,11 @@ var Sandbox = class extends Container {
|
|
|
3659
3746
|
*/
|
|
3660
3747
|
async createSession(options) {
|
|
3661
3748
|
const sessionId = options?.id || `session-${Date.now()}`;
|
|
3662
|
-
const
|
|
3749
|
+
const filteredEnv = filterEnvVars({
|
|
3663
3750
|
...this.envVars,
|
|
3664
3751
|
...options?.env ?? {}
|
|
3665
|
-
};
|
|
3666
|
-
const envPayload = Object.keys(
|
|
3752
|
+
});
|
|
3753
|
+
const envPayload = Object.keys(filteredEnv).length > 0 ? filteredEnv : void 0;
|
|
3667
3754
|
await this.client.utils.createSession({
|
|
3668
3755
|
id: sessionId,
|
|
3669
3756
|
...envPayload && { env: envPayload },
|
|
@@ -3743,9 +3830,15 @@ var Sandbox = class extends Container {
|
|
|
3743
3830
|
sessionId
|
|
3744
3831
|
}),
|
|
3745
3832
|
setEnvVars: async (envVars) => {
|
|
3833
|
+
const { toSet, toUnset } = partitionEnvVars(envVars);
|
|
3746
3834
|
try {
|
|
3747
|
-
for (const
|
|
3748
|
-
const
|
|
3835
|
+
for (const key of toUnset) {
|
|
3836
|
+
const unsetCommand = `unset ${key}`;
|
|
3837
|
+
const result = await this.client.commands.execute(unsetCommand, sessionId);
|
|
3838
|
+
if (result.exitCode !== 0) throw new Error(`Failed to unset ${key}: ${result.stderr || "Unknown error"}`);
|
|
3839
|
+
}
|
|
3840
|
+
for (const [key, value] of Object.entries(toSet)) {
|
|
3841
|
+
const exportCommand = `export ${key}=${shellEscape(value)}`;
|
|
3749
3842
|
const result = await this.client.commands.execute(exportCommand, sessionId);
|
|
3750
3843
|
if (result.exitCode !== 0) throw new Error(`Failed to set ${key}: ${result.stderr || "Unknown error"}`);
|
|
3751
3844
|
}
|