cdk-local 0.71.2 → 0.73.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 +63 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +61 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -2
- package/dist/{local-list-DA9d0XQR.d.ts → local-list-8PbawOd2.d.ts} +9 -2
- package/dist/{local-list-DA9d0XQR.d.ts.map → local-list-8PbawOd2.d.ts.map} +1 -1
- package/dist/{local-list-BS463YYd.js → local-list-Ch0RglJl.js} +658 -22
- package/dist/local-list-Ch0RglJl.js.map +1 -0
- package/package.json +1 -1
- package/dist/local-list-BS463YYd.js.map +0 -1
|
@@ -6,7 +6,7 @@ import path, { dirname, isAbsolute, join, normalize, resolve, sep } from "node:p
|
|
|
6
6
|
import { Command, Option } from "commander";
|
|
7
7
|
import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
|
|
8
8
|
import { MultiSelectPrompt } from "@clack/core";
|
|
9
|
-
import { S_BAR, S_BAR_END, S_BAR_START, S_CHECKBOX_ACTIVE, S_CHECKBOX_INACTIVE, S_CHECKBOX_SELECTED, confirm, isCancel, select } from "@clack/prompts";
|
|
9
|
+
import { S_BAR, S_BAR_END, S_BAR_START, S_CHECKBOX_ACTIVE, S_CHECKBOX_INACTIVE, S_CHECKBOX_SELECTED, confirm, isCancel, multiselect, select, text } from "@clack/prompts";
|
|
10
10
|
import { AssetManifestArtifact } from "@aws-cdk/cloud-assembly-api";
|
|
11
11
|
import { BaseCredentials, CdkAppMultiContext, NonInteractiveIoHost, Toolkit } from "@aws-cdk/toolkit-lib";
|
|
12
12
|
import { CloudFormationClient, DescribeStacksCommand, ListExportsCommand, ListStackResourcesCommand } from "@aws-sdk/client-cloudformation";
|
|
@@ -20055,6 +20055,12 @@ function sleep$1(ms) {
|
|
|
20055
20055
|
async function prepareImages(task, out, options) {
|
|
20056
20056
|
const logger = getLogger().child("ecs-runner");
|
|
20057
20057
|
for (const container of task.containers) {
|
|
20058
|
+
const overrideTag = options.imageOverrideByContainer?.get(container.name);
|
|
20059
|
+
if (overrideTag !== void 0) {
|
|
20060
|
+
out.set(container.name, overrideTag);
|
|
20061
|
+
logger.debug(`Container '${container.name}' image=${overrideTag} (--image-override)`);
|
|
20062
|
+
continue;
|
|
20063
|
+
}
|
|
20058
20064
|
const image = await prepareOneImage(task, container, options);
|
|
20059
20065
|
out.set(container.name, image);
|
|
20060
20066
|
logger.debug(`Container '${container.name}' image=${image}`);
|
|
@@ -24242,6 +24248,516 @@ function describePinnedImageUri(service) {
|
|
|
24242
24248
|
if (image.kind === "cdk-asset") return void 0;
|
|
24243
24249
|
return image.uri;
|
|
24244
24250
|
}
|
|
24251
|
+
/**
|
|
24252
|
+
* Issue #242 / N1 — dedupe pinned-target detection across the two
|
|
24253
|
+
* sites that need it during a `cdkl start-service` / `cdkl start-alb`
|
|
24254
|
+
* boot:
|
|
24255
|
+
*
|
|
24256
|
+
* 1. The `--image-override` engine's pre-boot resolution
|
|
24257
|
+
* ({@link resolveImageOverrides}) — needs the pinned set + per-
|
|
24258
|
+
* target deployed-registry URI labels so the picker + boot-prompt
|
|
24259
|
+
* hint can name the image the user is overriding.
|
|
24260
|
+
* 2. The post-boot WARN loop in `runEcsServiceEmulator` — surfaces a
|
|
24261
|
+
* per-target WARN naming the deployed-registry URI for every
|
|
24262
|
+
* pinned target the override engine did NOT cover.
|
|
24263
|
+
*
|
|
24264
|
+
* Before this helper both sites re-walked `isLocalCdkAssetImage` +
|
|
24265
|
+
* `describePinnedImageUri` independently. The semantic computation is
|
|
24266
|
+
* identical — given a `ResolvedEcsService`, decide pinned vs local-
|
|
24267
|
+
* asset and (for pinned targets) surface the URI label. Hoisting the
|
|
24268
|
+
* walk here keeps the two call sites in lock-step and gives both a
|
|
24269
|
+
* single test surface.
|
|
24270
|
+
*
|
|
24271
|
+
* Returns one entry per pinned target in the input's iteration order.
|
|
24272
|
+
* Targets whose representative image is a local CDK asset are
|
|
24273
|
+
* filtered out — the caller never sees them.
|
|
24274
|
+
*/
|
|
24275
|
+
function listPinnedTargets(resolvedServices) {
|
|
24276
|
+
const out = [];
|
|
24277
|
+
for (const { target, service } of resolvedServices) {
|
|
24278
|
+
if (isLocalCdkAssetImage(service)) continue;
|
|
24279
|
+
const label = describePinnedImageUri(service);
|
|
24280
|
+
out.push({
|
|
24281
|
+
target,
|
|
24282
|
+
...label !== void 0 && { label }
|
|
24283
|
+
});
|
|
24284
|
+
}
|
|
24285
|
+
return out;
|
|
24286
|
+
}
|
|
24287
|
+
|
|
24288
|
+
//#endregion
|
|
24289
|
+
//#region src/local/image-override-engine.ts
|
|
24290
|
+
/**
|
|
24291
|
+
* Errors surfaced by the engine. Used so the emulator can render a
|
|
24292
|
+
* consistent error class to the user (the global error handler maps
|
|
24293
|
+
* `CdkLocalError` subclasses to a clean stack-trace-free message).
|
|
24294
|
+
*/
|
|
24295
|
+
var ImageOverrideError = class ImageOverrideError extends CdkLocalError {
|
|
24296
|
+
constructor(message, cause) {
|
|
24297
|
+
super(message, "IMAGE_OVERRIDE_ERROR", cause);
|
|
24298
|
+
this.name = "ImageOverrideError";
|
|
24299
|
+
Object.setPrototypeOf(this, ImageOverrideError.prototype);
|
|
24300
|
+
}
|
|
24301
|
+
};
|
|
24302
|
+
/**
|
|
24303
|
+
* Parse the raw `--image-override` / `--image-build-arg` /
|
|
24304
|
+
* `--image-build-secret` / `--image-target` flag arrays Commander
|
|
24305
|
+
* surfaces into the structured {@link RawImageOverrideFlags} shape.
|
|
24306
|
+
* Pure (no docker / filesystem calls); throws
|
|
24307
|
+
* {@link ImageOverrideError} on a malformed token so the emulator can
|
|
24308
|
+
* surface a clean error before any container is touched.
|
|
24309
|
+
*
|
|
24310
|
+
* - `--image-override <svc>=<dockerfile>` -> `explicit.set(svc, dockerfile)`
|
|
24311
|
+
* - `--image-override <dockerfile>` (no `=`) -> `pickerPaths.push(...)`
|
|
24312
|
+
* - `--image-build-arg KEY=VAL` -> `globals.buildArgs.set('KEY', 'VAL')`
|
|
24313
|
+
* - `--image-build-arg <svc>:KEY=VAL` -> `perService(svc).buildArgs.set('KEY', 'VAL')` (issue #240)
|
|
24314
|
+
* - `--image-build-secret id=src` -> `globals.buildSecrets.set('id', 'src')`
|
|
24315
|
+
* - `--image-build-secret <svc>:id=src` -> `perService(svc).buildSecrets.set('id', 'src')` (issue #240)
|
|
24316
|
+
* - `--image-target <stage>` -> `globals.targetStage = '<stage>'`
|
|
24317
|
+
* - `--image-target <svc>=<stage>` -> `perService(svc).targetStage = '<stage>'` (issue #240)
|
|
24318
|
+
*
|
|
24319
|
+
* Per-service syntax convention (issue #240):
|
|
24320
|
+
* - Flags whose payload already contains `=` (build-arg, build-secret)
|
|
24321
|
+
* use `:` to separate the service prefix from the `<key>=<value>`
|
|
24322
|
+
* payload — `<svc>:KEY=VAL`. The FIRST `:` before the FIRST `=` (if
|
|
24323
|
+
* any) is treated as the prefix delimiter.
|
|
24324
|
+
* - Flags whose payload is a single token (target) use `=` to separate
|
|
24325
|
+
* the service prefix from the stage — `<svc>=stage`. Matches the
|
|
24326
|
+
* `--image-override <svc>=<dockerfile>` convention.
|
|
24327
|
+
* - Per-service entries override the global entry per-key when both
|
|
24328
|
+
* forms set the same key on the same target. Globals still apply to
|
|
24329
|
+
* every OTHER overridden target.
|
|
24330
|
+
*
|
|
24331
|
+
* Collision detection: a service target named more than once in
|
|
24332
|
+
* `--image-override <svc>=...` is an error (last-write-wins would
|
|
24333
|
+
* silently drop the earlier mapping; explicit error is clearer).
|
|
24334
|
+
* Repeated per-service `--image-build-arg <svc>:KEY=...` entries on
|
|
24335
|
+
* the same `<svc>:KEY` pair are last-write-wins (the same shape the
|
|
24336
|
+
* global form already uses for repeated `KEY=...` entries — both
|
|
24337
|
+
* forms behave identically inside their respective bucket).
|
|
24338
|
+
*
|
|
24339
|
+
* Empty-value semantics:
|
|
24340
|
+
* - `--image-build-arg KEY=` (empty value) is ACCEPTED. The empty
|
|
24341
|
+
* string is forwarded verbatim to `docker build --build-arg KEY=`,
|
|
24342
|
+
* which docker itself accepts (the canonical way to unset a
|
|
24343
|
+
* Dockerfile `ARG`'s default). `KEY=` (empty key) is rejected.
|
|
24344
|
+
* Per-service form `<svc>:KEY=` is similarly accepted (same empty-
|
|
24345
|
+
* value semantics applied to the per-service entry).
|
|
24346
|
+
* - `--image-target ""` (empty value) is REJECTED — an empty
|
|
24347
|
+
* `--target` is meaningless to `docker build` (`--target` is a
|
|
24348
|
+
* single stage name, not a list). Per-service `<svc>=` is similarly
|
|
24349
|
+
* rejected (empty stage).
|
|
24350
|
+
* - `--image-override <svc>=` and `--image-override =<dockerfile>`
|
|
24351
|
+
* are REJECTED — both halves must be non-empty.
|
|
24352
|
+
* - `--image-build-secret id=` and `--image-build-secret =src` are
|
|
24353
|
+
* REJECTED — both halves must be non-empty. Per-service form
|
|
24354
|
+
* `<svc>:id=` and `<svc>:=src` are similarly rejected.
|
|
24355
|
+
*/
|
|
24356
|
+
function parseImageOverrideFlags(input) {
|
|
24357
|
+
const explicit = /* @__PURE__ */ new Map();
|
|
24358
|
+
const pickerPaths = [];
|
|
24359
|
+
const perService = /* @__PURE__ */ new Map();
|
|
24360
|
+
const getPerSvc = (svc) => {
|
|
24361
|
+
let entry = perService.get(svc);
|
|
24362
|
+
if (!entry) {
|
|
24363
|
+
entry = {
|
|
24364
|
+
buildArgs: /* @__PURE__ */ new Map(),
|
|
24365
|
+
buildSecrets: /* @__PURE__ */ new Map()
|
|
24366
|
+
};
|
|
24367
|
+
perService.set(svc, entry);
|
|
24368
|
+
}
|
|
24369
|
+
return entry;
|
|
24370
|
+
};
|
|
24371
|
+
const splitPerServicePrefix = (raw) => {
|
|
24372
|
+
const colon = raw.indexOf(":");
|
|
24373
|
+
const eq = raw.indexOf("=");
|
|
24374
|
+
if (colon < 0) return null;
|
|
24375
|
+
if (eq >= 0 && eq < colon) return null;
|
|
24376
|
+
const svc = raw.slice(0, colon).trim();
|
|
24377
|
+
if (!svc) return null;
|
|
24378
|
+
return {
|
|
24379
|
+
svc,
|
|
24380
|
+
rest: raw.slice(colon + 1)
|
|
24381
|
+
};
|
|
24382
|
+
};
|
|
24383
|
+
for (const raw of input.imageOverride ?? []) {
|
|
24384
|
+
const eq = raw.indexOf("=");
|
|
24385
|
+
if (eq < 0) {
|
|
24386
|
+
if (!raw) throw new ImageOverrideError("Invalid --image-override value: empty string. Pass <service>=<dockerfile> or just <dockerfile>.");
|
|
24387
|
+
pickerPaths.push(raw);
|
|
24388
|
+
continue;
|
|
24389
|
+
}
|
|
24390
|
+
const svc = raw.slice(0, eq).trim();
|
|
24391
|
+
const dockerfile = raw.slice(eq + 1).trim();
|
|
24392
|
+
if (!svc) throw new ImageOverrideError(`Invalid --image-override value "${raw}": left side (service target) is empty.`);
|
|
24393
|
+
if (!dockerfile) throw new ImageOverrideError(`Invalid --image-override value "${raw}": right side (Dockerfile path) is empty.`);
|
|
24394
|
+
if (explicit.has(svc)) throw new ImageOverrideError(`Duplicate --image-override for service '${svc}': already mapped to '${explicit.get(svc)}'. Pass each service target at most once in explicit form.`);
|
|
24395
|
+
explicit.set(svc, dockerfile);
|
|
24396
|
+
}
|
|
24397
|
+
const buildArgs = /* @__PURE__ */ new Map();
|
|
24398
|
+
for (const raw of input.imageBuildArg ?? []) {
|
|
24399
|
+
const prefix = splitPerServicePrefix(raw);
|
|
24400
|
+
if (prefix) {
|
|
24401
|
+
const eq = prefix.rest.indexOf("=");
|
|
24402
|
+
if (eq <= 0) throw new ImageOverrideError(`Invalid --image-build-arg value "${raw}": expected <service>:KEY=VAL.`);
|
|
24403
|
+
const key = prefix.rest.slice(0, eq).trim();
|
|
24404
|
+
const value = prefix.rest.slice(eq + 1);
|
|
24405
|
+
if (!key) throw new ImageOverrideError(`Invalid --image-build-arg value "${raw}": empty key (after service prefix).`);
|
|
24406
|
+
getPerSvc(prefix.svc).buildArgs.set(key, value);
|
|
24407
|
+
continue;
|
|
24408
|
+
}
|
|
24409
|
+
const eq = raw.indexOf("=");
|
|
24410
|
+
if (eq <= 0) throw new ImageOverrideError(`Invalid --image-build-arg value "${raw}": expected KEY=VAL.`);
|
|
24411
|
+
const key = raw.slice(0, eq).trim();
|
|
24412
|
+
const value = raw.slice(eq + 1);
|
|
24413
|
+
if (!key) throw new ImageOverrideError(`Invalid --image-build-arg value "${raw}": empty key.`);
|
|
24414
|
+
buildArgs.set(key, value);
|
|
24415
|
+
}
|
|
24416
|
+
const buildSecrets = /* @__PURE__ */ new Map();
|
|
24417
|
+
for (const raw of input.imageBuildSecret ?? []) {
|
|
24418
|
+
const prefix = splitPerServicePrefix(raw);
|
|
24419
|
+
if (prefix) {
|
|
24420
|
+
const eq = prefix.rest.indexOf("=");
|
|
24421
|
+
if (eq <= 0) throw new ImageOverrideError(`Invalid --image-build-secret value "${raw}": expected <service>:id=src (e.g. AppService:npmrc=./.npmrc).`);
|
|
24422
|
+
const id = prefix.rest.slice(0, eq).trim();
|
|
24423
|
+
const src = prefix.rest.slice(eq + 1).trim();
|
|
24424
|
+
if (!id || !src) throw new ImageOverrideError(`Invalid --image-build-secret value "${raw}": id and src must both be non-empty (after service prefix).`);
|
|
24425
|
+
const absSrcPS = isAbsolute(src) ? src : resolve(process.cwd(), src);
|
|
24426
|
+
getPerSvc(prefix.svc).buildSecrets.set(id, absSrcPS);
|
|
24427
|
+
continue;
|
|
24428
|
+
}
|
|
24429
|
+
const eq = raw.indexOf("=");
|
|
24430
|
+
if (eq <= 0) throw new ImageOverrideError(`Invalid --image-build-secret value "${raw}": expected id=src (e.g. npmrc=./.npmrc).`);
|
|
24431
|
+
const id = raw.slice(0, eq).trim();
|
|
24432
|
+
const src = raw.slice(eq + 1).trim();
|
|
24433
|
+
if (!id || !src) throw new ImageOverrideError(`Invalid --image-build-secret value "${raw}": id and src must both be non-empty.`);
|
|
24434
|
+
const absSrc = isAbsolute(src) ? src : resolve(process.cwd(), src);
|
|
24435
|
+
buildSecrets.set(id, absSrc);
|
|
24436
|
+
}
|
|
24437
|
+
const globals = {
|
|
24438
|
+
buildArgs,
|
|
24439
|
+
buildSecrets
|
|
24440
|
+
};
|
|
24441
|
+
const imageTargetList = input.imageTarget === void 0 ? [] : Array.isArray(input.imageTarget) ? input.imageTarget : [input.imageTarget];
|
|
24442
|
+
for (const raw of imageTargetList) {
|
|
24443
|
+
if (raw === void 0 || raw === null) continue;
|
|
24444
|
+
if (!raw) throw new ImageOverrideError("Invalid --image-target value: empty string.");
|
|
24445
|
+
const eq = raw.indexOf("=");
|
|
24446
|
+
if (eq < 0) {
|
|
24447
|
+
globals.targetStage = raw.trim();
|
|
24448
|
+
continue;
|
|
24449
|
+
}
|
|
24450
|
+
const svc = raw.slice(0, eq).trim();
|
|
24451
|
+
const stage = raw.slice(eq + 1).trim();
|
|
24452
|
+
if (!svc) throw new ImageOverrideError(`Invalid --image-target value "${raw}": left side (service target) is empty.`);
|
|
24453
|
+
if (!stage) throw new ImageOverrideError(`Invalid --image-target value "${raw}": right side (stage) is empty.`);
|
|
24454
|
+
getPerSvc(svc).targetStage = stage;
|
|
24455
|
+
}
|
|
24456
|
+
return {
|
|
24457
|
+
explicit,
|
|
24458
|
+
pickerPaths,
|
|
24459
|
+
globals,
|
|
24460
|
+
perService
|
|
24461
|
+
};
|
|
24462
|
+
}
|
|
24463
|
+
/**
|
|
24464
|
+
* Resolve `--image-override` flag inputs (raw) + a list of pinned
|
|
24465
|
+
* service targets into the final {@link ImageOverrideMap}. Walks four
|
|
24466
|
+
* stages:
|
|
24467
|
+
*
|
|
24468
|
+
* 1. Apply every explicit `<svc>=<dockerfile>` mapping. A service in
|
|
24469
|
+
* `explicit` but NOT in `pinnedTargets` is a no-op warning
|
|
24470
|
+
* (logged at warn) — the user named a non-pinned target, which
|
|
24471
|
+
* probably indicates a typo or a CDK app change; we don't fail.
|
|
24472
|
+
* 2. For each picker-form Dockerfile path, fire a multi-select against
|
|
24473
|
+
* the still-uncovered pinned targets. A single picker-form path
|
|
24474
|
+
* may bind to multiple targets (one Dockerfile shared across N
|
|
24475
|
+
* services); already-bound targets are excluded from later pickers.
|
|
24476
|
+
* Skipped when not in a TTY OR `noInteractive` is true — those
|
|
24477
|
+
* contexts emit a warning and skip the picker path (treated as
|
|
24478
|
+
* "no override for any target").
|
|
24479
|
+
* 3. Optional boot prompt: when `interactiveBootPrompt` is true AND
|
|
24480
|
+
* we're in a TTY, walk each remaining uncovered pinned target
|
|
24481
|
+
* and ask "Override with a local build? [path / N]" via clack's
|
|
24482
|
+
* `text` prompt. A non-empty answer is treated as an additional
|
|
24483
|
+
* explicit mapping; `N` / empty / Esc skips the target.
|
|
24484
|
+
*
|
|
24485
|
+
* Returns the resolved override map. Throws
|
|
24486
|
+
* {@link ImageOverrideError} on a malformed picker response or a
|
|
24487
|
+
* Dockerfile path that doesn't exist (caught up-front before any
|
|
24488
|
+
* docker build runs).
|
|
24489
|
+
*/
|
|
24490
|
+
async function resolveImageOverrides(args) {
|
|
24491
|
+
const logger = getLogger();
|
|
24492
|
+
const { rawFlags, pinnedTargets, noInteractive } = args;
|
|
24493
|
+
const cwd = args.cwd ?? process.cwd();
|
|
24494
|
+
const pinnedSet = new Set(pinnedTargets);
|
|
24495
|
+
const labels = args.pinnedLabels ?? /* @__PURE__ */ new Map();
|
|
24496
|
+
const out = /* @__PURE__ */ new Map();
|
|
24497
|
+
for (const [svc, dockerfileRaw] of rawFlags.explicit.entries()) {
|
|
24498
|
+
if (!pinnedSet.has(svc)) {
|
|
24499
|
+
logger.warn(`--image-override: service '${svc}' is not in the pinned-target set (no deployed-registry pin detected for it). Mapping ignored.`);
|
|
24500
|
+
continue;
|
|
24501
|
+
}
|
|
24502
|
+
out.set(svc, makeEntryFromPath(dockerfileRaw, svc, rawFlags, cwd));
|
|
24503
|
+
}
|
|
24504
|
+
if (rawFlags.pickerPaths.length > 0) if (!(isInteractive() && noInteractive !== true)) logger.warn("--image-override <dockerfile> (picker form) requires an interactive TTY and --no-interactive-overrides not to be set. Skipping picker-form mapping(s).");
|
|
24505
|
+
else for (const dockerfileRaw of rawFlags.pickerPaths) {
|
|
24506
|
+
const uncovered = pinnedTargets.filter((t) => !out.has(t));
|
|
24507
|
+
if (uncovered.length === 0) {
|
|
24508
|
+
logger.warn(`--image-override ${dockerfileRaw}: no uncovered pinned targets left to bind to. Skipped.`);
|
|
24509
|
+
continue;
|
|
24510
|
+
}
|
|
24511
|
+
const options = uncovered.map((t) => ({
|
|
24512
|
+
value: t,
|
|
24513
|
+
label: t,
|
|
24514
|
+
...labels.get(t) ? { hint: labels.get(t) } : {}
|
|
24515
|
+
}));
|
|
24516
|
+
const picked = await multiselect({
|
|
24517
|
+
message: `Pick the pinned target(s) to bind to '${dockerfileRaw}' (space to toggle, enter to confirm; empty cancels):`,
|
|
24518
|
+
options,
|
|
24519
|
+
required: false
|
|
24520
|
+
});
|
|
24521
|
+
if (isCancel(picked)) throw new ImageOverrideError("Image-override picker cancelled. Re-run without picker-form or with --no-interactive-overrides.");
|
|
24522
|
+
const chosen = picked ?? [];
|
|
24523
|
+
if (chosen.length === 0) {
|
|
24524
|
+
logger.warn(`--image-override ${dockerfileRaw}: empty selection. Skipped.`);
|
|
24525
|
+
continue;
|
|
24526
|
+
}
|
|
24527
|
+
for (const t of chosen) out.set(t, makeEntryFromPath(dockerfileRaw, t, rawFlags, cwd));
|
|
24528
|
+
}
|
|
24529
|
+
if (args.interactiveBootPrompt === true && isInteractive() && noInteractive !== true) for (const target of pinnedTargets) {
|
|
24530
|
+
if (out.has(target)) continue;
|
|
24531
|
+
const label = labels.get(target);
|
|
24532
|
+
const answer = await text({
|
|
24533
|
+
message: `Detected pinned image on '${target}'` + (label ? ` (${label})` : "") + ". Override with a local build? [path / N]:",
|
|
24534
|
+
placeholder: ""
|
|
24535
|
+
});
|
|
24536
|
+
if (isCancel(answer)) throw new ImageOverrideError("Image-override boot prompt cancelled. Re-run with --no-interactive-overrides to suppress, or pass --image-override explicitly.");
|
|
24537
|
+
const value = (answer ?? "").trim();
|
|
24538
|
+
if (!value) continue;
|
|
24539
|
+
const lower = value.toLowerCase();
|
|
24540
|
+
if (lower === "n" || lower === "no") continue;
|
|
24541
|
+
out.set(target, makeEntryFromPath(value, target, rawFlags, cwd));
|
|
24542
|
+
}
|
|
24543
|
+
return out;
|
|
24544
|
+
}
|
|
24545
|
+
/**
|
|
24546
|
+
* Issue #240 — produce the EFFECTIVE per-target build inputs by layering
|
|
24547
|
+
* a per-service overlay on top of the global baseline. Per-service
|
|
24548
|
+
* entries override the global per-key (and per-service `targetStage`
|
|
24549
|
+
* overrides the global `targetStage`).
|
|
24550
|
+
*
|
|
24551
|
+
* The returned Maps are fresh copies — the caller may mutate the
|
|
24552
|
+
* resulting {@link ImageOverrideEntry}'s Maps without bleeding back
|
|
24553
|
+
* into the shared global Maps inside {@link RawImageOverrideFlags}.
|
|
24554
|
+
* Per-service entries are merged AFTER globals so a key shared between
|
|
24555
|
+
* the two ends up with the per-service value (Map's `set` overwrites
|
|
24556
|
+
* on duplicate key).
|
|
24557
|
+
*/
|
|
24558
|
+
function mergeForService(serviceTarget, globals, perService) {
|
|
24559
|
+
const buildArgs = new Map(globals.buildArgs);
|
|
24560
|
+
const buildSecrets = new Map(globals.buildSecrets);
|
|
24561
|
+
let targetStage = globals.targetStage;
|
|
24562
|
+
const overlay = perService.get(serviceTarget);
|
|
24563
|
+
if (overlay) {
|
|
24564
|
+
for (const [k, v] of overlay.buildArgs.entries()) buildArgs.set(k, v);
|
|
24565
|
+
for (const [k, v] of overlay.buildSecrets.entries()) buildSecrets.set(k, v);
|
|
24566
|
+
if (overlay.targetStage !== void 0) targetStage = overlay.targetStage;
|
|
24567
|
+
}
|
|
24568
|
+
return {
|
|
24569
|
+
buildArgs,
|
|
24570
|
+
buildSecrets,
|
|
24571
|
+
...targetStage !== void 0 && { targetStage }
|
|
24572
|
+
};
|
|
24573
|
+
}
|
|
24574
|
+
/**
|
|
24575
|
+
* Promote a per-target Dockerfile path + raw flags into a full
|
|
24576
|
+
* {@link ImageOverrideEntry}. Resolves the path against `cwd`, asserts
|
|
24577
|
+
* the Dockerfile exists and is a regular file (so the user sees
|
|
24578
|
+
* "file not found" up-front rather than mid-`docker build`), and
|
|
24579
|
+
* derives the build context as the Dockerfile's parent directory
|
|
24580
|
+
* (the v1 simplification — custom contexts are tracked separately).
|
|
24581
|
+
*
|
|
24582
|
+
* Issue #240 — `serviceTarget` is now part of the entry-construction
|
|
24583
|
+
* input so the global + per-service merge runs per target.
|
|
24584
|
+
*/
|
|
24585
|
+
function makeEntryFromPath(raw, serviceTarget, rawFlags, cwd) {
|
|
24586
|
+
const abs = isAbsolute(raw) ? raw : resolve(cwd, raw);
|
|
24587
|
+
if (!existsSync(abs)) throw new ImageOverrideError(`--image-override: Dockerfile '${raw}' does not exist (resolved to '${abs}').`);
|
|
24588
|
+
if (!statSync(abs).isFile()) throw new ImageOverrideError(`--image-override: '${raw}' is not a regular file (resolved to '${abs}').`);
|
|
24589
|
+
const merged = mergeForService(serviceTarget, rawFlags.globals, rawFlags.perService);
|
|
24590
|
+
return {
|
|
24591
|
+
dockerfile: abs,
|
|
24592
|
+
contextDir: dirname(abs),
|
|
24593
|
+
buildArgs: merged.buildArgs,
|
|
24594
|
+
buildSecrets: merged.buildSecrets,
|
|
24595
|
+
...merged.targetStage !== void 0 && { targetStage: merged.targetStage }
|
|
24596
|
+
};
|
|
24597
|
+
}
|
|
24598
|
+
/**
|
|
24599
|
+
* Deterministic local-only image tag for one override entry. Fingerprints
|
|
24600
|
+
* the Dockerfile path + its CONTENTS (sha256 of the bytes) + service
|
|
24601
|
+
* target + build args / secrets / stage. Including the Dockerfile bytes
|
|
24602
|
+
* means an edit to the Dockerfile flips the tag — the rebuild rolling
|
|
24603
|
+
* primitive then boots a new container under the bumped tag instead of
|
|
24604
|
+
* reusing the old build's cached layers under a stale tag. Two
|
|
24605
|
+
* back-to-back runs against an unchanged Dockerfile + flags still hit
|
|
24606
|
+
* the same tag so docker's layer cache works.
|
|
24607
|
+
*
|
|
24608
|
+
* Tag shape: `<resourceNamePrefix>-override-<svcSlug>-<hash>:local`.
|
|
24609
|
+
* `:local` is intentionally NOT `:latest` so a `docker pull` on it fails
|
|
24610
|
+
* fast (these images are local-build-only by design).
|
|
24611
|
+
*
|
|
24612
|
+
* Throws on a missing Dockerfile — the caller has already asserted
|
|
24613
|
+
* existence in {@link makeEntryFromPath}, so a throw here is a
|
|
24614
|
+
* programming error (or the Dockerfile vanished between resolve and
|
|
24615
|
+
* build, which is a race the user wants to know about).
|
|
24616
|
+
*/
|
|
24617
|
+
function buildImageOverrideTag(serviceTarget, entry) {
|
|
24618
|
+
const hash = createHash("sha256");
|
|
24619
|
+
hash.update("dockerfile=");
|
|
24620
|
+
hash.update(entry.dockerfile);
|
|
24621
|
+
hash.update("\0dockerfile-bytes-sha256=");
|
|
24622
|
+
const dockerfileBytesDigest = createHash("sha256").update(readFileSync(entry.dockerfile)).digest("hex");
|
|
24623
|
+
hash.update(dockerfileBytesDigest);
|
|
24624
|
+
hash.update("\0target=");
|
|
24625
|
+
hash.update(serviceTarget);
|
|
24626
|
+
hash.update("\0stage=");
|
|
24627
|
+
hash.update(entry.targetStage ?? "");
|
|
24628
|
+
hash.update("\0args={");
|
|
24629
|
+
for (const [k, v] of entry.buildArgs.entries()) {
|
|
24630
|
+
hash.update(k);
|
|
24631
|
+
hash.update("=");
|
|
24632
|
+
hash.update(v);
|
|
24633
|
+
hash.update(";");
|
|
24634
|
+
}
|
|
24635
|
+
hash.update("}\0secrets={");
|
|
24636
|
+
for (const [k, v] of entry.buildSecrets.entries()) {
|
|
24637
|
+
hash.update(k);
|
|
24638
|
+
hash.update("=");
|
|
24639
|
+
hash.update(v);
|
|
24640
|
+
hash.update(";");
|
|
24641
|
+
}
|
|
24642
|
+
hash.update("}");
|
|
24643
|
+
const svcSlug = serviceTarget.replace(/[^A-Za-z0-9]+/g, "-").slice(0, 24).toLowerCase();
|
|
24644
|
+
return `${getEmbedConfig().resourceNamePrefix}-override-${svcSlug}-${hash.digest("hex").slice(0, 16)}:local`;
|
|
24645
|
+
}
|
|
24646
|
+
/**
|
|
24647
|
+
* Build every overridden image via `docker build`. Returns a per-target
|
|
24648
|
+
* `serviceTarget -> localTag` map the emulator threads into each
|
|
24649
|
+
* runner's `imageOverrideByContainer` (mutating the resolved service's
|
|
24650
|
+
* representative container's image to this tag).
|
|
24651
|
+
*
|
|
24652
|
+
* Each build runs sequentially (parallelism is left to docker's own
|
|
24653
|
+
* BuildKit cache — most overrides share a base image, so a sequential
|
|
24654
|
+
* order maximizes cache reuse). On any failure the function rejects
|
|
24655
|
+
* with {@link ImageOverrideError}; the emulator surfaces this before
|
|
24656
|
+
* any container is started.
|
|
24657
|
+
*
|
|
24658
|
+
* Partial-failure rollback (issue #242 / N3): when build N (1-indexed)
|
|
24659
|
+
* fails, the 1..(N-1) successfully built local-only tags from THIS run
|
|
24660
|
+
* are best-effort `docker image rm`'d before re-throwing. Not a leak
|
|
24661
|
+
* (the tags are deterministic per
|
|
24662
|
+
* {@link buildImageOverrideTag} so a re-run collides safely with any
|
|
24663
|
+
* leftover), but the cleanup keeps the local Docker daemon tidy after
|
|
24664
|
+
* a failed boot — disk-pressure scenarios that occasionally surface
|
|
24665
|
+
* on dev laptops never accumulate orphan override images across a
|
|
24666
|
+
* day of iterations. Cleanup failures are logged at `debug` (another
|
|
24667
|
+
* container could be using the image, the image could already have
|
|
24668
|
+
* been removed by a parallel `docker system prune`, etc.) and the
|
|
24669
|
+
* loop continues so one un-removable tag doesn't shadow the others.
|
|
24670
|
+
* The originating {@link ImageOverrideError} is always the thrown
|
|
24671
|
+
* value — a cleanup-step error never replaces it.
|
|
24672
|
+
*/
|
|
24673
|
+
async function runImageOverrideBuilds(overrides) {
|
|
24674
|
+
const logger = getLogger();
|
|
24675
|
+
const out = /* @__PURE__ */ new Map();
|
|
24676
|
+
const builtTags = [];
|
|
24677
|
+
for (const [target, entry] of overrides.entries()) {
|
|
24678
|
+
const tag = buildImageOverrideTag(target, entry);
|
|
24679
|
+
const args = [
|
|
24680
|
+
"build",
|
|
24681
|
+
"--tag",
|
|
24682
|
+
tag,
|
|
24683
|
+
"--file",
|
|
24684
|
+
entry.dockerfile
|
|
24685
|
+
];
|
|
24686
|
+
for (const [k, v] of entry.buildArgs.entries()) args.push("--build-arg", `${k}=${v}`);
|
|
24687
|
+
for (const [id, src] of entry.buildSecrets.entries()) args.push("--secret", `id=${id},src=${src}`);
|
|
24688
|
+
if (entry.targetStage !== void 0) args.push("--target", entry.targetStage);
|
|
24689
|
+
args.push(".");
|
|
24690
|
+
logger.info(`Building override image for '${target}' from '${entry.dockerfile}' (tag=${tag})...`);
|
|
24691
|
+
try {
|
|
24692
|
+
await runDockerStreaming(args, {
|
|
24693
|
+
cwd: entry.contextDir,
|
|
24694
|
+
env: { BUILDX_NO_DEFAULT_ATTESTATIONS: "1" }
|
|
24695
|
+
});
|
|
24696
|
+
} catch (err) {
|
|
24697
|
+
const e = err;
|
|
24698
|
+
const wrapped = new ImageOverrideError(`docker build failed for --image-override '${target}' (Dockerfile=${entry.dockerfile}): ` + (e.stderr?.trim() || e.message || String(err)));
|
|
24699
|
+
for (const priorTag of builtTags) try {
|
|
24700
|
+
await runDockerStreaming([
|
|
24701
|
+
"image",
|
|
24702
|
+
"rm",
|
|
24703
|
+
priorTag
|
|
24704
|
+
], {});
|
|
24705
|
+
} catch (rmErr) {
|
|
24706
|
+
logger.debug(`Image-override rollback: \`docker image rm ${priorTag}\` failed: ${rmErr instanceof Error ? rmErr.message : String(rmErr)}.`);
|
|
24707
|
+
}
|
|
24708
|
+
throw wrapped;
|
|
24709
|
+
}
|
|
24710
|
+
out.set(target, tag);
|
|
24711
|
+
builtTags.push(tag);
|
|
24712
|
+
}
|
|
24713
|
+
return out;
|
|
24714
|
+
}
|
|
24715
|
+
/**
|
|
24716
|
+
* Issue #240 — orphan validation. A per-service build-input flag
|
|
24717
|
+
* (`--image-build-arg <svc>:KEY=VAL`, `--image-build-secret
|
|
24718
|
+
* <svc>:id=src`, or `--image-target <svc>=stage`) names a service that
|
|
24719
|
+
* MUST appear in the resolved override map — otherwise the per-service
|
|
24720
|
+
* value silently gets discarded because no `docker build` runs for
|
|
24721
|
+
* that service.
|
|
24722
|
+
*
|
|
24723
|
+
* Called AFTER {@link resolveImageOverrides} (Stage 3 boot prompt
|
|
24724
|
+
* complete) so a service the user added via the prompt counts as
|
|
24725
|
+
* covered. A still-orphan per-service flag at this point means the
|
|
24726
|
+
* user typo'd the service name OR forgot a corresponding
|
|
24727
|
+
* `--image-override <svc>=<dockerfile>` mapping (and either chose `N`
|
|
24728
|
+
* in the boot prompt or ran with `--no-interactive-overrides`).
|
|
24729
|
+
*
|
|
24730
|
+
* Throws {@link LocalStartServiceError} naming every offending
|
|
24731
|
+
* `<flag>` + `<service>` pair so the user can fix all in one go
|
|
24732
|
+
* (matches the `enforceStrictOverrides` pattern). Mutates nothing.
|
|
24733
|
+
*
|
|
24734
|
+
* Host-side use case: cdkd and other shim hosts that wrap the engine
|
|
24735
|
+
* re-export this via `cdk-local/internal` and call it right after
|
|
24736
|
+
* their own `resolveImageOverrides` invocation to inherit the same
|
|
24737
|
+
* orphan-detection semantics.
|
|
24738
|
+
*
|
|
24739
|
+
* @param rawFlags Output of {@link parseImageOverrideFlags}.
|
|
24740
|
+
* @param resolvedOverrides Output of {@link resolveImageOverrides}
|
|
24741
|
+
* (after the boot prompt has run).
|
|
24742
|
+
*/
|
|
24743
|
+
function enforceImageOverrideOrphans(rawFlags, resolvedOverrides) {
|
|
24744
|
+
if (rawFlags.perService.size === 0) return;
|
|
24745
|
+
const offenders = [];
|
|
24746
|
+
for (const [svc, overlay] of rawFlags.perService.entries()) {
|
|
24747
|
+
if (resolvedOverrides.has(svc)) continue;
|
|
24748
|
+
if (overlay.buildArgs.size > 0) {
|
|
24749
|
+
const keys = Array.from(overlay.buildArgs.keys()).join(", ");
|
|
24750
|
+
offenders.push(`--image-build-arg ${svc}:${keys} references a service with no --image-override mapping.`);
|
|
24751
|
+
}
|
|
24752
|
+
if (overlay.buildSecrets.size > 0) {
|
|
24753
|
+
const ids = Array.from(overlay.buildSecrets.keys()).join(", ");
|
|
24754
|
+
offenders.push(`--image-build-secret ${svc}:${ids} references a service with no --image-override mapping.`);
|
|
24755
|
+
}
|
|
24756
|
+
if (overlay.targetStage !== void 0) offenders.push(`--image-target ${svc}=${overlay.targetStage} references a service with no --image-override mapping.`);
|
|
24757
|
+
}
|
|
24758
|
+
if (offenders.length === 0) return;
|
|
24759
|
+
throw new LocalStartServiceError(`Per-service image-override flag(s) target a service with no --image-override coverage. Add the matching --image-override <service>=<dockerfile>, drop the per-service flag, or fix the service name:\n ${offenders.join("\n ")}`);
|
|
24760
|
+
}
|
|
24245
24761
|
|
|
24246
24762
|
//#endregion
|
|
24247
24763
|
//#region src/cli/commands/ecs-service-emulator.ts
|
|
@@ -24379,23 +24895,33 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
|
|
|
24379
24895
|
};
|
|
24380
24896
|
process.on("SIGINT", sigintHandler);
|
|
24381
24897
|
process.on("SIGTERM", sigintHandler);
|
|
24382
|
-
|
|
24898
|
+
const imageOverrideTags = await resolveAndBuildImageOverrides({
|
|
24899
|
+
perTarget: perTarget.map((pt) => pt.boot.target),
|
|
24900
|
+
stacks,
|
|
24901
|
+
options,
|
|
24902
|
+
extraStateProviders,
|
|
24903
|
+
logger
|
|
24904
|
+
});
|
|
24905
|
+
for (const pt of perTarget) pt.controller = await bootOneTarget(pt.boot, pt.runState, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorByService.get(pt.boot.target), strategy.suppressLoadBalancerWarning === true, imageOverrideTags.get(pt.boot.target));
|
|
24383
24906
|
if (perTarget.length > 0) {
|
|
24384
24907
|
const summary = perTarget.map((pt) => `${pt.controller.service.serviceName} (${pt.controller.activeReplicaCount()} replica(s))`).join(", ");
|
|
24385
24908
|
logger.info(`Service(s) running: ${summary}.`);
|
|
24386
24909
|
} else logger.info(`Service(s) running: ${frontDoorLambdaRunners.length} Lambda target(s) behind the ALB front-door.`);
|
|
24387
24910
|
logEndpointsBanner(perTarget, frontDoorServers, logger);
|
|
24388
24911
|
logger.info("Press ^C to shut down.");
|
|
24389
|
-
const
|
|
24390
|
-
|
|
24391
|
-
|
|
24392
|
-
|
|
24393
|
-
|
|
24394
|
-
|
|
24395
|
-
|
|
24396
|
-
|
|
24397
|
-
|
|
24398
|
-
|
|
24912
|
+
const uncoveredPinnedTargets = [];
|
|
24913
|
+
const pinnedEntries = listPinnedTargets(perTarget.filter((pt) => pt.controller?.service).map((pt) => ({
|
|
24914
|
+
target: pt.boot.target,
|
|
24915
|
+
service: pt.controller.service
|
|
24916
|
+
})));
|
|
24917
|
+
for (const entry of pinnedEntries) {
|
|
24918
|
+
if (imageOverrideTags.has(entry.target)) continue;
|
|
24919
|
+
const uriDisplay = entry.label ? `\`${entry.label}\`` : "a deployed registry";
|
|
24920
|
+
logger.warn(`'${entry.target}': running image is pinned to a deployed registry (${uriDisplay}). To iterate on local source, either pass \`--image-override <dockerfile>\` (or \`<service>=<dockerfile>\`) to substitute a local build, or drop \`--from-cfn-stack\` and switch the CDK app to \`ContainerImage.fromAsset(...)\`.`);
|
|
24921
|
+
uncoveredPinnedTargets.push(entry.target);
|
|
24922
|
+
}
|
|
24923
|
+
enforceStrictOverrides(options.strictOverrides === true, uncoveredPinnedTargets);
|
|
24924
|
+
if (options.watch === true && strategy.supportsWatch === true) {
|
|
24399
24925
|
const watchRoot = process.cwd();
|
|
24400
24926
|
const { ignored, shouldTrigger, excludePatterns } = createWatchPredicates({
|
|
24401
24927
|
watchRoot,
|
|
@@ -24422,6 +24948,7 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
|
|
|
24422
24948
|
profileCredsFile,
|
|
24423
24949
|
frontDoorByService,
|
|
24424
24950
|
changedPaths,
|
|
24951
|
+
imageOverrideTags,
|
|
24425
24952
|
logger
|
|
24426
24953
|
})).catch((err) => {
|
|
24427
24954
|
logger.error(`reloadAllServices threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -24490,7 +25017,7 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
|
|
|
24490
25017
|
* via the logger so the user can fix the source + save again.
|
|
24491
25018
|
*/
|
|
24492
25019
|
async function reloadAllServices(args) {
|
|
24493
|
-
const { perTarget, synthesizer, synthOpts, strategy, resolvedTargets, cloudMapIndexByStack, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorByService, changedPaths, logger } = args;
|
|
25020
|
+
const { perTarget, synthesizer, synthOpts, strategy, resolvedTargets, cloudMapIndexByStack, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorByService, changedPaths, imageOverrideTags, logger } = args;
|
|
24494
25021
|
let stacks;
|
|
24495
25022
|
try {
|
|
24496
25023
|
({stacks} = await synthesizer.synthesize(synthOpts));
|
|
@@ -24541,7 +25068,7 @@ async function reloadAllServices(args) {
|
|
|
24541
25068
|
reason: "classifier context unavailable; falling back to rebuild"
|
|
24542
25069
|
};
|
|
24543
25070
|
}
|
|
24544
|
-
if (verdict.kind === "rebuild" && verdict.reason === "target image is not a CDK docker-image asset" && !isLocalCdkAssetImage(controller.service)) {
|
|
25071
|
+
if (verdict.kind === "rebuild" && verdict.reason === "target image is not a CDK docker-image asset" && !isLocalCdkAssetImage(controller.service) && !imageOverrideTags.has(newBoot.target)) {
|
|
24545
25072
|
logger.info(`Reload skipped for '${newBoot.target}' (no-op): image pinned to deployed registry; no local rebuild possible.`);
|
|
24546
25073
|
continue;
|
|
24547
25074
|
}
|
|
@@ -24557,6 +25084,7 @@ async function reloadAllServices(args) {
|
|
|
24557
25084
|
frontDoorPools: frontDoorByService.get(newBoot.target),
|
|
24558
25085
|
suppressLoadBalancerWarning: strategy.suppressLoadBalancerWarning === true,
|
|
24559
25086
|
verdict,
|
|
25087
|
+
...imageOverrideTags.get(newBoot.target) !== void 0 && { imageOverrideTag: imageOverrideTags.get(newBoot.target) },
|
|
24560
25088
|
logger
|
|
24561
25089
|
});
|
|
24562
25090
|
}
|
|
@@ -24633,13 +25161,16 @@ async function loadAssetContextForTarget(args) {
|
|
|
24633
25161
|
* even when the resolve / roll throws.
|
|
24634
25162
|
*/
|
|
24635
25163
|
async function rollOneTarget(args) {
|
|
24636
|
-
const { controller, newBoot, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, verdict, logger } = args;
|
|
25164
|
+
const { controller, newBoot, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, verdict, imageOverrideTag, logger } = args;
|
|
24637
25165
|
const candidate = pickCandidateStack(parseEcsTarget(newBoot.target).stackPattern, stacks);
|
|
24638
25166
|
const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
|
|
24639
25167
|
try {
|
|
24640
25168
|
let resolved;
|
|
24641
25169
|
try {
|
|
24642
|
-
resolved = await resolveServiceAndRunnerOpts(newBoot, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, {
|
|
25170
|
+
resolved = await resolveServiceAndRunnerOpts(newBoot, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, {
|
|
25171
|
+
quiet: true,
|
|
25172
|
+
...imageOverrideTag !== void 0 && { imageOverrideTag }
|
|
25173
|
+
});
|
|
24643
25174
|
} catch (err) {
|
|
24644
25175
|
const reason = err instanceof Error ? err.message : String(err);
|
|
24645
25176
|
logger.error(`Reload of '${newBoot.target}' was rejected: ${reason}. Existing replica(s) keep serving.`);
|
|
@@ -24682,17 +25213,17 @@ async function rollOneTarget(args) {
|
|
|
24682
25213
|
if (stateProvider) stateProvider.dispose();
|
|
24683
25214
|
}
|
|
24684
25215
|
}
|
|
24685
|
-
async function bootOneTarget(boot, runState, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning) {
|
|
25216
|
+
async function bootOneTarget(boot, runState, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, imageOverrideTag) {
|
|
24686
25217
|
const candidate = pickCandidateStack(parseEcsTarget(boot.target).stackPattern, stacks);
|
|
24687
25218
|
const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
|
|
24688
25219
|
try {
|
|
24689
|
-
return await runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning);
|
|
25220
|
+
return await runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, imageOverrideTag);
|
|
24690
25221
|
} finally {
|
|
24691
25222
|
if (stateProvider) stateProvider.dispose();
|
|
24692
25223
|
}
|
|
24693
25224
|
}
|
|
24694
|
-
async function runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning) {
|
|
24695
|
-
const { service, runnerOpts } = await resolveServiceAndRunnerOpts(boot, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning);
|
|
25225
|
+
async function runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, imageOverrideTag) {
|
|
25226
|
+
const { service, runnerOpts } = await resolveServiceAndRunnerOpts(boot, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, imageOverrideTag !== void 0 ? { imageOverrideTag } : {});
|
|
24696
25227
|
return startEcsService(service, runnerOpts, runState);
|
|
24697
25228
|
}
|
|
24698
25229
|
/**
|
|
@@ -24775,6 +25306,10 @@ async function resolveServiceAndRunnerOpts(boot, stacks, options, discovery, ski
|
|
|
24775
25306
|
containerPath: profileCredsFile.containerPath,
|
|
24776
25307
|
profileName: profileCredsFile.profileName
|
|
24777
25308
|
};
|
|
25309
|
+
if (opts.imageOverrideTag !== void 0) {
|
|
25310
|
+
const essential = service.task.containers.find((c) => c.essential) ?? service.task.containers[0];
|
|
25311
|
+
if (essential) taskOpts.imageOverrideByContainer = new Map([[essential.name, opts.imageOverrideTag]]);
|
|
25312
|
+
}
|
|
24778
25313
|
return {
|
|
24779
25314
|
service,
|
|
24780
25315
|
runnerOpts: {
|
|
@@ -25230,6 +25765,86 @@ async function resolveSharedSidecarCredentials(options) {
|
|
|
25230
25765
|
if (options.profile) return resolveProfileCredentials(options.profile);
|
|
25231
25766
|
}
|
|
25232
25767
|
/**
|
|
25768
|
+
* Issue #238 — boot-time `--image-override` resolution + build pass.
|
|
25769
|
+
* Walks every booted service target's representative container image
|
|
25770
|
+
* to identify the pinned set (ECR / public-registry pin — the same
|
|
25771
|
+
* anchor `isLocalCdkAssetImage` uses), feeds the pinned set + raw
|
|
25772
|
+
* `--image-override` / `--image-build-arg` / `--image-build-secret` /
|
|
25773
|
+
* `--image-target` / `--no-interactive-overrides` flag values into
|
|
25774
|
+
* the engine ({@link resolveImageOverrides}), then `docker build`s
|
|
25775
|
+
* every covered Dockerfile via {@link runImageOverrideBuilds} and
|
|
25776
|
+
* returns the per-target local-tag map the boot path threads into
|
|
25777
|
+
* each runner.
|
|
25778
|
+
*
|
|
25779
|
+
* Sequential by design — most overrides share a base image, and the
|
|
25780
|
+
* engine's deterministic tag plus BuildKit's layer cache make
|
|
25781
|
+
* sequential builds nearly as fast as a parallel fan-out while
|
|
25782
|
+
* keeping the build log readable. Throws on a parser / picker /
|
|
25783
|
+
* build failure; the outer try/finally tears down what was set up.
|
|
25784
|
+
*
|
|
25785
|
+
* The boot prompt is fired only when STDIN+STDOUT are TTYs AND
|
|
25786
|
+
* `--no-interactive-overrides` is unset; non-TTY contexts silently
|
|
25787
|
+
* skip the prompt and rely on whatever the explicit flags resolved.
|
|
25788
|
+
*/
|
|
25789
|
+
async function resolveAndBuildImageOverrides(args) {
|
|
25790
|
+
const { perTarget, stacks, options, extraStateProviders, logger } = args;
|
|
25791
|
+
const resolvedForPeek = [];
|
|
25792
|
+
for (const target of perTarget) {
|
|
25793
|
+
const candidate = pickCandidateStack(parseEcsTarget(target).stackPattern, stacks);
|
|
25794
|
+
const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
|
|
25795
|
+
let imageContext;
|
|
25796
|
+
let service;
|
|
25797
|
+
try {
|
|
25798
|
+
imageContext = await buildEcsImageResolutionContext(target, stacks, options, stateProvider);
|
|
25799
|
+
service = resolveEcsServiceTarget(target, stacks, imageContext, { suppressLoadBalancerWarning: true });
|
|
25800
|
+
} catch (err) {
|
|
25801
|
+
logger.debug(`--image-override peek failed for '${target}': ${err instanceof Error ? err.message : String(err)}.`);
|
|
25802
|
+
continue;
|
|
25803
|
+
} finally {
|
|
25804
|
+
if (stateProvider) stateProvider.dispose();
|
|
25805
|
+
}
|
|
25806
|
+
if (!service) continue;
|
|
25807
|
+
resolvedForPeek.push({
|
|
25808
|
+
target,
|
|
25809
|
+
service
|
|
25810
|
+
});
|
|
25811
|
+
}
|
|
25812
|
+
const pinnedEntries = listPinnedTargets(resolvedForPeek);
|
|
25813
|
+
const pinnedTargets = pinnedEntries.map((e) => e.target);
|
|
25814
|
+
const pinnedLabels = /* @__PURE__ */ new Map();
|
|
25815
|
+
for (const entry of pinnedEntries) if (entry.label !== void 0) pinnedLabels.set(entry.target, entry.label);
|
|
25816
|
+
const rawFlags = parseImageOverrideFlags({
|
|
25817
|
+
...options.imageOverride && { imageOverride: options.imageOverride },
|
|
25818
|
+
...options.imageBuildArg && { imageBuildArg: options.imageBuildArg },
|
|
25819
|
+
...options.imageBuildSecret && { imageBuildSecret: options.imageBuildSecret },
|
|
25820
|
+
...options.imageTarget && { imageTarget: options.imageTarget }
|
|
25821
|
+
});
|
|
25822
|
+
if (pinnedTargets.length === 0 && rawFlags.explicit.size === 0 && rawFlags.pickerPaths.length === 0 && rawFlags.perService.size === 0) return /* @__PURE__ */ new Map();
|
|
25823
|
+
const overrides = await resolveImageOverrides({
|
|
25824
|
+
rawFlags,
|
|
25825
|
+
pinnedTargets,
|
|
25826
|
+
pinnedLabels,
|
|
25827
|
+
interactiveBootPrompt: true,
|
|
25828
|
+
noInteractive: options.interactiveOverrides === false
|
|
25829
|
+
});
|
|
25830
|
+
enforceImageOverrideOrphans(rawFlags, overrides);
|
|
25831
|
+
if (overrides.size === 0) return /* @__PURE__ */ new Map();
|
|
25832
|
+
return runImageOverrideBuilds(overrides);
|
|
25833
|
+
}
|
|
25834
|
+
/**
|
|
25835
|
+
* Issue #238 — `--strict-overrides` guard. Extracted so the boot path's
|
|
25836
|
+
* fail-fast behavior can be exercised under a unit test without booting
|
|
25837
|
+
* the full emulator. Throws {@link LocalStartServiceError} when `strict`
|
|
25838
|
+
* is true AND at least one pinned target remains uncovered; otherwise a
|
|
25839
|
+
* no-op. The error message names every uncovered target so the user can
|
|
25840
|
+
* pass the corresponding `--image-override` mapping.
|
|
25841
|
+
*/
|
|
25842
|
+
function enforceStrictOverrides(strict, uncoveredPinnedTargets) {
|
|
25843
|
+
if (!strict) return;
|
|
25844
|
+
if (uncoveredPinnedTargets.length === 0) return;
|
|
25845
|
+
throw new LocalStartServiceError(`--strict-overrides set, but ${uncoveredPinnedTargets.length} pinned target(s) remain uncovered: ${uncoveredPinnedTargets.join(", ")}. Pass --image-override <service>=<dockerfile> for each, drop --strict-overrides, or drop --from-cfn-stack to iterate on local CDK assets.`);
|
|
25846
|
+
}
|
|
25847
|
+
/**
|
|
25233
25848
|
* Add the CLI options shared by both ECS-service commands (`start-service` and
|
|
25234
25849
|
* `start-alb`) to a command. The command-specific argument / description and
|
|
25235
25850
|
* the one unique option (`--host-port` vs `--lb-port`) are added by each
|
|
@@ -25245,6 +25860,25 @@ function addCommonEcsServiceOptions(cmd) {
|
|
|
25245
25860
|
cmd.addOption(deprecatedRegionOption);
|
|
25246
25861
|
return cmd;
|
|
25247
25862
|
}
|
|
25863
|
+
/**
|
|
25864
|
+
* Issue #238 — register the `--image-override` flag family shared by
|
|
25865
|
+
* `cdkl start-service` and `cdkl start-alb`. Both commands accept the
|
|
25866
|
+
* same six flags; this helper keeps the wording in one place so a
|
|
25867
|
+
* future copy edit lands in both surfaces simultaneously.
|
|
25868
|
+
*
|
|
25869
|
+
* `--image-override` is repeatable (accumulator); `--image-build-arg`
|
|
25870
|
+
* and `--image-build-secret` are repeatable too; `--image-target` is a
|
|
25871
|
+
* single string (the global multi-stage target); `--no-interactive-overrides`
|
|
25872
|
+
* and `--strict-overrides` are booleans. The engine in
|
|
25873
|
+
* {@link runEcsServiceEmulator}'s boot path consumes them.
|
|
25874
|
+
*
|
|
25875
|
+
* Shared between `cdkl start-service` and `cdkl start-alb` and exposed
|
|
25876
|
+
* via `cdk-local/internal` so host CLIs (e.g. cdkd) that wrap the same
|
|
25877
|
+
* factories inherit the flag set without duplication.
|
|
25878
|
+
*/
|
|
25879
|
+
function addImageOverrideOptions(cmd) {
|
|
25880
|
+
return cmd.addOption(new Option("--image-override <service=dockerfile or dockerfile...>", "Replace a service target's deployed-registry image with a local `docker build` of the supplied Dockerfile (repeatable). Two forms: <service>=<dockerfile> binds the service explicitly; a bare <dockerfile> opens a multi-select picker against the still-uncovered pinned targets (one Dockerfile across N services). Mix freely. Lets `--from-cfn-stack` still reach real AWS state while you iterate locally on the application container.")).addOption(new Option("--image-build-arg <KEY=VAL...>", "Global `docker build --build-arg KEY=VAL` pair applied to every --image-override build (repeatable). Lets a CDK Dockerfile that branches on an ARG (env target, base image tag, package mirror) honor that ARG locally without editing the Dockerfile.")).addOption(new Option("--image-build-secret <id=src...>", "Global `docker build --secret id=<id>,src=<src>` entry applied to every --image-override build (repeatable). Enables `RUN --mount=type=secret,id=<id>` in the Dockerfile — the standard private-registry / npm-token recipe (e.g. `--image-build-secret npmrc=./.npmrc`).")).addOption(new Option("--image-target <stage or svc=stage...>", "Repeatable. Bare `<stage>` is a global --target for every --image-override build; `<service>=<stage>` (issue #240) scopes the --target to one overridden target so a monorepo with different multi-stage Dockerfiles per service can stop each at its own intermediate stage. Per-service form overrides the global on the named target.")).addOption(new Option("--no-interactive-overrides", "Suppress the interactive boot prompt that walks each pinned target asking for a Dockerfile path, and the multi-select prompt fired by `--image-override <dockerfile>` (picker form). Useful for CI / scripted invocations.")).addOption(new Option("--strict-overrides", "Fail fast at boot when any pinned target remains uncovered after `--image-override` + the boot prompt resolve. Off by default; the boot WARN per uncovered pinned target still fires regardless.").default(false));
|
|
25881
|
+
}
|
|
25248
25882
|
|
|
25249
25883
|
//#endregion
|
|
25250
25884
|
//#region src/cli/commands/local-start-service.ts
|
|
@@ -25315,6 +25949,7 @@ function createLocalStartServiceCommand(opts = {}) {
|
|
|
25315
25949
|
* for that contract to live.
|
|
25316
25950
|
*/
|
|
25317
25951
|
function addStartServiceSpecificOptions(cmd) {
|
|
25952
|
+
addImageOverrideOptions(cmd);
|
|
25318
25953
|
return cmd.addOption(new Option("--host-port <containerPort=hostPort...>", "Publish a container port on a specific host port (e.g. 80=8080); repeatable. Default: host port == container port. Use this on macOS to map a privileged container port (< 1024) to a non-privileged host port and avoid the Docker Desktop admin-password prompt. (Single-replica services only — multi-replica services do not publish host ports.)")).addOption(new Option("--watch", "Hot-reload: re-synth + per-replica reload when the CDK source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). A per-firing classifier picks the per-replica primitive: source-only edits on interpreted-language handlers (Node/Python/Ruby/shell) take a bind-mount FAST PATH (`docker cp` the new source into each replica + `docker restart`; no rebuild). Dockerfile / dependency manifest / compiled-language source / ambiguous edits fall through to the rebuild rolling primitive — boot a shadow under a bumped generation suffix, wait for its container port to accept a TCP connection, atomically swap Service-Connect / Cloud Map registrations, then retire the old container. Either path rolls one replica at a time, so peer services see zero connection refusals across the reload even on multi-replica services. Off by default; existing replica(s) keep serving when synth fails mid-reload.").default(false));
|
|
25319
25954
|
}
|
|
25320
25955
|
|
|
@@ -26168,6 +26803,7 @@ function createLocalStartAlbCommand(opts = {}) {
|
|
|
26168
26803
|
* clusters. Chainable: returns `cmd`.
|
|
26169
26804
|
*/
|
|
26170
26805
|
function addAlbSpecificOptions(cmd) {
|
|
26806
|
+
addImageOverrideOptions(cmd);
|
|
26171
26807
|
return cmd.addOption(new Option("--lb-port <listenerPort=hostPort...>", "Bind the local front-door on a specific host port (e.g. 80=8080); repeatable. Default: host port == ALB listener port. Use this on macOS to remap a privileged listener port (< 1024) to a non-privileged host port.")).addOption(new Option("--tls", "Terminate TLS locally for cloud-HTTPS listeners. Default: a cloud-HTTPS listener is served over plain HTTP locally (X-Forwarded-Proto: https is preserved so the upstream app still sees the deployed listener protocol). Implied by --tls-cert / --tls-key. Use this when local-dev cookies need Secure / SameSite=None, when the upstream app inspects TLS metadata, or for mTLS / SNI testing — otherwise plain HTTP is friendlier (no self-signed cert warnings in curl / browser).")).addOption(new Option("--tls-cert <path>", "PEM-encoded server certificate for HTTPS front-door listeners. Implies --tls. Must be set together with --tls-key. Pass --tls alone (without --tls-cert / --tls-key) to auto-generate a self-signed cert (cached under $XDG_CACHE_HOME/cdk-local/alb-https/, default ~/.cache/cdk-local/alb-https/); requires openssl on PATH. The deployed Listener Certificates[] are NOT fetched (ACM private keys are not retrievable by design). The auto-generated cert lists DNS:localhost,IP:127.0.0.1 as SubjectAltName, so a client validating a non-loopback --container-host will fail the SAN check — pass --tls-cert / --tls-key with a SAN covering that host instead.")).addOption(new Option("--tls-key <path>", "PEM-encoded server private key matching --tls-cert. Implies --tls. Must be set together with --tls-cert.")).addOption(new Option("--no-verify-auth", "Disable local enforcement of authenticate-cognito / authenticate-oidc actions. Every request is served as if the auth check passed. Useful for local dev where you do not want to mint a Bearer token at all.")).addOption(new Option("--bearer-token <jwt>", "Default Bearer JWT injected as Authorization: Bearer <jwt> when the inbound request has none. Verified against the same JWKS / OIDC discovery URL the deployed ALB would (signature + iss + aud + exp). Local-dev convenience; cookie pass-through (AWSELBAuthSessionCookie-*) also works.")).addOption(new Option("--watch", "Hot-reload: re-synth + per-replica reload of every ECS service behind the ALB when the CDK source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). A per-firing classifier picks the per-replica primitive: source-only edits on interpreted-language handlers (Node/Python/Ruby/shell) take a bind-mount FAST PATH (`docker cp` the new source into each replica + `docker restart`; no rebuild, front-door pool entry unchanged since the IP/port are preserved). Dockerfile / dependency manifest / compiled-language source / ambiguous edits fall through to the rebuild rolling primitive — boot a shadow under a bumped generation suffix, wait for its container port to accept a TCP connection, atomically register it in the front-door pool, then drop the old entry and retire the old container. Either path rolls one replica at a time, so a continuous external request stream against the listener port sees zero connection refusals across the reload. The host front-door (TLS, JWKS cache, Lambda-target containers, listener sockets) stays up across the reload. Lambda target groups behind the ALB are a no-op on reload (the warm RIE container keeps its boot-time image). Off by default; existing replica(s) keep serving when synth fails mid-reload.").default(false));
|
|
26172
26808
|
}
|
|
26173
26809
|
|
|
@@ -26264,5 +26900,5 @@ function addListSpecificOptions(cmd) {
|
|
|
26264
26900
|
}
|
|
26265
26901
|
|
|
26266
26902
|
//#endregion
|
|
26267
|
-
export {
|
|
26268
|
-
//# sourceMappingURL=local-list-
|
|
26903
|
+
export { groupRoutesByServer as $, tryResolveImageFnJoin as $n, AGENTCORE_SESSION_ID_HEADER as $t, isLocalCdkAssetImage as A, collectSsmParameterRefs as An, buildCognitoJwksUrl as At, addInvokeAgentCoreSpecificOptions as B, pickRefLogicalId as Bn, isFunctionUrlOacFronted as Bt, buildImageOverrideTag as C, createLocalStateProvider as Cn, ConnectionRegistry as Ct, resolveImageOverrides as D, resolveCfnRegion as Dn, buildConnectEvent as Dt, parseImageOverrideFlags as E, resolveCfnFallbackRegion as En, parseConnectionsPath as Et, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as F, listTargets as Fn, verifyJwtViaDiscovery as Ft, resolveApiTargetSubset as G, AGENTCORE_MCP_PROTOCOL as Gn, a2aInvokeOnce as Gt, addStartApiSpecificOptions as H, AGENTCORE_A2A_PROTOCOL as Hn, invokeAgentCoreWs as Ht, getContainerNetworkIp as I, discoverWebSocketApis as In, attachAuthorizers as It, attachStageContext as J, pickAgentCoreCandidateStack as Jn, MCP_PROTOCOL_VERSION as Jt, createAuthorizerCache as K, AGENTCORE_RUNTIME_TYPE as Kn, MCP_CONTAINER_PORT as Kt, addRunTaskSpecificOptions as L, discoverWebSocketApisOrThrow as Ln, applyCorsResponseHeaders as Lt, buildCloudMapIndex as M, resolveWatchConfig as Mn, createJwksCache as Mt, CloudMapRegistry as N, resolveSingleTarget as Nn, verifyCognitoJwt as Nt, runImageOverrideBuilds as O, resolveCfnStackName as On, buildDisconnectEvent as Ot, classifySourceChange as P, countTargets as Pn, verifyJwtAuthorizer as Pt, filterRoutesByApiIdentifiers as Q, substituteImagePlaceholders as Qn, signAgentCoreInvocation as Qt, createLocalRunTaskCommand as R, parseSelectionExpressionPath as Rn, buildCorsConfigByApiId as Rt, ImageOverrideError as S, LocalStateSourceError as Sn, bufferToBody as St, mergeForService as T, rejectExplicitCfnStackWithMultipleStacks as Tn, handleConnectionsRequest as Tt, createLocalStartApiCommand as U, AGENTCORE_AGUI_PROTOCOL as Un, A2A_CONTAINER_PORT as Ut, createLocalInvokeAgentCoreCommand as V, resolveLambdaArnIntrinsic as Vn, matchPreflight as Vt, createWatchPredicates as W, AGENTCORE_HTTP_PROTOCOL as Wn, A2A_PATH as Wt, availableApiIdentifiers as X, derivePseudoParametersFromRegion as Xn, parseSseForJsonRpc as Xt, buildStageMap as Y, resolveAgentCoreTarget as Yn, mcpInvokeOnce as Yt, filterRoutesByApiIdentifier as Z, formatStateRemedy as Zn, AGENTCORE_SIGV4_SERVICE as Zt, buildEcsImageResolutionContext as _, substituteAgainstStateAsync as _n, selectIntegrationResponse as _t, albStrategy as a, computeCodeImageTag as an, buildMethodArn as at, resolveSharedSidecarCredentials as b, resolveEnvVars as bn, HOST_GATEWAY_MIN_VERSION as bt, resolveAlbTarget as c, addInvokeSpecificOptions as cn, invokeRequestAuthorizer as ct, addStartServiceSpecificOptions as d, buildContainerImage as dn, translateLambdaResponse as dt, invokeAgentCore as en, LocalInvokeBuildError as er, readMtlsMaterialsFromDisk as et, createLocalStartServiceCommand as f, resolveRuntimeCodeMountPath as fn, applyAuthorizerOverlay as ft, addImageOverrideOptions as g, substituteAgainstState as gn, pickResponseTemplate as gt, addCommonEcsServiceOptions as h, EcsTaskResolutionError as hn, evaluateResponseParameters as ht, addAlbSpecificOptions as i, buildAgentCoreCodeImage as in, defaultCredentialsLoader as it, listPinnedTargets as j, resolveSsmParameters as jn, buildJwksUrlFromIssuer as jt, describePinnedImageUri as k, CfnLocalStateProvider as kn, buildMessageEvent as kt, isApplicationLoadBalancer as l, createLocalInvokeCommand as ln, invokeTokenAuthorizer as lt, MAX_TASKS_SUBNET_RANGE_CAP as m, resolveRuntimeImage as mn, buildRestV1Event as mt, createLocalListCommand as n, downloadAndExtractS3Bundle as nn, resolveSelectionExpression as nt, createLocalStartAlbCommand as o, renderCodeDockerfile as on, computeRequestIdentityHash as ot, serviceStrategy as p, resolveRuntimeFileExtension as pn, buildHttpApiV2Event as pt, createFileWatcher as q, AgentCoreResolutionError as qn, MCP_PATH as qt, formatTargetListing as r, SUPPORTED_CODE_RUNTIMES as rn, resolveServiceIntegrationParameters as rt, parseLbPortOverrides as s, toCmdArgv as sn, evaluateCachedLambdaPolicy as st, addListSpecificOptions as t, waitForAgentCorePing as tn, startApiServer as tt, resolveAlbFrontDoor as u, architectureToPlatform as un, matchRoute as ut, parseMaxTasks as v, substituteEnvVarsFromState as vn, tryParseStatus as vt, enforceImageOverrideOrphans as w, isCfnFlagPresent as wn, buildMgmtEndpointEnvUrl as wt, runEcsServiceEmulator as x, materializeLayerFromArn as xn, probeHostGatewaySupport as xt, parseRestartPolicy as y, substituteEnvVarsFromStateAsync as yn, VtlEvaluationError as yt, attachContainerLogStreamer as z, discoverRoutes as zn, buildCorsConfigFromCloudFrontChain as zt };
|
|
26904
|
+
//# sourceMappingURL=local-list-Ch0RglJl.js.map
|