@go-to-k/cdkd 0.0.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -78,7 +78,7 @@ Reproduce with `./tests/benchmark/run-benchmark.sh all`. See [tests/benchmark/RE
78
78
  │ cdkd Engine │
79
79
  │ - DAG Analysis │ Dependency graph construction
80
80
  │ - Diff Calc │ Compare with existing resources
81
- │ - Parallel Exec │ Deploy by levels
81
+ │ - Parallel Exec │ Event-driven dispatch
82
82
  └────────┬────────┘
83
83
 
84
84
  ┌────┴────┐
@@ -99,6 +99,8 @@ Reproduce with `./tests/benchmark/run-benchmark.sh all`. See [tests/benchmark/RE
99
99
  └── Initialize AWS clients
100
100
 
101
101
  2. Synthesis (self-implemented, no CDK CLI dependency)
102
+ ├── Short-circuit: if --app is an existing directory, treat it as a
103
+ │ pre-synthesized cloud assembly and skip the steps below
102
104
  ├── Load context (merge order, later wins):
103
105
  │ ├── CDK defaults (path-metadata, asset-metadata, version-reporting, bundling-stacks)
104
106
  │ ├── ~/.cdk.json "context" field (user defaults)
@@ -132,10 +134,11 @@ Reproduce with `./tests/benchmark/run-benchmark.sh all`. See [tests/benchmark/RE
132
134
  │ ├── Build DAG from template (Ref/Fn::GetAtt/DependsOn)
133
135
  │ ├── Calculate diff (CREATE/UPDATE/DELETE)
134
136
  │ ├── Resolve intrinsic functions (Ref, Fn::Sub, Fn::Join, etc.)
135
- │ ├── Execute by levels (parallel within each level):
137
+ │ ├── Execute via event-driven DAG dispatch (a resource starts as
138
+ │ │ soon as ALL of its own deps complete; no level barrier):
136
139
  │ │ ├── SDK Providers (direct API calls, preferred)
137
140
  │ │ └── Cloud Control API (fallback, async polling)
138
- │ ├── Save state after each level (partial state save)
141
+ │ ├── Save state after each successful resource (partial state save)
139
142
  │ └── Release lock
140
143
  └── synth does NOT publish assets or deploy (deploy only)
141
144
  ```
@@ -364,6 +367,9 @@ cdkd bootstrap \
364
367
  # Synthesize only
365
368
  cdkd synth --app "npx ts-node app.ts"
366
369
 
370
+ # Deploy from a pre-synthesized cloud assembly directory
371
+ cdkd deploy --app cdk.out
372
+
367
373
  # Deploy (single stack auto-detected, reads --app from cdk.json)
368
374
  cdkd deploy
369
375
 
@@ -454,7 +460,7 @@ LambdaStack
454
460
  ✓ Deployed LambdaStack (4 resources, 7.2s)
455
461
  ```
456
462
 
457
- Resources without dependencies (ServiceRole and Table) are created in parallel.
463
+ Resources are dispatched as soon as their own dependencies complete (event-driven DAG). ServiceRole and Table run in parallel; DefaultPolicy starts the moment ServiceRole is done — without waiting for Table — and Handler starts the moment DefaultPolicy is done.
458
464
 
459
465
  ## Architecture
460
466
 
package/dist/cli.js CHANGED
@@ -477,12 +477,16 @@ function parseContextOptions(contextArgs) {
477
477
  var commonOptions = [
478
478
  new Option("--verbose", "Enable verbose logging").default(false),
479
479
  new Option("--region <region>", "AWS region"),
480
- new Option("--profile <profile>", "AWS profile")
480
+ new Option("--profile <profile>", "AWS profile"),
481
+ new Option(
482
+ "-y, --yes",
483
+ "Automatically answer interactive prompts with the recommended response (e.g. confirm destroy)"
484
+ ).default(false)
481
485
  ];
482
486
  var appOptions = [
483
487
  new Option(
484
- "--app <command>",
485
- 'CDK app command (e.g., "npx ts-node app.ts"). Falls back to cdk.json or CDKD_APP env'
488
+ "-a, --app <command>",
489
+ 'CDK app command (e.g., "npx ts-node app.ts") or path to a pre-synthesized cloud assembly directory. Falls back to cdk.json or CDKD_APP env'
486
490
  ),
487
491
  new Option("--output <path>", "Output directory for synthesis").default("cdk.out")
488
492
  ];
@@ -517,7 +521,11 @@ var contextOptions = [
517
521
  "Set context values (can be specified multiple times)"
518
522
  )
519
523
  ];
520
- var destroyOptions = [new Option("--force", "Skip confirmation prompt").default(false)];
524
+ var destroyOptions = [
525
+ new Option("-f, --force", "Do not ask for confirmation before destroying the stacks").default(
526
+ false
527
+ )
528
+ ];
521
529
 
522
530
  // src/utils/logger.ts
523
531
  var colors = {
@@ -950,7 +958,7 @@ import { writeFileSync as writeFileSync3 } from "fs";
950
958
  import { join as join4 } from "path";
951
959
 
952
960
  // src/synthesis/synthesizer.ts
953
- import { mkdirSync } from "node:fs";
961
+ import { existsSync as existsSync3, mkdirSync, statSync } from "node:fs";
954
962
  import { resolve as resolve3 } from "node:path";
955
963
  import { GetCallerIdentityCommand as GetCallerIdentityCommand2, STSClient as STSClient2 } from "@aws-sdk/client-sts";
956
964
 
@@ -2118,6 +2126,14 @@ var Synthesizer = class {
2118
2126
  * 5. Return assembly with stacks
2119
2127
  */
2120
2128
  async synthesize(options) {
2129
+ const appPath = resolve3(options.app);
2130
+ if (existsSync3(appPath) && statSync(appPath).isDirectory()) {
2131
+ this.logger.debug(`Using pre-synthesized cloud assembly at ${appPath}`);
2132
+ const manifest = this.assemblyReader.readManifest(appPath);
2133
+ const stacks = this.assemblyReader.getAllStacks(appPath, manifest);
2134
+ this.logger.debug(`Loaded ${stacks.length} stack(s) from pre-synthesized assembly`);
2135
+ return { manifest, assemblyDir: appPath, stacks };
2136
+ }
2121
2137
  const outputDir = resolve3(options.output || "cdk.out");
2122
2138
  mkdirSync(outputDir, { recursive: true });
2123
2139
  const userCdkJson = loadUserCdkJson();
@@ -2318,7 +2334,7 @@ import { Command as Command3 } from "commander";
2318
2334
  import { readFileSync as readFileSync4 } from "node:fs";
2319
2335
 
2320
2336
  // src/assets/file-asset-publisher.ts
2321
- import { createReadStream, statSync } from "node:fs";
2337
+ import { createReadStream, statSync as statSync2 } from "node:fs";
2322
2338
  import { join as join5, basename } from "node:path";
2323
2339
  import { S3Client as S3Client2, HeadObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
2324
2340
  var FileAssetPublisher = class {
@@ -2388,7 +2404,7 @@ var FileAssetPublisher = class {
2388
2404
  * Upload a single file to S3
2389
2405
  */
2390
2406
  async uploadFile(client, filePath, bucket, key) {
2391
- const stat = statSync(filePath);
2407
+ const stat = statSync2(filePath);
2392
2408
  const stream = createReadStream(filePath);
2393
2409
  await client.send(
2394
2410
  new PutObjectCommand({
@@ -2410,7 +2426,7 @@ var FileAssetPublisher = class {
2410
2426
  archive.on("data", (chunk) => chunks.push(chunk));
2411
2427
  archive.on("end", () => resolve4(Buffer.concat(chunks)));
2412
2428
  archive.on("error", reject);
2413
- const stat = statSync(dirPath);
2429
+ const stat = statSync2(dirPath);
2414
2430
  if (stat.isDirectory()) {
2415
2431
  archive.directory(dirPath, false);
2416
2432
  } else {
@@ -25037,8 +25053,96 @@ function registerAllProviders(registry) {
25037
25053
  registry.register("AWS::S3Tables::Table", s3TablesProvider);
25038
25054
  }
25039
25055
 
25056
+ // src/deployment/dag-executor.ts
25057
+ var DagExecutor = class {
25058
+ nodes = /* @__PURE__ */ new Map();
25059
+ logger = getLogger().child("DagExecutor");
25060
+ add(node) {
25061
+ this.nodes.set(node.id, node);
25062
+ }
25063
+ has(id) {
25064
+ return this.nodes.has(id);
25065
+ }
25066
+ size() {
25067
+ return this.nodes.size;
25068
+ }
25069
+ values() {
25070
+ return this.nodes.values();
25071
+ }
25072
+ async execute(concurrency, fn, cancelled = () => false) {
25073
+ let active = 0;
25074
+ const errors = [];
25075
+ return new Promise((resolve4, reject) => {
25076
+ const dispatch = () => {
25077
+ let changed = true;
25078
+ while (changed) {
25079
+ changed = false;
25080
+ for (const node of this.nodes.values()) {
25081
+ if (node.state !== "pending")
25082
+ continue;
25083
+ const hasFailedDep = [...node.dependencies].some((depId) => {
25084
+ const dep = this.nodes.get(depId);
25085
+ return dep && (dep.state === "failed" || dep.state === "skipped");
25086
+ });
25087
+ if (hasFailedDep) {
25088
+ node.state = "skipped";
25089
+ changed = true;
25090
+ this.logger.debug(`Skipped ${node.id}: dependency failed or was skipped`);
25091
+ }
25092
+ }
25093
+ }
25094
+ const ready = [];
25095
+ for (const node of this.nodes.values()) {
25096
+ if (node.state !== "pending")
25097
+ continue;
25098
+ const depsReady = [...node.dependencies].every((depId) => {
25099
+ const dep = this.nodes.get(depId);
25100
+ return !dep || dep.state === "completed";
25101
+ });
25102
+ if (depsReady)
25103
+ ready.push(node);
25104
+ }
25105
+ if (!cancelled()) {
25106
+ for (const node of ready) {
25107
+ if (active >= concurrency)
25108
+ break;
25109
+ node.state = "running";
25110
+ active++;
25111
+ fn(node).then(() => {
25112
+ node.state = "completed";
25113
+ }).catch((error) => {
25114
+ node.state = "failed";
25115
+ errors.push({ id: node.id, error });
25116
+ }).finally(() => {
25117
+ active--;
25118
+ dispatch();
25119
+ });
25120
+ }
25121
+ }
25122
+ if (active === 0) {
25123
+ if (errors.length > 0) {
25124
+ reject(errors[0].error);
25125
+ return;
25126
+ }
25127
+ const stillPending = [...this.nodes.values()].some((n) => n.state === "pending");
25128
+ if (stillPending && !cancelled()) {
25129
+ const pending = [...this.nodes.values()].filter((n) => n.state === "pending").map((n) => n.id);
25130
+ reject(
25131
+ new Error(
25132
+ `Deadlock detected: ${pending.length} node(s) stuck with unresolvable dependencies (${pending.join(", ")})`
25133
+ )
25134
+ );
25135
+ return;
25136
+ }
25137
+ resolve4();
25138
+ }
25139
+ };
25140
+ dispatch();
25141
+ });
25142
+ }
25143
+ };
25144
+
25040
25145
  // src/deployment/deploy-engine.ts
25041
- import pLimit from "p-limit";
25042
25146
  var InterruptedError = class extends Error {
25043
25147
  constructor() {
25044
25148
  super("Deployment interrupted by user (Ctrl+C)");
@@ -25171,6 +25275,7 @@ var DeployEngine = class _DeployEngine {
25171
25275
  template,
25172
25276
  currentState,
25173
25277
  changes,
25278
+ dag,
25174
25279
  executionLevels,
25175
25280
  stackName,
25176
25281
  parameterValues,
@@ -25203,13 +25308,16 @@ var DeployEngine = class _DeployEngine {
25203
25308
  }
25204
25309
  }
25205
25310
  /**
25206
- * Execute deployment by processing resources in DAG order
25311
+ * Execute deployment by processing resources via event-driven DAG dispatch.
25207
25312
  *
25208
- * Important: DELETE operations are executed in reverse dependency order,
25209
- * while CREATE/UPDATE follow normal dependency order.
25210
- */
25211
- async executeDeployment(template, currentState, changes, executionLevels, stackName, parameterValues, conditions, currentEtag, progress) {
25212
- const limit = pLimit(this.options.concurrency);
25313
+ * - CREATE/UPDATE follow forward dependency order (a node starts as soon as
25314
+ * ALL of its dependencies are completed — does not wait for unrelated
25315
+ * siblings in the same "level")
25316
+ * - DELETE follows reverse dependency order (a node starts as soon as all
25317
+ * resources that depend ON it have finished deleting)
25318
+ */
25319
+ async executeDeployment(template, currentState, changes, dag, executionLevels, stackName, parameterValues, conditions, currentEtag, progress) {
25320
+ const concurrency = this.options.concurrency;
25213
25321
  const newResources = { ...currentState.resources };
25214
25322
  const actualCounts = { created: 0, updated: 0, deleted: 0, skipped: 0 };
25215
25323
  const completedOperations = [];
@@ -25240,32 +25348,36 @@ var DeployEngine = class _DeployEngine {
25240
25348
  Array.from(changes.entries()).filter(([_, change]) => change.changeType === "DELETE").map(([logicalId]) => logicalId)
25241
25349
  );
25242
25350
  try {
25243
- for (let levelIndex = 0; levelIndex < executionLevels.length; levelIndex++) {
25244
- if (this.interrupted) {
25245
- throw new InterruptedError();
25246
- }
25247
- const levelNodes = executionLevels[levelIndex];
25248
- if (!levelNodes)
25351
+ const createUpdateIds = [];
25352
+ for (const [id, change] of changes.entries()) {
25353
+ if (deleteChanges.has(id))
25249
25354
  continue;
25250
- const level = levelNodes.filter((id) => {
25251
- if (deleteChanges.has(id))
25252
- return false;
25253
- const change = changes.get(id);
25254
- return !!change && change.changeType !== "NO_CHANGE";
25255
- });
25256
- if (level.length === 0)
25355
+ if (change.changeType === "NO_CHANGE")
25257
25356
  continue;
25357
+ createUpdateIds.push(id);
25358
+ }
25359
+ if (createUpdateIds.length > 0) {
25258
25360
  this.logger.info(
25259
- `Level ${levelIndex + 1}/${executionLevels.length} (${level.length} resources)`
25361
+ `Deploying ${createUpdateIds.length} resource(s) (DAG: ${executionLevels.length} levels, max parallel: ${concurrency})`
25260
25362
  );
25261
- const results = await Promise.allSettled(
25262
- level.map(
25263
- (logicalId) => limit(async () => {
25264
- const change = changes.get(logicalId);
25265
- if (!change || change.changeType === "NO_CHANGE") {
25266
- this.logger.debug(`Skipping ${logicalId} (no change)`);
25267
- return;
25268
- }
25363
+ const createUpdateExecutor = new DagExecutor();
25364
+ const provisionable = new Set(createUpdateIds);
25365
+ for (const id of createUpdateIds) {
25366
+ const allDeps = this.dagBuilder.getDirectDependencies(dag, id);
25367
+ const deps = new Set(allDeps.filter((d) => provisionable.has(d)));
25368
+ createUpdateExecutor.add({
25369
+ id,
25370
+ dependencies: deps,
25371
+ state: "pending",
25372
+ data: changes.get(id)
25373
+ });
25374
+ }
25375
+ try {
25376
+ await createUpdateExecutor.execute(
25377
+ concurrency,
25378
+ async (node) => {
25379
+ const logicalId = node.id;
25380
+ const change = node.data;
25269
25381
  const previousState = currentState.resources[logicalId] ? { ...currentState.resources[logicalId] } : void 0;
25270
25382
  try {
25271
25383
  await this.provisionResource(
@@ -25292,30 +25404,36 @@ var DeployEngine = class _DeployEngine {
25292
25404
  properties: newResources[logicalId]?.properties
25293
25405
  });
25294
25406
  saveStateAfterResource(logicalId);
25295
- })
25296
- )
25297
- );
25298
- await saveChain;
25299
- const failures = results.filter((r) => r.status === "rejected");
25300
- if (failures.length > 0) {
25301
- throw failures[0].reason;
25407
+ },
25408
+ () => this.interrupted
25409
+ );
25410
+ } finally {
25411
+ await saveChain;
25412
+ }
25413
+ if (this.interrupted && this.hasPending(createUpdateExecutor)) {
25414
+ throw new InterruptedError();
25302
25415
  }
25303
25416
  }
25304
25417
  if (deleteChanges.size > 0) {
25305
25418
  this.logger.info(`Deleting ${deleteChanges.size} resource(s)`);
25306
- const deletionLevels = this.buildDeletionLevels(deleteChanges, currentState);
25307
- for (let levelIndex = 0; levelIndex < deletionLevels.length; levelIndex++) {
25308
- if (this.interrupted) {
25309
- throw new InterruptedError();
25310
- }
25311
- const level = deletionLevels[levelIndex];
25312
- if (level.length === 0)
25313
- continue;
25314
- const deleteResults = await Promise.allSettled(
25315
- level.map(
25316
- (logicalId) => limit(async () => {
25317
- const change = changes.get(logicalId);
25318
- const previousState = currentState.resources[logicalId] ? { ...currentState.resources[logicalId] } : void 0;
25419
+ const deleteDeps = this.buildDeletionDependencies(deleteChanges, currentState);
25420
+ const deleteExecutor = new DagExecutor();
25421
+ for (const id of deleteChanges) {
25422
+ deleteExecutor.add({
25423
+ id,
25424
+ dependencies: deleteDeps.get(id) ?? /* @__PURE__ */ new Set(),
25425
+ state: "pending",
25426
+ data: changes.get(id)
25427
+ });
25428
+ }
25429
+ try {
25430
+ await deleteExecutor.execute(
25431
+ concurrency,
25432
+ async (node) => {
25433
+ const logicalId = node.id;
25434
+ const change = node.data;
25435
+ const previousState = currentState.resources[logicalId] ? { ...currentState.resources[logicalId] } : void 0;
25436
+ try {
25319
25437
  await this.provisionResource(
25320
25438
  logicalId,
25321
25439
  change,
@@ -25327,23 +25445,25 @@ var DeployEngine = class _DeployEngine {
25327
25445
  actualCounts,
25328
25446
  progress
25329
25447
  );
25330
- completedOperations.push({
25331
- logicalId,
25332
- changeType: "DELETE",
25333
- resourceType: change.resourceType,
25334
- previousState
25335
- });
25336
- saveStateAfterResource(logicalId);
25337
- })
25338
- )
25448
+ } catch (provisionError) {
25449
+ this.interrupted = true;
25450
+ throw provisionError;
25451
+ }
25452
+ completedOperations.push({
25453
+ logicalId,
25454
+ changeType: "DELETE",
25455
+ resourceType: change.resourceType,
25456
+ previousState
25457
+ });
25458
+ saveStateAfterResource(logicalId);
25459
+ },
25460
+ () => this.interrupted
25339
25461
  );
25462
+ } finally {
25340
25463
  await saveChain;
25341
- const deleteFailures = deleteResults.filter(
25342
- (r) => r.status === "rejected"
25343
- );
25344
- if (deleteFailures.length > 0) {
25345
- throw deleteFailures[0].reason;
25346
- }
25464
+ }
25465
+ if (this.interrupted && this.hasPending(deleteExecutor)) {
25466
+ throw new InterruptedError();
25347
25467
  }
25348
25468
  }
25349
25469
  } catch (error) {
@@ -25437,12 +25557,12 @@ var DeployEngine = class _DeployEngine {
25437
25557
  * - UPDATE → update back to previous properties
25438
25558
  * - DELETE → cannot rollback (resource already deleted), log warning
25439
25559
  *
25440
- * Resources created in the same DAG level may have dependencies between them
25441
- * (e.g., IAM Policy depends on IAM Role). When rolling back CREATEs (deleting),
25442
- * dependent resources must be deleted before their dependencies. This method
25443
- * sorts CREATE rollback operations using dependency information from state,
25444
- * then processes UPDATE/DELETE rollbacks, and finally processes sorted CREATE
25445
- * rollback deletions.
25560
+ * Resources completed concurrently in the dispatcher may have dependencies
25561
+ * between them (e.g., IAM Policy depends on IAM Role). When rolling back
25562
+ * CREATEs (deleting), dependent resources must be deleted before their
25563
+ * dependencies. This method sorts CREATE rollback operations using dependency
25564
+ * information from state, then processes UPDATE/DELETE rollbacks, and finally
25565
+ * processes sorted CREATE rollback deletions.
25446
25566
  */
25447
25567
  async performRollback(completedOperations, stateResources, _stackName) {
25448
25568
  if (completedOperations.length === 0) {
@@ -25475,7 +25595,7 @@ var DeployEngine = class _DeployEngine {
25475
25595
  * Sort CREATE rollback operations so that resources depending on others
25476
25596
  * are deleted first (reverse dependency order).
25477
25597
  *
25478
- * Uses state dependencies to build deletion levels, similar to buildDeletionLevels.
25598
+ * Uses state dependencies to determine reverse-dependency order, similar to buildDeletionDependencies.
25479
25599
  */
25480
25600
  sortRollbackCreates(createOps, stateResources) {
25481
25601
  const opMap = /* @__PURE__ */ new Map();
@@ -25897,17 +26017,30 @@ var DeployEngine = class _DeployEngine {
25897
26017
  "AWS::EC2::SecurityGroup": ["AWS::EC2::SecurityGroupIngress", "AWS::EC2::SecurityGroupEgress"]
25898
26018
  };
25899
26019
  /**
25900
- * Build deletion levels from state dependencies (reverse topological order).
25901
- * Resources that are depended upon by others are deleted LAST.
26020
+ * Build a per-resource map of "must be deleted before me" dependencies for
26021
+ * the DELETE phase, derived from state-recorded dependencies plus implicit
26022
+ * type-based ordering rules.
26023
+ *
26024
+ * For a resource X, the returned set contains every resource Y such that Y
26025
+ * must finish deleting before X starts — i.e., Y depends on X (or is otherwise
26026
+ * required to vanish first per implicit type rules).
26027
+ */
26028
+ /**
26029
+ * Returns true if the executor still has un-started pending nodes —
26030
+ * used to distinguish "SIGINT cancelled real work" from "SIGINT landed
26031
+ * after all nodes already completed" (the latter should not error).
25902
26032
  */
25903
- buildDeletionLevels(deleteIds, state) {
26033
+ hasPending(executor) {
26034
+ for (const node of executor.values()) {
26035
+ if (node.state === "pending")
26036
+ return true;
26037
+ }
26038
+ return false;
26039
+ }
26040
+ buildDeletionDependencies(deleteIds, state) {
25904
26041
  const dependedBy = /* @__PURE__ */ new Map();
25905
- const inDegree = /* @__PURE__ */ new Map();
25906
26042
  for (const id of deleteIds) {
25907
- if (!dependedBy.has(id))
25908
- dependedBy.set(id, /* @__PURE__ */ new Set());
25909
- if (!inDegree.has(id))
25910
- inDegree.set(id, 0);
26043
+ dependedBy.set(id, /* @__PURE__ */ new Set());
25911
26044
  }
25912
26045
  for (const id of deleteIds) {
25913
26046
  const resource = state.resources[id];
@@ -25916,38 +26049,11 @@ var DeployEngine = class _DeployEngine {
25916
26049
  for (const dep of resource.dependencies) {
25917
26050
  if (!deleteIds.has(dep))
25918
26051
  continue;
25919
- if (!dependedBy.has(dep))
25920
- dependedBy.set(dep, /* @__PURE__ */ new Set());
25921
26052
  dependedBy.get(dep).add(id);
25922
- inDegree.set(id, (inDegree.get(id) ?? 0) + 1);
25923
26053
  }
25924
26054
  }
25925
26055
  this.addImplicitDeleteDependencies(deleteIds, state, dependedBy);
25926
- const levels = [];
25927
- let remaining = new Set(deleteIds);
25928
- while (remaining.size > 0) {
25929
- const level = [];
25930
- for (const id of remaining) {
25931
- const dependents = dependedBy.get(id);
25932
- const hasPendingDependents = dependents ? [...dependents].some((d) => remaining.has(d)) : false;
25933
- if (!hasPendingDependents) {
25934
- level.push(id);
25935
- }
25936
- }
25937
- if (level.length === 0) {
25938
- this.logger.warn(
25939
- `Circular dependency detected in delete order, deleting remaining ${remaining.size} resources`
25940
- );
25941
- levels.push([...remaining]);
25942
- break;
25943
- }
25944
- levels.push(level);
25945
- remaining = new Set([...remaining].filter((id) => !level.includes(id)));
25946
- }
25947
- this.logger.debug(
25948
- `Delete order: ${levels.length} levels - ${levels.map((l, i) => `L${i + 1}(${l.length})`).join(", ")}`
25949
- );
25950
- return levels;
26056
+ return dependedBy;
25951
26057
  }
25952
26058
  /**
25953
26059
  * Add implicit delete dependency edges based on resource type relationships.
@@ -26711,7 +26817,7 @@ Resources to be deleted (${resourceCount}):`);
26711
26817
  for (const [logicalId, resource] of Object.entries(currentState.resources)) {
26712
26818
  logger.info(` - ${logicalId} (${resource.resourceType})`);
26713
26819
  }
26714
- if (!options.force) {
26820
+ if (!options.yes && !options.force) {
26715
26821
  const rl = readline.createInterface({
26716
26822
  input: process.stdin,
26717
26823
  output: process.stdout
@@ -27013,7 +27119,7 @@ function reorderArgs(argv) {
27013
27119
  }
27014
27120
  async function main() {
27015
27121
  const program = new Command8();
27016
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.0.4");
27122
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.2.0");
27017
27123
  program.addCommand(createBootstrapCommand());
27018
27124
  program.addCommand(createSynthCommand());
27019
27125
  program.addCommand(createDeployCommand());