@go-to-k/cdkd 0.284.29 → 0.284.31
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/{asg-provider-DzbiUvJi.js → asg-provider-DJvcY8lj.js} +2 -2
- package/dist/{asg-provider-DzbiUvJi.js.map → asg-provider-DJvcY8lj.js.map} +1 -1
- package/dist/cli.js +355 -169
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-CE-tOz2d.js → deploy-engine-DYdEWX-s.js} +16 -7
- package/dist/deploy-engine-DYdEWX-s.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-CE-tOz2d.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as endCommandInterruptScope, $t as createAssetRedirectResolver, A as MULTI_REGION_RECREATE_BLOCKED_TYPES, An as resolveUseCdkBootstrapAssets, At as maskSecretsInText, B as collectDeclaredOutputNames, Bn as canonicalizeRegion, Bt as INTRINSIC_KEYS, C as ccRoutedFinalSnapshotError, Cn as getLegacyStateBucketName, Ct as STATE_SOURCED_READBACK_RULES, D as unsupportedFinalSnapshotError, Dn as resolveSkipPrefix, Dt as errorCauseChain, E as refusesFinalSnapshot, En as resolveCaptureObservedState, Et as dynamicReferenceTokens, F as cyan, Fn as MIGRATE_TMP_PREFIX, Ft as s3BucketDualStackDomainName, G as stateKeySecretExposure, Gn as resolveBucketRegion, Gt as LockManager, H as exportAliasCollisionScrubWarning, Hn as AssemblyReader, Ht as withRetry, I as gray, In as findLargeInlineResources, It as s3BucketRegionalDomainName, J as clearOnUpdateRemoval, Jn as resetAwsClients, Jt as shouldRetainResource, K as IAMRoleProvider, Kn as AwsClients, Kt as S3StateBackend, L as green, Ln as uploadCfnTemplate, Lt as s3BucketWebsiteUrl, M as renderStatefulReason, Mn as warnDeprecatedNoPrefixCliFlag, Mt as scrubResourceRecord, N as formatResourceLine, Nn as CFN_TEMPLATE_BODY_LIMIT, Nt as s3BucketArn, O as makeCanonicalizePropertiesFn, On as resolveStateBucketWithDefault, Ot as isSingleDynamicReferenceToken, P as bold, Pn as CFN_TEMPLATE_URL_LIMIT, Pt as s3BucketDomainName, Q as beginCommandInterruptScope, Qt as buildAssetRedirectMap, R as red, Rn as expectedOwnerParam, Rt as applyRoleArnIfSet, S as buildFinalSnapshotIdentifier, Sn as getDefaultStateBucketName, Sr as markNonRetryable, St as STATE_SOURCED_CROSS_GENERATION_RULES, T as isFinalSnapshotError, Tn as resolveAutoAssetStorage, Tt as createSecretMasker, U as isExportAliasCollision, Un as processStackMessages, Ut as DagBuilder, V as collectPublishedOutputNames, Vn as derivePartitionAndUrlSuffix, Vt as describeTypeWithThrottleRetry, W as secretBearingStateKeyWarning, Wt as TemplateParser, X as findActionableSilentDrops, Xt as stringifyValue, Y as ProviderRegistry, Yn as setAwsClients, Yt as AssetPublisher, Z as findSilentDropProperties, Zn as CdkdError, Zt as WorkGraph, _ as maskingRetryLogger, _n as runDockerStreaming, _r as normalizeAwsError, _t as readConfigString, a as DeploymentEventsStore, an as BOOTSTRAP_MARKER_PREFIX, ar as MissingCdkCliError, at as isTerminationProtectionPropagationError, b as ATOMIC_FINAL_SNAPSHOT_TYPES, bn as Synthesizer, br as isRetryableTransientError, bt as requireConfigObject, c as planRollback, cn as parseBootstrapMarker, cr as ProvisioningError, ct as getAccountInfo, d as replayRollback, dn as validateContainerRepoName, dr as StackHasActiveImportsError, dt as normalizeAwsTagsToCfn, en as loadPublishableAssetManifest, er as DeployCancelledError, et as isInterruptedWaitError, f as updatePartialMessage, fn as buildDenyExternalAccessPolicy, fr as StackTerminationProtectionError, ft as resolveExplicitPhysicalId, g as withResourceDeadline, gn as runDockerForeground, gt as configStringRefusal, h as deleteSkipReason, hn as getDockerCmd, ht as configBooleanRefusal, i as DeploymentEventsReader, in as AssetModeResolver, it as disableInstanceApiTermination, j as isStatefulRecreateTargetSync, jn as stateBucketExistenceConfirmed, jt as redactSecretsForState, k as extractDeploymentEventError, kn as resolveStateBucketWithDefaultAndSource, kt as maskSecretsInError, l as producerRegionsFromState, ln as readBootstrapMarkerBody, lr as ResourceTimeoutError, lt as refStateLookupFromResource, m as UNSPECIFIED_SKIP_REASON, mn as formatDockerLoginError, mt as coerceCfnBoolean, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as escapeRegExp, nr as LocalMigrateError, nt as CloudControlProvider, o as classifyReplaySecretRegion, on as ensureAssetStorage, or as NestedStackChildDirectDestroyError, ot as IntrinsicFunctionResolver, p as updatePartialReason, pn as buildDockerImage, pt as assertRegionMatch, q as collectInlinePolicyNamesManagedBySiblings, qn as getAwsClients, qt as rebuildClientForBucketRegion, r as DeployEngine, rn as stripControlChars, rr as LocalStartServiceError, rt as slowCcOperationTimeoutMs, s as planFailedOps, sn as getBootstrapMarkerKey, sr as PartialFailureError, st as cfnRefValueFromPhysicalId, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as rewriteTemplateAssetReferences, tr as LocalInvokeBuildError$1, tt as startInterruptWatch, u as replayFailedOperations, un as validateAssetBucketName, ur as ResourceUpdateNotSupportedError, ut as WAFv2WebACLProvider, v as IMPLICIT_DELETE_DEPENDENCIES, vn as AssetManifestLoader, vr as withErrorHandling, vt as replayWarn, w as createPreDeleteFinalSnapshot, wn as resolveApp, wt as TEMPLATE_SOURCED_RULES, x as PRE_DELETE_SNAPSHOT_TYPES, xn as synthesisStatusMessage, xr as isThrottlingError, xt as requireConfigString, y as computeImplicitDeleteEdges, yn as getDockerImageBySourceHash, yr as isMarkedNonRetryable, yt as requireConfigArray, z as yellow, zn as PARTITION_TABLE, zt as DiffCalculator } from "./deploy-engine-
|
|
2
|
+
import { $ as endCommandInterruptScope, $t as createAssetRedirectResolver, A as MULTI_REGION_RECREATE_BLOCKED_TYPES, An as resolveUseCdkBootstrapAssets, At as maskSecretsInText, B as collectDeclaredOutputNames, Bn as canonicalizeRegion, Bt as INTRINSIC_KEYS, C as ccRoutedFinalSnapshotError, Cn as getLegacyStateBucketName, Ct as STATE_SOURCED_READBACK_RULES, D as unsupportedFinalSnapshotError, Dn as resolveSkipPrefix, Dt as errorCauseChain, E as refusesFinalSnapshot, En as resolveCaptureObservedState, Et as dynamicReferenceTokens, F as cyan, Fn as MIGRATE_TMP_PREFIX, Ft as s3BucketDualStackDomainName, G as stateKeySecretExposure, Gn as resolveBucketRegion, Gt as LockManager, H as exportAliasCollisionScrubWarning, Hn as AssemblyReader, Ht as withRetry, I as gray, In as findLargeInlineResources, It as s3BucketRegionalDomainName, J as clearOnUpdateRemoval, Jn as resetAwsClients, Jt as shouldRetainResource, K as IAMRoleProvider, Kn as AwsClients, Kt as S3StateBackend, L as green, Ln as uploadCfnTemplate, Lt as s3BucketWebsiteUrl, M as renderStatefulReason, Mn as warnDeprecatedNoPrefixCliFlag, Mt as scrubResourceRecord, N as formatResourceLine, Nn as CFN_TEMPLATE_BODY_LIMIT, Nt as s3BucketArn, O as makeCanonicalizePropertiesFn, On as resolveStateBucketWithDefault, Ot as isSingleDynamicReferenceToken, P as bold, Pn as CFN_TEMPLATE_URL_LIMIT, Pt as s3BucketDomainName, Q as beginCommandInterruptScope, Qt as buildAssetRedirectMap, R as red, Rn as expectedOwnerParam, Rt as applyRoleArnIfSet, S as buildFinalSnapshotIdentifier, Sn as getDefaultStateBucketName, Sr as markNonRetryable, St as STATE_SOURCED_CROSS_GENERATION_RULES, T as isFinalSnapshotError, Tn as resolveAutoAssetStorage, Tt as createSecretMasker, U as isExportAliasCollision, Un as processStackMessages, Ut as DagBuilder, V as collectPublishedOutputNames, Vn as derivePartitionAndUrlSuffix, Vt as describeTypeWithThrottleRetry, W as secretBearingStateKeyWarning, Wt as TemplateParser, X as findActionableSilentDrops, Xt as stringifyValue, Y as ProviderRegistry, Yn as setAwsClients, Yt as AssetPublisher, Z as findSilentDropProperties, Zn as CdkdError, Zt as WorkGraph, _ as maskingRetryLogger, _n as runDockerStreaming, _r as normalizeAwsError, _t as readConfigString, a as DeploymentEventsStore, an as BOOTSTRAP_MARKER_PREFIX, ar as MissingCdkCliError, at as isTerminationProtectionPropagationError, b as ATOMIC_FINAL_SNAPSHOT_TYPES, bn as Synthesizer, br as isRetryableTransientError, bt as requireConfigObject, c as planRollback, cn as parseBootstrapMarker, cr as ProvisioningError, ct as getAccountInfo, d as replayRollback, dn as validateContainerRepoName, dr as StackHasActiveImportsError, dt as normalizeAwsTagsToCfn, en as loadPublishableAssetManifest, er as DeployCancelledError, et as isInterruptedWaitError, f as updatePartialMessage, fn as buildDenyExternalAccessPolicy, fr as StackTerminationProtectionError, ft as resolveExplicitPhysicalId, g as withResourceDeadline, gn as runDockerForeground, gt as configStringRefusal, h as deleteSkipReason, hn as getDockerCmd, ht as configBooleanRefusal, i as DeploymentEventsReader, in as AssetModeResolver, it as disableInstanceApiTermination, j as isStatefulRecreateTargetSync, jn as stateBucketExistenceConfirmed, jt as redactSecretsForState, k as extractDeploymentEventError, kn as resolveStateBucketWithDefaultAndSource, kt as maskSecretsInError, l as producerRegionsFromState, ln as readBootstrapMarkerBody, lr as ResourceTimeoutError, lt as refStateLookupFromResource, m as UNSPECIFIED_SKIP_REASON, mn as formatDockerLoginError, mt as coerceCfnBoolean, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as escapeRegExp, nr as LocalMigrateError, nt as CloudControlProvider, o as classifyReplaySecretRegion, on as ensureAssetStorage, or as NestedStackChildDirectDestroyError, ot as IntrinsicFunctionResolver, p as updatePartialReason, pn as buildDockerImage, pt as assertRegionMatch, q as collectInlinePolicyNamesManagedBySiblings, qn as getAwsClients, qt as rebuildClientForBucketRegion, r as DeployEngine, rn as stripControlChars, rr as LocalStartServiceError, rt as slowCcOperationTimeoutMs, s as planFailedOps, sn as getBootstrapMarkerKey, sr as PartialFailureError, st as cfnRefValueFromPhysicalId, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as rewriteTemplateAssetReferences, tr as LocalInvokeBuildError$1, tt as startInterruptWatch, u as replayFailedOperations, un as validateAssetBucketName, ur as ResourceUpdateNotSupportedError, ut as WAFv2WebACLProvider, v as IMPLICIT_DELETE_DEPENDENCIES, vn as AssetManifestLoader, vr as withErrorHandling, vt as replayWarn, w as createPreDeleteFinalSnapshot, wn as resolveApp, wt as TEMPLATE_SOURCED_RULES, x as PRE_DELETE_SNAPSHOT_TYPES, xn as synthesisStatusMessage, xr as isThrottlingError, xt as requireConfigString, y as computeImplicitDeleteEdges, yn as getDockerImageBySourceHash, yr as isMarkedNonRetryable, yt as requireConfigArray, z as yellow, zn as PARTITION_TABLE, zt as DiffCalculator } from "./deploy-engine-DYdEWX-s.js";
|
|
3
3
|
import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as getCurrentStackName, h as withStackName, i as runStackBuffered, m as withSkipPrefix, n as getLogger, o as PATTERN_B_NAME_OPTIONS, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-zRrlbaQt.js";
|
|
4
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-DJvcY8lj.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
7
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -77,6 +77,162 @@ import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
|
77
77
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
78
78
|
import { WebSocketServer } from "ws";
|
|
79
79
|
|
|
80
|
+
//#region src/cli/commands/state-file-keys.ts
|
|
81
|
+
/**
|
|
82
|
+
* Shared helpers for enumerating and describing cdkd state-bucket keys.
|
|
83
|
+
*
|
|
84
|
+
* Extracted from `bootstrap-destroy.ts` (issue #1010) so that `cdkd gc`
|
|
85
|
+
* (issue #1012) reuses the exact same whole-bucket state-file discovery
|
|
86
|
+
* instead of duplicating it — the "scan the WHOLE bucket, not just the
|
|
87
|
+
* default `cdkd/` prefix" rule was a review blocker once already (PR #1018)
|
|
88
|
+
* and must not drift between the two commands.
|
|
89
|
+
*/
|
|
90
|
+
/**
|
|
91
|
+
* Every cdkd state file ends with this suffix, regardless of the
|
|
92
|
+
* `--state-prefix` it was deployed under (`{prefix}/{stack}/{region}/state.json`,
|
|
93
|
+
* or legacy `{prefix}/{stack}/state.json`).
|
|
94
|
+
*/
|
|
95
|
+
const STATE_FILE_SUFFIX = "/state.json";
|
|
96
|
+
/**
|
|
97
|
+
* The prefix `cdkd bootstrap` writes under, and the one `gc` /
|
|
98
|
+
* `bootstrap --destroy` configure their own state backend with.
|
|
99
|
+
*
|
|
100
|
+
* Shared here rather than spelled per command for the same reason this module
|
|
101
|
+
* exists at all: `bootstrap-destroy.ts` and `gc.ts` each held their own copy,
|
|
102
|
+
* and it is now also the `knownPrefix` argument to {@link describeStateKey},
|
|
103
|
+
* where a copy that drifted from the backend's actual prefix would silently
|
|
104
|
+
* send every key back to the shape heuristic instead of the exact depth rule.
|
|
105
|
+
*
|
|
106
|
+
* Note this is only the DEFAULT. Other commands accept `--state-prefix`, so
|
|
107
|
+
* the whole-bucket listings deliberately do not scope to it.
|
|
108
|
+
*/
|
|
109
|
+
const DEFAULT_STATE_PREFIX = "cdkd";
|
|
110
|
+
/**
|
|
111
|
+
* Segments left under a known prefix for the legacy `{prefix}/{stack}` layout.
|
|
112
|
+
*
|
|
113
|
+
* The twin of `S3StateBackend`'s `LEGACY_KEY_DEPTH`, which counts the
|
|
114
|
+
* `state.json` segment as well; this one counts what remains after the prefix
|
|
115
|
+
* and the suffix are stripped, so it is that value minus one. Fenced against it
|
|
116
|
+
* by a unit test rather than imported, because a value import from
|
|
117
|
+
* `s3-state-backend.js` breaks the suites that mock that module.
|
|
118
|
+
*/
|
|
119
|
+
const LEGACY_SEGMENTS_UNDER_PREFIX = 1;
|
|
120
|
+
/**
|
|
121
|
+
* Every cdkd stack lock ends with this suffix — the lock lives next to the
|
|
122
|
+
* state file (`{prefix}/{stack}/{region}/lock.json`, or the legacy region-less
|
|
123
|
+
* `{prefix}/{stack}/lock.json` that `lock-manager.ts` still writes for a
|
|
124
|
+
* pre-v2 stack).
|
|
125
|
+
*/
|
|
126
|
+
const LOCK_FILE_SUFFIX = "/lock.json";
|
|
127
|
+
/**
|
|
128
|
+
* `us-east-1` / `ap-northeast-1` / `us-gov-west-1` — a region-shaped segment.
|
|
129
|
+
*
|
|
130
|
+
* The first token is `[a-z]{2}` OR the literal `eusc` (issue #2001). Every
|
|
131
|
+
* region AWS ships has a two-letter first token except the European Sovereign
|
|
132
|
+
* Cloud partition's, which is four — `eusc-de-east-1` — and the old
|
|
133
|
+
* `^[a-z]{2}` rejected that whole partition. Enumerated from
|
|
134
|
+
* `aws-cdk-lib/region-info` and `@aws-sdk/util-endpoints`: 46 REAL regions,
|
|
135
|
+
* first tokens all two letters or `eusc`, none three. (A count of 53 includes
|
|
136
|
+
* the `aws-global` / `aws-iso-*-global` PSEUDO-regions, whose first token is
|
|
137
|
+
* `aws`; they carry no trailing `-<digits>`, so this pattern rejects them and
|
|
138
|
+
* so did the old one — no behavioural difference, but at 53 the "two letters
|
|
139
|
+
* or eusc" claim is false as stated.)
|
|
140
|
+
*
|
|
141
|
+
* **The exception is enumerated rather than widened, and that is the whole
|
|
142
|
+
* design.** The obvious fix — relaxing the bound to `{2,4}` — was written
|
|
143
|
+
* first and review measured what it cost: `api-prod-1`, `demo-app-1`,
|
|
144
|
+
* `dev-api-1`, `core-api-1`, `test-stack-2` all become region-shaped under it.
|
|
145
|
+
* Those are idiomatic STACK names, and in the legacy region-less layout a
|
|
146
|
+
* stack name sits exactly where a region would, so the pattern would report
|
|
147
|
+
* the key's PREFIX as the stack for each of them — the same wrong answer this
|
|
148
|
+
* issue is about, arriving from the other side and at a far higher rate. A
|
|
149
|
+
* length class cannot separate `eusc` from `demo`; only naming the partition
|
|
150
|
+
* can.
|
|
151
|
+
*
|
|
152
|
+
* The cost of enumerating is that a future partition with a new non-two-letter
|
|
153
|
+
* token needs this line updated. That is the same failure this issue fixed, it
|
|
154
|
+
* is a one-token change, and it fails in the recoverable direction (a region
|
|
155
|
+
* read as a stack name) rather than corrupting ordinary stack names.
|
|
156
|
+
*
|
|
157
|
+
* It stays SHAPE-based and deliberately does not reuse `isClientSafeRegion`
|
|
158
|
+
* from `intrinsic-function-resolver.ts`: that predicate is charset-based (its
|
|
159
|
+
* job is to keep a value inside a hostname label, so it accepts anything
|
|
160
|
+
* lowercase-alphanumeric) and would classify almost every stack name as a
|
|
161
|
+
* region. This one has the opposite job — telling a region segment apart from
|
|
162
|
+
* a stack name in the same position — so it must constrain the shape.
|
|
163
|
+
*
|
|
164
|
+
* A two-letter STACK name like `db-prod-1` is still misread in the legacy
|
|
165
|
+
* layout; that is pre-existing (`main` does the same) and is what the
|
|
166
|
+
* `knownPrefix` depth rule below repairs for callers that can supply one.
|
|
167
|
+
*/
|
|
168
|
+
const REGION_SEGMENT = /^(?:[a-z]{2}|eusc)(-[a-z]+)+-\d+$/;
|
|
169
|
+
/**
|
|
170
|
+
* List every state file in the bucket — the WHOLE bucket, not just the
|
|
171
|
+
* default `cdkd/` prefix. Other commands accept `--state-prefix`, so live
|
|
172
|
+
* stack state may exist under ANY prefix in this bucket; scoping this
|
|
173
|
+
* listing to the default prefix would let reference scans and teardown
|
|
174
|
+
* guards silently miss those stacks and delete live data.
|
|
175
|
+
*/
|
|
176
|
+
async function listAllStateKeys(stateBackend) {
|
|
177
|
+
return (await stateBackend.listRawKeys("")).filter((k) => k.endsWith(STATE_FILE_SUFFIX));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* List every stack lock file in the bucket — same whole-bucket rule as
|
|
181
|
+
* {@link listAllStateKeys}. A lock under ANY prefix means a deploy /
|
|
182
|
+
* destroy may be in flight for that stack.
|
|
183
|
+
*/
|
|
184
|
+
async function listAllLockKeys(stateBackend) {
|
|
185
|
+
return (await stateBackend.listRawKeys("")).filter((k) => k.endsWith(LOCK_FILE_SUFFIX));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* `{prefix}/{stack}/{region}/state.json` → `stack (region)`; legacy
|
|
189
|
+
* `{prefix}/{stack}/state.json` → `stack`. Pass {@link LOCK_FILE_SUFFIX} to
|
|
190
|
+
* describe lock keys.
|
|
191
|
+
*
|
|
192
|
+
* The shape heuristic below cannot be made exact, and it errs in BOTH
|
|
193
|
+
* directions -- a region it fails to recognise is reported AS the stack name,
|
|
194
|
+
* while a region-shaped STACK name in the legacy layout makes it report the
|
|
195
|
+
* key's PREFIX as the stack. Neither is safe: `cdkd gc`'s corrupt-state abort
|
|
196
|
+
* turns whichever answer it gets into a `cdkd state show ...` recovery
|
|
197
|
+
* command, so a wrong one is handed to the user as a command to run.
|
|
198
|
+
*
|
|
199
|
+
* `knownPrefix` narrows it where a caller can: a key with exactly one segment
|
|
200
|
+
* under a prefix we know is the legacy `{stack}` layout by construction,
|
|
201
|
+
* whatever the stack is named, so no shape test is needed there at all. That
|
|
202
|
+
* repairs the pre-existing two-letter case (`cdkd/db-prod-1/state.json`, which
|
|
203
|
+
* `main` describes as `cdkd (db-prod-1)`).
|
|
204
|
+
*
|
|
205
|
+
* **It is a narrowing, not a safety net for the pattern above.** A first cut
|
|
206
|
+
* treated it as one — widening `REGION_SEGMENT` to `{2,4}` and relying on this
|
|
207
|
+
* rule to undo the damage — and review measured that the damage is repo-wide
|
|
208
|
+
* while this rule reaches only keys under the ONE prefix a caller names. The
|
|
209
|
+
* listing deliberately spans the whole bucket, so foreign and nested prefixes
|
|
210
|
+
* are in scope and unreachable from here; six shapes regressed against `main`
|
|
211
|
+
* that way. The pattern must be right on its own.
|
|
212
|
+
*
|
|
213
|
+
* **It deliberately does NOT claim the two-segment case.** A first cut did, on
|
|
214
|
+
* the reasoning that at that depth the last segment must be the region -- and
|
|
215
|
+
* review found `--state-prefix cdkd/team-a`, which nests inside the default
|
|
216
|
+
* prefix, so `cdkd/team-a/MyStack/state.json` is a LEGACY key two segments
|
|
217
|
+
* deep and would have been mis-split as `team-a (MyStack)` where `main` said
|
|
218
|
+
* `MyStack`. Depth cannot tell that apart from a real `{stack}/{region}`; only
|
|
219
|
+
* the shape of the tail can, so the heuristic keeps that case.
|
|
220
|
+
*/
|
|
221
|
+
function describeStateKey(key, suffix = STATE_FILE_SUFFIX, knownPrefix) {
|
|
222
|
+
const body = key.slice(0, -suffix.length);
|
|
223
|
+
const prefix = knownPrefix?.replace(/\/+$/, "");
|
|
224
|
+
if (prefix !== void 0 && prefix !== "" && key.startsWith(`${prefix}/`)) {
|
|
225
|
+
const rest = body.slice(prefix.length + 1).split("/");
|
|
226
|
+
if (rest.length === LEGACY_SEGMENTS_UNDER_PREFIX && rest[0]) return rest[0];
|
|
227
|
+
}
|
|
228
|
+
const segments = body.split("/");
|
|
229
|
+
const last = segments[segments.length - 1] ?? key;
|
|
230
|
+
const secondLast = segments[segments.length - 2];
|
|
231
|
+
if (secondLast !== void 0 && secondLast !== "" && REGION_SEGMENT.test(last)) return `${secondLast} (${last})`;
|
|
232
|
+
return last;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
//#endregion
|
|
80
236
|
//#region src/cli/options.ts
|
|
81
237
|
/**
|
|
82
238
|
* Parse context key=value pairs from CLI arguments into a Record
|
|
@@ -152,7 +308,7 @@ const appOptions = [new Option("-a, --app <command>", "CDK app command (e.g., \"
|
|
|
152
308
|
* --state-bucket is optional: falls back to CDKD_STATE_BUCKET env var,
|
|
153
309
|
* then cdk.json context.cdkd.stateBucket
|
|
154
310
|
*/
|
|
155
|
-
const stateOptions = [new Option("--state-bucket <bucket>", "S3 bucket for state storage. Falls back to CDKD_STATE_BUCKET env or cdk.json"), new Option("--state-prefix <prefix>", "S3 key prefix for state files").default(
|
|
311
|
+
const stateOptions = [new Option("--state-bucket <bucket>", "S3 bucket for state storage. Falls back to CDKD_STATE_BUCKET env or cdk.json"), new Option("--state-prefix <prefix>", "S3 key prefix for state files").default(DEFAULT_STATE_PREFIX)];
|
|
156
312
|
/**
|
|
157
313
|
* Stack options
|
|
158
314
|
*/
|
|
@@ -1025,91 +1181,9 @@ function regionNeedsReconciliation(effective) {
|
|
|
1025
1181
|
return effective.region !== LEGACY_DEFAULT_REGION;
|
|
1026
1182
|
}
|
|
1027
1183
|
|
|
1028
|
-
//#endregion
|
|
1029
|
-
//#region src/cli/commands/state-file-keys.ts
|
|
1030
|
-
/**
|
|
1031
|
-
* Shared helpers for enumerating and describing cdkd state-bucket keys.
|
|
1032
|
-
*
|
|
1033
|
-
* Extracted from `bootstrap-destroy.ts` (issue #1010) so that `cdkd gc`
|
|
1034
|
-
* (issue #1012) reuses the exact same whole-bucket state-file discovery
|
|
1035
|
-
* instead of duplicating it — the "scan the WHOLE bucket, not just the
|
|
1036
|
-
* default `cdkd/` prefix" rule was a review blocker once already (PR #1018)
|
|
1037
|
-
* and must not drift between the two commands.
|
|
1038
|
-
*/
|
|
1039
|
-
/**
|
|
1040
|
-
* Every cdkd state file ends with this suffix, regardless of the
|
|
1041
|
-
* `--state-prefix` it was deployed under (`{prefix}/{stack}/{region}/state.json`,
|
|
1042
|
-
* or legacy `{prefix}/{stack}/state.json`).
|
|
1043
|
-
*/
|
|
1044
|
-
const STATE_FILE_SUFFIX = "/state.json";
|
|
1045
|
-
/**
|
|
1046
|
-
* Every cdkd stack lock ends with this suffix — the lock lives next to the
|
|
1047
|
-
* state file (`{prefix}/{stack}/{region}/lock.json`).
|
|
1048
|
-
*/
|
|
1049
|
-
const LOCK_FILE_SUFFIX = "/lock.json";
|
|
1050
|
-
/** `us-east-1` / `ap-northeast-1` / `us-gov-west-1` — a region-shaped segment. */
|
|
1051
|
-
const REGION_SEGMENT = /^[a-z]{2}(-[a-z]+)+-\d+$/;
|
|
1052
|
-
/**
|
|
1053
|
-
* List every state file in the bucket — the WHOLE bucket, not just the
|
|
1054
|
-
* default `cdkd/` prefix. Other commands accept `--state-prefix`, so live
|
|
1055
|
-
* stack state may exist under ANY prefix in this bucket; scoping this
|
|
1056
|
-
* listing to the default prefix would let reference scans and teardown
|
|
1057
|
-
* guards silently miss those stacks and delete live data.
|
|
1058
|
-
*/
|
|
1059
|
-
async function listAllStateKeys(stateBackend) {
|
|
1060
|
-
return (await stateBackend.listRawKeys("")).filter((k) => k.endsWith(STATE_FILE_SUFFIX));
|
|
1061
|
-
}
|
|
1062
|
-
/**
|
|
1063
|
-
* List every stack lock file in the bucket — same whole-bucket rule as
|
|
1064
|
-
* {@link listAllStateKeys}. A lock under ANY prefix means a deploy /
|
|
1065
|
-
* destroy may be in flight for that stack.
|
|
1066
|
-
*/
|
|
1067
|
-
async function listAllLockKeys(stateBackend) {
|
|
1068
|
-
return (await stateBackend.listRawKeys("")).filter((k) => k.endsWith(LOCK_FILE_SUFFIX));
|
|
1069
|
-
}
|
|
1070
|
-
/**
|
|
1071
|
-
* `{prefix}/{stack}/{region}/state.json` → `stack (region)`; legacy
|
|
1072
|
-
* `{prefix}/{stack}/state.json` → `stack`. The prefix is arbitrary (custom
|
|
1073
|
-
* `--state-prefix` values included), so the stack/region pair is derived
|
|
1074
|
-
* from the key's TAIL: the last segment is treated as a region only when
|
|
1075
|
-
* it is region-shaped. Pass {@link LOCK_FILE_SUFFIX} to describe lock keys.
|
|
1076
|
-
*/
|
|
1077
|
-
function describeStateKey(key, suffix = STATE_FILE_SUFFIX) {
|
|
1078
|
-
const segments = key.slice(0, -suffix.length).split("/");
|
|
1079
|
-
const last = segments[segments.length - 1] ?? key;
|
|
1080
|
-
const secondLast = segments[segments.length - 2];
|
|
1081
|
-
if (secondLast !== void 0 && REGION_SEGMENT.test(last)) return `${secondLast} (${last})`;
|
|
1082
|
-
return last;
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
1184
|
//#endregion
|
|
1086
1185
|
//#region src/cli/commands/bootstrap-destroy.ts
|
|
1087
1186
|
/**
|
|
1088
|
-
* `cdkd bootstrap --destroy` — teardown of cdkd-created account resources
|
|
1089
|
-
* (issue #1010, the reverse of `cdkd bootstrap` / `ensureAssetStorage`).
|
|
1090
|
-
*
|
|
1091
|
-
* Scope per invocation is ONE region's asset storage (`--region`, same
|
|
1092
|
-
* option the create side uses): empty + delete the asset bucket, force-
|
|
1093
|
-
* delete the container-asset ECR repo, then delete the region's bootstrap
|
|
1094
|
-
* marker LAST — the mirror image of the create side's marker-written-last
|
|
1095
|
-
* ordering, so a crash mid-teardown leaves the region still consistently
|
|
1096
|
-
* opted in (marker present → deploys keep hard-erroring at the missing
|
|
1097
|
-
* resources with a re-bootstrap hint, never a silent legacy fallback).
|
|
1098
|
-
*
|
|
1099
|
-
* The asset bucket / repo NAMES are read from the region's bootstrap
|
|
1100
|
-
* marker, NOT recomputed from the naming convention — the marker is the
|
|
1101
|
-
* source of truth for names (design §4.1), which keeps this teardown
|
|
1102
|
-
* compatible with custom asset-storage names (issue #1011).
|
|
1103
|
-
*
|
|
1104
|
-
* The state bucket is NOT deleted by default: it is the account's source
|
|
1105
|
-
* of truth. `--include-state-bucket` opts in, and even then the deletion
|
|
1106
|
-
* is refused while ANY stack state exists in the bucket or any OTHER
|
|
1107
|
-
* region is still opted in to asset storage (deleting their markers would
|
|
1108
|
-
* silently flip those regions back to legacy mode).
|
|
1109
|
-
*/
|
|
1110
|
-
/** S3 key prefix for state files — same fixed value the create side uses. */
|
|
1111
|
-
const STATE_PREFIX = "cdkd";
|
|
1112
|
-
/**
|
|
1113
1187
|
* Interactive confirmation for the teardown. Follows the repo's
|
|
1114
1188
|
* destructive-prompt convention (see `recreate-confirm-prompt.ts`, the
|
|
1115
1189
|
* same pattern family as `confirm-prompt.ts`): print the deletion plan as
|
|
@@ -1157,7 +1231,7 @@ async function scanStateReferences(stateBackend, names) {
|
|
|
1157
1231
|
for (const key of stateKeys) {
|
|
1158
1232
|
const body = await stateBackend.getRawObject(key);
|
|
1159
1233
|
if (body === null) continue;
|
|
1160
|
-
if (names.some((name) => body.includes(name))) referencing.push(describeStateKey(key));
|
|
1234
|
+
if (names.some((name) => body.includes(name))) referencing.push(describeStateKey(key, STATE_FILE_SUFFIX, DEFAULT_STATE_PREFIX));
|
|
1161
1235
|
}
|
|
1162
1236
|
return referencing;
|
|
1163
1237
|
}
|
|
@@ -1332,7 +1406,7 @@ async function bootstrapDestroyCommand(options) {
|
|
|
1332
1406
|
...options.profile && { profile: options.profile }
|
|
1333
1407
|
}), {
|
|
1334
1408
|
bucket: bucketName,
|
|
1335
|
-
prefix:
|
|
1409
|
+
prefix: DEFAULT_STATE_PREFIX
|
|
1336
1410
|
}, {
|
|
1337
1411
|
region: effective.region,
|
|
1338
1412
|
...options.profile && { profile: options.profile }
|
|
@@ -1361,7 +1435,7 @@ async function bootstrapDestroyCommand(options) {
|
|
|
1361
1435
|
});
|
|
1362
1436
|
const stateBackend = new S3StateBackend(markerS3Client, {
|
|
1363
1437
|
bucket: bucketName,
|
|
1364
|
-
prefix:
|
|
1438
|
+
prefix: DEFAULT_STATE_PREFIX
|
|
1365
1439
|
}, {
|
|
1366
1440
|
region,
|
|
1367
1441
|
...options.profile && { profile: options.profile }
|
|
@@ -1413,7 +1487,7 @@ async function bootstrapDestroyCommand(options) {
|
|
|
1413
1487
|
if (options.includeStateBucket) {
|
|
1414
1488
|
const stateKeys = await listAllStateKeys(stateBackend);
|
|
1415
1489
|
if (stateKeys.length > 0) {
|
|
1416
|
-
const listing = stateKeys.map((k) => ` - ${describeStateKey(k)} [${k}]`).join("\n");
|
|
1490
|
+
const listing = stateKeys.map((k) => ` - ${describeStateKey(k, STATE_FILE_SUFFIX, DEFAULT_STATE_PREFIX)} [${k}]`).join("\n");
|
|
1417
1491
|
throw new CdkdError(`Refusing to delete state bucket '${bucketName}': ${stateKeys.length} stack(s) still have state in it:\n${listing}\nDestroy every stack ('cdkd destroy' / 'cdkd state destroy') before deleting the state bucket.`, "STATE_BUCKET_NOT_EMPTY");
|
|
1418
1492
|
}
|
|
1419
1493
|
if (markerSiblings.otherRegions.length > 0) throw new CdkdError(`Refusing to delete state bucket '${bucketName}': region(s) ${markerSiblings.otherRegions.join(", ")} are still opted in to cdkd asset storage (their bootstrap markers live in this bucket). Run 'cdkd bootstrap --destroy --region <r>' for each first.`, "STATE_BUCKET_HOLDS_MARKERS");
|
|
@@ -1509,7 +1583,7 @@ async function bootstrapCommand(options) {
|
|
|
1509
1583
|
...options.profile && { profile: options.profile }
|
|
1510
1584
|
}), {
|
|
1511
1585
|
bucket: bucketName,
|
|
1512
|
-
prefix:
|
|
1586
|
+
prefix: DEFAULT_STATE_PREFIX
|
|
1513
1587
|
}, {
|
|
1514
1588
|
region: effective.region,
|
|
1515
1589
|
...options.profile && { profile: options.profile }
|
|
@@ -1610,7 +1684,7 @@ async function bootstrapCommand(options) {
|
|
|
1610
1684
|
});
|
|
1611
1685
|
const stateBackend = new S3StateBackend(markerS3Client, {
|
|
1612
1686
|
bucket: bucketName,
|
|
1613
|
-
prefix:
|
|
1687
|
+
prefix: DEFAULT_STATE_PREFIX
|
|
1614
1688
|
}, {
|
|
1615
1689
|
region,
|
|
1616
1690
|
...options.profile && { profile: options.profile }
|
|
@@ -64081,6 +64155,30 @@ function stripWalk(value) {
|
|
|
64081
64155
|
//#endregion
|
|
64082
64156
|
//#region src/cli/commands/drift.ts
|
|
64083
64157
|
/**
|
|
64158
|
+
* Route one outcome to the handler for its variant.
|
|
64159
|
+
*
|
|
64160
|
+
* The mechanism issue [#2135](https://github.com/go-to-k/cdkd/issues/2135) asks
|
|
64161
|
+
* for, and the reason every consumer in this file goes through it rather than
|
|
64162
|
+
* filtering on `o.kind === '...'`: `handlers` is a MAPPED TYPE over
|
|
64163
|
+
* `DriftOutcome['kind']`, so it must name EVERY variant. Adding a member to the
|
|
64164
|
+
* union turns every call site that has not been updated into a compile error
|
|
64165
|
+
* ("property '<kind>' is missing"), instead of leaving it to fall through to
|
|
64166
|
+
* whatever it happened to do before. A `.filter()` predicate cannot do that —
|
|
64167
|
+
* it keeps compiling and silently stops matching the new state.
|
|
64168
|
+
*
|
|
64169
|
+
* A handler that deliberately does nothing with a variant still has to be
|
|
64170
|
+
* written out, which is the point: the author of the next variant has to decide,
|
|
64171
|
+
* per consumer, what it means there.
|
|
64172
|
+
*
|
|
64173
|
+
* Exported, with {@link DriftOutcome} and {@link NotComparedCause}, so
|
|
64174
|
+
* `tests/unit/cli/drift-outcome.test-d.ts` can fence the mapped type itself:
|
|
64175
|
+
* relaxing it to a `Partial` record would silently un-fence every consumer in
|
|
64176
|
+
* this file while changing no runtime behaviour, so no runtime test can see it.
|
|
64177
|
+
*/
|
|
64178
|
+
function matchOutcome(outcome, handlers) {
|
|
64179
|
+
return handlers[outcome.kind](outcome);
|
|
64180
|
+
}
|
|
64181
|
+
/**
|
|
64084
64182
|
* Distinguish "drift detected" (exit 1) from "command crashed" (exit 1
|
|
64085
64183
|
* via the default handler) so the drift command can fail fast and the
|
|
64086
64184
|
* top-level handler doesn't add a stack trace for the expected case.
|
|
@@ -64105,18 +64203,19 @@ var DriftDetectedError = class DriftDetectedError extends CdkdError {
|
|
|
64105
64203
|
* `{{resolve:ssm-secure:...}}` token — a pre-existing population cdkd resolves
|
|
64106
64204
|
* for nobody, which can never clear on a re-run, and which this change did not
|
|
64107
64205
|
* create. Exiting non-zero for them would break `cdkd drift` in CI forever over
|
|
64108
|
-
* an unrelated defect, so the exit reads `
|
|
64109
|
-
*
|
|
64206
|
+
* an unrelated defect, so the exit reads the `refused` cause alone while the
|
|
64207
|
+
* report covers both. See `NotComparedCause` and `outcomeExitSignal`.
|
|
64110
64208
|
*
|
|
64111
64209
|
* WHY A NON-ZERO EXIT, and why it is a PRESERVATION rather than a new signal.
|
|
64112
64210
|
* Pre-#2108 that population resolved the reference in the WRONG region, the
|
|
64113
64211
|
* baseline could never equal what AWS held, and the resource was reported
|
|
64114
64212
|
* `drifted` -- so `cdkd drift` exited 1 and every CI gate keyed on the exit code
|
|
64115
64213
|
* fired. #2108 correctly stopped comparing against a foreign region's plaintext,
|
|
64116
|
-
* which made the same population report `
|
|
64117
|
-
* exit 0 and print `no drift detected` for a stack whose
|
|
64118
|
-
* properties were never looked at. Round 1 of that change
|
|
64119
|
-
* in `--json` (`notCompared`) and in the human report,
|
|
64214
|
+
* which made the same population report `notCompared`; without this the
|
|
64215
|
+
* command would exit 0 and print `no drift detected` for a stack whose
|
|
64216
|
+
* secret-bearing properties were never looked at. Round 1 of that change
|
|
64217
|
+
* surfaced the refusal in `--json` (`notCompared`) and in the human report,
|
|
64218
|
+
* but the exit code -- the
|
|
64120
64219
|
* signal most CI gates actually read -- still said "pass".
|
|
64121
64220
|
*
|
|
64122
64221
|
* WHY EXIT 2 RATHER THAN 1. `2` is this repo's established "work completed but
|
|
@@ -64153,32 +64252,56 @@ var DriftComparisonRefusedError = class DriftComparisonRefusedError extends Cdkd
|
|
|
64153
64252
|
}
|
|
64154
64253
|
};
|
|
64155
64254
|
/**
|
|
64156
|
-
* Every outcome on one report that was only PARTIALLY compared --
|
|
64157
|
-
* `
|
|
64255
|
+
* Every outcome on one report that was only PARTIALLY compared -- the
|
|
64256
|
+
* `notCompared` variant, plus a `drifted` one whose `notComparedCause` is set
|
|
64257
|
+
* (issues #2108 / #2135).
|
|
64258
|
+
*
|
|
64259
|
+
* ONE spelling, because TWO renderings must agree about the same run: the
|
|
64260
|
+
* `--json` `notCompared` roll-up and the human report's `PARTIALLY compared`
|
|
64261
|
+
* block (whose count is also what the `N of M fully checked` line subtracts).
|
|
64262
|
+
* A roll-up spelled per reader is how the payload and the human summary come to
|
|
64263
|
+
* disagree.
|
|
64158
64264
|
*
|
|
64159
|
-
*
|
|
64160
|
-
*
|
|
64161
|
-
* and the EXIT CODE. A predicate spelled per reader is how the payload and the
|
|
64162
|
-
* exit code come to disagree about the same run.
|
|
64265
|
+
* The EXIT CODE deliberately does NOT read this — see {@link outcomeExitSignal}
|
|
64266
|
+
* and {@link NotComparedCause} for why it is scoped to refusals alone.
|
|
64163
64267
|
*/
|
|
64164
64268
|
function notComparedOutcomes(report) {
|
|
64165
|
-
return report.outcomes.
|
|
64269
|
+
return report.outcomes.flatMap((o) => matchOutcome(o, {
|
|
64270
|
+
drifted: (d) => d.notComparedCause === void 0 ? [] : [d],
|
|
64271
|
+
notCompared: (n) => [n],
|
|
64272
|
+
clean: () => [],
|
|
64273
|
+
unsupported: () => [],
|
|
64274
|
+
skipped: () => []
|
|
64275
|
+
}));
|
|
64166
64276
|
}
|
|
64167
64277
|
/**
|
|
64168
|
-
*
|
|
64169
|
-
*
|
|
64170
|
-
*
|
|
64171
|
-
*
|
|
64172
|
-
*
|
|
64173
|
-
*
|
|
64174
|
-
*
|
|
64175
|
-
*
|
|
64176
|
-
*
|
|
64177
|
-
*
|
|
64178
|
-
*
|
|
64179
|
-
|
|
64180
|
-
|
|
64181
|
-
|
|
64278
|
+
* What one outcome contributes to the run's EXIT CODE (issues #2108 / #2135).
|
|
64279
|
+
*
|
|
64280
|
+
* The exit-code CONSUMER, kept as an exhaustive `matchOutcome` so a new outcome
|
|
64281
|
+
* variant cannot inherit `none` by omission — reporting a resource cdkd never
|
|
64282
|
+
* compared as a pass is precisely the round-2 defect #2135 exists to make
|
|
64283
|
+
* impossible.
|
|
64284
|
+
*
|
|
64285
|
+
* `refused` is a STRICT SUBSET of what {@link notComparedOutcomes} rolls up, and
|
|
64286
|
+
* the asymmetry is the point: the REPORT covers everything that was not
|
|
64287
|
+
* compared, because as information both causes matter equally, while the exit
|
|
64288
|
+
* code covers only the population #2108 created. Exiting non-zero on
|
|
64289
|
+
* `unresolvedToken` would break `cdkd drift` in CI forever for every stack
|
|
64290
|
+
* holding an `{{resolve:ssm-secure:...}}` reference — permanent, unclearable,
|
|
64291
|
+
* and unrelated. See {@link NotComparedCause}.
|
|
64292
|
+
*
|
|
64293
|
+
* A `drifted` outcome reports `drifted` even when its own comparison was
|
|
64294
|
+
* refused: drift is the stronger, actionable signal and the caller ranks it
|
|
64295
|
+
* first, so a consumer gating on `1` loses nothing it gets today.
|
|
64296
|
+
*/
|
|
64297
|
+
function outcomeExitSignal(outcome) {
|
|
64298
|
+
return matchOutcome(outcome, {
|
|
64299
|
+
drifted: () => "drifted",
|
|
64300
|
+
notCompared: (n) => n.notComparedCause === "refused" ? "refused" : "none",
|
|
64301
|
+
clean: () => "none",
|
|
64302
|
+
unsupported: () => "none",
|
|
64303
|
+
skipped: () => "none"
|
|
64304
|
+
});
|
|
64182
64305
|
}
|
|
64183
64306
|
/**
|
|
64184
64307
|
* `cdkd drift [<stack>...]` command implementation.
|
|
@@ -64248,8 +64371,9 @@ async function driftCommand(stacks, options) {
|
|
|
64248
64371
|
}
|
|
64249
64372
|
if (options.json) writeJsonReport(reports);
|
|
64250
64373
|
else writeHumanReport(reports);
|
|
64251
|
-
const
|
|
64252
|
-
const
|
|
64374
|
+
const signals = reports.flatMap((r) => r.outcomes.map(outcomeExitSignal));
|
|
64375
|
+
const drifted = signals.includes("drifted");
|
|
64376
|
+
const anyRefused = signals.includes("refused");
|
|
64253
64377
|
if (!options.accept && !options.revert) {
|
|
64254
64378
|
if (drifted) throw new DriftDetectedError();
|
|
64255
64379
|
if (anyRefused) throw new DriftComparisonRefusedError();
|
|
@@ -64953,14 +65077,17 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
|
|
|
64953
65077
|
unionWalkObjects: useObserved,
|
|
64954
65078
|
unorderedPaths
|
|
64955
65079
|
}), secrets, secretResolutionFailed ? seededSecretPaths : secretPaths);
|
|
64956
|
-
const
|
|
64957
|
-
|
|
64958
|
-
|
|
64959
|
-
kind: "clean",
|
|
65080
|
+
const notComparedCause = secretResolutionFailed ? "refused" : unresolvedTokens.size > 0 ? "unresolvedToken" : void 0;
|
|
65081
|
+
if (reported.changes.length === 0) if (notComparedCause !== void 0) outcomes.push({
|
|
65082
|
+
kind: "notCompared",
|
|
64960
65083
|
logicalId,
|
|
64961
65084
|
resourceType: resource.resourceType,
|
|
64962
|
-
|
|
64963
|
-
|
|
65085
|
+
notComparedCause
|
|
65086
|
+
});
|
|
65087
|
+
else outcomes.push({
|
|
65088
|
+
kind: "clean",
|
|
65089
|
+
logicalId,
|
|
65090
|
+
resourceType: resource.resourceType
|
|
64964
65091
|
});
|
|
64965
65092
|
else outcomes.push({
|
|
64966
65093
|
kind: "drifted",
|
|
@@ -64969,8 +65096,7 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
|
|
|
64969
65096
|
...reported,
|
|
64970
65097
|
awsProperties: aws,
|
|
64971
65098
|
secrets,
|
|
64972
|
-
|
|
64973
|
-
comparisonRefused
|
|
65099
|
+
notComparedCause
|
|
64974
65100
|
});
|
|
64975
65101
|
}
|
|
64976
65102
|
return {
|
|
@@ -65067,7 +65193,13 @@ async function runAccept(reports, stateBackend, stateConfig, awsClients, options
|
|
|
65067
65193
|
const lockManager = new LockManager(awsClients.s3, stateConfig);
|
|
65068
65194
|
const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
|
|
65069
65195
|
for (const report of reports) {
|
|
65070
|
-
const driftedOutcomes = report.outcomes.
|
|
65196
|
+
const driftedOutcomes = report.outcomes.flatMap((o) => matchOutcome(o, {
|
|
65197
|
+
drifted: (d) => [d],
|
|
65198
|
+
clean: () => [],
|
|
65199
|
+
notCompared: () => [],
|
|
65200
|
+
unsupported: () => [],
|
|
65201
|
+
skipped: () => []
|
|
65202
|
+
}));
|
|
65071
65203
|
if (driftedOutcomes.length === 0) continue;
|
|
65072
65204
|
await lockManager.acquireLock(report.stackName, report.region, owner, "drift-accept");
|
|
65073
65205
|
try {
|
|
@@ -65685,7 +65817,13 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
|
|
|
65685
65817
|
let totalSucceeded = 0;
|
|
65686
65818
|
let totalUnresolvable = 0;
|
|
65687
65819
|
for (const report of reports) {
|
|
65688
|
-
const driftedOutcomes = report.outcomes.
|
|
65820
|
+
const driftedOutcomes = report.outcomes.flatMap((o) => matchOutcome(o, {
|
|
65821
|
+
drifted: (d) => [d],
|
|
65822
|
+
clean: () => [],
|
|
65823
|
+
notCompared: () => [],
|
|
65824
|
+
unsupported: () => [],
|
|
65825
|
+
skipped: () => []
|
|
65826
|
+
}));
|
|
65689
65827
|
if (driftedOutcomes.length === 0) continue;
|
|
65690
65828
|
await lockManager.acquireLock(report.stackName, report.region, owner, "drift-revert");
|
|
65691
65829
|
const narrowedByLogicalId = /* @__PURE__ */ new Map();
|
|
@@ -65836,7 +65974,13 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
|
|
|
65836
65974
|
*/
|
|
65837
65975
|
function printAcceptPlan(reports) {
|
|
65838
65976
|
for (const report of reports) {
|
|
65839
|
-
const drifted = report.outcomes.
|
|
65977
|
+
const drifted = report.outcomes.flatMap((o) => matchOutcome(o, {
|
|
65978
|
+
drifted: (d) => [d],
|
|
65979
|
+
clean: () => [],
|
|
65980
|
+
notCompared: () => [],
|
|
65981
|
+
unsupported: () => [],
|
|
65982
|
+
skipped: () => []
|
|
65983
|
+
}));
|
|
65840
65984
|
if (drifted.length === 0) continue;
|
|
65841
65985
|
process.stdout.write(`\nPlan (--accept): update cdkd state for ${report.stackName} (${report.region}):\n`);
|
|
65842
65986
|
for (const o of drifted) {
|
|
@@ -65859,7 +66003,13 @@ function printAcceptPlan(reports) {
|
|
|
65859
66003
|
*/
|
|
65860
66004
|
function printRevertPlan(reports) {
|
|
65861
66005
|
for (const report of reports) {
|
|
65862
|
-
const drifted = report.outcomes.
|
|
66006
|
+
const drifted = report.outcomes.flatMap((o) => matchOutcome(o, {
|
|
66007
|
+
drifted: (d) => [d],
|
|
66008
|
+
clean: () => [],
|
|
66009
|
+
notCompared: () => [],
|
|
66010
|
+
unsupported: () => [],
|
|
66011
|
+
skipped: () => []
|
|
66012
|
+
}));
|
|
65863
66013
|
if (drifted.length === 0) continue;
|
|
65864
66014
|
process.stdout.write(`\nPlan (--revert): push cdkd state values back into AWS for ${report.stackName} (${report.region}):\n`);
|
|
65865
66015
|
for (const o of drifted) {
|
|
@@ -65869,7 +66019,7 @@ function printRevertPlan(reports) {
|
|
|
65869
66019
|
const stateResource = report.state.resources[o.logicalId];
|
|
65870
66020
|
if (stateResource) {
|
|
65871
66021
|
const preserved = findRevertPreservedTagKeys(o.changes, stateResource.observedProperties ?? stateResource.properties ?? {}, o.awsProperties);
|
|
65872
|
-
const cannotMaskKeys = o.
|
|
66022
|
+
const cannotMaskKeys = o.notComparedCause !== void 0;
|
|
65873
66023
|
if (cannotMaskKeys && preserved.length > 0) process.stdout.write(` ! ${preserved.length} AWS-authored tag(s) will be preserved, but cdkd could not resolve this resource's dynamic reference(s), so their names are withheld — they come from the live readback and cannot be checked for secrets without them.\n`);
|
|
65874
66024
|
if (!cannotMaskKeys && preserved.length > 0) {
|
|
65875
66025
|
const tagWord = preserved.length === 1 ? "tag" : "tags";
|
|
@@ -65881,7 +66031,7 @@ function printRevertPlan(reports) {
|
|
|
65881
66031
|
}
|
|
65882
66032
|
if (stateResource && stateResource.observedProperties === void 0) {
|
|
65883
66033
|
const unbaselined = findRevertUnbaselinedAwsKeys(o.changes, stateResource.properties ?? {}, o.awsProperties);
|
|
65884
|
-
if (o.
|
|
66034
|
+
if (o.notComparedCause !== void 0 && unbaselined.length > 0) process.stdout.write(` ! ${unbaselined.length} AWS-authored value(s) will be left untouched, but cdkd could not resolve this resource's dynamic reference(s), so their paths are withheld — they come from the live readback and cannot be checked for secrets without them.\n`);
|
|
65885
66035
|
else if (unbaselined.length > 0) {
|
|
65886
66036
|
const word = unbaselined.length === 1 ? "value" : "values";
|
|
65887
66037
|
process.stdout.write(` ! this resource has no observed-capture baseline, so the revert pushes the raw TEMPLATE and LEAVES ${unbaselined.length} AWS-authored ${word} untouched:\n`);
|
|
@@ -65923,28 +66073,44 @@ async function confirmPrompt$5(prompt) {
|
|
|
65923
66073
|
}
|
|
65924
66074
|
function writeJsonReport(reports) {
|
|
65925
66075
|
const payload = reports.map((r) => {
|
|
65926
|
-
const drifted =
|
|
65927
|
-
|
|
65928
|
-
|
|
65929
|
-
|
|
65930
|
-
|
|
65931
|
-
|
|
65932
|
-
|
|
65933
|
-
|
|
65934
|
-
|
|
65935
|
-
|
|
65936
|
-
|
|
66076
|
+
const drifted = [];
|
|
66077
|
+
const clean = [];
|
|
66078
|
+
const notSupported = [];
|
|
66079
|
+
const skipped = [];
|
|
66080
|
+
for (const o of r.outcomes) matchOutcome(o, {
|
|
66081
|
+
drifted: (d) => {
|
|
66082
|
+
drifted.push({
|
|
66083
|
+
logicalId: d.logicalId,
|
|
66084
|
+
type: d.resourceType,
|
|
66085
|
+
changes: d.changes,
|
|
66086
|
+
referencesUnresolved: d.notComparedCause !== void 0
|
|
66087
|
+
});
|
|
66088
|
+
},
|
|
66089
|
+
clean: (c) => {
|
|
66090
|
+
clean.push({
|
|
66091
|
+
logicalId: c.logicalId,
|
|
66092
|
+
type: c.resourceType,
|
|
66093
|
+
referencesUnresolved: false
|
|
66094
|
+
});
|
|
66095
|
+
},
|
|
66096
|
+
notCompared: () => {},
|
|
66097
|
+
unsupported: (u) => {
|
|
66098
|
+
notSupported.push({
|
|
66099
|
+
logicalId: u.logicalId,
|
|
66100
|
+
type: u.resourceType
|
|
66101
|
+
});
|
|
66102
|
+
},
|
|
66103
|
+
skipped: (sk) => {
|
|
66104
|
+
skipped.push({
|
|
66105
|
+
logicalId: sk.logicalId,
|
|
66106
|
+
type: sk.resourceType
|
|
66107
|
+
});
|
|
66108
|
+
}
|
|
66109
|
+
});
|
|
65937
66110
|
const notCompared = notComparedOutcomes(r).map((o) => ({
|
|
65938
66111
|
logicalId: o.logicalId,
|
|
65939
|
-
type: o.resourceType
|
|
65940
|
-
|
|
65941
|
-
const notSupported = r.outcomes.filter((o) => o.kind === "unsupported").map((o) => ({
|
|
65942
|
-
logicalId: o.logicalId,
|
|
65943
|
-
type: o.resourceType
|
|
65944
|
-
}));
|
|
65945
|
-
const skipped = r.outcomes.filter((o) => o.kind === "skipped").map((o) => ({
|
|
65946
|
-
logicalId: o.logicalId,
|
|
65947
|
-
type: o.resourceType
|
|
66112
|
+
type: o.resourceType,
|
|
66113
|
+
referencesUnresolved: true
|
|
65948
66114
|
}));
|
|
65949
66115
|
return {
|
|
65950
66116
|
stack: r.stackName,
|
|
@@ -65960,10 +66126,27 @@ function writeJsonReport(reports) {
|
|
|
65960
66126
|
}
|
|
65961
66127
|
function writeHumanReport(reports) {
|
|
65962
66128
|
for (const report of reports) {
|
|
65963
|
-
const drifted =
|
|
65964
|
-
const unsupported =
|
|
65965
|
-
|
|
65966
|
-
const
|
|
66129
|
+
const drifted = [];
|
|
66130
|
+
const unsupported = [];
|
|
66131
|
+
let inspectedCount = 0;
|
|
66132
|
+
for (const o of report.outcomes) matchOutcome(o, {
|
|
66133
|
+
drifted: (d) => {
|
|
66134
|
+
drifted.push(d);
|
|
66135
|
+
inspectedCount += 1;
|
|
66136
|
+
},
|
|
66137
|
+
unsupported: (u) => {
|
|
66138
|
+
unsupported.push(u);
|
|
66139
|
+
inspectedCount += 1;
|
|
66140
|
+
},
|
|
66141
|
+
skipped: () => {},
|
|
66142
|
+
clean: () => {
|
|
66143
|
+
inspectedCount += 1;
|
|
66144
|
+
},
|
|
66145
|
+
notCompared: () => {
|
|
66146
|
+
inspectedCount += 1;
|
|
66147
|
+
}
|
|
66148
|
+
});
|
|
66149
|
+
const inspected = inspectedCount;
|
|
65967
66150
|
const notCompared = notComparedOutcomes(report);
|
|
65968
66151
|
const checked = inspected - notCompared.length;
|
|
65969
66152
|
if (drifted.length === 0) if (notCompared.length > 0) process.stdout.write(`⚠ ${report.stackName} (${report.region}): no drift detected, but ${checked} of ${inspected} resource${inspected === 1 ? "" : "s"} fully checked (${notCompared.length} only partially compared, ${unsupported.length} unsupported)\n`);
|
|
@@ -71646,8 +71829,8 @@ async function rollbackCommand(stackArg, options) {
|
|
|
71646
71829
|
try {
|
|
71647
71830
|
await setup.lockManager.acquireLockWithRetry(stackName, region, void 0, "rollback");
|
|
71648
71831
|
} catch (error) {
|
|
71649
|
-
unforwardSigterm();
|
|
71650
71832
|
process.removeListener("SIGINT", sigintHandler);
|
|
71833
|
+
unforwardSigterm();
|
|
71651
71834
|
throw error;
|
|
71652
71835
|
}
|
|
71653
71836
|
try {
|
|
@@ -71792,11 +71975,14 @@ async function rollbackCommand(stackArg, options) {
|
|
|
71792
71975
|
if (totalWarnings > 0) throw new PartialFailureError(`Rollback completed with ${totalWarnings} skipped/unrecoverable operation(s) (see warnings above).`);
|
|
71793
71976
|
logger.info(`\nRollback of '${stackName}' (${region}) complete.`);
|
|
71794
71977
|
} finally {
|
|
71795
|
-
|
|
71796
|
-
|
|
71797
|
-
|
|
71798
|
-
|
|
71799
|
-
}
|
|
71978
|
+
try {
|
|
71979
|
+
await setup.lockManager.releaseLock(stackName, region).catch((err) => {
|
|
71980
|
+
logger.warn(`Failed to release lock for '${stackName}' (${region}): ${err instanceof Error ? err.message : String(err)}`);
|
|
71981
|
+
});
|
|
71982
|
+
} finally {
|
|
71983
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
71984
|
+
unforwardSigterm();
|
|
71985
|
+
}
|
|
71800
71986
|
}
|
|
71801
71987
|
} finally {
|
|
71802
71988
|
if (stackAwsClients) {
|
|
@@ -72919,7 +73105,7 @@ async function scanReferencedAssets(stateBackend, marker, logger) {
|
|
|
72919
73105
|
try {
|
|
72920
73106
|
parsed = JSON.parse(body);
|
|
72921
73107
|
} catch (error) {
|
|
72922
|
-
const described = describeStateKey(key);
|
|
73108
|
+
const described = describeStateKey(key, STATE_FILE_SUFFIX, DEFAULT_STATE_PREFIX);
|
|
72923
73109
|
const regionMatch = /^(\S+) \((\S+)\)$/.exec(described);
|
|
72924
73110
|
throw new CdkdError(`State file '${key}' is not valid JSON — aborting: gc must know every referenced asset before deleting anything, and this file's references are unreadable. Repair or remove the corrupt state file ('${regionMatch ? `cdkd state show ${regionMatch[1]} --stack-region ${regionMatch[2]}` : `cdkd state show ${described}`}' to inspect), then re-run.`, "GC_STATE_UNREADABLE", error);
|
|
72925
73111
|
}
|
|
@@ -73124,7 +73310,7 @@ async function gcCommand(options) {
|
|
|
73124
73310
|
});
|
|
73125
73311
|
const stateBackend = new S3StateBackend(markerS3Client, {
|
|
73126
73312
|
bucket: bucketName,
|
|
73127
|
-
prefix:
|
|
73313
|
+
prefix: DEFAULT_STATE_PREFIX
|
|
73128
73314
|
}, {
|
|
73129
73315
|
region: effective.region,
|
|
73130
73316
|
...options.profile && { profile: options.profile }
|
|
@@ -73170,7 +73356,7 @@ async function gcCommand(options) {
|
|
|
73170
73356
|
const marker = parseBootstrapMarker(markerBody, resolvedMarkerKey);
|
|
73171
73357
|
const lockKeys = await listAllLockKeys(stateBackend);
|
|
73172
73358
|
if (lockKeys.length > 0) {
|
|
73173
|
-
const listing = lockKeys.map((k) => ` - ${describeStateKey(k, LOCK_FILE_SUFFIX)} [${k}]`).join("\n");
|
|
73359
|
+
const listing = lockKeys.map((k) => ` - ${describeStateKey(k, LOCK_FILE_SUFFIX, DEFAULT_STATE_PREFIX)} [${k}]`).join("\n");
|
|
73174
73360
|
throw new CdkdError(`Refusing to gc while ${lockKeys.length} stack(s) hold an active lock (a deploy in flight may have published assets whose state write has not landed yet):\n${listing}\nWait for the operation(s) to finish — or clear a stale lock with 'cdkd force-unlock <stack>' — then re-run 'cdkd gc'.`, "GC_LOCKED");
|
|
73175
73361
|
}
|
|
73176
73362
|
const refs = await scanReferencedAssets(stateBackend, marker, logger);
|
|
@@ -84972,7 +85158,7 @@ async function promptConfirm(message) {
|
|
|
84972
85158
|
* callers get the standard exit-code-2 routing.
|
|
84973
85159
|
*/
|
|
84974
85160
|
function createMigrateCommand() {
|
|
84975
|
-
return new Command("migrate").description("Adopt a plain (non-CDK) CloudFormation stack into a cdkd-managed CDK app. Generates new CDK code via upstream `cdk migrate`, builds a logical-ID mapping between the source CFn template and the synth template, writes cdkd state, and (optionally) retires the source CFn stack. AWS resources are never modified.").argument("[stack]", "Source CFn stack name. Alias for --from-cfn-stack.").addOption(new Option("--from-cfn-stack <name>", "Source CloudFormation stack name to adopt. Required (or pass positionally).")).addOption(new Option("--output-dir <dir>", "Directory to write the generated CDK app to. Defaults to <cwd>/<CfnStackName>.")).addOption(new Option("--language <choice>", "Generated code language. v1: typescript only.").choices(["typescript"]).default("typescript")).addOption(new Option("--region <region>", "AWS region. Defaults to AWS_REGION env / profile.")).addOption(new Option("--account <id>", "AWS account ID. Auto-detected via STS when omitted.")).addOption(new Option("--retire-cfn-stack", "After cdkd state is written, inject DeletionPolicy=Retain on every resource in the source CFn stack and DeleteStack. AWS resources stay; the CFn stack record is gone. Off by default.").default(false)).addOption(new Option("--filter <key=value>", "Pass-through to `cdk migrate --filter` for resource subsetting. Repeatable.").argParser((value, previous) => [...previous ?? [], value]).default([])).addOption(new Option("--skip-install", "Skip `npm install` after codegen.").default(false)).addOption(new Option("--skip-synth", "Skip `cdk synth` (does NOT write cdkd state). Mutually exclusive with --retire-cfn-stack.").default(false)).addOption(new Option("--dry-run", "Print the import plan without writing state or retiring the CFn stack. Mutually exclusive with --retire-cfn-stack.").default(false)).addOption(new Option("-y, --yes", "Auto-confirm the import + retirement prompts.").default(false)).addOption(new Option("--cdk-bin <path>", "Override the `cdk` binary path.")).addOption(new Option("--resource-mapping <file>", `Path to a JSON file of {sourceLogicalId: synthLogicalId} overrides. Same shape as the auto-written ${RESOURCE_MAPPING_FILENAME}.`)).addOption(new Option("--state-bucket <name>", "cdkd state bucket. Defaults to cdkd-state-<accountId>.")).addOption(new Option("--state-prefix <prefix>", "cdkd state prefix inside the bucket.").default(
|
|
85161
|
+
return new Command("migrate").description("Adopt a plain (non-CDK) CloudFormation stack into a cdkd-managed CDK app. Generates new CDK code via upstream `cdk migrate`, builds a logical-ID mapping between the source CFn template and the synth template, writes cdkd state, and (optionally) retires the source CFn stack. AWS resources are never modified.").argument("[stack]", "Source CFn stack name. Alias for --from-cfn-stack.").addOption(new Option("--from-cfn-stack <name>", "Source CloudFormation stack name to adopt. Required (or pass positionally).")).addOption(new Option("--output-dir <dir>", "Directory to write the generated CDK app to. Defaults to <cwd>/<CfnStackName>.")).addOption(new Option("--language <choice>", "Generated code language. v1: typescript only.").choices(["typescript"]).default("typescript")).addOption(new Option("--region <region>", "AWS region. Defaults to AWS_REGION env / profile.")).addOption(new Option("--account <id>", "AWS account ID. Auto-detected via STS when omitted.")).addOption(new Option("--retire-cfn-stack", "After cdkd state is written, inject DeletionPolicy=Retain on every resource in the source CFn stack and DeleteStack. AWS resources stay; the CFn stack record is gone. Off by default.").default(false)).addOption(new Option("--filter <key=value>", "Pass-through to `cdk migrate --filter` for resource subsetting. Repeatable.").argParser((value, previous) => [...previous ?? [], value]).default([])).addOption(new Option("--skip-install", "Skip `npm install` after codegen.").default(false)).addOption(new Option("--skip-synth", "Skip `cdk synth` (does NOT write cdkd state). Mutually exclusive with --retire-cfn-stack.").default(false)).addOption(new Option("--dry-run", "Print the import plan without writing state or retiring the CFn stack. Mutually exclusive with --retire-cfn-stack.").default(false)).addOption(new Option("-y, --yes", "Auto-confirm the import + retirement prompts.").default(false)).addOption(new Option("--cdk-bin <path>", "Override the `cdk` binary path.")).addOption(new Option("--resource-mapping <file>", `Path to a JSON file of {sourceLogicalId: synthLogicalId} overrides. Same shape as the auto-written ${RESOURCE_MAPPING_FILENAME}.`)).addOption(new Option("--state-bucket <name>", "cdkd state bucket. Defaults to cdkd-state-<accountId>.")).addOption(new Option("--state-prefix <prefix>", "cdkd state prefix inside the bucket.").default(DEFAULT_STATE_PREFIX)).addOption(new Option("--profile <name>", "AWS profile name.")).addOption(new Option("--role-arn <arn>", "IAM role to assume before any AWS call.")).addOption(new Option("--verbose", "Enable debug-level logging.").default(false)).action(withErrorHandling(migrateCommandAction));
|
|
84976
85162
|
}
|
|
84977
85163
|
|
|
84978
85164
|
//#endregion
|
|
@@ -84995,7 +85181,7 @@ function createMigrateCommand() {
|
|
|
84995
85181
|
*/
|
|
84996
85182
|
function buildProgram() {
|
|
84997
85183
|
const program = new Command();
|
|
84998
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.
|
|
85184
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.31");
|
|
84999
85185
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
85000
85186
|
const { profile } = actionCommand.optsWithGlobals();
|
|
85001
85187
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|