@go-to-k/cdkd 0.67.0 → 0.69.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 +11 -2
- package/dist/cli.js +417 -52
- package/dist/cli.js.map +3 -3
- package/dist/go-to-k-cdkd-0.69.0.tgz +0 -0
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.67.0.tgz +0 -0
package/README.md
CHANGED
|
@@ -581,8 +581,9 @@ Lambda Runtime Interface Emulator (RIE). Modeled on `sam local invoke`
|
|
|
581
581
|
but reusing cdkd's synthesis / asset / construct-path plumbing — no
|
|
582
582
|
`template.yaml` to maintain, no `cdk synth | sam ...` round-trip.
|
|
583
583
|
|
|
584
|
-
Requires Docker. v1 supports Node.js runtimes
|
|
585
|
-
`nodejs20.x` / `nodejs22.x`
|
|
584
|
+
Requires Docker. v1 supports Node.js and Python runtimes (`nodejs18.x` /
|
|
585
|
+
`nodejs20.x` / `nodejs22.x` / `python3.11` / `python3.12` / `python3.13`);
|
|
586
|
+
other runtimes follow in subsequent PRs.
|
|
586
587
|
|
|
587
588
|
```bash
|
|
588
589
|
# Invoke by CDK display path (single-stack apps may omit the prefix)
|
|
@@ -606,6 +607,14 @@ cdkd local invoke MyStack/Handler --assume-role arn:aws:iam::123456789012:role/M
|
|
|
606
607
|
|
|
607
608
|
# Attach a Node debugger
|
|
608
609
|
cdkd local invoke MyStack/Handler --debug-port 9229
|
|
610
|
+
|
|
611
|
+
# After `cdkd deploy`, recover intrinsic-valued env vars (Ref / Fn::GetAtt
|
|
612
|
+
# / Fn::Sub) from cdkd's S3 state instead of dropping them. Off by default
|
|
613
|
+
# — keeps the local-only / unscoped flow safe; opt in when you want the
|
|
614
|
+
# handler to see the deployed physical IDs (S3 bucket names, DDB table
|
|
615
|
+
# names, IAM role ARNs, ...). Disambiguate with `--stack-region <region>`
|
|
616
|
+
# when the same stack name has state in multiple regions.
|
|
617
|
+
cdkd local invoke MyStack/Handler --from-state
|
|
609
618
|
```
|
|
610
619
|
|
|
611
620
|
See [docs/cli-reference.md](docs/cli-reference.md#local-invoke-run-lambda-functions-locally)
|
package/dist/cli.js
CHANGED
|
@@ -70177,11 +70177,191 @@ function isLiteralEnvValue(value) {
|
|
|
70177
70177
|
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
70178
70178
|
}
|
|
70179
70179
|
|
|
70180
|
+
// src/local-invoke/state-resolver.ts
|
|
70181
|
+
function substituteAgainstState(value, resources) {
|
|
70182
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
70183
|
+
return { kind: "literal", value };
|
|
70184
|
+
}
|
|
70185
|
+
if (value === null || typeof value !== "object") {
|
|
70186
|
+
return {
|
|
70187
|
+
kind: "unresolved",
|
|
70188
|
+
reason: `unsupported value type: ${value === null ? "null" : typeof value}`
|
|
70189
|
+
};
|
|
70190
|
+
}
|
|
70191
|
+
const obj = value;
|
|
70192
|
+
const keys = Object.keys(obj);
|
|
70193
|
+
if (keys.length !== 1) {
|
|
70194
|
+
return {
|
|
70195
|
+
kind: "unresolved",
|
|
70196
|
+
reason: `expected an intrinsic with one key, got ${keys.length} keys`
|
|
70197
|
+
};
|
|
70198
|
+
}
|
|
70199
|
+
const intrinsic = keys[0];
|
|
70200
|
+
const arg = obj[intrinsic];
|
|
70201
|
+
if (intrinsic === "Ref") {
|
|
70202
|
+
return resolveRef(arg, resources);
|
|
70203
|
+
}
|
|
70204
|
+
if (intrinsic === "Fn::GetAtt") {
|
|
70205
|
+
return resolveGetAtt(arg, resources);
|
|
70206
|
+
}
|
|
70207
|
+
if (intrinsic === "Fn::Sub") {
|
|
70208
|
+
return resolveSub(arg, resources);
|
|
70209
|
+
}
|
|
70210
|
+
return {
|
|
70211
|
+
kind: "unresolved",
|
|
70212
|
+
reason: `unsupported intrinsic '${intrinsic}' (only Ref, Fn::GetAtt, Fn::Sub are wired in --from-state v1)`
|
|
70213
|
+
};
|
|
70214
|
+
}
|
|
70215
|
+
function resolveRef(arg, resources) {
|
|
70216
|
+
if (typeof arg !== "string" || arg.length === 0) {
|
|
70217
|
+
return { kind: "unresolved", reason: `Ref expects a non-empty logical ID, got ${typeof arg}` };
|
|
70218
|
+
}
|
|
70219
|
+
const resource = resources[arg];
|
|
70220
|
+
if (!resource) {
|
|
70221
|
+
return {
|
|
70222
|
+
kind: "unresolved",
|
|
70223
|
+
reason: `Ref '${arg}': no record in cdkd state (was the resource deployed?)`
|
|
70224
|
+
};
|
|
70225
|
+
}
|
|
70226
|
+
return { kind: "literal", value: resource.physicalId };
|
|
70227
|
+
}
|
|
70228
|
+
function resolveGetAtt(arg, resources) {
|
|
70229
|
+
let logicalId;
|
|
70230
|
+
let attr;
|
|
70231
|
+
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string") {
|
|
70232
|
+
logicalId = arg[0];
|
|
70233
|
+
if (typeof arg[1] !== "string") {
|
|
70234
|
+
return {
|
|
70235
|
+
kind: "unresolved",
|
|
70236
|
+
reason: `Fn::GetAtt's second arg must be a string attribute name, got ${typeof arg[1]} (nested intrinsics in attribute names are not supported in --from-state v1)`
|
|
70237
|
+
};
|
|
70238
|
+
}
|
|
70239
|
+
attr = arg[1];
|
|
70240
|
+
} else if (typeof arg === "string") {
|
|
70241
|
+
const dot = arg.indexOf(".");
|
|
70242
|
+
if (dot <= 0 || dot === arg.length - 1) {
|
|
70243
|
+
return {
|
|
70244
|
+
kind: "unresolved",
|
|
70245
|
+
reason: `Fn::GetAtt string form must be '<LogicalId>.<Attribute>', got '${arg}'`
|
|
70246
|
+
};
|
|
70247
|
+
}
|
|
70248
|
+
logicalId = arg.slice(0, dot);
|
|
70249
|
+
attr = arg.slice(dot + 1);
|
|
70250
|
+
} else {
|
|
70251
|
+
return {
|
|
70252
|
+
kind: "unresolved",
|
|
70253
|
+
reason: `Fn::GetAtt expects [LogicalId, Attribute] or 'LogicalId.Attribute', got ${Array.isArray(arg) ? `array of length ${arg.length}` : typeof arg}`
|
|
70254
|
+
};
|
|
70255
|
+
}
|
|
70256
|
+
const resource = resources[logicalId];
|
|
70257
|
+
if (!resource) {
|
|
70258
|
+
return {
|
|
70259
|
+
kind: "unresolved",
|
|
70260
|
+
reason: `Fn::GetAtt '${logicalId}.${attr}': no record in cdkd state`
|
|
70261
|
+
};
|
|
70262
|
+
}
|
|
70263
|
+
const cached = resource.attributes?.[attr];
|
|
70264
|
+
if (cached === void 0) {
|
|
70265
|
+
return {
|
|
70266
|
+
kind: "unresolved",
|
|
70267
|
+
reason: `Fn::GetAtt '${logicalId}.${attr}': attribute not captured in cdkd state at deploy time`
|
|
70268
|
+
};
|
|
70269
|
+
}
|
|
70270
|
+
if (typeof cached === "string" || typeof cached === "number" || typeof cached === "boolean") {
|
|
70271
|
+
return { kind: "literal", value: cached };
|
|
70272
|
+
}
|
|
70273
|
+
return { kind: "literal", value: JSON.stringify(cached) };
|
|
70274
|
+
}
|
|
70275
|
+
function resolveSub(arg, resources) {
|
|
70276
|
+
let template;
|
|
70277
|
+
let bindings = {};
|
|
70278
|
+
if (typeof arg === "string") {
|
|
70279
|
+
template = arg;
|
|
70280
|
+
} else if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && arg[1] !== null && typeof arg[1] === "object" && !Array.isArray(arg[1])) {
|
|
70281
|
+
template = arg[0];
|
|
70282
|
+
bindings = arg[1];
|
|
70283
|
+
} else {
|
|
70284
|
+
return {
|
|
70285
|
+
kind: "unresolved",
|
|
70286
|
+
reason: `Fn::Sub expects a string or [string, object], got ${Array.isArray(arg) ? "malformed array" : typeof arg}`
|
|
70287
|
+
};
|
|
70288
|
+
}
|
|
70289
|
+
const placeholderRegex = /\$\{([^}]+)\}/g;
|
|
70290
|
+
const placeholders = [];
|
|
70291
|
+
template.replace(placeholderRegex, (_, key) => {
|
|
70292
|
+
placeholders.push(key);
|
|
70293
|
+
return "";
|
|
70294
|
+
});
|
|
70295
|
+
const resolutions = /* @__PURE__ */ new Map();
|
|
70296
|
+
for (const placeholder of placeholders) {
|
|
70297
|
+
if (resolutions.has(placeholder))
|
|
70298
|
+
continue;
|
|
70299
|
+
if (placeholder in bindings) {
|
|
70300
|
+
const sub = substituteAgainstState(bindings[placeholder], resources);
|
|
70301
|
+
if (sub.kind !== "literal") {
|
|
70302
|
+
return {
|
|
70303
|
+
kind: "unresolved",
|
|
70304
|
+
reason: `Fn::Sub placeholder '\${${placeholder}}': ${sub.reason}`
|
|
70305
|
+
};
|
|
70306
|
+
}
|
|
70307
|
+
resolutions.set(placeholder, String(sub.value));
|
|
70308
|
+
continue;
|
|
70309
|
+
}
|
|
70310
|
+
const dot = placeholder.indexOf(".");
|
|
70311
|
+
if (dot === -1) {
|
|
70312
|
+
const sub = resolveRef(placeholder, resources);
|
|
70313
|
+
if (sub.kind !== "literal") {
|
|
70314
|
+
return {
|
|
70315
|
+
kind: "unresolved",
|
|
70316
|
+
reason: `Fn::Sub placeholder '\${${placeholder}}': ${sub.reason}`
|
|
70317
|
+
};
|
|
70318
|
+
}
|
|
70319
|
+
resolutions.set(placeholder, String(sub.value));
|
|
70320
|
+
} else {
|
|
70321
|
+
const sub = resolveGetAtt(placeholder, resources);
|
|
70322
|
+
if (sub.kind !== "literal") {
|
|
70323
|
+
return {
|
|
70324
|
+
kind: "unresolved",
|
|
70325
|
+
reason: `Fn::Sub placeholder '\${${placeholder}}': ${sub.reason}`
|
|
70326
|
+
};
|
|
70327
|
+
}
|
|
70328
|
+
resolutions.set(placeholder, String(sub.value));
|
|
70329
|
+
}
|
|
70330
|
+
}
|
|
70331
|
+
const out = template.replace(placeholderRegex, (_, key) => {
|
|
70332
|
+
return resolutions.get(key) ?? "";
|
|
70333
|
+
});
|
|
70334
|
+
return { kind: "literal", value: out };
|
|
70335
|
+
}
|
|
70336
|
+
function substituteEnvVarsFromState(templateEnv, resources) {
|
|
70337
|
+
const env = {};
|
|
70338
|
+
const audit = { resolvedKeys: [], unresolved: [] };
|
|
70339
|
+
if (!templateEnv)
|
|
70340
|
+
return { env, audit };
|
|
70341
|
+
for (const [key, value] of Object.entries(templateEnv)) {
|
|
70342
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
70343
|
+
env[key] = value;
|
|
70344
|
+
continue;
|
|
70345
|
+
}
|
|
70346
|
+
const result = substituteAgainstState(value, resources);
|
|
70347
|
+
if (result.kind === "literal") {
|
|
70348
|
+
env[key] = result.value;
|
|
70349
|
+
audit.resolvedKeys.push(key);
|
|
70350
|
+
} else {
|
|
70351
|
+
audit.unresolved.push({ key, reason: result.reason });
|
|
70352
|
+
}
|
|
70353
|
+
}
|
|
70354
|
+
return { env, audit };
|
|
70355
|
+
}
|
|
70356
|
+
|
|
70180
70357
|
// src/local-invoke/runtime-image.ts
|
|
70181
|
-
var
|
|
70182
|
-
"nodejs18.x": "public.ecr.aws/lambda/nodejs:18",
|
|
70183
|
-
"nodejs20.x": "public.ecr.aws/lambda/nodejs:20",
|
|
70184
|
-
"nodejs22.x": "public.ecr.aws/lambda/nodejs:22"
|
|
70358
|
+
var SUPPORTED_RUNTIMES = {
|
|
70359
|
+
"nodejs18.x": { image: "public.ecr.aws/lambda/nodejs:18", fileExtension: ".js" },
|
|
70360
|
+
"nodejs20.x": { image: "public.ecr.aws/lambda/nodejs:20", fileExtension: ".js" },
|
|
70361
|
+
"nodejs22.x": { image: "public.ecr.aws/lambda/nodejs:22", fileExtension: ".js" },
|
|
70362
|
+
"python3.11": { image: "public.ecr.aws/lambda/python:3.11", fileExtension: ".py" },
|
|
70363
|
+
"python3.12": { image: "public.ecr.aws/lambda/python:3.12", fileExtension: ".py" },
|
|
70364
|
+
"python3.13": { image: "public.ecr.aws/lambda/python:3.13", fileExtension: ".py" }
|
|
70185
70365
|
};
|
|
70186
70366
|
var UnsupportedRuntimeError = class _UnsupportedRuntimeError extends Error {
|
|
70187
70367
|
constructor(runtime, message) {
|
|
@@ -70192,24 +70372,30 @@ var UnsupportedRuntimeError = class _UnsupportedRuntimeError extends Error {
|
|
|
70192
70372
|
}
|
|
70193
70373
|
};
|
|
70194
70374
|
function resolveRuntimeImage(runtime) {
|
|
70375
|
+
return resolveRuntimeSpec(runtime).image;
|
|
70376
|
+
}
|
|
70377
|
+
function resolveRuntimeFileExtension(runtime) {
|
|
70378
|
+
return resolveRuntimeSpec(runtime).fileExtension;
|
|
70379
|
+
}
|
|
70380
|
+
function resolveRuntimeSpec(runtime) {
|
|
70195
70381
|
if (typeof runtime !== "string" || runtime.length === 0) {
|
|
70196
70382
|
throw new UnsupportedRuntimeError(
|
|
70197
70383
|
String(runtime),
|
|
70198
70384
|
"Lambda function has no Runtime property. Container-image Lambdas (Code.ImageUri) are not supported in cdkd local invoke v1."
|
|
70199
70385
|
);
|
|
70200
70386
|
}
|
|
70201
|
-
const
|
|
70202
|
-
if (
|
|
70203
|
-
return
|
|
70204
|
-
if (runtime.startsWith("
|
|
70387
|
+
const spec = SUPPORTED_RUNTIMES[runtime];
|
|
70388
|
+
if (spec)
|
|
70389
|
+
return spec;
|
|
70390
|
+
if (runtime.startsWith("java") || runtime.startsWith("dotnet") || runtime.startsWith("ruby") || runtime.startsWith("go") || runtime.startsWith("provided")) {
|
|
70205
70391
|
throw new UnsupportedRuntimeError(
|
|
70206
70392
|
runtime,
|
|
70207
|
-
`Runtime '${runtime}' is not supported in cdkd local invoke v1. Only Node.js
|
|
70393
|
+
`Runtime '${runtime}' is not supported in cdkd local invoke v1. Only Node.js (nodejs18.x / nodejs20.x / nodejs22.x) and Python (python3.11 / python3.12 / python3.13) runtimes are supported. Other runtimes follow in subsequent PRs.`
|
|
70208
70394
|
);
|
|
70209
70395
|
}
|
|
70210
70396
|
throw new UnsupportedRuntimeError(
|
|
70211
70397
|
runtime,
|
|
70212
|
-
`Unknown runtime '${runtime}'. cdkd local invoke v1 supports nodejs18.x / nodejs20.x / nodejs22.x.`
|
|
70398
|
+
`Unknown runtime '${runtime}'. cdkd local invoke v1 supports nodejs18.x / nodejs20.x / nodejs22.x / python3.11 / python3.12 / python3.13.`
|
|
70213
70399
|
);
|
|
70214
70400
|
}
|
|
70215
70401
|
|
|
@@ -70343,9 +70529,11 @@ async function waitForRieReady(host, port, timeoutMs = 5e3) {
|
|
|
70343
70529
|
let lastError;
|
|
70344
70530
|
while (Date.now() < deadline) {
|
|
70345
70531
|
try {
|
|
70346
|
-
const ok = await
|
|
70347
|
-
if (ok)
|
|
70532
|
+
const ok = await httpProbe(host, port, 500);
|
|
70533
|
+
if (ok) {
|
|
70534
|
+
await delay(250);
|
|
70348
70535
|
return;
|
|
70536
|
+
}
|
|
70349
70537
|
} catch (err) {
|
|
70350
70538
|
lastError = err;
|
|
70351
70539
|
}
|
|
@@ -70356,19 +70544,50 @@ async function waitForRieReady(host, port, timeoutMs = 5e3) {
|
|
|
70356
70544
|
`RIE did not become ready on ${host}:${port} within ${timeoutMs}ms${tail}. The container may have exited early \u2014 check 'docker logs' output.`
|
|
70357
70545
|
);
|
|
70358
70546
|
}
|
|
70359
|
-
async function
|
|
70360
|
-
const url = `http://${host}:${port}${INVOKE_PATH}`;
|
|
70361
|
-
const body = JSON.stringify(event ?? {});
|
|
70547
|
+
async function httpProbe(host, port, timeoutMs) {
|
|
70362
70548
|
const controller = new AbortController();
|
|
70363
70549
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
70364
|
-
let response;
|
|
70365
70550
|
try {
|
|
70366
|
-
response = await fetch(
|
|
70551
|
+
const response = await fetch(`http://${host}:${port}/`, {
|
|
70367
70552
|
method: "POST",
|
|
70368
70553
|
headers: { "Content-Type": "application/json" },
|
|
70369
|
-
body,
|
|
70554
|
+
body: "{}",
|
|
70370
70555
|
signal: controller.signal
|
|
70371
70556
|
});
|
|
70557
|
+
await response.text().catch(() => void 0);
|
|
70558
|
+
return true;
|
|
70559
|
+
} catch (err) {
|
|
70560
|
+
if (isTransientNetworkError(err))
|
|
70561
|
+
return false;
|
|
70562
|
+
throw err;
|
|
70563
|
+
} finally {
|
|
70564
|
+
clearTimeout(timer);
|
|
70565
|
+
}
|
|
70566
|
+
}
|
|
70567
|
+
function isTransientNetworkError(err) {
|
|
70568
|
+
if (!(err instanceof Error))
|
|
70569
|
+
return false;
|
|
70570
|
+
if (err.name === "AbortError")
|
|
70571
|
+
return true;
|
|
70572
|
+
if (err.name === "TypeError" && err.message === "fetch failed")
|
|
70573
|
+
return true;
|
|
70574
|
+
const cause = err.cause;
|
|
70575
|
+
if (cause?.code === "ECONNRESET")
|
|
70576
|
+
return true;
|
|
70577
|
+
if (cause?.code === "ECONNREFUSED")
|
|
70578
|
+
return true;
|
|
70579
|
+
if (cause?.code === "UND_ERR_SOCKET")
|
|
70580
|
+
return true;
|
|
70581
|
+
return false;
|
|
70582
|
+
}
|
|
70583
|
+
async function invokeRie(host, port, event, timeoutMs) {
|
|
70584
|
+
const url = `http://${host}:${port}${INVOKE_PATH}`;
|
|
70585
|
+
const body = JSON.stringify(event ?? {});
|
|
70586
|
+
const controller = new AbortController();
|
|
70587
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
70588
|
+
let response;
|
|
70589
|
+
try {
|
|
70590
|
+
response = await fetchWithStartupRetry(url, body, controller.signal);
|
|
70372
70591
|
} catch (err) {
|
|
70373
70592
|
if (err.name === "AbortError") {
|
|
70374
70593
|
throw new Error(
|
|
@@ -70387,36 +70606,32 @@ async function invokeRie(host, port, event, timeoutMs) {
|
|
|
70387
70606
|
}
|
|
70388
70607
|
return { payload, raw };
|
|
70389
70608
|
}
|
|
70390
|
-
async function
|
|
70391
|
-
const
|
|
70392
|
-
|
|
70393
|
-
|
|
70394
|
-
|
|
70395
|
-
|
|
70396
|
-
|
|
70397
|
-
|
|
70398
|
-
|
|
70399
|
-
|
|
70400
|
-
|
|
70401
|
-
|
|
70402
|
-
|
|
70403
|
-
|
|
70404
|
-
|
|
70405
|
-
|
|
70406
|
-
|
|
70407
|
-
|
|
70408
|
-
|
|
70409
|
-
|
|
70410
|
-
|
|
70411
|
-
|
|
70412
|
-
}
|
|
70413
|
-
rejectProbe(err);
|
|
70414
|
-
});
|
|
70415
|
-
socket.connect(port, host);
|
|
70416
|
-
});
|
|
70609
|
+
async function fetchWithStartupRetry(url, body, signal) {
|
|
70610
|
+
const maxAttempts = 3;
|
|
70611
|
+
let lastError;
|
|
70612
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
70613
|
+
try {
|
|
70614
|
+
return await fetch(url, {
|
|
70615
|
+
method: "POST",
|
|
70616
|
+
headers: { "Content-Type": "application/json" },
|
|
70617
|
+
body,
|
|
70618
|
+
signal
|
|
70619
|
+
});
|
|
70620
|
+
} catch (err) {
|
|
70621
|
+
const name = err.name;
|
|
70622
|
+
if (name === "AbortError")
|
|
70623
|
+
throw err;
|
|
70624
|
+
lastError = err;
|
|
70625
|
+
if (attempt === maxAttempts)
|
|
70626
|
+
break;
|
|
70627
|
+
await delay(200);
|
|
70628
|
+
}
|
|
70629
|
+
}
|
|
70630
|
+
throw lastError;
|
|
70417
70631
|
}
|
|
70418
70632
|
|
|
70419
70633
|
// src/cli/commands/local-invoke.ts
|
|
70634
|
+
init_aws_clients();
|
|
70420
70635
|
async function localInvokeCommand(target, options) {
|
|
70421
70636
|
const logger = getLogger();
|
|
70422
70637
|
if (options.verbose) {
|
|
@@ -70442,14 +70657,49 @@ async function localInvokeCommand(target, options) {
|
|
|
70442
70657
|
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
70443
70658
|
const lambda = resolveLambdaTarget(target, stacks);
|
|
70444
70659
|
logger.info(`Target: ${lambda.stack.stackName}/${lambda.logicalId} (${lambda.runtime})`);
|
|
70445
|
-
const codeDir = lambda.codePath ?? materializeInlineCode(
|
|
70660
|
+
const codeDir = lambda.codePath ?? materializeInlineCode(
|
|
70661
|
+
lambda.handler,
|
|
70662
|
+
lambda.inlineCode ?? "",
|
|
70663
|
+
resolveRuntimeFileExtension(lambda.runtime)
|
|
70664
|
+
);
|
|
70665
|
+
let stateAudit;
|
|
70666
|
+
let templateEnv = getTemplateEnv(lambda.resource);
|
|
70667
|
+
let stateForRoleHint;
|
|
70668
|
+
if (options.fromState) {
|
|
70669
|
+
const loaded = await loadStateForStack(lambda.stack.stackName, lambda.stack.region, {
|
|
70670
|
+
...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
|
|
70671
|
+
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
70672
|
+
statePrefix: options.statePrefix,
|
|
70673
|
+
...options.region !== void 0 && { region: options.region },
|
|
70674
|
+
...options.profile !== void 0 && { profile: options.profile }
|
|
70675
|
+
});
|
|
70676
|
+
if (loaded) {
|
|
70677
|
+
stateForRoleHint = loaded.state;
|
|
70678
|
+
const { env, audit } = substituteEnvVarsFromState(templateEnv, loaded.state.resources);
|
|
70679
|
+
templateEnv = env;
|
|
70680
|
+
stateAudit = audit;
|
|
70681
|
+
for (const key of audit.resolvedKeys) {
|
|
70682
|
+
logger.debug(`--from-state: substituted env var ${key} from cdkd state`);
|
|
70683
|
+
}
|
|
70684
|
+
for (const { key, reason } of audit.unresolved) {
|
|
70685
|
+
logger.warn(
|
|
70686
|
+
`--from-state: could not substitute env var ${key} (${reason}). Override it via --env-vars or it will be dropped.`
|
|
70687
|
+
);
|
|
70688
|
+
}
|
|
70689
|
+
}
|
|
70690
|
+
}
|
|
70446
70691
|
const overrides = readEnvOverridesFile(options.envVars);
|
|
70447
|
-
const envResult = resolveEnvVars(lambda.logicalId,
|
|
70692
|
+
const envResult = resolveEnvVars(lambda.logicalId, templateEnv, overrides);
|
|
70448
70693
|
for (const key of envResult.unresolved) {
|
|
70694
|
+
if (stateAudit && stateAudit.unresolved.some((u) => u.key === key))
|
|
70695
|
+
continue;
|
|
70449
70696
|
logger.warn(
|
|
70450
|
-
`Environment variable ${key} contains a CloudFormation intrinsic and was dropped. Override it with --env-vars (e.g. {"${lambda.logicalId}":{"${key}":"<literal>"}}) or
|
|
70697
|
+
`Environment variable ${key} contains a CloudFormation intrinsic and was dropped. Override it with --env-vars (e.g. {"${lambda.logicalId}":{"${key}":"<literal>"}}) or pass --from-state to recover deployed values.`
|
|
70451
70698
|
);
|
|
70452
70699
|
}
|
|
70700
|
+
if (options.fromState && !options.assumeRole && stateForRoleHint) {
|
|
70701
|
+
suggestAssumeRoleFromState(stateForRoleHint, lambda.logicalId);
|
|
70702
|
+
}
|
|
70453
70703
|
const event = await readEvent(options);
|
|
70454
70704
|
const image = resolveRuntimeImage(lambda.runtime);
|
|
70455
70705
|
const dockerEnv = {
|
|
@@ -70616,18 +70866,121 @@ function forwardAwsEnv(env) {
|
|
|
70616
70866
|
env[key] = value;
|
|
70617
70867
|
}
|
|
70618
70868
|
}
|
|
70619
|
-
function materializeInlineCode(handler, source) {
|
|
70869
|
+
function materializeInlineCode(handler, source, fileExtension) {
|
|
70620
70870
|
const lastDot = handler.lastIndexOf(".");
|
|
70621
70871
|
if (lastDot <= 0) {
|
|
70622
70872
|
throw new Error(`Handler '${handler}' is malformed: expected '<modulePath>.<exportName>'.`);
|
|
70623
70873
|
}
|
|
70624
70874
|
const modulePath = handler.substring(0, lastDot);
|
|
70625
70875
|
const dir = mkdtempSync2(path.join(tmpdir2(), "cdkd-local-invoke-"));
|
|
70626
|
-
const filePath = path.join(dir, `${modulePath}
|
|
70876
|
+
const filePath = path.join(dir, `${modulePath}${fileExtension}`);
|
|
70627
70877
|
mkdirSync2(path.dirname(filePath), { recursive: true });
|
|
70628
70878
|
writeFileSync5(filePath, source, "utf-8");
|
|
70629
70879
|
return dir;
|
|
70630
70880
|
}
|
|
70881
|
+
async function loadStateForStack(stackName, synthRegion, opts) {
|
|
70882
|
+
const logger = getLogger();
|
|
70883
|
+
const region = opts.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? synthRegion ?? "us-east-1";
|
|
70884
|
+
let stateBucket;
|
|
70885
|
+
try {
|
|
70886
|
+
stateBucket = await resolveStateBucketWithDefault(opts.stateBucket, region);
|
|
70887
|
+
} catch (err) {
|
|
70888
|
+
logger.warn(
|
|
70889
|
+
`--from-state: could not resolve state bucket: ${err instanceof Error ? err.message : String(err)}. Falling back to PR 1 warn-and-drop semantics.`
|
|
70890
|
+
);
|
|
70891
|
+
return void 0;
|
|
70892
|
+
}
|
|
70893
|
+
const awsClients = new AwsClients({
|
|
70894
|
+
...opts.region !== void 0 && { region: opts.region },
|
|
70895
|
+
...opts.profile !== void 0 && { profile: opts.profile }
|
|
70896
|
+
});
|
|
70897
|
+
setAwsClients(awsClients);
|
|
70898
|
+
try {
|
|
70899
|
+
const stateConfig = { bucket: stateBucket, prefix: opts.statePrefix };
|
|
70900
|
+
const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
|
|
70901
|
+
...opts.region !== void 0 && { region: opts.region },
|
|
70902
|
+
...opts.profile !== void 0 && { profile: opts.profile }
|
|
70903
|
+
});
|
|
70904
|
+
await stateBackend.verifyBucketExists();
|
|
70905
|
+
const refs = (await stateBackend.listStacks()).filter((r) => r.stackName === stackName);
|
|
70906
|
+
if (refs.length === 0) {
|
|
70907
|
+
logger.warn(
|
|
70908
|
+
`--from-state: no cdkd state found for stack '${stackName}' in bucket '${stateBucket}'. Was it deployed via 'cdkd deploy'? Falling back to PR 1 warn-and-drop semantics.`
|
|
70909
|
+
);
|
|
70910
|
+
return void 0;
|
|
70911
|
+
}
|
|
70912
|
+
let targetRegion;
|
|
70913
|
+
if (opts.stackRegion) {
|
|
70914
|
+
const found = refs.find((r) => r.region === opts.stackRegion);
|
|
70915
|
+
if (!found) {
|
|
70916
|
+
const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
70917
|
+
logger.warn(
|
|
70918
|
+
`--from-state: stack '${stackName}' has no state in region '${opts.stackRegion}' (available: ${seen}). Falling back.`
|
|
70919
|
+
);
|
|
70920
|
+
return void 0;
|
|
70921
|
+
}
|
|
70922
|
+
targetRegion = opts.stackRegion;
|
|
70923
|
+
} else if (synthRegion && refs.some((r) => r.region === synthRegion)) {
|
|
70924
|
+
targetRegion = synthRegion;
|
|
70925
|
+
} else if (refs.length === 1) {
|
|
70926
|
+
targetRegion = refs[0].region ?? synthRegion ?? region;
|
|
70927
|
+
} else {
|
|
70928
|
+
const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
|
|
70929
|
+
logger.warn(
|
|
70930
|
+
`--from-state: stack '${stackName}' has state in multiple regions (${seen}). Re-run with --stack-region <region>. Falling back.`
|
|
70931
|
+
);
|
|
70932
|
+
return void 0;
|
|
70933
|
+
}
|
|
70934
|
+
const stateData = await stateBackend.getState(stackName, targetRegion);
|
|
70935
|
+
if (!stateData) {
|
|
70936
|
+
logger.warn(
|
|
70937
|
+
`--from-state: state record for '${stackName}' (${targetRegion}) returned empty. Falling back.`
|
|
70938
|
+
);
|
|
70939
|
+
return void 0;
|
|
70940
|
+
}
|
|
70941
|
+
logger.debug(`--from-state: loaded state for ${stackName} (${targetRegion})`);
|
|
70942
|
+
return { state: stateData.state, region: targetRegion };
|
|
70943
|
+
} finally {
|
|
70944
|
+
awsClients.destroy();
|
|
70945
|
+
}
|
|
70946
|
+
}
|
|
70947
|
+
function suggestAssumeRoleFromState(state, logicalId) {
|
|
70948
|
+
const logger = getLogger();
|
|
70949
|
+
const lambda = state.resources[logicalId];
|
|
70950
|
+
if (!lambda)
|
|
70951
|
+
return;
|
|
70952
|
+
const roleRef = lambda.properties?.["Role"] ?? lambda.observedProperties?.["Role"];
|
|
70953
|
+
let roleArn;
|
|
70954
|
+
if (typeof roleRef === "string" && roleRef.startsWith("arn:")) {
|
|
70955
|
+
roleArn = roleRef;
|
|
70956
|
+
} else if (typeof roleRef === "object" && roleRef !== null) {
|
|
70957
|
+
const refLogicalId = pickReferencedLogicalId(roleRef);
|
|
70958
|
+
if (refLogicalId) {
|
|
70959
|
+
const roleResource = state.resources[refLogicalId];
|
|
70960
|
+
const cached = roleResource?.attributes?.["Arn"];
|
|
70961
|
+
if (typeof cached === "string" && cached.startsWith("arn:")) {
|
|
70962
|
+
roleArn = cached;
|
|
70963
|
+
}
|
|
70964
|
+
}
|
|
70965
|
+
}
|
|
70966
|
+
if (roleArn) {
|
|
70967
|
+
logger.info(
|
|
70968
|
+
`Hint: the deployed function uses execution role ${roleArn}. Re-run with --assume-role <that-arn> to invoke under the deployed function's narrow permissions.`
|
|
70969
|
+
);
|
|
70970
|
+
}
|
|
70971
|
+
}
|
|
70972
|
+
function pickReferencedLogicalId(intrinsic) {
|
|
70973
|
+
if ("Ref" in intrinsic && typeof intrinsic["Ref"] === "string")
|
|
70974
|
+
return intrinsic["Ref"];
|
|
70975
|
+
if ("Fn::GetAtt" in intrinsic) {
|
|
70976
|
+
const arg = intrinsic["Fn::GetAtt"];
|
|
70977
|
+
if (Array.isArray(arg) && typeof arg[0] === "string")
|
|
70978
|
+
return arg[0];
|
|
70979
|
+
if (typeof arg === "string")
|
|
70980
|
+
return arg.split(".")[0];
|
|
70981
|
+
}
|
|
70982
|
+
return void 0;
|
|
70983
|
+
}
|
|
70631
70984
|
function createLocalCommand() {
|
|
70632
70985
|
const local = new Command14("local").description(
|
|
70633
70986
|
"Local Lambda execution against the AWS Lambda Runtime Interface Emulator (Docker required)"
|
|
@@ -70646,8 +70999,20 @@ function createLocalCommand() {
|
|
|
70646
70999
|
"--assume-role <arn>",
|
|
70647
71000
|
`Assume the Lambda's deployed execution role and forward STS-issued temp credentials to the container so the handler runs with the deployed function's narrow permissions (closes the "developer admin / function narrow" skew). Off by default \u2014 when omitted, the developer's shell credentials are forwarded unchanged (SAM-compatible default).`
|
|
70648
71001
|
)
|
|
71002
|
+
).addOption(
|
|
71003
|
+
new Option7(
|
|
71004
|
+
"--from-state",
|
|
71005
|
+
"Read cdkd S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub in env vars with the deployed physical IDs / attributes. Off by default \u2014 keep PR 1 warn-and-drop semantics; turn on for stacks already deployed via cdkd deploy."
|
|
71006
|
+
).default(false)
|
|
71007
|
+
).addOption(
|
|
71008
|
+
new Option7(
|
|
71009
|
+
"--stack-region <region>",
|
|
71010
|
+
"Region of the cdkd state record to read (used with --from-state when the same stack name has state in multiple regions)."
|
|
71011
|
+
)
|
|
70649
71012
|
).action(withErrorHandling(localInvokeCommand));
|
|
70650
|
-
[...commonOptions, ...appOptions, ...contextOptions].forEach(
|
|
71013
|
+
[...commonOptions, ...appOptions, ...contextOptions, ...stateOptions].forEach(
|
|
71014
|
+
(opt) => invoke.addOption(opt)
|
|
71015
|
+
);
|
|
70651
71016
|
invoke.addOption(deprecatedRegionOption);
|
|
70652
71017
|
local.addCommand(invoke);
|
|
70653
71018
|
return local;
|
|
@@ -70682,7 +71047,7 @@ function reorderArgs(argv) {
|
|
|
70682
71047
|
}
|
|
70683
71048
|
async function main() {
|
|
70684
71049
|
const program = new Command15();
|
|
70685
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
71050
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.69.0");
|
|
70686
71051
|
program.addCommand(createBootstrapCommand());
|
|
70687
71052
|
program.addCommand(createSynthCommand());
|
|
70688
71053
|
program.addCommand(createListCommand());
|