@go-to-k/cdkd 0.248.0 → 0.249.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.
@@ -1,683 +0,0 @@
1
- import { n as getLogger } from "./logger-BzO-joNR.js";
2
- import { ModifyInstanceAttributeCommand } from "@aws-sdk/client-ec2";
3
-
4
- //#region src/utils/error-handler.ts
5
- /**
6
- * Base error class for cdkd
7
- */
8
- var CdkdError = class CdkdError extends Error {
9
- code;
10
- cause;
11
- constructor(message, code, cause) {
12
- super(message);
13
- this.code = code;
14
- this.cause = cause;
15
- this.name = "CdkdError";
16
- Object.setPrototypeOf(this, CdkdError.prototype);
17
- }
18
- };
19
- /**
20
- * State management errors
21
- */
22
- var StateError = class StateError extends CdkdError {
23
- constructor(message, cause) {
24
- super(message, "STATE_ERROR", cause);
25
- this.name = "StateError";
26
- Object.setPrototypeOf(this, StateError.prototype);
27
- }
28
- };
29
- /**
30
- * Lock acquisition errors
31
- */
32
- var LockError = class LockError extends CdkdError {
33
- constructor(message, cause) {
34
- super(message, "LOCK_ERROR", cause);
35
- this.name = "LockError";
36
- Object.setPrototypeOf(this, LockError.prototype);
37
- }
38
- };
39
- /**
40
- * Synthesis errors
41
- */
42
- var SynthesisError = class SynthesisError extends CdkdError {
43
- constructor(message, cause) {
44
- super(message, "SYNTHESIS_ERROR", cause);
45
- this.name = "SynthesisError";
46
- Object.setPrototypeOf(this, SynthesisError.prototype);
47
- }
48
- };
49
- /**
50
- * Asset errors
51
- */
52
- var AssetError = class AssetError extends CdkdError {
53
- constructor(message, cause) {
54
- super(message, "ASSET_ERROR", cause);
55
- this.name = "AssetError";
56
- Object.setPrototypeOf(this, AssetError.prototype);
57
- }
58
- };
59
- /**
60
- * Local-invoke `docker build` failures.
61
- *
62
- * Surfaces the stderr captured from `docker build` so the user can
63
- * re-run the same command directly to debug Dockerfile syntax errors
64
- * or missing build context. Used by `src/local/docker-image-builder.ts`
65
- * (PR 5) for container Lambdas; the parallel `AssetError` covers the
66
- * `cdkd publish-assets` / `cdkd deploy` build path. Kept distinct from
67
- * `AssetError` so `cdkd local invoke` failures don't show up under the
68
- * "asset" error class.
69
- */
70
- var LocalInvokeBuildError = class LocalInvokeBuildError extends CdkdError {
71
- constructor(message, cause) {
72
- super(message, "LOCAL_INVOKE_BUILD_ERROR", cause);
73
- this.name = "LocalInvokeBuildError";
74
- Object.setPrototypeOf(this, LocalInvokeBuildError.prototype);
75
- }
76
- };
77
- /**
78
- * Resource provisioning errors
79
- */
80
- var ProvisioningError = class ProvisioningError extends CdkdError {
81
- resourceType;
82
- logicalId;
83
- physicalId;
84
- constructor(message, resourceType, logicalId, physicalId, cause) {
85
- super(message, "PROVISIONING_ERROR", cause);
86
- this.resourceType = resourceType;
87
- this.logicalId = logicalId;
88
- this.physicalId = physicalId;
89
- this.name = "ProvisioningError";
90
- Object.setPrototypeOf(this, ProvisioningError.prototype);
91
- }
92
- };
93
- /**
94
- * Resource provisioning timeout errors (per-resource wall-clock deadline).
95
- *
96
- * Thrown by `withResourceDeadline` when a single CREATE / UPDATE / DELETE
97
- * operation exceeds the user-configured `--resource-timeout`. The deploy
98
- * engine catches this, wraps it in {@link ProvisioningError}, and lets the
99
- * existing failure path (interrupt siblings → pre-rollback save → rollback
100
- * unless `--no-rollback`) take over.
101
- *
102
- * The message intentionally names the resource, type, region, elapsed time
103
- * and operation, plus how to override the default. Long-running providers
104
- * (e.g. Custom Resource: 1h polling cap) self-report their needed budget
105
- * via `getMinResourceTimeoutMs()`, so the user only needs a per-type
106
- * override (`--resource-timeout TYPE=DURATION`) when they want to bump a
107
- * specific non-self-reporting type or shorten a self-reported one.
108
- */
109
- var ResourceTimeoutError = class ResourceTimeoutError extends CdkdError {
110
- logicalId;
111
- resourceType;
112
- region;
113
- elapsedMs;
114
- operation;
115
- timeoutMs;
116
- constructor(logicalId, resourceType, region, elapsedMs, operation, timeoutMs) {
117
- const elapsedLabel = formatDuration(elapsedMs);
118
- const timeoutLabel = formatDuration(timeoutMs);
119
- super(`Resource ${logicalId} (${resourceType}) in ${region} timed out after ${timeoutLabel} during ${operation} (elapsed ${elapsedLabel}).\nThis may indicate a stuck Cloud Control polling loop, hung Custom Resource, or
120
- slow ENI provisioning. Re-run with --resource-timeout ${resourceType}=<DURATION>\nto bump the budget for this resource type only, or --verbose to see the
121
- underlying provider activity.`, "RESOURCE_TIMEOUT");
122
- this.logicalId = logicalId;
123
- this.resourceType = resourceType;
124
- this.region = region;
125
- this.elapsedMs = elapsedMs;
126
- this.operation = operation;
127
- this.timeoutMs = timeoutMs;
128
- this.name = "ResourceTimeoutError";
129
- Object.setPrototypeOf(this, ResourceTimeoutError.prototype);
130
- }
131
- };
132
- /**
133
- * Format a duration in milliseconds as a short human-readable label
134
- * (`30m`, `1h30m`, `45s`). Used by {@link ResourceTimeoutError} so the
135
- * error message stays compact.
136
- */
137
- function formatDuration(ms) {
138
- if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
139
- const totalMinutes = Math.round(ms / 6e4);
140
- if (totalMinutes < 60) return `${totalMinutes}m`;
141
- const hours = Math.floor(totalMinutes / 60);
142
- const minutes = totalMinutes % 60;
143
- return minutes === 0 ? `${hours}h` : `${hours}h${minutes}m`;
144
- }
145
- /**
146
- * Dependency resolution errors
147
- */
148
- var DependencyError = class DependencyError extends CdkdError {
149
- constructor(message, cause) {
150
- super(message, "DEPENDENCY_ERROR", cause);
151
- this.name = "DependencyError";
152
- Object.setPrototypeOf(this, DependencyError.prototype);
153
- }
154
- };
155
- /**
156
- * Configuration errors
157
- */
158
- var ConfigError = class ConfigError extends CdkdError {
159
- constructor(message, cause) {
160
- super(message, "CONFIG_ERROR", cause);
161
- this.name = "ConfigError";
162
- Object.setPrototypeOf(this, ConfigError.prototype);
163
- }
164
- };
165
- /**
166
- * Signals a partial-failure outcome that should map to exit code 2 (not 1).
167
- *
168
- * Used by `cdkd destroy` and `cdkd state destroy` when one or more
169
- * per-resource deletes failed but the overall command finished its work
170
- * (state.json is preserved, the rest of the stack was deleted, and the
171
- * user can re-run to clean up the remaining resources).
172
- *
173
- * Exit code conventions:
174
- * - 0: command completed successfully, no resources left in error state.
175
- * - 1: command-level failure (auth error, bad arguments, synth crash,
176
- * unhandled exception). Default for any thrown error.
177
- * - 2: partial failure — work completed but some resources are still in
178
- * an error state. Re-running typically resolves it. Documented in
179
- * README's "Exit codes" section.
180
- *
181
- * `handleError` recognizes this class via `instanceof` and uses its
182
- * `exitCode` instead of the default 1.
183
- */
184
- var PartialFailureError = class PartialFailureError extends CdkdError {
185
- exitCode = 2;
186
- constructor(message, cause) {
187
- super(message, "PARTIAL_FAILURE", cause);
188
- this.name = "PartialFailureError";
189
- Object.setPrototypeOf(this, PartialFailureError.prototype);
190
- }
191
- };
192
- /**
193
- * Signals that a provider cannot perform an in-place `update` for a
194
- * resource type — most commonly because the AWS resource is structurally
195
- * immutable (`AWS::Lambda::LayerVersion`, `AWS::S3Tables::TableBucket` once
196
- * created, certain `AWS::EC2::*` sub-resources) or because the provider
197
- * surfaces a sub-resource attachment whose only mutation pattern is
198
- * delete + add (Lambda permission statements, IAM policy attachments).
199
- *
200
- * Surfaced through `cdkd drift --revert`, which calls
201
- * `provider.update(logicalId, physicalId, type, stateProps, awsProps)` to
202
- * push cdkd state values back into AWS for every drifted resource. When a
203
- * provider throws this error, the drift command collects it as a
204
- * per-resource outcome distinct from a generic AWS update failure: the
205
- * fix is to re-deploy with `--replace` (or recreate the resource), not to
206
- * retry the update.
207
- *
208
- * Carries the same `exitCode = 2` as {@link PartialFailureError} so a
209
- * drift run that hits one immutable resource is reported as partial-
210
- * success rather than fatal — the rest of the drifted resources still
211
- * had their `update` invoked, and the user has a clear next step printed
212
- * for the unsupported one.
213
- */
214
- var ResourceUpdateNotSupportedError = class ResourceUpdateNotSupportedError extends CdkdError {
215
- exitCode = 2;
216
- resourceType;
217
- logicalId;
218
- /**
219
- * Human-readable hint printed alongside the error. The default is
220
- * "use cdkd deploy with --replace, or change the resource definition
221
- * to create a new version" — providers are encouraged to override
222
- * with a more specific suggestion when one is available (e.g.
223
- * Lambda::Permission's "delete + add a new statement").
224
- */
225
- suggestion;
226
- constructor(resourceType, logicalId, suggestion, cause) {
227
- super(`${resourceType} (${logicalId}) cannot be updated in place: ${suggestion ? suggestion : "use cdkd deploy with --replace, or change the resource definition to create a new version"}.`, "RESOURCE_UPDATE_NOT_SUPPORTED", cause);
228
- this.resourceType = resourceType;
229
- this.logicalId = logicalId;
230
- this.suggestion = suggestion;
231
- this.name = "ResourceUpdateNotSupportedError";
232
- Object.setPrototypeOf(this, ResourceUpdateNotSupportedError.prototype);
233
- }
234
- };
235
- /**
236
- * Signals a refusal to destroy a stack whose CDK manifest has
237
- * `terminationProtection: true`.
238
- *
239
- * Surfaced from `cdkd destroy <stack>` / `cdkd destroy --all` BEFORE
240
- * any lock acquisition or per-resource delete. In multi-stack runs
241
- * (e.g. `--all`) this counts as a per-stack failure and the rest of
242
- * the targets continue — the aggregated count is wrapped in
243
- * {@link PartialFailureError} so the command exits with code 2.
244
- *
245
- * The bypass workflow is documented in the message: edit the CDK code
246
- * (`new Stack(app, '...', { terminationProtection: false })`),
247
- * redeploy, then retry the destroy. A future `--remove-protection`
248
- * flag (separate scope) will provide an explicit one-shot bypass.
249
- *
250
- * Note: `cdkd state destroy` (state-only, no synth) does NOT honor
251
- * `terminationProtection` — the flag is a CDK property not persisted
252
- * in cdkd's state.json. Use `cdkd destroy` when synth is available.
253
- */
254
- var StackTerminationProtectionError = class StackTerminationProtectionError extends CdkdError {
255
- stackName;
256
- constructor(stackName, cause) {
257
- super(`Stack '${stackName}' has terminationProtection: true and cannot be destroyed. Set terminationProtection: false in the CDK code, redeploy, then retry 'cdkd destroy ${stackName}'.`, "STACK_TERMINATION_PROTECTION", cause);
258
- this.stackName = stackName;
259
- this.name = "StackTerminationProtectionError";
260
- Object.setPrototypeOf(this, StackTerminationProtectionError.prototype);
261
- }
262
- };
263
- /**
264
- * `cdkd destroy <child>` refused because the named stack is a nested
265
- * child of another stack. Mirrors CloudFormation's `you can't directly
266
- * destroy a nested stack` semantic — destroying the child without
267
- * touching the parent would leave the parent's `AWS::CloudFormation::Stack`
268
- * record pointing at non-existent resources, and the parent's next
269
- * deploy would silently try to re-create them.
270
- *
271
- * Detected by reading the loaded state's v6 `parentStack` field — only
272
- * state files written by `NestedStackProvider.create` (or by the
273
- * recursive `cdkd import --migrate-from-cloudformation` walk) carry
274
- * this field; top-level stacks have `parentStack: undefined` and pass
275
- * the guard unchanged.
276
- *
277
- * Fires from `cdkd destroy` AFTER state load but BEFORE lock
278
- * acquisition or any per-resource delete, so the refusal is cheap.
279
- * Surfaced as a per-stack failure (wrapped in {@link PartialFailureError},
280
- * exit code 2) in multi-stack runs — siblings continue.
281
- *
282
- * Bypass paths (both intentional escape hatches):
283
- *
284
- * 1. `cdkd destroy <parent>` — the normal cascading-destroy path; the
285
- * parent's reverse-DAG walks into the child via
286
- * `NestedStackProvider.delete` and removes both layers atomically.
287
- * 2. `cdkd state destroy <child>` — state-only destroy with no parent
288
- * coupling check. The state-driven entry point intentionally
289
- * bypasses this guard for the same reason `cdkd state destroy`
290
- * bypasses `terminationProtection`: it's the "I know what I'm
291
- * doing" path for cleaning up state when synth is unavailable or
292
- * the user accepts leaving the parent's reference dangling.
293
- */
294
- var NestedStackChildDirectDestroyError = class NestedStackChildDirectDestroyError extends CdkdError {
295
- stackName;
296
- parentStack;
297
- parentLogicalId;
298
- constructor(stackName, parentStack, parentLogicalId, cause) {
299
- const logicalIdSuffix = parentLogicalId ? ` (parent's logical id: ${parentLogicalId})` : "";
300
- super(`Stack '${stackName}' is a nested child of '${parentStack}'${logicalIdSuffix}; directly destroying a nested stack is not supported. Either run 'cdkd destroy ${parentStack}' to cascade-delete this child along with its parent, or run 'cdkd state destroy ${stackName}' if you intentionally want to leave the parent's reference dangling (the state-only escape hatch).`, "NESTED_STACK_CHILD_DIRECT_DESTROY", cause);
301
- this.stackName = stackName;
302
- this.parentStack = parentStack;
303
- if (parentLogicalId !== void 0) this.parentLogicalId = parentLogicalId;
304
- this.name = "NestedStackChildDirectDestroyError";
305
- Object.setPrototypeOf(this, NestedStackChildDirectDestroyError.prototype);
306
- }
307
- };
308
- /**
309
- * `cdkd destroy <producer>` refused because at least one consumer stack
310
- * still records an `Fn::ImportValue` reference to one of the producer's
311
- * outputs. This matches CloudFormation's strong-reference semantics —
312
- * CFn rejects `DeleteStack` for an exporter while an importer exists.
313
- *
314
- * cdkd has no `--force` escape hatch for this (intentionally, mirroring
315
- * CFn). The error message lists every offending consumer and points the
316
- * user at the two valid resolution paths:
317
- *
318
- * 1. Destroy the consumer first: `cdkd destroy <consumer>`
319
- * 2. Remove the `Fn::ImportValue` from the consumer's template and
320
- * redeploy, then retry the producer destroy.
321
- *
322
- * Weak-reference consumers (`Fn::GetStackOutput`, cdkd-specific) never
323
- * trigger this error by design — the producer stays deletable
324
- * independently of consumers when the user intentionally chose a weak
325
- * reference at template-authoring time.
326
- *
327
- * Exit code 2 (same as `PartialFailureError`) so multi-stack `cdkd
328
- * destroy --all` runs that partially succeed still surface as
329
- * non-zero without being indistinguishable from a fatal cdkd error.
330
- */
331
- var StackHasActiveImportsError = class StackHasActiveImportsError extends CdkdError {
332
- exitCode = 2;
333
- producerStack;
334
- producerRegion;
335
- consumers;
336
- constructor(producerStack, producerRegion, consumers, cause) {
337
- const lines = consumers.map((c) => ` - ${c.consumerStack} (${c.consumerRegion}): imports export '${c.exportName}'`);
338
- super(`Cannot destroy stack '${producerStack}' (${producerRegion}): the following stacks still import its outputs via Fn::ImportValue:\n${lines.join("\n")}\n\nThis matches CloudFormation's strong-reference semantics — exports are\nprotected as long as a consumer references them.\n\nTo proceed:\n 1. Destroy the consumer first: cdkd destroy <consumer-stack>\n 2. Or remove the Fn::ImportValue from the consumer's template\n (e.g. inline the value, or refactor) and re-deploy the consumer,\n then retry this destroy.\n\nNote: cdkd's Fn::GetStackOutput intrinsic is a weak alternative that\ndoes NOT protect the producer — use it when you intentionally want\nthe producer to be deletable independently of consumers.`, "STACK_HAS_ACTIVE_IMPORTS", cause);
339
- this.producerStack = producerStack;
340
- this.producerRegion = producerRegion;
341
- this.consumers = consumers;
342
- this.name = "StackHasActiveImportsError";
343
- Object.setPrototypeOf(this, StackHasActiveImportsError.prototype);
344
- }
345
- };
346
- /**
347
- * Signals a `cdkd local start-service` orchestration failure (Phase 2
348
- * of #262 — `AWS::ECS::Service` emulator). Distinct from
349
- * `LocalRunTaskError` because the service runner has its own lifecycle
350
- * (long-running replica pool, restart-on-exit), so a failure inside it
351
- * carries different operator semantics than a one-shot task failure.
352
- */
353
- var LocalStartServiceError = class LocalStartServiceError extends CdkdError {
354
- constructor(message, cause) {
355
- super(message, "LOCAL_START_SERVICE_ERROR", cause);
356
- this.name = "LocalStartServiceError";
357
- Object.setPrototypeOf(this, LocalStartServiceError.prototype);
358
- }
359
- };
360
- /**
361
- * Signals that the upstream `cdk` CLI is not available on PATH (or at
362
- * the override path passed via `--cdk-bin`). Surfaced from `cdkd migrate`
363
- * (#465 PR A) before any other work runs.
364
- *
365
- * The message includes the install hint `npm install -g aws-cdk@latest`
366
- * so users on a fresh machine see exactly how to recover.
367
- */
368
- var MissingCdkCliError = class MissingCdkCliError extends CdkdError {
369
- constructor(detail, cause) {
370
- super(`${detail ?? "upstream 'cdk' CLI not found on PATH"}. 'cdkd migrate' shells out to the upstream aws-cdk CLI for L1 codegen — install it with 'npm install -g aws-cdk@latest' (or pass --cdk-bin <path>).`, "MISSING_CDK_CLI", cause);
371
- this.name = "MissingCdkCliError";
372
- Object.setPrototypeOf(this, MissingCdkCliError.prototype);
373
- }
374
- };
375
- /**
376
- * Generic local-migrate orchestration failure (#465 PR A). Used by
377
- * `cdkd migrate` for pre-flight rejections (Custom Resource / nested
378
- * stack / non-terminal CFn stack state), output-dir collisions, and
379
- * `cdk migrate` subprocess failures whose underlying stderr is folded
380
- * into the error message. Exit code 2 (partial-failure family) because
381
- * some pre-flight failures leave the user with a partially-populated
382
- * output directory that's still useful for debugging.
383
- */
384
- var LocalMigrateError = class LocalMigrateError extends CdkdError {
385
- exitCode = 2;
386
- constructor(message, cause) {
387
- super(message, "LOCAL_MIGRATE_ERROR", cause);
388
- this.name = "LocalMigrateError";
389
- Object.setPrototypeOf(this, LocalMigrateError.prototype);
390
- }
391
- };
392
- /**
393
- * CloudFormation macro / `Fn::Transform` expansion failure (#463).
394
- *
395
- * cdkd hands templates that declare `Transform: [...]` (or carry
396
- * `Fn::Transform: {...}` snippets) to CloudFormation server-side via a
397
- * transient `CreateChangeSet --change-set-type CREATE` against a
398
- * `cdkd-macro-expand-<id>` stack name. This error wraps every failure
399
- * mode of that round-trip:
400
- *
401
- * - `CreateChangeSet` rejection (bad template, missing macro IAM
402
- * permission, custom macro not found in the account).
403
- * - Changeset settles in `FAILED` (`StatusReason` from CFn is
404
- * surfaced verbatim — typically a custom macro Lambda error).
405
- * - Waiter timeout (the macro Lambda is stuck or oversized).
406
- * - `GetTemplate --template-stage Processed` returns no body (would
407
- * indicate a CFn-side regression — fail loud rather than silently
408
- * proceed with the un-expanded template).
409
- * - Multi-stage detection: the expanded template still contains
410
- * macros, which cdkd v1 does not support (the design intentionally
411
- * rejects this so a second round-trip is not silently triggered).
412
- *
413
- * The error surfaces at exit code 2 (partial-failure family) — the
414
- * cleanup `finally` in the expander always runs `DeleteChangeSet` +
415
- * `DeleteStack` regardless of this error firing, so a failed
416
- * expansion never leaves a transient CFn stack behind in a routine
417
- * case. The user can re-run `cdkd deploy` once the upstream cause is
418
- * fixed.
419
- */
420
- var MacroExpansionError = class MacroExpansionError extends CdkdError {
421
- exitCode = 2;
422
- constructor(message, cause) {
423
- super(message, "MACRO_EXPANSION_ERROR", cause);
424
- this.name = "MacroExpansionError";
425
- Object.setPrototypeOf(this, MacroExpansionError.prototype);
426
- }
427
- };
428
- /**
429
- * Check if error is a cdkd error
430
- */
431
- function isCdkdError(error) {
432
- return error instanceof CdkdError;
433
- }
434
- /**
435
- * Format error for display
436
- */
437
- function formatError(error) {
438
- if (isCdkdError(error)) {
439
- let message = `${error.name}: ${error.message}`;
440
- if (error.cause) message += `\nCaused by: ${error.cause.message}`;
441
- return message;
442
- }
443
- if (error instanceof Error) return `${error.name}: ${error.message}`;
444
- return String(error);
445
- }
446
- /**
447
- * Global error handler
448
- *
449
- * Default exit code is 1 (general error). `PartialFailureError`
450
- * overrides it to 2 so callers can distinguish "command crashed /
451
- * unauthorized / bad arguments" from "command completed but some
452
- * resources are still in an error state, re-run to clean up".
453
- *
454
- * A {@link CdkdError} subclass may set `silent = true` to suppress the
455
- * default `logger.error` line — used by `cdkd drift` where the command
456
- * has already printed a richer report and only needs the exit code.
457
- */
458
- function handleError(error) {
459
- const logger = getLogger();
460
- if (!(error instanceof CdkdError && error.silent)) logger.error(formatError(error));
461
- if (error instanceof Error && error.stack) logger.debug("Stack trace:", error.stack);
462
- const customExitCode = error instanceof CdkdError ? error.exitCode : void 0;
463
- const exitCode = typeof customExitCode === "number" ? customExitCode : 1;
464
- process.exit(exitCode);
465
- }
466
- /**
467
- * Wrap async function with error handling
468
- *
469
- * Note: Uses `any[]` for args to support Commander.js action handlers
470
- * which can have various parameter types
471
- */
472
- function withErrorHandling(fn) {
473
- return async (...args) => {
474
- try {
475
- await fn(...args);
476
- } catch (error) {
477
- handleError(error);
478
- }
479
- };
480
- }
481
- /**
482
- * Convert AWS SDK v3's synthetic `Unknown` / `UnknownError` exception into
483
- * an actionable `Error` keyed off `$metadata.httpStatusCode`.
484
- *
485
- * Background — why this helper exists:
486
- * AWS SDK v3 produces a synthetic `name: 'Unknown'`, `message:
487
- * 'UnknownError'` exception when the protocol parser hits a HEAD response
488
- * with an empty body. The most common trigger is `HeadBucket` against a
489
- * bucket in a different region than the client (S3 returns 301
490
- * PermanentRedirect with `x-amz-bucket-region` set, but the redirect
491
- * middleware doesn't recover from the empty body). Surfacing the literal
492
- * string `UnknownError` to users is uninformative.
493
- *
494
- * Behavior:
495
- * - Non-AWS-SDK errors (anything where `name` is not `Unknown` and
496
- * `message` is not `UnknownError`) pass through unchanged.
497
- * - AWS SDK Unknown errors are mapped by HTTP status:
498
- * - 301 → `Bucket '<name>' is in a different region…` (auto-resolved
499
- * elsewhere; if this surfaces, it's a bug worth reporting).
500
- * - 403 → `Access denied to bucket '<name>'.`
501
- * - 404 → `Bucket '<name>' does not exist.`
502
- * - other / unknown → `S3 error during <operation> on '<bucket>' (HTTP
503
- * <status>).`
504
- */
505
- function normalizeAwsError(err, context = {}) {
506
- if (!(err instanceof Error)) return new Error(String(err));
507
- if (!(err.name === "Unknown" || err.message === "UnknownError")) return err;
508
- const status = err.$metadata?.httpStatusCode;
509
- const bucket = context.bucket ?? "<unknown bucket>";
510
- const operation = context.operation ?? "operation";
511
- switch (status) {
512
- case 301: {
513
- const responseHeaders = err.$response?.headers;
514
- const region = responseHeaders?.["x-amz-bucket-region"] ?? responseHeaders?.["X-Amz-Bucket-Region"];
515
- const where = region ? ` (in ${region})` : "";
516
- return /* @__PURE__ */ new Error(`Bucket '${bucket}'${where} is in a different region than the client. cdkd resolves this automatically; if you see this message, please report it.`);
517
- }
518
- case 403: return /* @__PURE__ */ new Error(`Access denied to bucket '${bucket}'. Verify credentials and bucket policy.`);
519
- case 404: return /* @__PURE__ */ new Error(`Bucket '${bucket}' does not exist.`);
520
- default: {
521
- const statusStr = status !== void 0 ? `HTTP ${status}` : "unknown HTTP status";
522
- return /* @__PURE__ */ new Error(`S3 error during ${operation} on '${bucket}' (${statusStr}). See CloudTrail for details.`);
523
- }
524
- }
525
- }
526
-
527
- //#endregion
528
- //#region src/provisioning/region-check.ts
529
- /**
530
- * Verify that the AWS client's region matches the region the resource is
531
- * expected to live in before treating a `NotFound` error as idempotent
532
- * delete success.
533
- *
534
- * Why: a destroy run with the wrong region would otherwise receive
535
- * `*NotFound` for every resource and silently strip them all from state,
536
- * leaving the actual AWS resources orphaned in the real region. The
537
- * silent-failure incident that motivated this check was a Lambda in
538
- * `us-west-2` removed from state by a destroy that ran with a `us-east-1`
539
- * client.
540
- *
541
- * Behavior:
542
- * - If `expectedRegion` is unset, this is a no-op (back-compat: existing
543
- * idempotent semantics preserved for callers that have not been
544
- * threaded with state region).
545
- * - If `clientRegion` matches `expectedRegion`, returns silently.
546
- * - Otherwise throws `ProvisioningError` so the caller surfaces the
547
- * mismatch instead of swallowing the NotFound.
548
- *
549
- * @param clientRegion Region resolved from the AWS SDK client config
550
- * (typically `await client.config.region()`).
551
- * @param expectedRegion Region recorded in stack state, or undefined if
552
- * the caller has no expected region.
553
- * @param resourceType CloudFormation resource type, used in the error
554
- * message and on the thrown ProvisioningError.
555
- * @param logicalId Logical ID of the resource, used in the error message
556
- * and on the thrown ProvisioningError.
557
- * @param physicalId Optional physical ID, used in the error message and
558
- * on the thrown ProvisioningError.
559
- */
560
- function assertRegionMatch(clientRegion, expectedRegion, resourceType, logicalId, physicalId) {
561
- if (!expectedRegion) return;
562
- if (!clientRegion) throw new ProvisioningError(`Refusing to treat NotFound as idempotent delete success for ${logicalId} (${resourceType}): AWS client region is unknown but stack state expects ${expectedRegion}. The resource may exist in ${expectedRegion} and would be silently removed from state if this NotFound were trusted.`, resourceType, logicalId, physicalId);
563
- if (clientRegion !== expectedRegion) throw new ProvisioningError(`Refusing to treat NotFound as idempotent delete success for ${logicalId} (${resourceType}): AWS client region ${clientRegion} does not match stack state region ${expectedRegion}. The resource likely still exists in ${expectedRegion}; rerun the destroy with the correct region (e.g. --region ${expectedRegion}).`, resourceType, logicalId, physicalId);
564
- }
565
-
566
- //#endregion
567
- //#region src/provisioning/import-helpers.ts
568
- /**
569
- * Read an explicit name field from template properties. Returns `undefined`
570
- * when the property is missing or not a string — callers fall back to
571
- * tag-based lookup in that case.
572
- */
573
- function readNameProperty(input, propertyName) {
574
- const value = input.properties?.[propertyName];
575
- return typeof value === "string" && value.length > 0 ? value : void 0;
576
- }
577
- /**
578
- * Resolve the physical id when the template provides an explicit name OR the
579
- * caller passed `--resource`/`--resource-mapping`. Returns `undefined` when
580
- * neither shortcut applies — caller must then fall back to tag-based lookup.
581
- *
582
- * Does NOT verify the resource exists: callers should follow up with a
583
- * service-specific `Head*`/`Get*`/`Describe*` to fail fast if the named
584
- * resource is missing.
585
- */
586
- function resolveExplicitPhysicalId(input, nameProperty) {
587
- if (input.knownPhysicalId) return input.knownPhysicalId;
588
- if (nameProperty) {
589
- const name = readNameProperty(input, nameProperty);
590
- if (name) return name;
591
- }
592
- }
593
- /**
594
- * The standard tag CDK puts on every deployed resource — its construct path
595
- * within the app, e.g. `MyStack/MyConstruct/MyBucket`. Used as the lookup key
596
- * when no explicit name is in the template.
597
- */
598
- const CDK_PATH_TAG = "aws:cdk:path";
599
- /**
600
- * Match an AWS resource's tag set against the CDK path the template carries.
601
- * Returns true if the resource was deployed by the same CDK construct.
602
- */
603
- function matchesCdkPath(tags, cdkPath) {
604
- if (!tags || !cdkPath) return false;
605
- for (const t of tags) if (t.Key === "aws:cdk:path" && t.Value === cdkPath) return true;
606
- return false;
607
- }
608
- /**
609
- * Re-shape an AWS tag list (any of the common shapes — array of `{Key, Value}`,
610
- * map keyed by tag name, or v2-style array of `{TagKey, TagValue}`) into the
611
- * canonical CFn shape (`Array<{Key, Value}>`) that cdkd state holds, with
612
- * `aws:`-prefixed entries filtered out.
613
- *
614
- * AWS reserves the `aws:` tag prefix; CDK injects `aws:cdk:path` (and
615
- * sometimes `aws:cdk:metadata`) on every resource it deploys. Those tags are
616
- * NOT in cdkd state's `Tags` (they come from CDK template `Metadata`, not
617
- * `Properties.Tags`), so leaving them in the AWS-current snapshot would fire
618
- * false-positive drift on every CDK-deployed resource.
619
- *
620
- * Returns an empty array `[]` when AWS reports no user tags. Callers decide
621
- * whether to surface `Tags: []` (most providers — matches the typical
622
- * CFn behavior of always emitting Tags in templates) or omit the key
623
- * entirely (when the corresponding `create()` only sets Tags when the user
624
- * explicitly passes them — see each provider's docstring).
625
- */
626
- function normalizeAwsTagsToCfn(tags) {
627
- if (!tags) return [];
628
- const out = [];
629
- if (Array.isArray(tags)) for (const t of tags) {
630
- const obj = t;
631
- const k = (typeof obj["Key"] === "string" ? obj["Key"] : void 0) ?? (typeof obj["TagKey"] === "string" ? obj["TagKey"] : void 0) ?? (typeof obj["key"] === "string" ? obj["key"] : void 0);
632
- const v = (typeof obj["Value"] === "string" ? obj["Value"] : void 0) ?? (typeof obj["TagValue"] === "string" ? obj["TagValue"] : void 0) ?? (typeof obj["value"] === "string" ? obj["value"] : void 0);
633
- if (typeof k !== "string" || k.length === 0) continue;
634
- if (k.startsWith("aws:")) continue;
635
- out.push({
636
- Key: k,
637
- Value: typeof v === "string" ? v : ""
638
- });
639
- }
640
- else for (const [k, v] of Object.entries(tags)) {
641
- if (!k || k.startsWith("aws:")) continue;
642
- out.push({
643
- Key: k,
644
- Value: typeof v === "string" ? v : ""
645
- });
646
- }
647
- out.sort((a, b) => a.Key < b.Key ? -1 : a.Key > b.Key ? 1 : 0);
648
- return out;
649
- }
650
-
651
- //#endregion
652
- //#region src/provisioning/ec2-termination-protection.ts
653
- /**
654
- * Flip `DisableApiTermination` off on an instance. Idempotent — EC2 accepts the
655
- * call when the attribute is already false. Non-fatal: a NotFound (already
656
- * gone) or any other error is swallowed at debug so the actual delete still
657
- * proceeds (it will surface the real failure if the instance truly cannot be
658
- * deleted).
659
- */
660
- async function disableInstanceApiTermination(client, instanceId, logger) {
661
- try {
662
- await client.send(new ModifyInstanceAttributeCommand({
663
- InstanceId: instanceId,
664
- DisableApiTermination: { Value: false }
665
- }));
666
- logger.debug(`Disabled DisableApiTermination on EC2 Instance ${instanceId} before deletion`);
667
- } catch (flipError) {
668
- logger.debug(`Could not disable DisableApiTermination on ${instanceId}: ${flipError instanceof Error ? flipError.message : String(flipError)}`);
669
- }
670
- }
671
- /**
672
- * Does this error message indicate the terminate / delete raced the
673
- * `DisableApiTermination` flip-off propagation (so re-flipping + retrying is
674
- * the right move)? Matches both the SDK `TerminateInstances` 400 and the Cloud
675
- * Control `DeleteResource` wrapper of the same underlying EC2 error.
676
- */
677
- function isTerminationProtectionPropagationError(message) {
678
- return /may not be terminated|disableApiTermination/i.test(message);
679
- }
680
-
681
- //#endregion
682
- export { withErrorHandling as A, StackHasActiveImportsError as C, formatError as D, SynthesisError as E, isCdkdError as O, ResourceUpdateNotSupportedError as S, StateError as T, MissingCdkCliError as _, normalizeAwsTagsToCfn as a, ProvisioningError as b, AssetError as c, DependencyError as d, LocalInvokeBuildError as f, MacroExpansionError as g, LockError as h, matchesCdkPath as i, normalizeAwsError as k, CdkdError as l, LocalStartServiceError as m, isTerminationProtectionPropagationError as n, resolveExplicitPhysicalId as o, LocalMigrateError as p, CDK_PATH_TAG as r, assertRegionMatch as s, disableInstanceApiTermination as t, ConfigError as u, NestedStackChildDirectDestroyError as v, StackTerminationProtectionError as w, ResourceTimeoutError as x, PartialFailureError as y };
683
- //# sourceMappingURL=ec2-termination-protection-q-WGWgxa.js.map