@go-to-k/cdkd 0.161.3 → 0.162.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 +309 -8
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-D4iGkZAC.js → deploy-engine-DEbogepd.js} +34 -15
- package/dist/{deploy-engine-D4iGkZAC.js.map → deploy-engine-DEbogepd.js.map} +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { _ as withSkipPrefix, a as runDockerStreaming, c as getLogger, d as getLiveRenderer, f as PATTERN_B_NAME_PROPERTIES, g as generateResourceNameWithFallback, h as generateResourceName, i as runDockerForeground, n as formatDockerLoginError, p as PATTERN_B_RESOURCE_TYPES, r as getDockerCmd, u as runStackBuffered, v as withStackName } from "./docker-cmd-iDMcWcre.js";
|
|
3
|
-
import { A as S3StateBackend, B as resolveCaptureObservedState, C as assertRegionMatch, D as DagBuilder, E as DiffCalculator, F as buildDockerImage, G as CFN_TEMPLATE_BODY_LIMIT, H as resolveStateBucketWithDefault, I as Synthesizer, J as findLargeInlineResources, K as CFN_TEMPLATE_URL_LIMIT, L as getDefaultStateBucketName, M as AssetPublisher, N as stringifyValue, O as TemplateParser, P as WorkGraph, Q as resolveBucketRegion, R as getLegacyStateBucketName, S as CloudControlProvider, T as applyRoleArnIfSet, U as resolveStateBucketWithDefaultAndSource, V as resolveSkipPrefix, W as warnDeprecatedNoPrefixCliFlag, X as AssemblyReader, Y as uploadCfnTemplate, _ as matchesCdkPath, a as withRetry, at as LocalStartServiceError, b as ProviderRegistry, bt as normalizeAwsError, c as bold, ct as NestedStackChildDirectDestroyError, d as green, dt as ResourceTimeoutError, et as CdkdError, f as red, ft as ResourceUpdateNotSupportedError, g as CDK_PATH_TAG, h as collectInlinePolicyNamesManagedBySiblings, ht as StackTerminationProtectionError, i as withResourceDeadline, it as LocalMigrateError, j as shouldRetainResource, k as LockManager, l as cyan, lt as PartialFailureError, m as IAMRoleProvider, mt as StackHasActiveImportsError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, o as IMPLICIT_DELETE_DEPENDENCIES, p as yellow, pt as RouteDiscoveryError, q as MIGRATE_TMP_PREFIX, r as DeployEngine, rt as LocalInvokeBuildError, s as formatResourceLine, st as MissingCdkCliError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, ut as ProvisioningError, v as normalizeAwsTagsToCfn, w as IntrinsicFunctionResolver, x as findActionableSilentDrops, xt as withErrorHandling, y as resolveExplicitPhysicalId, z as resolveApp } from "./deploy-engine-
|
|
3
|
+
import { A as S3StateBackend, B as resolveCaptureObservedState, C as assertRegionMatch, D as DagBuilder, E as DiffCalculator, F as buildDockerImage, G as CFN_TEMPLATE_BODY_LIMIT, H as resolveStateBucketWithDefault, I as Synthesizer, J as findLargeInlineResources, K as CFN_TEMPLATE_URL_LIMIT, L as getDefaultStateBucketName, M as AssetPublisher, N as stringifyValue, O as TemplateParser, P as WorkGraph, Q as resolveBucketRegion, R as getLegacyStateBucketName, S as CloudControlProvider, T as applyRoleArnIfSet, U as resolveStateBucketWithDefaultAndSource, V as resolveSkipPrefix, W as warnDeprecatedNoPrefixCliFlag, X as AssemblyReader, Y as uploadCfnTemplate, _ as matchesCdkPath, a as withRetry, at as LocalStartServiceError, b as ProviderRegistry, bt as normalizeAwsError, c as bold, ct as NestedStackChildDirectDestroyError, d as green, dt as ResourceTimeoutError, et as CdkdError, f as red, ft as ResourceUpdateNotSupportedError, g as CDK_PATH_TAG, h as collectInlinePolicyNamesManagedBySiblings, ht as StackTerminationProtectionError, i as withResourceDeadline, it as LocalMigrateError, j as shouldRetainResource, k as LockManager, l as cyan, lt as PartialFailureError, m as IAMRoleProvider, mt as StackHasActiveImportsError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, o as IMPLICIT_DELETE_DEPENDENCIES, p as yellow, pt as RouteDiscoveryError, q as MIGRATE_TMP_PREFIX, r as DeployEngine, rt as LocalInvokeBuildError, s as formatResourceLine, st as MissingCdkCliError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, ut as ProvisioningError, v as normalizeAwsTagsToCfn, w as IntrinsicFunctionResolver, x as findActionableSilentDrops, xt as withErrorHandling, y as resolveExplicitPhysicalId, z as resolveApp } from "./deploy-engine-DEbogepd.js";
|
|
4
4
|
import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-B15NAPbL.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash, createHmac, createPublicKey, createVerify, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -423,6 +423,46 @@ function parseAllowUnsupportedPropertiesToken(value, previous) {
|
|
|
423
423
|
return [...previous ?? [], ...parsed];
|
|
424
424
|
}
|
|
425
425
|
const allowUnsupportedPropertiesOption = new Option("--allow-unsupported-properties <entries>", "Comma-separated <ResourceType>:<PropertyName> tokens to accept as silently dropped at deploy time. Escape hatch — the property will NOT be written to AWS, the deployed resource will be missing the field. Example: --allow-unsupported-properties AWS::Lambda::Function:LoggingConfig,AWS::RDS::DBInstance:CACertificateIdentifier").argParser(parseAllowUnsupportedPropertiesToken);
|
|
426
|
+
/**
|
|
427
|
+
* Issue [#615] — `--recreate-via-cc-api <LogicalId>` (repeatable). Each
|
|
428
|
+
* named resource is destroyed + recreated this deploy via Cloud Control
|
|
429
|
+
* API regardless of whether the existing state stamps it `sdk` or has
|
|
430
|
+
* no `provisionedBy` field (legacy). The new physical id stamps
|
|
431
|
+
* `provisionedBy: 'cc-api'` so all subsequent ops route via CC (sticky).
|
|
432
|
+
*
|
|
433
|
+
* Mirrors `--allow-unsupported-properties` per-resource explicit naming
|
|
434
|
+
* (one flag-instance per logical id), but the argument is a single
|
|
435
|
+
* logical id (no comma split — `MyLambda,Other` would be ambiguous
|
|
436
|
+
* since logical ids do not embed commas anyway).
|
|
437
|
+
*
|
|
438
|
+
* Format-checks each logical id against CFn's `Logical IDs are
|
|
439
|
+
* alphanumeric (A-Z, a-z, 0-9)` rule. A typo aborts at parse time so
|
|
440
|
+
* an unmatched id is surfaced immediately rather than silently
|
|
441
|
+
* skipped at deploy time.
|
|
442
|
+
*/
|
|
443
|
+
const LOGICAL_ID_FORMAT = /^[A-Za-z][A-Za-z0-9]{0,254}$/;
|
|
444
|
+
function parseRecreateViaCcApiToken(value, previous) {
|
|
445
|
+
const token = value.trim();
|
|
446
|
+
if (!LOGICAL_ID_FORMAT.test(token)) throw new Error(`Invalid --recreate-via-cc-api value "${value}": expected a CloudFormation logical id (alphanumeric, starts with a letter, max 255 chars). One --recreate-via-cc-api flag per resource — repeat the flag for additional targets.`);
|
|
447
|
+
return [...previous ?? [], token];
|
|
448
|
+
}
|
|
449
|
+
const recreateViaCcApiOption = new Option("--recreate-via-cc-api <logicalId>", "Destroy + recreate the named resource (by CloudFormation logical id) via Cloud Control API in this deploy, so a top-level CFn property cdkd would otherwise silently drop reaches AWS via CC. Repeatable — pass the flag once per resource. Per-resource opt-in (no bulk / no per-stack shortcut) so the destroy-and-recreate cost is acknowledged for each target. Stateful resource types (RDS, DynamoDB, S3, EFS, ...) refuse unless --force-stateful-recreation is ALSO passed (two-flag protection). Cannot be combined with --allow-unsupported-properties on the same resource type and property.").argParser(parseRecreateViaCcApiToken);
|
|
450
|
+
/**
|
|
451
|
+
* Issue [#615] — `--force-stateful-recreation` (boolean) is the second
|
|
452
|
+
* flag required to allow `--recreate-via-cc-api` to operate on a
|
|
453
|
+
* stateful resource (RDS / DynamoDB / EFS / S3 with data / etc.). Two
|
|
454
|
+
* flags so the user explicitly opts into the data-loss footgun.
|
|
455
|
+
*
|
|
456
|
+
* The guard list of "stateful" types lives in
|
|
457
|
+
* `src/provisioning/stateful-types.ts` so it can be queried from both
|
|
458
|
+
* the CLI pre-flight and the deploy engine.
|
|
459
|
+
*
|
|
460
|
+
* There is no per-resource granularity on the force flag — when set,
|
|
461
|
+
* EVERY named recreate target bypasses the stateful guard. Per-resource
|
|
462
|
+
* force would create a false sense of granularity (the user is opting
|
|
463
|
+
* into a footgun; pretending to scope it per-resource is misleading).
|
|
464
|
+
*/
|
|
465
|
+
const forceStatefulRecreationOption = new Option("--force-stateful-recreation", "Bypass the stateful-resource guard for --recreate-via-cc-api targets. Required when ANY named target is a stateful type (RDS / DynamoDB / EFS / S3 with data / Logs with retention / Cognito / Secrets / SSM / Glue / ECR / CloudFront / Kinesis / OpenSearch). Destroy + recreate loses ALL data in the resource — no automatic data migration. Triple-opt-in for CI use: --recreate-via-cc-api <id> --force-stateful-recreation --yes.").default(false);
|
|
426
466
|
const deployOptions = [
|
|
427
467
|
new Option("--concurrency <number>", "Maximum concurrent resource operations").default(10).argParser((value) => parseInt(value, 10)),
|
|
428
468
|
new Option("--stack-concurrency <number>", "Maximum concurrent stack deployments").default(4).argParser((value) => parseInt(value, 10)),
|
|
@@ -438,6 +478,8 @@ const deployOptions = [
|
|
|
438
478
|
new Option("-e, --exclusively", "Only deploy requested stacks, do not include dependencies").default(false),
|
|
439
479
|
allowUnsupportedTypesOption,
|
|
440
480
|
allowUnsupportedPropertiesOption,
|
|
481
|
+
recreateViaCcApiOption,
|
|
482
|
+
forceStatefulRecreationOption,
|
|
441
483
|
...resourceTimeoutOptions
|
|
442
484
|
];
|
|
443
485
|
/**
|
|
@@ -883,6 +925,219 @@ function createListCommand() {
|
|
|
883
925
|
return cmd;
|
|
884
926
|
}
|
|
885
927
|
|
|
928
|
+
//#endregion
|
|
929
|
+
//#region src/provisioning/stateful-types.ts
|
|
930
|
+
/**
|
|
931
|
+
* Stateful-resource guard list (issue [#615]).
|
|
932
|
+
*
|
|
933
|
+
* `--recreate-via-cc-api <LogicalId>` destroys + recreates the named
|
|
934
|
+
* resource in one deploy so a previously-silent-dropped top-level CFn
|
|
935
|
+
* property reaches AWS via Cloud Control API. For most types this is
|
|
936
|
+
* safe — destroying + recreating an IAM Role or a Lambda Function
|
|
937
|
+
* loses no user data — but for **data-bearing** types the destroy
|
|
938
|
+
* cycle loses everything in the resource: rows in a DynamoDB table,
|
|
939
|
+
* objects in an S3 bucket, log lines in a LogGroup, images in an ECR
|
|
940
|
+
* repository, etc.
|
|
941
|
+
*
|
|
942
|
+
* To avoid an accidental data-loss footgun, cdkd refuses to recreate
|
|
943
|
+
* any resource whose type is in {@link STATEFUL_TYPES} unless the user
|
|
944
|
+
* ALSO passes `--force-stateful-recreation`. The two-flag protection
|
|
945
|
+
* mirrors `--remove-protection`'s pattern (see
|
|
946
|
+
* `src/cli/commands/destroy-runner.ts`).
|
|
947
|
+
*
|
|
948
|
+
* The list is hand-curated and intentionally **conservative**: every
|
|
949
|
+
* type here carries user data that the AWS service does NOT
|
|
950
|
+
* automatically migrate to the replacement resource. Types that the
|
|
951
|
+
* AWS service treats as ephemeral (e.g. Lambda Function, IAM Role)
|
|
952
|
+
* are NOT in this list — recreate is cheap.
|
|
953
|
+
*
|
|
954
|
+
* Two entries are **conditionally stateful** — they only count when
|
|
955
|
+
* the resource actually contains data:
|
|
956
|
+
*
|
|
957
|
+
* - `AWS::S3::Bucket`: empty buckets are safe to recreate. The
|
|
958
|
+
* deploy engine probes `s3:ListObjectsV2` at plan time and only
|
|
959
|
+
* refuses when the bucket has at least one object.
|
|
960
|
+
* - `AWS::Logs::LogGroup`: a log group with `RetentionInDays`
|
|
961
|
+
* undefined or zero is functionally ephemeral. The deploy engine
|
|
962
|
+
* refuses only when `RetentionInDays > 0`.
|
|
963
|
+
*
|
|
964
|
+
* Both conditional checks live in {@link isStatefulRecreateTarget};
|
|
965
|
+
* the bare {@link STATEFUL_TYPES} set is the type-only first-cut.
|
|
966
|
+
*/
|
|
967
|
+
const STATEFUL_TYPES = new Set([
|
|
968
|
+
"AWS::RDS::DBInstance",
|
|
969
|
+
"AWS::RDS::DBCluster",
|
|
970
|
+
"AWS::DocDB::DBInstance",
|
|
971
|
+
"AWS::DocDB::DBCluster",
|
|
972
|
+
"AWS::Neptune::DBInstance",
|
|
973
|
+
"AWS::Neptune::DBCluster",
|
|
974
|
+
"AWS::DynamoDB::Table",
|
|
975
|
+
"AWS::DynamoDB::GlobalTable",
|
|
976
|
+
"AWS::EFS::FileSystem",
|
|
977
|
+
"AWS::S3::Bucket",
|
|
978
|
+
"AWS::ECR::Repository",
|
|
979
|
+
"AWS::Kinesis::Stream",
|
|
980
|
+
"AWS::Elasticsearch::Domain",
|
|
981
|
+
"AWS::OpenSearchService::Domain",
|
|
982
|
+
"AWS::Cognito::UserPool",
|
|
983
|
+
"AWS::SecretsManager::Secret",
|
|
984
|
+
"AWS::SSM::Parameter",
|
|
985
|
+
"AWS::Glue::Database",
|
|
986
|
+
"AWS::Glue::Table",
|
|
987
|
+
"AWS::Logs::LogGroup",
|
|
988
|
+
"AWS::CloudFront::Distribution"
|
|
989
|
+
]);
|
|
990
|
+
/**
|
|
991
|
+
* Multi-region resource types — `--recreate-via-cc-api` refuses these
|
|
992
|
+
* outright in v1 regardless of `--force-stateful-recreation`. Design
|
|
993
|
+
* doc §8 calls these "out of scope": the destroy + recreate cycle
|
|
994
|
+
* across replica regions is more involved than a single-region
|
|
995
|
+
* destroy-and-create (replica regions, automated backups, eventual
|
|
996
|
+
* consistency across the replication mesh, etc.).
|
|
997
|
+
*
|
|
998
|
+
* Distinct from {@link STATEFUL_TYPES} — STATEFUL_TYPES gates on data
|
|
999
|
+
* loss (bypassable with `--force-stateful-recreation`); this set is
|
|
1000
|
+
* an out-of-scope refusal (no bypass).
|
|
1001
|
+
*/
|
|
1002
|
+
const MULTI_REGION_RECREATE_BLOCKED_TYPES = new Set(["AWS::DynamoDB::GlobalTable"]);
|
|
1003
|
+
/**
|
|
1004
|
+
* Cheap, synchronous read of the resource's recorded properties only.
|
|
1005
|
+
* For `AWS::S3::Bucket` this returns `null` — the live `ListObjectsV2`
|
|
1006
|
+
* probe to distinguish empty buckets (safe to recreate) from
|
|
1007
|
+
* non-empty (data loss) needs an S3 client + an AWS round-trip and is
|
|
1008
|
+
* deferred to a follow-up issue (v1 sync-defers; an `--force-stateful-
|
|
1009
|
+
* recreation` is recommended for any potentially-non-empty S3 target).
|
|
1010
|
+
*
|
|
1011
|
+
* Returns the {@link StatefulReason} when the type is stateful (or
|
|
1012
|
+
* `null` for non-stateful types).
|
|
1013
|
+
*/
|
|
1014
|
+
function isStatefulRecreateTargetSync(resourceType, recordedProperties) {
|
|
1015
|
+
if (!STATEFUL_TYPES.has(resourceType)) return null;
|
|
1016
|
+
if (resourceType === "AWS::Logs::LogGroup") {
|
|
1017
|
+
const retention = recordedProperties?.["RetentionInDays"];
|
|
1018
|
+
if (typeof retention === "number" && retention > 0) return "has-retention";
|
|
1019
|
+
return null;
|
|
1020
|
+
}
|
|
1021
|
+
if (resourceType === "AWS::S3::Bucket") return null;
|
|
1022
|
+
return "always";
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Human-readable rendering of {@link StatefulReason} for error
|
|
1026
|
+
* messages. Used by the pre-flight guard's "X resources require
|
|
1027
|
+
* --force-stateful-recreation" listing.
|
|
1028
|
+
*/
|
|
1029
|
+
function renderStatefulReason(reason) {
|
|
1030
|
+
switch (reason) {
|
|
1031
|
+
case "always": return "destroy loses all data in the resource";
|
|
1032
|
+
case "has-objects": return "S3 bucket is non-empty";
|
|
1033
|
+
case "has-retention": return "log group retains data (RetentionInDays > 0)";
|
|
1034
|
+
case null: return "(not stateful)";
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
//#endregion
|
|
1039
|
+
//#region src/deployment/recreate-targets.ts
|
|
1040
|
+
/**
|
|
1041
|
+
* Plan-time validation of the user's recreate-via-cc-api list.
|
|
1042
|
+
*
|
|
1043
|
+
* Pure with respect to AWS — does NOT probe S3 bucket emptiness. The
|
|
1044
|
+
* S3 conditional check is deferred to a follow-up issue (the live
|
|
1045
|
+
* `s3:ListObjectsV2` probe is out of scope for v1).
|
|
1046
|
+
*
|
|
1047
|
+
* Input order is preserved; duplicate logical ids in the user's input
|
|
1048
|
+
* are deduplicated.
|
|
1049
|
+
*/
|
|
1050
|
+
const EMPTY_ALLOW_SET$1 = /* @__PURE__ */ new Set();
|
|
1051
|
+
function validateRecreateTargets(input) {
|
|
1052
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1053
|
+
const targets = [];
|
|
1054
|
+
const unknownLogicalIds = [];
|
|
1055
|
+
const missingFromState = [];
|
|
1056
|
+
const ambiguousIntent = [];
|
|
1057
|
+
const blockedStatefulTargets = [];
|
|
1058
|
+
const blockedMultiRegionTargets = [];
|
|
1059
|
+
for (const logicalId of input.recreateViaCcApi) {
|
|
1060
|
+
if (seen.has(logicalId)) continue;
|
|
1061
|
+
seen.add(logicalId);
|
|
1062
|
+
const templateResource = input.template.Resources?.[logicalId];
|
|
1063
|
+
if (!templateResource) {
|
|
1064
|
+
unknownLogicalIds.push(logicalId);
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
const recordedResource = input.state.resources[logicalId];
|
|
1068
|
+
if (!recordedResource) {
|
|
1069
|
+
missingFromState.push(logicalId);
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
const resourceType = recordedResource.resourceType;
|
|
1073
|
+
const target = {
|
|
1074
|
+
logicalId,
|
|
1075
|
+
resourceType,
|
|
1076
|
+
physicalId: recordedResource.physicalId,
|
|
1077
|
+
statefulReason: isStatefulRecreateTargetSync(resourceType, recordedResource.properties)
|
|
1078
|
+
};
|
|
1079
|
+
targets.push(target);
|
|
1080
|
+
if (MULTI_REGION_RECREATE_BLOCKED_TYPES.has(resourceType)) blockedMultiRegionTargets.push(target);
|
|
1081
|
+
const actionableDrops = findActionableSilentDrops(resourceType, templateResource.Properties, EMPTY_ALLOW_SET$1);
|
|
1082
|
+
for (const { property } of actionableDrops) {
|
|
1083
|
+
const allowKey = `${resourceType}:${property}`;
|
|
1084
|
+
if (input.allowUnsupportedProperties.has(allowKey)) ambiguousIntent.push({
|
|
1085
|
+
logicalId,
|
|
1086
|
+
resourceType,
|
|
1087
|
+
property
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
if (target.statefulReason !== null && !input.forceStatefulRecreation) blockedStatefulTargets.push(target);
|
|
1091
|
+
}
|
|
1092
|
+
return {
|
|
1093
|
+
targets,
|
|
1094
|
+
unknownLogicalIds,
|
|
1095
|
+
missingFromState,
|
|
1096
|
+
ambiguousIntent,
|
|
1097
|
+
blockedStatefulTargets,
|
|
1098
|
+
blockedMultiRegionTargets
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Render the validation failures into a single multi-line error
|
|
1103
|
+
* message. Returns `null` when the validation was clean (no errors).
|
|
1104
|
+
* The deploy command throws this string as the message of a
|
|
1105
|
+
* `ProvisioningError` so the surface is `cdkd deploy` exit code 1
|
|
1106
|
+
* with the same shape as other pre-flight failures.
|
|
1107
|
+
*/
|
|
1108
|
+
function renderRecreateTargetsErrors(validation) {
|
|
1109
|
+
const lines = [];
|
|
1110
|
+
if (validation.unknownLogicalIds.length > 0) {
|
|
1111
|
+
lines.push(`--recreate-via-cc-api named ${validation.unknownLogicalIds.length} logical id(s) not present in the synth template:`);
|
|
1112
|
+
for (const id of validation.unknownLogicalIds) lines.push(` - ${id}`);
|
|
1113
|
+
lines.push(" Fix: confirm each id exists in the template (CDK display path is the parent; the logical id is the CFn-emitted name, e.g. cdkd synth | jq '.Resources | keys'). Recreate operates on the synth template's logical ids, not CDK display paths.");
|
|
1114
|
+
}
|
|
1115
|
+
if (validation.missingFromState.length > 0) {
|
|
1116
|
+
if (lines.length > 0) lines.push("");
|
|
1117
|
+
lines.push(`--recreate-via-cc-api named ${validation.missingFromState.length} logical id(s) the template declares but cdkd state has no record of:`);
|
|
1118
|
+
for (const id of validation.missingFromState) lines.push(` - ${id}`);
|
|
1119
|
+
lines.push(" These are fresh CREATEs on the next deploy — recreate has nothing to destroy first. Remove the --recreate-via-cc-api flag for these resources; the new auto-route via Cloud Control (#614) handles fresh deploys.");
|
|
1120
|
+
}
|
|
1121
|
+
if (validation.ambiguousIntent.length > 0) {
|
|
1122
|
+
if (lines.length > 0) lines.push("");
|
|
1123
|
+
lines.push(`Ambiguous intent — ${validation.ambiguousIntent.length} resource(s) are named in BOTH --recreate-via-cc-api and --allow-unsupported-properties with the same Type:Prop on a silent-drop property the template uses:`);
|
|
1124
|
+
for (const overlap of validation.ambiguousIntent) lines.push(` - ${overlap.logicalId} (${overlap.resourceType}) — both --recreate-via-cc-api ${overlap.logicalId} (would migrate to CC, honoring ${overlap.property}) AND --allow-unsupported-properties ${overlap.resourceType}:${overlap.property} (would keep on SDK, accepting silent drop)`);
|
|
1125
|
+
lines.push(` Fix: pick ONE strategy per resource.`);
|
|
1126
|
+
}
|
|
1127
|
+
if (validation.blockedStatefulTargets.length > 0) {
|
|
1128
|
+
if (lines.length > 0) lines.push("");
|
|
1129
|
+
lines.push(`--recreate-via-cc-api would destroy + recreate ${validation.blockedStatefulTargets.length} stateful resource(s). Recreate loses ALL data — no automatic data migration. Re-run with --force-stateful-recreation to acknowledge the data-loss footgun.`);
|
|
1130
|
+
for (const blocked of validation.blockedStatefulTargets) lines.push(` - ${blocked.logicalId} (${blocked.resourceType}) — ${renderStatefulReason(blocked.statefulReason)}`);
|
|
1131
|
+
}
|
|
1132
|
+
if (validation.blockedMultiRegionTargets.length > 0) {
|
|
1133
|
+
if (lines.length > 0) lines.push("");
|
|
1134
|
+
lines.push(`--recreate-via-cc-api refuses to operate on ${validation.blockedMultiRegionTargets.length} multi-region resource(s) — out of scope for v1 of this flag (the destroy + recreate cycle across replica regions is more involved than the single-region path):`);
|
|
1135
|
+
for (const blocked of validation.blockedMultiRegionTargets) lines.push(` - ${blocked.logicalId} (${blocked.resourceType})`);
|
|
1136
|
+
lines.push(" No --force-stateful-recreation bypass — this category is structurally unsupported in v1. File an issue if you need this path.");
|
|
1137
|
+
}
|
|
1138
|
+
return lines.length > 0 ? lines.join("\n") : null;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
886
1141
|
//#endregion
|
|
887
1142
|
//#region src/state/export-index-store.ts
|
|
888
1143
|
/**
|
|
@@ -33339,10 +33594,40 @@ async function deployCommand(stacks, options) {
|
|
|
33339
33594
|
if (!await promptMigrationConfirm(pending, { yes: options.yes })) return;
|
|
33340
33595
|
}
|
|
33341
33596
|
}
|
|
33597
|
+
let recreateViaCcApiTargets;
|
|
33598
|
+
if (options.recreateViaCcApi?.length) {
|
|
33599
|
+
const stateForRecreateCheck = await stackStateBackend.getState(stackInfo.stackName, stackRegion);
|
|
33600
|
+
const validation = validateRecreateTargets({
|
|
33601
|
+
template: stackInfo.template,
|
|
33602
|
+
state: stateForRecreateCheck?.state ?? {
|
|
33603
|
+
version: 7,
|
|
33604
|
+
stackName: stackInfo.stackName,
|
|
33605
|
+
region: stackRegion,
|
|
33606
|
+
resources: {},
|
|
33607
|
+
outputs: {},
|
|
33608
|
+
lastModified: Date.now()
|
|
33609
|
+
},
|
|
33610
|
+
recreateViaCcApi: options.recreateViaCcApi,
|
|
33611
|
+
allowUnsupportedProperties: new Set(options.allowUnsupportedProperties ?? []),
|
|
33612
|
+
forceStatefulRecreation: options.forceStatefulRecreation ?? false
|
|
33613
|
+
});
|
|
33614
|
+
const errorBlock = renderRecreateTargetsErrors(validation);
|
|
33615
|
+
if (errorBlock) throw new CdkdError(errorBlock, "RECREATE_VIA_CC_API_INVALID");
|
|
33616
|
+
recreateViaCcApiTargets = new Set(validation.targets.map((t) => t.logicalId));
|
|
33617
|
+
if (recreateViaCcApiTargets.size > 0) {
|
|
33618
|
+
logger.warn(`--recreate-via-cc-api will destroy + recreate ${recreateViaCcApiTargets.size} resource(s) via Cloud Control API on stack ${stackInfo.stackName}:`);
|
|
33619
|
+
for (const t of validation.targets) {
|
|
33620
|
+
const stateNote = t.statefulReason !== null ? ` ⚠ stateful (${t.statefulReason}) — --force-stateful-recreation acknowledged` : "";
|
|
33621
|
+
logger.warn(` - ${t.logicalId} (${t.resourceType})${stateNote}`);
|
|
33622
|
+
}
|
|
33623
|
+
logger.warn(" The destroy + recreate cycle is per-resource; sibling resources are unaffected. Downstream consumers of any recreated resource's outputs (Fn::GetStackOutput / Fn::ImportValue) will need a re-deploy to see the new physical id.");
|
|
33624
|
+
}
|
|
33625
|
+
}
|
|
33342
33626
|
const deployEngineOptions = {
|
|
33343
33627
|
concurrency: options.concurrency,
|
|
33344
33628
|
dryRun: options.dryRun,
|
|
33345
33629
|
noRollback: !options.rollback,
|
|
33630
|
+
...recreateViaCcApiTargets && recreateViaCcApiTargets.size > 0 && { recreateViaCcApiTargets },
|
|
33346
33631
|
captureObservedState: resolveCaptureObservedState(options.captureObservedState),
|
|
33347
33632
|
...options.resourceWarnAfter?.globalMs !== void 0 && { resourceWarnAfterMs: options.resourceWarnAfter.globalMs },
|
|
33348
33633
|
...options.resourceTimeout?.globalMs !== void 0 && { resourceTimeoutMs: options.resourceTimeout.globalMs },
|
|
@@ -47040,6 +47325,7 @@ function discoverFunctionUrl(logicalId, resource, template, stackName) {
|
|
|
47040
47325
|
apiVersion: "v2",
|
|
47041
47326
|
stage: "$default",
|
|
47042
47327
|
apiStackName: stackName,
|
|
47328
|
+
apiLogicalId: logicalId,
|
|
47043
47329
|
...lambdaCdkPath !== void 0 && { apiCdkPath: lambdaCdkPath },
|
|
47044
47330
|
declaredAt: `${stackName}/${logicalId}`
|
|
47045
47331
|
};
|
|
@@ -50800,17 +51086,32 @@ function isPlaceholder(segment) {
|
|
|
50800
51086
|
//#endregion
|
|
50801
51087
|
//#region src/local/cors-handler.ts
|
|
50802
51088
|
/**
|
|
50803
|
-
* Build a `
|
|
50804
|
-
*
|
|
50805
|
-
*
|
|
50806
|
-
*
|
|
51089
|
+
* Build a `logicalId → CorsConfig | undefined` map. Walks the template
|
|
51090
|
+
* once and picks two CORS-bearing resource types:
|
|
51091
|
+
*
|
|
51092
|
+
* - `AWS::ApiGatewayV2::Api` → `Properties.CorsConfiguration`
|
|
51093
|
+
* (HTTP API v2; the original PR 8c surface)
|
|
51094
|
+
* - `AWS::Lambda::Url` → `Properties.Cors` (Function URL; issue #644)
|
|
51095
|
+
*
|
|
51096
|
+
* Both blocks are field-for-field identical in CFn schema (same
|
|
51097
|
+
* `AllowOrigins` / `AllowMethods` / `AllowHeaders` / `ExposeHeaders` /
|
|
51098
|
+
* `MaxAge` / `AllowCredentials`), so a single parser handles both. The
|
|
51099
|
+
* map key is the resource's own logical ID — that ID is later looked up
|
|
51100
|
+
* against `DiscoveredRoute.apiLogicalId` (set to the surface-bearing
|
|
51101
|
+
* resource at route-discovery time) so the preflight interceptor finds
|
|
51102
|
+
* the right config.
|
|
51103
|
+
*
|
|
51104
|
+
* Resources without a CORS block (or whose block is malformed) are NOT
|
|
51105
|
+
* entered into the map.
|
|
50807
51106
|
*/
|
|
50808
51107
|
function buildCorsConfigByApiId(template) {
|
|
50809
51108
|
const out = /* @__PURE__ */ new Map();
|
|
50810
51109
|
const resources = template.Resources ?? {};
|
|
50811
51110
|
for (const [logicalId, resource] of Object.entries(resources)) {
|
|
50812
|
-
|
|
50813
|
-
|
|
51111
|
+
let raw;
|
|
51112
|
+
if (resource.Type === "AWS::ApiGatewayV2::Api") raw = (resource.Properties ?? {})["CorsConfiguration"];
|
|
51113
|
+
else if (resource.Type === "AWS::Lambda::Url") raw = (resource.Properties ?? {})["Cors"];
|
|
51114
|
+
else continue;
|
|
50814
51115
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
50815
51116
|
const parsed = parseCorsConfiguration(raw);
|
|
50816
51117
|
if (parsed) out.set(logicalId, parsed);
|
|
@@ -59764,7 +60065,7 @@ function reorderArgs(argv) {
|
|
|
59764
60065
|
*/
|
|
59765
60066
|
async function main() {
|
|
59766
60067
|
const program = new Command();
|
|
59767
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
60068
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.162.0");
|
|
59768
60069
|
program.addCommand(createBootstrapCommand());
|
|
59769
60070
|
program.addCommand(createSynthCommand());
|
|
59770
60071
|
program.addCommand(createListCommand());
|