@go-to-k/cdkd 0.125.0 → 0.126.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/dist/cli.js +179 -8
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -65,6 +65,7 @@ import { promisify } from "node:util";
|
|
|
65
65
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
66
66
|
import { readFile } from "fs/promises";
|
|
67
67
|
import { createServer as createServer$1 } from "node:http";
|
|
68
|
+
import { createServer as createServer$2 } from "node:https";
|
|
68
69
|
import * as chokidar from "chokidar";
|
|
69
70
|
|
|
70
71
|
//#region src/cli/options.ts
|
|
@@ -40562,7 +40563,7 @@ function buildHttpApiV2Event(req, ctx, opts = {}) {
|
|
|
40562
40563
|
stage: ctx.route.stage,
|
|
40563
40564
|
time: formatRequestTime(now),
|
|
40564
40565
|
timeEpoch: now.getTime(),
|
|
40565
|
-
authentication: null,
|
|
40566
|
+
authentication: req.clientCert ? { clientCert: req.clientCert } : null,
|
|
40566
40567
|
authorizer: null
|
|
40567
40568
|
},
|
|
40568
40569
|
body,
|
|
@@ -40612,7 +40613,8 @@ function buildRestV1Event(req, ctx, opts = {}) {
|
|
|
40612
40613
|
httpMethod: req.method.toUpperCase(),
|
|
40613
40614
|
identity: {
|
|
40614
40615
|
sourceIp: req.sourceIp ?? "127.0.0.1",
|
|
40615
|
-
userAgent: headers["user-agent"] ?? ""
|
|
40616
|
+
userAgent: headers["user-agent"] ?? "",
|
|
40617
|
+
...req.clientCert && { clientCert: req.clientCert }
|
|
40616
40618
|
},
|
|
40617
40619
|
path: `/${ctx.route.stage}${ctx.matchedPath}`,
|
|
40618
40620
|
protocol: "HTTP/1.1",
|
|
@@ -42804,12 +42806,20 @@ function amzDateOutsideSkew(amzDate, now) {
|
|
|
42804
42806
|
async function startApiServer(opts) {
|
|
42805
42807
|
const logger = getLogger().child("start-api");
|
|
42806
42808
|
let currentState = opts.state;
|
|
42807
|
-
const
|
|
42809
|
+
const requestHandler = (req, res) => {
|
|
42808
42810
|
handleRequest(req, res, currentState, opts).catch((err) => {
|
|
42809
42811
|
logger.error(`Unhandled request error: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
|
|
42810
42812
|
if (!res.headersSent) writeError(res, 502);
|
|
42811
42813
|
});
|
|
42812
|
-
}
|
|
42814
|
+
};
|
|
42815
|
+
const server = opts.mtls ? createServer$2({
|
|
42816
|
+
requestCert: true,
|
|
42817
|
+
rejectUnauthorized: true,
|
|
42818
|
+
ca: opts.mtls.caPem,
|
|
42819
|
+
cert: opts.mtls.certPem,
|
|
42820
|
+
key: opts.mtls.keyPem
|
|
42821
|
+
}, requestHandler) : createServer$1(requestHandler);
|
|
42822
|
+
const scheme = opts.mtls ? "https" : "http";
|
|
42813
42823
|
server.on("connection", (socket) => {
|
|
42814
42824
|
socket.setNoDelay(true);
|
|
42815
42825
|
});
|
|
@@ -42831,6 +42841,7 @@ async function startApiServer(opts) {
|
|
|
42831
42841
|
return {
|
|
42832
42842
|
port: actualPort,
|
|
42833
42843
|
host: actualHost,
|
|
42844
|
+
scheme,
|
|
42834
42845
|
server,
|
|
42835
42846
|
close: async () => {
|
|
42836
42847
|
if (closed) return;
|
|
@@ -42886,12 +42897,14 @@ async function handleRequest(req, res, state, opts) {
|
|
|
42886
42897
|
return;
|
|
42887
42898
|
}
|
|
42888
42899
|
const authorizer = state.routes.find((r) => r.route.declaredAt === match.route.declaredAt && r.route.method === match.route.method)?.authorizer;
|
|
42900
|
+
const clientCert = opts.mtls ? extractClientCert(req) : void 0;
|
|
42889
42901
|
const snapshot = {
|
|
42890
42902
|
method,
|
|
42891
42903
|
rawUrl,
|
|
42892
42904
|
headers: collectHeaders(req),
|
|
42893
42905
|
body: bodyBuf,
|
|
42894
|
-
...req.socket.remoteAddress !== void 0 && { sourceIp: req.socket.remoteAddress }
|
|
42906
|
+
...req.socket.remoteAddress !== void 0 && { sourceIp: req.socket.remoteAddress },
|
|
42907
|
+
...clientCert && { clientCert }
|
|
42895
42908
|
};
|
|
42896
42909
|
const matchCtx = {
|
|
42897
42910
|
route: match.route,
|
|
@@ -43357,6 +43370,135 @@ function writeMockCorsPreflight(res, preflight) {
|
|
|
43357
43370
|
for (const [name, value] of Object.entries(preflight.headers)) res.setHeader(name, value);
|
|
43358
43371
|
res.end();
|
|
43359
43372
|
}
|
|
43373
|
+
/**
|
|
43374
|
+
* Extract the verified client certificate from a request's TLS socket.
|
|
43375
|
+
*
|
|
43376
|
+
* Pre-conditions (load-bearing — caller MUST gate on `opts.mtls`):
|
|
43377
|
+
* - The server was started with `https.createServer({requestCert: true,
|
|
43378
|
+
* rejectUnauthorized: true, ...})`, so the TLS handshake has
|
|
43379
|
+
* already rejected unknown-CA / self-signed / missing-cert clients
|
|
43380
|
+
* by the time `handleRequest` runs. Any peer cert we see here is
|
|
43381
|
+
* structurally valid against the supplied CA bundle — we do NOT
|
|
43382
|
+
* re-verify in code.
|
|
43383
|
+
*
|
|
43384
|
+
* Returns `undefined` when the request was not over a TLS socket (the
|
|
43385
|
+
* caller should NOT call this on plain-HTTP requests; the gate is the
|
|
43386
|
+
* `opts.mtls` check in `handleRequest`).
|
|
43387
|
+
*
|
|
43388
|
+
* The returned shape is the AWS-canonical
|
|
43389
|
+
* `requestContext.identity.clientCert` per
|
|
43390
|
+
* https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mutual-tls.html#api-gateway-mutual-tls-event-shape:
|
|
43391
|
+
*
|
|
43392
|
+
* {
|
|
43393
|
+
* clientCertPem: "-----BEGIN CERTIFICATE-----\n...",
|
|
43394
|
+
* subjectDN: "CN=client,O=example,C=US",
|
|
43395
|
+
* issuerDN: "CN=My CA,O=example,C=US",
|
|
43396
|
+
* serialNumber: "01:23:45:67:...",
|
|
43397
|
+
* validity: { notBefore: "May 22 03:30:00 2026 GMT",
|
|
43398
|
+
* notAfter: "May 22 03:30:00 2027 GMT" }
|
|
43399
|
+
* }
|
|
43400
|
+
*
|
|
43401
|
+
* Exported for unit testing — the helper is pure-functional given a
|
|
43402
|
+
* cert object and never touches the network.
|
|
43403
|
+
*/
|
|
43404
|
+
function extractClientCert(req) {
|
|
43405
|
+
const socket = req.socket;
|
|
43406
|
+
if (typeof socket.getPeerCertificate !== "function") return void 0;
|
|
43407
|
+
return peerCertificateToAws(socket.getPeerCertificate(false));
|
|
43408
|
+
}
|
|
43409
|
+
/**
|
|
43410
|
+
* Convert Node's `PeerCertificate` object to the AWS-canonical
|
|
43411
|
+
* `clientCert` event shape. Exported separately from
|
|
43412
|
+
* {@link extractClientCert} so the conversion can be unit-tested
|
|
43413
|
+
* against a synthetic cert object without a real TLS socket.
|
|
43414
|
+
*
|
|
43415
|
+
* Returns `undefined` when the cert is empty (`getPeerCertificate`
|
|
43416
|
+
* returns `{}` when there is no peer cert). Otherwise emits every
|
|
43417
|
+
* field defined by the AWS shape, falling back to `''` for missing
|
|
43418
|
+
* subject / issuer DN segments so handlers do not need to null-check.
|
|
43419
|
+
*/
|
|
43420
|
+
function peerCertificateToAws(cert) {
|
|
43421
|
+
if (!cert || typeof cert !== "object") return void 0;
|
|
43422
|
+
if (Object.keys(cert).length === 0) return void 0;
|
|
43423
|
+
const c = cert;
|
|
43424
|
+
const subject = c["subject"];
|
|
43425
|
+
const issuer = c["issuer"];
|
|
43426
|
+
const raw = c["raw"];
|
|
43427
|
+
const subjectDN = formatDN(subject);
|
|
43428
|
+
const issuerDN = formatDN(issuer);
|
|
43429
|
+
const serialNumber = typeof c["serialNumber"] === "string" ? c["serialNumber"] : "";
|
|
43430
|
+
const validity = {
|
|
43431
|
+
notBefore: typeof c["valid_from"] === "string" ? c["valid_from"] : "",
|
|
43432
|
+
notAfter: typeof c["valid_to"] === "string" ? c["valid_to"] : ""
|
|
43433
|
+
};
|
|
43434
|
+
return {
|
|
43435
|
+
clientCertPem: Buffer.isBuffer(raw) ? derBufferToPem(raw) : "",
|
|
43436
|
+
subjectDN,
|
|
43437
|
+
issuerDN,
|
|
43438
|
+
serialNumber,
|
|
43439
|
+
validity
|
|
43440
|
+
};
|
|
43441
|
+
}
|
|
43442
|
+
/**
|
|
43443
|
+
* Format a Node `subject` / `issuer` object (e.g.
|
|
43444
|
+
* `{C: 'US', O: 'example', CN: 'client'}`) as the canonical
|
|
43445
|
+
* comma-separated DN string AWS emits (`CN=client,O=example,C=US`).
|
|
43446
|
+
*
|
|
43447
|
+
* Ordering follows AWS / OpenSSL convention: CN first, then OU, O, L,
|
|
43448
|
+
* ST, C. Fields the cert does not declare are skipped silently.
|
|
43449
|
+
*/
|
|
43450
|
+
function formatDN(dn) {
|
|
43451
|
+
if (!dn || typeof dn !== "object") return "";
|
|
43452
|
+
const obj = dn;
|
|
43453
|
+
const order = [
|
|
43454
|
+
"CN",
|
|
43455
|
+
"OU",
|
|
43456
|
+
"O",
|
|
43457
|
+
"L",
|
|
43458
|
+
"ST",
|
|
43459
|
+
"C"
|
|
43460
|
+
];
|
|
43461
|
+
const parts = [];
|
|
43462
|
+
for (const key of order) {
|
|
43463
|
+
const v = obj[key];
|
|
43464
|
+
if (typeof v === "string" && v.length > 0) parts.push(`${key}=${v}`);
|
|
43465
|
+
}
|
|
43466
|
+
return parts.join(",");
|
|
43467
|
+
}
|
|
43468
|
+
/**
|
|
43469
|
+
* Encode a DER-encoded certificate Buffer as PEM. We wrap the base64
|
|
43470
|
+
* in 64-char-per-line segments the way `openssl x509` does so the
|
|
43471
|
+
* round-trip looks like what AWS API Gateway emits.
|
|
43472
|
+
*/
|
|
43473
|
+
function derBufferToPem(der) {
|
|
43474
|
+
const b64 = der.toString("base64");
|
|
43475
|
+
const lines = [];
|
|
43476
|
+
for (let i = 0; i < b64.length; i += 64) lines.push(b64.slice(i, i + 64));
|
|
43477
|
+
return `-----BEGIN CERTIFICATE-----\n${lines.join("\n")}\n-----END CERTIFICATE-----\n`;
|
|
43478
|
+
}
|
|
43479
|
+
/**
|
|
43480
|
+
* Read mTLS materials from disk. Each path is a PEM file. The function
|
|
43481
|
+
* throws a wrapped error naming the offending path on `ENOENT` /
|
|
43482
|
+
* permission failures so the CLI surfaces a clear error before the
|
|
43483
|
+
* server starts.
|
|
43484
|
+
*
|
|
43485
|
+
* Exported for the CLI's resolve-then-construct flow + for unit tests.
|
|
43486
|
+
*/
|
|
43487
|
+
function readMtlsMaterialsFromDisk(opts) {
|
|
43488
|
+
return {
|
|
43489
|
+
caPem: readPemOrThrow(opts.truststorePath, "--mtls-truststore"),
|
|
43490
|
+
certPem: readPemOrThrow(opts.certPath, "--mtls-cert"),
|
|
43491
|
+
keyPem: readPemOrThrow(opts.keyPath, "--mtls-key")
|
|
43492
|
+
};
|
|
43493
|
+
}
|
|
43494
|
+
function readPemOrThrow(path, flagName) {
|
|
43495
|
+
try {
|
|
43496
|
+
return readFileSync(path);
|
|
43497
|
+
} catch (err) {
|
|
43498
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
43499
|
+
throw new Error(`${flagName}: cannot read PEM file at '${path}': ${msg}`);
|
|
43500
|
+
}
|
|
43501
|
+
}
|
|
43360
43502
|
|
|
43361
43503
|
//#endregion
|
|
43362
43504
|
//#region src/local/api-server-grouping.ts
|
|
@@ -43955,6 +44097,8 @@ async function localStartApiCommand(target, options) {
|
|
|
43955
44097
|
const rieTimeoutMs = Math.max(3e4, maxTimeoutSec * 2 * 1e3);
|
|
43956
44098
|
const basePort = parseInt(options.port, 10);
|
|
43957
44099
|
if (!Number.isFinite(basePort) || basePort < 0 || basePort > 65535) throw new Error(`--port must be 0..65535 (got ${options.port}).`);
|
|
44100
|
+
const mtlsConfig = resolveMtlsConfig(options);
|
|
44101
|
+
if (mtlsConfig) logger.info("mTLS enabled: client certificates required (chain check against --mtls-truststore at TLS handshake).");
|
|
43958
44102
|
const initialGroups = groupRoutesByServer(initialMaterial.routes);
|
|
43959
44103
|
const servers = [];
|
|
43960
44104
|
let nextPort = basePort;
|
|
@@ -43976,6 +44120,7 @@ async function localStartApiCommand(target, options) {
|
|
|
43976
44120
|
state: groupState,
|
|
43977
44121
|
rieTimeoutMs,
|
|
43978
44122
|
host: options.host,
|
|
44123
|
+
...mtlsConfig && { mtls: mtlsConfig },
|
|
43979
44124
|
port: basePort === 0 ? 0 : nextPort,
|
|
43980
44125
|
authorizerCache,
|
|
43981
44126
|
jwksCache,
|
|
@@ -43993,7 +44138,7 @@ async function localStartApiCommand(target, options) {
|
|
|
43993
44138
|
printPerServerRouteTables(servers);
|
|
43994
44139
|
warnUnsupportedRoutes(servers.flatMap((s) => s.group.routes.map((r) => r.route)), logger);
|
|
43995
44140
|
logger.info(`Per-Lambda concurrency: ${perLambdaConcurrency} (override with --per-lambda-concurrency)`);
|
|
43996
|
-
for (const { group, server } of servers) process.stdout.write(`Server listening on
|
|
44141
|
+
for (const { group, server } of servers) process.stdout.write(`Server listening on ${server.scheme}://${server.host}:${server.port} (${group.displayName})\n`);
|
|
43997
44142
|
process.stdout.write("^C to stop and clean up containers.\n");
|
|
43998
44143
|
let watcher;
|
|
43999
44144
|
let reloadChain = Promise.resolve();
|
|
@@ -44714,10 +44859,36 @@ function parseDebugPort(raw) {
|
|
|
44714
44859
|
return parsed;
|
|
44715
44860
|
}
|
|
44716
44861
|
/**
|
|
44862
|
+
* Resolve the mTLS configuration from CLI options. Returns `undefined`
|
|
44863
|
+
* when none of the three `--mtls-*` flags is set (the server stays
|
|
44864
|
+
* plain-HTTP). When any of the three is set, ALL THREE must be set —
|
|
44865
|
+
* partial configurations are rejected at parse time so the server
|
|
44866
|
+
* never boots in a half-configured state.
|
|
44867
|
+
*
|
|
44868
|
+
* Exported for unit testing.
|
|
44869
|
+
*/
|
|
44870
|
+
function resolveMtlsConfig(options) {
|
|
44871
|
+
const present = [];
|
|
44872
|
+
const absent = [];
|
|
44873
|
+
if (options.mtlsTruststore !== void 0 && options.mtlsTruststore !== "") present.push("--mtls-truststore");
|
|
44874
|
+
else absent.push("--mtls-truststore");
|
|
44875
|
+
if (options.mtlsCert !== void 0 && options.mtlsCert !== "") present.push("--mtls-cert");
|
|
44876
|
+
else absent.push("--mtls-cert");
|
|
44877
|
+
if (options.mtlsKey !== void 0 && options.mtlsKey !== "") present.push("--mtls-key");
|
|
44878
|
+
else absent.push("--mtls-key");
|
|
44879
|
+
if (present.length === 0) return void 0;
|
|
44880
|
+
if (absent.length > 0) throw new Error(`mTLS configuration is incomplete: ${present.join(", ")} set but ${absent.join(", ")} missing. All three of --mtls-truststore, --mtls-cert, and --mtls-key must be set together to enable mTLS, or all three left unset for plain HTTP.`);
|
|
44881
|
+
return readMtlsMaterialsFromDisk({
|
|
44882
|
+
truststorePath: options.mtlsTruststore,
|
|
44883
|
+
certPath: options.mtlsCert,
|
|
44884
|
+
keyPath: options.mtlsKey
|
|
44885
|
+
});
|
|
44886
|
+
}
|
|
44887
|
+
/**
|
|
44717
44888
|
* Builder for the `start-api` subcommand. Wired up by `local.ts`.
|
|
44718
44889
|
*/
|
|
44719
44890
|
function createLocalStartApiCommand() {
|
|
44720
|
-
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 REST v1 AWS_IAM (SigV4 signature verification only — IAM policy evaluation is NOT emulated; see docs/local-emulation.md). 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("--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-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).")).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(localStartApiCommand));
|
|
44891
|
+
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 REST v1 AWS_IAM (SigV4 signature verification only — IAM policy evaluation is NOT emulated; see docs/local-emulation.md). 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("--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-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).")).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=cdkd-local-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(localStartApiCommand));
|
|
44721
44892
|
[
|
|
44722
44893
|
...commonOptions,
|
|
44723
44894
|
...appOptions,
|
|
@@ -47768,7 +47939,7 @@ function reorderArgs(argv) {
|
|
|
47768
47939
|
*/
|
|
47769
47940
|
async function main() {
|
|
47770
47941
|
const program = new Command();
|
|
47771
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
47942
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.126.0");
|
|
47772
47943
|
program.addCommand(createBootstrapCommand());
|
|
47773
47944
|
program.addCommand(createSynthCommand());
|
|
47774
47945
|
program.addCommand(createListCommand());
|