@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
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -649,7 +649,7 @@ Caused by: ${error.cause.message}`;
|
|
|
649
649
|
init_aws_clients();
|
|
650
650
|
|
|
651
651
|
// src/synthesis/synthesizer.ts
|
|
652
|
-
import { mkdirSync } from "node:fs";
|
|
652
|
+
import { existsSync as existsSync3, mkdirSync, statSync } from "node:fs";
|
|
653
653
|
import { resolve as resolve3 } from "node:path";
|
|
654
654
|
import { GetCallerIdentityCommand, STSClient as STSClient2 } from "@aws-sdk/client-sts";
|
|
655
655
|
|
|
@@ -1846,6 +1846,14 @@ var Synthesizer = class {
|
|
|
1846
1846
|
* 5. Return assembly with stacks
|
|
1847
1847
|
*/
|
|
1848
1848
|
async synthesize(options) {
|
|
1849
|
+
const appPath = resolve3(options.app);
|
|
1850
|
+
if (existsSync3(appPath) && statSync(appPath).isDirectory()) {
|
|
1851
|
+
this.logger.debug(`Using pre-synthesized cloud assembly at ${appPath}`);
|
|
1852
|
+
const manifest = this.assemblyReader.readManifest(appPath);
|
|
1853
|
+
const stacks = this.assemblyReader.getAllStacks(appPath, manifest);
|
|
1854
|
+
this.logger.debug(`Loaded ${stacks.length} stack(s) from pre-synthesized assembly`);
|
|
1855
|
+
return { manifest, assemblyDir: appPath, stacks };
|
|
1856
|
+
}
|
|
1849
1857
|
const outputDir = resolve3(options.output || "cdk.out");
|
|
1850
1858
|
mkdirSync(outputDir, { recursive: true });
|
|
1851
1859
|
const userCdkJson = loadUserCdkJson();
|
|
@@ -1933,7 +1941,7 @@ function setsEqual(a, b) {
|
|
|
1933
1941
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
1934
1942
|
|
|
1935
1943
|
// src/assets/file-asset-publisher.ts
|
|
1936
|
-
import { createReadStream, statSync } from "node:fs";
|
|
1944
|
+
import { createReadStream, statSync as statSync2 } from "node:fs";
|
|
1937
1945
|
import { join as join4, basename } from "node:path";
|
|
1938
1946
|
import { S3Client as S3Client2, HeadObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
1939
1947
|
var FileAssetPublisher = class {
|
|
@@ -2003,7 +2011,7 @@ var FileAssetPublisher = class {
|
|
|
2003
2011
|
* Upload a single file to S3
|
|
2004
2012
|
*/
|
|
2005
2013
|
async uploadFile(client, filePath, bucket, key) {
|
|
2006
|
-
const stat =
|
|
2014
|
+
const stat = statSync2(filePath);
|
|
2007
2015
|
const stream = createReadStream(filePath);
|
|
2008
2016
|
await client.send(
|
|
2009
2017
|
new PutObjectCommand({
|
|
@@ -2025,7 +2033,7 @@ var FileAssetPublisher = class {
|
|
|
2025
2033
|
archive.on("data", (chunk) => chunks.push(chunk));
|
|
2026
2034
|
archive.on("end", () => resolve4(Buffer.concat(chunks)));
|
|
2027
2035
|
archive.on("error", reject);
|
|
2028
|
-
const stat =
|
|
2036
|
+
const stat = statSync2(dirPath);
|
|
2029
2037
|
if (stat.isDirectory()) {
|
|
2030
2038
|
archive.directory(dirPath, false);
|
|
2031
2039
|
} else {
|
|
@@ -6985,8 +6993,96 @@ var IAMRoleProvider = class {
|
|
|
6985
6993
|
}
|
|
6986
6994
|
};
|
|
6987
6995
|
|
|
6996
|
+
// src/deployment/dag-executor.ts
|
|
6997
|
+
var DagExecutor = class {
|
|
6998
|
+
nodes = /* @__PURE__ */ new Map();
|
|
6999
|
+
logger = getLogger().child("DagExecutor");
|
|
7000
|
+
add(node) {
|
|
7001
|
+
this.nodes.set(node.id, node);
|
|
7002
|
+
}
|
|
7003
|
+
has(id) {
|
|
7004
|
+
return this.nodes.has(id);
|
|
7005
|
+
}
|
|
7006
|
+
size() {
|
|
7007
|
+
return this.nodes.size;
|
|
7008
|
+
}
|
|
7009
|
+
values() {
|
|
7010
|
+
return this.nodes.values();
|
|
7011
|
+
}
|
|
7012
|
+
async execute(concurrency, fn, cancelled = () => false) {
|
|
7013
|
+
let active = 0;
|
|
7014
|
+
const errors = [];
|
|
7015
|
+
return new Promise((resolve4, reject) => {
|
|
7016
|
+
const dispatch = () => {
|
|
7017
|
+
let changed = true;
|
|
7018
|
+
while (changed) {
|
|
7019
|
+
changed = false;
|
|
7020
|
+
for (const node of this.nodes.values()) {
|
|
7021
|
+
if (node.state !== "pending")
|
|
7022
|
+
continue;
|
|
7023
|
+
const hasFailedDep = [...node.dependencies].some((depId) => {
|
|
7024
|
+
const dep = this.nodes.get(depId);
|
|
7025
|
+
return dep && (dep.state === "failed" || dep.state === "skipped");
|
|
7026
|
+
});
|
|
7027
|
+
if (hasFailedDep) {
|
|
7028
|
+
node.state = "skipped";
|
|
7029
|
+
changed = true;
|
|
7030
|
+
this.logger.debug(`Skipped ${node.id}: dependency failed or was skipped`);
|
|
7031
|
+
}
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
7034
|
+
const ready = [];
|
|
7035
|
+
for (const node of this.nodes.values()) {
|
|
7036
|
+
if (node.state !== "pending")
|
|
7037
|
+
continue;
|
|
7038
|
+
const depsReady = [...node.dependencies].every((depId) => {
|
|
7039
|
+
const dep = this.nodes.get(depId);
|
|
7040
|
+
return !dep || dep.state === "completed";
|
|
7041
|
+
});
|
|
7042
|
+
if (depsReady)
|
|
7043
|
+
ready.push(node);
|
|
7044
|
+
}
|
|
7045
|
+
if (!cancelled()) {
|
|
7046
|
+
for (const node of ready) {
|
|
7047
|
+
if (active >= concurrency)
|
|
7048
|
+
break;
|
|
7049
|
+
node.state = "running";
|
|
7050
|
+
active++;
|
|
7051
|
+
fn(node).then(() => {
|
|
7052
|
+
node.state = "completed";
|
|
7053
|
+
}).catch((error) => {
|
|
7054
|
+
node.state = "failed";
|
|
7055
|
+
errors.push({ id: node.id, error });
|
|
7056
|
+
}).finally(() => {
|
|
7057
|
+
active--;
|
|
7058
|
+
dispatch();
|
|
7059
|
+
});
|
|
7060
|
+
}
|
|
7061
|
+
}
|
|
7062
|
+
if (active === 0) {
|
|
7063
|
+
if (errors.length > 0) {
|
|
7064
|
+
reject(errors[0].error);
|
|
7065
|
+
return;
|
|
7066
|
+
}
|
|
7067
|
+
const stillPending = [...this.nodes.values()].some((n) => n.state === "pending");
|
|
7068
|
+
if (stillPending && !cancelled()) {
|
|
7069
|
+
const pending = [...this.nodes.values()].filter((n) => n.state === "pending").map((n) => n.id);
|
|
7070
|
+
reject(
|
|
7071
|
+
new Error(
|
|
7072
|
+
`Deadlock detected: ${pending.length} node(s) stuck with unresolvable dependencies (${pending.join(", ")})`
|
|
7073
|
+
)
|
|
7074
|
+
);
|
|
7075
|
+
return;
|
|
7076
|
+
}
|
|
7077
|
+
resolve4();
|
|
7078
|
+
}
|
|
7079
|
+
};
|
|
7080
|
+
dispatch();
|
|
7081
|
+
});
|
|
7082
|
+
}
|
|
7083
|
+
};
|
|
7084
|
+
|
|
6988
7085
|
// src/deployment/deploy-engine.ts
|
|
6989
|
-
import pLimit from "p-limit";
|
|
6990
7086
|
var InterruptedError = class extends Error {
|
|
6991
7087
|
constructor() {
|
|
6992
7088
|
super("Deployment interrupted by user (Ctrl+C)");
|
|
@@ -7119,6 +7215,7 @@ var DeployEngine = class _DeployEngine {
|
|
|
7119
7215
|
template,
|
|
7120
7216
|
currentState,
|
|
7121
7217
|
changes,
|
|
7218
|
+
dag,
|
|
7122
7219
|
executionLevels,
|
|
7123
7220
|
stackName,
|
|
7124
7221
|
parameterValues,
|
|
@@ -7151,13 +7248,16 @@ var DeployEngine = class _DeployEngine {
|
|
|
7151
7248
|
}
|
|
7152
7249
|
}
|
|
7153
7250
|
/**
|
|
7154
|
-
* Execute deployment by processing resources
|
|
7251
|
+
* Execute deployment by processing resources via event-driven DAG dispatch.
|
|
7155
7252
|
*
|
|
7156
|
-
*
|
|
7157
|
-
*
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7253
|
+
* - CREATE/UPDATE follow forward dependency order (a node starts as soon as
|
|
7254
|
+
* ALL of its dependencies are completed — does not wait for unrelated
|
|
7255
|
+
* siblings in the same "level")
|
|
7256
|
+
* - DELETE follows reverse dependency order (a node starts as soon as all
|
|
7257
|
+
* resources that depend ON it have finished deleting)
|
|
7258
|
+
*/
|
|
7259
|
+
async executeDeployment(template, currentState, changes, dag, executionLevels, stackName, parameterValues, conditions, currentEtag, progress) {
|
|
7260
|
+
const concurrency = this.options.concurrency;
|
|
7161
7261
|
const newResources = { ...currentState.resources };
|
|
7162
7262
|
const actualCounts = { created: 0, updated: 0, deleted: 0, skipped: 0 };
|
|
7163
7263
|
const completedOperations = [];
|
|
@@ -7188,32 +7288,36 @@ var DeployEngine = class _DeployEngine {
|
|
|
7188
7288
|
Array.from(changes.entries()).filter(([_, change]) => change.changeType === "DELETE").map(([logicalId]) => logicalId)
|
|
7189
7289
|
);
|
|
7190
7290
|
try {
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
}
|
|
7195
|
-
const levelNodes = executionLevels[levelIndex];
|
|
7196
|
-
if (!levelNodes)
|
|
7291
|
+
const createUpdateIds = [];
|
|
7292
|
+
for (const [id, change] of changes.entries()) {
|
|
7293
|
+
if (deleteChanges.has(id))
|
|
7197
7294
|
continue;
|
|
7198
|
-
|
|
7199
|
-
if (deleteChanges.has(id))
|
|
7200
|
-
return false;
|
|
7201
|
-
const change = changes.get(id);
|
|
7202
|
-
return !!change && change.changeType !== "NO_CHANGE";
|
|
7203
|
-
});
|
|
7204
|
-
if (level.length === 0)
|
|
7295
|
+
if (change.changeType === "NO_CHANGE")
|
|
7205
7296
|
continue;
|
|
7297
|
+
createUpdateIds.push(id);
|
|
7298
|
+
}
|
|
7299
|
+
if (createUpdateIds.length > 0) {
|
|
7206
7300
|
this.logger.info(
|
|
7207
|
-
`
|
|
7301
|
+
`Deploying ${createUpdateIds.length} resource(s) (DAG: ${executionLevels.length} levels, max parallel: ${concurrency})`
|
|
7208
7302
|
);
|
|
7209
|
-
const
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7303
|
+
const createUpdateExecutor = new DagExecutor();
|
|
7304
|
+
const provisionable = new Set(createUpdateIds);
|
|
7305
|
+
for (const id of createUpdateIds) {
|
|
7306
|
+
const allDeps = this.dagBuilder.getDirectDependencies(dag, id);
|
|
7307
|
+
const deps = new Set(allDeps.filter((d) => provisionable.has(d)));
|
|
7308
|
+
createUpdateExecutor.add({
|
|
7309
|
+
id,
|
|
7310
|
+
dependencies: deps,
|
|
7311
|
+
state: "pending",
|
|
7312
|
+
data: changes.get(id)
|
|
7313
|
+
});
|
|
7314
|
+
}
|
|
7315
|
+
try {
|
|
7316
|
+
await createUpdateExecutor.execute(
|
|
7317
|
+
concurrency,
|
|
7318
|
+
async (node) => {
|
|
7319
|
+
const logicalId = node.id;
|
|
7320
|
+
const change = node.data;
|
|
7217
7321
|
const previousState = currentState.resources[logicalId] ? { ...currentState.resources[logicalId] } : void 0;
|
|
7218
7322
|
try {
|
|
7219
7323
|
await this.provisionResource(
|
|
@@ -7240,30 +7344,36 @@ var DeployEngine = class _DeployEngine {
|
|
|
7240
7344
|
properties: newResources[logicalId]?.properties
|
|
7241
7345
|
});
|
|
7242
7346
|
saveStateAfterResource(logicalId);
|
|
7243
|
-
}
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7347
|
+
},
|
|
7348
|
+
() => this.interrupted
|
|
7349
|
+
);
|
|
7350
|
+
} finally {
|
|
7351
|
+
await saveChain;
|
|
7352
|
+
}
|
|
7353
|
+
if (this.interrupted && this.hasPending(createUpdateExecutor)) {
|
|
7354
|
+
throw new InterruptedError();
|
|
7250
7355
|
}
|
|
7251
7356
|
}
|
|
7252
7357
|
if (deleteChanges.size > 0) {
|
|
7253
7358
|
this.logger.info(`Deleting ${deleteChanges.size} resource(s)`);
|
|
7254
|
-
const
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7359
|
+
const deleteDeps = this.buildDeletionDependencies(deleteChanges, currentState);
|
|
7360
|
+
const deleteExecutor = new DagExecutor();
|
|
7361
|
+
for (const id of deleteChanges) {
|
|
7362
|
+
deleteExecutor.add({
|
|
7363
|
+
id,
|
|
7364
|
+
dependencies: deleteDeps.get(id) ?? /* @__PURE__ */ new Set(),
|
|
7365
|
+
state: "pending",
|
|
7366
|
+
data: changes.get(id)
|
|
7367
|
+
});
|
|
7368
|
+
}
|
|
7369
|
+
try {
|
|
7370
|
+
await deleteExecutor.execute(
|
|
7371
|
+
concurrency,
|
|
7372
|
+
async (node) => {
|
|
7373
|
+
const logicalId = node.id;
|
|
7374
|
+
const change = node.data;
|
|
7375
|
+
const previousState = currentState.resources[logicalId] ? { ...currentState.resources[logicalId] } : void 0;
|
|
7376
|
+
try {
|
|
7267
7377
|
await this.provisionResource(
|
|
7268
7378
|
logicalId,
|
|
7269
7379
|
change,
|
|
@@ -7275,23 +7385,25 @@ var DeployEngine = class _DeployEngine {
|
|
|
7275
7385
|
actualCounts,
|
|
7276
7386
|
progress
|
|
7277
7387
|
);
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7388
|
+
} catch (provisionError) {
|
|
7389
|
+
this.interrupted = true;
|
|
7390
|
+
throw provisionError;
|
|
7391
|
+
}
|
|
7392
|
+
completedOperations.push({
|
|
7393
|
+
logicalId,
|
|
7394
|
+
changeType: "DELETE",
|
|
7395
|
+
resourceType: change.resourceType,
|
|
7396
|
+
previousState
|
|
7397
|
+
});
|
|
7398
|
+
saveStateAfterResource(logicalId);
|
|
7399
|
+
},
|
|
7400
|
+
() => this.interrupted
|
|
7287
7401
|
);
|
|
7402
|
+
} finally {
|
|
7288
7403
|
await saveChain;
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
);
|
|
7292
|
-
if (deleteFailures.length > 0) {
|
|
7293
|
-
throw deleteFailures[0].reason;
|
|
7294
|
-
}
|
|
7404
|
+
}
|
|
7405
|
+
if (this.interrupted && this.hasPending(deleteExecutor)) {
|
|
7406
|
+
throw new InterruptedError();
|
|
7295
7407
|
}
|
|
7296
7408
|
}
|
|
7297
7409
|
} catch (error) {
|
|
@@ -7385,12 +7497,12 @@ var DeployEngine = class _DeployEngine {
|
|
|
7385
7497
|
* - UPDATE → update back to previous properties
|
|
7386
7498
|
* - DELETE → cannot rollback (resource already deleted), log warning
|
|
7387
7499
|
*
|
|
7388
|
-
* Resources
|
|
7389
|
-
* (e.g., IAM Policy depends on IAM Role). When rolling back
|
|
7390
|
-
* dependent resources must be deleted before their
|
|
7391
|
-
* sorts CREATE rollback operations using dependency
|
|
7392
|
-
* then processes UPDATE/DELETE rollbacks, and finally
|
|
7393
|
-
* rollback deletions.
|
|
7500
|
+
* Resources completed concurrently in the dispatcher may have dependencies
|
|
7501
|
+
* between them (e.g., IAM Policy depends on IAM Role). When rolling back
|
|
7502
|
+
* CREATEs (deleting), dependent resources must be deleted before their
|
|
7503
|
+
* dependencies. This method sorts CREATE rollback operations using dependency
|
|
7504
|
+
* information from state, then processes UPDATE/DELETE rollbacks, and finally
|
|
7505
|
+
* processes sorted CREATE rollback deletions.
|
|
7394
7506
|
*/
|
|
7395
7507
|
async performRollback(completedOperations, stateResources, _stackName) {
|
|
7396
7508
|
if (completedOperations.length === 0) {
|
|
@@ -7423,7 +7535,7 @@ var DeployEngine = class _DeployEngine {
|
|
|
7423
7535
|
* Sort CREATE rollback operations so that resources depending on others
|
|
7424
7536
|
* are deleted first (reverse dependency order).
|
|
7425
7537
|
*
|
|
7426
|
-
* Uses state dependencies to
|
|
7538
|
+
* Uses state dependencies to determine reverse-dependency order, similar to buildDeletionDependencies.
|
|
7427
7539
|
*/
|
|
7428
7540
|
sortRollbackCreates(createOps, stateResources) {
|
|
7429
7541
|
const opMap = /* @__PURE__ */ new Map();
|
|
@@ -7845,17 +7957,30 @@ var DeployEngine = class _DeployEngine {
|
|
|
7845
7957
|
"AWS::EC2::SecurityGroup": ["AWS::EC2::SecurityGroupIngress", "AWS::EC2::SecurityGroupEgress"]
|
|
7846
7958
|
};
|
|
7847
7959
|
/**
|
|
7848
|
-
* Build
|
|
7849
|
-
*
|
|
7960
|
+
* Build a per-resource map of "must be deleted before me" dependencies for
|
|
7961
|
+
* the DELETE phase, derived from state-recorded dependencies plus implicit
|
|
7962
|
+
* type-based ordering rules.
|
|
7963
|
+
*
|
|
7964
|
+
* For a resource X, the returned set contains every resource Y such that Y
|
|
7965
|
+
* must finish deleting before X starts — i.e., Y depends on X (or is otherwise
|
|
7966
|
+
* required to vanish first per implicit type rules).
|
|
7967
|
+
*/
|
|
7968
|
+
/**
|
|
7969
|
+
* Returns true if the executor still has un-started pending nodes —
|
|
7970
|
+
* used to distinguish "SIGINT cancelled real work" from "SIGINT landed
|
|
7971
|
+
* after all nodes already completed" (the latter should not error).
|
|
7850
7972
|
*/
|
|
7851
|
-
|
|
7973
|
+
hasPending(executor) {
|
|
7974
|
+
for (const node of executor.values()) {
|
|
7975
|
+
if (node.state === "pending")
|
|
7976
|
+
return true;
|
|
7977
|
+
}
|
|
7978
|
+
return false;
|
|
7979
|
+
}
|
|
7980
|
+
buildDeletionDependencies(deleteIds, state) {
|
|
7852
7981
|
const dependedBy = /* @__PURE__ */ new Map();
|
|
7853
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
7854
7982
|
for (const id of deleteIds) {
|
|
7855
|
-
|
|
7856
|
-
dependedBy.set(id, /* @__PURE__ */ new Set());
|
|
7857
|
-
if (!inDegree.has(id))
|
|
7858
|
-
inDegree.set(id, 0);
|
|
7983
|
+
dependedBy.set(id, /* @__PURE__ */ new Set());
|
|
7859
7984
|
}
|
|
7860
7985
|
for (const id of deleteIds) {
|
|
7861
7986
|
const resource = state.resources[id];
|
|
@@ -7864,38 +7989,11 @@ var DeployEngine = class _DeployEngine {
|
|
|
7864
7989
|
for (const dep of resource.dependencies) {
|
|
7865
7990
|
if (!deleteIds.has(dep))
|
|
7866
7991
|
continue;
|
|
7867
|
-
if (!dependedBy.has(dep))
|
|
7868
|
-
dependedBy.set(dep, /* @__PURE__ */ new Set());
|
|
7869
7992
|
dependedBy.get(dep).add(id);
|
|
7870
|
-
inDegree.set(id, (inDegree.get(id) ?? 0) + 1);
|
|
7871
7993
|
}
|
|
7872
7994
|
}
|
|
7873
7995
|
this.addImplicitDeleteDependencies(deleteIds, state, dependedBy);
|
|
7874
|
-
|
|
7875
|
-
let remaining = new Set(deleteIds);
|
|
7876
|
-
while (remaining.size > 0) {
|
|
7877
|
-
const level = [];
|
|
7878
|
-
for (const id of remaining) {
|
|
7879
|
-
const dependents = dependedBy.get(id);
|
|
7880
|
-
const hasPendingDependents = dependents ? [...dependents].some((d) => remaining.has(d)) : false;
|
|
7881
|
-
if (!hasPendingDependents) {
|
|
7882
|
-
level.push(id);
|
|
7883
|
-
}
|
|
7884
|
-
}
|
|
7885
|
-
if (level.length === 0) {
|
|
7886
|
-
this.logger.warn(
|
|
7887
|
-
`Circular dependency detected in delete order, deleting remaining ${remaining.size} resources`
|
|
7888
|
-
);
|
|
7889
|
-
levels.push([...remaining]);
|
|
7890
|
-
break;
|
|
7891
|
-
}
|
|
7892
|
-
levels.push(level);
|
|
7893
|
-
remaining = new Set([...remaining].filter((id) => !level.includes(id)));
|
|
7894
|
-
}
|
|
7895
|
-
this.logger.debug(
|
|
7896
|
-
`Delete order: ${levels.length} levels - ${levels.map((l, i) => `L${i + 1}(${l.length})`).join(", ")}`
|
|
7897
|
-
);
|
|
7898
|
-
return levels;
|
|
7996
|
+
return dependedBy;
|
|
7899
7997
|
}
|
|
7900
7998
|
/**
|
|
7901
7999
|
* Add implicit delete dependency edges based on resource type relationships.
|