@go-to-k/cdkd 0.232.0 → 0.234.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 +1 -1
- package/dist/cli.js +223 -395
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-BkPZar1R.js → deploy-engine-Dj9mXUQa.js} +743 -7
- package/dist/deploy-engine-Dj9mXUQa.js.map +1 -0
- package/dist/index.d.ts +65 -46
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-BkPZar1R.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { A as withErrorHandling, B as generateResourceName, C as StackHasActiveImportsError, F as getLiveRenderer, H as withSkipPrefix, I as PATTERN_B_NAME_OPTIONS, L as PATTERN_B_NAME_PROPERTIES, M as getLogger, P as runStackBuffered, R as PATTERN_B_RESOURCE_TYPES, S as ResourceUpdateNotSupportedError, U as withStackName, V as generateResourceNameWithFallback, _ as MissingCdkCliError, a as normalizeAwsTagsToCfn, b as ProvisioningError, f as LocalInvokeBuildError$1, i as matchesCdkPath, k as normalizeAwsError, l as CdkdError, m as LocalStartServiceError, n as isTerminationProtectionPropagationError, o as resolveExplicitPhysicalId, p as LocalMigrateError, r as CDK_PATH_TAG, s as assertRegionMatch, t as disableInstanceApiTermination, v as NestedStackChildDirectDestroyError, w as StackTerminationProtectionError, x as ResourceTimeoutError, y as PartialFailureError } from "./ec2-termination-protection-uvVnteGl.js";
|
|
3
3
|
import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-pjPwZz1r.js";
|
|
4
|
-
import { $ as
|
|
4
|
+
import { $ as AssetManifestLoader, A as DiffCalculator, B as buildAssetRedirectMap, C as findActionableSilentDrops, D as refStateLookupFromResource, E as cfnRefValueFromPhysicalId, F as rebuildClientForBucketRegion, G as BOOTSTRAP_MARKER_PREFIX, H as loadPublishableAssetManifest, I as shouldRetainResource, J as buildDockerImage, K as ensureAssetStorage, L as AssetPublisher, M as TemplateParser, N as LockManager, O as WAFv2WebACLProvider, P as S3StateBackend, Q as runDockerStreaming, R as stringifyValue, S as ProviderRegistry, T as IntrinsicFunctionResolver, U as rewriteTemplateAssetReferences, V as createAssetRedirectResolver, W as AssetModeResolver, X as getDockerCmd, Y as formatDockerLoginError, Z as runDockerForeground, _ as green, _t as AssemblyReader, a as withRetry, at as resolveApp, b as IAMRoleProvider, c as computeImplicitDeleteEdges, ct as resolveStateBucketWithDefault, d as isStatefulRecreateTargetSync, dt as warnDeprecatedNoPrefixCliFlag, et as getDockerImageBySourceHash, f as renderStatefulReason, ft as CFN_TEMPLATE_BODY_LIMIT, g as gray, gt as uploadCfnTemplate, h as cyan, ht as findLargeInlineResources, i as withResourceDeadline, it as getLegacyStateBucketName, j as DagBuilder, k as applyRoleArnIfSet, l as extractDeploymentEventError, lt as resolveStateBucketWithDefaultAndSource, m as bold, mt as MIGRATE_TMP_PREFIX, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as synthesisStatusMessage, o as isRetryableTransientError, ot as resolveCaptureObservedState, p as formatResourceLine, pt as CFN_TEMPLATE_URL_LIMIT, q as parseBootstrapMarker, r as DeployEngine, rt as getDefaultStateBucketName, s as IMPLICIT_DELETE_DEPENDENCIES, st as resolveSkipPrefix, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as Synthesizer, u as MULTI_REGION_RECREATE_BLOCKED_TYPES, ut as resolveUseCdkBootstrapAssets, v as red, w as CloudControlProvider, x as collectInlinePolicyNamesManagedBySiblings, y as yellow, yt as resolveBucketRegion, z as WorkGraph } from "./deploy-engine-Dj9mXUQa.js";
|
|
5
5
|
import { t as ASGProvider } from "./asg-provider-Dd9Xze-T.js";
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
@@ -492,6 +492,14 @@ const recreateViaSdkProviderOption = new Option("--recreate-via-sdk-provider <lo
|
|
|
492
492
|
*/
|
|
493
493
|
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);
|
|
494
494
|
const replaceOption = new Option("--replace", "Replace (DELETE + CREATE) a resource whose in-place update is rejected because an immutable property changed and AWS exposes no update API for it (e.g. Lambda LayerVersion content, EFS AccessPoint, ECS TaskDefinition, ApiGatewayV2 immutable fields). Without this flag such a change fails the deploy. Stateful types (RDS / DynamoDB / EFS / S3 with data / Logs with retention / etc.) ALSO require --force-stateful-recreation since the replacement is a data-losing DELETE + CREATE.").default(false);
|
|
495
|
+
/**
|
|
496
|
+
* Issue #1002 PR 2 — per-invocation legacy pin for asset destinations
|
|
497
|
+
* (design §4.2). Shared by `deploy`, `diff`, `import`, and `publish-assets`
|
|
498
|
+
* (every command the redirection / rewrite affects) so the CLI surface stays
|
|
499
|
+
* consistent; the per-app pin is `cdk.json context.cdkd.useCdkBootstrapAssets`
|
|
500
|
+
* (see `resolveUseCdkBootstrapAssets` in config-loader.ts).
|
|
501
|
+
*/
|
|
502
|
+
const useCdkBootstrapAssetsOption = new Option("--use-cdk-bootstrap-assets", "Publish assets to the CDK bootstrap bucket/repo named by the asset manifest verbatim, skipping the cdkd asset-storage redirection even when this region is opted in via 'cdkd bootstrap'. For apps deployed via both CloudFormation and cdkd during a migration window. Per-app pin: cdk.json context.cdkd.useCdkBootstrapAssets. Also suppresses the legacy-mode 'cdk gc' notice.").default(false);
|
|
495
503
|
const deployOptions = [
|
|
496
504
|
new Option("--concurrency <number>", "Maximum concurrent resource operations").default(10).argParser((value) => parseInt(value, 10)),
|
|
497
505
|
new Option("--stack-concurrency <number>", "Maximum concurrent stack deployments").default(4).argParser((value) => parseInt(value, 10)),
|
|
@@ -511,6 +519,7 @@ const deployOptions = [
|
|
|
511
519
|
recreateViaSdkProviderOption,
|
|
512
520
|
forceStatefulRecreationOption,
|
|
513
521
|
replaceOption,
|
|
522
|
+
useCdkBootstrapAssetsOption,
|
|
514
523
|
...resourceTimeoutOptions
|
|
515
524
|
];
|
|
516
525
|
/**
|
|
@@ -598,310 +607,6 @@ const destroyOptions = [
|
|
|
598
607
|
allowUnsupportedTypesOption
|
|
599
608
|
];
|
|
600
609
|
|
|
601
|
-
//#endregion
|
|
602
|
-
//#region src/assets/asset-storage.ts
|
|
603
|
-
/**
|
|
604
|
-
* cdkd-owned asset storage — naming, bootstrap marker, and deploy-time
|
|
605
|
-
* asset-mode detection (issue #1002, design at
|
|
606
|
-
* docs/design/1002-cdkd-asset-storage.md).
|
|
607
|
-
*
|
|
608
|
-
* Why this exists: cdkd publishes assets to the CDK bootstrap bucket / ECR
|
|
609
|
-
* repo, but `cdk gc` decides "in use" by scanning CloudFormation stack
|
|
610
|
-
* templates — cdkd-deployed stacks have no CFn stack, so every cdkd-published
|
|
611
|
-
* asset looks isolated and gets deleted. The fix is cdkd-owned asset storage
|
|
612
|
-
* created by `cdkd bootstrap` (structurally out of `cdk gc`'s reach because
|
|
613
|
-
* gc only discovers storage from the CDK bootstrap stack's outputs).
|
|
614
|
-
*
|
|
615
|
-
* The upgrade is strictly transparent: deploys behave byte-identically to
|
|
616
|
-
* before until the user re-runs `cdkd bootstrap` for a region, which writes a
|
|
617
|
-
* per-region marker object into the state bucket. Deploy reads the marker to
|
|
618
|
-
* pick the mode; PR 2 of the phasing wires the actual publish redirection +
|
|
619
|
-
* template rewrite onto the `cdkd-assets` mode resolved here.
|
|
620
|
-
*/
|
|
621
|
-
/** Marker schema version, bumped if the marker shape ever changes. */
|
|
622
|
-
const ASSET_SUPPORT_VERSION = 1;
|
|
623
|
-
/**
|
|
624
|
-
* S3 key prefix for bootstrap markers in the state bucket. Deliberately
|
|
625
|
-
* outside the `cdkd/` state prefix so `state list` key-listing never mistakes
|
|
626
|
-
* a marker for a stack, and concurrent bootstraps in two regions cannot race
|
|
627
|
-
* last-writer-wins on a shared object (design §12.1).
|
|
628
|
-
*/
|
|
629
|
-
const BOOTSTRAP_MARKER_PREFIX = "cdkd-bootstrap/";
|
|
630
|
-
/**
|
|
631
|
-
* Name of the cdkd-owned asset bucket for an (account, region) pair.
|
|
632
|
-
* Per-region because Lambda requires the code bucket to be in the function's
|
|
633
|
-
* region — the same reason CDK's asset bucket is per-region (design §3).
|
|
634
|
-
*/
|
|
635
|
-
function getCdkdAssetBucketName(accountId, region) {
|
|
636
|
-
return `cdkd-assets-${accountId}-${region}`;
|
|
637
|
-
}
|
|
638
|
-
/**
|
|
639
|
-
* Name of the cdkd-owned container-asset ECR repository for an
|
|
640
|
-
* (account, region) pair. ECR repos are account+region scoped by ARN, so the
|
|
641
|
-
* suffix is not strictly needed — the CDK-parallel shape keeps the future
|
|
642
|
-
* template rewrite uniform and the names self-describing (design §3).
|
|
643
|
-
*/
|
|
644
|
-
function getCdkdContainerRepoName(accountId, region) {
|
|
645
|
-
return `cdkd-container-assets-${accountId}-${region}`;
|
|
646
|
-
}
|
|
647
|
-
/**
|
|
648
|
-
* State-bucket key of the bootstrap marker for a region.
|
|
649
|
-
*/
|
|
650
|
-
function getBootstrapMarkerKey(region) {
|
|
651
|
-
return `${BOOTSTRAP_MARKER_PREFIX}${region}.json`;
|
|
652
|
-
}
|
|
653
|
-
/**
|
|
654
|
-
* Parse and validate a bootstrap marker body.
|
|
655
|
-
*
|
|
656
|
-
* Throws on malformed JSON or missing required fields — a corrupt marker is
|
|
657
|
-
* treated like the marker-present-but-resources-missing case (hard error,
|
|
658
|
-
* never a silent legacy fallback that would flip-flop stack properties
|
|
659
|
-
* between deploys — design §4.1).
|
|
660
|
-
*/
|
|
661
|
-
function parseBootstrapMarker(body, markerKey) {
|
|
662
|
-
let parsed;
|
|
663
|
-
try {
|
|
664
|
-
parsed = JSON.parse(body);
|
|
665
|
-
} catch (error) {
|
|
666
|
-
throw new CdkdError(`Bootstrap marker '${markerKey}' in the state bucket is not valid JSON. Re-run 'cdkd bootstrap' for this region to rewrite it.`, "INVALID_BOOTSTRAP_MARKER", error);
|
|
667
|
-
}
|
|
668
|
-
const marker = parsed;
|
|
669
|
-
if (typeof marker.assetBucket !== "string" || marker.assetBucket.length === 0 || typeof marker.containerRepo !== "string" || marker.containerRepo.length === 0 || typeof marker.assetSupportVersion !== "number") throw new CdkdError(`Bootstrap marker '${markerKey}' in the state bucket is malformed (missing assetBucket / containerRepo / assetSupportVersion). Re-run 'cdkd bootstrap' for this region to rewrite it.`, "INVALID_BOOTSTRAP_MARKER");
|
|
670
|
-
if (marker.assetSupportVersion > 1) throw new CdkdError(`Bootstrap marker '${markerKey}' has assetSupportVersion ${marker.assetSupportVersion}, but this cdkd only understands up to ${1}. Upgrade cdkd to deploy in this region.`, "UNSUPPORTED_BOOTSTRAP_MARKER_VERSION");
|
|
671
|
-
return {
|
|
672
|
-
assetBucket: marker.assetBucket,
|
|
673
|
-
containerRepo: marker.containerRepo,
|
|
674
|
-
assetSupportVersion: marker.assetSupportVersion,
|
|
675
|
-
createdAt: typeof marker.createdAt === "string" ? marker.createdAt : ""
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* Verify that the asset bucket + ECR repo named by a marker actually exist.
|
|
680
|
-
*
|
|
681
|
-
* Called when a marker is present: the user opted the region in, so missing
|
|
682
|
-
* resources (user deleted them) are a hard error naming the missing resource
|
|
683
|
-
* and the `cdkd bootstrap` fix — never a silent fallback to CDK bootstrap
|
|
684
|
-
* storage (design §4.1).
|
|
685
|
-
*
|
|
686
|
-
* Every S3 call passes `ExpectedBucketOwner` so a marker pointing at a
|
|
687
|
-
* since-recreated foreign bucket can never leak assets cross-account
|
|
688
|
-
* (bucket-squatting defense, design §5).
|
|
689
|
-
*/
|
|
690
|
-
async function verifyAssetStorageExists(marker, accountId, region, opts = {}) {
|
|
691
|
-
const rebootstrapHint = `Run 'cdkd bootstrap --region ${region}' to recreate it. cdkd never silently falls back to CDK bootstrap asset storage once a region is opted in.`;
|
|
692
|
-
const clientOpts = {
|
|
693
|
-
region,
|
|
694
|
-
...opts.profile && { profile: opts.profile }
|
|
695
|
-
};
|
|
696
|
-
const s3Client = new S3Client(clientOpts);
|
|
697
|
-
const ecrClient = new ECRClient(clientOpts);
|
|
698
|
-
try {
|
|
699
|
-
try {
|
|
700
|
-
await s3Client.send(new HeadBucketCommand({
|
|
701
|
-
Bucket: marker.assetBucket,
|
|
702
|
-
ExpectedBucketOwner: accountId
|
|
703
|
-
}));
|
|
704
|
-
} catch (error) {
|
|
705
|
-
const err = error;
|
|
706
|
-
if (err.name === "NotFound" || err.name === "NoSuchBucket") throw new CdkdError(`cdkd asset storage is bootstrapped for region '${region}' but the asset bucket '${marker.assetBucket}' is missing. ${rebootstrapHint}`, "ASSET_STORAGE_MISSING");
|
|
707
|
-
if (err.$metadata?.httpStatusCode === 403) throw new CdkdError(`Asset bucket '${marker.assetBucket}' exists but is not owned by account ${accountId} (or access is denied). Refusing to use it. ${rebootstrapHint}`, "ASSET_STORAGE_FOREIGN_BUCKET", error);
|
|
708
|
-
throw error;
|
|
709
|
-
}
|
|
710
|
-
try {
|
|
711
|
-
await ecrClient.send(new DescribeRepositoriesCommand({ repositoryNames: [marker.containerRepo] }));
|
|
712
|
-
} catch (error) {
|
|
713
|
-
if (error.name === "RepositoryNotFoundException") throw new CdkdError(`cdkd asset storage is bootstrapped for region '${region}' but the container-asset ECR repository '${marker.containerRepo}' is missing. ${rebootstrapHint}`, "ASSET_STORAGE_MISSING");
|
|
714
|
-
throw error;
|
|
715
|
-
}
|
|
716
|
-
} finally {
|
|
717
|
-
s3Client.destroy();
|
|
718
|
-
ecrClient.destroy();
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
/**
|
|
722
|
-
* Create (or adopt, when already owned by this account) the cdkd asset
|
|
723
|
-
* bucket + container-asset ECR repo for a region, then write the bootstrap
|
|
724
|
-
* marker. Called by `cdkd bootstrap` unless `--no-assets` is passed.
|
|
725
|
-
*
|
|
726
|
-
* Idempotent like the state-bucket path: existing resources are left as-is
|
|
727
|
-
* unless `force` re-applies their configuration. The marker is written LAST,
|
|
728
|
-
* only after both resources exist (design §5) — a crash mid-bootstrap leaves
|
|
729
|
-
* no marker, so deploys stay in legacy mode.
|
|
730
|
-
*
|
|
731
|
-
* Bucket-squatting defense: the bucket name is predictable (same weakness as
|
|
732
|
-
* CDK's bootstrap bucket), so this refuses to adopt a bucket this account
|
|
733
|
-
* does not own, and the `HeadBucket` probe passes `ExpectedBucketOwner`.
|
|
734
|
-
*/
|
|
735
|
-
async function ensureAssetStorage(options) {
|
|
736
|
-
const logger = getLogger();
|
|
737
|
-
const { s3Client, ecrClient, stateBackend, accountId, region, force } = options;
|
|
738
|
-
const assetBucket = getCdkdAssetBucketName(accountId, region);
|
|
739
|
-
const containerRepo = getCdkdContainerRepoName(accountId, region);
|
|
740
|
-
let bucketExists = false;
|
|
741
|
-
try {
|
|
742
|
-
await s3Client.send(new HeadBucketCommand({
|
|
743
|
-
Bucket: assetBucket,
|
|
744
|
-
ExpectedBucketOwner: accountId
|
|
745
|
-
}));
|
|
746
|
-
bucketExists = true;
|
|
747
|
-
logger.info(`Asset bucket ${assetBucket} already exists`);
|
|
748
|
-
} catch (error) {
|
|
749
|
-
const err = error;
|
|
750
|
-
if (err.name === "NotFound" || err.name === "NoSuchBucket") {} else if (err.$metadata?.httpStatusCode === 403) throw new CdkdError(`Asset bucket name '${assetBucket}' is already taken by a bucket this account does not own (or access is denied). Refusing to adopt it — resolve the naming conflict before re-running 'cdkd bootstrap'.`, "ASSET_STORAGE_FOREIGN_BUCKET", error);
|
|
751
|
-
else throw normalizeAwsError(error, {
|
|
752
|
-
bucket: assetBucket,
|
|
753
|
-
operation: "HeadBucket"
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
if (!bucketExists) {
|
|
757
|
-
logger.info(`Creating asset bucket: ${assetBucket} in region ${region}`);
|
|
758
|
-
try {
|
|
759
|
-
await s3Client.send(new CreateBucketCommand({
|
|
760
|
-
Bucket: assetBucket,
|
|
761
|
-
...region !== "us-east-1" && { CreateBucketConfiguration: { LocationConstraint: region } }
|
|
762
|
-
}));
|
|
763
|
-
logger.info(`✓ Created asset bucket: ${assetBucket}`);
|
|
764
|
-
} catch (error) {
|
|
765
|
-
const err = error;
|
|
766
|
-
if (err.name === "BucketAlreadyOwnedByYou") logger.info(`Asset bucket ${assetBucket} already exists`);
|
|
767
|
-
else if (err.name === "BucketAlreadyExists") throw new CdkdError(`Asset bucket name '${assetBucket}' is already taken by another AWS account. Refusing to adopt it — resolve the naming conflict before re-running 'cdkd bootstrap'.`, "ASSET_STORAGE_FOREIGN_BUCKET", error);
|
|
768
|
-
else throw normalizeAwsError(error, {
|
|
769
|
-
bucket: assetBucket,
|
|
770
|
-
operation: "CreateBucket"
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
if (!bucketExists || force) {
|
|
775
|
-
await s3Client.send(new PutBucketEncryptionCommand({
|
|
776
|
-
Bucket: assetBucket,
|
|
777
|
-
ExpectedBucketOwner: accountId,
|
|
778
|
-
ServerSideEncryptionConfiguration: { Rules: [{
|
|
779
|
-
ApplyServerSideEncryptionByDefault: { SSEAlgorithm: "AES256" },
|
|
780
|
-
BucketKeyEnabled: true
|
|
781
|
-
}] }
|
|
782
|
-
}));
|
|
783
|
-
await s3Client.send(new PutPublicAccessBlockCommand({
|
|
784
|
-
Bucket: assetBucket,
|
|
785
|
-
ExpectedBucketOwner: accountId,
|
|
786
|
-
PublicAccessBlockConfiguration: {
|
|
787
|
-
BlockPublicAcls: true,
|
|
788
|
-
BlockPublicPolicy: true,
|
|
789
|
-
IgnorePublicAcls: true,
|
|
790
|
-
RestrictPublicBuckets: true
|
|
791
|
-
}
|
|
792
|
-
}));
|
|
793
|
-
await s3Client.send(new PutBucketPolicyCommand({
|
|
794
|
-
Bucket: assetBucket,
|
|
795
|
-
ExpectedBucketOwner: accountId,
|
|
796
|
-
Policy: JSON.stringify({
|
|
797
|
-
Version: "2012-10-17",
|
|
798
|
-
Statement: [{
|
|
799
|
-
Sid: "DenyExternalAccess",
|
|
800
|
-
Effect: "Deny",
|
|
801
|
-
Principal: "*",
|
|
802
|
-
Action: "s3:*",
|
|
803
|
-
Resource: [`arn:aws:s3:::${assetBucket}`, `arn:aws:s3:::${assetBucket}/*`],
|
|
804
|
-
Condition: { StringNotEquals: { "aws:PrincipalAccount": accountId } }
|
|
805
|
-
}]
|
|
806
|
-
})
|
|
807
|
-
}));
|
|
808
|
-
logger.info("✓ Configured asset bucket (AES-256 encryption, public access block, deny external access)");
|
|
809
|
-
}
|
|
810
|
-
let repoExists = false;
|
|
811
|
-
try {
|
|
812
|
-
await ecrClient.send(new DescribeRepositoriesCommand({ repositoryNames: [containerRepo] }));
|
|
813
|
-
repoExists = true;
|
|
814
|
-
logger.info(`Container-asset repository ${containerRepo} already exists`);
|
|
815
|
-
} catch (error) {
|
|
816
|
-
if (error.name !== "RepositoryNotFoundException") throw error;
|
|
817
|
-
}
|
|
818
|
-
if (!repoExists) {
|
|
819
|
-
logger.info(`Creating container-asset ECR repository: ${containerRepo}`);
|
|
820
|
-
try {
|
|
821
|
-
await ecrClient.send(new CreateRepositoryCommand({
|
|
822
|
-
repositoryName: containerRepo,
|
|
823
|
-
imageTagMutability: "IMMUTABLE"
|
|
824
|
-
}));
|
|
825
|
-
logger.info(`✓ Created container-asset ECR repository: ${containerRepo}`);
|
|
826
|
-
} catch (error) {
|
|
827
|
-
if (error.name === "RepositoryAlreadyExistsException") logger.info(`Container-asset repository ${containerRepo} already exists`);
|
|
828
|
-
else throw error;
|
|
829
|
-
}
|
|
830
|
-
} else if (force) {
|
|
831
|
-
await ecrClient.send(new PutImageTagMutabilityCommand({
|
|
832
|
-
repositoryName: containerRepo,
|
|
833
|
-
imageTagMutability: "IMMUTABLE"
|
|
834
|
-
}));
|
|
835
|
-
logger.info("✓ Configured container-asset repository (immutable tags)");
|
|
836
|
-
}
|
|
837
|
-
const marker = {
|
|
838
|
-
assetBucket,
|
|
839
|
-
containerRepo,
|
|
840
|
-
assetSupportVersion: 1,
|
|
841
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
842
|
-
};
|
|
843
|
-
await stateBackend.putRawObject(getBootstrapMarkerKey(region), JSON.stringify(marker, null, 2));
|
|
844
|
-
logger.info(`✓ Wrote bootstrap marker (${getBootstrapMarkerKey(region)})`);
|
|
845
|
-
return {
|
|
846
|
-
assetBucket,
|
|
847
|
-
containerRepo
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
/**
|
|
851
|
-
* Deploy-time asset-mode resolver. One instance per CLI invocation; results
|
|
852
|
-
* are cached per region for the process lifetime (the marker read is one
|
|
853
|
-
* GetObject against a bucket every deploy already reads — design §4.1).
|
|
854
|
-
*
|
|
855
|
-
* In legacy mode, one `logger.info` line per invocation mentions the
|
|
856
|
-
* `cdk gc` hazard and the `cdkd bootstrap` opt-in (info, not warn — existing
|
|
857
|
-
* users are not doing anything wrong; design §12.2).
|
|
858
|
-
*/
|
|
859
|
-
var AssetModeResolver = class {
|
|
860
|
-
logger = getLogger().child("AssetMode");
|
|
861
|
-
cache = /* @__PURE__ */ new Map();
|
|
862
|
-
legacyNoticeShown = false;
|
|
863
|
-
stateBackend;
|
|
864
|
-
accountId;
|
|
865
|
-
profile;
|
|
866
|
-
constructor(stateBackend, accountId, opts = {}) {
|
|
867
|
-
this.stateBackend = stateBackend;
|
|
868
|
-
this.accountId = accountId;
|
|
869
|
-
this.profile = opts.profile;
|
|
870
|
-
}
|
|
871
|
-
/**
|
|
872
|
-
* Resolve the asset mode for a deploy region. Concurrent callers for the
|
|
873
|
-
* same region share one in-flight resolution.
|
|
874
|
-
*/
|
|
875
|
-
resolve(region) {
|
|
876
|
-
const cached = this.cache.get(region);
|
|
877
|
-
if (cached) return cached;
|
|
878
|
-
const inFlight = this.doResolve(region).catch((error) => {
|
|
879
|
-
this.cache.delete(region);
|
|
880
|
-
throw error;
|
|
881
|
-
});
|
|
882
|
-
this.cache.set(region, inFlight);
|
|
883
|
-
return inFlight;
|
|
884
|
-
}
|
|
885
|
-
async doResolve(region) {
|
|
886
|
-
const markerKey = getBootstrapMarkerKey(region);
|
|
887
|
-
const body = await this.stateBackend.getRawObject(markerKey);
|
|
888
|
-
if (body === null) {
|
|
889
|
-
if (!this.legacyNoticeShown) {
|
|
890
|
-
this.legacyNoticeShown = true;
|
|
891
|
-
this.logger.info("Assets are published to the CDK bootstrap bucket/repo, which 'cdk gc' may garbage-collect (cdkd-deployed stacks have no CloudFormation stack for gc to scan). Run 'cdkd bootstrap' to create cdkd-owned asset storage that 'cdk gc' never touches.");
|
|
892
|
-
}
|
|
893
|
-
return { mode: "legacy" };
|
|
894
|
-
}
|
|
895
|
-
const marker = parseBootstrapMarker(body, markerKey);
|
|
896
|
-
await verifyAssetStorageExists(marker, this.accountId, region, { ...this.profile && { profile: this.profile } });
|
|
897
|
-
this.logger.debug(`cdkd asset storage active for region '${region}': ${marker.assetBucket} / ${marker.containerRepo}`);
|
|
898
|
-
return {
|
|
899
|
-
mode: "cdkd-assets",
|
|
900
|
-
marker
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
|
-
};
|
|
904
|
-
|
|
905
610
|
//#endregion
|
|
906
611
|
//#region src/cli/commands/bootstrap.ts
|
|
907
612
|
/**
|
|
@@ -1795,7 +1500,7 @@ const FLUSH_INTERVAL_MS = 2e3;
|
|
|
1795
1500
|
const FLUSH_EVENT_THRESHOLD = 50;
|
|
1796
1501
|
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
1797
1502
|
function getCdkdVersion() {
|
|
1798
|
-
return "0.
|
|
1503
|
+
return "0.234.0";
|
|
1799
1504
|
}
|
|
1800
1505
|
/**
|
|
1801
1506
|
* Generate a time-sortable unique run id, e.g.
|
|
@@ -37119,7 +36824,7 @@ var NestedStackProvider = class {
|
|
|
37119
36824
|
this.requireDeployContext(ctx, "create");
|
|
37120
36825
|
const childTemplatePath = ctx.nestedTemplates[logicalId];
|
|
37121
36826
|
if (!childTemplatePath) throw new Error(`Nested template file not found for AWS::CloudFormation::Stack '${logicalId}' under parent '${ctx.parentStackName}'. Verify the synth output emits Metadata['aws:asset:path'] on this resource (CDK 2.x cdk.NestedStack does so by default).`);
|
|
37122
|
-
const childTemplate = this.readChildTemplate(childTemplatePath);
|
|
36827
|
+
const childTemplate = this.readChildTemplate(childTemplatePath, ctx.assetRedirect);
|
|
37123
36828
|
const childStackName = this.deriveChildStackName(ctx.parentStackName, logicalId);
|
|
37124
36829
|
const childRegion = ctx.parentRegion;
|
|
37125
36830
|
const childParameters = this.extractParameters(properties);
|
|
@@ -37138,7 +36843,7 @@ var NestedStackProvider = class {
|
|
|
37138
36843
|
this.requireDeployContext(ctx, "update");
|
|
37139
36844
|
const childTemplatePath = ctx.nestedTemplates[logicalId];
|
|
37140
36845
|
if (!childTemplatePath) throw new Error(`Nested template file not found for AWS::CloudFormation::Stack '${logicalId}' on update.`);
|
|
37141
|
-
const childTemplate = this.readChildTemplate(childTemplatePath);
|
|
36846
|
+
const childTemplate = this.readChildTemplate(childTemplatePath, ctx.assetRedirect);
|
|
37142
36847
|
const childStackName = this.deriveChildStackName(ctx.parentStackName, logicalId);
|
|
37143
36848
|
const childRegion = ctx.parentRegion;
|
|
37144
36849
|
const childParameters = this.extractParameters(properties);
|
|
@@ -37233,18 +36938,24 @@ var NestedStackProvider = class {
|
|
|
37233
36938
|
else throw new Error(`NestedStackProvider: child Parameter '${k}' resolved to a non-scalar value (type=${v === null ? "null" : typeof v}). Parameters must be scalars (string / number / boolean) by the time they reach the provider — an unresolved intrinsic here means IntrinsicFunctionResolver upstream did not handle the value, which is a bug. Surface the unresolved input rather than silently coercing to '[object Object]'.`);
|
|
37234
36939
|
return result;
|
|
37235
36940
|
}
|
|
37236
|
-
readChildTemplate(templatePath) {
|
|
36941
|
+
readChildTemplate(templatePath, assetRedirect) {
|
|
37237
36942
|
let raw;
|
|
37238
36943
|
try {
|
|
37239
36944
|
raw = fs.readFileSync(templatePath, "utf-8");
|
|
37240
36945
|
} catch (err) {
|
|
37241
36946
|
throw new Error(`Failed to read nested template at ${templatePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
37242
36947
|
}
|
|
36948
|
+
let template;
|
|
37243
36949
|
try {
|
|
37244
|
-
|
|
36950
|
+
template = JSON.parse(raw);
|
|
37245
36951
|
} catch (err) {
|
|
37246
36952
|
throw new Error(`Failed to parse nested template at ${templatePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
37247
36953
|
}
|
|
36954
|
+
if (assetRedirect) {
|
|
36955
|
+
const n = rewriteTemplateAssetReferences(template, assetRedirect);
|
|
36956
|
+
if (n > 0) this.logger.debug(`Rewrote ${n} asset reference(s) to cdkd asset storage in nested template ${templatePath}`);
|
|
36957
|
+
}
|
|
36958
|
+
return template;
|
|
37248
36959
|
}
|
|
37249
36960
|
indexGrandchildTemplates(childTemplate, childTemplatePath) {
|
|
37250
36961
|
const dir = path.dirname(childTemplatePath);
|
|
@@ -37994,7 +37705,11 @@ async function deployCommand(stacks, options) {
|
|
|
37994
37705
|
const accountId = (await stsClient.send(new GetCallerIdentityCommand({}))).Account;
|
|
37995
37706
|
stsClient.destroy();
|
|
37996
37707
|
const assetPublisher = new AssetPublisher();
|
|
37997
|
-
const
|
|
37708
|
+
const useCdkBootstrapAssets = resolveUseCdkBootstrapAssets(options.useCdkBootstrapAssets);
|
|
37709
|
+
const assetModeResolver = new AssetModeResolver(preflightStateBackend, accountId, {
|
|
37710
|
+
...options.profile && { profile: options.profile },
|
|
37711
|
+
...useCdkBootstrapAssets && { useCdkBootstrapAssets: true }
|
|
37712
|
+
});
|
|
37998
37713
|
const stateConfig = {
|
|
37999
37714
|
bucket: stateBucket,
|
|
38000
37715
|
prefix: options.statePrefix
|
|
@@ -38008,27 +37723,36 @@ async function deployCommand(stacks, options) {
|
|
|
38008
37723
|
};
|
|
38009
37724
|
const workGraph = new WorkGraph();
|
|
38010
37725
|
const stackMap = new Map(targetStacks.map((s) => [s.stackName, s]));
|
|
37726
|
+
const stackRedirects = /* @__PURE__ */ new Map();
|
|
38011
37727
|
for (const stack of targetStacks) {
|
|
38012
37728
|
const stackNodeId = `stack:${stack.stackName}`;
|
|
38013
37729
|
const stackDeps = /* @__PURE__ */ new Set();
|
|
38014
|
-
if (
|
|
37730
|
+
if (stack.assetManifestPath) {
|
|
38015
37731
|
const assetRegion = stack.region || baseRegion;
|
|
38016
|
-
|
|
38017
|
-
|
|
38018
|
-
nodeIds = assetPublisher.addAssetsToGraph(workGraph, stack.assetManifestPath, {
|
|
38019
|
-
accountId,
|
|
38020
|
-
region: assetRegion,
|
|
38021
|
-
...options.profile && { profile: options.profile },
|
|
38022
|
-
nodePrefix: `${stack.stackName}:`
|
|
38023
|
-
});
|
|
38024
|
-
} catch (error) {
|
|
38025
|
-
if (error.code !== "ENOENT") throw error;
|
|
38026
|
-
}
|
|
38027
|
-
if (nodeIds.length > 0) {
|
|
37732
|
+
const manifest = loadPublishableAssetManifest(stack.assetManifestPath);
|
|
37733
|
+
if (manifest) {
|
|
38028
37734
|
const assetMode = await assetModeResolver.resolve(assetRegion);
|
|
38029
37735
|
logger.debug(`Asset mode for region ${assetRegion}: ${assetMode.mode} (stack ${stack.stackName})`);
|
|
37736
|
+
let redirect;
|
|
37737
|
+
if (assetMode.mode === "cdkd-assets") {
|
|
37738
|
+
redirect = buildAssetRedirectMap(manifest, assetMode.marker, accountId, assetRegion);
|
|
37739
|
+
if (redirect.entries.length > 0) {
|
|
37740
|
+
stackRedirects.set(stack.stackName, redirect);
|
|
37741
|
+
const rewritten = rewriteTemplateAssetReferences(stack.template, redirect);
|
|
37742
|
+
logger.debug(`Rewrote ${rewritten} asset reference(s) to cdkd asset storage (${assetMode.marker.assetBucket} / ${assetMode.marker.containerRepo}) in template of stack ${stack.stackName}`);
|
|
37743
|
+
} else redirect = void 0;
|
|
37744
|
+
}
|
|
37745
|
+
if (!options.skipAssets) {
|
|
37746
|
+
const nodeIds = assetPublisher.addAssetsToGraph(workGraph, stack.assetManifestPath, {
|
|
37747
|
+
accountId,
|
|
37748
|
+
region: assetRegion,
|
|
37749
|
+
...options.profile && { profile: options.profile },
|
|
37750
|
+
nodePrefix: `${stack.stackName}:`,
|
|
37751
|
+
...redirect && { redirect }
|
|
37752
|
+
});
|
|
37753
|
+
for (const id of nodeIds) stackDeps.add(id);
|
|
37754
|
+
}
|
|
38030
37755
|
}
|
|
38031
|
-
for (const id of nodeIds) stackDeps.add(id);
|
|
38032
37756
|
}
|
|
38033
37757
|
for (const depName of effectiveStackDeps(stack.stackName, stack.dependencyNames)) if (stackMap.has(depName)) stackDeps.add(`stack:${depName}`);
|
|
38034
37758
|
workGraph.addNode({
|
|
@@ -38127,10 +37851,12 @@ async function deployCommand(stacks, options) {
|
|
|
38127
37851
|
})) return;
|
|
38128
37852
|
}
|
|
38129
37853
|
}
|
|
37854
|
+
const assetRedirect = stackRedirects.get(stackInfo.stackName);
|
|
38130
37855
|
const deployEngineOptions = {
|
|
38131
37856
|
concurrency: options.concurrency,
|
|
38132
37857
|
dryRun: options.dryRun,
|
|
38133
37858
|
noRollback: !options.rollback,
|
|
37859
|
+
...assetRedirect && { assetRedirect },
|
|
38134
37860
|
...eventRecorder && { eventRecorder },
|
|
38135
37861
|
...recreateViaCcApiTargets && recreateViaCcApiTargets.size > 0 && { recreateViaCcApiTargets },
|
|
38136
37862
|
...recreateViaSdkProviderTargets && recreateViaSdkProviderTargets.size > 0 && { recreateViaSdkProviderTargets },
|
|
@@ -38156,7 +37882,8 @@ async function deployCommand(stacks, options) {
|
|
|
38156
37882
|
nestedTemplates: stackInfo.nestedTemplates ?? {},
|
|
38157
37883
|
dagBuilder,
|
|
38158
37884
|
diffCalculator,
|
|
38159
|
-
options: deployEngineOptions
|
|
37885
|
+
options: deployEngineOptions,
|
|
37886
|
+
...assetRedirect && { assetRedirect }
|
|
38160
37887
|
}, () => stackDeployEngine.deploy(stackInfo.stackName, stackInfo.template));
|
|
38161
37888
|
logger.info(`\n${bold("Deployment Summary:")}`);
|
|
38162
37889
|
logger.info(` Stack: ${bold(cyan(deployResult.stackName))}`);
|
|
@@ -39194,7 +38921,7 @@ async function resolveChildStackParameters(parentStackRow, parentTemplate, paren
|
|
|
39194
38921
|
* fail at deploy time.
|
|
39195
38922
|
*/
|
|
39196
38923
|
async function buildDiffTree(args) {
|
|
39197
|
-
const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parameters } = args;
|
|
38924
|
+
const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parameters, assetRedirect } = args;
|
|
39198
38925
|
const state = await loadStateOrEmpty(stackName, region, stateBackend);
|
|
39199
38926
|
const node = {
|
|
39200
38927
|
stackName,
|
|
@@ -39213,6 +38940,7 @@ async function buildDiffTree(args) {
|
|
|
39213
38940
|
if (!childTemplatePath) throw new Error(`Nested template file not found for ${NESTED_STACK_RESOURCE_TYPE} '${logicalId}' under stack '${stackName}'. Verify the synth output emits Metadata['aws:asset:path'] on this resource (CDK 2.x cdk.NestedStack does so by default), then re-run synth.`);
|
|
39214
38941
|
const childStackName = `${stackName}~${logicalId}`;
|
|
39215
38942
|
const childTemplate = readNestedTemplate(childTemplatePath);
|
|
38943
|
+
if (assetRedirect) rewriteTemplateAssetReferences(childTemplate, assetRedirect);
|
|
39216
38944
|
const grandchildTemplates = indexNestedChildTemplates(childTemplate, childTemplatePath);
|
|
39217
38945
|
const childParameters = await resolveChildStackParameters(resource, template, state, region, stackName, stateBackend, parameters);
|
|
39218
38946
|
node.children.push(await buildDiffTree({
|
|
@@ -39224,7 +38952,8 @@ async function buildDiffTree(args) {
|
|
|
39224
38952
|
recursive: true,
|
|
39225
38953
|
stateBackend,
|
|
39226
38954
|
diffCalculator,
|
|
39227
|
-
parameters: childParameters
|
|
38955
|
+
parameters: childParameters,
|
|
38956
|
+
...assetRedirect && { assetRedirect }
|
|
39228
38957
|
}));
|
|
39229
38958
|
}
|
|
39230
38959
|
for (const [logicalId, resource] of Object.entries(state.resources)) {
|
|
@@ -39534,10 +39263,22 @@ async function diffCommand(stacks, options) {
|
|
|
39534
39263
|
});
|
|
39535
39264
|
const diffCalculator = new DiffCalculator();
|
|
39536
39265
|
const recursive = options.recursive ?? false;
|
|
39266
|
+
const resolveAssetRedirect = createAssetRedirectResolver({
|
|
39267
|
+
stateBackend,
|
|
39268
|
+
stsRegion: region,
|
|
39269
|
+
...options.profile && { profile: options.profile },
|
|
39270
|
+
useCdkBootstrapAssets: resolveUseCdkBootstrapAssets(options.useCdkBootstrapAssets),
|
|
39271
|
+
suppressLegacyNotice: true
|
|
39272
|
+
});
|
|
39537
39273
|
const trees = [];
|
|
39538
39274
|
for (const stackInfo of targetStacks) {
|
|
39539
39275
|
logger.info(`\nCalculating diff for stack: ${stackInfo.stackName}`);
|
|
39540
39276
|
const stackRegion = stackInfo.region || region;
|
|
39277
|
+
const assetRedirect = await resolveAssetRedirect(stackInfo.assetManifestPath, stackRegion);
|
|
39278
|
+
if (assetRedirect) {
|
|
39279
|
+
const rewritten = rewriteTemplateAssetReferences(stackInfo.template, assetRedirect);
|
|
39280
|
+
logger.debug(`Rewrote ${rewritten} asset reference(s) to cdkd asset storage in template of stack ${stackInfo.stackName}`);
|
|
39281
|
+
}
|
|
39541
39282
|
trees.push(await buildDiffTree({
|
|
39542
39283
|
stackName: stackInfo.stackName,
|
|
39543
39284
|
displayName: stackInfo.stackName,
|
|
@@ -39546,7 +39287,8 @@ async function diffCommand(stacks, options) {
|
|
|
39546
39287
|
nestedTemplates: stackInfo.nestedTemplates ?? {},
|
|
39547
39288
|
recursive,
|
|
39548
39289
|
stateBackend,
|
|
39549
|
-
diffCalculator
|
|
39290
|
+
diffCalculator,
|
|
39291
|
+
...assetRedirect && { assetRedirect }
|
|
39550
39292
|
}));
|
|
39551
39293
|
}
|
|
39552
39294
|
if (options.json) process.stdout.write(`${JSON.stringify(trees.map(diffTreeToJson), null, 2)}\n`);
|
|
@@ -39566,7 +39308,7 @@ async function diffCommand(stacks, options) {
|
|
|
39566
39308
|
* Create diff command
|
|
39567
39309
|
*/
|
|
39568
39310
|
function createDiffCommand() {
|
|
39569
|
-
const cmd = new Command("diff").description("Show difference between current state and desired state").argument("[stacks...]", "Stack name(s) to diff. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*').").option("--all", "Diff all stacks", false).option("--recursive", "Recurse into each AWS::CloudFormation::Stack row and diff every nested-stack child against its own deployed state (DFS order). Default is non-recursive, matching cdk diff.", false).option("--fail", "Exit with code 1 when any change is detected (matches cdk diff --fail). With --recursive, considers the whole nested-stack tree.", false).option("--json", "Output the diff as JSON (nested tree shape when combined with --recursive)", false).action(withErrorHandling(diffCommand));
|
|
39311
|
+
const cmd = new Command("diff").description("Show difference between current state and desired state").argument("[stacks...]", "Stack name(s) to diff. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*').").option("--all", "Diff all stacks", false).option("--recursive", "Recurse into each AWS::CloudFormation::Stack row and diff every nested-stack child against its own deployed state (DFS order). Default is non-recursive, matching cdk diff.", false).option("--fail", "Exit with code 1 when any change is detected (matches cdk diff --fail). With --recursive, considers the whole nested-stack tree.", false).option("--json", "Output the diff as JSON (nested tree shape when combined with --recursive)", false).addOption(useCdkBootstrapAssetsOption).action(withErrorHandling(diffCommand));
|
|
39570
39312
|
[
|
|
39571
39313
|
...commonOptions,
|
|
39572
39314
|
...appOptions,
|
|
@@ -41859,36 +41601,45 @@ async function publishAssetsCommand(stacks, options) {
|
|
|
41859
41601
|
const stsClient = new STSClient({ region: baseRegion });
|
|
41860
41602
|
const accountId = (await stsClient.send(new GetCallerIdentityCommand({}))).Account;
|
|
41861
41603
|
stsClient.destroy();
|
|
41604
|
+
const useCdkBootstrapAssets = resolveUseCdkBootstrapAssets(options.useCdkBootstrapAssets);
|
|
41605
|
+
let markerAccess;
|
|
41606
|
+
const getModeResolver = async () => {
|
|
41607
|
+
if (markerAccess !== void 0) return markerAccess?.resolver ?? null;
|
|
41608
|
+
let bucket;
|
|
41609
|
+
try {
|
|
41610
|
+
bucket = await resolveStateBucketWithDefault(options.stateBucket, baseRegion);
|
|
41611
|
+
} catch {
|
|
41612
|
+
logger.info("No cdkd state bucket found — publishing to the asset-manifest destinations (CDK bootstrap storage) verbatim. Run 'cdkd bootstrap' to opt into cdkd-owned asset storage that 'cdk gc' never touches.");
|
|
41613
|
+
markerAccess = null;
|
|
41614
|
+
return null;
|
|
41615
|
+
}
|
|
41616
|
+
const markerClients = new AwsClients({
|
|
41617
|
+
region: baseRegion,
|
|
41618
|
+
...options.profile && { profile: options.profile }
|
|
41619
|
+
});
|
|
41620
|
+
const resolver = new AssetModeResolver(new S3StateBackend(markerClients.s3, {
|
|
41621
|
+
bucket,
|
|
41622
|
+
prefix: options.statePrefix
|
|
41623
|
+
}, {
|
|
41624
|
+
region: baseRegion,
|
|
41625
|
+
...options.profile && { profile: options.profile }
|
|
41626
|
+
}), accountId, { ...options.profile && { profile: options.profile } });
|
|
41627
|
+
markerAccess = {
|
|
41628
|
+
resolver,
|
|
41629
|
+
dispose: () => markerClients.destroy()
|
|
41630
|
+
};
|
|
41631
|
+
return resolver;
|
|
41632
|
+
};
|
|
41862
41633
|
const assetPublisher = new AssetPublisher();
|
|
41863
41634
|
const results = [];
|
|
41864
|
-
|
|
41865
|
-
const
|
|
41866
|
-
|
|
41867
|
-
|
|
41868
|
-
|
|
41869
|
-
if (!stack.assetManifestPath) {
|
|
41870
|
-
logger.debug(`Stack ${stack.stackName} has no asset manifest; nothing to publish`);
|
|
41871
|
-
results.push({
|
|
41872
|
-
stackName: stack.stackName,
|
|
41873
|
-
displayName: stack.displayName,
|
|
41874
|
-
assetCount: 0,
|
|
41875
|
-
durationMs: Date.now() - startedAt
|
|
41876
|
-
});
|
|
41877
|
-
continue;
|
|
41878
|
-
}
|
|
41879
|
-
logger.info(`\nPublishing assets for stack: ${describeStack(stack)}`);
|
|
41880
|
-
const workGraph = new WorkGraph();
|
|
41881
|
-
let nodeIds = [];
|
|
41635
|
+
try {
|
|
41636
|
+
for (const stack of targetStacks) {
|
|
41637
|
+
const startedAt = Date.now();
|
|
41638
|
+
let assetCount = 0;
|
|
41639
|
+
let error;
|
|
41882
41640
|
try {
|
|
41883
|
-
|
|
41884
|
-
|
|
41885
|
-
region: stack.region || baseRegion,
|
|
41886
|
-
...options.profile && { profile: options.profile },
|
|
41887
|
-
nodePrefix: `${stack.stackName}:`
|
|
41888
|
-
});
|
|
41889
|
-
} catch (err) {
|
|
41890
|
-
if (err.code === "ENOENT") {
|
|
41891
|
-
logger.debug(`Asset manifest not found for ${stack.stackName} (${stack.assetManifestPath}); skipping`);
|
|
41641
|
+
if (!stack.assetManifestPath) {
|
|
41642
|
+
logger.debug(`Stack ${stack.stackName} has no asset manifest; nothing to publish`);
|
|
41892
41643
|
results.push({
|
|
41893
41644
|
stackName: stack.stackName,
|
|
41894
41645
|
displayName: stack.displayName,
|
|
@@ -41897,36 +41648,76 @@ async function publishAssetsCommand(stacks, options) {
|
|
|
41897
41648
|
});
|
|
41898
41649
|
continue;
|
|
41899
41650
|
}
|
|
41900
|
-
|
|
41901
|
-
|
|
41902
|
-
|
|
41903
|
-
|
|
41904
|
-
|
|
41905
|
-
|
|
41906
|
-
|
|
41907
|
-
|
|
41908
|
-
|
|
41909
|
-
|
|
41910
|
-
|
|
41911
|
-
|
|
41651
|
+
logger.info(`\nPublishing assets for stack: ${describeStack(stack)}`);
|
|
41652
|
+
let redirect;
|
|
41653
|
+
if (!useCdkBootstrapAssets) {
|
|
41654
|
+
const manifest = loadPublishableAssetManifest(stack.assetManifestPath);
|
|
41655
|
+
if (manifest) {
|
|
41656
|
+
const modeResolver = await getModeResolver();
|
|
41657
|
+
if (modeResolver) {
|
|
41658
|
+
const stackRegion = stack.region || baseRegion;
|
|
41659
|
+
const assetMode = await modeResolver.resolve(stackRegion);
|
|
41660
|
+
if (assetMode.mode === "cdkd-assets") {
|
|
41661
|
+
const map = buildAssetRedirectMap(manifest, assetMode.marker, accountId, stackRegion);
|
|
41662
|
+
if (map.entries.length > 0) redirect = map;
|
|
41663
|
+
}
|
|
41664
|
+
}
|
|
41665
|
+
}
|
|
41666
|
+
}
|
|
41667
|
+
const workGraph = new WorkGraph();
|
|
41668
|
+
let nodeIds = [];
|
|
41669
|
+
try {
|
|
41670
|
+
nodeIds = assetPublisher.addAssetsToGraph(workGraph, stack.assetManifestPath, {
|
|
41671
|
+
accountId,
|
|
41672
|
+
region: stack.region || baseRegion,
|
|
41673
|
+
...options.profile && { profile: options.profile },
|
|
41674
|
+
nodePrefix: `${stack.stackName}:`,
|
|
41675
|
+
...redirect && { redirect }
|
|
41676
|
+
});
|
|
41677
|
+
} catch (err) {
|
|
41678
|
+
if (err.code === "ENOENT") {
|
|
41679
|
+
logger.debug(`Asset manifest not found for ${stack.stackName} (${stack.assetManifestPath}); skipping`);
|
|
41680
|
+
results.push({
|
|
41681
|
+
stackName: stack.stackName,
|
|
41682
|
+
displayName: stack.displayName,
|
|
41683
|
+
assetCount: 0,
|
|
41684
|
+
durationMs: Date.now() - startedAt
|
|
41685
|
+
});
|
|
41686
|
+
continue;
|
|
41687
|
+
}
|
|
41688
|
+
throw err;
|
|
41689
|
+
}
|
|
41690
|
+
assetCount = nodeIds.filter((id) => id.startsWith("asset-publish:")).length;
|
|
41691
|
+
if (assetCount === 0) {
|
|
41692
|
+
logger.info(" (no assets to publish)");
|
|
41693
|
+
results.push({
|
|
41694
|
+
stackName: stack.stackName,
|
|
41695
|
+
displayName: stack.displayName,
|
|
41696
|
+
assetCount: 0,
|
|
41697
|
+
durationMs: Date.now() - startedAt
|
|
41698
|
+
});
|
|
41699
|
+
continue;
|
|
41700
|
+
}
|
|
41701
|
+
await workGraph.execute({
|
|
41702
|
+
"asset-build": options.imageBuildConcurrency,
|
|
41703
|
+
"asset-publish": options.assetPublishConcurrency,
|
|
41704
|
+
stack: 0
|
|
41705
|
+
}, (node) => assetPublisher.executeNode(node));
|
|
41706
|
+
logger.info(` ✓ Published ${assetCount} asset(s) in ${((Date.now() - startedAt) / 1e3).toFixed(2)}s`);
|
|
41707
|
+
} catch (err) {
|
|
41708
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
41709
|
+
logger.error(` ✗ ${stack.stackName}: ${error.message}`);
|
|
41912
41710
|
}
|
|
41913
|
-
|
|
41914
|
-
|
|
41915
|
-
|
|
41916
|
-
|
|
41917
|
-
|
|
41918
|
-
|
|
41919
|
-
|
|
41920
|
-
|
|
41921
|
-
|
|
41922
|
-
|
|
41923
|
-
results.push({
|
|
41924
|
-
stackName: stack.stackName,
|
|
41925
|
-
displayName: stack.displayName,
|
|
41926
|
-
assetCount,
|
|
41927
|
-
durationMs: Date.now() - startedAt,
|
|
41928
|
-
...error && { error }
|
|
41929
|
-
});
|
|
41711
|
+
results.push({
|
|
41712
|
+
stackName: stack.stackName,
|
|
41713
|
+
displayName: stack.displayName,
|
|
41714
|
+
assetCount,
|
|
41715
|
+
durationMs: Date.now() - startedAt,
|
|
41716
|
+
...error && { error }
|
|
41717
|
+
});
|
|
41718
|
+
}
|
|
41719
|
+
} finally {
|
|
41720
|
+
markerAccess?.dispose();
|
|
41930
41721
|
}
|
|
41931
41722
|
const failed = results.filter((r) => r.error);
|
|
41932
41723
|
const totalAssets = results.reduce((sum, r) => sum + r.assetCount, 0);
|
|
@@ -41949,11 +41740,12 @@ async function publishAssetsCommand(stacks, options) {
|
|
|
41949
41740
|
* `cdkd deploy --no-asset-publish` (which doesn't exist).
|
|
41950
41741
|
*/
|
|
41951
41742
|
function createPublishAssetsCommand() {
|
|
41952
|
-
const cmd = new Command("publish-assets").description("Synthesize the CDK app and publish assets to S3/ECR for the selected stack(s) without deploying").argument("[stacks...]", "Stack name(s) to publish assets for. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards.").option("--all", "Publish assets for all stacks", false).addOption(new Option("--asset-publish-concurrency <number>", "Maximum concurrent asset publish operations").default(8).argParser((value) => parseInt(value, 10))).addOption(new Option("--image-build-concurrency <number>", "Maximum concurrent Docker image builds").default(4).argParser((value) => parseInt(value, 10))).action(withErrorHandling(publishAssetsCommand));
|
|
41743
|
+
const cmd = new Command("publish-assets").description("Synthesize the CDK app and publish assets to S3/ECR for the selected stack(s) without deploying").argument("[stacks...]", "Stack name(s) to publish assets for. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards.").option("--all", "Publish assets for all stacks", false).addOption(new Option("--asset-publish-concurrency <number>", "Maximum concurrent asset publish operations").default(8).argParser((value) => parseInt(value, 10))).addOption(new Option("--image-build-concurrency <number>", "Maximum concurrent Docker image builds").default(4).argParser((value) => parseInt(value, 10))).addOption(useCdkBootstrapAssetsOption).action(withErrorHandling(publishAssetsCommand));
|
|
41953
41744
|
[
|
|
41954
41745
|
...commonOptions,
|
|
41955
41746
|
...appOptions,
|
|
41956
|
-
...contextOptions
|
|
41747
|
+
...contextOptions,
|
|
41748
|
+
...stateOptions
|
|
41957
41749
|
].forEach((opt) => cmd.addOption(opt));
|
|
41958
41750
|
cmd.addOption(new Option("--stack <name>", "Stack name to publish (alternative to positional)"));
|
|
41959
41751
|
cmd.addOption(deprecatedRegionOption);
|
|
@@ -45977,6 +45769,17 @@ async function importCommand(stackArg, options) {
|
|
|
45977
45769
|
else throw new Error(`Multiple stacks found: ${result.stacks.map((s) => s.stackName).join(", ")}. Specify the stack name as a positional argument.`);
|
|
45978
45770
|
const targetRegion = stackInfo.region || region;
|
|
45979
45771
|
logger.info(`Target stack: ${stackInfo.stackName} (${targetRegion})`);
|
|
45772
|
+
const assetRedirect = await createAssetRedirectResolver({
|
|
45773
|
+
stateBackend,
|
|
45774
|
+
stsRegion: region,
|
|
45775
|
+
...options.profile && { profile: options.profile },
|
|
45776
|
+
useCdkBootstrapAssets: resolveUseCdkBootstrapAssets(options.useCdkBootstrapAssets),
|
|
45777
|
+
suppressLegacyNotice: true
|
|
45778
|
+
})(stackInfo.assetManifestPath, targetRegion);
|
|
45779
|
+
if (assetRedirect) {
|
|
45780
|
+
const rewritten = rewriteTemplateAssetReferences(stackInfo.template, assetRedirect);
|
|
45781
|
+
logger.debug(`Rewrote ${rewritten} asset reference(s) to cdkd asset storage in template of stack ${stackInfo.stackName}`);
|
|
45782
|
+
}
|
|
45980
45783
|
const overrides = parseResourceOverrides(options.resource, options.resourceMapping, options.resourceMappingInline);
|
|
45981
45784
|
if (overrides.size > 0) logger.debug(`User-supplied physical IDs: ${[...overrides.keys()].join(", ")}`);
|
|
45982
45785
|
const migrationCfnStackName = options.migrateFromCloudformation ? typeof options.migrateFromCloudformation === "string" && options.migrateFromCloudformation.length > 0 ? options.migrateFromCloudformation : stackInfo.stackName : void 0;
|
|
@@ -46113,7 +45916,8 @@ async function importCommand(stackArg, options) {
|
|
|
46113
45916
|
templateParser,
|
|
46114
45917
|
lockOwner: owner,
|
|
46115
45918
|
accountId: accountIdForNestedSynth,
|
|
46116
|
-
logger
|
|
45919
|
+
logger,
|
|
45920
|
+
assetRedirect
|
|
46117
45921
|
});
|
|
46118
45922
|
if (migrationCfnStackName) {
|
|
46119
45923
|
const orphaned = resources.length - importedRows.length;
|
|
@@ -46528,7 +46332,7 @@ async function confirmPrompt(prompt) {
|
|
|
46528
46332
|
* default behavior, now opt-in.
|
|
46529
46333
|
*/
|
|
46530
46334
|
function createImportCommand() {
|
|
46531
|
-
const cmd = new Command("import").description("Adopt already-deployed AWS resources into cdkd state. Reads the CDK app to find logical IDs, resource types, and dependencies. With no flags, imports every resource via the aws:cdk:path tag. With --resource / --resource-mapping, only the listed resources are imported (CDK CLI parity); pass --auto to also tag-import the rest.").argument("[stack]", "Stack to import. Optional when the synthesized app contains exactly one stack.").option("--resource <id=physical>", "Explicit physical-id override for one logical ID. Repeatable. When at least one --resource is given, only listed resources are imported (CDK CLI parity). Pass --auto to also tag-import everything else.", collectMultiple, []).option("--resource-mapping <file>", "Path to a JSON file of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping` compatible). Implies selective mode unless --auto is set. Mutually exclusive with --resource-mapping-inline.").option("--resource-mapping-inline <json>", "Inline JSON object of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping-inline` compatible). Same shape as --resource-mapping but supplied as a string — useful for non-TTY CI scripts that do not want a separate file. Implies selective mode unless --auto is set. Mutually exclusive with --resource-mapping.").option("--record-resource-mapping <file>", "After cdkd resolves every logical ID (via --resource / --resource-mapping / tag-based auto-lookup), write the resulting {logicalId: physicalId} map to <file> as JSON. Useful in auto / hybrid mode for capturing the tag-resolved mapping and feeding it back as --resource-mapping in non-interactive CI re-runs. Written before the confirmation prompt (so the user can review the file before saying \"yes\") and even when the user says \"no\". Mirrors `cdk import --record-resource-mapping`.").option("--auto", "Hybrid mode: when explicit overrides are supplied, ALSO tag-import every other resource in the template. Without this flag, --resource / --resource-mapping behave as a whitelist (CDK CLI parity).", false).option("--dry-run", "Show planned imports without writing state", false).option("--force", "Confirm a destructive write to existing state. Required for auto / whole-stack import when state already exists (rebuilds the entire resource map). Also required in selective mode if a listed override would overwrite a resource already in state. Not needed for a pure selective merge (adding new resources without touching unlisted entries).", false).option("--migrate-from-cloudformation [cfn-stack-name]", "After cdkd state is written, retire the named CloudFormation stack (deletes the CFn stack record; AWS resources are NOT deleted): inject DeletionPolicy=Retain and UpdateReplacePolicy=Retain on every resource via UpdateStack, then DeleteStack. cdkd takes over management. Pass without a value to use the cdkd stack name as the CFn stack name (the typical case for a CDK app that was previously deployed via `cdk deploy`); pass an explicit value when the CFn stack name differs.").action(withErrorHandling(importCommand));
|
|
46335
|
+
const cmd = new Command("import").description("Adopt already-deployed AWS resources into cdkd state. Reads the CDK app to find logical IDs, resource types, and dependencies. With no flags, imports every resource via the aws:cdk:path tag. With --resource / --resource-mapping, only the listed resources are imported (CDK CLI parity); pass --auto to also tag-import the rest.").argument("[stack]", "Stack to import. Optional when the synthesized app contains exactly one stack.").option("--resource <id=physical>", "Explicit physical-id override for one logical ID. Repeatable. When at least one --resource is given, only listed resources are imported (CDK CLI parity). Pass --auto to also tag-import everything else.", collectMultiple, []).option("--resource-mapping <file>", "Path to a JSON file of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping` compatible). Implies selective mode unless --auto is set. Mutually exclusive with --resource-mapping-inline.").option("--resource-mapping-inline <json>", "Inline JSON object of {logicalId: physicalId} overrides (CDK CLI `cdk import --resource-mapping-inline` compatible). Same shape as --resource-mapping but supplied as a string — useful for non-TTY CI scripts that do not want a separate file. Implies selective mode unless --auto is set. Mutually exclusive with --resource-mapping.").option("--record-resource-mapping <file>", "After cdkd resolves every logical ID (via --resource / --resource-mapping / tag-based auto-lookup), write the resulting {logicalId: physicalId} map to <file> as JSON. Useful in auto / hybrid mode for capturing the tag-resolved mapping and feeding it back as --resource-mapping in non-interactive CI re-runs. Written before the confirmation prompt (so the user can review the file before saying \"yes\") and even when the user says \"no\". Mirrors `cdk import --record-resource-mapping`.").option("--auto", "Hybrid mode: when explicit overrides are supplied, ALSO tag-import every other resource in the template. Without this flag, --resource / --resource-mapping behave as a whitelist (CDK CLI parity).", false).option("--dry-run", "Show planned imports without writing state", false).option("--force", "Confirm a destructive write to existing state. Required for auto / whole-stack import when state already exists (rebuilds the entire resource map). Also required in selective mode if a listed override would overwrite a resource already in state. Not needed for a pure selective merge (adding new resources without touching unlisted entries).", false).option("--migrate-from-cloudformation [cfn-stack-name]", "After cdkd state is written, retire the named CloudFormation stack (deletes the CFn stack record; AWS resources are NOT deleted): inject DeletionPolicy=Retain and UpdateReplacePolicy=Retain on every resource via UpdateStack, then DeleteStack. cdkd takes over management. Pass without a value to use the cdkd stack name as the CFn stack name (the typical case for a CDK app that was previously deployed via `cdk deploy`); pass an explicit value when the CFn stack name differs.").addOption(useCdkBootstrapAssetsOption).action(withErrorHandling(importCommand));
|
|
46532
46336
|
[
|
|
46533
46337
|
...commonOptions,
|
|
46534
46338
|
...appOptions,
|
|
@@ -46666,7 +46470,7 @@ function validateNestedStackShape(template, tree, parentStackName, nestedTemplat
|
|
|
46666
46470
|
* is verified by unit test.
|
|
46667
46471
|
*/
|
|
46668
46472
|
async function importNestedStackChildrenRecursive(args) {
|
|
46669
|
-
const { parentStackName, parentRegion, parentNestedTemplates, parentTree, stateBackend, lockManager, providerRegistry, templateParser, lockOwner, accountId, logger } = args;
|
|
46473
|
+
const { parentStackName, parentRegion, parentNestedTemplates, parentTree, stateBackend, lockManager, providerRegistry, templateParser, lockOwner, accountId, logger, assetRedirect } = args;
|
|
46670
46474
|
for (const [childLogicalId, childTreeNode] of parentTree.nested) {
|
|
46671
46475
|
const childStackName = `${parentStackName}~${childLogicalId}`;
|
|
46672
46476
|
const childRegion = parentRegion;
|
|
@@ -46674,6 +46478,7 @@ async function importNestedStackChildrenRecursive(args) {
|
|
|
46674
46478
|
if (!childTemplatePath) throw new Error(`cdkd import --migrate-from-cloudformation: missing nested-template path for '${childLogicalId}' under parent '${parentStackName}' — validateNestedStackShape should have rejected this; please file a bug.`);
|
|
46675
46479
|
logger.info(`Adopting nested stack '${childLogicalId}' as cdkd stack '${childStackName}' (${childRegion})...`);
|
|
46676
46480
|
const childTemplate = readNestedChildTemplate(childTemplatePath, childLogicalId);
|
|
46481
|
+
if (assetRedirect) rewriteTemplateAssetReferences(childTemplate, assetRedirect);
|
|
46677
46482
|
await lockManager.acquireLock(childStackName, childRegion, lockOwner, "import");
|
|
46678
46483
|
try {
|
|
46679
46484
|
const childResources = collectImportableResources(childTemplate);
|
|
@@ -46725,7 +46530,8 @@ async function importNestedStackChildrenRecursive(args) {
|
|
|
46725
46530
|
templateParser,
|
|
46726
46531
|
lockOwner,
|
|
46727
46532
|
accountId,
|
|
46728
|
-
logger
|
|
46533
|
+
logger,
|
|
46534
|
+
assetRedirect
|
|
46729
46535
|
});
|
|
46730
46536
|
} finally {
|
|
46731
46537
|
await lockManager.releaseLock(childStackName, childRegion).catch((err) => {
|
|
@@ -48070,6 +47876,28 @@ function buildSubstitutionContextFromImageContext(context) {
|
|
|
48070
47876
|
return subContext;
|
|
48071
47877
|
}
|
|
48072
47878
|
/**
|
|
47879
|
+
* A container-image URI that points at a CDK-bootstrap OR cdkd-owned assets
|
|
47880
|
+
* ECR repository:
|
|
47881
|
+
*
|
|
47882
|
+
* - `cdk-<qualifier>-container-assets-<acct>-<region>` — the CDK bootstrap
|
|
47883
|
+
* repo (default qualifier `hnb659fds`, or a custom `cdk bootstrap
|
|
47884
|
+
* --qualifier` value);
|
|
47885
|
+
* - `cdkd-container-assets-<acct>-<region>` — the repo `cdkd bootstrap`
|
|
47886
|
+
* creates and `cdkd deploy` publishes into once a bootstrap marker exists
|
|
47887
|
+
* (issue #1002; a migrated stack's template / `--from-state` state carries
|
|
47888
|
+
* this shape).
|
|
47889
|
+
*
|
|
47890
|
+
* Either is a CDK asset image the ECS runner resolves back to the on-disk
|
|
47891
|
+
* `cdk.out` build, so both must classify as `kind: 'cdk-asset'`. The `cdk-`
|
|
47892
|
+
* qualifier is generalized to `[a-z0-9]+` so a custom-qualifier bootstrap is
|
|
47893
|
+
* matched too (previously only the hardcoded `hnb659fds` default was).
|
|
47894
|
+
*/
|
|
47895
|
+
const CDK_ASSET_IMAGE_REPO_RE = /(?:cdk-[a-z0-9]+|cdkd)-container-assets-/;
|
|
47896
|
+
/** True when `uri` embeds a CDK-bootstrap or cdkd-owned container-assets repo. */
|
|
47897
|
+
function isCdkAssetImageUri(uri) {
|
|
47898
|
+
return CDK_ASSET_IMAGE_REPO_RE.test(uri);
|
|
47899
|
+
}
|
|
47900
|
+
/**
|
|
48073
47901
|
* Parse the `Image` field of an ECS container definition.
|
|
48074
47902
|
*
|
|
48075
47903
|
* Three shapes:
|
|
@@ -48109,7 +47937,7 @@ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stac
|
|
|
48109
47937
|
if (joinResolved.kind === "unsupported-join") throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an unsupported Fn::Join Image shape: ${joinResolved.reason}. cdkd local run-task recognizes the canonical CDK 2.x ContainerImage.fromEcrRepository Fn::Join shape (delimiter "" with nested Fn::Select/Fn::Split over an ECR Repository Arn GetAtt + Ref to the repo).`);
|
|
48110
47938
|
const flat = extractImageString(raw);
|
|
48111
47939
|
if (!flat) throw new EcsTaskResolutionError(`Container '${containerName}' in task '${taskLogicalId}' has an unparseable Image property. cdkd local run-task v1 supports flat string images, single-key Fn::Sub bodies, and CDK-asset Image references.`);
|
|
48112
|
-
if (flat
|
|
47940
|
+
if (isCdkAssetImageUri(flat)) {
|
|
48113
47941
|
const hashMatch = /:([a-f0-9]{8,})$/.exec(flat);
|
|
48114
47942
|
const out = { kind: "cdk-asset" };
|
|
48115
47943
|
if (hashMatch) out.assetHash = hashMatch[1];
|
|
@@ -48157,7 +47985,7 @@ function findUnresolvedEcrRepositoryRef(substituted, resources) {
|
|
|
48157
47985
|
* `Fn::GetAtt` path can share the regex-match branches.
|
|
48158
47986
|
*/
|
|
48159
47987
|
function classifyResolvedImage(uri) {
|
|
48160
|
-
if (uri
|
|
47988
|
+
if (isCdkAssetImageUri(uri)) {
|
|
48161
47989
|
const hashMatch = /:([a-f0-9]{8,})$/.exec(uri);
|
|
48162
47990
|
const out = { kind: "cdk-asset" };
|
|
48163
47991
|
if (hashMatch) out.assetHash = hashMatch[1];
|
|
@@ -56183,7 +56011,7 @@ function reorderArgs(argv) {
|
|
|
56183
56011
|
async function main() {
|
|
56184
56012
|
installPipeCloseHandler();
|
|
56185
56013
|
const program = new Command();
|
|
56186
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
56014
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.234.0");
|
|
56187
56015
|
program.addCommand(createBootstrapCommand());
|
|
56188
56016
|
program.addCommand(createSynthCommand());
|
|
56189
56017
|
program.addCommand(createListCommand());
|