cdk-local 0.5.1 → 0.6.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/README.md +22 -5
- package/dist/cli.js +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{local-start-service-gYRarUgM.js → local-start-service-irdy6U57.js} +107 -23
- package/dist/local-start-service-irdy6U57.js.map +1 -0
- package/package.json +1 -1
- package/dist/local-start-service-gYRarUgM.js.map +0 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ cdk-local deliberately does NOT emulate AWS managed services. The bet is: keep d
|
|
|
22
22
|
It also picks up where `sam local` leaves off:
|
|
23
23
|
|
|
24
24
|
- **CDK-native** — point it at your CDK app's `cdk.json`. No SAM templates, no extra config files.
|
|
25
|
-
- **Wider coverage** — Lambda (ZIP + container image), API Gateway REST v1 / HTTP v2 /
|
|
25
|
+
- **Wider coverage** — Lambda (ZIP + container image, plus Function URL), API Gateway REST v1 / HTTP v2 / WebSocket API, ECS run-task, ECS service with Service Connect + Cloud Map.
|
|
26
26
|
- **Real container images** — Lambda code runs in the real `public.ecr.aws/lambda/*` base image via Lambda Runtime Interface Emulator (RIE). ECS tasks run as real Docker containers. The only dependency is Docker.
|
|
27
27
|
|
|
28
28
|
## What runs locally, what doesn't
|
|
@@ -32,7 +32,7 @@ cdk-local runs your **application compute** locally in Docker, using your CDK ap
|
|
|
32
32
|
**Runs locally (application compute):**
|
|
33
33
|
|
|
34
34
|
- **Lambda functions** — your code in a real `public.ecr.aws/lambda/*` container via the Lambda Runtime Interface Emulator
|
|
35
|
-
- **
|
|
35
|
+
- **HTTP APIs & Function URLs** — API Gateway REST v1 / HTTP v2 / WebSocket and Lambda Function URLs served by a local HTTP server
|
|
36
36
|
- **ECS** — tasks and services as real Docker containers (awsvpc / Service Connect / Cloud Map registry)
|
|
37
37
|
- **Authorizers** — Lambda authorizers, Cognito User Pool JWT verification, IAM SigV4 verification
|
|
38
38
|
|
|
@@ -66,14 +66,31 @@ cdkl invoke MyStack/MyFunction --event ./event.json
|
|
|
66
66
|
|
|
67
67
|

|
|
68
68
|
|
|
69
|
-
####
|
|
69
|
+
#### HTTP APIs & Function URLs — `start-api`
|
|
70
70
|
|
|
71
|
-
Serve your API Gateway routes (REST v1 / HTTP v2 / Function
|
|
71
|
+
Serve your app's HTTP surface locally — API Gateway routes (REST v1 / HTTP v2 / WebSocket) and Lambda Function URLs — on a local HTTP server.
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
|
+
# Single-stack app: serve every API in the stack (each on its own port)
|
|
75
|
+
cdkl start-api
|
|
76
|
+
|
|
77
|
+
# Or target one API
|
|
74
78
|
cdkl start-api MyStack/MyApi
|
|
75
79
|
```
|
|
76
80
|
|
|
81
|
+
In a multi-stack app, a bare `cdkl start-api` errors out rather than serving every stack's API at once (so a side-stack's API is never booted by accident). Either serve them all explicitly, or pick one stack with the first selector you supply:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Serve every stack's API (each on its own port)
|
|
85
|
+
cdkl start-api --all-stacks
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- `--stack <name>` — the synth stack name, matched against your CDK app.
|
|
89
|
+
- `--from-cfn-stack <name>` — the deployed CloudFormation stack name; doubles as the stack selector (see [section 2](#2-bound-to-a-deployed-stack)).
|
|
90
|
+
- a stack-qualified target like `MyStack/MyApi` — the `MyStack` prefix selects the stack.
|
|
91
|
+
|
|
92
|
+
`--all-stacks` is mutually exclusive with those single-target selectors (the bare `--from-cfn-stack` flag stays compatible). See [docs/cli-reference.md](docs/cli-reference.md) for the full precedence rules.
|
|
93
|
+
|
|
77
94
|
#### ECS — `run-task` / `start-service`
|
|
78
95
|
|
|
79
96
|
Run an ECS task definition once, or start a long-running service with Service Connect / Cloud Map registry.
|
|
@@ -91,7 +108,7 @@ Use this for fast iteration on Lambda code, API routing checks, and container ta
|
|
|
91
108
|
|
|
92
109
|
Once your stack is deployed to AWS (via the AWS CDK CLI or any other tool), pass `--from-cfn-stack <StackName>` and cdk-local reads the deployed CloudFormation stack to inject real ARNs, Secrets values, and IAM credentials (resolved from your current AWS profile) into the local execution.
|
|
93
110
|
|
|
94
|
-
####
|
|
111
|
+
#### HTTP APIs & Function URLs — `start-api` (the headline use case)
|
|
95
112
|
|
|
96
113
|
A local API talking to real AWS — point a frontend at it for end-to-end debugging, including real Cognito JWT verification.
|
|
97
114
|
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as createLocalInvokeCommand, n as createLocalRunTaskCommand, r as createLocalStartApiCommand, t as createLocalStartServiceCommand } from "./local-start-service-
|
|
2
|
+
import { i as createLocalInvokeCommand, n as createLocalRunTaskCommand, r as createLocalStartApiCommand, t as createLocalStartServiceCommand } from "./local-start-service-irdy6U57.js";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
|
|
5
5
|
//#region src/cli/index.ts
|
|
6
6
|
const program = new Command();
|
|
7
|
-
program.name("cdkl").description("Run AWS CDK stacks locally with Docker.").version("0.
|
|
7
|
+
program.name("cdkl").description("Run AWS CDK stacks locally with Docker.").version("0.6.0");
|
|
8
8
|
program.addCommand(createLocalInvokeCommand());
|
|
9
9
|
program.addCommand(createLocalStartApiCommand());
|
|
10
10
|
program.addCommand(createLocalRunTaskCommand());
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types/state.ts","../src/local/state-resolver.ts","../src/local/local-state-provider.ts","../src/cli/commands/local-state-source.ts","../src/local/embed-config.ts","../src/cli/commands/local-invoke.ts","../src/cli/commands/local-start-api.ts","../src/cli/commands/local-run-task.ts","../src/cli/commands/local-start-service.ts","../src/local/cfn-local-state-provider.ts"],"mappings":";;;;UAuLiB,aAAA;EAEf,UAAA;EAGA,YAAA;EAGA,UAAA,EAAY,MAAA;EAcZ,kBAAA,GAAqB,MAAA;EAGrB,UAAA,GAAa,MAAA;EAGb,YAAA;EAGA,QAAA,GAAW,MAAA;EAmBX,cAAA;EAMA,mBAAA;EAyBA,aAAA;AAAA;;;UCxIe,gBAAA;EACf,SAAA;EACA,MAAA;EACA,SAAA;EACA,SAAA;AAAA;AAAA,UAqBe,kBAAA;EAOf,aAAA,CAAc,UAAA,WAAqB,OAAA;EAQnC,qBAAA,CACE,aAAA,UACA,cAAA,UACA,UAAA,WACC,OAAA;AAAA;AAAA,UAGY,mBAAA;EAEf,SAAA,EAAW,MAAA,SAAe,aAAA;EAE1B,gBAAA,GAAmB,gBAAA;EAOnB,kBAAA,GAAqB,kBAAA;EAOrB,cAAA;AAAA;;;UCrJe,gBAAA;EAQf,SAAA,EAAW,MAAA,SAAe,aAAA;EAO1B,OAAA,EAAS,MAAA;EAOT,MAAA;AAAA;AAAA,UAsBe,kBAAA;EAAA,SAMN,KAAA;EAQT,IAAA,CAAK,SAAA,UAAmB,WAAA,uBAAkC,OAAA,CAAQ,gBAAA;EAalE,uBAAA,CAAwB,cAAA,WAAyB,OAAA,CAAQ,kBAAA;EAKzD,OAAA;AAAA;;;UC9Ee,uBAAA;EAOf,YAAA;EAEA,MAAA;EAEA,OAAA;EAMA,WAAA;EAAA,CAEC,GAAA;AAAA;AAAA,KAQS,yBAAA,IAA6B,OAAA,EAAS,uBAAA,KAA4B,kBAAA;AAAA,KAUlE,mBAAA,GAAsB,MAAA,SAAe,yBAAA;AAAA,iBAUjC,mBAAA,CAAoB,YAAA,oBAAgC,SAAA;AAAA,iBAapD,gBAAA,CAAiB,IAAA,EAAM,IAAA,CAAK,uBAAA;AAAA,iBAgB5B,gBAAA,CACd,OAAA,EAAS,IAAA,CAAK,uBAAA,6BACd,WAAA;AAAA,cAqBW,qBAAA,SAA8B,KAAA;cAC7B,OAAA;AAAA;AAAA,iBAkBE,wCAAA,CACd,OAAA,EAAS,IAAA,CAAK,uBAAA,mBACd,gBAAA;AAAA,iBAiCc,wBAAA,CACd,OAAA,EAAS,uBAAA,EACT,SAAA,UACA,WAAA,sBACA,mBAAA,GAAsB,mBAAA,GACrB,kBAAA;;;UCtLc,mBAAA;EAMf,OAAA;EAOA,UAAA;EAMA,WAAA;EAOA,kBAAA;EAMA,gBAAA;EAMA,SAAA;AAAA;;;UC8Ee,+BAAA;EACf,mBAAA,GAAsB,mBAAA;EAEtB,WAAA,GAAc,mBAAA;AAAA;AAAA,iBA22BA,wBAAA,CAAyB,IAAA,GAAM,+BAAA,GAAuC,OAAA;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types/state.ts","../src/local/state-resolver.ts","../src/local/local-state-provider.ts","../src/cli/commands/local-state-source.ts","../src/local/embed-config.ts","../src/cli/commands/local-invoke.ts","../src/cli/commands/local-start-api.ts","../src/cli/commands/local-run-task.ts","../src/cli/commands/local-start-service.ts","../src/local/cfn-local-state-provider.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as LocalStateSourceError, c as rejectExplicitCfnStackWithMultipleStacks, d as CfnLocalStateProvider, i as createLocalInvokeCommand, l as resolveCfnRegion, n as createLocalRunTaskCommand, o as createLocalStateProvider, r as createLocalStartApiCommand, s as isCfnFlagPresent, t as createLocalStartServiceCommand, u as resolveCfnStackName } from "./local-start-service-
|
|
1
|
+
import { a as LocalStateSourceError, c as rejectExplicitCfnStackWithMultipleStacks, d as CfnLocalStateProvider, i as createLocalInvokeCommand, l as resolveCfnRegion, n as createLocalRunTaskCommand, o as createLocalStateProvider, r as createLocalStartApiCommand, s as isCfnFlagPresent, t as createLocalStartServiceCommand, u as resolveCfnStackName } from "./local-start-service-irdy6U57.js";
|
|
2
2
|
|
|
3
3
|
export { CfnLocalStateProvider, LocalStateSourceError, createLocalInvokeCommand, createLocalRunTaskCommand, createLocalStartApiCommand, createLocalStartServiceCommand, createLocalStateProvider, isCfnFlagPresent, rejectExplicitCfnStackWithMultipleStacks, resolveCfnRegion, resolveCfnStackName };
|
|
@@ -3802,7 +3802,7 @@ async function runDetached(opts) {
|
|
|
3802
3802
|
const ro = mount.readOnly ? ":ro" : "";
|
|
3803
3803
|
args.push("-v", `${mount.hostPath}:${mount.containerPath}${ro}`);
|
|
3804
3804
|
}
|
|
3805
|
-
|
|
3805
|
+
const passthroughEnv = appendEnvFlags(args, opts.env, SENSITIVE_ENV_KEYS);
|
|
3806
3806
|
if (opts.tmpfs) args.push("--tmpfs", `${opts.tmpfs.target}:rw,size=${opts.tmpfs.sizeMb}m`);
|
|
3807
3807
|
if (opts.workingDir) args.push("--workdir", opts.workingDir);
|
|
3808
3808
|
let entryPointTail = [];
|
|
@@ -3813,7 +3813,10 @@ async function runDetached(opts) {
|
|
|
3813
3813
|
args.push(opts.image, ...entryPointTail, ...opts.cmd);
|
|
3814
3814
|
getLogger().child("docker").debug(`${getDockerCmd()} ${redactAwsCredentialsInArgs(args).join(" ")}`);
|
|
3815
3815
|
try {
|
|
3816
|
-
const { stdout } = await execFileAsync$3(getDockerCmd(), args, {
|
|
3816
|
+
const { stdout } = await execFileAsync$3(getDockerCmd(), args, {
|
|
3817
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
3818
|
+
...execEnvForSecrets(passthroughEnv)
|
|
3819
|
+
});
|
|
3817
3820
|
return stdout.trim();
|
|
3818
3821
|
} catch (error) {
|
|
3819
3822
|
const err = error;
|
|
@@ -3908,23 +3911,66 @@ function pickFreePort() {
|
|
|
3908
3911
|
});
|
|
3909
3912
|
}
|
|
3910
3913
|
/**
|
|
3911
|
-
*
|
|
3912
|
-
*
|
|
3913
|
-
*
|
|
3914
|
-
*
|
|
3915
|
-
*
|
|
3916
|
-
*
|
|
3914
|
+
* Env keys whose VALUES are sensitive (AWS credentials). These are
|
|
3915
|
+
* routed through docker's value-from-process-env form by
|
|
3916
|
+
* {@link appendEnvFlags} so the value never appears in `docker run`'s
|
|
3917
|
+
* argv (`ps` / `/proc/<pid>/cmdline`), and they are also redacted by
|
|
3918
|
+
* {@link redactAwsCredentialsInArgs} as a defense for any path that
|
|
3919
|
+
* still emits the inline `-e <KEY>=<value>` form into the debug log.
|
|
3917
3920
|
*/
|
|
3918
|
-
const
|
|
3921
|
+
const SENSITIVE_ENV_KEYS = new Set([
|
|
3919
3922
|
"AWS_ACCESS_KEY_ID",
|
|
3920
3923
|
"AWS_SECRET_ACCESS_KEY",
|
|
3921
3924
|
"AWS_SESSION_TOKEN"
|
|
3922
3925
|
]);
|
|
3923
3926
|
/**
|
|
3927
|
+
* Append `-e` flags for `env` to `args`, routing keys in `sensitiveKeys`
|
|
3928
|
+
* through docker's value-from-process-env form (`-e KEY`, with NO
|
|
3929
|
+
* `=value`). Docker reads the value from its OWN environment at run time,
|
|
3930
|
+
* so the secret never lands in `docker run`'s argv — invisible to
|
|
3931
|
+
* `ps aux` / `/proc/<pid>/cmdline` (other users on a shared host) and to
|
|
3932
|
+
* shell history. Non-sensitive keys keep the inline `-e KEY=VALUE` form
|
|
3933
|
+
* (fine for non-secret config, and keeps `--debug` output readable).
|
|
3934
|
+
*
|
|
3935
|
+
* Returns the `{ KEY: value }` map of routed-through keys; the caller MUST
|
|
3936
|
+
* merge it into the spawned docker process's `env` (see
|
|
3937
|
+
* {@link execEnvForSecrets}) so docker can resolve each passed-through
|
|
3938
|
+
* key. Values still appear in `docker inspect` Config.Env — that is
|
|
3939
|
+
* inherent to container env and matches production behavior.
|
|
3940
|
+
*
|
|
3941
|
+
* Unlike `--env-file`, this handles multi-line values (e.g. PEM secrets)
|
|
3942
|
+
* and needs no temp file on disk.
|
|
3943
|
+
*/
|
|
3944
|
+
function appendEnvFlags(args, env, sensitiveKeys) {
|
|
3945
|
+
const passthrough = {};
|
|
3946
|
+
for (const [k, v] of Object.entries(env)) if (sensitiveKeys.has(k)) {
|
|
3947
|
+
args.push("-e", k);
|
|
3948
|
+
passthrough[k] = v;
|
|
3949
|
+
} else args.push("-e", `${k}=${v}`);
|
|
3950
|
+
return passthrough;
|
|
3951
|
+
}
|
|
3952
|
+
/**
|
|
3953
|
+
* Build the `env` option for an `execFile` / `spawn` call that runs a
|
|
3954
|
+
* `docker run` whose args include passed-through sensitive keys (from
|
|
3955
|
+
* {@link appendEnvFlags}). Returns `{ env: {...process.env, ...passthrough} }`
|
|
3956
|
+
* so docker inherits the normal environment PLUS the sensitive values it
|
|
3957
|
+
* must resolve, or `{}` when there is nothing to pass through (preserving
|
|
3958
|
+
* the default inherited-environment behavior).
|
|
3959
|
+
*/
|
|
3960
|
+
function execEnvForSecrets(passthrough) {
|
|
3961
|
+
if (Object.keys(passthrough).length === 0) return {};
|
|
3962
|
+
return { env: {
|
|
3963
|
+
...process.env,
|
|
3964
|
+
...passthrough
|
|
3965
|
+
} };
|
|
3966
|
+
}
|
|
3967
|
+
/**
|
|
3924
3968
|
* Returns a copy of `args` with any `-e <KEY>=<value>` pair whose KEY is
|
|
3925
|
-
* in {@link
|
|
3969
|
+
* in {@link SENSITIVE_ENV_KEYS} replaced with `-e <KEY>=***`. The actual
|
|
3926
3970
|
* `args` passed to `spawn` are never mutated — this is for log output
|
|
3927
|
-
* only.
|
|
3971
|
+
* only. With {@link appendEnvFlags} routing credentials through the
|
|
3972
|
+
* value-less `-e KEY` form this rarely fires, but it stays as defense for
|
|
3973
|
+
* any inline-form credential that slips into a logged command.
|
|
3928
3974
|
*/
|
|
3929
3975
|
function redactAwsCredentialsInArgs(args) {
|
|
3930
3976
|
const out = [];
|
|
@@ -3935,7 +3981,7 @@ function redactAwsCredentialsInArgs(args) {
|
|
|
3935
3981
|
const eqIdx = next.indexOf("=");
|
|
3936
3982
|
if (eqIdx > 0) {
|
|
3937
3983
|
const key = next.substring(0, eqIdx);
|
|
3938
|
-
if (
|
|
3984
|
+
if (SENSITIVE_ENV_KEYS.has(key)) {
|
|
3939
3985
|
out.push("-e", `${key}=***`);
|
|
3940
3986
|
i++;
|
|
3941
3987
|
continue;
|
|
@@ -14155,6 +14201,8 @@ async function localStartApiCommand(target, options, extraStateProviders) {
|
|
|
14155
14201
|
logger.warn(`[deprecated] --api <id> will be removed in a future major release. Use the positional argument instead: '${getEmbedConfig().cliName} start-api <id>'.`);
|
|
14156
14202
|
apiFilter = options.api;
|
|
14157
14203
|
}
|
|
14204
|
+
const allStacksConflictList = allStacksConflicts(options, target, apiFilter);
|
|
14205
|
+
if (allStacksConflictList.length > 0) throw new Error(`--all-stacks serves every stack's API and cannot be combined with a single-target selector (${allStacksConflictList.join(", ")}). Drop --all-stacks to target one stack, or drop the selector to serve them all. The bare --from-cfn-stack flag (no value) IS compatible with --all-stacks.`);
|
|
14158
14206
|
warnIfDeprecatedRegion(options);
|
|
14159
14207
|
await applyRoleArnIfSet({
|
|
14160
14208
|
roleArn: options.roleArn,
|
|
@@ -14201,7 +14249,7 @@ async function localStartApiCommand(target, options, extraStateProviders) {
|
|
|
14201
14249
|
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
14202
14250
|
const cfnStackFallback = typeof options.fromCfnStack === "string" ? options.fromCfnStack : void 0;
|
|
14203
14251
|
const targetStackPrefix = target?.includes("/") === true ? target.slice(0, target.indexOf("/")) : void 0;
|
|
14204
|
-
const targetStacks = pickTargetStacks(stacks, options.stack, cfnStackFallback, targetStackPrefix);
|
|
14252
|
+
const targetStacks = pickTargetStacks(stacks, options.stack, cfnStackFallback, targetStackPrefix, options.allStacks);
|
|
14205
14253
|
if (targetStacks.length === 0) throw new Error("No stacks matched. Pass --stack <name> (or --from-cfn-stack <name>) or run from a single-stack app.");
|
|
14206
14254
|
const routedStackNames = targetStacks.map((s) => s.stackName);
|
|
14207
14255
|
tryEmitFromCfnRedundancyTipOnce(options.fromCfnStack, routedStackNames, fromCfnTipEmitted, (routedStackName) => {
|
|
@@ -14583,12 +14631,37 @@ async function localStartApiCommand(target, options, extraStateProviders) {
|
|
|
14583
14631
|
* routing rules.
|
|
14584
14632
|
*/
|
|
14585
14633
|
/** @internal exported for unit tests. */
|
|
14586
|
-
function pickTargetStacks(stacks, pattern, cfnStackFallback, targetFallback) {
|
|
14634
|
+
function pickTargetStacks(stacks, pattern, cfnStackFallback, targetFallback, allStacks) {
|
|
14635
|
+
if (allStacks) return stacks;
|
|
14587
14636
|
const effective = pattern ?? cfnStackFallback ?? targetFallback;
|
|
14588
14637
|
if (effective) return matchStacks(stacks, [effective]);
|
|
14589
14638
|
if (stacks.length === 1) return stacks;
|
|
14590
14639
|
if (stacks.length === 0) return [];
|
|
14591
|
-
throw new Error(`Multi-stack app: pass --stack <name>, --from-cfn-stack <name>,
|
|
14640
|
+
throw new Error(`Multi-stack app: pass --stack <name>, --from-cfn-stack <name>, a stack-qualified target like "<StackName>/<construct>", or --all-stacks to serve every stack. Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`);
|
|
14641
|
+
}
|
|
14642
|
+
/**
|
|
14643
|
+
* Issue #55: `--all-stacks` serves every stack's API as a union, so it
|
|
14644
|
+
* cannot be combined with a selector that names exactly ONE target.
|
|
14645
|
+
* Returns the human-readable list of conflicting selectors (empty when
|
|
14646
|
+
* `--all-stacks` is off or there is no conflict).
|
|
14647
|
+
*
|
|
14648
|
+
* The bare `--from-cfn-stack` flag (Commander maps it to boolean `true`)
|
|
14649
|
+
* is NOT a conflict — it binds each routed stack to its own CFn stack,
|
|
14650
|
+
* which is exactly the multi-stack union case. Only an explicit
|
|
14651
|
+
* `--from-cfn-stack <name>` (a string) names a single stack and conflicts.
|
|
14652
|
+
*
|
|
14653
|
+
* Extracted as a pure function so it can be unit-tested without booting
|
|
14654
|
+
* the full server.
|
|
14655
|
+
*
|
|
14656
|
+
* @internal exported for unit tests.
|
|
14657
|
+
*/
|
|
14658
|
+
function allStacksConflicts(options, target, apiFilter) {
|
|
14659
|
+
if (!options.allStacks) return [];
|
|
14660
|
+
const conflicts = [];
|
|
14661
|
+
if (apiFilter !== void 0) conflicts.push(target !== void 0 ? `target '${target}'` : `--api '${options.api}'`);
|
|
14662
|
+
if (options.stack !== void 0) conflicts.push(`--stack '${options.stack}'`);
|
|
14663
|
+
if (typeof options.fromCfnStack === "string") conflicts.push(`--from-cfn-stack '${options.fromCfnStack}'`);
|
|
14664
|
+
return conflicts;
|
|
14592
14665
|
}
|
|
14593
14666
|
/**
|
|
14594
14667
|
* Decide whether the `--from-cfn-stack <name>` redundancy tip should
|
|
@@ -15586,7 +15659,7 @@ function resolveMtlsConfig(options) {
|
|
|
15586
15659
|
*/
|
|
15587
15660
|
function createLocalStartApiCommand(opts = {}) {
|
|
15588
15661
|
setEmbedConfig(opts.embedConfig);
|
|
15589
|
-
const startApi = new Command("start-api").description("Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and AWS_IAM auth (REST v1 `AuthorizationType: AWS_IAM` and Function URL `AuthType: AWS_IAM` — SigV4 signature verification only; IAM policy evaluation is NOT emulated). When JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line — local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail.").argument("[target]", `Optional API filter. Accepts the bare CDK logical id ('MyHttpApi'; single-stack apps only), stack-qualified logical id ('MyStack:MyHttpApi'), full CDK Construct path ('MyStack/MyHttpApi/Resource'), or an ancestor Construct path that prefix-matches ('MyStack/MyHttpApi'). When omitted, every discovered API gets its own server. Mirrors \`${getEmbedConfig().cliName} invoke\` / \`${getEmbedConfig().cliName} run-task\` target syntax.`).addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: re-synth + re-discover routes when cdk.out/ or asset directories change. Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <target> argument instead. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers (issue #448). Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in Lambda env vars with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the resolved stack name per routed stack; pass an explicit value when a single CFn stack should serve every routed stack. Fn::GetAtt is warn-and-dropped in v1 (CFn ListStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).addOption(new Option("--mtls-truststore <path>", `PEM-encoded CA bundle for client-certificate verification (mutual TLS). When set, the local server switches from HTTP to HTTPS and the TLS handshake rejects clients whose certificate doesn't chain to one of these CAs. Verified certs are surfaced on the Lambda event under requestContext.identity.clientCert (REST v1) / requestContext.authentication.clientCert (HTTP API v2). Must be set together with --mtls-cert + --mtls-key; partial flag sets are rejected. Generate a CA + server + client cert for local dev: openssl req -x509 -newkey rsa:2048 -nodes -keyout ca-key.pem -out ca.pem -subj "/CN=${getEmbedConfig().resourceNamePrefix}-ca" -days 365; openssl req -newkey rsa:2048 -nodes -keyout server-key.pem -out server-csr.pem -subj "/CN=localhost"; openssl x509 -req -in server-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365; openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client-csr.pem -subj "/CN=client"; openssl x509 -req -in client-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365; curl --cacert ca.pem --cert client-cert.pem --key client-key.pem https://localhost:<port>/...`)).addOption(new Option("--mtls-cert <path>", "PEM-encoded server certificate for mutual TLS. Self-signed is fine for local dev. Must be set together with --mtls-truststore + --mtls-key.")).addOption(new Option("--mtls-key <path>", "PEM-encoded server private key matching --mtls-cert. Must be set together with --mtls-truststore + --mtls-cert.")).addOption(new Option("--allow-unverified-sigv4", "Opt-in: allow AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id, OR no local AWS credentials configured) to pass through with a placeholder principalId. DEFAULT off — fail-closed so unauthenticated bypass is impossible against `event.requestContext.identity.accessKey`-trusting handler code. Use only in dev loops where you understand the risk.").default(false)).action(withErrorHandling(async (target, options) => {
|
|
15662
|
+
const startApi = new Command("start-api").description("Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and AWS_IAM auth (REST v1 `AuthorizationType: AWS_IAM` and Function URL `AuthType: AWS_IAM` — SigV4 signature verification only; IAM policy evaluation is NOT emulated). When JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line — local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail.").argument("[target]", `Optional API filter. Accepts the bare CDK logical id ('MyHttpApi'; single-stack apps only), stack-qualified logical id ('MyStack:MyHttpApi'), full CDK Construct path ('MyStack/MyHttpApi/Resource'), or an ancestor Construct path that prefix-matches ('MyStack/MyHttpApi'). When omitted, every discovered API gets its own server. Mirrors \`${getEmbedConfig().cliName} invoke\` / \`${getEmbedConfig().cliName} run-task\` target syntax.`).addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(new Option("--all-stacks", "Serve every stack's API in a multi-stack app (each API on its own port) instead of erroring out. Mutually exclusive with a positional target, --stack, and an explicit --from-cfn-stack <name>; the bare --from-cfn-stack flag stays compatible (binds each routed stack to its own CFn stack).").default(false)).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: re-synth + re-discover routes when cdk.out/ or asset directories change. Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <target> argument instead. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers (issue #448). Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in Lambda env vars with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the resolved stack name per routed stack; pass an explicit value when a single CFn stack should serve every routed stack. Fn::GetAtt is warn-and-dropped in v1 (CFn ListStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).addOption(new Option("--mtls-truststore <path>", `PEM-encoded CA bundle for client-certificate verification (mutual TLS). When set, the local server switches from HTTP to HTTPS and the TLS handshake rejects clients whose certificate doesn't chain to one of these CAs. Verified certs are surfaced on the Lambda event under requestContext.identity.clientCert (REST v1) / requestContext.authentication.clientCert (HTTP API v2). Must be set together with --mtls-cert + --mtls-key; partial flag sets are rejected. Generate a CA + server + client cert for local dev: openssl req -x509 -newkey rsa:2048 -nodes -keyout ca-key.pem -out ca.pem -subj "/CN=${getEmbedConfig().resourceNamePrefix}-ca" -days 365; openssl req -newkey rsa:2048 -nodes -keyout server-key.pem -out server-csr.pem -subj "/CN=localhost"; openssl x509 -req -in server-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365; openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client-csr.pem -subj "/CN=client"; openssl x509 -req -in client-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365; curl --cacert ca.pem --cert client-cert.pem --key client-key.pem https://localhost:<port>/...`)).addOption(new Option("--mtls-cert <path>", "PEM-encoded server certificate for mutual TLS. Self-signed is fine for local dev. Must be set together with --mtls-truststore + --mtls-key.")).addOption(new Option("--mtls-key <path>", "PEM-encoded server private key matching --mtls-cert. Must be set together with --mtls-truststore + --mtls-cert.")).addOption(new Option("--allow-unverified-sigv4", "Opt-in: allow AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id, OR no local AWS credentials configured) to pass through with a placeholder principalId. DEFAULT off — fail-closed so unauthenticated bypass is impossible against `event.requestContext.identity.accessKey`-trusting handler code. Use only in dev loops where you understand the risk.").default(false)).action(withErrorHandling(async (target, options) => {
|
|
15590
15663
|
await localStartApiCommand(target, options, opts.extraStateProviders);
|
|
15591
15664
|
}));
|
|
15592
15665
|
[
|
|
@@ -15731,11 +15804,14 @@ async function createNetworkAndSidecar(args) {
|
|
|
15731
15804
|
if (credentials.sessionToken) sidecarEnv["AWS_SESSION_TOKEN"] = credentials.sessionToken;
|
|
15732
15805
|
}
|
|
15733
15806
|
if (cluster) sidecarEnv["CLUSTER"] = cluster;
|
|
15734
|
-
|
|
15807
|
+
const sidecarPassthroughEnv = appendEnvFlags(sidecarArgs, sidecarEnv, SENSITIVE_ENV_KEYS);
|
|
15735
15808
|
sidecarArgs.push(METADATA_ENDPOINT_IMAGE);
|
|
15736
15809
|
logger.info(`Starting ECS local-container-endpoints sidecar at ${sidecarIp}...`);
|
|
15737
15810
|
try {
|
|
15738
|
-
const { stdout } = await execFileAsync$2(getDockerCmd(), sidecarArgs, {
|
|
15811
|
+
const { stdout } = await execFileAsync$2(getDockerCmd(), sidecarArgs, {
|
|
15812
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
15813
|
+
...execEnvForSecrets(sidecarPassthroughEnv)
|
|
15814
|
+
});
|
|
15739
15815
|
return stdout.trim();
|
|
15740
15816
|
} catch (err) {
|
|
15741
15817
|
await destroyNetworkOnly(networkName);
|
|
@@ -16068,11 +16144,14 @@ async function runEcsTask(task, options, state) {
|
|
|
16068
16144
|
for (const containerName of startOrder) {
|
|
16069
16145
|
const container = task.containers.find((c) => c.name === containerName);
|
|
16070
16146
|
await awaitDependencies(container, startedByName);
|
|
16071
|
-
const args = dockerCmds.get(container.name);
|
|
16147
|
+
const { args, sensitiveEnv } = dockerCmds.get(container.name);
|
|
16072
16148
|
logger.info(`Starting container '${container.name}' (image=${imagePlan.get(container.name)})`);
|
|
16073
16149
|
let id;
|
|
16074
16150
|
try {
|
|
16075
|
-
const { stdout } = await execFileAsync$1(getDockerCmd(), args, {
|
|
16151
|
+
const { stdout } = await execFileAsync$1(getDockerCmd(), args, {
|
|
16152
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
16153
|
+
...execEnvForSecrets(sensitiveEnv)
|
|
16154
|
+
});
|
|
16076
16155
|
id = stdout.trim();
|
|
16077
16156
|
} catch (err) {
|
|
16078
16157
|
const e = err;
|
|
@@ -16426,7 +16505,9 @@ function buildDockerRunArgs(opts) {
|
|
|
16426
16505
|
applyOverrideMap(finalEnv, overrides["Parameters"]);
|
|
16427
16506
|
applyOverrideMap(finalEnv, overrides[container.name]);
|
|
16428
16507
|
}
|
|
16429
|
-
|
|
16508
|
+
const sensitiveEnvKeys = new Set(SENSITIVE_ENV_KEYS);
|
|
16509
|
+
for (const s of secrets) sensitiveEnvKeys.add(s.name);
|
|
16510
|
+
const sensitiveEnv = appendEnvFlags(args, finalEnv, sensitiveEnvKeys);
|
|
16430
16511
|
if (container.user) args.push("--user", container.user);
|
|
16431
16512
|
if (container.privileged) args.push("--privileged");
|
|
16432
16513
|
if (container.readonlyRootFilesystem) args.push("--read-only");
|
|
@@ -16446,7 +16527,10 @@ function buildDockerRunArgs(opts) {
|
|
|
16446
16527
|
entryPointTail = container.entryPoint.slice(1);
|
|
16447
16528
|
}
|
|
16448
16529
|
args.push(image, ...entryPointTail, ...container.command ?? []);
|
|
16449
|
-
return
|
|
16530
|
+
return {
|
|
16531
|
+
args,
|
|
16532
|
+
sensitiveEnv
|
|
16533
|
+
};
|
|
16450
16534
|
}
|
|
16451
16535
|
function applyOverrideMap(acc, map) {
|
|
16452
16536
|
if (!map) return;
|
|
@@ -18142,4 +18226,4 @@ function createLocalStartServiceCommand(opts = {}) {
|
|
|
18142
18226
|
|
|
18143
18227
|
//#endregion
|
|
18144
18228
|
export { LocalStateSourceError as a, rejectExplicitCfnStackWithMultipleStacks as c, CfnLocalStateProvider as d, createLocalInvokeCommand as i, resolveCfnRegion as l, createLocalRunTaskCommand as n, createLocalStateProvider as o, createLocalStartApiCommand as r, isCfnFlagPresent as s, createLocalStartServiceCommand as t, resolveCfnStackName as u };
|
|
18145
|
-
//# sourceMappingURL=local-start-service-
|
|
18229
|
+
//# sourceMappingURL=local-start-service-irdy6U57.js.map
|