@go-to-k/cdkd 0.82.1 → 0.83.1

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
@@ -21471,6 +21471,39 @@ var TemplateParser = class {
21471
21471
  }
21472
21472
  return;
21473
21473
  }
21474
+ if ("Fn::Sub" in obj) {
21475
+ const subValue = obj["Fn::Sub"];
21476
+ let body;
21477
+ let mapKeys;
21478
+ if (typeof subValue === "string") {
21479
+ body = subValue;
21480
+ } else if (Array.isArray(subValue) && subValue.length >= 1 && typeof subValue[0] === "string") {
21481
+ body = subValue[0];
21482
+ const variables = subValue[1];
21483
+ if (variables && typeof variables === "object" && !Array.isArray(variables)) {
21484
+ const varMap = variables;
21485
+ mapKeys = new Set(Object.keys(varMap));
21486
+ Object.values(varMap).forEach((v) => this.extractRefsFromValue(v, dependencies));
21487
+ }
21488
+ }
21489
+ if (body !== void 0) {
21490
+ for (const match of body.matchAll(/\$\{([^}]+)\}/g)) {
21491
+ const placeholder = match[1];
21492
+ if (!placeholder)
21493
+ continue;
21494
+ const dot = placeholder.indexOf(".");
21495
+ const name = dot >= 0 ? placeholder.slice(0, dot) : placeholder;
21496
+ if (!name)
21497
+ continue;
21498
+ if (name.startsWith("AWS::"))
21499
+ continue;
21500
+ if (mapKeys?.has(name))
21501
+ continue;
21502
+ dependencies.add(name);
21503
+ }
21504
+ }
21505
+ return;
21506
+ }
21474
21507
  Object.values(obj).forEach((v) => this.extractRefsFromValue(v, dependencies));
21475
21508
  }
21476
21509
  /**
@@ -70040,6 +70073,76 @@ import { dirname as dirname7 } from "node:path";
70040
70073
  import * as path2 from "node:path";
70041
70074
  import { Command as Command16, Option as Option9 } from "commander";
70042
70075
 
70076
+ // src/cli/commands/local-state-loader.ts
70077
+ init_aws_clients();
70078
+ async function loadStateForStack(stackName, synthRegion, opts) {
70079
+ const logger = getLogger();
70080
+ const prefix = opts.logPrefix ?? "--from-state";
70081
+ const region = opts.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? synthRegion ?? "us-east-1";
70082
+ let stateBucket;
70083
+ try {
70084
+ stateBucket = await resolveStateBucketWithDefault(opts.stateBucket, region);
70085
+ } catch (err) {
70086
+ logger.warn(
70087
+ `${prefix}: could not resolve state bucket: ${err instanceof Error ? err.message : String(err)}. Falling back.`
70088
+ );
70089
+ return void 0;
70090
+ }
70091
+ const awsClients = new AwsClients({
70092
+ ...opts.region !== void 0 && { region: opts.region },
70093
+ ...opts.profile !== void 0 && { profile: opts.profile }
70094
+ });
70095
+ setAwsClients(awsClients);
70096
+ try {
70097
+ const stateConfig = { bucket: stateBucket, prefix: opts.statePrefix };
70098
+ const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
70099
+ ...opts.region !== void 0 && { region: opts.region },
70100
+ ...opts.profile !== void 0 && { profile: opts.profile }
70101
+ });
70102
+ await stateBackend.verifyBucketExists();
70103
+ const refs = (await stateBackend.listStacks()).filter((r) => r.stackName === stackName);
70104
+ if (refs.length === 0) {
70105
+ logger.warn(
70106
+ `${prefix}: no cdkd state found for stack '${stackName}' in bucket '${stateBucket}'. Was it deployed via 'cdkd deploy'? Falling back.`
70107
+ );
70108
+ return void 0;
70109
+ }
70110
+ let targetRegion;
70111
+ if (opts.stackRegion) {
70112
+ const found = refs.find((r) => r.region === opts.stackRegion);
70113
+ if (!found) {
70114
+ const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
70115
+ logger.warn(
70116
+ `${prefix}: stack '${stackName}' has no state in region '${opts.stackRegion}' (available: ${seen}). Falling back.`
70117
+ );
70118
+ return void 0;
70119
+ }
70120
+ targetRegion = opts.stackRegion;
70121
+ } else if (synthRegion && refs.some((r) => r.region === synthRegion)) {
70122
+ targetRegion = synthRegion;
70123
+ } else if (refs.length === 1) {
70124
+ targetRegion = refs[0].region ?? synthRegion ?? region;
70125
+ } else {
70126
+ const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
70127
+ logger.warn(
70128
+ `${prefix}: stack '${stackName}' has state in multiple regions (${seen}). Re-run with --stack-region <region>. Falling back.`
70129
+ );
70130
+ return void 0;
70131
+ }
70132
+ const stateData = await stateBackend.getState(stackName, targetRegion);
70133
+ if (!stateData) {
70134
+ logger.warn(
70135
+ `${prefix}: state record for '${stackName}' (${targetRegion}) returned empty. Falling back.`
70136
+ );
70137
+ return void 0;
70138
+ }
70139
+ logger.debug(`${prefix}: loaded state for ${stackName} (${targetRegion})`);
70140
+ return { state: stateData.state, region: targetRegion };
70141
+ } finally {
70142
+ awsClients.destroy();
70143
+ }
70144
+ }
70145
+
70043
70146
  // src/local/lambda-resolver.ts
70044
70147
  import { existsSync as existsSync4, statSync as statSync3 } from "node:fs";
70045
70148
  import { dirname, isAbsolute, resolve as resolve4 } from "node:path";
@@ -71217,9 +71320,6 @@ function extractHashFromImageUri(imageUri) {
71217
71320
  return match?.[1];
71218
71321
  }
71219
71322
 
71220
- // src/cli/commands/local-invoke.ts
71221
- init_aws_clients();
71222
-
71223
71323
  // src/utils/single-flight.ts
71224
71324
  function singleFlight(fn, onError) {
71225
71325
  let promise;
@@ -76646,6 +76746,83 @@ var EcsTaskResolutionError = class _EcsTaskResolutionError extends Error {
76646
76746
  Object.setPrototypeOf(this, _EcsTaskResolutionError.prototype);
76647
76747
  }
76648
76748
  };
76749
+ function derivePartitionAndUrlSuffix(region) {
76750
+ if (region.startsWith("cn-"))
76751
+ return { partition: "aws-cn", urlSuffix: "amazonaws.com.cn" };
76752
+ if (region.startsWith("us-gov-"))
76753
+ return { partition: "aws-us-gov", urlSuffix: "amazonaws.com" };
76754
+ if (region.startsWith("us-iso-"))
76755
+ return { partition: "aws-iso", urlSuffix: "c2s.ic.gov" };
76756
+ if (region.startsWith("us-isob-"))
76757
+ return { partition: "aws-iso-b", urlSuffix: "sc2s.sgov.gov" };
76758
+ return { partition: "aws", urlSuffix: "amazonaws.com" };
76759
+ }
76760
+ function detectEcsImageResolutionNeeds(stack) {
76761
+ const resources = stack.template.Resources ?? {};
76762
+ let needsPseudoParameters = false;
76763
+ let needsStateResources = false;
76764
+ for (const res of Object.values(resources)) {
76765
+ if (res.Type !== "AWS::ECS::TaskDefinition")
76766
+ continue;
76767
+ const props = res.Properties ?? {};
76768
+ const containers = Array.isArray(props["ContainerDefinitions"]) ? props["ContainerDefinitions"] : [];
76769
+ for (const c of containers) {
76770
+ if (!c || typeof c !== "object")
76771
+ continue;
76772
+ const image = c["Image"];
76773
+ const need = inspectImageForSubstitutions(image, resources);
76774
+ if (need.pseudo)
76775
+ needsPseudoParameters = true;
76776
+ if (need.state)
76777
+ needsStateResources = true;
76778
+ if (needsPseudoParameters && needsStateResources)
76779
+ break;
76780
+ }
76781
+ if (needsPseudoParameters && needsStateResources)
76782
+ break;
76783
+ }
76784
+ return { needsPseudoParameters, needsStateResources };
76785
+ }
76786
+ function inspectImageForSubstitutions(image, resources) {
76787
+ if (!image || typeof image !== "object")
76788
+ return { pseudo: false, state: false };
76789
+ const obj = image;
76790
+ const getAtt = obj["Fn::GetAtt"];
76791
+ if (getAtt !== void 0) {
76792
+ let lid;
76793
+ if (Array.isArray(getAtt) && typeof getAtt[0] === "string")
76794
+ lid = getAtt[0];
76795
+ else if (typeof getAtt === "string")
76796
+ lid = getAtt.split(".")[0];
76797
+ if (lid && resources[lid]?.Type === "AWS::ECR::Repository") {
76798
+ return { pseudo: false, state: true };
76799
+ }
76800
+ }
76801
+ let sub;
76802
+ const subRaw = obj["Fn::Sub"];
76803
+ if (typeof subRaw === "string")
76804
+ sub = subRaw;
76805
+ else if (Array.isArray(subRaw) && typeof subRaw[0] === "string")
76806
+ sub = subRaw[0];
76807
+ if (!sub)
76808
+ return { pseudo: false, state: false };
76809
+ let pseudo = false;
76810
+ let state = false;
76811
+ const placeholderRegex = /\$\{([^}]+)\}/g;
76812
+ let m;
76813
+ while ((m = placeholderRegex.exec(sub)) !== null) {
76814
+ const key = m[1];
76815
+ if (key.startsWith("AWS::")) {
76816
+ pseudo = true;
76817
+ continue;
76818
+ }
76819
+ const dot = key.indexOf(".");
76820
+ const lid = dot === -1 ? key : key.slice(0, dot);
76821
+ if (resources[lid]?.Type === "AWS::ECR::Repository")
76822
+ state = true;
76823
+ }
76824
+ return { pseudo, state };
76825
+ }
76649
76826
  function parseEcsTarget(target) {
76650
76827
  if (typeof target !== "string" || target.length === 0) {
76651
76828
  throw new EcsTaskResolutionError(
@@ -76667,7 +76844,7 @@ function parseEcsTarget(target) {
76667
76844
  }
76668
76845
  return { stackPattern: null, pathOrId: target, isPath: false };
76669
76846
  }
76670
- function resolveEcsTaskTarget(target, stacks) {
76847
+ function resolveEcsTaskTarget(target, stacks, context) {
76671
76848
  if (stacks.length === 0) {
76672
76849
  throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
76673
76850
  }
@@ -76710,7 +76887,7 @@ function resolveEcsTaskTarget(target, stacks) {
76710
76887
  `Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not an AWS::ECS::TaskDefinition.`
76711
76888
  );
76712
76889
  }
76713
- return extractTaskDefinitionProperties(stack, logicalId, resource);
76890
+ return extractTaskDefinitionProperties(stack, logicalId, resource, context);
76714
76891
  }
76715
76892
  function pickStack2(parsed, stacks) {
76716
76893
  if (parsed.stackPattern === null) {
@@ -76733,7 +76910,7 @@ function pickStack2(parsed, stacks) {
76733
76910
  }
76734
76911
  return matched[0];
76735
76912
  }
76736
- function extractTaskDefinitionProperties(stack, logicalId, resource) {
76913
+ function extractTaskDefinitionProperties(stack, logicalId, resource, context) {
76737
76914
  const props = resource.Properties ?? {};
76738
76915
  const warnings = [];
76739
76916
  const family = pickString(props["Family"]) ?? logicalId;
@@ -76760,7 +76937,7 @@ function extractTaskDefinitionProperties(stack, logicalId, resource) {
76760
76937
  throw new EcsTaskResolutionError(`Task definition '${logicalId}' has no ContainerDefinitions.`);
76761
76938
  }
76762
76939
  const containers = rawContainers.map(
76763
- (c, idx) => parseContainerDefinition(c, idx, logicalId, resources, stack)
76940
+ (c, idx) => parseContainerDefinition(c, idx, logicalId, resources, stack, context)
76764
76941
  );
76765
76942
  const rawVolumes = props["Volumes"];
76766
76943
  const volumes = Array.isArray(rawVolumes) ? rawVolumes.map((v, idx) => parseVolume(v, idx, logicalId)) : [];
@@ -76806,7 +76983,7 @@ function parseRuntimePlatform(value) {
76806
76983
  return void 0;
76807
76984
  return { cpuArchitecture: cpu, operatingSystemFamily: os };
76808
76985
  }
76809
- function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack) {
76986
+ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, context) {
76810
76987
  if (!raw || typeof raw !== "object") {
76811
76988
  throw new EcsTaskResolutionError(
76812
76989
  `Task '${taskLogicalId}' ContainerDefinitions[${idx}] is not an object.`
@@ -76819,7 +76996,7 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack) {
76819
76996
  `Task '${taskLogicalId}' ContainerDefinitions[${idx}] has no Name.`
76820
76997
  );
76821
76998
  }
76822
- const image = parseContainerImage(c["Image"], name, taskLogicalId, resources, stack);
76999
+ const image = parseContainerImage(c["Image"], name, taskLogicalId, resources, stack, context);
76823
77000
  const command = pickStringArray2(c["Command"]);
76824
77001
  const entryPoint = pickStringArray2(c["EntryPoint"]);
76825
77002
  const workingDirectory = pickString(c["WorkingDirectory"]);
@@ -76965,7 +77142,11 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack) {
76965
77142
  out.readonlyRootFilesystem = readonlyRootFilesystem;
76966
77143
  return out;
76967
77144
  }
76968
- function parseContainerImage(raw, containerName, taskLogicalId, resources, _stack) {
77145
+ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stack, context) {
77146
+ const getAttImage = tryResolveImageGetAtt(raw, resources, context);
77147
+ if (getAttImage) {
77148
+ return classifyResolvedImage(getAttImage);
77149
+ }
76969
77150
  const flat = extractImageString(raw);
76970
77151
  if (!flat) {
76971
77152
  throw new EcsTaskResolutionError(
@@ -76979,23 +77160,123 @@ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stac
76979
77160
  out.assetHash = hashMatch[1];
76980
77161
  return out;
76981
77162
  }
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")) {
77163
+ const substituted = substituteImagePlaceholders(flat, resources, context);
77164
+ if (substituted.includes("${")) {
77165
+ const unresolvedRepoRef = findUnresolvedEcrRepositoryRef(substituted, resources);
77166
+ if (unresolvedRepoRef) {
77167
+ throw new EcsTaskResolutionError(
77168
+ `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.`
77169
+ );
77170
+ }
77171
+ if (substituted.includes("AWS::")) {
77172
+ throw new EcsTaskResolutionError(
77173
+ `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.`
77174
+ );
77175
+ }
76987
77176
  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.`
77177
+ `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
77178
  );
76990
77179
  }
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
- );
77180
+ const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(substituted);
77181
+ if (ecrMatch) {
77182
+ return { kind: "ecr", uri: substituted, account: ecrMatch[1], region: ecrMatch[2] };
77183
+ }
77184
+ return { kind: "public", uri: substituted };
77185
+ }
77186
+ function findUnresolvedEcrRepositoryRef(substituted, resources) {
77187
+ const placeholderRegex = /\$\{([^}]+)\}/g;
77188
+ let m;
77189
+ while ((m = placeholderRegex.exec(substituted)) !== null) {
77190
+ const key = m[1];
77191
+ if (key.startsWith("AWS::"))
77192
+ continue;
77193
+ const dot = key.indexOf(".");
77194
+ const lid = dot === -1 ? key : key.slice(0, dot);
77195
+ if (resources[lid]?.Type === "AWS::ECR::Repository")
77196
+ return lid;
77197
+ }
77198
+ return void 0;
77199
+ }
77200
+ function classifyResolvedImage(uri) {
77201
+ if (uri.includes("cdk-hnb659fds-container-assets-")) {
77202
+ const hashMatch = /:([a-f0-9]{8,})$/.exec(uri);
77203
+ const out = { kind: "cdk-asset" };
77204
+ if (hashMatch)
77205
+ out.assetHash = hashMatch[1];
77206
+ return out;
77207
+ }
77208
+ const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(uri);
77209
+ if (ecrMatch) {
77210
+ return { kind: "ecr", uri, account: ecrMatch[1], region: ecrMatch[2] };
77211
+ }
77212
+ return { kind: "public", uri };
77213
+ }
77214
+ function substituteImagePlaceholders(flat, resources, context) {
77215
+ if (!flat.includes("${"))
77216
+ return flat;
77217
+ return flat.replace(/\$\{([^}]+)\}/g, (full, key) => {
77218
+ if (context?.pseudoParameters) {
77219
+ if (key === "AWS::AccountId" && context.pseudoParameters.accountId) {
77220
+ return context.pseudoParameters.accountId;
77221
+ }
77222
+ if (key === "AWS::Region" && context.pseudoParameters.region) {
77223
+ return context.pseudoParameters.region;
77224
+ }
77225
+ if (key === "AWS::Partition" && context.pseudoParameters.partition) {
77226
+ return context.pseudoParameters.partition;
77227
+ }
77228
+ if (key === "AWS::URLSuffix" && context.pseudoParameters.urlSuffix) {
77229
+ return context.pseudoParameters.urlSuffix;
77230
+ }
77231
+ }
77232
+ if (context?.stateResources) {
77233
+ const dot = key.indexOf(".");
77234
+ const logicalId = dot === -1 ? key : key.slice(0, dot);
77235
+ const refResource = resources[logicalId];
77236
+ const stateEntry = context.stateResources[logicalId];
77237
+ if (refResource?.Type === "AWS::ECR::Repository" && stateEntry) {
77238
+ if (dot === -1) {
77239
+ return stateEntry.physicalId;
77240
+ }
77241
+ const attr = key.slice(dot + 1);
77242
+ const cached = stateEntry.attributes?.[attr];
77243
+ if (typeof cached === "string")
77244
+ return cached;
77245
+ }
77246
+ }
77247
+ return full;
77248
+ });
77249
+ }
77250
+ function tryResolveImageGetAtt(raw, resources, context) {
77251
+ if (!raw || typeof raw !== "object")
77252
+ return void 0;
77253
+ const obj = raw;
77254
+ const arg = obj["Fn::GetAtt"];
77255
+ if (arg === void 0)
77256
+ return void 0;
77257
+ let logicalId;
77258
+ let attr;
77259
+ if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && typeof arg[1] === "string") {
77260
+ logicalId = arg[0];
77261
+ attr = arg[1];
77262
+ } else if (typeof arg === "string") {
77263
+ const dot = arg.indexOf(".");
77264
+ if (dot > 0 && dot < arg.length - 1) {
77265
+ logicalId = arg.slice(0, dot);
77266
+ attr = arg.slice(dot + 1);
76996
77267
  }
76997
77268
  }
76998
- return { kind: "public", uri: flat };
77269
+ if (!logicalId || !attr)
77270
+ return void 0;
77271
+ const refResource = resources[logicalId];
77272
+ if (refResource?.Type !== "AWS::ECR::Repository")
77273
+ return void 0;
77274
+ if (attr !== "RepositoryUri" && attr !== "Arn")
77275
+ return void 0;
77276
+ const cached = context?.stateResources?.[logicalId]?.attributes?.[attr];
77277
+ if (typeof cached === "string" && cached.length > 0)
77278
+ return cached;
77279
+ return void 0;
76999
77280
  }
77000
77281
  function extractImageString(value) {
77001
77282
  if (typeof value === "string" && value.length > 0)
@@ -77074,6 +77355,7 @@ function parseVolume(raw, idx, taskLogicalId) {
77074
77355
  }
77075
77356
  return { name, kind: "host" };
77076
77357
  }
77358
+ var TASK_ROLE_ACCOUNT_PLACEHOLDER = "${AWS::AccountId}";
77077
77359
  function resolveRoleArn(value, resources) {
77078
77360
  if (value === void 0 || value === null)
77079
77361
  return void 0;
@@ -77082,24 +77364,23 @@ function resolveRoleArn(value, resources) {
77082
77364
  if (typeof value !== "object")
77083
77365
  return void 0;
77084
77366
  const obj = value;
77367
+ let refLogicalId;
77085
77368
  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) {
77369
+ refLogicalId = obj["Ref"];
77370
+ } else if ("Fn::GetAtt" in obj) {
77093
77371
  const arg = obj["Fn::GetAtt"];
77094
77372
  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
- }
77373
+ const attr = typeof arg[1] === "string" ? arg[1] : "";
77374
+ if (attr === "" || attr === "Arn")
77375
+ refLogicalId = arg[0];
77100
77376
  }
77101
77377
  }
77102
- return void 0;
77378
+ if (refLogicalId === void 0)
77379
+ return void 0;
77380
+ const role = resources[refLogicalId];
77381
+ if (role?.Type !== "AWS::IAM::Role")
77382
+ return void 0;
77383
+ return `arn:aws:iam::${TASK_ROLE_ACCOUNT_PLACEHOLDER}:role/${refLogicalId}`;
77103
77384
  }
77104
77385
  function pickString(value) {
77105
77386
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -77936,7 +78217,8 @@ async function localRunTaskCommand(target, options) {
77936
78217
  ...Object.keys(context).length > 0 && { context }
77937
78218
  };
77938
78219
  const { stacks } = await synthesizer.synthesize(synthOpts);
77939
- const task = resolveEcsTaskTarget(target, stacks);
78220
+ const imageContext = await buildEcsImageResolutionContext(target, stacks, options);
78221
+ const task = resolveEcsTaskTarget(target, stacks, imageContext);
77940
78222
  logger.info(
77941
78223
  `Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`
77942
78224
  );
@@ -77955,10 +78237,10 @@ async function localRunTaskCommand(target, options) {
77955
78237
  if (options.assumeTaskRole === true) {
77956
78238
  if (!task.taskRoleArn) {
77957
78239
  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>`
78240
+ `--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
78241
  );
77960
78242
  }
77961
- resolvedRoleArn = task.taskRoleArn;
78243
+ resolvedRoleArn = await resolvePlaceholderAccount(task.taskRoleArn, options.region);
77962
78244
  assumedCredentials = await assumeTaskRole(resolvedRoleArn, options.region);
77963
78245
  } else if (typeof options.assumeTaskRole === "string") {
77964
78246
  resolvedRoleArn = options.assumeTaskRole;
@@ -78006,6 +78288,24 @@ async function localRunTaskCommand(target, options) {
78006
78288
  await cleanup();
78007
78289
  }
78008
78290
  }
78291
+ async function resolvePlaceholderAccount(arn, region) {
78292
+ if (!arn.includes(TASK_ROLE_ACCOUNT_PLACEHOLDER))
78293
+ return arn;
78294
+ const { STSClient: STSClient11, GetCallerIdentityCommand: GetCallerIdentityCommand12 } = await import("@aws-sdk/client-sts");
78295
+ const sts = new STSClient11({ ...region && { region } });
78296
+ try {
78297
+ const identity = await sts.send(new GetCallerIdentityCommand12({}));
78298
+ const account = identity.Account;
78299
+ if (!account) {
78300
+ throw new Error(
78301
+ `--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN '${arn}'. Pass the ARN explicitly: --assume-task-role <arn>`
78302
+ );
78303
+ }
78304
+ return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
78305
+ } finally {
78306
+ sts.destroy();
78307
+ }
78308
+ }
78009
78309
  async function assumeTaskRole(roleArn, region) {
78010
78310
  const { STSClient: STSClient11, AssumeRoleCommand: AssumeRoleCommand2 } = await import("@aws-sdk/client-sts");
78011
78311
  const sts = new STSClient11({ ...region && { region } });
@@ -78030,6 +78330,80 @@ async function assumeTaskRole(roleArn, region) {
78030
78330
  sts.destroy();
78031
78331
  }
78032
78332
  }
78333
+ async function buildEcsImageResolutionContext(target, stacks, options) {
78334
+ const logger = getLogger();
78335
+ const parsed = parseEcsTarget(target);
78336
+ const candidate = pickCandidateStack(parsed.stackPattern, stacks);
78337
+ if (!candidate)
78338
+ return void 0;
78339
+ const needs = detectEcsImageResolutionNeeds(candidate);
78340
+ if (!needs.needsPseudoParameters && !needs.needsStateResources)
78341
+ return void 0;
78342
+ const ctx = {};
78343
+ if (needs.needsPseudoParameters) {
78344
+ const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
78345
+ if (!region) {
78346
+ logger.warn(
78347
+ "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."
78348
+ );
78349
+ }
78350
+ let accountId;
78351
+ try {
78352
+ accountId = await resolveCallerAccountId(region);
78353
+ } catch (err) {
78354
+ logger.warn(
78355
+ `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.`
78356
+ );
78357
+ }
78358
+ const partitionAndSuffix = region ? derivePartitionAndUrlSuffix(region) : void 0;
78359
+ ctx.pseudoParameters = {
78360
+ ...accountId !== void 0 && { accountId },
78361
+ ...region !== void 0 && { region },
78362
+ ...partitionAndSuffix && {
78363
+ partition: partitionAndSuffix.partition,
78364
+ urlSuffix: partitionAndSuffix.urlSuffix
78365
+ }
78366
+ };
78367
+ }
78368
+ if (options.fromState && needs.needsStateResources) {
78369
+ const loaded = await loadStateForStack(candidate.stackName, candidate.region, {
78370
+ ...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
78371
+ ...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
78372
+ statePrefix: options.statePrefix,
78373
+ ...options.region !== void 0 && { region: options.region },
78374
+ ...options.profile !== void 0 && { profile: options.profile }
78375
+ });
78376
+ if (loaded) {
78377
+ ctx.stateResources = loaded.state.resources;
78378
+ }
78379
+ } else if (!options.fromState && needs.needsStateResources) {
78380
+ logger.warn(
78381
+ "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."
78382
+ );
78383
+ }
78384
+ return ctx;
78385
+ }
78386
+ function pickCandidateStack(stackPattern, stacks) {
78387
+ if (stackPattern === null) {
78388
+ if (stacks.length === 1)
78389
+ return stacks[0];
78390
+ return void 0;
78391
+ }
78392
+ const matched = matchStacks(stacks, [stackPattern]);
78393
+ if (matched.length === 1)
78394
+ return matched[0];
78395
+ return void 0;
78396
+ }
78397
+ async function resolveCallerAccountId(region) {
78398
+ const { STSClient: STSClient11, GetCallerIdentityCommand: GetCallerIdentityCommand12 } = await import("@aws-sdk/client-sts");
78399
+ const sts = new STSClient11({ ...region && { region } });
78400
+ try {
78401
+ const identity = await sts.send(new GetCallerIdentityCommand12({}));
78402
+ return identity.Account;
78403
+ } finally {
78404
+ sts.destroy();
78405
+ }
78406
+ }
78033
78407
  function readEnvOverridesFile2(filePath) {
78034
78408
  if (!filePath)
78035
78409
  return void 0;
@@ -78097,8 +78471,20 @@ function createLocalRunTaskCommand() {
78097
78471
  "--detach",
78098
78472
  "Start the containers in the background and exit (skip log streaming + auto teardown). Useful in CI smoke tests; caller manages container lifecycle."
78099
78473
  ).default(false)
78474
+ ).addOption(
78475
+ new Option8(
78476
+ "--from-state",
78477
+ "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."
78478
+ ).default(false)
78479
+ ).addOption(
78480
+ new Option8(
78481
+ "--stack-region <region>",
78482
+ "Region of the cdkd state record to read (used with --from-state when the same stack name has state in multiple regions)."
78483
+ )
78100
78484
  ).action(withErrorHandling(localRunTaskCommand));
78101
- [...commonOptions, ...appOptions, ...contextOptions].forEach((opt) => cmd.addOption(opt));
78485
+ [...commonOptions, ...appOptions, ...contextOptions, ...stateOptions].forEach(
78486
+ (opt) => cmd.addOption(opt)
78487
+ );
78102
78488
  cmd.addOption(deprecatedRegionOption);
78103
78489
  return cmd;
78104
78490
  }
@@ -78507,72 +78893,6 @@ function materializeInlineCode2(handler, source, fileExtension) {
78507
78893
  writeFileSync6(filePath, source, "utf-8");
78508
78894
  return dir;
78509
78895
  }
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
78896
  function suggestAssumeRoleFromState(state, logicalId) {
78577
78897
  const logger = getLogger();
78578
78898
  const lambda = state.resources[logicalId];
@@ -78688,7 +79008,7 @@ function reorderArgs(argv) {
78688
79008
  }
78689
79009
  async function main() {
78690
79010
  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");
79011
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.83.1");
78692
79012
  program.addCommand(createBootstrapCommand());
78693
79013
  program.addCommand(createSynthCommand());
78694
79014
  program.addCommand(createListCommand());