@go-to-k/cdkd 0.82.1 → 0.83.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/dist/cli.js CHANGED
@@ -70040,6 +70040,76 @@ import { dirname as dirname7 } from "node:path";
70040
70040
  import * as path2 from "node:path";
70041
70041
  import { Command as Command16, Option as Option9 } from "commander";
70042
70042
 
70043
+ // src/cli/commands/local-state-loader.ts
70044
+ init_aws_clients();
70045
+ async function loadStateForStack(stackName, synthRegion, opts) {
70046
+ const logger = getLogger();
70047
+ const prefix = opts.logPrefix ?? "--from-state";
70048
+ const region = opts.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? synthRegion ?? "us-east-1";
70049
+ let stateBucket;
70050
+ try {
70051
+ stateBucket = await resolveStateBucketWithDefault(opts.stateBucket, region);
70052
+ } catch (err) {
70053
+ logger.warn(
70054
+ `${prefix}: could not resolve state bucket: ${err instanceof Error ? err.message : String(err)}. Falling back.`
70055
+ );
70056
+ return void 0;
70057
+ }
70058
+ const awsClients = new AwsClients({
70059
+ ...opts.region !== void 0 && { region: opts.region },
70060
+ ...opts.profile !== void 0 && { profile: opts.profile }
70061
+ });
70062
+ setAwsClients(awsClients);
70063
+ try {
70064
+ const stateConfig = { bucket: stateBucket, prefix: opts.statePrefix };
70065
+ const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
70066
+ ...opts.region !== void 0 && { region: opts.region },
70067
+ ...opts.profile !== void 0 && { profile: opts.profile }
70068
+ });
70069
+ await stateBackend.verifyBucketExists();
70070
+ const refs = (await stateBackend.listStacks()).filter((r) => r.stackName === stackName);
70071
+ if (refs.length === 0) {
70072
+ logger.warn(
70073
+ `${prefix}: no cdkd state found for stack '${stackName}' in bucket '${stateBucket}'. Was it deployed via 'cdkd deploy'? Falling back.`
70074
+ );
70075
+ return void 0;
70076
+ }
70077
+ let targetRegion;
70078
+ if (opts.stackRegion) {
70079
+ const found = refs.find((r) => r.region === opts.stackRegion);
70080
+ if (!found) {
70081
+ const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
70082
+ logger.warn(
70083
+ `${prefix}: stack '${stackName}' has no state in region '${opts.stackRegion}' (available: ${seen}). Falling back.`
70084
+ );
70085
+ return void 0;
70086
+ }
70087
+ targetRegion = opts.stackRegion;
70088
+ } else if (synthRegion && refs.some((r) => r.region === synthRegion)) {
70089
+ targetRegion = synthRegion;
70090
+ } else if (refs.length === 1) {
70091
+ targetRegion = refs[0].region ?? synthRegion ?? region;
70092
+ } else {
70093
+ const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
70094
+ logger.warn(
70095
+ `${prefix}: stack '${stackName}' has state in multiple regions (${seen}). Re-run with --stack-region <region>. Falling back.`
70096
+ );
70097
+ return void 0;
70098
+ }
70099
+ const stateData = await stateBackend.getState(stackName, targetRegion);
70100
+ if (!stateData) {
70101
+ logger.warn(
70102
+ `${prefix}: state record for '${stackName}' (${targetRegion}) returned empty. Falling back.`
70103
+ );
70104
+ return void 0;
70105
+ }
70106
+ logger.debug(`${prefix}: loaded state for ${stackName} (${targetRegion})`);
70107
+ return { state: stateData.state, region: targetRegion };
70108
+ } finally {
70109
+ awsClients.destroy();
70110
+ }
70111
+ }
70112
+
70043
70113
  // src/local/lambda-resolver.ts
70044
70114
  import { existsSync as existsSync4, statSync as statSync3 } from "node:fs";
70045
70115
  import { dirname, isAbsolute, resolve as resolve4 } from "node:path";
@@ -71217,9 +71287,6 @@ function extractHashFromImageUri(imageUri) {
71217
71287
  return match?.[1];
71218
71288
  }
71219
71289
 
71220
- // src/cli/commands/local-invoke.ts
71221
- init_aws_clients();
71222
-
71223
71290
  // src/utils/single-flight.ts
71224
71291
  function singleFlight(fn, onError) {
71225
71292
  let promise;
@@ -76646,6 +76713,83 @@ var EcsTaskResolutionError = class _EcsTaskResolutionError extends Error {
76646
76713
  Object.setPrototypeOf(this, _EcsTaskResolutionError.prototype);
76647
76714
  }
76648
76715
  };
76716
+ function derivePartitionAndUrlSuffix(region) {
76717
+ if (region.startsWith("cn-"))
76718
+ return { partition: "aws-cn", urlSuffix: "amazonaws.com.cn" };
76719
+ if (region.startsWith("us-gov-"))
76720
+ return { partition: "aws-us-gov", urlSuffix: "amazonaws.com" };
76721
+ if (region.startsWith("us-iso-"))
76722
+ return { partition: "aws-iso", urlSuffix: "c2s.ic.gov" };
76723
+ if (region.startsWith("us-isob-"))
76724
+ return { partition: "aws-iso-b", urlSuffix: "sc2s.sgov.gov" };
76725
+ return { partition: "aws", urlSuffix: "amazonaws.com" };
76726
+ }
76727
+ function detectEcsImageResolutionNeeds(stack) {
76728
+ const resources = stack.template.Resources ?? {};
76729
+ let needsPseudoParameters = false;
76730
+ let needsStateResources = false;
76731
+ for (const res of Object.values(resources)) {
76732
+ if (res.Type !== "AWS::ECS::TaskDefinition")
76733
+ continue;
76734
+ const props = res.Properties ?? {};
76735
+ const containers = Array.isArray(props["ContainerDefinitions"]) ? props["ContainerDefinitions"] : [];
76736
+ for (const c of containers) {
76737
+ if (!c || typeof c !== "object")
76738
+ continue;
76739
+ const image = c["Image"];
76740
+ const need = inspectImageForSubstitutions(image, resources);
76741
+ if (need.pseudo)
76742
+ needsPseudoParameters = true;
76743
+ if (need.state)
76744
+ needsStateResources = true;
76745
+ if (needsPseudoParameters && needsStateResources)
76746
+ break;
76747
+ }
76748
+ if (needsPseudoParameters && needsStateResources)
76749
+ break;
76750
+ }
76751
+ return { needsPseudoParameters, needsStateResources };
76752
+ }
76753
+ function inspectImageForSubstitutions(image, resources) {
76754
+ if (!image || typeof image !== "object")
76755
+ return { pseudo: false, state: false };
76756
+ const obj = image;
76757
+ const getAtt = obj["Fn::GetAtt"];
76758
+ if (getAtt !== void 0) {
76759
+ let lid;
76760
+ if (Array.isArray(getAtt) && typeof getAtt[0] === "string")
76761
+ lid = getAtt[0];
76762
+ else if (typeof getAtt === "string")
76763
+ lid = getAtt.split(".")[0];
76764
+ if (lid && resources[lid]?.Type === "AWS::ECR::Repository") {
76765
+ return { pseudo: false, state: true };
76766
+ }
76767
+ }
76768
+ let sub;
76769
+ const subRaw = obj["Fn::Sub"];
76770
+ if (typeof subRaw === "string")
76771
+ sub = subRaw;
76772
+ else if (Array.isArray(subRaw) && typeof subRaw[0] === "string")
76773
+ sub = subRaw[0];
76774
+ if (!sub)
76775
+ return { pseudo: false, state: false };
76776
+ let pseudo = false;
76777
+ let state = false;
76778
+ const placeholderRegex = /\$\{([^}]+)\}/g;
76779
+ let m;
76780
+ while ((m = placeholderRegex.exec(sub)) !== null) {
76781
+ const key = m[1];
76782
+ if (key.startsWith("AWS::")) {
76783
+ pseudo = true;
76784
+ continue;
76785
+ }
76786
+ const dot = key.indexOf(".");
76787
+ const lid = dot === -1 ? key : key.slice(0, dot);
76788
+ if (resources[lid]?.Type === "AWS::ECR::Repository")
76789
+ state = true;
76790
+ }
76791
+ return { pseudo, state };
76792
+ }
76649
76793
  function parseEcsTarget(target) {
76650
76794
  if (typeof target !== "string" || target.length === 0) {
76651
76795
  throw new EcsTaskResolutionError(
@@ -76667,7 +76811,7 @@ function parseEcsTarget(target) {
76667
76811
  }
76668
76812
  return { stackPattern: null, pathOrId: target, isPath: false };
76669
76813
  }
76670
- function resolveEcsTaskTarget(target, stacks) {
76814
+ function resolveEcsTaskTarget(target, stacks, context) {
76671
76815
  if (stacks.length === 0) {
76672
76816
  throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
76673
76817
  }
@@ -76710,7 +76854,7 @@ function resolveEcsTaskTarget(target, stacks) {
76710
76854
  `Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not an AWS::ECS::TaskDefinition.`
76711
76855
  );
76712
76856
  }
76713
- return extractTaskDefinitionProperties(stack, logicalId, resource);
76857
+ return extractTaskDefinitionProperties(stack, logicalId, resource, context);
76714
76858
  }
76715
76859
  function pickStack2(parsed, stacks) {
76716
76860
  if (parsed.stackPattern === null) {
@@ -76733,7 +76877,7 @@ function pickStack2(parsed, stacks) {
76733
76877
  }
76734
76878
  return matched[0];
76735
76879
  }
76736
- function extractTaskDefinitionProperties(stack, logicalId, resource) {
76880
+ function extractTaskDefinitionProperties(stack, logicalId, resource, context) {
76737
76881
  const props = resource.Properties ?? {};
76738
76882
  const warnings = [];
76739
76883
  const family = pickString(props["Family"]) ?? logicalId;
@@ -76760,7 +76904,7 @@ function extractTaskDefinitionProperties(stack, logicalId, resource) {
76760
76904
  throw new EcsTaskResolutionError(`Task definition '${logicalId}' has no ContainerDefinitions.`);
76761
76905
  }
76762
76906
  const containers = rawContainers.map(
76763
- (c, idx) => parseContainerDefinition(c, idx, logicalId, resources, stack)
76907
+ (c, idx) => parseContainerDefinition(c, idx, logicalId, resources, stack, context)
76764
76908
  );
76765
76909
  const rawVolumes = props["Volumes"];
76766
76910
  const volumes = Array.isArray(rawVolumes) ? rawVolumes.map((v, idx) => parseVolume(v, idx, logicalId)) : [];
@@ -76806,7 +76950,7 @@ function parseRuntimePlatform(value) {
76806
76950
  return void 0;
76807
76951
  return { cpuArchitecture: cpu, operatingSystemFamily: os };
76808
76952
  }
76809
- function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack) {
76953
+ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, context) {
76810
76954
  if (!raw || typeof raw !== "object") {
76811
76955
  throw new EcsTaskResolutionError(
76812
76956
  `Task '${taskLogicalId}' ContainerDefinitions[${idx}] is not an object.`
@@ -76819,7 +76963,7 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack) {
76819
76963
  `Task '${taskLogicalId}' ContainerDefinitions[${idx}] has no Name.`
76820
76964
  );
76821
76965
  }
76822
- const image = parseContainerImage(c["Image"], name, taskLogicalId, resources, stack);
76966
+ const image = parseContainerImage(c["Image"], name, taskLogicalId, resources, stack, context);
76823
76967
  const command = pickStringArray2(c["Command"]);
76824
76968
  const entryPoint = pickStringArray2(c["EntryPoint"]);
76825
76969
  const workingDirectory = pickString(c["WorkingDirectory"]);
@@ -76965,7 +77109,11 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack) {
76965
77109
  out.readonlyRootFilesystem = readonlyRootFilesystem;
76966
77110
  return out;
76967
77111
  }
76968
- function parseContainerImage(raw, containerName, taskLogicalId, resources, _stack) {
77112
+ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stack, context) {
77113
+ const getAttImage = tryResolveImageGetAtt(raw, resources, context);
77114
+ if (getAttImage) {
77115
+ return classifyResolvedImage(getAttImage);
77116
+ }
76969
77117
  const flat = extractImageString(raw);
76970
77118
  if (!flat) {
76971
77119
  throw new EcsTaskResolutionError(
@@ -76979,23 +77127,123 @@ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stac
76979
77127
  out.assetHash = hashMatch[1];
76980
77128
  return out;
76981
77129
  }
76982
- const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(flat);
76983
- if (ecrMatch) {
76984
- return { kind: "ecr", uri: flat, account: ecrMatch[1], region: ecrMatch[2] };
76985
- }
76986
- if (flat.includes("${") && flat.includes("AWS::AccountId")) {
77130
+ const substituted = substituteImagePlaceholders(flat, resources, context);
77131
+ if (substituted.includes("${")) {
77132
+ const unresolvedRepoRef = findUnresolvedEcrRepositoryRef(substituted, resources);
77133
+ if (unresolvedRepoRef) {
77134
+ throw new EcsTaskResolutionError(
77135
+ `Container '${containerName}' in task '${taskLogicalId}' references same-stack ECR repository '${unresolvedRepoRef}'. cdkd local run-task v1 cannot resolve the repository URI without state \u2014 pass --from-state (the stack must have been deployed via cdkd deploy), build via ContainerImage.fromAsset, or pin a public image.`
77136
+ );
77137
+ }
77138
+ if (substituted.includes("AWS::")) {
77139
+ throw new EcsTaskResolutionError(
77140
+ `Container '${containerName}' in task '${taskLogicalId}' has an Image that references AWS pseudo parameters (${substituted}). cdkd could not resolve them: confirm AWS credentials are configured so STS GetCallerIdentity succeeds, and that --region / AWS_REGION names the target region. Workaround: build the image locally (ContainerImage.fromAsset) or pin a public image.`
77141
+ );
77142
+ }
76987
77143
  throw new EcsTaskResolutionError(
76988
- `Container '${containerName}' in task '${taskLogicalId}' has an Image that references AWS pseudo parameters (${flat}). cdkd local run-task v1 cannot resolve account-scoped ECR repos at synth time. Build the image locally (CDK ContainerImage.fromAsset) or pin to a public image to test locally.`
77144
+ `Container '${containerName}' in task '${taskLogicalId}' has an Image with unresolved \${...} placeholders (${substituted}). cdkd local run-task v1 only resolves AWS pseudo parameters and same-stack AWS::ECR::Repository refs.`
76989
77145
  );
76990
77146
  }
76991
- for (const [refLogicalId, res] of Object.entries(resources)) {
76992
- if (res.Type === "AWS::ECR::Repository" && flat.includes(refLogicalId)) {
76993
- throw new EcsTaskResolutionError(
76994
- `Container '${containerName}' in task '${taskLogicalId}' references same-stack ECR repository '${refLogicalId}'. cdkd local run-task v1 cannot resolve the repository URI without state \u2014 build via ContainerImage.fromAsset or pin a public image.`
76995
- );
77147
+ const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(substituted);
77148
+ if (ecrMatch) {
77149
+ return { kind: "ecr", uri: substituted, account: ecrMatch[1], region: ecrMatch[2] };
77150
+ }
77151
+ return { kind: "public", uri: substituted };
77152
+ }
77153
+ function findUnresolvedEcrRepositoryRef(substituted, resources) {
77154
+ const placeholderRegex = /\$\{([^}]+)\}/g;
77155
+ let m;
77156
+ while ((m = placeholderRegex.exec(substituted)) !== null) {
77157
+ const key = m[1];
77158
+ if (key.startsWith("AWS::"))
77159
+ continue;
77160
+ const dot = key.indexOf(".");
77161
+ const lid = dot === -1 ? key : key.slice(0, dot);
77162
+ if (resources[lid]?.Type === "AWS::ECR::Repository")
77163
+ return lid;
77164
+ }
77165
+ return void 0;
77166
+ }
77167
+ function classifyResolvedImage(uri) {
77168
+ if (uri.includes("cdk-hnb659fds-container-assets-")) {
77169
+ const hashMatch = /:([a-f0-9]{8,})$/.exec(uri);
77170
+ const out = { kind: "cdk-asset" };
77171
+ if (hashMatch)
77172
+ out.assetHash = hashMatch[1];
77173
+ return out;
77174
+ }
77175
+ const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(uri);
77176
+ if (ecrMatch) {
77177
+ return { kind: "ecr", uri, account: ecrMatch[1], region: ecrMatch[2] };
77178
+ }
77179
+ return { kind: "public", uri };
77180
+ }
77181
+ function substituteImagePlaceholders(flat, resources, context) {
77182
+ if (!flat.includes("${"))
77183
+ return flat;
77184
+ return flat.replace(/\$\{([^}]+)\}/g, (full, key) => {
77185
+ if (context?.pseudoParameters) {
77186
+ if (key === "AWS::AccountId" && context.pseudoParameters.accountId) {
77187
+ return context.pseudoParameters.accountId;
77188
+ }
77189
+ if (key === "AWS::Region" && context.pseudoParameters.region) {
77190
+ return context.pseudoParameters.region;
77191
+ }
77192
+ if (key === "AWS::Partition" && context.pseudoParameters.partition) {
77193
+ return context.pseudoParameters.partition;
77194
+ }
77195
+ if (key === "AWS::URLSuffix" && context.pseudoParameters.urlSuffix) {
77196
+ return context.pseudoParameters.urlSuffix;
77197
+ }
77198
+ }
77199
+ if (context?.stateResources) {
77200
+ const dot = key.indexOf(".");
77201
+ const logicalId = dot === -1 ? key : key.slice(0, dot);
77202
+ const refResource = resources[logicalId];
77203
+ const stateEntry = context.stateResources[logicalId];
77204
+ if (refResource?.Type === "AWS::ECR::Repository" && stateEntry) {
77205
+ if (dot === -1) {
77206
+ return stateEntry.physicalId;
77207
+ }
77208
+ const attr = key.slice(dot + 1);
77209
+ const cached = stateEntry.attributes?.[attr];
77210
+ if (typeof cached === "string")
77211
+ return cached;
77212
+ }
77213
+ }
77214
+ return full;
77215
+ });
77216
+ }
77217
+ function tryResolveImageGetAtt(raw, resources, context) {
77218
+ if (!raw || typeof raw !== "object")
77219
+ return void 0;
77220
+ const obj = raw;
77221
+ const arg = obj["Fn::GetAtt"];
77222
+ if (arg === void 0)
77223
+ return void 0;
77224
+ let logicalId;
77225
+ let attr;
77226
+ if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && typeof arg[1] === "string") {
77227
+ logicalId = arg[0];
77228
+ attr = arg[1];
77229
+ } else if (typeof arg === "string") {
77230
+ const dot = arg.indexOf(".");
77231
+ if (dot > 0 && dot < arg.length - 1) {
77232
+ logicalId = arg.slice(0, dot);
77233
+ attr = arg.slice(dot + 1);
76996
77234
  }
76997
77235
  }
76998
- return { kind: "public", uri: flat };
77236
+ if (!logicalId || !attr)
77237
+ return void 0;
77238
+ const refResource = resources[logicalId];
77239
+ if (refResource?.Type !== "AWS::ECR::Repository")
77240
+ return void 0;
77241
+ if (attr !== "RepositoryUri" && attr !== "Arn")
77242
+ return void 0;
77243
+ const cached = context?.stateResources?.[logicalId]?.attributes?.[attr];
77244
+ if (typeof cached === "string" && cached.length > 0)
77245
+ return cached;
77246
+ return void 0;
76999
77247
  }
77000
77248
  function extractImageString(value) {
77001
77249
  if (typeof value === "string" && value.length > 0)
@@ -77074,6 +77322,7 @@ function parseVolume(raw, idx, taskLogicalId) {
77074
77322
  }
77075
77323
  return { name, kind: "host" };
77076
77324
  }
77325
+ var TASK_ROLE_ACCOUNT_PLACEHOLDER = "${AWS::AccountId}";
77077
77326
  function resolveRoleArn(value, resources) {
77078
77327
  if (value === void 0 || value === null)
77079
77328
  return void 0;
@@ -77082,24 +77331,23 @@ function resolveRoleArn(value, resources) {
77082
77331
  if (typeof value !== "object")
77083
77332
  return void 0;
77084
77333
  const obj = value;
77334
+ let refLogicalId;
77085
77335
  if ("Ref" in obj && typeof obj["Ref"] === "string") {
77086
- const refLogicalId = obj["Ref"];
77087
- const role = resources[refLogicalId];
77088
- if (role?.Type === "AWS::IAM::Role") {
77089
- return void 0;
77090
- }
77091
- }
77092
- if ("Fn::GetAtt" in obj) {
77336
+ refLogicalId = obj["Ref"];
77337
+ } else if ("Fn::GetAtt" in obj) {
77093
77338
  const arg = obj["Fn::GetAtt"];
77094
77339
  if (Array.isArray(arg) && typeof arg[0] === "string") {
77095
- const refLogicalId = arg[0];
77096
- const role = resources[refLogicalId];
77097
- if (role?.Type === "AWS::IAM::Role") {
77098
- return void 0;
77099
- }
77340
+ const attr = typeof arg[1] === "string" ? arg[1] : "";
77341
+ if (attr === "" || attr === "Arn")
77342
+ refLogicalId = arg[0];
77100
77343
  }
77101
77344
  }
77102
- return void 0;
77345
+ if (refLogicalId === void 0)
77346
+ return void 0;
77347
+ const role = resources[refLogicalId];
77348
+ if (role?.Type !== "AWS::IAM::Role")
77349
+ return void 0;
77350
+ return `arn:aws:iam::${TASK_ROLE_ACCOUNT_PLACEHOLDER}:role/${refLogicalId}`;
77103
77351
  }
77104
77352
  function pickString(value) {
77105
77353
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -77936,7 +78184,8 @@ async function localRunTaskCommand(target, options) {
77936
78184
  ...Object.keys(context).length > 0 && { context }
77937
78185
  };
77938
78186
  const { stacks } = await synthesizer.synthesize(synthOpts);
77939
- const task = resolveEcsTaskTarget(target, stacks);
78187
+ const imageContext = await buildEcsImageResolutionContext(target, stacks, options);
78188
+ const task = resolveEcsTaskTarget(target, stacks, imageContext);
77940
78189
  logger.info(
77941
78190
  `Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`
77942
78191
  );
@@ -77955,10 +78204,10 @@ async function localRunTaskCommand(target, options) {
77955
78204
  if (options.assumeTaskRole === true) {
77956
78205
  if (!task.taskRoleArn) {
77957
78206
  throw new Error(
77958
- `--assume-task-role passed without an ARN but the task definition's TaskRoleArn could not be resolved statically. Pass the ARN explicitly: --assume-task-role <arn>`
78207
+ `--assume-task-role passed without an ARN but the task definition has no resolvable TaskRoleArn. Either the task definition does not set TaskRoleArn, or it points at a resource cdkd cannot resolve to an IAM Role at synth time. Pass the ARN explicitly: --assume-task-role <arn>`
77959
78208
  );
77960
78209
  }
77961
- resolvedRoleArn = task.taskRoleArn;
78210
+ resolvedRoleArn = await resolvePlaceholderAccount(task.taskRoleArn, options.region);
77962
78211
  assumedCredentials = await assumeTaskRole(resolvedRoleArn, options.region);
77963
78212
  } else if (typeof options.assumeTaskRole === "string") {
77964
78213
  resolvedRoleArn = options.assumeTaskRole;
@@ -78006,6 +78255,24 @@ async function localRunTaskCommand(target, options) {
78006
78255
  await cleanup();
78007
78256
  }
78008
78257
  }
78258
+ async function resolvePlaceholderAccount(arn, region) {
78259
+ if (!arn.includes(TASK_ROLE_ACCOUNT_PLACEHOLDER))
78260
+ return arn;
78261
+ const { STSClient: STSClient11, GetCallerIdentityCommand: GetCallerIdentityCommand12 } = await import("@aws-sdk/client-sts");
78262
+ const sts = new STSClient11({ ...region && { region } });
78263
+ try {
78264
+ const identity = await sts.send(new GetCallerIdentityCommand12({}));
78265
+ const account = identity.Account;
78266
+ if (!account) {
78267
+ throw new Error(
78268
+ `--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN '${arn}'. Pass the ARN explicitly: --assume-task-role <arn>`
78269
+ );
78270
+ }
78271
+ return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
78272
+ } finally {
78273
+ sts.destroy();
78274
+ }
78275
+ }
78009
78276
  async function assumeTaskRole(roleArn, region) {
78010
78277
  const { STSClient: STSClient11, AssumeRoleCommand: AssumeRoleCommand2 } = await import("@aws-sdk/client-sts");
78011
78278
  const sts = new STSClient11({ ...region && { region } });
@@ -78030,6 +78297,80 @@ async function assumeTaskRole(roleArn, region) {
78030
78297
  sts.destroy();
78031
78298
  }
78032
78299
  }
78300
+ async function buildEcsImageResolutionContext(target, stacks, options) {
78301
+ const logger = getLogger();
78302
+ const parsed = parseEcsTarget(target);
78303
+ const candidate = pickCandidateStack(parsed.stackPattern, stacks);
78304
+ if (!candidate)
78305
+ return void 0;
78306
+ const needs = detectEcsImageResolutionNeeds(candidate);
78307
+ if (!needs.needsPseudoParameters && !needs.needsStateResources)
78308
+ return void 0;
78309
+ const ctx = {};
78310
+ if (needs.needsPseudoParameters) {
78311
+ const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
78312
+ if (!region) {
78313
+ logger.warn(
78314
+ "Container Image references ${AWS::Region} but cdkd could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack."
78315
+ );
78316
+ }
78317
+ let accountId;
78318
+ try {
78319
+ accountId = await resolveCallerAccountId(region);
78320
+ } catch (err) {
78321
+ logger.warn(
78322
+ `Container Image references \${AWS::AccountId} but STS GetCallerIdentity failed: ${err instanceof Error ? err.message : String(err)}. Substitution will be skipped; the resolver will surface its existing error.`
78323
+ );
78324
+ }
78325
+ const partitionAndSuffix = region ? derivePartitionAndUrlSuffix(region) : void 0;
78326
+ ctx.pseudoParameters = {
78327
+ ...accountId !== void 0 && { accountId },
78328
+ ...region !== void 0 && { region },
78329
+ ...partitionAndSuffix && {
78330
+ partition: partitionAndSuffix.partition,
78331
+ urlSuffix: partitionAndSuffix.urlSuffix
78332
+ }
78333
+ };
78334
+ }
78335
+ if (options.fromState && needs.needsStateResources) {
78336
+ const loaded = await loadStateForStack(candidate.stackName, candidate.region, {
78337
+ ...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
78338
+ ...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
78339
+ statePrefix: options.statePrefix,
78340
+ ...options.region !== void 0 && { region: options.region },
78341
+ ...options.profile !== void 0 && { profile: options.profile }
78342
+ });
78343
+ if (loaded) {
78344
+ ctx.stateResources = loaded.state.resources;
78345
+ }
78346
+ } else if (!options.fromState && needs.needsStateResources) {
78347
+ logger.warn(
78348
+ "Container Image references a same-stack AWS::ECR::Repository. Pass --from-state to substitute the deployed repository URI (requires the stack to have been deployed via cdkd deploy). Otherwise the resolver will surface its existing error."
78349
+ );
78350
+ }
78351
+ return ctx;
78352
+ }
78353
+ function pickCandidateStack(stackPattern, stacks) {
78354
+ if (stackPattern === null) {
78355
+ if (stacks.length === 1)
78356
+ return stacks[0];
78357
+ return void 0;
78358
+ }
78359
+ const matched = matchStacks(stacks, [stackPattern]);
78360
+ if (matched.length === 1)
78361
+ return matched[0];
78362
+ return void 0;
78363
+ }
78364
+ async function resolveCallerAccountId(region) {
78365
+ const { STSClient: STSClient11, GetCallerIdentityCommand: GetCallerIdentityCommand12 } = await import("@aws-sdk/client-sts");
78366
+ const sts = new STSClient11({ ...region && { region } });
78367
+ try {
78368
+ const identity = await sts.send(new GetCallerIdentityCommand12({}));
78369
+ return identity.Account;
78370
+ } finally {
78371
+ sts.destroy();
78372
+ }
78373
+ }
78033
78374
  function readEnvOverridesFile2(filePath) {
78034
78375
  if (!filePath)
78035
78376
  return void 0;
@@ -78097,8 +78438,20 @@ function createLocalRunTaskCommand() {
78097
78438
  "--detach",
78098
78439
  "Start the containers in the background and exit (skip log streaming + auto teardown). Useful in CI smoke tests; caller manages container lifecycle."
78099
78440
  ).default(false)
78441
+ ).addOption(
78442
+ new Option8(
78443
+ "--from-state",
78444
+ "Read cdkd S3 state for the target stack and substitute Fn::Sub / Fn::GetAtt references to same-stack AWS::ECR::Repository resources with the deployed URI. Off by default \u2014 only the AWS pseudo-parameter tier (${AWS::AccountId} / ${AWS::Region}) is resolved without this flag."
78445
+ ).default(false)
78446
+ ).addOption(
78447
+ new Option8(
78448
+ "--stack-region <region>",
78449
+ "Region of the cdkd state record to read (used with --from-state when the same stack name has state in multiple regions)."
78450
+ )
78100
78451
  ).action(withErrorHandling(localRunTaskCommand));
78101
- [...commonOptions, ...appOptions, ...contextOptions].forEach((opt) => cmd.addOption(opt));
78452
+ [...commonOptions, ...appOptions, ...contextOptions, ...stateOptions].forEach(
78453
+ (opt) => cmd.addOption(opt)
78454
+ );
78102
78455
  cmd.addOption(deprecatedRegionOption);
78103
78456
  return cmd;
78104
78457
  }
@@ -78507,72 +78860,6 @@ function materializeInlineCode2(handler, source, fileExtension) {
78507
78860
  writeFileSync6(filePath, source, "utf-8");
78508
78861
  return dir;
78509
78862
  }
78510
- async function loadStateForStack(stackName, synthRegion, opts) {
78511
- const logger = getLogger();
78512
- const region = opts.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? synthRegion ?? "us-east-1";
78513
- let stateBucket;
78514
- try {
78515
- stateBucket = await resolveStateBucketWithDefault(opts.stateBucket, region);
78516
- } catch (err) {
78517
- logger.warn(
78518
- `--from-state: could not resolve state bucket: ${err instanceof Error ? err.message : String(err)}. Falling back to PR 1 warn-and-drop semantics.`
78519
- );
78520
- return void 0;
78521
- }
78522
- const awsClients = new AwsClients({
78523
- ...opts.region !== void 0 && { region: opts.region },
78524
- ...opts.profile !== void 0 && { profile: opts.profile }
78525
- });
78526
- setAwsClients(awsClients);
78527
- try {
78528
- const stateConfig = { bucket: stateBucket, prefix: opts.statePrefix };
78529
- const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
78530
- ...opts.region !== void 0 && { region: opts.region },
78531
- ...opts.profile !== void 0 && { profile: opts.profile }
78532
- });
78533
- await stateBackend.verifyBucketExists();
78534
- const refs = (await stateBackend.listStacks()).filter((r) => r.stackName === stackName);
78535
- if (refs.length === 0) {
78536
- logger.warn(
78537
- `--from-state: no cdkd state found for stack '${stackName}' in bucket '${stateBucket}'. Was it deployed via 'cdkd deploy'? Falling back to PR 1 warn-and-drop semantics.`
78538
- );
78539
- return void 0;
78540
- }
78541
- let targetRegion;
78542
- if (opts.stackRegion) {
78543
- const found = refs.find((r) => r.region === opts.stackRegion);
78544
- if (!found) {
78545
- const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
78546
- logger.warn(
78547
- `--from-state: stack '${stackName}' has no state in region '${opts.stackRegion}' (available: ${seen}). Falling back.`
78548
- );
78549
- return void 0;
78550
- }
78551
- targetRegion = opts.stackRegion;
78552
- } else if (synthRegion && refs.some((r) => r.region === synthRegion)) {
78553
- targetRegion = synthRegion;
78554
- } else if (refs.length === 1) {
78555
- targetRegion = refs[0].region ?? synthRegion ?? region;
78556
- } else {
78557
- const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
78558
- logger.warn(
78559
- `--from-state: stack '${stackName}' has state in multiple regions (${seen}). Re-run with --stack-region <region>. Falling back.`
78560
- );
78561
- return void 0;
78562
- }
78563
- const stateData = await stateBackend.getState(stackName, targetRegion);
78564
- if (!stateData) {
78565
- logger.warn(
78566
- `--from-state: state record for '${stackName}' (${targetRegion}) returned empty. Falling back.`
78567
- );
78568
- return void 0;
78569
- }
78570
- logger.debug(`--from-state: loaded state for ${stackName} (${targetRegion})`);
78571
- return { state: stateData.state, region: targetRegion };
78572
- } finally {
78573
- awsClients.destroy();
78574
- }
78575
- }
78576
78863
  function suggestAssumeRoleFromState(state, logicalId) {
78577
78864
  const logger = getLogger();
78578
78865
  const lambda = state.resources[logicalId];
@@ -78688,7 +78975,7 @@ function reorderArgs(argv) {
78688
78975
  }
78689
78976
  async function main() {
78690
78977
  const program = new Command17();
78691
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.82.1");
78978
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.83.0");
78692
78979
  program.addCommand(createBootstrapCommand());
78693
78980
  program.addCommand(createSynthCommand());
78694
78981
  program.addCommand(createListCommand());