cdk-local 0.149.1 → 0.149.3
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 +1 -1
- package/dist/cli.js +2 -2
- package/dist/{docker-cmd-DLX1TqVw.js → docker-cmd-SocNyC9q.js} +87 -3
- package/dist/docker-cmd-SocNyC9q.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/internal.d.ts +5 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/{local-studio-D8cHeCcp.js → local-studio-BbLfdSy4.js} +686 -61
- package/dist/local-studio-BbLfdSy4.js.map +1 -0
- package/dist/local-studio-D-j3TZWX.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/docker-cmd-DLX1TqVw.js.map +0 -1
- package/dist/local-studio-D8cHeCcp.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as runDockerForeground, c as warnFinchArgvExposure, d as getEmbedConfig, i as getDockerCmd, l as getLogger, n as finchSecretArgvRefusal, o as runDockerStreaming, p as setEmbedConfig, r as formatDockerLoginError, s as spawnStreaming, u as resolveConfiguredLogLevel } from "./docker-cmd-SocNyC9q.js";
|
|
2
2
|
import { chmodSync, cpSync, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -2552,7 +2552,12 @@ function resolveAssemblyPath(dir, candidate, options) {
|
|
|
2552
2552
|
* because `cdk synth --no-staging` emits one — CDK writes the asset's absolute
|
|
2553
2553
|
* SOURCE directory under `aws:cdk:disable-asset-staging`, usually outside the
|
|
2554
2554
|
* outdir — and they WARN rather than refuse when it leaves the bound, so this
|
|
2555
|
-
* returns a verdict rather than throwing.
|
|
2555
|
+
* returns a verdict rather than throwing. The asset-MANIFEST readers that
|
|
2556
|
+
* honour an absolute value take the same verdict: `'honour-warn'` (the
|
|
2557
|
+
* start-cloudfront origin) warns when the value is a folder inside the user's
|
|
2558
|
+
* project not under a credential or version-control directory, and refuses
|
|
2559
|
+
* otherwise, `'honour'` (the soft-reload
|
|
2560
|
+
* sources) refuses (#745).
|
|
2556
2561
|
*
|
|
2557
2562
|
* It lives HERE so the containment rule has one spelling: it reuses this
|
|
2558
2563
|
* module's own {@link isInside} and {@link resolveThroughLinks}, symlink arm
|
|
@@ -2603,15 +2608,60 @@ function absoluteAssemblyPathEscape(bound, absolutePath) {
|
|
|
2603
2608
|
};
|
|
2604
2609
|
}
|
|
2605
2610
|
/**
|
|
2611
|
+
* Whether `candidate` names the SAME directory as `bound` — lexically, or
|
|
2612
|
+
* through a symbolic link on either side.
|
|
2613
|
+
*
|
|
2614
|
+
* An asset source legitimately names a DIRECTORY, so a value that resolves
|
|
2615
|
+
* onto the output directory itself is not an escape; but it hands the WHOLE
|
|
2616
|
+
* assembly to the sink, which is worth a line. A caller that re-spells this as
|
|
2617
|
+
* `resolve(a) === resolve(b)` misses every second spelling of the bound
|
|
2618
|
+
* (macOS `/tmp` vs `/private/tmp`, a symlinked `cdk.out`), and
|
|
2619
|
+
* {@link absoluteAssemblyPathEscape} exonerates exactly those spellings as
|
|
2620
|
+
* inside — so the equality beside it would stay silent for them.
|
|
2621
|
+
*
|
|
2622
|
+
* Conservative on failure: an unresolvable side answers from the lexical
|
|
2623
|
+
* comparison alone, so it can only say "not the same", never wrongly claim
|
|
2624
|
+
* identity.
|
|
2625
|
+
*/
|
|
2626
|
+
function namesTheSameDirectory(bound, candidate) {
|
|
2627
|
+
const resolvedBound = resolve(bound);
|
|
2628
|
+
const target = resolve(candidate);
|
|
2629
|
+
if (target === resolvedBound) return true;
|
|
2630
|
+
const realBound = resolveThroughLinks(resolvedBound);
|
|
2631
|
+
const realTarget = resolveThroughLinks(target);
|
|
2632
|
+
return realBound !== void 0 && realTarget !== void 0 && realBound === realTarget;
|
|
2633
|
+
}
|
|
2634
|
+
/**
|
|
2635
|
+
* The escape verdict for an assembly-supplied path WITHOUT throwing, taking
|
|
2636
|
+
* whichever arm the value's own shape calls for: {@link absoluteAssemblyPathEscape}
|
|
2637
|
+
* for an absolute value, {@link resolveAssemblyPath} for a relative one.
|
|
2638
|
+
*
|
|
2639
|
+
* For a caller that only WARNS and has many values to judge — the BuildKit
|
|
2640
|
+
* passthroughs in `src/assets/buildkit-passthrough-warnings.ts`. `base` is what
|
|
2641
|
+
* a relative value resolves against, `bound` what it must stay inside.
|
|
2642
|
+
* Returns `undefined` when the value is fine, including when it names `bound`
|
|
2643
|
+
* itself ({@link namesTheSameDirectory}).
|
|
2644
|
+
*/
|
|
2645
|
+
function assemblyPathEscape(base, bound, candidate) {
|
|
2646
|
+
if (isAbsolute(candidate)) return absoluteAssemblyPathEscape(bound, candidate);
|
|
2647
|
+
const resolved = resolveAssemblyPath(base, candidate, { containWithin: bound });
|
|
2648
|
+
if (resolved.contained || namesTheSameDirectory(bound, resolved.path)) return void 0;
|
|
2649
|
+
return resolved;
|
|
2650
|
+
}
|
|
2651
|
+
/**
|
|
2606
2652
|
* The shared tail of a containment refusal: what the value resolved to, what
|
|
2607
2653
|
* it escaped, and why that means the assembly is not CDK-generated. The call
|
|
2608
2654
|
* site supplies its own subject ("Lambda 'X' has ... which ") and its own
|
|
2609
2655
|
* error class. `action` completes "Refusing to ..." and is REQUIRED rather
|
|
2610
2656
|
* than defaulted: a default is a branch no caller takes, so it can neither be
|
|
2611
2657
|
* fenced nor be right for the next caller.
|
|
2658
|
+
*
|
|
2659
|
+
* `provenanceOverride` replaces that whole closing sentence for a caller that
|
|
2660
|
+
* does NOT refuse — the BuildKit passthrough warnings, where "Refusing to ..."
|
|
2661
|
+
* would be a false statement about a value that is forwarded anyway.
|
|
2612
2662
|
*/
|
|
2613
|
-
function renderAssemblyPathEscape(escape, dir, action) {
|
|
2614
|
-
const provenance = `CDK emits assembly paths that stay inside the assembly directory; one that leaves it indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to ${action}.`;
|
|
2663
|
+
function renderAssemblyPathEscape(escape, dir, action, provenanceOverride) {
|
|
2664
|
+
const provenance = provenanceOverride ?? `CDK emits assembly paths that stay inside the assembly directory; one that leaves it indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to ${action}.`;
|
|
2615
2665
|
const base = resolve(dir);
|
|
2616
2666
|
const realBase = resolveThroughLinks(base) ?? base;
|
|
2617
2667
|
const shownPath = sanitizeServiceExceptionMessage(escape.path);
|
|
@@ -3049,6 +3099,30 @@ function assetPathDirs(stack) {
|
|
|
3049
3099
|
};
|
|
3050
3100
|
}
|
|
3051
3101
|
/**
|
|
3102
|
+
* The containment bound for the `--watch` reload readers, which re-read
|
|
3103
|
+
* `<output>/<stack>.assets.json` after each synth (their BASE is
|
|
3104
|
+
* {@link watchManifestDir}).
|
|
3105
|
+
*
|
|
3106
|
+
* A stack carrying `assetOutdir` (always, through `AssemblyReader`) gets the
|
|
3107
|
+
* same Stage-aware bound as {@link assetPathDirs}. One without it gets
|
|
3108
|
+
* `outputDir` itself, NOT `assetPathDirs`'s fallback, which can be the process
|
|
3109
|
+
* cwd — a DISJOINT bound that refuses every value with a message blaming the
|
|
3110
|
+
* assembly. `outputDir` is the user's own flag, never assembly-supplied, so it
|
|
3111
|
+
* is a legitimate bound.
|
|
3112
|
+
*/
|
|
3113
|
+
function outputAssetBound(stack, outputDir) {
|
|
3114
|
+
return stack.assetOutdir === void 0 || stack.assetOutdir === "" ? outputDir : assetPathDirs(stack).assetOutdir;
|
|
3115
|
+
}
|
|
3116
|
+
/**
|
|
3117
|
+
* Where a `--watch` reader resolves a RELATIVE manifest value from: the
|
|
3118
|
+
* manifest's own directory, exactly as the build does — so a value resolves
|
|
3119
|
+
* to the same directory the running image was built from. Falls back to the
|
|
3120
|
+
* user's `--output` for a stack record without a manifest path.
|
|
3121
|
+
*/
|
|
3122
|
+
function watchManifestDir(stack, outputDir) {
|
|
3123
|
+
return stack.assetManifestPath ? dirname(stack.assetManifestPath) : outputDir;
|
|
3124
|
+
}
|
|
3125
|
+
/**
|
|
3052
3126
|
* The real assembly ROOT for a `--app` that names a `cdk.Stage`
|
|
3053
3127
|
* SUB-assembly.
|
|
3054
3128
|
*
|
|
@@ -8967,6 +9041,380 @@ function resolveRuntimeCodeMountPath(runtime) {
|
|
|
8967
9041
|
return "/var/task";
|
|
8968
9042
|
}
|
|
8969
9043
|
|
|
9044
|
+
//#endregion
|
|
9045
|
+
//#region src/assets/asset-source-path.ts
|
|
9046
|
+
/**
|
|
9047
|
+
* Resolve a manifest-supplied asset path and REFUSE it when it leaves the
|
|
9048
|
+
* app's outdir — lexically, or through a symbolic link. The one exception is
|
|
9049
|
+
* an ABSOLUTE value under `'honour-warn'`, which is ACCEPTED with a warning
|
|
9050
|
+
* when it is a folder inside the user's project, not under a credential or
|
|
9051
|
+
* version-control directory, and refused
|
|
9052
|
+
* otherwise (see {@link AssetSourcePathOptions.absolute}).
|
|
9053
|
+
*
|
|
9054
|
+
* Returns the RESOLVED, normalized absolute path, and callers must use THAT
|
|
9055
|
+
* rather than re-joining the raw value. It matters for the kernel: a raw
|
|
9056
|
+
* `<dir>/sub/link/..` handed to the OS applies `..` AFTER following `link`,
|
|
9057
|
+
* while every lexical model folds it first. Opening the normalized path makes
|
|
9058
|
+
* the judged path and the opened path the same string, so the `<link>/..`
|
|
9059
|
+
* shape has no second reading left to exploit.
|
|
9060
|
+
*
|
|
9061
|
+
* Naming the bound ITSELF (`.`, or `..` from a Stage manifest) is accepted —
|
|
9062
|
+
* an asset source is a directory by design, and the absolute arm treats the
|
|
9063
|
+
* bound as inside, so the two arms must agree — but it is WARNED about,
|
|
9064
|
+
* because it hands the whole assembly to the sink.
|
|
9065
|
+
*/
|
|
9066
|
+
function resolveAssetSourcePath(opts) {
|
|
9067
|
+
const { manifestDir, value, assetOutdir, field, subject, action, wrapError } = opts;
|
|
9068
|
+
const shownValue = sanitizeServiceExceptionMessage(value);
|
|
9069
|
+
if (isAbsolute(value) && opts.absolute !== "fold") {
|
|
9070
|
+
const absolute = resolve(value);
|
|
9071
|
+
const escape = absoluteAssemblyPathEscape(assetOutdir, absolute);
|
|
9072
|
+
if (escape !== void 0 && opts.absolute === "honour") throw wrapError(`${subject} has an absolute ${field}='${shownValue}' which ` + renderAssemblyPathEscape(escape, assetOutdir, action));
|
|
9073
|
+
if (escape !== void 0) {
|
|
9074
|
+
const refusal = originScopeRefusal(absolute, assetOutdir);
|
|
9075
|
+
if (refusal !== void 0) throw wrapError(`${subject} has an absolute ${field}='${shownValue}' which ${refusal}. An absolute ${field} is accepted only as a cdk synth --no-staging source folder: a directory inside your project (the git work tree holding the output directory, or the current directory when it contains the output directory or is a git work-tree root), not under a credential or version-control directory. Refusing to ${action}.`);
|
|
9076
|
+
warnAbsoluteOutsideAssembly(opts, absolute, escape);
|
|
9077
|
+
return absolute;
|
|
9078
|
+
}
|
|
9079
|
+
if (namesTheSameDirectory(assetOutdir, absolute)) warnWholeAssemblyAsSource(opts, absolute);
|
|
9080
|
+
return absolute;
|
|
9081
|
+
}
|
|
9082
|
+
const resolved = resolveAssemblyPath(manifestDir, value, { containWithin: assetOutdir });
|
|
9083
|
+
if (resolved.contained) return resolved.path;
|
|
9084
|
+
if (namesTheSameDirectory(assetOutdir, resolved.path)) {
|
|
9085
|
+
warnWholeAssemblyAsSource(opts, resolved.path);
|
|
9086
|
+
return resolved.path;
|
|
9087
|
+
}
|
|
9088
|
+
throw wrapError(`${subject} has ${field}='${shownValue}' which ` + renderAssemblyPathEscape(resolved, assetOutdir, action));
|
|
9089
|
+
}
|
|
9090
|
+
/**
|
|
9091
|
+
* Why an accepted-absolute root is too broad to be a `--no-staging` source, or
|
|
9092
|
+
* `undefined`. `root` is already a real path ({@link realPath}); the other
|
|
9093
|
+
* sides are resolved the same way, so a link to `/` or to `$HOME` is the same
|
|
9094
|
+
* root:
|
|
9095
|
+
*
|
|
9096
|
+
* - `/` — the whole filesystem;
|
|
9097
|
+
* - the user's home directory (`os.homedir()`) or any ANCESTOR of it
|
|
9098
|
+
* (`/Users`, `/home`) — each contains `~/.aws` / `~/.ssh`; a project folder
|
|
9099
|
+
* UNDER home is fine. The ancestor arm matters when the project is not under
|
|
9100
|
+
* home (`/tmp`, `/workspaces`), where the outdir arm does not cover it;
|
|
9101
|
+
* - an ANCESTOR of the app's outdir, which contains the assembly and
|
|
9102
|
+
* everything beside it.
|
|
9103
|
+
*/
|
|
9104
|
+
function broadRootReason(root, assetOutdir) {
|
|
9105
|
+
if (root === resolve("/")) return "names the filesystem root";
|
|
9106
|
+
if (containsOrIs(root, realPath(homedir()))) return "names your home directory or a directory containing it";
|
|
9107
|
+
if (containsOrIs(root, realPath(assetOutdir))) return "names a directory containing the app's output directory";
|
|
9108
|
+
}
|
|
9109
|
+
/**
|
|
9110
|
+
* Why an `'honour-warn'` absolute root is refused, or `undefined` to accept it
|
|
9111
|
+
* with the warning. Every comparison is on REAL paths, so a symlink whose
|
|
9112
|
+
* target leaves the project is refused however it is spelled.
|
|
9113
|
+
*
|
|
9114
|
+
* 1. {@link broadRootReason} — roots no `--no-staging` source can be.
|
|
9115
|
+
* 2. The root must lie STRICTLY inside a project root
|
|
9116
|
+
* ({@link projectRoots}), and no path component between that project root
|
|
9117
|
+
* and the origin may be a credential / VCS directory
|
|
9118
|
+
* ({@link CREDENTIAL_DIR_NAMES}, case-insensitive — APFS folds case). Other
|
|
9119
|
+
* hidden directories are fine: `docs/.vitepress/dist`, Nuxt's
|
|
9120
|
+
* `.output/public` and SvelteKit's `.svelte-kit/output` are real
|
|
9121
|
+
* `--no-staging` site folders. A monorepo sibling
|
|
9122
|
+
* (`<repo>/packages/web/dist` beside `<repo>/packages/infra/cdk.out`) is
|
|
9123
|
+
* inside the git work tree and accepted. Hidden FILES under an accepted
|
|
9124
|
+
* root are kept out at serve time instead (`cloudfront-static-origin.ts`).
|
|
9125
|
+
*/
|
|
9126
|
+
function originScopeRefusal(absolute, assetOutdir) {
|
|
9127
|
+
const root = realPath(absolute);
|
|
9128
|
+
const broad = broadRootReason(root, assetOutdir);
|
|
9129
|
+
if (broad !== void 0) return broad;
|
|
9130
|
+
const projects = projectRoots(assetOutdir);
|
|
9131
|
+
let hidden;
|
|
9132
|
+
let isProjectRoot = false;
|
|
9133
|
+
for (const project of projects) {
|
|
9134
|
+
const rel = relative(project, root);
|
|
9135
|
+
if (rel === "") isProjectRoot = true;
|
|
9136
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) continue;
|
|
9137
|
+
const dotted = rel.split(sep).find((c) => CREDENTIAL_DIR_NAMES.has(c.toLowerCase()));
|
|
9138
|
+
if (dotted === void 0) return void 0;
|
|
9139
|
+
hidden = dotted;
|
|
9140
|
+
}
|
|
9141
|
+
if (isProjectRoot) return "is your project root itself; name a folder inside it";
|
|
9142
|
+
if (hidden !== void 0) return `passes through the credential / version-control directory '${sanitizeServiceExceptionMessage(hidden)}' inside your project`;
|
|
9143
|
+
const shown = projects.map((p) => `'${sanitizeServiceExceptionMessage(p)}'`).join(" or ");
|
|
9144
|
+
return projects.length === 0 ? "is outside any usable project root (the current directory and the git work tree are too broad to scope it)" : `resolves to '${sanitizeServiceExceptionMessage(root)}', outside your project (${shown})`;
|
|
9145
|
+
}
|
|
9146
|
+
/**
|
|
9147
|
+
* Directory names no `--no-staging` site folder sits under: credential stores
|
|
9148
|
+
* and version-control / package-manager metadata. Compared lower-cased.
|
|
9149
|
+
*/
|
|
9150
|
+
const CREDENTIAL_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
9151
|
+
".git",
|
|
9152
|
+
".hg",
|
|
9153
|
+
".svn",
|
|
9154
|
+
".ssh",
|
|
9155
|
+
".aws",
|
|
9156
|
+
".gnupg",
|
|
9157
|
+
".docker",
|
|
9158
|
+
".kube",
|
|
9159
|
+
".config",
|
|
9160
|
+
".azure",
|
|
9161
|
+
".gcloud",
|
|
9162
|
+
".terraform",
|
|
9163
|
+
".npm",
|
|
9164
|
+
".pnpm-store",
|
|
9165
|
+
".yarn",
|
|
9166
|
+
".cache",
|
|
9167
|
+
".local"
|
|
9168
|
+
]);
|
|
9169
|
+
/**
|
|
9170
|
+
* The directories an accepted absolute origin may live in, both real paths:
|
|
9171
|
+
*
|
|
9172
|
+
* - the process cwd, ONLY when it contains the outdir or is itself a git work
|
|
9173
|
+
* tree root (holds a `.git` entry) — running from a folder of repos (`~/work`,
|
|
9174
|
+
* `/tmp`) must not make every project under it a scope;
|
|
9175
|
+
* - the nearest ancestor of the outdir holding a `.git` entry (a directory, or
|
|
9176
|
+
* a FILE in a linked worktree / submodule). `git` is not spawned.
|
|
9177
|
+
*
|
|
9178
|
+
* A candidate that is `/`, the home directory or an ancestor of it is DROPPED
|
|
9179
|
+
* rather than used — running from `~` must not widen the scope to all of home.
|
|
9180
|
+
*/
|
|
9181
|
+
function projectRoots(assetOutdir) {
|
|
9182
|
+
const roots = [];
|
|
9183
|
+
const usable = (p) => p !== resolve("/") && !containsOrIs(p, realPath(homedir()));
|
|
9184
|
+
const cwd = realPath(process.cwd());
|
|
9185
|
+
const outdir = realPath(assetOutdir);
|
|
9186
|
+
if (usable(cwd) && (containsOrIs(cwd, outdir) || existsSync(join(cwd, ".git")))) roots.push(cwd);
|
|
9187
|
+
let dir = outdir;
|
|
9188
|
+
for (;;) {
|
|
9189
|
+
if (existsSync(join(dir, ".git"))) {
|
|
9190
|
+
if (usable(dir) && !roots.includes(dir)) roots.push(dir);
|
|
9191
|
+
break;
|
|
9192
|
+
}
|
|
9193
|
+
const parent = dirname(dir);
|
|
9194
|
+
if (parent === dir) break;
|
|
9195
|
+
dir = parent;
|
|
9196
|
+
}
|
|
9197
|
+
return roots;
|
|
9198
|
+
}
|
|
9199
|
+
/** `outer` is `inner` itself or an ancestor of it (both real paths). */
|
|
9200
|
+
function containsOrIs(outer, inner) {
|
|
9201
|
+
const rel = relative(outer, inner);
|
|
9202
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
9203
|
+
}
|
|
9204
|
+
/**
|
|
9205
|
+
* `realpath(3)` of `p`, or — when `p` does not exist — the real path of its
|
|
9206
|
+
* deepest existing ancestor with the rest re-appended, so a not-yet-existing
|
|
9207
|
+
* path under a symlinked parent (`/tmp` -> `/private/tmp`) compares in the
|
|
9208
|
+
* same spelling as its existing neighbours. A DANGLING link component is
|
|
9209
|
+
* followed by hand (`readlink`), so `project/site -> /outside/missing` is
|
|
9210
|
+
* judged as `/outside/missing` — the place a later-created target would be
|
|
9211
|
+
* served from — not as `project/site`.
|
|
9212
|
+
*/
|
|
9213
|
+
function realPath(p, hops = 0) {
|
|
9214
|
+
const abs = resolve(p);
|
|
9215
|
+
try {
|
|
9216
|
+
return realpathSync.native(abs);
|
|
9217
|
+
} catch {
|
|
9218
|
+
const parent = dirname(abs);
|
|
9219
|
+
if (parent === abs) return abs;
|
|
9220
|
+
const realParent = realPath(parent, hops);
|
|
9221
|
+
let link;
|
|
9222
|
+
try {
|
|
9223
|
+
link = readlinkSync(abs);
|
|
9224
|
+
} catch {
|
|
9225
|
+
link = void 0;
|
|
9226
|
+
}
|
|
9227
|
+
if (link !== void 0 && hops < 40) return realPath(resolve(realParent, link), hops + 1);
|
|
9228
|
+
return join(realParent, basename(abs));
|
|
9229
|
+
}
|
|
9230
|
+
}
|
|
9231
|
+
/**
|
|
9232
|
+
* Absolute-outside lines already printed this process (deduped for the same
|
|
9233
|
+
* reason as {@link warnedWholeAssembly}).
|
|
9234
|
+
*/
|
|
9235
|
+
const warnedAbsoluteOutside = /* @__PURE__ */ new Set();
|
|
9236
|
+
function warnAbsoluteOutsideAssembly(opts, absolute, escape) {
|
|
9237
|
+
const logger = getLogger().child("assets");
|
|
9238
|
+
const line = `${opts.subject} has an absolute ${opts.field} pointing outside the assembly: '${sanitizeServiceExceptionMessage(absolute)}'` + (escape.escape === "symlink" ? ` (through a symbolic link to '${sanitizeServiceExceptionMessage(escape.realPath)}')` : "") + `. ${getEmbedConfig().productName} will ${opts.sink}. ` + (escape.escape === "lexical" ? "This is what cdk synth --no-staging emits, and is expected for it; if you did not synthesize with that flag, treat this assembly as untrusted." : "No CDK synth writes this, so treat this assembly as untrusted unless you made that link yourself.");
|
|
9239
|
+
if (warnedAbsoluteOutside.has(line)) {
|
|
9240
|
+
logger.debug(line);
|
|
9241
|
+
return;
|
|
9242
|
+
}
|
|
9243
|
+
warnedAbsoluteOutside.add(line);
|
|
9244
|
+
logger.warn(line);
|
|
9245
|
+
}
|
|
9246
|
+
/**
|
|
9247
|
+
* Whole-assembly lines already printed this process. A `start-service` with
|
|
9248
|
+
* `DesiredCount: 3` builds per replica and again per crash-loop restart, so an
|
|
9249
|
+
* undeduped line is a storm, and a storm is not read. Repeats drop to debug;
|
|
9250
|
+
* the WORK is never deduped.
|
|
9251
|
+
*/
|
|
9252
|
+
const warnedWholeAssembly = /* @__PURE__ */ new Set();
|
|
9253
|
+
function warnWholeAssemblyAsSource(opts, outdir) {
|
|
9254
|
+
const logger = getLogger().child("assets");
|
|
9255
|
+
const line = `${opts.subject} has ${opts.field} naming the assembly's output directory ITSELF: '${sanitizeServiceExceptionMessage(outdir)}'. ${getEmbedConfig().productName} will ${opts.sink} — that is the WHOLE assembly, every template and every staged asset, not one asset directory. No CDK synth emits this, so treat this assembly as untrusted unless you wrote that path yourself.`;
|
|
9256
|
+
if (warnedWholeAssembly.has(line)) {
|
|
9257
|
+
logger.debug(line);
|
|
9258
|
+
return;
|
|
9259
|
+
}
|
|
9260
|
+
warnedWholeAssembly.add(line);
|
|
9261
|
+
logger.warn(line);
|
|
9262
|
+
}
|
|
9263
|
+
|
|
9264
|
+
//#endregion
|
|
9265
|
+
//#region src/assets/docker-cache-option.ts
|
|
9266
|
+
/**
|
|
9267
|
+
* Render a `cacheFrom` / `cacheTo` entry as the single `--cache-from` /
|
|
9268
|
+
* `--cache-to` value BuildKit parses: `type=<t>,<k>=<v>,...`, unquoted.
|
|
9269
|
+
*
|
|
9270
|
+
* Its own module because two readers need the SAME string: the argv builder in
|
|
9271
|
+
* `docker-build.ts`, and the passthrough warnings, which judge the rendered
|
|
9272
|
+
* flag rather than the struct (a `params` value can smuggle a `,src=` that the
|
|
9273
|
+
* struct never shows).
|
|
9274
|
+
*/
|
|
9275
|
+
function cacheOptionToFlag(option) {
|
|
9276
|
+
let flag = `type=${option.type}`;
|
|
9277
|
+
if (option.params) for (const [k, v] of Object.entries(option.params)) flag += `,${k}=${v}`;
|
|
9278
|
+
return flag;
|
|
9279
|
+
}
|
|
9280
|
+
|
|
9281
|
+
//#endregion
|
|
9282
|
+
//#region src/assets/buildkit-passthrough-warnings.ts
|
|
9283
|
+
/**
|
|
9284
|
+
* Every candidate host path in a buildx option value: a part with a key yields
|
|
9285
|
+
* its value when the key is `src` / `source` / `dest` (the keys that make
|
|
9286
|
+
* BuildKit open a file), and a part without a key yields itself.
|
|
9287
|
+
*
|
|
9288
|
+
* Over-inclusive on purpose for a BARE part: a non-path string resolves inside
|
|
9289
|
+
* the build context and is dropped before anything is printed. A keyed part is
|
|
9290
|
+
* limited to the three file keys because `ref=` / `scope=` / `env=` are not
|
|
9291
|
+
* paths, and taking them announced a registry ref as a host read.
|
|
9292
|
+
*
|
|
9293
|
+
* Known incomplete, and both misses cost a WARNING, never a refusal: a quoted
|
|
9294
|
+
* CSV value containing a comma is split wrongly here; a leading or trailing
|
|
9295
|
+
* space is trimmed here while buildx keeps it (an over-warn only).
|
|
9296
|
+
*/
|
|
9297
|
+
function candidateHostPaths(value, opts) {
|
|
9298
|
+
const out = [];
|
|
9299
|
+
for (const part of value.split(",")) {
|
|
9300
|
+
const eq = part.indexOf("=");
|
|
9301
|
+
if (eq < 0) {
|
|
9302
|
+
const bare = part.trim();
|
|
9303
|
+
if (bare.length > 0) out.push({
|
|
9304
|
+
path: bare,
|
|
9305
|
+
write: opts.bareIsWrite
|
|
9306
|
+
});
|
|
9307
|
+
continue;
|
|
9308
|
+
}
|
|
9309
|
+
const key = part.slice(0, eq).trim().toLowerCase();
|
|
9310
|
+
const v = part.slice(eq + 1).trim();
|
|
9311
|
+
if (v.length === 0) continue;
|
|
9312
|
+
if (key !== "src" && key !== "source" && key !== "dest") continue;
|
|
9313
|
+
out.push({
|
|
9314
|
+
path: v,
|
|
9315
|
+
write: key === "dest"
|
|
9316
|
+
});
|
|
9317
|
+
}
|
|
9318
|
+
return out;
|
|
9319
|
+
}
|
|
9320
|
+
/**
|
|
9321
|
+
* Every host path this source hands to BuildKit. Each field is judged as the
|
|
9322
|
+
* RENDERED argv string `buildDockerBuildCommand` pushes, not as the struct —
|
|
9323
|
+
* nothing there is quoted, so CSV smuggled into a key or a `params` value
|
|
9324
|
+
* reaches BuildKit and must reach this walk too.
|
|
9325
|
+
*/
|
|
9326
|
+
function hostPathsOf(source) {
|
|
9327
|
+
const refs = [];
|
|
9328
|
+
if (source.dockerFile) refs.push({
|
|
9329
|
+
field: "dockerFile",
|
|
9330
|
+
where: "dockerFile",
|
|
9331
|
+
path: source.dockerFile,
|
|
9332
|
+
write: false
|
|
9333
|
+
});
|
|
9334
|
+
for (const [k, v] of Object.entries(source.dockerBuildContexts ?? {})) {
|
|
9335
|
+
const rendered = `${k}=${v}`;
|
|
9336
|
+
const contextPath = rendered.slice(rendered.indexOf("=") + 1).trim().replace(/^oci-layout:\/\//, "");
|
|
9337
|
+
if (contextPath.length === 0) continue;
|
|
9338
|
+
refs.push({
|
|
9339
|
+
field: "dockerBuildContexts",
|
|
9340
|
+
where: k,
|
|
9341
|
+
path: contextPath,
|
|
9342
|
+
write: false
|
|
9343
|
+
});
|
|
9344
|
+
}
|
|
9345
|
+
const ssh = source.dockerBuildSsh ?? "";
|
|
9346
|
+
const sshEq = ssh.indexOf("=");
|
|
9347
|
+
(sshEq < 0 ? [] : ssh.slice(sshEq + 1).split(",")).forEach((entry, i) => {
|
|
9348
|
+
const p = entry.trim();
|
|
9349
|
+
if (p.length > 0) refs.push({
|
|
9350
|
+
field: "dockerBuildSsh",
|
|
9351
|
+
where: String(i),
|
|
9352
|
+
path: p,
|
|
9353
|
+
write: false
|
|
9354
|
+
});
|
|
9355
|
+
});
|
|
9356
|
+
for (const [k, v] of Object.entries(source.dockerBuildSecrets ?? {})) for (const { path: p, write } of candidateHostPaths(`id=${k},${v}`, { bareIsWrite: false })) refs.push({
|
|
9357
|
+
field: "dockerBuildSecrets",
|
|
9358
|
+
where: k,
|
|
9359
|
+
path: p,
|
|
9360
|
+
write
|
|
9361
|
+
});
|
|
9362
|
+
(source.cacheFrom ?? []).forEach((c, i) => {
|
|
9363
|
+
for (const { path: p, write } of candidateHostPaths(cacheOptionToFlag(c), { bareIsWrite: false })) refs.push({
|
|
9364
|
+
field: "cacheFrom",
|
|
9365
|
+
where: String(i),
|
|
9366
|
+
path: p,
|
|
9367
|
+
write
|
|
9368
|
+
});
|
|
9369
|
+
});
|
|
9370
|
+
if (source.cacheTo) for (const { path: p, write } of candidateHostPaths(cacheOptionToFlag(source.cacheTo), { bareIsWrite: false })) refs.push({
|
|
9371
|
+
field: "cacheTo",
|
|
9372
|
+
where: "cacheTo",
|
|
9373
|
+
path: p,
|
|
9374
|
+
write
|
|
9375
|
+
});
|
|
9376
|
+
(source.dockerOutputs ?? []).forEach((o, i) => {
|
|
9377
|
+
for (const { path: p, write } of candidateHostPaths(o, { bareIsWrite: true })) refs.push({
|
|
9378
|
+
field: "dockerOutputs",
|
|
9379
|
+
where: String(i),
|
|
9380
|
+
path: p,
|
|
9381
|
+
write
|
|
9382
|
+
});
|
|
9383
|
+
});
|
|
9384
|
+
return refs;
|
|
9385
|
+
}
|
|
9386
|
+
/**
|
|
9387
|
+
* Lines already printed this process; a repeat drops to debug. Replicas and
|
|
9388
|
+
* reloads rebuild the same asset, and the same line N times is a storm.
|
|
9389
|
+
*/
|
|
9390
|
+
const warnedPassthroughs = /* @__PURE__ */ new Set();
|
|
9391
|
+
/**
|
|
9392
|
+
* Warn for each BuildKit passthrough whose host path leaves the assembly.
|
|
9393
|
+
*
|
|
9394
|
+
* `base` is the build context directory (what BuildKit resolves a relative
|
|
9395
|
+
* value against — the build's cwd); `bound` is the app's outdir. Called above
|
|
9396
|
+
* the spawn so the line precedes the read or write it describes.
|
|
9397
|
+
*
|
|
9398
|
+
* No "inside the build context" exemption is needed, unlike a builder that
|
|
9399
|
+
* honours an absolute context: `buildDockerImage` has already contained the
|
|
9400
|
+
* context within `bound`, so a path inside the context is inside the bound.
|
|
9401
|
+
*/
|
|
9402
|
+
function warnEscapingBuildKitPaths(source, base, bound) {
|
|
9403
|
+
const logger = getLogger().child("assets");
|
|
9404
|
+
for (const ref of hostPathsOf(source)) {
|
|
9405
|
+
const escape = assemblyPathEscape(base, bound, ref.path);
|
|
9406
|
+
if (escape === void 0) continue;
|
|
9407
|
+
const verb = ref.write ? "WRITE to" : "read";
|
|
9408
|
+
const line = `Docker asset ${ref.field}` + (ref.where === ref.field ? "" : `['${sanitizeServiceExceptionMessage(ref.where)}']`) + ` names a host path outside the assembly, which ` + renderAssemblyPathEscape(escape, bound, "load", `${getEmbedConfig().productName} will ${verb} it during the image build, matching the CDK CLI — a pre-synthesized assembly is trusted input. If you did not produce this assembly, that is a host path it chose and the CloudFormation template does not show.`);
|
|
9409
|
+
if (warnedPassthroughs.has(line)) {
|
|
9410
|
+
logger.debug(line);
|
|
9411
|
+
continue;
|
|
9412
|
+
}
|
|
9413
|
+
warnedPassthroughs.add(line);
|
|
9414
|
+
logger.warn(line);
|
|
9415
|
+
}
|
|
9416
|
+
}
|
|
9417
|
+
|
|
8970
9418
|
//#endregion
|
|
8971
9419
|
//#region src/assets/docker-build.ts
|
|
8972
9420
|
/**
|
|
@@ -9042,11 +9490,23 @@ function warnManifestExecutable(cmd, executable, cwd) {
|
|
|
9042
9490
|
async function buildDockerImage(asset, cdkOutDir, options) {
|
|
9043
9491
|
const source = asset.source;
|
|
9044
9492
|
const logger = getLogger().child("docker-build");
|
|
9493
|
+
const assetOutdir = options.assetOutdir ?? cdkOutDir;
|
|
9494
|
+
const contextDirectory = (directory, sink) => resolveAssetSourcePath({
|
|
9495
|
+
manifestDir: cdkOutDir,
|
|
9496
|
+
value: directory,
|
|
9497
|
+
assetOutdir,
|
|
9498
|
+
absolute: "fold",
|
|
9499
|
+
field: "source.directory",
|
|
9500
|
+
subject: "Docker image asset",
|
|
9501
|
+
action: "build it",
|
|
9502
|
+
sink,
|
|
9503
|
+
wrapError: (message) => new LocalInvokeBuildError(message)
|
|
9504
|
+
});
|
|
9045
9505
|
if (source.executable && source.executable.length > 0) {
|
|
9046
9506
|
const [cmd, ...args] = source.executable;
|
|
9047
9507
|
if (!cmd) throw options.wrapError("asset source.executable[] is empty");
|
|
9048
|
-
const cwd = source.directory ?
|
|
9049
|
-
logger.debug(`Building Docker image via executable: ${source.executable.join(" ")} (cwd=${cwd})`);
|
|
9508
|
+
const cwd = source.directory ? contextDirectory(source.directory, "run this asset's source.executable with that directory as its working directory") : cdkOutDir;
|
|
9509
|
+
logger.debug(`Building Docker image via executable: ${sanitizeServiceExceptionMessage(source.executable.join(" "))} (cwd=${sanitizeServiceExceptionMessage(cwd)})`);
|
|
9050
9510
|
warnManifestExecutable(cmd, source.executable, cwd);
|
|
9051
9511
|
let result;
|
|
9052
9512
|
try {
|
|
@@ -9062,8 +9522,9 @@ async function buildDockerImage(asset, cdkOutDir, options) {
|
|
|
9062
9522
|
if (!source.directory) throw options.wrapError(`DockerImageAssetSource must set either 'directory' or 'executable' (got: ${JSON.stringify(source)})`);
|
|
9063
9523
|
if (!options.tag) throw options.wrapError("buildDockerImage(directory mode) requires options.tag");
|
|
9064
9524
|
const buildArgs = buildDockerBuildCommand(source, options.tag, options.platform);
|
|
9065
|
-
const contextDir =
|
|
9525
|
+
const contextDir = contextDirectory(source.directory, `send that directory to docker build as the context of an image ${getEmbedConfig().productName} then runs locally`);
|
|
9066
9526
|
buildArgs.push(".");
|
|
9527
|
+
warnEscapingBuildKitPaths(source, contextDir, assetOutdir);
|
|
9067
9528
|
logger.debug(`${getDockerCmd()} ${buildArgs.join(" ")} (cwd=${contextDir})`);
|
|
9068
9529
|
try {
|
|
9069
9530
|
await runDockerStreaming(buildArgs, {
|
|
@@ -9104,11 +9565,6 @@ function buildDockerBuildCommand(source, tag, platformOverride) {
|
|
|
9104
9565
|
if (source.cacheDisabled) args.push("--no-cache");
|
|
9105
9566
|
return args;
|
|
9106
9567
|
}
|
|
9107
|
-
function cacheOptionToFlag(option) {
|
|
9108
|
-
let flag = `type=${option.type}`;
|
|
9109
|
-
if (option.params) for (const [k, v] of Object.entries(option.params)) flag += `,${k}=${v}`;
|
|
9110
|
-
return flag;
|
|
9111
|
-
}
|
|
9112
9568
|
|
|
9113
9569
|
//#endregion
|
|
9114
9570
|
//#region src/local/ecr-puller.ts
|
|
@@ -9384,8 +9840,9 @@ async function buildContainerImage(asset, cdkOutDir, options) {
|
|
|
9384
9840
|
const actualTag = await buildDockerImage(asset, cdkOutDir, {
|
|
9385
9841
|
tag,
|
|
9386
9842
|
platform,
|
|
9387
|
-
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`),
|
|
9388
|
-
progressLabel: `Building container image (platform=${platform})
|
|
9843
|
+
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${sanitizeServiceExceptionMessage(asset.source.directory ?? asset.source.executable?.join(" ") ?? "")}): ${stderr}`),
|
|
9844
|
+
progressLabel: `Building container image (platform=${platform})`,
|
|
9845
|
+
...options.assetOutdir !== void 0 && { assetOutdir: options.assetOutdir }
|
|
9389
9846
|
});
|
|
9390
9847
|
if (actualTag !== tag) {
|
|
9391
9848
|
logger.debug(`Re-tagging executable-built image '${actualTag}' → '${tag}'`);
|
|
@@ -9615,6 +10072,10 @@ async function runDetached(opts) {
|
|
|
9615
10072
|
}
|
|
9616
10073
|
args.push(opts.image, ...entryPointTail, ...opts.cmd);
|
|
9617
10074
|
getLogger().child("docker").debug(`${getDockerCmd()} ${redactAwsCredentialsInArgs(args).join(" ")}`);
|
|
10075
|
+
const forwardedKeys = Object.keys(passthroughEnv);
|
|
10076
|
+
const refusal = finchSecretArgvRefusal(forwardedKeys.filter((k) => opts.sensitiveEnvKeys?.has(k)), `Container for image ${opts.image}`);
|
|
10077
|
+
if (refusal !== void 0) throw new DockerRunnerError(refusal);
|
|
10078
|
+
warnFinchArgvExposure(forwardedKeys);
|
|
9618
10079
|
try {
|
|
9619
10080
|
const { stdout } = await execFileAsync$5(getDockerCmd(), args, {
|
|
9620
10081
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -9813,6 +10274,8 @@ var AssetManifestLoader = class {
|
|
|
9813
10274
|
* @returns Asset manifest or null if not found
|
|
9814
10275
|
*/
|
|
9815
10276
|
async loadManifest(cdkOutputDir, stackName) {
|
|
10277
|
+
const resolved = resolveAssemblyPath(cdkOutputDir, `${stackName}.assets.json`);
|
|
10278
|
+
if (!resolved.contained) throw new Error(`Refusing to read the asset manifest for stack '${sanitizeServiceExceptionMessage(stackName)}': it ` + renderAssemblyPathEscape(resolved, cdkOutputDir, "read it"));
|
|
9816
10279
|
const manifestPath = join$1(cdkOutputDir, `${stackName}.assets.json`);
|
|
9817
10280
|
try {
|
|
9818
10281
|
this.logger.debug(`Loading asset manifest from: ${manifestPath}`);
|
|
@@ -9847,14 +10310,31 @@ var AssetManifestLoader = class {
|
|
|
9847
10310
|
return fileAssets;
|
|
9848
10311
|
}
|
|
9849
10312
|
/**
|
|
9850
|
-
* Get asset source
|
|
10313
|
+
* Get a file asset's source directory (absolute path), REFUSING a
|
|
10314
|
+
* `source.path` that leaves `opts.assetOutdir` (go-to-k/cdk-local#745).
|
|
10315
|
+
*
|
|
10316
|
+
* The value resolves against `cdkOutputDir` with `path.join` semantics, as
|
|
10317
|
+
* it always has, so an ABSOLUTE value is folded under it rather than
|
|
10318
|
+
* honoured (`'fold'` in `resolveAssetSourcePath`). `assetOutdir` is the
|
|
10319
|
+
* app's outdir — `assetPathDirs(stack).assetOutdir` — and is REQUIRED so a
|
|
10320
|
+
* caller cannot silently bound a Stage's `../asset.<hash>` at the manifest
|
|
10321
|
+
* directory.
|
|
9851
10322
|
*
|
|
9852
|
-
*
|
|
9853
|
-
*
|
|
9854
|
-
* @returns Absolute path to asset source
|
|
10323
|
+
* The one reader is the AgentCore CodeConfiguration bundle, which builds an
|
|
10324
|
+
* image FROM this directory; `subject` names it in the refusal.
|
|
9855
10325
|
*/
|
|
9856
|
-
getAssetSourcePath(cdkOutputDir, asset) {
|
|
9857
|
-
return
|
|
10326
|
+
getAssetSourcePath(cdkOutputDir, asset, opts) {
|
|
10327
|
+
return resolveAssetSourcePath({
|
|
10328
|
+
manifestDir: cdkOutputDir,
|
|
10329
|
+
value: asset.source.path,
|
|
10330
|
+
assetOutdir: opts.assetOutdir,
|
|
10331
|
+
absolute: "fold",
|
|
10332
|
+
field: "source.path",
|
|
10333
|
+
subject: opts.subject,
|
|
10334
|
+
action: "build an image from it",
|
|
10335
|
+
sink: "build an image from that directory and run it locally",
|
|
10336
|
+
wrapError: opts.wrapError
|
|
10337
|
+
});
|
|
9858
10338
|
}
|
|
9859
10339
|
/**
|
|
9860
10340
|
* Resolve asset destination values (replace ${AWS::AccountId}, ${AWS::Region}, etc.)
|
|
@@ -19405,6 +19885,7 @@ async function resolveContainerImageForStartApi(lambda, skipPull, profile, optio
|
|
|
19405
19885
|
const localBuild = await resolveLocalBuildPlan$1(lambda);
|
|
19406
19886
|
if (localBuild) return { imageRef: await buildContainerImage(localBuild.asset, localBuild.cdkOutDir, {
|
|
19407
19887
|
architecture: lambda.architecture,
|
|
19888
|
+
assetOutdir: localBuild.assetOutdir,
|
|
19408
19889
|
...options?.noBuild !== void 0 && { noBuild: options.noBuild }
|
|
19409
19890
|
}) };
|
|
19410
19891
|
if (!parseEcrUri(lambda.imageUri)) throw new Error(`Container Lambda '${lambda.logicalId}' has no matching asset in cdk.out, and Code.ImageUri '${lambda.imageUri}' is not an ECR URI ${getEmbedConfig().binaryName} can authenticate against. Re-synthesize the CDK app (so cdk.out includes the build context) or deploy the image to ECR first.`);
|
|
@@ -19434,7 +19915,8 @@ async function resolveLocalBuildPlan$1(lambda) {
|
|
|
19434
19915
|
if (!entry) return void 0;
|
|
19435
19916
|
return {
|
|
19436
19917
|
asset: entry.asset,
|
|
19437
|
-
cdkOutDir
|
|
19918
|
+
cdkOutDir,
|
|
19919
|
+
assetOutdir: assetPathDirs(lambda.stack).assetOutdir
|
|
19438
19920
|
};
|
|
19439
19921
|
}
|
|
19440
19922
|
/**
|
|
@@ -20462,7 +20944,8 @@ async function resolveContainerImagePlan$1(lambda, options) {
|
|
|
20462
20944
|
let imageRef;
|
|
20463
20945
|
if (localBuild) imageRef = await buildContainerImage(localBuild.asset, localBuild.cdkOutDir, {
|
|
20464
20946
|
architecture: lambda.architecture,
|
|
20465
|
-
noBuild: options.build === false
|
|
20947
|
+
noBuild: options.build === false,
|
|
20948
|
+
assetOutdir: localBuild.assetOutdir
|
|
20466
20949
|
});
|
|
20467
20950
|
else {
|
|
20468
20951
|
if (!parseEcrUri(lambda.imageUri)) throw new Error(`Container Lambda '${lambda.logicalId}' has no matching asset in cdk.out, and Code.ImageUri '${lambda.imageUri}' is not an ECR URI ${getEmbedConfig().binaryName} can authenticate against. Re-synthesize the CDK app (so cdk.out includes the build context) or deploy the image to ECR first.`);
|
|
@@ -20496,7 +20979,8 @@ async function resolveLocalBuildPlan(lambda) {
|
|
|
20496
20979
|
if (!entry) return void 0;
|
|
20497
20980
|
return {
|
|
20498
20981
|
asset: entry.asset,
|
|
20499
|
-
cdkOutDir
|
|
20982
|
+
cdkOutDir,
|
|
20983
|
+
assetOutdir: assetPathDirs(lambda.stack).assetOutdir
|
|
20500
20984
|
};
|
|
20501
20985
|
}
|
|
20502
20986
|
function envHasIntrinsicValue(templateEnv) {
|
|
@@ -21146,7 +21630,7 @@ async function buildAgentCoreCodeImage(options) {
|
|
|
21146
21630
|
], { progressLabel: `Building agent image (runtime=${options.runtime})` });
|
|
21147
21631
|
} catch (err) {
|
|
21148
21632
|
const stderr = err.stderr?.trim();
|
|
21149
|
-
throw new LocalInvokeBuildError(`docker build failed for AgentCore code artifact (${options.sourceDir})${stderr ? `: ${stderr}` : ""}`);
|
|
21633
|
+
throw new LocalInvokeBuildError(`docker build failed for AgentCore code artifact (${flattenToOneLine(options.sourceDir)})${stderr ? `: ${stderr}` : ""}`);
|
|
21150
21634
|
} finally {
|
|
21151
21635
|
await rm(buildDir, {
|
|
21152
21636
|
recursive: true,
|
|
@@ -21191,14 +21675,14 @@ async function warnIfDependenciesNotVendored(sourceDir, runtime, isNode, logger)
|
|
|
21191
21675
|
}
|
|
21192
21676
|
const has = (name) => entries.includes(name);
|
|
21193
21677
|
if (isNode) {
|
|
21194
|
-
if (has("package.json") && !has("node_modules")) logger.warn(`AgentCore code bundle '${sourceDir}' declares package.json but does not vendor node_modules. The AgentCore managed runtime does NOT install dependencies at runtime, so the deployed agent will fail to resolve them. Vendor dependencies into the bundle (e.g. 'npm install --omit=dev' in the bundle dir) so the deploy artifact is self-contained. cdk-local runs the bundle as-is to match the deployed runtime.`);
|
|
21678
|
+
if (has("package.json") && !has("node_modules")) logger.warn(`AgentCore code bundle '${flattenToOneLine(sourceDir)}' declares package.json but does not vendor node_modules. The AgentCore managed runtime does NOT install dependencies at runtime, so the deployed agent will fail to resolve them. Vendor dependencies into the bundle (e.g. 'npm install --omit=dev' in the bundle dir) so the deploy artifact is self-contained. cdk-local runs the bundle as-is to match the deployed runtime.`);
|
|
21195
21679
|
return;
|
|
21196
21680
|
}
|
|
21197
21681
|
const manifest = await pythonManifestDeclaringDeps(sourceDir, entries);
|
|
21198
21682
|
const vendored = entries.some((e) => e.endsWith(".dist-info"));
|
|
21199
21683
|
if (manifest && !vendored) {
|
|
21200
21684
|
const pyVersion = runtime.replace("PYTHON_", "").replace("_", ".");
|
|
21201
|
-
logger.warn(`AgentCore code bundle '${sourceDir}' declares ${manifest} but does not vendor its dependencies. The AgentCore managed runtime does NOT install dependencies at runtime, so the deployed agent will fail with ModuleNotFoundError. Vendor arm64 wheels into the bundle, e.g.:
|
|
21685
|
+
logger.warn(`AgentCore code bundle '${flattenToOneLine(sourceDir)}' declares ${manifest} but does not vendor its dependencies. The AgentCore managed runtime does NOT install dependencies at runtime, so the deployed agent will fail with ModuleNotFoundError. Vendor arm64 wheels into the bundle, e.g.:
|
|
21202
21686
|
uv pip install --python-platform aarch64-manylinux2014 --python-version ${pyVersion} --target <bundle-dir> -r requirements.txt\ncdk-local runs the bundle as-is to match the deployed runtime.`);
|
|
21203
21687
|
}
|
|
21204
21688
|
}
|
|
@@ -22496,7 +22980,8 @@ async function resolveAgentCoreImage(resolved, options, loaded, stateProvider) {
|
|
|
22496
22980
|
const entry = getDockerImageBySourceHash(manifest, containerUri);
|
|
22497
22981
|
if (entry) return buildContainerImage(entry.asset, cdkOutDir, {
|
|
22498
22982
|
architecture,
|
|
22499
|
-
noBuild: options.build === false
|
|
22983
|
+
noBuild: options.build === false,
|
|
22984
|
+
assetOutdir: assetPathDirs(resolved.stack).assetOutdir
|
|
22500
22985
|
});
|
|
22501
22986
|
}
|
|
22502
22987
|
}
|
|
@@ -22531,8 +23016,12 @@ async function resolveAgentCoreCodeImage(resolved, code, options, architecture,
|
|
|
22531
23016
|
const fileAssets = manifest ? loader.getFileAssets(manifest) : void 0;
|
|
22532
23017
|
const asset = fileAssets ? fileAssets.get(code.codeAssetHash) ?? findFileAssetByObjectKey$1(fileAssets, code.codeAssetHash) : void 0;
|
|
22533
23018
|
if (!asset) throw new CdkLocalError(`AgentCore Runtime '${resolved.logicalId}' code bundle (asset ${code.codeAssetHash}) was not found in the cdk.out asset manifest. ${getEmbedConfig().cliName} invoke-agentcore runs a local from-source build of a fromCodeAsset bundle — re-synthesize the app so the asset is staged in cdk.out and retry. (A fromS3 bundle is downloaded from S3 instead; this runtime has no literal Code.S3.Bucket.)`, "LOCAL_INVOKE_AGENTCORE_CODE_ASSET_NOT_FOUND");
|
|
22534
|
-
const sourceDir = loader.getAssetSourcePath(cdkOutDir, asset
|
|
22535
|
-
|
|
23019
|
+
const sourceDir = loader.getAssetSourcePath(cdkOutDir, asset, {
|
|
23020
|
+
assetOutdir: assetPathDirs(resolved.stack).assetOutdir,
|
|
23021
|
+
subject: `AgentCore Runtime '${flattenToOneLine(resolved.logicalId)}' code bundle`,
|
|
23022
|
+
wrapError: (message) => new CdkLocalError(message, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_ESCAPES_ASSEMBLY")
|
|
23023
|
+
});
|
|
23024
|
+
if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) throw new CdkLocalError(`AgentCore Runtime '${flattenToOneLine(resolved.logicalId)}' code bundle source '${flattenToOneLine(sourceDir)}' does not exist or is not a directory. Re-synthesize the app and retry.`, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_MISSING");
|
|
22536
23025
|
return buildAgentCoreCodeImage({
|
|
22537
23026
|
sourceDir,
|
|
22538
23027
|
runtime: code.runtime,
|
|
@@ -23248,7 +23737,7 @@ async function softReloadAgentContainer(containerId, newAssetSourceDir) {
|
|
|
23248
23737
|
throw new CdkLocalError(`softReloadAgentContainer: docker inspect of container '${containerId}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_INSPECT_FAILED");
|
|
23249
23738
|
}
|
|
23250
23739
|
const workdirDest = workdir.endsWith("/") ? workdir : `${workdir}/`;
|
|
23251
|
-
logger.info(`Soft-reload: docker cp ${newAssetSourceDir} -> ${containerId}:${workdirDest}; restart.`);
|
|
23740
|
+
logger.info(`Soft-reload: docker cp ${flattenToOneLine(newAssetSourceDir)} -> ${containerId}:${workdirDest}; restart.`);
|
|
23252
23741
|
try {
|
|
23253
23742
|
await execFileAsync$4(dockerCmd, [
|
|
23254
23743
|
"cp",
|
|
@@ -23295,7 +23784,11 @@ async function loadAgentCoreAssetContext(args) {
|
|
|
23295
23784
|
if (!manifest) return void 0;
|
|
23296
23785
|
const asset = assetLoader.getFileAssets(manifest).get(resolved.codeArtifact.codeAssetHash);
|
|
23297
23786
|
if (!asset) return void 0;
|
|
23298
|
-
const sourceDir = assetLoader.getAssetSourcePath(cdkOutDir, asset
|
|
23787
|
+
const sourceDir = assetLoader.getAssetSourcePath(watchManifestDir(newCandidate, cdkOutDir), asset, {
|
|
23788
|
+
assetOutdir: outputAssetBound(newCandidate, cdkOutDir),
|
|
23789
|
+
subject: `AgentCore Runtime '${flattenToOneLine(resolved.logicalId)}' code bundle`,
|
|
23790
|
+
wrapError: (message) => new CdkLocalError(message, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_ESCAPES_ASSEMBLY")
|
|
23791
|
+
});
|
|
23299
23792
|
return {
|
|
23300
23793
|
...oldAssetHash !== void 0 && { oldAssetHash },
|
|
23301
23794
|
newAssetHash: resolved.codeArtifact.codeAssetHash,
|
|
@@ -23311,7 +23804,17 @@ async function loadAgentCoreAssetContext(args) {
|
|
|
23311
23804
|
const newAssetHash = dockerImageEntry.hash;
|
|
23312
23805
|
const newDockerImage = dockerImageEntry.asset;
|
|
23313
23806
|
if (!newDockerImage.source.directory) return void 0;
|
|
23314
|
-
const newAssetSourceDir =
|
|
23807
|
+
const newAssetSourceDir = resolveAssetSourcePath({
|
|
23808
|
+
manifestDir: watchManifestDir(newCandidate, cdkOutDir),
|
|
23809
|
+
value: newDockerImage.source.directory,
|
|
23810
|
+
assetOutdir: outputAssetBound(newCandidate, cdkOutDir),
|
|
23811
|
+
absolute: "honour",
|
|
23812
|
+
field: "source.directory",
|
|
23813
|
+
subject: `AgentCore Runtime '${flattenToOneLine(resolved.logicalId)}' image asset`,
|
|
23814
|
+
action: "copy from it",
|
|
23815
|
+
sink: "copy that directory into the running agent container on a soft reload",
|
|
23816
|
+
wrapError: (message) => new CdkLocalError(message, "LOCAL_INVOKE_AGENTCORE_CODE_SOURCE_ESCAPES_ASSEMBLY")
|
|
23817
|
+
});
|
|
23315
23818
|
return {
|
|
23316
23819
|
...oldAssetHash !== void 0 && { oldAssetHash },
|
|
23317
23820
|
newAssetHash,
|
|
@@ -23734,6 +24237,7 @@ async function createNetworkAndSidecar(args) {
|
|
|
23734
24237
|
if (cluster) sidecarEnv["CLUSTER"] = cluster;
|
|
23735
24238
|
const sidecarPassthroughEnv = appendEnvFlags(sidecarArgs, sidecarEnv, SENSITIVE_ENV_KEYS);
|
|
23736
24239
|
sidecarArgs.push(METADATA_ENDPOINT_IMAGE);
|
|
24240
|
+
warnFinchArgvExposure(Object.keys(sidecarPassthroughEnv));
|
|
23737
24241
|
logger.info(`Starting ECS local-container-endpoints sidecar at ${sidecarIp}...`);
|
|
23738
24242
|
try {
|
|
23739
24243
|
const { stdout } = await execFileAsync$3(getDockerCmd(), sidecarArgs, {
|
|
@@ -24044,6 +24548,12 @@ async function runEcsTask(task, options, state) {
|
|
|
24044
24548
|
if (task.containers.length === 0) throw new EcsTaskRunnerError(`Task '${task.taskDefinitionLogicalId}' has no containers — nothing to run.`);
|
|
24045
24549
|
for (const w of task.warnings) logger.warn(w);
|
|
24046
24550
|
const startOrder = topoSort(buildDependencyGraph(task.containers), task.containers);
|
|
24551
|
+
const finchRefusals = [];
|
|
24552
|
+
for (const c of task.containers) {
|
|
24553
|
+
const refusal = finchSecretArgvRefusal([...c.secrets.map((s) => s.name), ...c.sensitiveEnvKeys], `Container '${c.name}'`);
|
|
24554
|
+
if (refusal !== void 0) finchRefusals.push(refusal);
|
|
24555
|
+
}
|
|
24556
|
+
if (finchRefusals.length > 0) throw new EcsTaskRunnerError(finchRefusals.join("\n"));
|
|
24047
24557
|
const imagePlan = options.imagePlanByContainer ?? /* @__PURE__ */ new Map();
|
|
24048
24558
|
if (!options.imagePlanByContainer) await prepareImages(task, imagePlan, options);
|
|
24049
24559
|
const allSecrets = [];
|
|
@@ -24128,6 +24638,7 @@ async function runEcsTask(task, options, state) {
|
|
|
24128
24638
|
const { args, sensitiveEnv } = dockerCmds.get(container.name);
|
|
24129
24639
|
logger.info(`Starting container '${container.name}' (image=${imagePlan.get(container.name)})`);
|
|
24130
24640
|
let id;
|
|
24641
|
+
warnFinchArgvExposure(Object.keys(sensitiveEnv));
|
|
24131
24642
|
try {
|
|
24132
24643
|
const { stdout } = await execFileAsync$2(getDockerCmd(), args, {
|
|
24133
24644
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -24340,8 +24851,9 @@ async function prepareOneImage(task, container, options) {
|
|
|
24340
24851
|
}
|
|
24341
24852
|
const actualTag = await buildDockerImage(asset, cdkOutDir, {
|
|
24342
24853
|
tag,
|
|
24854
|
+
assetOutdir: assetPathDirs(task.stack).assetOutdir,
|
|
24343
24855
|
...options.platformOverride !== void 0 && { platform: options.platformOverride },
|
|
24344
|
-
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for ECS container '${container.name}' (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`),
|
|
24856
|
+
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for ECS container '${container.name}' (${sanitizeServiceExceptionMessage(asset.source.directory ?? asset.source.executable?.join(" ") ?? "")}): ${stderr}`),
|
|
24345
24857
|
progressLabel: `Building container image for '${container.name}'`
|
|
24346
24858
|
});
|
|
24347
24859
|
if (actualTag !== tag) try {
|
|
@@ -25718,7 +26230,7 @@ async function softReloadReplica(args) {
|
|
|
25718
26230
|
instance.softReloadInProgress = true;
|
|
25719
26231
|
instance.softReloadGeneration = (instance.softReloadGeneration ?? 0) + 1;
|
|
25720
26232
|
try {
|
|
25721
|
-
logger.info(`Soft-reloading replica r${instance.index} (gen ${instance.generation}): docker cp ${sourceDirToCopy} -> ${targets.length} essential container(s); restart.`);
|
|
26233
|
+
logger.info(`Soft-reloading replica r${instance.index} (gen ${instance.generation}): docker cp ${flattenToOneLine(sourceDirToCopy)} -> ${targets.length} essential container(s); restart.`);
|
|
25722
26234
|
for (const target of targets) {
|
|
25723
26235
|
let workdir;
|
|
25724
26236
|
try {
|
|
@@ -25766,7 +26278,7 @@ async function softReloadReplica(args) {
|
|
|
25766
26278
|
const defaultDockerInspectWorkdirImpl = async (containerId) => {
|
|
25767
26279
|
const { execFile } = await import("node:child_process");
|
|
25768
26280
|
const { promisify } = await import("node:util");
|
|
25769
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
26281
|
+
const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
|
|
25770
26282
|
const { stdout } = await promisify(execFile)(getDockerCmd(), [
|
|
25771
26283
|
"inspect",
|
|
25772
26284
|
"--format",
|
|
@@ -25785,7 +26297,7 @@ let dockerInspectWorkdirImpl = defaultDockerInspectWorkdirImpl;
|
|
|
25785
26297
|
const defaultDockerCpImpl = async (src, dst) => {
|
|
25786
26298
|
const { execFile } = await import("node:child_process");
|
|
25787
26299
|
const { promisify } = await import("node:util");
|
|
25788
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
26300
|
+
const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
|
|
25789
26301
|
await promisify(execFile)(getDockerCmd(), [
|
|
25790
26302
|
"cp",
|
|
25791
26303
|
src,
|
|
@@ -25800,7 +26312,7 @@ let dockerCpImpl = defaultDockerCpImpl;
|
|
|
25800
26312
|
const defaultDockerRestartImpl = async (containerId) => {
|
|
25801
26313
|
const { execFile } = await import("node:child_process");
|
|
25802
26314
|
const { promisify } = await import("node:util");
|
|
25803
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
26315
|
+
const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
|
|
25804
26316
|
await promisify(execFile)(getDockerCmd(), ["restart", containerId]);
|
|
25805
26317
|
};
|
|
25806
26318
|
let dockerRestartImpl = defaultDockerRestartImpl;
|
|
@@ -25840,7 +26352,7 @@ async function disconnectOldFromSharedNetwork(oldInstance) {
|
|
|
25840
26352
|
const defaultDockerNetworkDisconnectImpl = async (networkName, containerId) => {
|
|
25841
26353
|
const { execFile } = await import("node:child_process");
|
|
25842
26354
|
const { promisify } = await import("node:util");
|
|
25843
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
26355
|
+
const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
|
|
25844
26356
|
await promisify(execFile)(getDockerCmd(), [
|
|
25845
26357
|
"network",
|
|
25846
26358
|
"disconnect",
|
|
@@ -26017,7 +26529,7 @@ function pickEssentialContainerId(instance, service) {
|
|
|
26017
26529
|
const defaultWaitForExitImpl = async (containerId) => {
|
|
26018
26530
|
const { execFile } = await import("node:child_process");
|
|
26019
26531
|
const { promisify } = await import("node:util");
|
|
26020
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
26532
|
+
const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
|
|
26021
26533
|
const { stdout } = await promisify(execFile)(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
|
|
26022
26534
|
const code = parseInt(stdout.trim(), 10);
|
|
26023
26535
|
return Number.isFinite(code) ? code : -1;
|
|
@@ -26037,7 +26549,7 @@ const EXIT_LOG_TAIL_LINES = 50;
|
|
|
26037
26549
|
const defaultReadContainerLogsImpl = async (containerId) => {
|
|
26038
26550
|
const { execFile } = await import("node:child_process");
|
|
26039
26551
|
const { promisify } = await import("node:util");
|
|
26040
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
26552
|
+
const { getDockerCmd } = await import("./docker-cmd-SocNyC9q.js").then((n) => n.t);
|
|
26041
26553
|
const { stdout, stderr } = await promisify(execFile)(getDockerCmd(), [
|
|
26042
26554
|
"logs",
|
|
26043
26555
|
"--tail",
|
|
@@ -27846,7 +28358,10 @@ async function resolveContainerImagePlan(lambda, opts) {
|
|
|
27846
28358
|
const manifest = await new AssetManifestLoader().loadManifest(cdkOutDir, lambda.stack.stackName);
|
|
27847
28359
|
const entry = manifest ? getDockerImageBySourceHash(manifest, lambda.imageUri) : void 0;
|
|
27848
28360
|
if (entry) {
|
|
27849
|
-
imageRef = await buildContainerImage(entry.asset, cdkOutDir, {
|
|
28361
|
+
imageRef = await buildContainerImage(entry.asset, cdkOutDir, {
|
|
28362
|
+
architecture: lambda.architecture,
|
|
28363
|
+
assetOutdir: assetPathDirs(lambda.stack).assetOutdir
|
|
28364
|
+
});
|
|
27850
28365
|
localBuilt = true;
|
|
27851
28366
|
}
|
|
27852
28367
|
}
|
|
@@ -29243,7 +29758,17 @@ async function loadAssetContextForTarget(args) {
|
|
|
29243
29758
|
logger.debug(`loadAssetContext: docker asset '${newAssetHash}' for stack '${candidate.stackName}' is executable-mode (no source.directory). Classifier will see no asset context (rebuild).`);
|
|
29244
29759
|
return;
|
|
29245
29760
|
}
|
|
29246
|
-
const newAssetSourceDir =
|
|
29761
|
+
const newAssetSourceDir = resolveAssetSourcePath({
|
|
29762
|
+
manifestDir: watchManifestDir(candidate, cdkOutDir),
|
|
29763
|
+
value: newDockerImage.source.directory,
|
|
29764
|
+
assetOutdir: outputAssetBound(candidate, cdkOutDir),
|
|
29765
|
+
absolute: "honour",
|
|
29766
|
+
field: "source.directory",
|
|
29767
|
+
subject: `Docker image asset '${sanitizeServiceExceptionMessage(newAssetHash)}' of stack '${sanitizeServiceExceptionMessage(candidate.stackName)}'`,
|
|
29768
|
+
action: "copy from it",
|
|
29769
|
+
sink: "copy that directory into the running replicas on a soft reload",
|
|
29770
|
+
wrapError: (message) => new Error(message)
|
|
29771
|
+
});
|
|
29247
29772
|
let oldAssetHash;
|
|
29248
29773
|
const liveReplica = controller.runState.replicas.find((r) => !r.shuttingDown);
|
|
29249
29774
|
if (liveReplica?.lastDeployedAssetHash !== void 0) oldAssetHash = liveReplica.lastDeployedAssetHash;
|
|
@@ -29591,6 +30116,7 @@ async function buildFrontDoor(plan, options, logger, extraStateProviders) {
|
|
|
29591
30116
|
...action,
|
|
29592
30117
|
auth: authForGuard(guard)
|
|
29593
30118
|
} : action;
|
|
30119
|
+
let bootingLambda;
|
|
29594
30120
|
try {
|
|
29595
30121
|
for (const listener of plan.listeners) {
|
|
29596
30122
|
const defaultRoute = listener.defaultAction ? attachAuth(toRouteAction(listener.defaultAction), listener.defaultAuthGuard) : void 0;
|
|
@@ -29641,12 +30167,15 @@ async function buildFrontDoor(plan, options, logger, extraStateProviders) {
|
|
|
29641
30167
|
if (!listener.defaultAction) logger.info(" (no default action: unmatched requests return 404)");
|
|
29642
30168
|
}
|
|
29643
30169
|
for (const runner of lambdaRegistry.values()) {
|
|
29644
|
-
|
|
30170
|
+
bootingLambda = flattenToOneLine(runner.logicalId);
|
|
30171
|
+
logger.info(`Booting Lambda target '${bootingLambda}' behind the ALB front-door...`);
|
|
29645
30172
|
await runner.start();
|
|
29646
30173
|
}
|
|
30174
|
+
bootingLambda = void 0;
|
|
29647
30175
|
} catch (err) {
|
|
29648
30176
|
await Promise.allSettled(servers.map((s) => s.close()));
|
|
29649
30177
|
await Promise.allSettled([...lambdaRegistry.values()].map((r) => r.stop()));
|
|
30178
|
+
if (bootingLambda !== void 0) throw new LocalStartServiceError(`Failed to boot ALB Lambda target '${bootingLambda}': ${err instanceof Error ? err.message : String(err)}`);
|
|
29650
30179
|
throw new LocalStartServiceError(`Failed to start ALB front-door: ${err instanceof Error ? err.message : String(err)}. If a listener port is privileged (< 1024), remap it to a non-privileged host port with --lb-port <listenerPort>=<hostPort> (e.g. --lb-port 80=8080).`);
|
|
29651
30180
|
}
|
|
29652
30181
|
const frontDoorByService = /* @__PURE__ */ new Map();
|
|
@@ -32112,12 +32641,17 @@ function resolveOrigins(dc, template, stack, overrides) {
|
|
|
32112
32641
|
continue;
|
|
32113
32642
|
}
|
|
32114
32643
|
const localDirs = bucketLogicalId && manifest ? resolveBucketDeploymentDirs(template, manifest, stack, bucketLogicalId) : [];
|
|
32115
|
-
if (localDirs.length > 0)
|
|
32116
|
-
|
|
32117
|
-
|
|
32118
|
-
|
|
32119
|
-
|
|
32120
|
-
|
|
32644
|
+
if (localDirs.length > 0) {
|
|
32645
|
+
const { assetOutdir } = assetPathDirs(stack);
|
|
32646
|
+
const hideDotfilesIn = localDirs.filter((d) => absoluteAssemblyPathEscape(assetOutdir, d) !== void 0);
|
|
32647
|
+
out.set(originId, {
|
|
32648
|
+
kind: "s3",
|
|
32649
|
+
originId,
|
|
32650
|
+
localDirs,
|
|
32651
|
+
fromAssembly: true,
|
|
32652
|
+
...hideDotfilesIn.length > 0 && { hideDotfilesIn }
|
|
32653
|
+
});
|
|
32654
|
+
} else {
|
|
32121
32655
|
const literalBucket = bucketLogicalId === void 0 ? s3Domain.bucketName : void 0;
|
|
32122
32656
|
out.set(originId, {
|
|
32123
32657
|
kind: "s3-unresolved",
|
|
@@ -32192,8 +32726,8 @@ function loadStackManifest(stack) {
|
|
|
32192
32726
|
* object key cannot be matched to a manifest file asset.
|
|
32193
32727
|
*/
|
|
32194
32728
|
function resolveBucketDeploymentDirs(template, manifest, stack, bucketLogicalId) {
|
|
32195
|
-
|
|
32196
|
-
|
|
32729
|
+
if (!stack.assetManifestPath) return [];
|
|
32730
|
+
const { manifestDir, assetOutdir } = assetPathDirs(stack);
|
|
32197
32731
|
const dirs = [];
|
|
32198
32732
|
for (const resource of Object.values(template.Resources ?? {})) {
|
|
32199
32733
|
if (!String(resource.Type).startsWith("Custom::CDKBucketDeployment")) continue;
|
|
@@ -32204,7 +32738,17 @@ function resolveBucketDeploymentDirs(template, manifest, stack, bucketLogicalId)
|
|
|
32204
32738
|
if (typeof key !== "string") continue;
|
|
32205
32739
|
const asset = findFileAssetByObjectKey(manifest, key);
|
|
32206
32740
|
if (!asset) continue;
|
|
32207
|
-
dirs.push(
|
|
32741
|
+
dirs.push(resolveAssetSourcePath({
|
|
32742
|
+
manifestDir,
|
|
32743
|
+
value: asset.source.path,
|
|
32744
|
+
assetOutdir,
|
|
32745
|
+
absolute: "honour-warn",
|
|
32746
|
+
field: "source.path",
|
|
32747
|
+
subject: `BucketDeployment source asset for bucket '${sanitizeServiceExceptionMessage(bucketLogicalId)}'`,
|
|
32748
|
+
action: "serve it",
|
|
32749
|
+
sink: "serve every file under that directory as this bucket's local S3 origin",
|
|
32750
|
+
wrapError: (message) => new Error(message)
|
|
32751
|
+
}));
|
|
32208
32752
|
}
|
|
32209
32753
|
}
|
|
32210
32754
|
return dirs;
|
|
@@ -32641,14 +33185,16 @@ function normalizeHeaders(headers) {
|
|
|
32641
33185
|
*/
|
|
32642
33186
|
function serveFromStaticOrigin(input) {
|
|
32643
33187
|
const key = uriToKey(input.uri, input.defaultRootObject);
|
|
32644
|
-
const
|
|
33188
|
+
const containLinks = input.containLinks === true;
|
|
33189
|
+
const hideIn = new Set(input.hideDotfilesIn ?? []);
|
|
33190
|
+
const direct = readKey(input.localDirs, key, containLinks, hideIn);
|
|
32645
33191
|
if (direct) return {
|
|
32646
33192
|
statusCode: 200,
|
|
32647
33193
|
headers: { "content-type": contentTypeForKey(key) },
|
|
32648
33194
|
body: direct
|
|
32649
33195
|
};
|
|
32650
33196
|
for (const candidate of resolveErrorResponseCandidates(input.customErrorResponses)) {
|
|
32651
|
-
const body = readKey(input.localDirs, candidate.errorKey);
|
|
33197
|
+
const body = readKey(input.localDirs, candidate.errorKey, containLinks, hideIn);
|
|
32652
33198
|
if (body) return {
|
|
32653
33199
|
statusCode: candidate.responseCode,
|
|
32654
33200
|
headers: { "content-type": contentTypeForKey(candidate.errorKey) },
|
|
@@ -32702,15 +33248,30 @@ function uriToKey(uri, defaultRootObject) {
|
|
|
32702
33248
|
}
|
|
32703
33249
|
/**
|
|
32704
33250
|
* Read a key from the first directory that contains it as a regular file.
|
|
32705
|
-
* Path-traversal safe:
|
|
32706
|
-
*
|
|
32707
|
-
*
|
|
33251
|
+
* Path-traversal safe: a `../` in the key never leaves the origin directory
|
|
33252
|
+
* (`safeJoin`, always), and with `containLinks` neither does a symlink.
|
|
33253
|
+
* Returns `undefined` when no directory has the key.
|
|
33254
|
+
*
|
|
33255
|
+
* The symlink half is {@link realPathInsideRoot}, not `safeJoin`: `safeJoin`
|
|
33256
|
+
* judges the path's TEXT, and `statSync` / `readFileSync` follow links, so a
|
|
33257
|
+
* `cdk.out/asset.x/index.html -> ~/.aws/credentials` inside a contained origin
|
|
33258
|
+
* directory used to be served (go-to-k/cdk-local#745).
|
|
32708
33259
|
*/
|
|
32709
|
-
function readKey(localDirs, key) {
|
|
33260
|
+
function readKey(localDirs, key, containLinks, hideDotfilesIn) {
|
|
32710
33261
|
if (key === "") return void 0;
|
|
32711
33262
|
for (const dir of localDirs) {
|
|
32712
|
-
const
|
|
32713
|
-
if (!
|
|
33263
|
+
const joined = safeJoin(dir, key);
|
|
33264
|
+
if (!joined) continue;
|
|
33265
|
+
let resolved = joined;
|
|
33266
|
+
if (containLinks) {
|
|
33267
|
+
const real = realPathInsideRoot(dir, joined);
|
|
33268
|
+
if (real === false) continue;
|
|
33269
|
+
if (real !== void 0) resolved = real;
|
|
33270
|
+
}
|
|
33271
|
+
if (hideDotfilesIn.has(dir) && isHiddenKey(dir, key, resolved)) {
|
|
33272
|
+
if (isExistingFile(resolved)) warnHiddenKey(dir, key, resolved);
|
|
33273
|
+
continue;
|
|
33274
|
+
}
|
|
32714
33275
|
try {
|
|
32715
33276
|
if (statSync(resolved).isFile()) return readFileSync(resolved);
|
|
32716
33277
|
} catch {}
|
|
@@ -32727,6 +33288,68 @@ function safeJoin(dir, key) {
|
|
|
32727
33288
|
if (candidate !== root && !candidate.startsWith(rootWithSep)) return void 0;
|
|
32728
33289
|
return candidate;
|
|
32729
33290
|
}
|
|
33291
|
+
/**
|
|
33292
|
+
* `file`'s REAL path (every symbolic link followed by the kernel) when it lies
|
|
33293
|
+
* inside `root`'s real path; `false` when it leaves it, with a warning once per
|
|
33294
|
+
* file; `undefined` when either does not resolve — the read after it fails the
|
|
33295
|
+
* same way, so it reaches nothing.
|
|
33296
|
+
*/
|
|
33297
|
+
function realPathInsideRoot(root, file) {
|
|
33298
|
+
let realFile;
|
|
33299
|
+
let realRoot;
|
|
33300
|
+
try {
|
|
33301
|
+
realFile = realpathSync.native(file);
|
|
33302
|
+
realRoot = realpathSync.native(root);
|
|
33303
|
+
} catch {
|
|
33304
|
+
return;
|
|
33305
|
+
}
|
|
33306
|
+
const rel = relative(realRoot, realFile);
|
|
33307
|
+
if (rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel)) return realFile;
|
|
33308
|
+
if (!warnedEscapes.has(file)) {
|
|
33309
|
+
warnedEscapes.add(file);
|
|
33310
|
+
getLogger().warn(`Not serving '${flattenToOneLine(file)}': it is a symbolic link to '${flattenToOneLine(realFile)}', outside the origin directory '${flattenToOneLine(root)}' the asset manifest named. Answering as if the key did not exist.`);
|
|
33311
|
+
}
|
|
33312
|
+
return false;
|
|
33313
|
+
}
|
|
33314
|
+
/** Files already warned about; a browser re-requests the same key. */
|
|
33315
|
+
const warnedEscapes = /* @__PURE__ */ new Set();
|
|
33316
|
+
/**
|
|
33317
|
+
* Whether a key under an accepted absolute `--no-staging` folder names a
|
|
33318
|
+
* hidden entry — on the request key itself, or on the file's REAL path relative
|
|
33319
|
+
* to the folder's real path (a link inside the folder pointing at `.env`).
|
|
33320
|
+
* `.well-known` is the one hidden name served, for parity with a deployed
|
|
33321
|
+
* bucket's ACME / app-association files. The caller warns, and only for an
|
|
33322
|
+
* entry that exists.
|
|
33323
|
+
*/
|
|
33324
|
+
function isHiddenKey(dir, key, resolved) {
|
|
33325
|
+
const hidden = (rel) => rel.split(/[\\/]/).some((c) => c.startsWith(".") && c !== "." && c !== ".." && c !== ".well-known");
|
|
33326
|
+
let realRel = "";
|
|
33327
|
+
try {
|
|
33328
|
+
realRel = relative(realpathSync.native(dir), realpathSync.native(resolved));
|
|
33329
|
+
} catch {
|
|
33330
|
+
realRel = "";
|
|
33331
|
+
}
|
|
33332
|
+
return hidden(key) || hidden(realRel);
|
|
33333
|
+
}
|
|
33334
|
+
function isExistingFile(p) {
|
|
33335
|
+
try {
|
|
33336
|
+
return statSync(p).isFile();
|
|
33337
|
+
} catch {
|
|
33338
|
+
return false;
|
|
33339
|
+
}
|
|
33340
|
+
}
|
|
33341
|
+
/**
|
|
33342
|
+
* Warn once per withheld FILE. Keyed on the resolved path, not the request
|
|
33343
|
+
* key: `x/../.env`, `xx/../.env`, ... are unbounded spellings of one file, and
|
|
33344
|
+
* keying on them let any client grow this set and the log without limit.
|
|
33345
|
+
*/
|
|
33346
|
+
function warnHiddenKey(dir, key, resolved) {
|
|
33347
|
+
if (warnedHiddenKeys.has(resolved)) return;
|
|
33348
|
+
warnedHiddenKeys.add(resolved);
|
|
33349
|
+
getLogger().warn(`Not serving '${flattenToOneLine(key)}' from '${flattenToOneLine(dir)}': it is a hidden entry in a cdk synth --no-staging source folder (your own tree, not a staged asset). Answering as if the key did not exist.`);
|
|
33350
|
+
}
|
|
33351
|
+
/** Hidden keys already warned about. */
|
|
33352
|
+
const warnedHiddenKeys = /* @__PURE__ */ new Set();
|
|
32730
33353
|
function stripLeadingSlash(s) {
|
|
32731
33354
|
return s.startsWith("/") ? s.replace(/^\/+/, "") : s;
|
|
32732
33355
|
}
|
|
@@ -33093,6 +33716,8 @@ async function serveFromOrigin(origin, args) {
|
|
|
33093
33716
|
if (origin.kind !== "s3") return void 0;
|
|
33094
33717
|
const result = serveFromStaticOrigin({
|
|
33095
33718
|
localDirs: origin.localDirs,
|
|
33719
|
+
...origin.fromAssembly === true && { containLinks: true },
|
|
33720
|
+
...origin.hideDotfilesIn !== void 0 && { hideDotfilesIn: origin.hideDotfilesIn },
|
|
33096
33721
|
uri: args.uri,
|
|
33097
33722
|
...distribution.defaultRootObject !== void 0 && { defaultRootObject: distribution.defaultRootObject },
|
|
33098
33723
|
customErrorResponses: distribution.customErrorResponses
|
|
@@ -41059,4 +41684,4 @@ function addStudioSpecificOptions(cmd) {
|
|
|
41059
41684
|
|
|
41060
41685
|
//#endregion
|
|
41061
41686
|
export { applyEdgeResponseResult as $, buildJwksUrlFromIssuer as $n, resolveCfnStackName as $r, buildCloudMapIndex as $t, startAgentCoreHttpServer as A, describeCredentialLoadFailure as Ai, classifySourceChange as An, ConnectionRegistry as Ar, addRunTaskSpecificOptions as At, idFromArn as B, buildStageMap as Bn, resolveRuntimeFileExtension as Br, resolveEcsAssumeRoleOption as Bt, addListSpecificOptions as C, resolveAgentCoreTarget as Ci, waitForAgentCorePing as Cn, tryParseStatus as Cr, parseLbPortOverrides as Ct, createLocalStartAgentCoreCommand as D, tryResolveImageFnJoin as Di, computeCodeImageTag as Dn, probeHostGatewaySupport as Dr, addStartServiceSpecificOptions as Dt, addStartAgentCoreSpecificOptions as E, substituteImagePlaceholders as Ei, buildAgentCoreCodeImage as En, HOST_GATEWAY_MIN_VERSION as Er, resolveAlbFrontDoor as Et, createLocalStartCloudFrontCommand as F, createWatchPredicates as Fn, buildDisconnectEvent as Fr, addImageOverrideOptions as Ft, classifyS3Error as G, filterRoutesByApiIdentifiers as Gn, substituteEnvVarsFromState as Gr, enforceImageOverrideOrphans as Gt, createDeployedKvsDataSource as H, resolveEnvVars$1 as Hn, EcsTaskResolutionError as Hr, runEcsServiceEmulator as Ht, normalizeKvsFileKeys as I, resolveApiTargetSubset as In, buildMessageEvent as Ir, buildEcsImageResolutionContext$1 as It, startCloudFrontServer as J, startApiServer as Jn, createLocalStateProvider as Jr, resolveImageOverrides as Jt, createS3OriginReader as K, groupRoutesByServer as Kn, substituteEnvVarsFromStateAsync as Kr, mergeForService as Kt, parseKvsFileOverrides as L, createAuthorizerCache as Ln, architectureToPlatform as Lr, ecsClusterOption as Lt, startAgentCoreWsBridge as M, resolveProfileCredentials as Mi, createLocalInvokeCommand as Mn, handleConnectionsRequest as Mr, MAX_TASKS_SUBNET_RANGE_CAP as Mt, LocalStartCloudFrontError as N, buildProxyClientConfig as Ni, addStartApiSpecificOptions as Nn, parseConnectionsPath as Nr, addCommonEcsServiceOptions as Nt, buildAgentCoreServeAuthCheck as O, LocalInvokeBuildError as Oi, renderCodeDockerfile as On, resolveHostGatewayExtraHosts as Or, createLocalStartServiceCommand as Ot, addStartCloudFrontSpecificOptions as P, isProxyEnvConfigured as Pi, createLocalStartApiCommand as Pn, buildConnectEvent as Pr, addEcsAssumeRoleOptions as Pt, applyEdgeRequestResult as Q, buildCognitoJwksUrl as Qn, resolveCfnRegion as Qr, listPinnedTargets as Qt, parseOriginOverrides as R, createFileWatcher as Rn, buildContainerImage as Rr, parseMaxTasks as Rt, StudioEventBus as S, pickAgentCoreCandidateStack as Si, waitForAgentCoreHttpReady as Sn, selectIntegrationResponse as Sr, createLocalStartAlbCommand as St, formatTargetListing as T, formatStateRemedy as Ti, SUPPORTED_CODE_RUNTIMES as Tn, HOST_DOCKER_INTERNAL_GATEWAY as Tr, isApplicationLoadBalancer as Tt, resolveDeployedKvsArnByName as U, availableApiIdentifiers as Un, substituteAgainstState as Ur, ImageOverrideError as Ut, resolveKvsModulesForDistribution as V, materializeLayerFromArn as Vn, resolveRuntimeImage as Vr, resolveSharedSidecarCredentials as Vt, resolveDeployedOriginBucket as W, filterRoutesByApiIdentifier as Wn, substituteAgainstStateAsync as Wr, buildImageOverrideTag as Wt, serveFromStaticOrigin as X, resolveServiceIntegrationParameters as Xn, rejectExplicitCfnStackWithMultipleStacks as Xr, describePinnedImageUri as Xt, resolveErrorResponseCandidates as Y, resolveSelectionExpression as Yn, isCfnFlagPresent as Yr, runImageOverrideBuilds as Yt, serveLambdaUrlOrigin as Z, defaultCredentialsLoader as Zn, resolveCfnFallbackRegion as Zr, isLocalCdkAssetImage as Zt, filterStudioTargetGroups as _, AGENTCORE_AGUI_PROTOCOL as _i, parseSseForJsonRpc as _n, applyAuthorizerOverlay as _r, createCloudFrontModule as _t, createLocalStudioCommand as a, countTargets as ai, attachContainerLogStreamer as an, computeRequestIdentityHash as ar, describeS3OriginDomain as at, renderStudioHtml as b, AGENTCORE_RUNTIME_TYPE as bi, AGENTCORE_SESSION_ID_HEADER as bn, evaluateResponseParameters as br, addAlbSpecificOptions as bt, startStudioProxy as c, discoverWebSocketApis as ci, bridgeAgentCoreWs as cn, invokeTokenAuthorizer as cr, pickFunctionUrlLogicalIdFromOrigin as ct, createStudioDispatcher as d, parseSelectionExpressionPath as di, A2A_PATH as dn, buildCorsConfigByApiId as dr, pickTargetFunctionLogicalId as dt, CfnLocalStateProvider as ei, CloudMapRegistry as en, createJwksCache as er, buildEdgeRequestEvent as et, filterStudioCustomResources as f, webSocketApiMatchesIdentifier as fi, a2aInvokeOnce as fn, buildCorsConfigFromCloudFrontChain as fr, resolveCloudFrontDistribution as ft, annotatePinnedEcsTargets as g, AGENTCORE_A2A_PROTOCOL as gi, mcpInvokeOnce as gn, translateLambdaResponse as gr, stripCloudFrontImport as gt, annotateEcsTaskPinnedTargets as h, resolveLambdaArnIntrinsic as hi, MCP_PROTOCOL_VERSION as hn, matchRoute as hr, runViewerResponse as ht, coerceStopRequest as i, resolveSingleTarget as ii, getContainerNetworkIp as in, buildMethodArn as ir, CLOUDFRONT_DISTRIBUTION_TYPE as it, attachAgentCoreWsBridge as j, buildStsClientConfig as ji, addInvokeSpecificOptions as jn, buildMgmtEndpointEnvUrl as jr, createLocalRunTaskCommand as jt, selectServeInboundAuth as k, describeAwsFailureForWarn as ki, toCmdArgv as kn, bufferToBody as kr, serviceStrategy as kt, relayServeRequest as l, discoverWebSocketApisOrThrow as li, invokeAgentCoreWs as ln, attachAuthorizers as lr, pickKvsLogicalIdFromArn as lt, annotateAlbPinnedBackingServices as m, pickRefLogicalId as mi, MCP_PATH as mn, matchPreflight as mr, runViewerRequest as mt, coerceRunRequest as n, resolveSsmParameters as ni, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as nn, verifyJwtAuthorizer as nr, edgeHeadersToHttp as nt, resolveServeBaseUrl as o, listTargets as oi, addInvokeAgentCoreSpecificOptions as on, evaluateCachedLambdaPolicy as or, extractKvsAssociations as ot, isCustomResourceLambdaTarget as p, discoverRoutes as pi, MCP_CONTAINER_PORT as pn, isFunctionUrlOacFronted as pr, compileCloudFrontFunction as pt, matchBehavior as q, readMtlsMaterialsFromDisk as qn, LocalStateSourceError as qr, parseImageOverrideFlags as qt, coerceServeRequest as r, resolveWatchConfig as ri, setShadowReadyTimeoutMs as rn, verifyJwtViaDiscovery as rr, httpHeadersToEdge as rt, createStudioServeManager as s, availableWebSocketApiIdentifiers as si, createLocalInvokeAgentCoreCommand as sn, invokeRequestAuthorizer as sr, isCloudFrontDistribution as st, addStudioSpecificOptions as t, collectSsmParameterRefs as ti, DEFAULT_SHADOW_READY_TIMEOUT_MS as tn, verifyCognitoJwt as tr, buildEdgeResponseEvent as tt, reinvoke as u, filterWebSocketApisByIdentifiers as ui, A2A_CONTAINER_PORT as un, applyCorsResponseHeaders as ur, pickLambdaEdgeFunctionLogicalId as ut, startStudioServer as v, AGENTCORE_HTTP_PROTOCOL as vi, AGENTCORE_SIGV4_SERVICE as vn, buildHttpApiV2Event as vr, createLocalFileKvsDataSource as vt, createLocalListCommand as w, derivePseudoParametersFromRegion as wi, downloadAndExtractS3Bundle as wn, VtlEvaluationError as wr, resolveAlbTarget as wt, createStudioStore as x, AgentCoreResolutionError as xi, invokeAgentCore as xn, pickResponseTemplate as xr, albStrategy as xt, toStudioTargetGroups as y, AGENTCORE_MCP_PROTOCOL as yi, signAgentCoreInvocation as yn, buildRestV1Event as yr, createUnboundCloudFrontModule as yt, resolveCloudFrontTarget as z, attachStageContext as zn, resolveRuntimeCodeMountPath as zr, parseRestartPolicy as zt };
|
|
41062
|
-
//# sourceMappingURL=local-studio-
|
|
41687
|
+
//# sourceMappingURL=local-studio-BbLfdSy4.js.map
|