cdk-local 0.71.1 → 0.72.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 +47 -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 +45 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -2
- package/dist/{local-list-EOmrWeTr.js → local-list-BOmsOq0r.js} +501 -11
- package/dist/local-list-BOmsOq0r.js.map +1 -0
- package/dist/{local-list-Czcuil71.d.ts → local-list-D-TP9Cy0.d.ts} +127 -2
- package/dist/local-list-D-TP9Cy0.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/local-list-Czcuil71.d.ts.map +0 -1
- package/dist/local-list-EOmrWeTr.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}`);
|
|
@@ -24164,6 +24170,361 @@ function cookieHasSessionPrefix(cookieHeader, sessionCookiePrefix) {
|
|
|
24164
24170
|
return false;
|
|
24165
24171
|
}
|
|
24166
24172
|
|
|
24173
|
+
//#endregion
|
|
24174
|
+
//#region src/local/image-pin-detector.ts
|
|
24175
|
+
/**
|
|
24176
|
+
* Issue #234 — detect whether a booted ECS service's representative
|
|
24177
|
+
* container image is a local CDK docker-image asset (i.e. one the
|
|
24178
|
+
* `--watch` reload pathway can actually rebuild from local source) or
|
|
24179
|
+
* a deployed-registry pin (ECR / public registry) the rolling primitive
|
|
24180
|
+
* would re-pull byte-identical from on every save.
|
|
24181
|
+
*
|
|
24182
|
+
* The "representative" container picks the first essential container,
|
|
24183
|
+
* falling back to the first container if no container is marked
|
|
24184
|
+
* essential — same heuristic {@link loadAssetContextForTarget} (the
|
|
24185
|
+
* source-change classifier's context loader) uses for picking the
|
|
24186
|
+
* image whose asset hash drives the rebuild-vs-soft-reload decision.
|
|
24187
|
+
* Multi-essential tasks with mixed image kinds are rare; both call
|
|
24188
|
+
* sites of this helper (the boot-time `--watch` warn + the reload-time
|
|
24189
|
+
* skip guard) match on that same first-essential anchor so the
|
|
24190
|
+
* verdict surfaced to the user aligns with what the classifier sees.
|
|
24191
|
+
*
|
|
24192
|
+
* Host-side use case: cdkd and other shim hosts that wrap
|
|
24193
|
+
* `runEcsServiceEmulator` reuse this helper to produce the same
|
|
24194
|
+
* boot-time WARN + reload-time skip diagnostics under `--watch` when
|
|
24195
|
+
* the deployed image is pinned by `--from-cfn-stack` against a
|
|
24196
|
+
* `ContainerImage.fromEcrRepository(...)` service. Without this
|
|
24197
|
+
* helper a host would silently inherit the same no-op-disguised-as-
|
|
24198
|
+
* success symptom issue #234 documents.
|
|
24199
|
+
*
|
|
24200
|
+
* @internal — not part of the semver-covered public surface; exposed
|
|
24201
|
+
* via `cdk-local/internal` only. The stable entry point for hosts is
|
|
24202
|
+
* the emulator (`runEcsServiceEmulator`); call this directly only if
|
|
24203
|
+
* you need to reproduce the per-target image-kind classification.
|
|
24204
|
+
*/
|
|
24205
|
+
/**
|
|
24206
|
+
* Returns the first essential container's image, falling back to the
|
|
24207
|
+
* first container if none are marked essential. `undefined` when the
|
|
24208
|
+
* service has no containers (degenerate; the resolver would have
|
|
24209
|
+
* already warned).
|
|
24210
|
+
*/
|
|
24211
|
+
function representativeImage(service) {
|
|
24212
|
+
return (service.task.containers.find((c) => c.essential) ?? service.task.containers[0])?.image;
|
|
24213
|
+
}
|
|
24214
|
+
/**
|
|
24215
|
+
* Returns true when the service's representative container image is a
|
|
24216
|
+
* local CDK docker-image asset (`ContainerImage.fromAsset(...)`-style)
|
|
24217
|
+
* — i.e. the `--watch` reload pathway has a local source tree to
|
|
24218
|
+
* rebuild from. Returns false when the image is an ECR pin
|
|
24219
|
+
* (`ContainerImage.fromEcrRepository(repo, tag)`-style; typically
|
|
24220
|
+
* surfaced under `--from-cfn-stack`) or a public-registry pin
|
|
24221
|
+
* (`ContainerImage.fromRegistry('nginx:latest')`-style). In both
|
|
24222
|
+
* "false" cases the rolling primitive would re-pull byte-identical
|
|
24223
|
+
* content on every save and disguise a no-op as a successful reload.
|
|
24224
|
+
*
|
|
24225
|
+
* Mirrors the bail-out branch in {@link loadAssetContextForTarget}:
|
|
24226
|
+
* when this returns false, the source-change classifier sees
|
|
24227
|
+
* `undefined` for the asset context and returns
|
|
24228
|
+
* `{ kind: 'rebuild', reason: 'target image is not a CDK docker-image asset' }`.
|
|
24229
|
+
*/
|
|
24230
|
+
function isLocalCdkAssetImage(service) {
|
|
24231
|
+
const image = representativeImage(service);
|
|
24232
|
+
return image !== void 0 && image.kind === "cdk-asset";
|
|
24233
|
+
}
|
|
24234
|
+
/**
|
|
24235
|
+
* Returns a short human-readable label for the deployed-registry URI
|
|
24236
|
+
* the representative container image is pinned to, or `undefined`
|
|
24237
|
+
* when the image IS a local CDK asset (in which case the boot-time
|
|
24238
|
+
* warn does not fire). For `ecr` images this surfaces the full
|
|
24239
|
+
* `<acct>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>` URI; for
|
|
24240
|
+
* `public` images this surfaces the registry URI as-is
|
|
24241
|
+
* (e.g. `public.ecr.aws/foo/bar:tag` or `nginx:latest`). The boot-time
|
|
24242
|
+
* warn includes this so the user can see which image is pinning the
|
|
24243
|
+
* service to a no-source-edit-pickup configuration.
|
|
24244
|
+
*/
|
|
24245
|
+
function describePinnedImageUri(service) {
|
|
24246
|
+
const image = representativeImage(service);
|
|
24247
|
+
if (!image) return void 0;
|
|
24248
|
+
if (image.kind === "cdk-asset") return void 0;
|
|
24249
|
+
return image.uri;
|
|
24250
|
+
}
|
|
24251
|
+
|
|
24252
|
+
//#endregion
|
|
24253
|
+
//#region src/local/image-override-engine.ts
|
|
24254
|
+
/**
|
|
24255
|
+
* Errors surfaced by the engine. Used so the emulator can render a
|
|
24256
|
+
* consistent error class to the user (the global error handler maps
|
|
24257
|
+
* `CdkLocalError` subclasses to a clean stack-trace-free message).
|
|
24258
|
+
*/
|
|
24259
|
+
var ImageOverrideError = class ImageOverrideError extends CdkLocalError {
|
|
24260
|
+
constructor(message, cause) {
|
|
24261
|
+
super(message, "IMAGE_OVERRIDE_ERROR", cause);
|
|
24262
|
+
this.name = "ImageOverrideError";
|
|
24263
|
+
Object.setPrototypeOf(this, ImageOverrideError.prototype);
|
|
24264
|
+
}
|
|
24265
|
+
};
|
|
24266
|
+
/**
|
|
24267
|
+
* Parse the raw `--image-override` / `--image-build-arg` /
|
|
24268
|
+
* `--image-build-secret` / `--image-target` flag arrays Commander
|
|
24269
|
+
* surfaces into the structured {@link RawImageOverrideFlags} shape.
|
|
24270
|
+
* Pure (no docker / filesystem calls); throws
|
|
24271
|
+
* {@link ImageOverrideError} on a malformed token so the emulator can
|
|
24272
|
+
* surface a clean error before any container is touched.
|
|
24273
|
+
*
|
|
24274
|
+
* - `--image-override <svc>=<dockerfile>` -> `explicit.set(svc, dockerfile)`
|
|
24275
|
+
* - `--image-override <dockerfile>` (no `=`) -> `pickerPaths.push(...)`
|
|
24276
|
+
* - `--image-build-arg KEY=VAL` -> `globals.buildArgs.set('KEY', 'VAL')`
|
|
24277
|
+
* - `--image-build-secret id=src` -> `globals.buildSecrets.set('id', 'src')`
|
|
24278
|
+
* - `--image-target <stage>` -> `globals.targetStage = '<stage>'`
|
|
24279
|
+
*
|
|
24280
|
+
* Collision detection: a service target named more than once in
|
|
24281
|
+
* `--image-override <svc>=...` is an error (last-write-wins would
|
|
24282
|
+
* silently drop the earlier mapping; explicit error is clearer).
|
|
24283
|
+
*/
|
|
24284
|
+
function parseImageOverrideFlags(input) {
|
|
24285
|
+
const explicit = /* @__PURE__ */ new Map();
|
|
24286
|
+
const pickerPaths = [];
|
|
24287
|
+
for (const raw of input.imageOverride ?? []) {
|
|
24288
|
+
const eq = raw.indexOf("=");
|
|
24289
|
+
if (eq < 0) {
|
|
24290
|
+
if (!raw) throw new ImageOverrideError("Invalid --image-override value: empty string. Pass <service>=<dockerfile> or just <dockerfile>.");
|
|
24291
|
+
pickerPaths.push(raw);
|
|
24292
|
+
continue;
|
|
24293
|
+
}
|
|
24294
|
+
const svc = raw.slice(0, eq).trim();
|
|
24295
|
+
const dockerfile = raw.slice(eq + 1).trim();
|
|
24296
|
+
if (!svc) throw new ImageOverrideError(`Invalid --image-override value "${raw}": left side (service target) is empty.`);
|
|
24297
|
+
if (!dockerfile) throw new ImageOverrideError(`Invalid --image-override value "${raw}": right side (Dockerfile path) is empty.`);
|
|
24298
|
+
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.`);
|
|
24299
|
+
explicit.set(svc, dockerfile);
|
|
24300
|
+
}
|
|
24301
|
+
const buildArgs = /* @__PURE__ */ new Map();
|
|
24302
|
+
for (const raw of input.imageBuildArg ?? []) {
|
|
24303
|
+
const eq = raw.indexOf("=");
|
|
24304
|
+
if (eq <= 0) throw new ImageOverrideError(`Invalid --image-build-arg value "${raw}": expected KEY=VAL.`);
|
|
24305
|
+
const key = raw.slice(0, eq).trim();
|
|
24306
|
+
const value = raw.slice(eq + 1);
|
|
24307
|
+
if (!key) throw new ImageOverrideError(`Invalid --image-build-arg value "${raw}": empty key.`);
|
|
24308
|
+
buildArgs.set(key, value);
|
|
24309
|
+
}
|
|
24310
|
+
const buildSecrets = /* @__PURE__ */ new Map();
|
|
24311
|
+
for (const raw of input.imageBuildSecret ?? []) {
|
|
24312
|
+
const eq = raw.indexOf("=");
|
|
24313
|
+
if (eq <= 0) throw new ImageOverrideError(`Invalid --image-build-secret value "${raw}": expected id=src (e.g. npmrc=./.npmrc).`);
|
|
24314
|
+
const id = raw.slice(0, eq).trim();
|
|
24315
|
+
const src = raw.slice(eq + 1).trim();
|
|
24316
|
+
if (!id || !src) throw new ImageOverrideError(`Invalid --image-build-secret value "${raw}": id and src must both be non-empty.`);
|
|
24317
|
+
const absSrc = isAbsolute(src) ? src : resolve(process.cwd(), src);
|
|
24318
|
+
buildSecrets.set(id, absSrc);
|
|
24319
|
+
}
|
|
24320
|
+
const globals = {
|
|
24321
|
+
buildArgs,
|
|
24322
|
+
buildSecrets
|
|
24323
|
+
};
|
|
24324
|
+
if (input.imageTarget !== void 0) {
|
|
24325
|
+
if (!input.imageTarget) throw new ImageOverrideError("Invalid --image-target value: empty string.");
|
|
24326
|
+
globals.targetStage = input.imageTarget;
|
|
24327
|
+
}
|
|
24328
|
+
return {
|
|
24329
|
+
explicit,
|
|
24330
|
+
pickerPaths,
|
|
24331
|
+
globals
|
|
24332
|
+
};
|
|
24333
|
+
}
|
|
24334
|
+
/**
|
|
24335
|
+
* Resolve `--image-override` flag inputs (raw) + a list of pinned
|
|
24336
|
+
* service targets into the final {@link ImageOverrideMap}. Walks four
|
|
24337
|
+
* stages:
|
|
24338
|
+
*
|
|
24339
|
+
* 1. Apply every explicit `<svc>=<dockerfile>` mapping. A service in
|
|
24340
|
+
* `explicit` but NOT in `pinnedTargets` is a no-op warning
|
|
24341
|
+
* (logged at warn) — the user named a non-pinned target, which
|
|
24342
|
+
* probably indicates a typo or a CDK app change; we don't fail.
|
|
24343
|
+
* 2. For each picker-form Dockerfile path, fire a multi-select against
|
|
24344
|
+
* the still-uncovered pinned targets. A single picker-form path
|
|
24345
|
+
* may bind to multiple targets (one Dockerfile shared across N
|
|
24346
|
+
* services); already-bound targets are excluded from later pickers.
|
|
24347
|
+
* Skipped when not in a TTY OR `noInteractive` is true — those
|
|
24348
|
+
* contexts emit a warning and skip the picker path (treated as
|
|
24349
|
+
* "no override for any target").
|
|
24350
|
+
* 3. Optional boot prompt: when `interactiveBootPrompt` is true AND
|
|
24351
|
+
* we're in a TTY, walk each remaining uncovered pinned target
|
|
24352
|
+
* and ask "Override with a local build? [path / N]" via clack's
|
|
24353
|
+
* `text` prompt. A non-empty answer is treated as an additional
|
|
24354
|
+
* explicit mapping; `N` / empty / Esc skips the target.
|
|
24355
|
+
*
|
|
24356
|
+
* Returns the resolved override map. Throws
|
|
24357
|
+
* {@link ImageOverrideError} on a malformed picker response or a
|
|
24358
|
+
* Dockerfile path that doesn't exist (caught up-front before any
|
|
24359
|
+
* docker build runs).
|
|
24360
|
+
*/
|
|
24361
|
+
async function resolveImageOverrides(args) {
|
|
24362
|
+
const logger = getLogger();
|
|
24363
|
+
const { rawFlags, pinnedTargets, noInteractive } = args;
|
|
24364
|
+
const cwd = args.cwd ?? process.cwd();
|
|
24365
|
+
const pinnedSet = new Set(pinnedTargets);
|
|
24366
|
+
const labels = args.pinnedLabels ?? /* @__PURE__ */ new Map();
|
|
24367
|
+
const out = /* @__PURE__ */ new Map();
|
|
24368
|
+
for (const [svc, dockerfileRaw] of rawFlags.explicit.entries()) {
|
|
24369
|
+
if (!pinnedSet.has(svc)) {
|
|
24370
|
+
logger.warn(`--image-override: service '${svc}' is not in the pinned-target set (no deployed-registry pin detected for it). Mapping ignored.`);
|
|
24371
|
+
continue;
|
|
24372
|
+
}
|
|
24373
|
+
out.set(svc, makeEntryFromPath(dockerfileRaw, rawFlags.globals, cwd));
|
|
24374
|
+
}
|
|
24375
|
+
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).");
|
|
24376
|
+
else for (const dockerfileRaw of rawFlags.pickerPaths) {
|
|
24377
|
+
const uncovered = pinnedTargets.filter((t) => !out.has(t));
|
|
24378
|
+
if (uncovered.length === 0) {
|
|
24379
|
+
logger.warn(`--image-override ${dockerfileRaw}: no uncovered pinned targets left to bind to. Skipped.`);
|
|
24380
|
+
continue;
|
|
24381
|
+
}
|
|
24382
|
+
const options = uncovered.map((t) => ({
|
|
24383
|
+
value: t,
|
|
24384
|
+
label: t,
|
|
24385
|
+
...labels.get(t) ? { hint: labels.get(t) } : {}
|
|
24386
|
+
}));
|
|
24387
|
+
const picked = await multiselect({
|
|
24388
|
+
message: `Pick the pinned target(s) to bind to '${dockerfileRaw}' (space to toggle, enter to confirm; empty cancels):`,
|
|
24389
|
+
options,
|
|
24390
|
+
required: false
|
|
24391
|
+
});
|
|
24392
|
+
if (isCancel(picked)) throw new ImageOverrideError("Image-override picker cancelled. Re-run without picker-form or with --no-interactive-overrides.");
|
|
24393
|
+
const chosen = picked ?? [];
|
|
24394
|
+
if (chosen.length === 0) {
|
|
24395
|
+
logger.warn(`--image-override ${dockerfileRaw}: empty selection. Skipped.`);
|
|
24396
|
+
continue;
|
|
24397
|
+
}
|
|
24398
|
+
const entry = makeEntryFromPath(dockerfileRaw, rawFlags.globals, cwd);
|
|
24399
|
+
for (const t of chosen) out.set(t, entry);
|
|
24400
|
+
}
|
|
24401
|
+
if (args.interactiveBootPrompt === true && isInteractive() && noInteractive !== true) for (const target of pinnedTargets) {
|
|
24402
|
+
if (out.has(target)) continue;
|
|
24403
|
+
const label = labels.get(target);
|
|
24404
|
+
const answer = await text({
|
|
24405
|
+
message: `Detected pinned image on '${target}'` + (label ? ` (${label})` : "") + ". Override with a local build? [path / N]:",
|
|
24406
|
+
placeholder: ""
|
|
24407
|
+
});
|
|
24408
|
+
if (isCancel(answer)) throw new ImageOverrideError("Image-override boot prompt cancelled. Re-run with --no-interactive-overrides to suppress, or pass --image-override explicitly.");
|
|
24409
|
+
const value = (answer ?? "").trim();
|
|
24410
|
+
if (!value) continue;
|
|
24411
|
+
const lower = value.toLowerCase();
|
|
24412
|
+
if (lower === "n" || lower === "no") continue;
|
|
24413
|
+
out.set(target, makeEntryFromPath(value, rawFlags.globals, cwd));
|
|
24414
|
+
}
|
|
24415
|
+
return out;
|
|
24416
|
+
}
|
|
24417
|
+
/**
|
|
24418
|
+
* Promote a per-target Dockerfile path + globals into a full
|
|
24419
|
+
* {@link ImageOverrideEntry}. Resolves the path against `cwd`, asserts
|
|
24420
|
+
* the Dockerfile exists and is a regular file (so the user sees
|
|
24421
|
+
* "file not found" up-front rather than mid-`docker build`), and
|
|
24422
|
+
* derives the build context as the Dockerfile's parent directory
|
|
24423
|
+
* (the v1 simplification — custom contexts are tracked separately).
|
|
24424
|
+
*/
|
|
24425
|
+
function makeEntryFromPath(raw, globals, cwd) {
|
|
24426
|
+
const abs = isAbsolute(raw) ? raw : resolve(cwd, raw);
|
|
24427
|
+
if (!existsSync(abs)) throw new ImageOverrideError(`--image-override: Dockerfile '${raw}' does not exist (resolved to '${abs}').`);
|
|
24428
|
+
if (!statSync(abs).isFile()) throw new ImageOverrideError(`--image-override: '${raw}' is not a regular file (resolved to '${abs}').`);
|
|
24429
|
+
return {
|
|
24430
|
+
dockerfile: abs,
|
|
24431
|
+
contextDir: dirname(abs),
|
|
24432
|
+
buildArgs: globals.buildArgs,
|
|
24433
|
+
buildSecrets: globals.buildSecrets,
|
|
24434
|
+
...globals.targetStage !== void 0 && { targetStage: globals.targetStage }
|
|
24435
|
+
};
|
|
24436
|
+
}
|
|
24437
|
+
/**
|
|
24438
|
+
* Deterministic local-only image tag for one override entry. Fingerprints
|
|
24439
|
+
* the Dockerfile path + its CONTENTS (sha256 of the bytes) + service
|
|
24440
|
+
* target + build args / secrets / stage. Including the Dockerfile bytes
|
|
24441
|
+
* means an edit to the Dockerfile flips the tag — the rebuild rolling
|
|
24442
|
+
* primitive then boots a new container under the bumped tag instead of
|
|
24443
|
+
* reusing the old build's cached layers under a stale tag. Two
|
|
24444
|
+
* back-to-back runs against an unchanged Dockerfile + flags still hit
|
|
24445
|
+
* the same tag so docker's layer cache works.
|
|
24446
|
+
*
|
|
24447
|
+
* Tag shape: `<resourceNamePrefix>-override-<svcSlug>-<hash>:local`.
|
|
24448
|
+
* `:local` is intentionally NOT `:latest` so a `docker pull` on it fails
|
|
24449
|
+
* fast (these images are local-build-only by design).
|
|
24450
|
+
*
|
|
24451
|
+
* Throws on a missing Dockerfile — the caller has already asserted
|
|
24452
|
+
* existence in {@link makeEntryFromPath}, so a throw here is a
|
|
24453
|
+
* programming error (or the Dockerfile vanished between resolve and
|
|
24454
|
+
* build, which is a race the user wants to know about).
|
|
24455
|
+
*/
|
|
24456
|
+
function buildImageOverrideTag(serviceTarget, entry) {
|
|
24457
|
+
const hash = createHash("sha256");
|
|
24458
|
+
hash.update("dockerfile=");
|
|
24459
|
+
hash.update(entry.dockerfile);
|
|
24460
|
+
hash.update("\0dockerfile-bytes-sha256=");
|
|
24461
|
+
const dockerfileBytesDigest = createHash("sha256").update(readFileSync(entry.dockerfile)).digest("hex");
|
|
24462
|
+
hash.update(dockerfileBytesDigest);
|
|
24463
|
+
hash.update("\0target=");
|
|
24464
|
+
hash.update(serviceTarget);
|
|
24465
|
+
hash.update("\0stage=");
|
|
24466
|
+
hash.update(entry.targetStage ?? "");
|
|
24467
|
+
hash.update("\0args={");
|
|
24468
|
+
for (const [k, v] of entry.buildArgs.entries()) {
|
|
24469
|
+
hash.update(k);
|
|
24470
|
+
hash.update("=");
|
|
24471
|
+
hash.update(v);
|
|
24472
|
+
hash.update(";");
|
|
24473
|
+
}
|
|
24474
|
+
hash.update("}\0secrets={");
|
|
24475
|
+
for (const [k, v] of entry.buildSecrets.entries()) {
|
|
24476
|
+
hash.update(k);
|
|
24477
|
+
hash.update("=");
|
|
24478
|
+
hash.update(v);
|
|
24479
|
+
hash.update(";");
|
|
24480
|
+
}
|
|
24481
|
+
hash.update("}");
|
|
24482
|
+
const svcSlug = serviceTarget.replace(/[^A-Za-z0-9]+/g, "-").slice(0, 24).toLowerCase();
|
|
24483
|
+
return `${getEmbedConfig().resourceNamePrefix}-override-${svcSlug}-${hash.digest("hex").slice(0, 16)}:local`;
|
|
24484
|
+
}
|
|
24485
|
+
/**
|
|
24486
|
+
* Build every overridden image via `docker build`. Returns a per-target
|
|
24487
|
+
* `serviceTarget -> localTag` map the emulator threads into each
|
|
24488
|
+
* runner's `imageOverrideByContainer` (mutating the resolved service's
|
|
24489
|
+
* representative container's image to this tag).
|
|
24490
|
+
*
|
|
24491
|
+
* Each build runs sequentially (parallelism is left to docker's own
|
|
24492
|
+
* BuildKit cache — most overrides share a base image, so a sequential
|
|
24493
|
+
* order maximizes cache reuse). On any failure the function rejects
|
|
24494
|
+
* with {@link ImageOverrideError}; the emulator surfaces this before
|
|
24495
|
+
* any container is started.
|
|
24496
|
+
*/
|
|
24497
|
+
async function runImageOverrideBuilds(overrides) {
|
|
24498
|
+
const logger = getLogger();
|
|
24499
|
+
const out = /* @__PURE__ */ new Map();
|
|
24500
|
+
for (const [target, entry] of overrides.entries()) {
|
|
24501
|
+
const tag = buildImageOverrideTag(target, entry);
|
|
24502
|
+
const args = [
|
|
24503
|
+
"build",
|
|
24504
|
+
"--tag",
|
|
24505
|
+
tag,
|
|
24506
|
+
"--file",
|
|
24507
|
+
entry.dockerfile
|
|
24508
|
+
];
|
|
24509
|
+
for (const [k, v] of entry.buildArgs.entries()) args.push("--build-arg", `${k}=${v}`);
|
|
24510
|
+
for (const [id, src] of entry.buildSecrets.entries()) args.push("--secret", `id=${id},src=${src}`);
|
|
24511
|
+
if (entry.targetStage !== void 0) args.push("--target", entry.targetStage);
|
|
24512
|
+
args.push(".");
|
|
24513
|
+
logger.info(`Building override image for '${target}' from '${entry.dockerfile}' (tag=${tag})...`);
|
|
24514
|
+
try {
|
|
24515
|
+
await runDockerStreaming(args, {
|
|
24516
|
+
cwd: entry.contextDir,
|
|
24517
|
+
env: { BUILDX_NO_DEFAULT_ATTESTATIONS: "1" }
|
|
24518
|
+
});
|
|
24519
|
+
} catch (err) {
|
|
24520
|
+
const e = err;
|
|
24521
|
+
throw new ImageOverrideError(`docker build failed for --image-override '${target}' (Dockerfile=${entry.dockerfile}): ` + (e.stderr?.trim() || e.message || String(err)));
|
|
24522
|
+
}
|
|
24523
|
+
out.set(target, tag);
|
|
24524
|
+
}
|
|
24525
|
+
return out;
|
|
24526
|
+
}
|
|
24527
|
+
|
|
24167
24528
|
//#endregion
|
|
24168
24529
|
//#region src/cli/commands/ecs-service-emulator.ts
|
|
24169
24530
|
/**
|
|
@@ -24300,13 +24661,32 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
|
|
|
24300
24661
|
};
|
|
24301
24662
|
process.on("SIGINT", sigintHandler);
|
|
24302
24663
|
process.on("SIGTERM", sigintHandler);
|
|
24303
|
-
|
|
24664
|
+
const imageOverrideTags = await resolveAndBuildImageOverrides({
|
|
24665
|
+
perTarget: perTarget.map((pt) => pt.boot.target),
|
|
24666
|
+
stacks,
|
|
24667
|
+
options,
|
|
24668
|
+
extraStateProviders,
|
|
24669
|
+
logger
|
|
24670
|
+
});
|
|
24671
|
+
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));
|
|
24304
24672
|
if (perTarget.length > 0) {
|
|
24305
24673
|
const summary = perTarget.map((pt) => `${pt.controller.service.serviceName} (${pt.controller.activeReplicaCount()} replica(s))`).join(", ");
|
|
24306
24674
|
logger.info(`Service(s) running: ${summary}.`);
|
|
24307
24675
|
} else logger.info(`Service(s) running: ${frontDoorLambdaRunners.length} Lambda target(s) behind the ALB front-door.`);
|
|
24308
24676
|
logEndpointsBanner(perTarget, frontDoorServers, logger);
|
|
24309
24677
|
logger.info("Press ^C to shut down.");
|
|
24678
|
+
const uncoveredPinnedTargets = [];
|
|
24679
|
+
for (const pt of perTarget) {
|
|
24680
|
+
const service = pt.controller?.service;
|
|
24681
|
+
if (!service) continue;
|
|
24682
|
+
if (isLocalCdkAssetImage(service)) continue;
|
|
24683
|
+
if (imageOverrideTags.has(pt.boot.target)) continue;
|
|
24684
|
+
const pinnedUri = describePinnedImageUri(service);
|
|
24685
|
+
const uriDisplay = pinnedUri ? `\`${pinnedUri}\`` : "a deployed registry";
|
|
24686
|
+
logger.warn(`'${pt.boot.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(...)\`.`);
|
|
24687
|
+
uncoveredPinnedTargets.push(pt.boot.target);
|
|
24688
|
+
}
|
|
24689
|
+
enforceStrictOverrides(options.strictOverrides === true, uncoveredPinnedTargets);
|
|
24310
24690
|
if (options.watch === true && strategy.supportsWatch === true) {
|
|
24311
24691
|
const watchRoot = process.cwd();
|
|
24312
24692
|
const { ignored, shouldTrigger, excludePatterns } = createWatchPredicates({
|
|
@@ -24334,6 +24714,7 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
|
|
|
24334
24714
|
profileCredsFile,
|
|
24335
24715
|
frontDoorByService,
|
|
24336
24716
|
changedPaths,
|
|
24717
|
+
imageOverrideTags,
|
|
24337
24718
|
logger
|
|
24338
24719
|
})).catch((err) => {
|
|
24339
24720
|
logger.error(`reloadAllServices threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -24402,7 +24783,7 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
|
|
|
24402
24783
|
* via the logger so the user can fix the source + save again.
|
|
24403
24784
|
*/
|
|
24404
24785
|
async function reloadAllServices(args) {
|
|
24405
|
-
const { perTarget, synthesizer, synthOpts, strategy, resolvedTargets, cloudMapIndexByStack, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorByService, changedPaths, logger } = args;
|
|
24786
|
+
const { perTarget, synthesizer, synthOpts, strategy, resolvedTargets, cloudMapIndexByStack, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorByService, changedPaths, imageOverrideTags, logger } = args;
|
|
24406
24787
|
let stacks;
|
|
24407
24788
|
try {
|
|
24408
24789
|
({stacks} = await synthesizer.synthesize(synthOpts));
|
|
@@ -24453,6 +24834,10 @@ async function reloadAllServices(args) {
|
|
|
24453
24834
|
reason: "classifier context unavailable; falling back to rebuild"
|
|
24454
24835
|
};
|
|
24455
24836
|
}
|
|
24837
|
+
if (verdict.kind === "rebuild" && verdict.reason === "target image is not a CDK docker-image asset" && !isLocalCdkAssetImage(controller.service) && !imageOverrideTags.has(newBoot.target)) {
|
|
24838
|
+
logger.info(`Reload skipped for '${newBoot.target}' (no-op): image pinned to deployed registry; no local rebuild possible.`);
|
|
24839
|
+
continue;
|
|
24840
|
+
}
|
|
24456
24841
|
await rollOneTarget({
|
|
24457
24842
|
controller,
|
|
24458
24843
|
newBoot,
|
|
@@ -24465,6 +24850,7 @@ async function reloadAllServices(args) {
|
|
|
24465
24850
|
frontDoorPools: frontDoorByService.get(newBoot.target),
|
|
24466
24851
|
suppressLoadBalancerWarning: strategy.suppressLoadBalancerWarning === true,
|
|
24467
24852
|
verdict,
|
|
24853
|
+
...imageOverrideTags.get(newBoot.target) !== void 0 && { imageOverrideTag: imageOverrideTags.get(newBoot.target) },
|
|
24468
24854
|
logger
|
|
24469
24855
|
});
|
|
24470
24856
|
}
|
|
@@ -24541,13 +24927,16 @@ async function loadAssetContextForTarget(args) {
|
|
|
24541
24927
|
* even when the resolve / roll throws.
|
|
24542
24928
|
*/
|
|
24543
24929
|
async function rollOneTarget(args) {
|
|
24544
|
-
const { controller, newBoot, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, verdict, logger } = args;
|
|
24930
|
+
const { controller, newBoot, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, verdict, imageOverrideTag, logger } = args;
|
|
24545
24931
|
const candidate = pickCandidateStack(parseEcsTarget(newBoot.target).stackPattern, stacks);
|
|
24546
24932
|
const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
|
|
24547
24933
|
try {
|
|
24548
24934
|
let resolved;
|
|
24549
24935
|
try {
|
|
24550
|
-
resolved = await resolveServiceAndRunnerOpts(newBoot, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, {
|
|
24936
|
+
resolved = await resolveServiceAndRunnerOpts(newBoot, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, {
|
|
24937
|
+
quiet: true,
|
|
24938
|
+
...imageOverrideTag !== void 0 && { imageOverrideTag }
|
|
24939
|
+
});
|
|
24551
24940
|
} catch (err) {
|
|
24552
24941
|
const reason = err instanceof Error ? err.message : String(err);
|
|
24553
24942
|
logger.error(`Reload of '${newBoot.target}' was rejected: ${reason}. Existing replica(s) keep serving.`);
|
|
@@ -24590,17 +24979,17 @@ async function rollOneTarget(args) {
|
|
|
24590
24979
|
if (stateProvider) stateProvider.dispose();
|
|
24591
24980
|
}
|
|
24592
24981
|
}
|
|
24593
|
-
async function bootOneTarget(boot, runState, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning) {
|
|
24982
|
+
async function bootOneTarget(boot, runState, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, imageOverrideTag) {
|
|
24594
24983
|
const candidate = pickCandidateStack(parseEcsTarget(boot.target).stackPattern, stacks);
|
|
24595
24984
|
const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
|
|
24596
24985
|
try {
|
|
24597
|
-
return await runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning);
|
|
24986
|
+
return await runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, imageOverrideTag);
|
|
24598
24987
|
} finally {
|
|
24599
24988
|
if (stateProvider) stateProvider.dispose();
|
|
24600
24989
|
}
|
|
24601
24990
|
}
|
|
24602
|
-
async function runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning) {
|
|
24603
|
-
const { service, runnerOpts } = await resolveServiceAndRunnerOpts(boot, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning);
|
|
24991
|
+
async function runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, imageOverrideTag) {
|
|
24992
|
+
const { service, runnerOpts } = await resolveServiceAndRunnerOpts(boot, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, imageOverrideTag !== void 0 ? { imageOverrideTag } : {});
|
|
24604
24993
|
return startEcsService(service, runnerOpts, runState);
|
|
24605
24994
|
}
|
|
24606
24995
|
/**
|
|
@@ -24683,6 +25072,10 @@ async function resolveServiceAndRunnerOpts(boot, stacks, options, discovery, ski
|
|
|
24683
25072
|
containerPath: profileCredsFile.containerPath,
|
|
24684
25073
|
profileName: profileCredsFile.profileName
|
|
24685
25074
|
};
|
|
25075
|
+
if (opts.imageOverrideTag !== void 0) {
|
|
25076
|
+
const essential = service.task.containers.find((c) => c.essential) ?? service.task.containers[0];
|
|
25077
|
+
if (essential) taskOpts.imageOverrideByContainer = new Map([[essential.name, opts.imageOverrideTag]]);
|
|
25078
|
+
}
|
|
24686
25079
|
return {
|
|
24687
25080
|
service,
|
|
24688
25081
|
runnerOpts: {
|
|
@@ -25138,6 +25531,82 @@ async function resolveSharedSidecarCredentials(options) {
|
|
|
25138
25531
|
if (options.profile) return resolveProfileCredentials(options.profile);
|
|
25139
25532
|
}
|
|
25140
25533
|
/**
|
|
25534
|
+
* Issue #238 — boot-time `--image-override` resolution + build pass.
|
|
25535
|
+
* Walks every booted service target's representative container image
|
|
25536
|
+
* to identify the pinned set (ECR / public-registry pin — the same
|
|
25537
|
+
* anchor `isLocalCdkAssetImage` uses), feeds the pinned set + raw
|
|
25538
|
+
* `--image-override` / `--image-build-arg` / `--image-build-secret` /
|
|
25539
|
+
* `--image-target` / `--no-interactive-overrides` flag values into
|
|
25540
|
+
* the engine ({@link resolveImageOverrides}), then `docker build`s
|
|
25541
|
+
* every covered Dockerfile via {@link runImageOverrideBuilds} and
|
|
25542
|
+
* returns the per-target local-tag map the boot path threads into
|
|
25543
|
+
* each runner.
|
|
25544
|
+
*
|
|
25545
|
+
* Sequential by design — most overrides share a base image, and the
|
|
25546
|
+
* engine's deterministic tag plus BuildKit's layer cache make
|
|
25547
|
+
* sequential builds nearly as fast as a parallel fan-out while
|
|
25548
|
+
* keeping the build log readable. Throws on a parser / picker /
|
|
25549
|
+
* build failure; the outer try/finally tears down what was set up.
|
|
25550
|
+
*
|
|
25551
|
+
* The boot prompt is fired only when STDIN+STDOUT are TTYs AND
|
|
25552
|
+
* `--no-interactive-overrides` is unset; non-TTY contexts silently
|
|
25553
|
+
* skip the prompt and rely on whatever the explicit flags resolved.
|
|
25554
|
+
*/
|
|
25555
|
+
async function resolveAndBuildImageOverrides(args) {
|
|
25556
|
+
const { perTarget, stacks, options, extraStateProviders, logger } = args;
|
|
25557
|
+
const pinnedTargets = [];
|
|
25558
|
+
const pinnedLabels = /* @__PURE__ */ new Map();
|
|
25559
|
+
for (const target of perTarget) {
|
|
25560
|
+
const candidate = pickCandidateStack(parseEcsTarget(target).stackPattern, stacks);
|
|
25561
|
+
const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
|
|
25562
|
+
let imageContext;
|
|
25563
|
+
let service;
|
|
25564
|
+
try {
|
|
25565
|
+
imageContext = await buildEcsImageResolutionContext(target, stacks, options, stateProvider);
|
|
25566
|
+
service = resolveEcsServiceTarget(target, stacks, imageContext, { suppressLoadBalancerWarning: true });
|
|
25567
|
+
} catch (err) {
|
|
25568
|
+
logger.debug(`--image-override peek failed for '${target}': ${err instanceof Error ? err.message : String(err)}.`);
|
|
25569
|
+
continue;
|
|
25570
|
+
} finally {
|
|
25571
|
+
if (stateProvider) stateProvider.dispose();
|
|
25572
|
+
}
|
|
25573
|
+
if (!service) continue;
|
|
25574
|
+
if (isLocalCdkAssetImage(service)) continue;
|
|
25575
|
+
pinnedTargets.push(target);
|
|
25576
|
+
const uri = describePinnedImageUri(service);
|
|
25577
|
+
if (uri) pinnedLabels.set(target, uri);
|
|
25578
|
+
}
|
|
25579
|
+
const rawFlags = parseImageOverrideFlags({
|
|
25580
|
+
...options.imageOverride && { imageOverride: options.imageOverride },
|
|
25581
|
+
...options.imageBuildArg && { imageBuildArg: options.imageBuildArg },
|
|
25582
|
+
...options.imageBuildSecret && { imageBuildSecret: options.imageBuildSecret },
|
|
25583
|
+
...options.imageTarget && { imageTarget: options.imageTarget }
|
|
25584
|
+
});
|
|
25585
|
+
if (pinnedTargets.length === 0 && rawFlags.explicit.size === 0 && rawFlags.pickerPaths.length === 0) return /* @__PURE__ */ new Map();
|
|
25586
|
+
const overrides = await resolveImageOverrides({
|
|
25587
|
+
rawFlags,
|
|
25588
|
+
pinnedTargets,
|
|
25589
|
+
pinnedLabels,
|
|
25590
|
+
interactiveBootPrompt: true,
|
|
25591
|
+
noInteractive: options.interactiveOverrides === false
|
|
25592
|
+
});
|
|
25593
|
+
if (overrides.size === 0) return /* @__PURE__ */ new Map();
|
|
25594
|
+
return runImageOverrideBuilds(overrides);
|
|
25595
|
+
}
|
|
25596
|
+
/**
|
|
25597
|
+
* Issue #238 — `--strict-overrides` guard. Extracted so the boot path's
|
|
25598
|
+
* fail-fast behavior can be exercised under a unit test without booting
|
|
25599
|
+
* the full emulator. Throws {@link LocalStartServiceError} when `strict`
|
|
25600
|
+
* is true AND at least one pinned target remains uncovered; otherwise a
|
|
25601
|
+
* no-op. The error message names every uncovered target so the user can
|
|
25602
|
+
* pass the corresponding `--image-override` mapping.
|
|
25603
|
+
*/
|
|
25604
|
+
function enforceStrictOverrides(strict, uncoveredPinnedTargets) {
|
|
25605
|
+
if (!strict) return;
|
|
25606
|
+
if (uncoveredPinnedTargets.length === 0) return;
|
|
25607
|
+
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.`);
|
|
25608
|
+
}
|
|
25609
|
+
/**
|
|
25141
25610
|
* Add the CLI options shared by both ECS-service commands (`start-service` and
|
|
25142
25611
|
* `start-alb`) to a command. The command-specific argument / description and
|
|
25143
25612
|
* the one unique option (`--host-port` vs `--lb-port`) are added by each
|
|
@@ -25153,6 +25622,25 @@ function addCommonEcsServiceOptions(cmd) {
|
|
|
25153
25622
|
cmd.addOption(deprecatedRegionOption);
|
|
25154
25623
|
return cmd;
|
|
25155
25624
|
}
|
|
25625
|
+
/**
|
|
25626
|
+
* Issue #238 — register the `--image-override` flag family shared by
|
|
25627
|
+
* `cdkl start-service` and `cdkl start-alb`. Both commands accept the
|
|
25628
|
+
* same six flags; this helper keeps the wording in one place so a
|
|
25629
|
+
* future copy edit lands in both surfaces simultaneously.
|
|
25630
|
+
*
|
|
25631
|
+
* `--image-override` is repeatable (accumulator); `--image-build-arg`
|
|
25632
|
+
* and `--image-build-secret` are repeatable too; `--image-target` is a
|
|
25633
|
+
* single string (the global multi-stage target); `--no-interactive-overrides`
|
|
25634
|
+
* and `--strict-overrides` are booleans. The engine in
|
|
25635
|
+
* {@link runEcsServiceEmulator}'s boot path consumes them.
|
|
25636
|
+
*
|
|
25637
|
+
* Shared between `cdkl start-service` and `cdkl start-alb` and exposed
|
|
25638
|
+
* via `cdk-local/internal` so host CLIs (e.g. cdkd) that wrap the same
|
|
25639
|
+
* factories inherit the flag set without duplication.
|
|
25640
|
+
*/
|
|
25641
|
+
function addImageOverrideOptions(cmd) {
|
|
25642
|
+
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>", "Global `docker build --target=<stage>` forwarded to every --image-override build. Useful when the Dockerfile is multi-stage and you want to stop at a specific intermediate stage.")).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));
|
|
25643
|
+
}
|
|
25156
25644
|
|
|
25157
25645
|
//#endregion
|
|
25158
25646
|
//#region src/cli/commands/local-start-service.ts
|
|
@@ -25223,6 +25711,7 @@ function createLocalStartServiceCommand(opts = {}) {
|
|
|
25223
25711
|
* for that contract to live.
|
|
25224
25712
|
*/
|
|
25225
25713
|
function addStartServiceSpecificOptions(cmd) {
|
|
25714
|
+
addImageOverrideOptions(cmd);
|
|
25226
25715
|
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));
|
|
25227
25716
|
}
|
|
25228
25717
|
|
|
@@ -26076,6 +26565,7 @@ function createLocalStartAlbCommand(opts = {}) {
|
|
|
26076
26565
|
* clusters. Chainable: returns `cmd`.
|
|
26077
26566
|
*/
|
|
26078
26567
|
function addAlbSpecificOptions(cmd) {
|
|
26568
|
+
addImageOverrideOptions(cmd);
|
|
26079
26569
|
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));
|
|
26080
26570
|
}
|
|
26081
26571
|
|
|
@@ -26172,5 +26662,5 @@ function addListSpecificOptions(cmd) {
|
|
|
26172
26662
|
}
|
|
26173
26663
|
|
|
26174
26664
|
//#endregion
|
|
26175
|
-
export {
|
|
26176
|
-
//# sourceMappingURL=local-list-
|
|
26665
|
+
export { resolveSelectionExpression as $, downloadAndExtractS3Bundle as $t, CloudMapRegistry as A, resolveSingleTarget as An, verifyCognitoJwt as At, createLocalStartApiCommand as B, AGENTCORE_AGUI_PROTOCOL as Bn, A2A_CONTAINER_PORT as Bt, buildImageOverrideTag as C, resolveCfnFallbackRegion as Cn, parseConnectionsPath as Ct, describePinnedImageUri as D, collectSsmParameterRefs as Dn, buildCognitoJwksUrl as Dt, runImageOverrideBuilds as E, CfnLocalStateProvider as En, buildMessageEvent as Et, createLocalRunTaskCommand as F, parseSelectionExpressionPath as Fn, buildCorsConfigByApiId as Ft, attachStageContext as G, pickAgentCoreCandidateStack as Gn, MCP_PROTOCOL_VERSION as Gt, resolveApiTargetSubset as H, AGENTCORE_MCP_PROTOCOL as Hn, a2aInvokeOnce as Ht, attachContainerLogStreamer as I, discoverRoutes as In, buildCorsConfigFromCloudFrontChain as It, filterRoutesByApiIdentifier as J, formatStateRemedy as Jn, AGENTCORE_SIGV4_SERVICE as Jt, buildStageMap as K, resolveAgentCoreTarget as Kn, mcpInvokeOnce as Kt, addInvokeAgentCoreSpecificOptions as L, pickRefLogicalId as Ln, isFunctionUrlOacFronted as Lt, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as M, listTargets as Mn, verifyJwtViaDiscovery as Mt, getContainerNetworkIp as N, discoverWebSocketApis as Nn, attachAuthorizers as Nt, isLocalCdkAssetImage as O, resolveSsmParameters as On, buildJwksUrlFromIssuer as Ot, addRunTaskSpecificOptions as P, discoverWebSocketApisOrThrow as Pn, applyCorsResponseHeaders as Pt, startApiServer as Q, waitForAgentCorePing as Qt, createLocalInvokeAgentCoreCommand as R, resolveLambdaArnIntrinsic as Rn, matchPreflight as Rt, ImageOverrideError as S, rejectExplicitCfnStackWithMultipleStacks as Sn, handleConnectionsRequest as St, resolveImageOverrides as T, resolveCfnStackName as Tn, buildDisconnectEvent as Tt, createAuthorizerCache as U, AGENTCORE_RUNTIME_TYPE as Un, MCP_CONTAINER_PORT as Ut, createWatchPredicates as V, AGENTCORE_HTTP_PROTOCOL as Vn, A2A_PATH as Vt, createFileWatcher as W, AgentCoreResolutionError as Wn, MCP_PATH as Wt, groupRoutesByServer as X, tryResolveImageFnJoin as Xn, AGENTCORE_SESSION_ID_HEADER as Xt, filterRoutesByApiIdentifiers as Y, substituteImagePlaceholders as Yn, signAgentCoreInvocation as Yt, readMtlsMaterialsFromDisk as Z, LocalInvokeBuildError as Zn, invokeAgentCore as Zt, buildEcsImageResolutionContext as _, resolveEnvVars as _n, HOST_GATEWAY_MIN_VERSION as _t, albStrategy as a, addInvokeSpecificOptions as an, invokeRequestAuthorizer as at, resolveSharedSidecarCredentials as b, createLocalStateProvider as bn, ConnectionRegistry as bt, resolveAlbTarget as c, buildContainerImage as cn, translateLambdaResponse as ct, addStartServiceSpecificOptions as d, resolveRuntimeImage as dn, buildRestV1Event as dt, SUPPORTED_CODE_RUNTIMES as en, resolveServiceIntegrationParameters as et, createLocalStartServiceCommand as f, EcsTaskResolutionError as fn, evaluateResponseParameters as ft, addImageOverrideOptions as g, substituteEnvVarsFromStateAsync as gn, VtlEvaluationError as gt, addCommonEcsServiceOptions as h, substituteEnvVarsFromState as hn, tryParseStatus as ht, addAlbSpecificOptions as i, toCmdArgv as in, evaluateCachedLambdaPolicy as it, classifySourceChange as j, countTargets as jn, verifyJwtAuthorizer as jt, buildCloudMapIndex as k, resolveWatchConfig as kn, createJwksCache as kt, isApplicationLoadBalancer as l, resolveRuntimeCodeMountPath as ln, applyAuthorizerOverlay as lt, MAX_TASKS_SUBNET_RANGE_CAP as m, substituteAgainstStateAsync as mn, selectIntegrationResponse as mt, createLocalListCommand as n, computeCodeImageTag as nn, buildMethodArn as nt, createLocalStartAlbCommand as o, createLocalInvokeCommand as on, invokeTokenAuthorizer as ot, serviceStrategy as p, substituteAgainstState as pn, pickResponseTemplate as pt, availableApiIdentifiers as q, derivePseudoParametersFromRegion as qn, parseSseForJsonRpc as qt, formatTargetListing as r, renderCodeDockerfile as rn, computeRequestIdentityHash as rt, parseLbPortOverrides as s, architectureToPlatform as sn, matchRoute as st, addListSpecificOptions as t, buildAgentCoreCodeImage as tn, defaultCredentialsLoader as tt, resolveAlbFrontDoor as u, resolveRuntimeFileExtension as un, buildHttpApiV2Event as ut, parseMaxTasks as v, materializeLayerFromArn as vn, probeHostGatewaySupport as vt, parseImageOverrideFlags as w, resolveCfnRegion as wn, buildConnectEvent as wt, runEcsServiceEmulator as x, isCfnFlagPresent as xn, buildMgmtEndpointEnvUrl as xt, parseRestartPolicy as y, LocalStateSourceError as yn, bufferToBody as yt, addStartApiSpecificOptions as z, AGENTCORE_A2A_PROTOCOL as zn, invokeAgentCoreWs as zt };
|
|
26666
|
+
//# sourceMappingURL=local-list-BOmsOq0r.js.map
|