@go-to-k/cdkd 0.98.2 → 0.99.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 +3 -1
- package/dist/cli.js +136 -7
- package/dist/cli.js.map +1 -1
- package/dist/go-to-k-cdkd-0.99.0.tgz +0 -0
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.98.2.tgz +0 -0
package/README.md
CHANGED
|
@@ -448,13 +448,15 @@ cdkd local start-api --port 3000 # pin the first server's port
|
|
|
448
448
|
cdkd local start-api MyHttpApi # filter to one API (logical id, single-stack apps)
|
|
449
449
|
cdkd local start-api MyStack/MyHttpApi # OR: CDK Construct path
|
|
450
450
|
cdkd local start-api --warm --watch # pre-start + hot reload
|
|
451
|
+
cdkd local start-api --from-state # substitute deployed env vars in Lambda Environment
|
|
451
452
|
```
|
|
452
453
|
|
|
453
454
|
One server per discovered API — authorizers, CORS configs, and stage
|
|
454
455
|
variables stay scoped to the owning API. Supports REST v1 + HTTP API +
|
|
455
456
|
Function URL with AWS_PROXY integrations; Lambda TOKEN / REQUEST,
|
|
456
457
|
Cognito User Pool, and HTTP v2 JWT authorizers (JWKS-verified); CORS
|
|
457
|
-
preflight; hot reload via `--watch
|
|
458
|
+
preflight; hot reload via `--watch`; deploy-state-backed env var
|
|
459
|
+
substitution via `--from-state`.
|
|
458
460
|
|
|
459
461
|
### `local run-task`
|
|
460
462
|
|
package/dist/cli.js
CHANGED
|
@@ -38841,6 +38841,7 @@ async function localStartApiCommand(target, options) {
|
|
|
38841
38841
|
const m = buildCorsConfigByApiId(stack.template);
|
|
38842
38842
|
for (const [k, v] of m) corsConfigByApiId.set(k, v);
|
|
38843
38843
|
}
|
|
38844
|
+
const stateByStack = options.fromState ? await loadStateForRoutedStacks(targetStacks, routes, routesWithAuth, options) : /* @__PURE__ */ new Map();
|
|
38844
38845
|
const lambdaIds = uniqueLambdaIds(routes, routesWithAuth);
|
|
38845
38846
|
const specs = /* @__PURE__ */ new Map();
|
|
38846
38847
|
for (let i = 0; i < lambdaIds.length; i++) {
|
|
@@ -38854,7 +38855,8 @@ async function localStartApiCommand(target, options) {
|
|
|
38854
38855
|
...debugPortBase !== void 0 && { debugPort: debugPortBase + i },
|
|
38855
38856
|
stsRegion: options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"],
|
|
38856
38857
|
inlineTmpDirs,
|
|
38857
|
-
layerTmpDirs
|
|
38858
|
+
layerTmpDirs,
|
|
38859
|
+
stateByStack
|
|
38858
38860
|
});
|
|
38859
38861
|
specs.set(logicalId, spec);
|
|
38860
38862
|
}
|
|
@@ -39133,12 +39135,27 @@ function warnVpcConfigLambdas(routesWithAuth, stacks) {
|
|
|
39133
39135
|
* missing, runtime not supported).
|
|
39134
39136
|
*/
|
|
39135
39137
|
async function buildContainerSpec(args) {
|
|
39136
|
-
const { logicalId, stacks, overrides, assumeRole, containerHost, debugPort, stsRegion, inlineTmpDirs, layerTmpDirs } = args;
|
|
39138
|
+
const { logicalId, stacks, overrides, assumeRole, containerHost, debugPort, stsRegion, inlineTmpDirs, layerTmpDirs, stateByStack } = args;
|
|
39137
39139
|
const lambda = resolveLambdaByLogicalId(logicalId, stacks);
|
|
39138
39140
|
const codeDir = lambda.codePath ?? materializeInlineCode$1(lambda.handler, lambda.inlineCode ?? "", resolveRuntimeFileExtension(lambda.runtime), inlineTmpDirs);
|
|
39139
39141
|
const optDir = materializeLambdaLayers$1(lambda.layers, layerTmpDirs);
|
|
39140
|
-
|
|
39141
|
-
|
|
39142
|
+
let templateEnv = getTemplateEnv$1(lambda.resource);
|
|
39143
|
+
const stateBundle = stateByStack.get(lambda.stack.stackName);
|
|
39144
|
+
let stateAudit;
|
|
39145
|
+
if (stateBundle) {
|
|
39146
|
+
const context = { resources: stateBundle.state.resources };
|
|
39147
|
+
if (stateBundle.pseudoParameters) context.pseudoParameters = stateBundle.pseudoParameters;
|
|
39148
|
+
const { env, audit } = substituteEnvVarsFromState(templateEnv, context);
|
|
39149
|
+
templateEnv = env;
|
|
39150
|
+
stateAudit = audit;
|
|
39151
|
+
for (const key of audit.resolvedKeys) getLogger().debug(`Lambda ${logicalId}: --from-state substituted env var ${key}`);
|
|
39152
|
+
for (const { key, reason } of audit.unresolved) getLogger().warn(`Lambda ${logicalId}: --from-state could not substitute env var ${key} (${reason}). Override it via --env-vars or it will be dropped.`);
|
|
39153
|
+
}
|
|
39154
|
+
const envResult = resolveEnvVars(logicalId, templateEnv, overrides);
|
|
39155
|
+
for (const key of envResult.unresolved) {
|
|
39156
|
+
if (stateAudit && stateAudit.unresolved.some((u) => u.key === key)) continue;
|
|
39157
|
+
getLogger().warn(`Lambda ${logicalId}: env var ${key} contains a CloudFormation intrinsic and was dropped. Override it with --env-vars (e.g. {"${logicalId}":{"${key}":"<literal>"}}) or pass --from-state to recover deployed values.`);
|
|
39158
|
+
}
|
|
39142
39159
|
const dockerEnv = {
|
|
39143
39160
|
AWS_LAMBDA_FUNCTION_NAME: logicalId,
|
|
39144
39161
|
AWS_LAMBDA_FUNCTION_MEMORY_SIZE: String(lambda.memoryMb),
|
|
@@ -39446,6 +39463,117 @@ async function reloadAllServers(args) {
|
|
|
39446
39463
|
if (watcher) watcher.update([output, ...lastAssetPaths.value]);
|
|
39447
39464
|
printPerServerRouteTables(servers);
|
|
39448
39465
|
}
|
|
39466
|
+
/**
|
|
39467
|
+
* Returns true when any value in the function's template env map is a
|
|
39468
|
+
* CFn intrinsic (non-primitive). Used to gate the pseudo-parameter STS
|
|
39469
|
+
* hop inside the `--from-state` flow: literal-only env maps don't need
|
|
39470
|
+
* the pseudo-parameter bag and shouldn't pay for an STS call. Mirrors
|
|
39471
|
+
* the same gating in `local-invoke.ts` (`envHasIntrinsicValue`) and
|
|
39472
|
+
* `ecs-task-resolver.ts` (`containerHasIntrinsicEnvOrSecret`).
|
|
39473
|
+
*/
|
|
39474
|
+
function envHasIntrinsicValue$1(templateEnv) {
|
|
39475
|
+
if (!templateEnv) return false;
|
|
39476
|
+
for (const v of Object.values(templateEnv)) {
|
|
39477
|
+
if (v === void 0 || v === null) continue;
|
|
39478
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") continue;
|
|
39479
|
+
return true;
|
|
39480
|
+
}
|
|
39481
|
+
return false;
|
|
39482
|
+
}
|
|
39483
|
+
/**
|
|
39484
|
+
* Load cdkd's S3 state for every stack that owns a routed Lambda. Once
|
|
39485
|
+
* per `synthesizeAndBuild` pass (initial boot + every reload), so a
|
|
39486
|
+
* Lambda's per-spec build does not pay one round-trip per Lambda. Per-
|
|
39487
|
+
* stack failures (no state, ambiguous region, bucket resolution error)
|
|
39488
|
+
* degrade to warn-and-fall-back via {@link loadStateForStack} — the
|
|
39489
|
+
* affected stack's reachable Lambdas behave as if `--from-state` were
|
|
39490
|
+
* not set, while sibling stacks with loadable state still substitute.
|
|
39491
|
+
*
|
|
39492
|
+
* Pseudo parameters are resolved per stack and only when at least one
|
|
39493
|
+
* reachable Lambda in that stack has an intrinsic-valued env entry
|
|
39494
|
+
* (gated via {@link envHasIntrinsicValue}). STS failures degrade to
|
|
39495
|
+
* warn and leave `pseudoParameters: undefined` — substitution still
|
|
39496
|
+
* runs for non-`AWS::*` refs.
|
|
39497
|
+
*/
|
|
39498
|
+
async function loadStateForRoutedStacks(stacks, routes, routesWithAuth, options) {
|
|
39499
|
+
const logger = getLogger();
|
|
39500
|
+
const out = /* @__PURE__ */ new Map();
|
|
39501
|
+
const lambdaIds = uniqueLambdaIds(routes, routesWithAuth);
|
|
39502
|
+
const reachableStackNames = /* @__PURE__ */ new Set();
|
|
39503
|
+
for (const logicalId of lambdaIds) for (const stack of stacks) {
|
|
39504
|
+
const resource = stack.template.Resources?.[logicalId];
|
|
39505
|
+
if (resource && resource.Type === "AWS::Lambda::Function") {
|
|
39506
|
+
reachableStackNames.add(stack.stackName);
|
|
39507
|
+
break;
|
|
39508
|
+
}
|
|
39509
|
+
}
|
|
39510
|
+
const stackHasIntrinsicEnv = (stackName) => {
|
|
39511
|
+
for (const logicalId of lambdaIds) for (const stack of stacks) {
|
|
39512
|
+
if (stack.stackName !== stackName) continue;
|
|
39513
|
+
const resource = stack.template.Resources?.[logicalId];
|
|
39514
|
+
if (!resource || resource.Type !== "AWS::Lambda::Function") continue;
|
|
39515
|
+
if (envHasIntrinsicValue$1(getTemplateEnv$1(resource))) return true;
|
|
39516
|
+
}
|
|
39517
|
+
return false;
|
|
39518
|
+
};
|
|
39519
|
+
for (const stackName of reachableStackNames) {
|
|
39520
|
+
const stack = stacks.find((s) => s.stackName === stackName);
|
|
39521
|
+
if (!stack) continue;
|
|
39522
|
+
const loaded = await loadStateForStack(stack.stackName, stack.region, {
|
|
39523
|
+
...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
|
|
39524
|
+
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
39525
|
+
statePrefix: options.statePrefix,
|
|
39526
|
+
...options.region !== void 0 && { region: options.region },
|
|
39527
|
+
...options.profile !== void 0 && { profile: options.profile }
|
|
39528
|
+
});
|
|
39529
|
+
if (!loaded) continue;
|
|
39530
|
+
const bundle = { state: loaded.state };
|
|
39531
|
+
if (stackHasIntrinsicEnv(stackName)) {
|
|
39532
|
+
const pseudo = await resolvePseudoParametersForStartApi(loaded.region, options);
|
|
39533
|
+
if (pseudo) bundle.pseudoParameters = pseudo;
|
|
39534
|
+
}
|
|
39535
|
+
out.set(stackName, bundle);
|
|
39536
|
+
logger.debug(`--from-state: loaded state for ${stackName} (${loaded.region})`);
|
|
39537
|
+
}
|
|
39538
|
+
return out;
|
|
39539
|
+
}
|
|
39540
|
+
/**
|
|
39541
|
+
* Build the AWS pseudo-parameter bag for `--from-state` env-var
|
|
39542
|
+
* substitution. Mirrors `resolvePseudoParametersForInvoke` in
|
|
39543
|
+
* `local-invoke.ts` byte-for-byte — kept inlined here rather than
|
|
39544
|
+
* extracted into a shared helper because the two call sites differ in
|
|
39545
|
+
* region precedence (this one is per-stack so the resolved state
|
|
39546
|
+
* region takes priority).
|
|
39547
|
+
*
|
|
39548
|
+
* Region precedence: `--region` > `AWS_REGION` > `AWS_DEFAULT_REGION` >
|
|
39549
|
+
* the state record's region (returned by `loadStateForStack`).
|
|
39550
|
+
*/
|
|
39551
|
+
async function resolvePseudoParametersForStartApi(stateRegion, options) {
|
|
39552
|
+
const logger = getLogger();
|
|
39553
|
+
const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? stateRegion;
|
|
39554
|
+
let accountId;
|
|
39555
|
+
try {
|
|
39556
|
+
const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
|
|
39557
|
+
const sts = new STSClient({ ...region && { region } });
|
|
39558
|
+
try {
|
|
39559
|
+
accountId = (await sts.send(new GetCallerIdentityCommand({}))).Account;
|
|
39560
|
+
} finally {
|
|
39561
|
+
sts.destroy();
|
|
39562
|
+
}
|
|
39563
|
+
} catch (err) {
|
|
39564
|
+
logger.warn(`--from-state: resolver needs \${AWS::AccountId} but STS GetCallerIdentity failed: ${err instanceof Error ? err.message : String(err)}. Substitution will be skipped for AWS::AccountId; affected env entries will be dropped with per-key warnings.`);
|
|
39565
|
+
}
|
|
39566
|
+
const partitionAndSuffix = region ? derivePartitionAndUrlSuffix(region) : void 0;
|
|
39567
|
+
const bag = {
|
|
39568
|
+
...accountId !== void 0 && { accountId },
|
|
39569
|
+
...region !== void 0 && { region },
|
|
39570
|
+
...partitionAndSuffix && {
|
|
39571
|
+
partition: partitionAndSuffix.partition,
|
|
39572
|
+
urlSuffix: partitionAndSuffix.urlSuffix
|
|
39573
|
+
}
|
|
39574
|
+
};
|
|
39575
|
+
return Object.keys(bag).length === 0 ? void 0 : bag;
|
|
39576
|
+
}
|
|
39449
39577
|
/** Validate `--debug-port-base`. */
|
|
39450
39578
|
function parseDebugPort(raw) {
|
|
39451
39579
|
const parsed = parseInt(raw, 10);
|
|
@@ -39456,11 +39584,12 @@ function parseDebugPort(raw) {
|
|
|
39456
39584
|
* Builder for the `start-api` subcommand. Wired up by `local.ts`.
|
|
39457
39585
|
*/
|
|
39458
39586
|
function createLocalStartApiCommand() {
|
|
39459
|
-
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 and Cognito User Pool / HTTP v2 JWT authorizers; 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 `cdkd local invoke` / `cdkd local 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.")).action(withErrorHandling(localStartApiCommand));
|
|
39587
|
+
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 and Cognito User Pool / HTTP v2 JWT authorizers; 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 `cdkd local invoke` / `cdkd local 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("--from-state", "Read cdkd S3 state for every routed stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::Join (and AWS pseudo parameters) in Lambda env vars with the deployed physical IDs / attributes. Off by default — pre-PR warn-and-drop semantics are preserved. Turn on for stacks already deployed via cdkd deploy. Mirrors `cdkd local invoke --from-state` / `cdkd local run-task --from-state`. Re-runs against fresh state on every hot-reload firing (--watch).").default(false)).addOption(new Option("--stack-region <region>", "Region of the cdkd state record to read (used with --from-state when the same stack name has state in multiple regions).")).action(withErrorHandling(localStartApiCommand));
|
|
39460
39588
|
[
|
|
39461
39589
|
...commonOptions,
|
|
39462
39590
|
...appOptions,
|
|
39463
|
-
...contextOptions
|
|
39591
|
+
...contextOptions,
|
|
39592
|
+
...stateOptions
|
|
39464
39593
|
].forEach((opt) => startApi.addOption(opt));
|
|
39465
39594
|
startApi.addOption(deprecatedRegionOption);
|
|
39466
39595
|
return startApi;
|
|
@@ -42295,7 +42424,7 @@ function reorderArgs(argv) {
|
|
|
42295
42424
|
*/
|
|
42296
42425
|
async function main() {
|
|
42297
42426
|
const program = new Command();
|
|
42298
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.98.
|
|
42427
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.98.2");
|
|
42299
42428
|
program.addCommand(createBootstrapCommand());
|
|
42300
42429
|
program.addCommand(createSynthCommand());
|
|
42301
42430
|
program.addCommand(createListCommand());
|