@go-to-k/cdkd 0.0.3 → 0.1.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 +5 -0
- package/dist/cli.js +149 -10
- package/dist/cli.js.map +3 -3
- package/dist/go-to-k-cdkd-0.1.0.tgz +0 -0
- package/dist/index.js +135 -4
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.0.3.tgz +0 -0
package/README.md
CHANGED
|
@@ -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)
|
|
@@ -364,6 +366,9 @@ cdkd bootstrap \
|
|
|
364
366
|
# Synthesize only
|
|
365
367
|
cdkd synth --app "npx ts-node app.ts"
|
|
366
368
|
|
|
369
|
+
# Deploy from a pre-synthesized cloud assembly directory
|
|
370
|
+
cdkd deploy --app cdk.out
|
|
371
|
+
|
|
367
372
|
# Deploy (single stack auto-detected, reads --app from cdk.json)
|
|
368
373
|
cdkd deploy
|
|
369
374
|
|
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 {
|
|
@@ -3519,6 +3535,11 @@ var TemplateParser = class {
|
|
|
3519
3535
|
|
|
3520
3536
|
// src/analyzer/dag-builder.ts
|
|
3521
3537
|
var { Graph, alg } = graphlib;
|
|
3538
|
+
var IAM_ROLE_POLICY_TYPES = /* @__PURE__ */ new Set([
|
|
3539
|
+
"AWS::IAM::Policy",
|
|
3540
|
+
"AWS::IAM::RolePolicy",
|
|
3541
|
+
"AWS::IAM::ManagedPolicy"
|
|
3542
|
+
]);
|
|
3522
3543
|
var DagBuilder = class {
|
|
3523
3544
|
logger = getLogger().child("DagBuilder");
|
|
3524
3545
|
parser = new TemplateParser();
|
|
@@ -3559,6 +3580,7 @@ var DagBuilder = class {
|
|
|
3559
3580
|
}
|
|
3560
3581
|
}
|
|
3561
3582
|
this.logger.debug(`Dependency graph built: ${resourceIds.length} nodes, ${edgeCount} edges`);
|
|
3583
|
+
edgeCount += this.addCustomResourcePolicyEdges(graph, template);
|
|
3562
3584
|
if (!alg.isAcyclic(graph)) {
|
|
3563
3585
|
const cycles = this.findCycles(graph);
|
|
3564
3586
|
throw new DependencyError(
|
|
@@ -3701,6 +3723,123 @@ var DagBuilder = class {
|
|
|
3701
3723
|
const deps = this.getAllDependencies(graph, resourceA);
|
|
3702
3724
|
return deps.has(resourceB);
|
|
3703
3725
|
}
|
|
3726
|
+
/**
|
|
3727
|
+
* Add implicit edges from IAM::Policy resources to Custom Resources whose
|
|
3728
|
+
* ServiceToken Lambda's execution role those policies attach to.
|
|
3729
|
+
*
|
|
3730
|
+
* Returns the number of edges added.
|
|
3731
|
+
*/
|
|
3732
|
+
addCustomResourcePolicyEdges(graph, template) {
|
|
3733
|
+
const rolePolicies = this.buildRolePoliciesMap(template);
|
|
3734
|
+
if (rolePolicies.size === 0) {
|
|
3735
|
+
return 0;
|
|
3736
|
+
}
|
|
3737
|
+
let added = 0;
|
|
3738
|
+
for (const logicalId of this.parser.getResourceIds(template)) {
|
|
3739
|
+
const resource = this.parser.getResource(template, logicalId);
|
|
3740
|
+
if (!resource || !this.isCustomResourceType(resource.Type)) {
|
|
3741
|
+
continue;
|
|
3742
|
+
}
|
|
3743
|
+
const serviceToken = (resource.Properties ?? {})["ServiceToken"];
|
|
3744
|
+
const lambdaId = this.extractLogicalIdFromReference(serviceToken);
|
|
3745
|
+
if (!lambdaId)
|
|
3746
|
+
continue;
|
|
3747
|
+
const lambdaResource = this.parser.getResource(template, lambdaId);
|
|
3748
|
+
if (!lambdaResource || lambdaResource.Type !== "AWS::Lambda::Function") {
|
|
3749
|
+
continue;
|
|
3750
|
+
}
|
|
3751
|
+
const roleId = this.extractLogicalIdFromReference((lambdaResource.Properties ?? {})["Role"]);
|
|
3752
|
+
if (!roleId)
|
|
3753
|
+
continue;
|
|
3754
|
+
const policies = rolePolicies.get(roleId);
|
|
3755
|
+
if (!policies)
|
|
3756
|
+
continue;
|
|
3757
|
+
for (const policyId of policies) {
|
|
3758
|
+
if (policyId === logicalId)
|
|
3759
|
+
continue;
|
|
3760
|
+
if (!graph.hasNode(policyId))
|
|
3761
|
+
continue;
|
|
3762
|
+
if (graph.hasEdge(policyId, logicalId))
|
|
3763
|
+
continue;
|
|
3764
|
+
graph.setEdge(policyId, logicalId);
|
|
3765
|
+
added++;
|
|
3766
|
+
this.logger.debug(
|
|
3767
|
+
`Added implicit edge (custom resource policy): ${policyId} -> ${logicalId}`
|
|
3768
|
+
);
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
if (added > 0) {
|
|
3772
|
+
this.logger.debug(`Added ${added} implicit edges for custom resource policies`);
|
|
3773
|
+
}
|
|
3774
|
+
return added;
|
|
3775
|
+
}
|
|
3776
|
+
isCustomResourceType(type) {
|
|
3777
|
+
return type === "AWS::CloudFormation::CustomResource" || type.startsWith("Custom::");
|
|
3778
|
+
}
|
|
3779
|
+
/**
|
|
3780
|
+
* Build a map of roleLogicalId -> Set<policyLogicalId> by scanning the
|
|
3781
|
+
* template for IAM::Policy / IAM::RolePolicy / IAM::ManagedPolicy resources
|
|
3782
|
+
* that attach to a role by Ref/GetAtt.
|
|
3783
|
+
*/
|
|
3784
|
+
buildRolePoliciesMap(template) {
|
|
3785
|
+
const map = /* @__PURE__ */ new Map();
|
|
3786
|
+
for (const [policyId, resource] of Object.entries(template.Resources)) {
|
|
3787
|
+
if (!IAM_ROLE_POLICY_TYPES.has(resource.Type))
|
|
3788
|
+
continue;
|
|
3789
|
+
for (const roleId of this.extractAttachedRoleIds(resource)) {
|
|
3790
|
+
let set = map.get(roleId);
|
|
3791
|
+
if (!set) {
|
|
3792
|
+
set = /* @__PURE__ */ new Set();
|
|
3793
|
+
map.set(roleId, set);
|
|
3794
|
+
}
|
|
3795
|
+
set.add(policyId);
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
return map;
|
|
3799
|
+
}
|
|
3800
|
+
/**
|
|
3801
|
+
* Extract the logical IDs of IAM::Role resources that a policy resource
|
|
3802
|
+
* attaches to. Supports both `Roles: [Ref]` (IAM::Policy / IAM::ManagedPolicy)
|
|
3803
|
+
* and `RoleName: Ref` (IAM::RolePolicy) shapes.
|
|
3804
|
+
*/
|
|
3805
|
+
extractAttachedRoleIds(resource) {
|
|
3806
|
+
const ids = [];
|
|
3807
|
+
const props = resource.Properties ?? {};
|
|
3808
|
+
const roles = props["Roles"];
|
|
3809
|
+
if (Array.isArray(roles)) {
|
|
3810
|
+
for (const entry of roles) {
|
|
3811
|
+
const id = this.extractLogicalIdFromReference(entry);
|
|
3812
|
+
if (id)
|
|
3813
|
+
ids.push(id);
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
const roleName = props["RoleName"];
|
|
3817
|
+
const roleNameId = this.extractLogicalIdFromReference(roleName);
|
|
3818
|
+
if (roleNameId)
|
|
3819
|
+
ids.push(roleNameId);
|
|
3820
|
+
return ids;
|
|
3821
|
+
}
|
|
3822
|
+
/**
|
|
3823
|
+
* Extract a resource logical ID from a direct Ref or Fn::GetAtt expression.
|
|
3824
|
+
* Returns undefined for literals or intrinsics we can't statically resolve
|
|
3825
|
+
* (Fn::Join, Fn::ImportValue, etc.) — callers should skip in that case.
|
|
3826
|
+
*/
|
|
3827
|
+
extractLogicalIdFromReference(value) {
|
|
3828
|
+
if (typeof value !== "object" || value === null)
|
|
3829
|
+
return void 0;
|
|
3830
|
+
const obj = value;
|
|
3831
|
+
if ("Ref" in obj && typeof obj["Ref"] === "string") {
|
|
3832
|
+
const ref = obj["Ref"];
|
|
3833
|
+
return ref.startsWith("AWS::") ? void 0 : ref;
|
|
3834
|
+
}
|
|
3835
|
+
if ("Fn::GetAtt" in obj) {
|
|
3836
|
+
const getAtt = obj["Fn::GetAtt"];
|
|
3837
|
+
if (Array.isArray(getAtt) && typeof getAtt[0] === "string") {
|
|
3838
|
+
return getAtt[0];
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
return void 0;
|
|
3842
|
+
}
|
|
3704
3843
|
};
|
|
3705
3844
|
|
|
3706
3845
|
// src/analyzer/replacement-rules.ts
|
|
@@ -26588,7 +26727,7 @@ Resources to be deleted (${resourceCount}):`);
|
|
|
26588
26727
|
for (const [logicalId, resource] of Object.entries(currentState.resources)) {
|
|
26589
26728
|
logger.info(` - ${logicalId} (${resource.resourceType})`);
|
|
26590
26729
|
}
|
|
26591
|
-
if (!options.force) {
|
|
26730
|
+
if (!options.yes && !options.force) {
|
|
26592
26731
|
const rl = readline.createInterface({
|
|
26593
26732
|
input: process.stdin,
|
|
26594
26733
|
output: process.stdout
|
|
@@ -26890,7 +27029,7 @@ function reorderArgs(argv) {
|
|
|
26890
27029
|
}
|
|
26891
27030
|
async function main() {
|
|
26892
27031
|
const program = new Command8();
|
|
26893
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.0
|
|
27032
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.1.0");
|
|
26894
27033
|
program.addCommand(createBootstrapCommand());
|
|
26895
27034
|
program.addCommand(createSynthCommand());
|
|
26896
27035
|
program.addCommand(createDeployCommand());
|