@go-to-k/cdkd 0.150.0 → 0.150.2

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/dist/cli.js CHANGED
@@ -35832,6 +35832,18 @@ async function confirmPrompt$2(prompt) {
35832
35832
  * itself; we do not try to maintain a closed allowlist here.
35833
35833
  */
35834
35834
  const NEVER_IMPORTABLE_TYPES = new Set(["AWS::CDK::Metadata"]);
35835
+ /**
35836
+ * Per-non-root-child lock-acquisition retry budget for the nested-stack
35837
+ * export loop (`runPerStackImportLoop`). `acquireLockWithRetry` retries
35838
+ * `maxRetries` times with `retryDelay` between attempts, so the total wait
35839
+ * ceiling is `maxRetries * retryDelay` ≈ 30s. A genuinely-active concurrent
35840
+ * cdkd run on a child is awaited briefly instead of failing instantly;
35841
+ * `acquireLock`'s own expired-lock auto-cleanup (30-min TTL) covers the
35842
+ * crashed-previous-run case. 30s keeps an interactive `cdkd export` snappy
35843
+ * even across a multi-level tree (each child waits at most this ceiling).
35844
+ */
35845
+ const CHILD_LOCK_RETRY_MAX_ATTEMPTS = 6;
35846
+ const CHILD_LOCK_RETRY_DELAY_MS = 5e3;
35835
35847
  function isNeverImportableType(resourceType) {
35836
35848
  if (NEVER_IMPORTABLE_TYPES.has(resourceType)) return true;
35837
35849
  if (isCustomResourceType(resourceType)) return true;
@@ -36430,10 +36442,22 @@ function flattenCdkdStateTreeLeafFirst(tree) {
36430
36442
  * Top-level cdkd stack names that already conform to CFn's character set
36431
36443
  * are passed through unchanged — `cdkd2cfnStackName('MyApp') === 'MyApp'`.
36432
36444
  *
36445
+ * The post-substitution result is validated against the same CFn
36446
+ * stack-name regex `parseCfnChildStackNameOverrides` enforces
36447
+ * (`[a-zA-Z][-a-zA-Z0-9]*`). The `~` → `-` swap only handles the v6
36448
+ * separator; a cdkd name that is otherwise CFn-illegal (leading digit /
36449
+ * underscore, embedded `.` or `/`, etc.) would still pass through and
36450
+ * surface as an opaque CFn API rejection deep inside the IMPORT loop.
36451
+ * Throwing here surfaces the problem at orchestrator pre-flight time with
36452
+ * an actionable pointer at the `--cfn-stack-name` / `--cfn-child-stack-name`
36453
+ * override escape hatch.
36454
+ *
36433
36455
  * Issue #464 design §9 Q8.
36434
36456
  */
36435
36457
  function cdkd2cfnStackName(cdkdStackName) {
36436
- return cdkdStackName.replace(/~/g, "-");
36458
+ const cfnName = cdkdStackName.replace(/~/g, "-");
36459
+ if (!/^[a-zA-Z][-a-zA-Z0-9]*$/.test(cfnName)) throw new Error(`cdkd stack name '${cdkdStackName}' maps to CFn stack name '${cfnName}', which violates the CloudFormation stack-name constraint [a-zA-Z][-a-zA-Z0-9]* (must start with a letter; no '_', '.', '/', or other special characters). Supply an explicit name via --cfn-stack-name (root) or --cfn-child-stack-name '<cdkdName>=<cfnName>' (nested child).`);
36460
+ return cfnName;
36437
36461
  }
36438
36462
  /**
36439
36463
  * Parse the repeatable `--cfn-child-stack-name <cdkdName>=<cfnName>` CLI
@@ -37327,10 +37351,15 @@ async function collectImportFailureSummary(cfnClient, stackName) {
37327
37351
  * `cdkd:nested-export-flip` tag is the only delta). This is the canonical
37328
37352
  * AWS workaround for the IMPORT_COMPLETE → UPDATE_COMPLETE transition.
37329
37353
  *
37330
- * Idempotency: each invocation uses a fresh ISO 8601 timestamp value so
37331
- * AWS never returns "No updates are to be performed" (which would leave
37332
- * the stack stuck in `IMPORT_COMPLETE`). The tag accumulates across
37333
- * retries harmless and easy to reap manually under the `cdkd:` prefix.
37354
+ * Idempotency: each invocation uses a fresh ISO 8601 timestamp PLUS an
37355
+ * 8-char random UUID slice as the tag value so AWS never returns "No
37356
+ * updates are to be performed" (which would leave the stack stuck in
37357
+ * `IMPORT_COMPLETE`). The timestamp alone is insufficient two rapid
37358
+ * back-to-back flips on the same stack within the same millisecond (e.g.
37359
+ * a retry after a transient SDK error) would otherwise emit identical
37360
+ * values; the UUID suffix guarantees uniqueness regardless of clock
37361
+ * resolution. The tag accumulates across retries — harmless and easy to
37362
+ * reap manually under the `cdkd:` prefix.
37334
37363
  *
37335
37364
  * Exported for unit testing.
37336
37365
  */
@@ -37340,7 +37369,7 @@ async function flipStackToUpdateComplete(cfnClient, cfnStackName) {
37340
37369
  UsePreviousTemplate: true,
37341
37370
  Tags: [{
37342
37371
  Key: "cdkd:nested-export-flip",
37343
- Value: (/* @__PURE__ */ new Date()).toISOString()
37372
+ Value: `${(/* @__PURE__ */ new Date()).toISOString()}-${randomUUID().slice(0, 8)}`
37344
37373
  }],
37345
37374
  Capabilities: [
37346
37375
  "CAPABILITY_IAM",
@@ -37566,7 +37595,11 @@ async function runPerStackImportLoop(args) {
37566
37595
  try {
37567
37596
  for (const node of leafFirst) {
37568
37597
  if (node.stackName === rootStackName) continue;
37569
- if (!await deps.lockManager.acquireLock(node.stackName, node.region, deps.lockOwner, "export")) throw new Error(`Could not acquire lock for nested-stack child '${node.stackName}' (${node.region}) — another cdkd process holds it. Wait for it to finish, or run 'cdkd force-unlock ${node.stackName}' if you are certain no other process is active. No CloudFormation changeset has been submitted; cdkd state is unchanged.`);
37598
+ try {
37599
+ await deps.lockManager.acquireLockWithRetry(node.stackName, node.region, deps.lockOwner, "export", CHILD_LOCK_RETRY_MAX_ATTEMPTS, CHILD_LOCK_RETRY_DELAY_MS);
37600
+ } catch (err) {
37601
+ throw new Error(`Could not acquire lock for nested-stack child '${node.stackName}' (${node.region}) — another cdkd process held it through the retry window. Wait for it to finish, or run 'cdkd force-unlock ${node.stackName}' if you are certain no other process is active. No CloudFormation changeset has been submitted; cdkd state is unchanged.`, { cause: err instanceof Error ? err : void 0 });
37602
+ }
37570
37603
  acquiredChildLocks.push({
37571
37604
  stackName: node.stackName,
37572
37605
  region: node.region
@@ -37575,6 +37608,7 @@ async function runPerStackImportLoop(args) {
37575
37608
  const cfnArnByCdkdName = /* @__PURE__ */ new Map();
37576
37609
  const uploadCleanups = [];
37577
37610
  const importedStacks = [];
37611
+ const sessionIntrinsicSkipped = /* @__PURE__ */ new Map();
37578
37612
  try {
37579
37613
  for (let i = 0; i < perStackPlans.length; i++) {
37580
37614
  const plan = perStackPlans[i];
@@ -37588,7 +37622,10 @@ async function runPerStackImportLoop(args) {
37588
37622
  const parentPlan = perStackPlans.find((p) => p.cdkdName === parentStackName);
37589
37623
  if (!parentPlan) throw new Error(`runPerStackImportLoop: child '${plan.cdkdName}' references parent '${parentStackName}' which is not in the per-stack plan list — tree shape is inconsistent.`);
37590
37624
  const extracted = extractChildImportParameters(parentPlan.template, parentLogicalId);
37591
- if (extracted.intrinsicSkipped.length > 0) logger.warn(` Child '${plan.cdkdName}': skipping intrinsic-valued Parameter(s) ${extracted.intrinsicSkipped.join(", ")} (intrinsic-resolution at leaf-IMPORT time is a deferred follow-up). The child template's Parameter Default values must cover these — otherwise CFn will reject the IMPORT.`);
37625
+ if (extracted.intrinsicSkipped.length > 0) {
37626
+ sessionIntrinsicSkipped.set(plan.cdkdName, extracted.intrinsicSkipped);
37627
+ logger.warn(` Child '${plan.cdkdName}': skipping intrinsic-valued Parameter(s) ${extracted.intrinsicSkipped.join(", ")} (intrinsic-resolution at leaf-IMPORT time is a deferred follow-up). The child template's Parameter Default values must cover these — otherwise CFn will reject the IMPORT.`);
37628
+ }
37592
37629
  stackParameters = extracted.params;
37593
37630
  }
37594
37631
  const phase1ATemplate = filterTemplateForImport(plan.template, plan.phase1Imports);
@@ -37695,6 +37732,10 @@ async function runPerStackImportLoop(args) {
37695
37732
  const lines = stateDeletionFailures.map((f) => ` - cdkd/${f.stackName}/${f.region}/state.json: ${f.reason}`).join("\n");
37696
37733
  throw new Error(`${stateDeletionFailures.length} cdkd state record(s) could not be deleted after a successful per-stack IMPORT loop. Every stack in the tree IS CFn-managed (the migration succeeded AWS-side); orphan state remains under:\n${lines}\nRecover with 'cdkd state orphan <stack>' per record.`);
37697
37734
  }
37735
+ if (sessionIntrinsicSkipped.size > 0) {
37736
+ const detail = [...sessionIntrinsicSkipped.entries()].map(([cdkdName, params]) => `${cdkdName} (${params.join(", ")})`).join("; ");
37737
+ logger.warn(`${sessionIntrinsicSkipped.size} stack(s) had intrinsic-valued Parameter(s) skipped at IMPORT time: ${detail}. Verify each child template's Parameter Default values cover these before the first 'cdk deploy' — intrinsic resolution at leaf-IMPORT time is a deferred follow-up.`);
37738
+ }
37698
37739
  return {
37699
37740
  outcome: "success",
37700
37741
  importedStacks
@@ -53688,6 +53729,7 @@ async function runEcsTask(task, options, state) {
53688
53729
  platformOverride: options.platformOverride,
53689
53730
  region: options.region,
53690
53731
  sidecarIp: state.network.sidecarIp,
53732
+ ...options.skipHostPortPublish ? { skipHostPortPublish: true } : {},
53691
53733
  ...options.addHostFlags && options.addHostFlags.length > 0 ? { addHostFlags: options.addHostFlags } : {},
53692
53734
  ...(options.networkAliasesByContainer?.get(container.name)?.length ?? 0) > 0 ? { networkAliases: options.networkAliasesByContainer.get(container.name) } : {}
53693
53735
  }));
@@ -54017,7 +54059,7 @@ function buildDockerRunArgs(opts) {
54017
54059
  if (opts.addHostFlags && opts.addHostFlags.length > 0) for (const f of opts.addHostFlags) args.push(f);
54018
54060
  if (opts.platformOverride) args.push("--platform", opts.platformOverride);
54019
54061
  else if (task.runtimePlatform) args.push("--platform", task.runtimePlatform.cpuArchitecture === "ARM64" ? "linux/arm64" : "linux/amd64");
54020
- for (const pm of container.portMappings) {
54062
+ if (!opts.skipHostPortPublish) for (const pm of container.portMappings) {
54021
54063
  const hostPort = pm.hostPort ?? pm.containerPort;
54022
54064
  args.push("-p", `${containerHost}:${hostPort}:${pm.containerPort}/${pm.protocol}`);
54023
54065
  }
@@ -54910,10 +54952,12 @@ async function bootReplica(service, options, instance) {
54910
54952
  const addHostFlags = options.discovery?.registry ? options.discovery.registry.buildAddHostFlags(ownerKeyPrefix) : [];
54911
54953
  const sharedNetwork = options.discovery?.sharedNetwork;
54912
54954
  const networkAliasesByContainer = buildNetworkAliasesByContainer(service);
54955
+ const skipHostPortPublish = computeReplicaCount(service.desiredCount, options.maxTasks) > 1;
54913
54956
  const perReplicaTaskOptions = {
54914
54957
  ...options.taskOptions,
54915
54958
  cluster: perReplicaCluster,
54916
54959
  detach: true,
54960
+ ...skipHostPortPublish ? { skipHostPortPublish: true } : {},
54917
54961
  ...sharedNetwork ? { existingNetwork: sharedNetwork } : { subnetOctet: pickSubnetOctet(instance.index) },
54918
54962
  ...addHostFlags.length > 0 ? { addHostFlags } : {},
54919
54963
  ...networkAliasesByContainer.size > 0 ? { networkAliasesByContainer } : {}
@@ -57585,7 +57629,7 @@ function reorderArgs(argv) {
57585
57629
  */
57586
57630
  async function main() {
57587
57631
  const program = new Command();
57588
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.150.0");
57632
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.150.2");
57589
57633
  program.addCommand(createBootstrapCommand());
57590
57634
  program.addCommand(createSynthCommand());
57591
57635
  program.addCommand(createListCommand());