@go-to-k/cdkd 0.132.4 → 0.134.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 +108 -81
- package/dist/cli.js.map +1 -1
- package/dist/deploy-engine-Dn7oV5rA.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -34750,6 +34750,31 @@ function createStateCommand() {
|
|
|
34750
34750
|
return cmd;
|
|
34751
34751
|
}
|
|
34752
34752
|
|
|
34753
|
+
//#endregion
|
|
34754
|
+
//#region src/cli/cfn-stack-states.ts
|
|
34755
|
+
/**
|
|
34756
|
+
* Stable terminal CloudFormation stack states.
|
|
34757
|
+
*
|
|
34758
|
+
* Sourced from the AWS CloudFormation API documentation: a stack in any
|
|
34759
|
+
* of these states has settled (success or rolled-back) and is safe to
|
|
34760
|
+
* read / mutate. Every other status (`*_IN_PROGRESS`, `*_FAILED`,
|
|
34761
|
+
* `REVIEW_IN_PROGRESS`) means the stack is mid-operation or in an
|
|
34762
|
+
* unhealthy state — callers gate AWS-side mutations behind this set so
|
|
34763
|
+
* the user can settle the source before paying for further work.
|
|
34764
|
+
*
|
|
34765
|
+
* Single source of truth — consumed by both `cdkd migrate`'s pre-flight
|
|
34766
|
+
* check (`src/cli/commands/migrate/cfn-stack-prefetch.ts`) and the
|
|
34767
|
+
* `cdkd import --migrate-from-cloudformation` retirement flow
|
|
34768
|
+
* (`src/cli/commands/retire-cfn-stack.ts`).
|
|
34769
|
+
*/
|
|
34770
|
+
const STABLE_TERMINAL_STATUSES = new Set([
|
|
34771
|
+
"CREATE_COMPLETE",
|
|
34772
|
+
"UPDATE_COMPLETE",
|
|
34773
|
+
"UPDATE_ROLLBACK_COMPLETE",
|
|
34774
|
+
"IMPORT_COMPLETE",
|
|
34775
|
+
"IMPORT_ROLLBACK_COMPLETE"
|
|
34776
|
+
]);
|
|
34777
|
+
|
|
34753
34778
|
//#endregion
|
|
34754
34779
|
//#region src/cli/upload-cfn-template.ts
|
|
34755
34780
|
/**
|
|
@@ -35191,18 +35216,6 @@ function parseCfnTemplateWithFormat(text) {
|
|
|
35191
35216
|
//#endregion
|
|
35192
35217
|
//#region src/cli/commands/retire-cfn-stack.ts
|
|
35193
35218
|
/**
|
|
35194
|
-
* Stack states from which an UpdateStack call is safe. Anything else (an
|
|
35195
|
-
* IN_PROGRESS, FAILED, or REVIEW_IN_PROGRESS state) means the stack is
|
|
35196
|
-
* mid-operation or in an unhealthy state we should not touch.
|
|
35197
|
-
*/
|
|
35198
|
-
const STABLE_TERMINAL_STATUSES = new Set([
|
|
35199
|
-
"CREATE_COMPLETE",
|
|
35200
|
-
"UPDATE_COMPLETE",
|
|
35201
|
-
"UPDATE_ROLLBACK_COMPLETE",
|
|
35202
|
-
"IMPORT_COMPLETE",
|
|
35203
|
-
"IMPORT_ROLLBACK_COMPLETE"
|
|
35204
|
-
]);
|
|
35205
|
-
/**
|
|
35206
35219
|
* UpdateStack TemplateBody hard limit (51,200 bytes). Templates larger than
|
|
35207
35220
|
* this are uploaded to cdkd's state S3 bucket and submitted via `TemplateURL`
|
|
35208
35221
|
* instead — see {@link uploadTemplateForUpdateStack}.
|
|
@@ -44085,7 +44098,7 @@ function attachAuthorizers(stacks, routes) {
|
|
|
44085
44098
|
const out = [];
|
|
44086
44099
|
const errors = [];
|
|
44087
44100
|
for (const route of routes) {
|
|
44088
|
-
if (route.unsupported || route.mockCors
|
|
44101
|
+
if (route.unsupported || route.mockCors) {
|
|
44089
44102
|
out.push({ route });
|
|
44090
44103
|
continue;
|
|
44091
44104
|
}
|
|
@@ -44115,45 +44128,6 @@ function attachAuthorizers(stacks, routes) {
|
|
|
44115
44128
|
return out;
|
|
44116
44129
|
}
|
|
44117
44130
|
/**
|
|
44118
|
-
* Walk every discovered route and return the service-integration routes
|
|
44119
|
-
* whose source CFn resource declares an authorizer (HTTP API v2 routes
|
|
44120
|
-
* with `AuthorizationType !== 'NONE'`). Service-integration routes only
|
|
44121
|
-
* exist on `AWS::ApiGatewayV2::Route`; REST v1 service integrations are
|
|
44122
|
-
* a different shape and not yet supported.
|
|
44123
|
-
*/
|
|
44124
|
-
function findIgnoredServiceIntegrationAuthorizers(stacks, routes) {
|
|
44125
|
-
const stackByRoute = /* @__PURE__ */ new Map();
|
|
44126
|
-
for (const stack of stacks) {
|
|
44127
|
-
const prefix = `${stack.stackName}/`;
|
|
44128
|
-
for (const route of routes) if (route.declaredAt.startsWith(prefix)) stackByRoute.set(route.declaredAt, stack);
|
|
44129
|
-
}
|
|
44130
|
-
const out = [];
|
|
44131
|
-
for (const route of routes) {
|
|
44132
|
-
if (!route.serviceIntegration) continue;
|
|
44133
|
-
const stack = stackByRoute.get(route.declaredAt);
|
|
44134
|
-
if (!stack) continue;
|
|
44135
|
-
const slash = route.declaredAt.indexOf("/");
|
|
44136
|
-
if (slash < 0) continue;
|
|
44137
|
-
const logicalId = route.declaredAt.slice(slash + 1);
|
|
44138
|
-
const resource = stack.template.Resources?.[logicalId];
|
|
44139
|
-
if (!resource || resource.Type !== "AWS::ApiGatewayV2::Route") continue;
|
|
44140
|
-
const props = resource.Properties ?? {};
|
|
44141
|
-
const authType = props["AuthorizationType"];
|
|
44142
|
-
if (typeof authType !== "string" || authType === "" || authType.toUpperCase() === "NONE") continue;
|
|
44143
|
-
let authorizerName;
|
|
44144
|
-
if (authType === "AWS_IAM") authorizerName = "AWS_IAM";
|
|
44145
|
-
else {
|
|
44146
|
-
const ref = props["AuthorizerId"];
|
|
44147
|
-
authorizerName = (ref && typeof ref === "object" && "Ref" in ref && typeof ref.Ref === "string" ? ref.Ref : void 0) ?? `<authType=${authType}, AuthorizerId malformed>`;
|
|
44148
|
-
}
|
|
44149
|
-
out.push({
|
|
44150
|
-
declaredAt: route.declaredAt,
|
|
44151
|
-
authorizerName
|
|
44152
|
-
});
|
|
44153
|
-
}
|
|
44154
|
-
return out;
|
|
44155
|
-
}
|
|
44156
|
-
/**
|
|
44157
44131
|
* Detect the authorizer (if any) attached to a discovered route.
|
|
44158
44132
|
* Walks the original CFn resource for the route in `stack.template`.
|
|
44159
44133
|
*/
|
|
@@ -45424,6 +45398,11 @@ function resolveSingleReference(ref, ctx) {
|
|
|
45424
45398
|
}
|
|
45425
45399
|
if (ref.startsWith("$context.")) {
|
|
45426
45400
|
const key = ref.substring(9);
|
|
45401
|
+
if (key === "authorizer" || key.startsWith("authorizer.")) {
|
|
45402
|
+
if (!ctx.authorizer) return "";
|
|
45403
|
+
if (key === "authorizer") return JSON.stringify(ctx.authorizer);
|
|
45404
|
+
return resolveAuthorizerPath(ctx.authorizer, key.substring(11));
|
|
45405
|
+
}
|
|
45427
45406
|
return ctx.context[key] ?? "";
|
|
45428
45407
|
}
|
|
45429
45408
|
if (ref.startsWith("$stageVariables.")) {
|
|
@@ -45467,6 +45446,36 @@ function resolveBodyJsonPath(body, path) {
|
|
|
45467
45446
|
if (typeof cursor === "string") return cursor;
|
|
45468
45447
|
return JSON.stringify(cursor);
|
|
45469
45448
|
}
|
|
45449
|
+
/**
|
|
45450
|
+
* Walk a dot-separated path against the authorizer verdict map (#502).
|
|
45451
|
+
* Used by `$context.authorizer.X` selection expressions on
|
|
45452
|
+
* service-integration routes. Mirrors `resolveBodyJsonPath`'s shape:
|
|
45453
|
+
* - Empty leaf / undefined → `""`.
|
|
45454
|
+
* - String leaf → returned verbatim.
|
|
45455
|
+
* - Non-string leaf → `JSON.stringify`'d (matches AWS-deployed
|
|
45456
|
+
* behavior; `$context.authorizer.jwt.claims` resolves to the JSON
|
|
45457
|
+
* object as a string).
|
|
45458
|
+
*
|
|
45459
|
+
* Array indexing via `[N]` is supported (rare for authorizer
|
|
45460
|
+
* verdicts but cheap to support).
|
|
45461
|
+
*/
|
|
45462
|
+
function resolveAuthorizerPath(authorizer, path) {
|
|
45463
|
+
if (path === "") return "";
|
|
45464
|
+
const segments = path.split(/\.|\[(\d+)\]/).filter((s) => s !== void 0 && s !== "");
|
|
45465
|
+
let cursor = authorizer;
|
|
45466
|
+
for (const seg of segments) {
|
|
45467
|
+
if (cursor === null || cursor === void 0) return "";
|
|
45468
|
+
if (typeof cursor !== "object") return "";
|
|
45469
|
+
if (Array.isArray(cursor)) {
|
|
45470
|
+
const idx = Number(seg);
|
|
45471
|
+
if (!Number.isInteger(idx)) return "";
|
|
45472
|
+
cursor = cursor[idx];
|
|
45473
|
+
} else cursor = cursor[seg];
|
|
45474
|
+
}
|
|
45475
|
+
if (cursor === void 0 || cursor === null) return "";
|
|
45476
|
+
if (typeof cursor === "string") return cursor;
|
|
45477
|
+
return JSON.stringify(cursor);
|
|
45478
|
+
}
|
|
45470
45479
|
|
|
45471
45480
|
//#endregion
|
|
45472
45481
|
//#region src/local/http-server.ts
|
|
@@ -45568,10 +45577,6 @@ async function handleRequest(req, res, state, opts) {
|
|
|
45568
45577
|
writeNotImplemented(res, match.route.unsupported.reason);
|
|
45569
45578
|
return;
|
|
45570
45579
|
}
|
|
45571
|
-
if (match.route.serviceIntegration) {
|
|
45572
|
-
await handleServiceIntegrationRequest(req, res, match, bodyBuf, opts);
|
|
45573
|
-
return;
|
|
45574
|
-
}
|
|
45575
45580
|
const authorizer = state.routes.find((r) => r.route.declaredAt === match.route.declaredAt && r.route.method === match.route.method)?.authorizer;
|
|
45576
45581
|
const clientCert = opts.mtls ? extractClientCert(req) : void 0;
|
|
45577
45582
|
const snapshot = {
|
|
@@ -45606,6 +45611,10 @@ async function handleRequest(req, res, state, opts) {
|
|
|
45606
45611
|
const overlay = buildOverlay(authorizer, authResult);
|
|
45607
45612
|
if (overlay) baseEvent = applyAuthorizerOverlay(baseEvent, overlay);
|
|
45608
45613
|
}
|
|
45614
|
+
if (match.route.serviceIntegration) {
|
|
45615
|
+
await handleServiceIntegrationRequest(req, res, match, bodyBuf, opts, authorizer, authResult);
|
|
45616
|
+
return;
|
|
45617
|
+
}
|
|
45609
45618
|
if (match.route.restV1Integration) {
|
|
45610
45619
|
try {
|
|
45611
45620
|
writeIntegrationOutcome(res, await dispatchRestV1Integration(match.route.restV1Integration, snapshot, matchCtx, state, opts));
|
|
@@ -46171,7 +46180,7 @@ function writeError(res, statusCode, body = "{\"message\":\"Internal server erro
|
|
|
46171
46180
|
* auth pass. A follow-up PR can hoist the auth pass earlier — keeping it
|
|
46172
46181
|
* out of this PR limits the blast radius.
|
|
46173
46182
|
*/
|
|
46174
|
-
async function handleServiceIntegrationRequest(req, res, match, bodyBuf, opts) {
|
|
46183
|
+
async function handleServiceIntegrationRequest(req, res, match, bodyBuf, opts, authorizer, authResult) {
|
|
46175
46184
|
const route = match.route;
|
|
46176
46185
|
const svc = route.serviceIntegration;
|
|
46177
46186
|
if (!svc) {
|
|
@@ -46183,6 +46192,7 @@ async function handleServiceIntegrationRequest(req, res, match, bodyBuf, opts) {
|
|
|
46183
46192
|
const queryString = parseQueryStringSingular(rawUrl);
|
|
46184
46193
|
const requestPath = rawUrl.split("?")[0] ?? "/";
|
|
46185
46194
|
const context = buildServiceIntegrationContextVars(req, route);
|
|
46195
|
+
const authorizerCtx = buildAuthorizerContextForServiceIntegration(authorizer, authResult);
|
|
46186
46196
|
const ctx = {
|
|
46187
46197
|
headers: headersFlat,
|
|
46188
46198
|
queryString,
|
|
@@ -46190,7 +46200,8 @@ async function handleServiceIntegrationRequest(req, res, match, bodyBuf, opts) {
|
|
|
46190
46200
|
requestPath,
|
|
46191
46201
|
body: bodyBuf.toString("utf8"),
|
|
46192
46202
|
context,
|
|
46193
|
-
stageVariables: route.stageVariables ?? {}
|
|
46203
|
+
stageVariables: route.stageVariables ?? {},
|
|
46204
|
+
...authorizerCtx && { authorizer: authorizerCtx }
|
|
46194
46205
|
};
|
|
46195
46206
|
const outcome = resolveServiceIntegrationParameters(svc.requestParameters, ctx);
|
|
46196
46207
|
if (outcome.kind === "error") {
|
|
@@ -46261,6 +46272,44 @@ function buildServiceIntegrationContextVars(req, route) {
|
|
|
46261
46272
|
};
|
|
46262
46273
|
}
|
|
46263
46274
|
/**
|
|
46275
|
+
* Build the `authorizer` field for the parameter-mapping context on
|
|
46276
|
+
* service-integration routes (#502). Surfaces the authorizer's verdict
|
|
46277
|
+
* in the same shape `applyAuthorizerOverlay` writes onto the Lambda
|
|
46278
|
+
* event so users can reference `$context.authorizer.X` /
|
|
46279
|
+
* `$context.authorizer.jwt.claims.X` / `$context.authorizer.claims.X`
|
|
46280
|
+
* in `RequestParameters`.
|
|
46281
|
+
*
|
|
46282
|
+
* Per-kind shape:
|
|
46283
|
+
* - Lambda authorizers (`lambda-token` / `lambda-request` / `iam`):
|
|
46284
|
+
* `principalId` + flat `context` fields land at the top level
|
|
46285
|
+
* (`$context.authorizer.principalId`, `$context.authorizer.<key>`).
|
|
46286
|
+
* - Cognito (REST v1): claims under `$context.authorizer.claims.X`.
|
|
46287
|
+
* - JWT (HTTP v2): claims under `$context.authorizer.jwt.claims.X` +
|
|
46288
|
+
* `$context.authorizer.jwt.scopes`.
|
|
46289
|
+
*
|
|
46290
|
+
* Returns `undefined` when no authorizer fired (route had
|
|
46291
|
+
* `AuthorizationType: NONE` / no authorizer attached).
|
|
46292
|
+
*/
|
|
46293
|
+
function buildAuthorizerContextForServiceIntegration(authorizer, result) {
|
|
46294
|
+
if (!authorizer || !result) return void 0;
|
|
46295
|
+
if (authorizer.kind === "lambda-token" || authorizer.kind === "lambda-request") {
|
|
46296
|
+
const ctx = {};
|
|
46297
|
+
if (result.principalId !== void 0) ctx["principalId"] = result.principalId;
|
|
46298
|
+
if (result.context) Object.assign(ctx, result.context);
|
|
46299
|
+
return ctx;
|
|
46300
|
+
}
|
|
46301
|
+
if (authorizer.kind === "iam") {
|
|
46302
|
+
const ctx = {};
|
|
46303
|
+
if (result.principalId !== void 0) ctx["principalId"] = result.principalId;
|
|
46304
|
+
return ctx;
|
|
46305
|
+
}
|
|
46306
|
+
if (authorizer.kind === "cognito") return { claims: { ...result.context ?? {} } };
|
|
46307
|
+
return { jwt: {
|
|
46308
|
+
claims: { ...result.context ?? {} },
|
|
46309
|
+
scopes: []
|
|
46310
|
+
} };
|
|
46311
|
+
}
|
|
46312
|
+
/**
|
|
46264
46313
|
* Write the 501 Not Implemented response surfaced for routes the
|
|
46265
46314
|
* discovery layer flagged as `unsupported`. The integration's reason
|
|
46266
46315
|
* (e.g. "MOCK integration is not emulated", "WebSocket APIs are not
|
|
@@ -47022,7 +47071,6 @@ async function localStartApiCommand(target, options) {
|
|
|
47022
47071
|
warnVpcConfigLambdas(initialMaterial.routes, initialMaterial.stacks ?? []);
|
|
47023
47072
|
sigV4CredentialsLoader = defaultCredentialsLoader();
|
|
47024
47073
|
warnIamRoutes(initialMaterial.routes);
|
|
47025
|
-
warnIgnoredServiceIntegrationAuthorizers(initialMaterial.routes, initialMaterial.stacks ?? []);
|
|
47026
47074
|
let maxTimeoutSec = 0;
|
|
47027
47075
|
for (const spec of initialMaterial.specs.values()) if (spec.lambda.timeoutSec > maxTimeoutSec) maxTimeoutSec = spec.lambda.timeoutSec;
|
|
47028
47076
|
const rieTimeoutMs = Math.max(3e4, maxTimeoutSec * 2 * 1e3);
|
|
@@ -47284,27 +47332,6 @@ function warnIamRoutes(routesWithAuth) {
|
|
|
47284
47332
|
return true;
|
|
47285
47333
|
}
|
|
47286
47334
|
/**
|
|
47287
|
-
* #458 / PR #500 review: emit a one-line warn naming every service-
|
|
47288
|
-
* integration route whose source CFn resource declares an authorizer
|
|
47289
|
-
* (HTTP API v2 routes with `AuthorizationType !== 'NONE'`). The
|
|
47290
|
-
* dispatcher in `http-server.ts` runs the SDK call BEFORE the
|
|
47291
|
-
* authorizer pass would fire, so without this warn a CDK app that
|
|
47292
|
-
* wires JWT / Lambda / Cognito / IAM authorizers onto service
|
|
47293
|
-
* integrations would silently let every local request reach the SDK
|
|
47294
|
-
* call without auth. Threading the auth pass through the
|
|
47295
|
-
* service-integration dispatcher is a follow-up issue. Returns the
|
|
47296
|
-
* number of warned routes so tests can assert the firing path; the
|
|
47297
|
-
* value is otherwise unused.
|
|
47298
|
-
*/
|
|
47299
|
-
function warnIgnoredServiceIntegrationAuthorizers(routesWithAuth, stacks) {
|
|
47300
|
-
const logger = getLogger();
|
|
47301
|
-
const ignored = findIgnoredServiceIntegrationAuthorizers(stacks, routesWithAuth.map((entry) => entry.route));
|
|
47302
|
-
if (ignored.length === 0) return 0;
|
|
47303
|
-
logger.warn(`${ignored.length} HTTP API v2 service-integration route(s) declare an authorizer but cdkd local start-api dispatches the SDK call BEFORE the authorizer pass — every local request reaches the SDK call WITHOUT authentication. This is a deferred feature; see https://github.com/go-to-k/cdkd/issues/502 for the follow-up tracking issue.`);
|
|
47304
|
-
for (const entry of ignored) logger.warn(` - ${entry.declaredAt}: authorizer '${entry.authorizerName}' is configured but ignored`);
|
|
47305
|
-
return ignored.length;
|
|
47306
|
-
}
|
|
47307
|
-
/**
|
|
47308
47335
|
* Build the per-Lambda container spec — code dir, env vars (template +
|
|
47309
47336
|
* --env-vars overlay), STS-issued creds when --assume-role names this
|
|
47310
47337
|
* Lambda, optional --debug-port reservation. Errors out with a clear
|
|
@@ -51873,7 +51900,7 @@ function reorderArgs(argv) {
|
|
|
51873
51900
|
*/
|
|
51874
51901
|
async function main() {
|
|
51875
51902
|
const program = new Command();
|
|
51876
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
51903
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.134.0");
|
|
51877
51904
|
program.addCommand(createBootstrapCommand());
|
|
51878
51905
|
program.addCommand(createSynthCommand());
|
|
51879
51906
|
program.addCommand(createListCommand());
|