@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 +10 -4
- package/dist/cli.js +223 -117
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.2.0.tgz +0 -0
- package/dist/index.js +209 -111
- package/dist/index.js.map +4 -4
- package/package.json +3 -2
- package/dist/go-to-k-cdkd-0.0.4.tgz +0 -0
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 │
|
|
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
|
|
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
|
|
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
|
|
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 = [
|
|
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 =
|
|
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 =
|
|
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
|
|
25311
|
+
* Execute deployment by processing resources via event-driven DAG dispatch.
|
|
25207
25312
|
*
|
|
25208
|
-
*
|
|
25209
|
-
*
|
|
25210
|
-
|
|
25211
|
-
|
|
25212
|
-
|
|
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
|
-
|
|
25244
|
-
|
|
25245
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
25361
|
+
`Deploying ${createUpdateIds.length} resource(s) (DAG: ${executionLevels.length} levels, max parallel: ${concurrency})`
|
|
25260
25362
|
);
|
|
25261
|
-
const
|
|
25262
|
-
|
|
25263
|
-
|
|
25264
|
-
|
|
25265
|
-
|
|
25266
|
-
|
|
25267
|
-
|
|
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
|
-
|
|
25299
|
-
|
|
25300
|
-
|
|
25301
|
-
|
|
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
|
|
25307
|
-
|
|
25308
|
-
|
|
25309
|
-
|
|
25310
|
-
|
|
25311
|
-
|
|
25312
|
-
|
|
25313
|
-
|
|
25314
|
-
|
|
25315
|
-
|
|
25316
|
-
|
|
25317
|
-
|
|
25318
|
-
|
|
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
|
-
|
|
25331
|
-
|
|
25332
|
-
|
|
25333
|
-
|
|
25334
|
-
|
|
25335
|
-
|
|
25336
|
-
|
|
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
|
-
|
|
25342
|
-
|
|
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
|
|
25441
|
-
* (e.g., IAM Policy depends on IAM Role). When rolling back
|
|
25442
|
-
* dependent resources must be deleted before their
|
|
25443
|
-
* sorts CREATE rollback operations using dependency
|
|
25444
|
-
* then processes UPDATE/DELETE rollbacks, and finally
|
|
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
|
|
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
|
|
25901
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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());
|