fix(js-sdk): unpin useDefineForClassFields — make caller-directory resolution emit-invariant - #1539
Conversation
🦋 Changeset detectedLatest commit: 75c63ed The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
PR SummaryMedium Risk Overview Default resolution now runs in the Reviewed by Cursor Bugbot for commit 75c63ed. Bugbot is set up for automated code reviews on this repo. Configure here. |
Package ArtifactsBuilt from 1377cc2. Download artifacts from this workflow run. JS SDK ( npm install ./e2b-2.32.1-fix-usedefineforclassfields.0.tgzCLI ( npm install ./e2b-cli-2.13.2-fix-usedefineforclassfields.0.tgzPython SDK ( pip install ./e2b-2.31.0+fix.usedefineforclassfields-py3-none-any.whl |
Resolve the template builder's default fileContextPath in the constructor body instead of a class field initializer. With [[Define]] class-field semantics (the default at target es2022+), field initializers run in an extra <instance_members_initializer> stack frame, shifting the fixed-depth stack walk in getCallerDirectory by one — .copy() sources resolved against the SDK's own directory instead of the caller's. The constructor-body call is emit-invariant, so the useDefineForClassFields: false pin in the js-sdk tsconfig is no longer needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9cd65a4 to
75c63ed
Compare
…depth (#1599) ## Description Template build stack traces were captured by walking a fixed number of frames (`STACK_TRACE_DEPTH` plus `±1` arithmetic at ~15 call sites), which broke whenever the frame count between `new Error()` and user code shifted — TS class-field initializer frames (#1539) and Bun's tail-call frame elision were both this bug. This PR makes two related changes: 1. **Boundary-based frame selection.** The caller's frame is now the first one whose file lies outside the SDK package, making extra transpiler frames and elided delegating frames irrelevant. In the JS SDK, frame parsing is delegated to `error-stack-parser-es` (ESM-only, so it's a devDependency inlined into both dist formats via tsdown `noExternal` — the engines range includes Node versions without `require(esm)`); the Python SDK equivalently walks `f_back` until `co_filename` leaves the `e2b` package root, in the shared builder used by both sync and async. If no user frame is identifiable (e.g. the SDK is bundled into the caller's own file), capture degrades to no trace rather than a wrong frame. 2. **Dead machinery removed.** Because boundary capture resolves through SDK-internal delegation (`remove()` → `runCmd()`, `fromDockerfile()` → parser) to the user's call site on its own, the suppress/override collection machinery (`runInNewStackTraceContext`, `runInStackTraceOverrideContext`, the enabled/override flags, and their Python equivalents) became redundant and is removed — superseding the approach in #1596. Error `.stack` synthesis (keeping the `Name: message` header and the throw site on `cause`) was prototyped here and backed out — it will come as a follow-up PR. ## Usage No API changes — build errors now point at the user's call site regardless of runtime or transpiler: ```ts const template = Template() .fromBaseImage() .runCmd('./does-not-exist') // ← build failures point exactly here await Template.build(template, 'my-template') ``` ## Testing - JS: `unit` + `template` vitest projects green against the real API (incl. 27 per-method stacktrace tests pinning exact call-site line/columns, `bunInstall` now covered); edge-compat bundle test and CLI build verified; built CJS/ESM dists smoke-tested with `require()`/`import()`. - Python: all 184 template tests green (shared + sync + async, incl. both `test_stacktrace.py` suites, `bun_install` now covered); `ruff` and `ty` clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Follow-up to #1536, which pinned
useDefineForClassFields: falsein the js-sdk tsconfig because raisingtargettoes2022flips the default totrue, and that broke the template builder. This PR fixes the root cause and removes the pin, so the SDK now compiles with the standard es2022[[Define]]class-field semantics.Root cause
TemplateBaseresolved its defaultfileContextPathin a class field initializer:With native class fields (define semantics), V8 evaluates field initializers in an extra
<instance_members_initializer>stack frame:getCallerDirectorywalks the stack at a fixed depth, so it landed on the SDK's ownsrc/templatedirectory instead of the caller's —.copy('folder/*', …)then globbed against the wrong base dir (Error: No files found in .../src/template/...), and the resulting client-side failure mis-attributed build-step stack traces (the twostacktrace.test.tsfailures were cascades of this one bug).Fix
Move the default resolution into the constructor body, where the stack shape is identical under both emits:
The call is now emit-invariant (same
STACK_TRACE_DEPTH), so the tsconfig pin is removed. The method-levelgetCallerFramecall sites were never affected — method bodies don't change shape with class-field semantics.Only the js-sdk is touched: the Python SDKs resolve the caller via
inspectand don't have this failure mode, and the CLI bundle doesn't includeTemplateBase.Usage example
Fixes relative-path resolution for SDK consumers whose toolchain emits native class fields (e.g. esbuild/vitest with
target: es2022+):Verification
tests/template/stacktrace.test.ts— 30/30 pass with the flag defaulted (true), and still 30/30 when explicitly set back tofalse(emit-invariance)tests/template/build.test.ts— 4/4 pass against the real backend (real.copyglob + build)dist/index.mjsanddist/index.jsfrom an external directory:fileContextPathresolves to the importing script's directory in bothE2B_API_KEY-gated live tests)pnpm run typecheck,lint,format✅🤖 Generated with Claude Code