Context
In dagger/typescript-sdk we moved module codegen out of the engine and into the SDK module, so the SDK now writes the tree the engine runtime executes. That made "does a generated module actually run?" the contract worth testing, and it needs cases sdk-sdk's own check set doesn't cover — notably one per TypeScript runtime, since node, bun and deno each execute the entrypoint in a different container with a different interpreter.
Depending on sdk-sdk to build those was the obvious move: the harness already does the hard part, vendoring the SDK into a scratch git workspace and driving a release CLI through sdk install → module init → generate.
Part of it worked. SdkSdk.target(workspaceView, sourceRootPath) is public, and target.run([...]) runs an arbitrary CLI command against the generated state, so invoking a function on a scaffolded module is a one-liner:
let target = sdkSdk.target(ws.directory("/"), ".")
let run = target.run(["call", target.moduleName, "base-image-address"])
run.assertSuccess
Two things stopped us going further.
1. No seam for SDK-specific init arguments
initArgs is private and fixed (sdk-target.dang):
let initArgs: [String!]! {
["module", "init", sdkInstallName, moduleName]
}
A TypeScript module's runtime is chosen at init (--runtime bun), and that choice changes nothing about generation but everything about execution. There's no way to reach it: the field is private, and SdkTarget's constructor fields (workspaceView, sourceRootPath) are private too, so the target can't be rebuilt with different args from a dependent module.
Every SDK with variants has this shape — Python's package manager, Go's toolchain, our three runtimes.
Suggestion: a builder alongside the existing withTimeout, which already sets the convention:
pub withInitArgs(args: [String!]!): SdkTarget! # extra args appended to `module init`
so a dependent can write target.withInitArgs(["--runtime", "bun"]).run([...]).
2. Runs don't compose
SdkRun.container is private (sdk-run.dang), so a run is terminal: you get stdout/stderr/exitCode but not the state it produced. runInstalled, run and runInModule each start from a state the harness picked, so a custom sequence — init with a flag, then generate, then call — can't be expressed.
Suggestion: either make the resulting state reachable (pub state: Container! on SdkRun, or pub then(args: [String!]!): SdkRun!), or expose the intermediate states (pub installedState / initializedState / generatedState) so callers can pipe their own commands.
What we did instead
Built the per-runtime checks with our own fixtures, loading each generated module to force the runtime to execute its entrypoint, and used sdk-sdk only for the invocation case: dagger/typescript-sdk#31
That works, but it reimplements scaffolding sdk-sdk already does well, and it means our runtime coverage drifts from the contract suite instead of extending it.
Why it seems worth doing
sdk-sdk is already the place SDK authors go for black-box coverage. Right now that's all-or-nothing: you get the built-in checks, or you rebuild the harness. A couple of small seams would make it a library SDK repos extend with their own language-specific cases, which is where most of the interesting failures live.
Happy to send a PR if the shape above sounds right — in particular whether you'd prefer withInitArgs or something more structured like a moduleOptions map.
Context
In
dagger/typescript-sdkwe moved module codegen out of the engine and into the SDK module, so the SDK now writes the tree the engine runtime executes. That made "does a generated module actually run?" the contract worth testing, and it needs cases sdk-sdk's own check set doesn't cover — notably one per TypeScript runtime, since node, bun and deno each execute the entrypoint in a different container with a different interpreter.Depending on sdk-sdk to build those was the obvious move: the harness already does the hard part, vendoring the SDK into a scratch git workspace and driving a release CLI through
sdk install→module init→generate.Part of it worked.
SdkSdk.target(workspaceView, sourceRootPath)is public, andtarget.run([...])runs an arbitrary CLI command against the generated state, so invoking a function on a scaffolded module is a one-liner:Two things stopped us going further.
1. No seam for SDK-specific init arguments
initArgsis private and fixed (sdk-target.dang):A TypeScript module's runtime is chosen at init (
--runtime bun), and that choice changes nothing about generation but everything about execution. There's no way to reach it: the field is private, andSdkTarget's constructor fields (workspaceView,sourceRootPath) are private too, so the target can't be rebuilt with different args from a dependent module.Every SDK with variants has this shape — Python's package manager, Go's toolchain, our three runtimes.
Suggestion: a builder alongside the existing
withTimeout, which already sets the convention:so a dependent can write
target.withInitArgs(["--runtime", "bun"]).run([...]).2. Runs don't compose
SdkRun.containeris private (sdk-run.dang), so a run is terminal: you getstdout/stderr/exitCodebut not the state it produced.runInstalled,runandrunInModuleeach start from a state the harness picked, so a custom sequence — init with a flag, then generate, then call — can't be expressed.Suggestion: either make the resulting state reachable (
pub state: Container!onSdkRun, orpub then(args: [String!]!): SdkRun!), or expose the intermediate states (pub installedState/initializedState/generatedState) so callers can pipe their own commands.What we did instead
Built the per-runtime checks with our own fixtures, loading each generated module to force the runtime to execute its entrypoint, and used sdk-sdk only for the invocation case: dagger/typescript-sdk#31
That works, but it reimplements scaffolding sdk-sdk already does well, and it means our runtime coverage drifts from the contract suite instead of extending it.
Why it seems worth doing
sdk-sdk is already the place SDK authors go for black-box coverage. Right now that's all-or-nothing: you get the built-in checks, or you rebuild the harness. A couple of small seams would make it a library SDK repos extend with their own language-specific cases, which is where most of the interesting failures live.
Happy to send a PR if the shape above sounds right — in particular whether you'd prefer
withInitArgsor something more structured like amoduleOptionsmap.