Skip to content

Commit 059e2d9

Browse files
committed
Merge remote-tracking branch 'origin/main' into base-image
2 parents 3f71ec3 + 9166bff commit 059e2d9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+5210
-5106
lines changed

.changeset/top-level-await.md

Lines changed: 0 additions & 15 deletions
This file was deleted.

.github/workflows/claude-code-review.yml

Lines changed: 298 additions & 96 deletions
Large diffs are not rendered by default.

.github/workflows/release.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ on:
55
branches:
66
- main
77

8+
permissions:
9+
id-token: write # Required for OIDC
10+
contents: read
11+
812
concurrency:
913
group: ${{ github.workflow }}-${{ github.ref }}
1014
cancel-in-progress: true
@@ -144,6 +148,7 @@ jobs:
144148
runs-on: ubuntu-latest
145149
timeout-minutes: 30
146150
permissions:
151+
id-token: write # Required for trusted publishing
147152
contents: write
148153
pull-requests: write
149154

@@ -155,12 +160,16 @@ jobs:
155160
- uses: actions/setup-node@v4
156161
with:
157162
node-version: 24
163+
registry-url: 'https://registry.npmjs.org'
158164
cache: 'npm'
159165

160166
- uses: oven-sh/setup-bun@v2
161167
with:
162168
bun-version: latest
163169

170+
- name: Upgrade npm for OIDC trusted publishing
171+
run: npm install -g npm@latest
172+
164173
- name: Install dependencies
165174
run: npm ci
166175

@@ -220,8 +229,7 @@ jobs:
220229
publish: npx tsx .github/changeset-publish.ts
221230
env:
222231
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
223-
NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
224-
NPM_PUBLISH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
232+
NPM_CONFIG_PROVENANCE: true
225233

226234
- name: Upload standalone binary to GitHub release
227235
if: steps.changesets.outputs.published == 'true'

CLAUDE.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ npm run build:clean # Force rebuild without cache
6464
# Unit tests (runs in Workers runtime with vitest-pool-workers)
6565
npm test
6666

67-
# E2E tests (requires Docker, runs sequentially due to container provisioning)
67+
# E2E tests (requires Docker)
6868
npm run test:e2e
6969

7070
# Run a single E2E test file
@@ -74,7 +74,7 @@ npm run test:e2e -- -- tests/e2e/process-lifecycle-workflow.test.ts
7474
npm run test:e2e -- -- tests/e2e/git-clone-workflow.test.ts -t 'test name'
7575
```
7676

77-
**Important**: E2E tests (`tests/e2e/`) run sequentially (not in parallel) to avoid container resource contention. Each test spawns its own wrangler dev instance.
77+
**Important**: E2E tests share a single sandbox container for performance. Tests run in parallel using unique sessions for isolation.
7878

7979
### Code Quality
8080

@@ -211,11 +211,12 @@ npm run test:e2e -- -- tests/e2e/git-clone-workflow.test.ts -t 'should handle cl
211211
**Architecture:**
212212

213213
- Tests in `tests/e2e/` run against real Cloudflare Workers + Docker containers
214-
- **In CI**: Tests deploy to actual Cloudflare infrastructure and run against deployed workers
215-
- **Locally**: Each test file spawns its own `wrangler dev` instance
214+
- **Shared sandbox**: All tests share ONE container, using sessions for isolation
215+
- **In CI**: Tests deploy to actual Cloudflare infrastructure
216+
- **Locally**: Global setup spawns wrangler dev once, all tests share it
216217
- Config: `vitest.e2e.config.ts` (root level)
217-
- Sequential execution (`singleFork: true`) to prevent container resource contention
218-
- Longer timeouts (2min per test) for container operations
218+
- Parallel execution via thread pool (~30s for full suite)
219+
- See `docs/E2E_TESTING.md` for writing tests
219220

220221
**Build system trust:** The monorepo build system (turbo + npm workspaces) is robust and handles all package dependencies automatically. E2E tests always run against the latest built code - there's no need to manually rebuild or worry about stale builds unless explicitly working on the build setup itself.
221222

CONTRIBUTING.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,11 @@ Located in `tests/e2e/`:
165165

166166
- Test full workflows against real Workers and containers
167167
- Require Docker
168-
- Slower but comprehensive
168+
- Share a single sandbox container for performance (~30s for full suite)
169+
- Use sessions for test isolation
169170

170171
Run with: `npm run test:e2e`
171172

172-
You can also run specific test files or individual tests:
173-
174173
```bash
175174
# Run a single E2E test file
176175
npm run test:e2e -- -- tests/e2e/process-lifecycle-workflow.test.ts
@@ -179,12 +178,15 @@ npm run test:e2e -- -- tests/e2e/process-lifecycle-workflow.test.ts
179178
npm run test:e2e -- -- tests/e2e/git-clone-workflow.test.ts -t 'should handle cloning to default directory'
180179
```
181180

181+
**See `docs/E2E_TESTING.md` for the complete guide on writing E2E tests.**
182+
182183
### Writing Tests
183184

184185
- Write tests for new features
185186
- Add regression tests for bug fixes
186187
- Ensure tests are deterministic (no flaky tests)
187188
- Use descriptive test names
189+
- For E2E tests: use `getSharedSandbox()` and `createUniqueSession()` for isolation
188190

189191
## Documentation
190192

docs/E2E_TESTING.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# E2E Testing Guide
2+
3+
E2E tests validate full workflows against real Cloudflare Workers and Docker containers.
4+
5+
## Architecture
6+
7+
All E2E tests share a **single sandbox container** for performance. Test isolation is achieved through **sessions** - each test file gets a unique session that provides isolated shell state (env vars, working directory) within the shared container.
8+
9+
```
10+
┌─────────────────────────────────────────────────────┐
11+
│ Shared Sandbox │
12+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
13+
│ │ Session A │ │ Session B │ │ Session C │ │
14+
│ │ (test 1) │ │ (test 2) │ │ (test 3) │ │
15+
│ └─────────────┘ └─────────────┘ └─────────────┘ │
16+
│ │
17+
│ Shared filesystem & processes │
18+
└─────────────────────────────────────────────────────┘
19+
```
20+
21+
**Key files:**
22+
23+
- `tests/e2e/global-setup.ts` - Creates sandbox before tests, warms containers
24+
- `tests/e2e/helpers/global-sandbox.ts` - Provides `getSharedSandbox()` API
25+
- `vitest.e2e.config.ts` - Configures parallel execution with global setup
26+
27+
## Writing Tests
28+
29+
### Basic Template
30+
31+
```typescript
32+
import { describe, test, expect, beforeAll } from 'vitest';
33+
import {
34+
getSharedSandbox,
35+
createUniqueSession
36+
} from './helpers/global-sandbox';
37+
38+
describe('My Feature', () => {
39+
let workerUrl: string;
40+
let headers: Record<string, string>;
41+
42+
beforeAll(async () => {
43+
const sandbox = await getSharedSandbox();
44+
workerUrl = sandbox.workerUrl;
45+
headers = sandbox.createHeaders(createUniqueSession());
46+
}, 120000);
47+
48+
test('should do something', async () => {
49+
const response = await fetch(`${workerUrl}/api/execute`, {
50+
method: 'POST',
51+
headers,
52+
body: JSON.stringify({ command: 'echo hello' })
53+
});
54+
expect(response.status).toBe(200);
55+
}, 60000);
56+
});
57+
```
58+
59+
### Using Python Image
60+
61+
For tests requiring Python (code interpreter, etc.):
62+
63+
```typescript
64+
beforeAll(async () => {
65+
const sandbox = await getSharedSandbox();
66+
workerUrl = sandbox.workerUrl;
67+
// Use createPythonHeaders instead of createHeaders
68+
headers = sandbox.createPythonHeaders(createUniqueSession());
69+
}, 120000);
70+
```
71+
72+
### File Isolation
73+
74+
Since the filesystem is shared, use unique paths to avoid conflicts:
75+
76+
```typescript
77+
const sandbox = await getSharedSandbox();
78+
const testDir = sandbox.uniquePath('my-feature'); // /workspace/test-abc123/my-feature
79+
80+
await fetch(`${workerUrl}/api/file/write`, {
81+
method: 'POST',
82+
headers,
83+
body: JSON.stringify({
84+
path: `${testDir}/config.json`,
85+
content: '{"key": "value"}'
86+
})
87+
});
88+
```
89+
90+
### Port Usage
91+
92+
Ports must be exposed in the Dockerfile. Currently exposed:
93+
94+
- `8080` - General testing
95+
- `9090`, `9091`, `9092` - Process readiness tests
96+
- `9998` - Process lifecycle tests
97+
- `9999` - WebSocket tests
98+
99+
To use a new port:
100+
101+
1. Add it to both `tests/e2e/test-worker/Dockerfile` and `Dockerfile.python`
102+
2. Document which test uses it
103+
104+
### Process Cleanup
105+
106+
Always clean up background processes:
107+
108+
```typescript
109+
test('should start server', async () => {
110+
const startRes = await fetch(`${workerUrl}/api/process/start`, {
111+
method: 'POST',
112+
headers,
113+
body: JSON.stringify({ command: 'bun run server.js' })
114+
});
115+
const { id: processId } = await startRes.json();
116+
117+
// ... test logic ...
118+
119+
// Cleanup
120+
await fetch(`${workerUrl}/api/process/${processId}`, {
121+
method: 'DELETE',
122+
headers
123+
});
124+
}, 60000);
125+
```
126+
127+
## Test Organization
128+
129+
| File | Purpose |
130+
| --------------------------------------- | ---------------------------- |
131+
| `comprehensive-workflow.test.ts` | Happy path integration tests |
132+
| `process-lifecycle-workflow.test.ts` | Error handling for processes |
133+
| `process-readiness-workflow.test.ts` | waitForLog/waitForPort tests |
134+
| `code-interpreter-workflow.test.ts` | Python/JS code execution |
135+
| `file-operations-workflow.test.ts` | File read/write/list |
136+
| `streaming-operations-workflow.test.ts` | Streaming command output |
137+
| `websocket-workflow.test.ts` | WebSocket connections |
138+
| `bucket-mounting.test.ts` | R2 bucket mounting (CI only) |
139+
140+
## Running Tests
141+
142+
```bash
143+
# All E2E tests
144+
npm run test:e2e
145+
146+
# Single file
147+
npm run test:e2e -- -- tests/e2e/process-lifecycle-workflow.test.ts
148+
149+
# Single test by name
150+
npm run test:e2e -- -- tests/e2e/git-clone-workflow.test.ts -t 'should clone repo'
151+
```
152+
153+
## Debugging
154+
155+
- Tests auto-retry once on failure (`retry: 1` in config)
156+
- Global setup logs sandbox ID on startup - check for initialization errors
157+
- If tests fail on first run only, the container might not be warmed (check global-setup.ts initializes the right image type)
158+
- Port conflicts: check no other test uses the same port
159+
160+
## What NOT to Do
161+
162+
- **Don't create new sandboxes unless strictly necessary** - use `getSharedSandbox()`
163+
- **Don't skip cleanup** - leaked processes affect other tests
164+
- **Don't use hardcoded ports** without adding to Dockerfile
165+
- **Don't rely on filesystem state** from other tests - use unique paths

examples/claude-code/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM docker.io/cloudflare/sandbox:0.6.0
1+
FROM docker.io/cloudflare/sandbox:0.6.3
22
RUN npm install -g @anthropic-ai/claude-code
33
ENV COMMAND_TIMEOUT_MS=300000
44
EXPOSE 3000
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
FROM docker.io/cloudflare/sandbox:0.6.0
1+
FROM docker.io/cloudflare/sandbox:0.6.3-python

examples/code-interpreter/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ async function callCloudflareAPI(
4343

4444
async function executePythonCode(env: Env, code: string): Promise<string> {
4545
const sandboxId = env.Sandbox.idFromName('default');
46-
const sandbox = getSandbox(env.Sandbox, sandboxId.toString());
46+
const sandbox = getSandbox(env.Sandbox, sandboxId.toString().slice(0, 63));
4747
const pythonCtx = await sandbox.createCodeContext({ language: 'python' });
4848
const result = await sandbox.runCode(code, {
4949
context: pythonCtx

examples/minimal/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM docker.io/cloudflare/sandbox:0.6.0
1+
FROM docker.io/cloudflare/sandbox:0.6.3
22

33
# Required during local development to access exposed ports
44
EXPOSE 8080

0 commit comments

Comments
 (0)