This repository demonstrates how lambda.Code.fromCustomCommand works and when to reach for it.
Code.fromCustomCommand(outputDir, command, options) lets you run an arbitrary shell command during CDK synthesis and use the directory it produces as a Lambda code asset. Under the hood CDK calls Node's spawnSync synchronously, waits for the command to finish, then treats outputDir exactly like Code.fromAsset() — zipping it and staging it to S3.
lambda.Code.fromCustomCommand(
path.join(__dirname, '..', 'dist', 'lambda'), // output CDK will package
['bash', 'scripts/build.sh', 'dist/lambda'], // command run during synthesis
{ commandOptions: { stdio: 'inherit' } }, // forwarded to spawnSync
)CDK's built-in Code.fromAsset bundles whatever is already on disk. Code.fromCustomCommand lets you drive the build step from within the CDK app itself — useful when:
- Your Lambda is written in Rust, Go, or another language with its own build tool (
cargo build,go build, …). - You need a bundler (esbuild, webpack, rollup) with flags or plugins that
NodejsFunctiondoesn't expose. - You want to run post-processing steps (stripping debug symbols, injecting a version file, etc.) before the asset is zipped.
When there is no CDK-native way to pull in an asset from an external source — a private package registry, an S3 bucket, a GitHub release — you can shell out to aws s3 cp, curl, or any other tool and write the result to outputDir.
Because the command runs unconditionally on every synth, you can use Code.fromCustomCommand purely for its side effects — generating config files, writing environment manifests, seeding a local cache — and simply point outputDir at a throwaway directory. The Lambda asset CDK produces from that directory can be a stub; the real value was the side effect.
cdk synth / cdk diff / cdk deploy / npm test (when the test synthesizes the stack)
│
└─▶ CDK executes your app (bin/main.ts)
│
└─▶ new SampleStack(...)
│
└─▶ Code.fromCustomCommand(outputDir, command, options)
│
├─ spawnSync(command[0], command.slice(1), options.commandOptions)
│ runs synchronously — CDK blocks until it exits
│
├─ non-zero exit → synthesis fails immediately
│
└─ exit 0 → CDK treats outputDir like Code.fromAsset(outputDir)
(zip + stage to S3 asset store)
| Command | Script runs? | Reason |
|---|---|---|
cdk synth |
✅ | Synthesis executes the CDK app |
cdk diff |
✅ | Diff synthesizes first |
cdk deploy |
✅ | Deploy synthesizes first |
npm test |
✅ | Tests that call Template.fromStack() synthesize the stack |
npm run build |
❌ | Only compiles TypeScript; CDK app is never executed |
- Synchronous — synthesis blocks on the command. Long-running builds will slow down every synth.
- No caching — CDK runs the command on every synth, regardless of whether inputs changed. Add your own up-to-date check inside the script if you need to skip unnecessary work.
commandOptions— forwarded directly to Node'sSpawnSyncOptions. Use this to controlcwd,env,shell,stdio, and more.- Failure is fatal — a non-zero exit code aborts synthesis. Make your script exit cleanly on success and with a descriptive error message on failure.
bin/
main.ts # CDK app entry point
lib/
sample-stack.ts # Stack using Code.fromCustomCommand
scripts/
build.sh # Custom build script called during synthesis
test/
sample-stack.test.ts # CDK assertions tests (also trigger the script)
| Command | Description |
|---|---|
npm run build |
Compile TypeScript to JS (does not run the build script) |
npm run watch |
Watch for changes and recompile |
npm run test |
Run Jest unit tests (synthesizes the stack, so the build script runs) |
npx cdk synth |
Emit the synthesized CloudFormation template (build script runs) |
npx cdk diff |
Compare deployed stack with current state (build script runs) |
npx cdk deploy |
Deploy the stack to your AWS account/region (build script runs) |