@go-to-k/cdkd 0.290.42 → 0.290.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{asg-provider-Cms9Daqn.js → asg-provider-BwN09Zcn.js} +3 -3
- package/dist/{asg-provider-Cms9Daqn.js.map → asg-provider-BwN09Zcn.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-C1dgx124.js → deploy-engine-CMD4BtQe.js} +767 -38
- package/dist/deploy-engine-CMD4BtQe.js.map +1 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{logger-D-2HeLmd.js → logger-DjBU2Ap3.js} +10 -1
- package/dist/logger-DjBU2Ap3.js.map +1 -0
- package/dist/{program-HtHlEaZm.js → program-CGhtrsw6.js} +278 -151
- package/dist/{program-HtHlEaZm.js.map → program-CGhtrsw6.js.map} +1 -1
- package/dist/{version-Divizw1k.js → version-4uhXCFhs.js} +2 -2
- package/dist/{version-Divizw1k.js.map → version-4uhXCFhs.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-C1dgx124.js.map +0 -1
- package/dist/logger-D-2HeLmd.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { C as displayAwsMessage, D as truncateCodePoints, E as isPasteableIdent, S as UNRENDERABLE, T as displaySafe, d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, w as displayIdent, x as STACK_REF_MAX_CODE_POINTS, y as withoutGeneratedFallbackName } from "./logger-
|
|
1
|
+
import { C as displayAwsMessage, D as truncateCodePoints, E as isPasteableIdent, S as UNRENDERABLE, T as displaySafe, d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, w as displayIdent, x as STACK_REF_MAX_CODE_POINTS, y as withoutGeneratedFallbackName } from "./logger-DjBU2Ap3.js";
|
|
2
2
|
import { a as setAssumedRoleCredentials, i as getPreAssumeEnvCredentials, n as awsClientDefaults, o as setPreAssumeEnvCredentials, r as getAssumedRoleCredentials, t as SECOND_ROLE_PUBLISH_REFUSAL } from "./aws-client-defaults-BoIxyCir.js";
|
|
3
|
-
import { t as getCdkdVersion } from "./version-
|
|
3
|
+
import { t as getCdkdVersion } from "./version-4uhXCFhs.js";
|
|
4
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
6
|
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -25,7 +25,9 @@ import { CreateClusterSnapshotCommand, DescribeClusterSnapshotsCommand, Describe
|
|
|
25
25
|
import { CreateSnapshotCommand as CreateSnapshotCommand$1, DescribeReplicationGroupsCommand, DescribeSnapshotsCommand as DescribeSnapshotsCommand$1, ElastiCacheClient } from "@aws-sdk/client-elasticache";
|
|
26
26
|
import { ACMClient } from "@aws-sdk/client-acm";
|
|
27
27
|
import { LambdaMicrovmsClient } from "@aws-sdk/client-lambda-microvms";
|
|
28
|
+
import * as fs from "node:fs";
|
|
28
29
|
import { createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
30
|
+
import * as path from "node:path";
|
|
29
31
|
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
30
32
|
import { spawn } from "node:child_process";
|
|
31
33
|
import { homedir, tmpdir } from "node:os";
|
|
@@ -33,7 +35,6 @@ import { GetHostedZoneCommand, ListHostedZonesByNameCommand, Route53Client } fro
|
|
|
33
35
|
import { DescribeListenersCommand, DescribeLoadBalancersCommand, ElasticLoadBalancingV2Client } from "@aws-sdk/client-elastic-load-balancing-v2";
|
|
34
36
|
import { KMSClient, ListAliasesCommand } from "@aws-sdk/client-kms";
|
|
35
37
|
import { readFile } from "fs/promises";
|
|
36
|
-
import { join as join$1 } from "path";
|
|
37
38
|
import { CreateRepositoryCommand, DescribeImagesCommand as DescribeImagesCommand$1, DescribeRepositoriesCommand, ECRClient, GetAuthorizationTokenCommand, PutImageTagMutabilityCommand } from "@aws-sdk/client-ecr";
|
|
38
39
|
import { inspect } from "node:util";
|
|
39
40
|
import { hostname } from "os";
|
|
@@ -2803,6 +2804,316 @@ function parseEnvironment(env) {
|
|
|
2803
2804
|
};
|
|
2804
2805
|
}
|
|
2805
2806
|
|
|
2807
|
+
//#endregion
|
|
2808
|
+
//#region src/utils/assembly-path.ts
|
|
2809
|
+
/**
|
|
2810
|
+
* `true` when `candidate` names something strictly beneath `base`, both given
|
|
2811
|
+
* as already-resolved absolute paths.
|
|
2812
|
+
*
|
|
2813
|
+
* The `..` test is SEPARATOR-AWARE rather than a bare `startsWith('..')`,
|
|
2814
|
+
* which would also reject a legitimate sibling named `..foo`. The empty-string
|
|
2815
|
+
* case is `base` itself: a directory is never a template, a manifest or a
|
|
2816
|
+
* metadata side file, so `.` and `./` are refused rather than read.
|
|
2817
|
+
*/
|
|
2818
|
+
function isInside(base, candidate) {
|
|
2819
|
+
const rel = path.relative(base, candidate);
|
|
2820
|
+
if (rel === "" || rel === "..") return false;
|
|
2821
|
+
if (rel.startsWith(`..${path.sep}`)) return false;
|
|
2822
|
+
return !path.isAbsolute(rel);
|
|
2823
|
+
}
|
|
2824
|
+
/**
|
|
2825
|
+
* `realpath(3)`, or `undefined` for a path that does not fully resolve.
|
|
2826
|
+
*
|
|
2827
|
+
* `.native` IS load-bearing and must not be simplified to `fs.realpathSync`.
|
|
2828
|
+
* The plain form is a JavaScript walker that folds `..` LEXICALLY — the same
|
|
2829
|
+
* mistake this module's own model branch is careful not to make — so it
|
|
2830
|
+
* disagrees with the kernel on a target carrying a `..` after a symlinked
|
|
2831
|
+
* component, in both directions:
|
|
2832
|
+
*
|
|
2833
|
+
* - It answers `ENOENT` for a path the kernel resolves and `readFileSync`
|
|
2834
|
+
* then reads. With `cdk.out/a -> <outside>/sub` and
|
|
2835
|
+
* `cdk.out/LINK.json -> a/../c.json` (both live), the JS walker folds to
|
|
2836
|
+
* `cdk.out/c.json`, finds nothing, and the verdict falls through to the
|
|
2837
|
+
* model — which folds the same way and calls it CONTAINED while the read
|
|
2838
|
+
* returns `<outside>/c.json`. That is go-to-k/cdkd#3489's original defect,
|
|
2839
|
+
* reopened by the guard meant to close it.
|
|
2840
|
+
* - On a case-insensitive filesystem (APFS by default) the same shape can make
|
|
2841
|
+
* it SPIN. A `try`/`catch` cannot catch a spin, so a hand-modified assembly
|
|
2842
|
+
* hung the very commands this check protects.
|
|
2843
|
+
*
|
|
2844
|
+
* `.native` is libuv's `realpath(3)` (`GetFinalPathNameByHandle` on Windows),
|
|
2845
|
+
* available since Node 8 against a floor of 22, and throws the same
|
|
2846
|
+
* `ENOENT` / `ELOOP` / `EACCES`. Using it is what makes the "exact whenever
|
|
2847
|
+
* the path resolves" claim above TRUE rather than aspirational.
|
|
2848
|
+
*/
|
|
2849
|
+
function tryRealpath(p) {
|
|
2850
|
+
try {
|
|
2851
|
+
return fs.realpathSync.native(p);
|
|
2852
|
+
} catch {
|
|
2853
|
+
return;
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
/** `fs.readlinkSync`, or `undefined` when `p` is not a symbolic link. */
|
|
2857
|
+
function tryReadlink(p) {
|
|
2858
|
+
try {
|
|
2859
|
+
return fs.readlinkSync(p);
|
|
2860
|
+
} catch {
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Link follows before the walk gives up and reports the path as unresolvable.
|
|
2866
|
+
* It does NOT refuse: an exhausted budget answers `undefined`, which leaves
|
|
2867
|
+
* the symlink arm silent and the lexical verdict standing. That is safe
|
|
2868
|
+
* because the OS gives up first — macOS caps at 32 (`ELOOP`) — so a chain that
|
|
2869
|
+
* reaches this cap is one nothing can read or create through anyway.
|
|
2870
|
+
*/
|
|
2871
|
+
const MAX_LINK_HOPS = 40;
|
|
2872
|
+
/**
|
|
2873
|
+
* Unresolvable path COMPONENTS the climb walks before giving up. Bounds the
|
|
2874
|
+
* recursion below, which is one frame per component: 20 000 components raised
|
|
2875
|
+
* an uncaught `RangeError` and 5 000 cost ~200 ms per call. Like the hop cap
|
|
2876
|
+
* this gives up rather than refusing, which is safe for the same reason —
|
|
2877
|
+
* nothing can read or create through a path that deep.
|
|
2878
|
+
*/
|
|
2879
|
+
const MAX_PATH_COMPONENTS = 1e3;
|
|
2880
|
+
/**
|
|
2881
|
+
* Where `target` REALLY points, for a path that may not exist yet.
|
|
2882
|
+
*
|
|
2883
|
+
* `fs.realpathSync` answers only for a path that fully resolves, and it throws
|
|
2884
|
+
* `ENOENT` for a DANGLING symbolic link exactly as it does for an absent file
|
|
2885
|
+
* — the two are indistinguishable from its result. That difference is the
|
|
2886
|
+
* whole point here: a write FOLLOWS a dangling link and creates the file at
|
|
2887
|
+
* its target, so `cdk.out/Foo.template.json -> ~/.ssh/authorized_keys` (target
|
|
2888
|
+
* absent) is an out-of-directory write that a realpath-only check reports as
|
|
2889
|
+
* contained. Measured: `cdkd synth --verbose` created the victim file.
|
|
2890
|
+
*
|
|
2891
|
+
* So each unresolvable component is handled by hand: climb to the deepest
|
|
2892
|
+
* ancestor that DOES resolve, then re-apply the remaining components,
|
|
2893
|
+
* following any symbolic link with `readlink` and re-resolving its target.
|
|
2894
|
+
* `undefined` means the walk could not resolve the path at all, which for
|
|
2895
|
+
* every caller means their own open fails too.
|
|
2896
|
+
*
|
|
2897
|
+
* KNOW WHAT THIS IS EXACT ABOUT, because the distinction is what bounds it:
|
|
2898
|
+
*
|
|
2899
|
+
* - For a path that FULLY RESOLVES, the answer is `fs.realpathSync`'s and is
|
|
2900
|
+
* therefore exact. Every READ site is in this case by construction — a file
|
|
2901
|
+
* that does not resolve cannot be read — so the containment verdict there is
|
|
2902
|
+
* the kernel's, not this function's. The BASE side is exact there too: every
|
|
2903
|
+
* one is either user-supplied (`-a`, `--output`, `cdkOutDir`) or the
|
|
2904
|
+
* `dirname` of a file cdkd has just read successfully, so neither operand of
|
|
2905
|
+
* a read is in the model's territory.
|
|
2906
|
+
* - For a path that does NOT exist yet, this is a best-effort MODEL of kernel
|
|
2907
|
+
* resolution, and a model can be wrong at an edge. The known one: `..` INSIDE
|
|
2908
|
+
* an unresolvable link's target is folded lexically here, while the kernel
|
|
2909
|
+
* folds it only after following each preceding component, so a target of
|
|
2910
|
+
* `a/../c.json` under a symlinked `a` diverges.
|
|
2911
|
+
*
|
|
2912
|
+
* That edge decides which of the TWO sites that WRITE needs a second check,
|
|
2913
|
+
* and the difference is the BASE rather than the act of writing:
|
|
2914
|
+
*
|
|
2915
|
+
* - `resolveVerboseTemplatePath` (`src/cli/commands/synth.ts`) writes into
|
|
2916
|
+
* `--output`, which may be a pre-existing directory the assembly's author
|
|
2917
|
+
* supplied, so the leaf CAN already be a symbolic link. It therefore asks
|
|
2918
|
+
* the kernel directly with `lstat` and refuses one whatever it points at. A
|
|
2919
|
+
* path-string model can be argued around; `lstat` cannot.
|
|
2920
|
+
* - `resolveInlineCodeFilePath` (`src/cli/commands/local-invoke.ts`) writes
|
|
2921
|
+
* into a `mkdtemp` cdkd has just created, mode 0700 and empty, so no
|
|
2922
|
+
* component of the candidate can be a symbolic link at all and the edge is
|
|
2923
|
+
* unreachable. It needs no `lstat`.
|
|
2924
|
+
*
|
|
2925
|
+
* **A third write site over a base cdkd did not create needs the `lstat`**,
|
|
2926
|
+
* and inherits nothing from this function that would give it one.
|
|
2927
|
+
*
|
|
2928
|
+
* That second site is also the repo's first caller to `mkdirSync(...,
|
|
2929
|
+
* { recursive: true })` through a resolved path, which an earlier revision of
|
|
2930
|
+
* this comment leaned on not existing. It is still safe: the climb charges no
|
|
2931
|
+
* hop for a component, and exhausting `MAX_PATH_COMPONENTS` gives up into the
|
|
2932
|
+
* lexical verdict, which a freshly created directory has no link to defeat.
|
|
2933
|
+
*/
|
|
2934
|
+
function resolveThroughLinks(target, hops = 0, climbs = 0) {
|
|
2935
|
+
const direct = tryRealpath(target);
|
|
2936
|
+
if (direct !== void 0) return direct;
|
|
2937
|
+
if (hops >= MAX_LINK_HOPS) return void 0;
|
|
2938
|
+
if (climbs >= MAX_PATH_COMPONENTS) return void 0;
|
|
2939
|
+
const parent = path.dirname(target);
|
|
2940
|
+
if (parent === target) return void 0;
|
|
2941
|
+
const realParent = resolveThroughLinks(parent, hops, climbs + 1);
|
|
2942
|
+
if (realParent === void 0) return void 0;
|
|
2943
|
+
const link = tryReadlink(target);
|
|
2944
|
+
if (link === void 0) return path.join(realParent, path.basename(target));
|
|
2945
|
+
return resolveThroughLinks(path.resolve(realParent, link), hops + 1, climbs);
|
|
2946
|
+
}
|
|
2947
|
+
/**
|
|
2948
|
+
* Resolve an assembly-supplied `candidate` against `dir` and report whether
|
|
2949
|
+
* the result stays inside it.
|
|
2950
|
+
*
|
|
2951
|
+
* The lexical arm joins exactly the way the call sites used to
|
|
2952
|
+
* (`path.join`, NOT `path.resolve`), so the verdict is about the path the
|
|
2953
|
+
* caller will actually open. `join`'s handling of an absolute candidate makes
|
|
2954
|
+
* this arm strictly more permissive than a `resolve`-based one would be; an
|
|
2955
|
+
* absolute value therefore stays a matter for each site's own tripwire.
|
|
2956
|
+
*
|
|
2957
|
+
* The SYMLINK arm exists because the lexical arm alone leaves an equivalent
|
|
2958
|
+
* hole: `cdk.out/link -> /etc` plus a candidate of `link/passwd` is lexically
|
|
2959
|
+
* contained and still reads `/etc/passwd`. Both sides go through
|
|
2960
|
+
* {@link resolveThroughLinks}, so an assembly directory REACHED through a link
|
|
2961
|
+
* (macOS spells `/tmp` as `/private/tmp`; a user may symlink `cdk.out` itself)
|
|
2962
|
+
* is unaffected, while a candidate that does not exist YET — the shape
|
|
2963
|
+
* `cdkd synth --verbose` writes, and the shape a DANGLING link presents — is
|
|
2964
|
+
* still resolved to where it would actually land. A real `cdk synth` writes no symbolic link
|
|
2965
|
+
* along any of these paths (measured against a CDK 2.268 assembly carrying a
|
|
2966
|
+
* Stage, two nesting levels and an asset manifest), so the arm costs a
|
|
2967
|
+
* legitimate assembly nothing.
|
|
2968
|
+
*
|
|
2969
|
+
* Windows shapes need no special case HERE: `path.relative` is the platform's
|
|
2970
|
+
* own, so a drive-relative or UNC candidate is judged by `path.win32` on
|
|
2971
|
+
* Windows, while on POSIX `C:\evil` and `..\..\evil` are single filename
|
|
2972
|
+
* components that no `readFileSync` can follow out of the directory.
|
|
2973
|
+
*
|
|
2974
|
+
* The verdict is about the assembly AS IT SITS ON DISK, which is the whole
|
|
2975
|
+
* threat model: a hand-modified or third-party `cdk.out` the user then points
|
|
2976
|
+
* cdkd at. It is NOT a defence against a process rewriting that directory
|
|
2977
|
+
* concurrently — the caller opens by path after this returns, so a link
|
|
2978
|
+
* swapped in between is outside what any check here can see.
|
|
2979
|
+
*/
|
|
2980
|
+
function resolveAssemblyPath(dir, candidate, options) {
|
|
2981
|
+
const base = path.resolve(dir);
|
|
2982
|
+
const bound = options?.containWithin === void 0 ? base : path.resolve(options.containWithin);
|
|
2983
|
+
const joined = path.resolve(path.join(base, candidate));
|
|
2984
|
+
if (!isInside(bound, joined)) return {
|
|
2985
|
+
contained: false,
|
|
2986
|
+
escape: "lexical",
|
|
2987
|
+
path: joined
|
|
2988
|
+
};
|
|
2989
|
+
const realBase = resolveThroughLinks(bound);
|
|
2990
|
+
if (realBase !== void 0) {
|
|
2991
|
+
const realTarget = resolveThroughLinks(joined);
|
|
2992
|
+
if (realTarget !== void 0 && !isInside(realBase, realTarget)) return {
|
|
2993
|
+
contained: false,
|
|
2994
|
+
escape: "symlink",
|
|
2995
|
+
path: joined,
|
|
2996
|
+
realPath: realTarget
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
return {
|
|
3000
|
+
contained: true,
|
|
3001
|
+
path: joined
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
3004
|
+
/**
|
|
3005
|
+
* The shared tail of every containment refusal: what the value resolved to,
|
|
3006
|
+
* what it escaped, and why that means the assembly is not CDK-generated. Each
|
|
3007
|
+
* call site supplies its own subject ("Stack 'X' has templateFile='...' which
|
|
3008
|
+
* ") and its own error class, because the sites throw four different types.
|
|
3009
|
+
* `action` completes "Refusing to ..." for a caller that declines something
|
|
3010
|
+
* other than loading the file — `renderNestedTemplateTreeDefect` says "deploy"
|
|
3011
|
+
* or "diff", matching its own sibling refusals.
|
|
3012
|
+
*
|
|
3013
|
+
* Every interpolation goes through `displaySafe` for the reason
|
|
3014
|
+
* `AssemblyReader`'s own refusals give (go-to-k/cdkd#3277): this text exists
|
|
3015
|
+
* FOR a hand-modified assembly, so the candidate, the resolved path and even
|
|
3016
|
+
* `dir` (below a Stage it derives from the manifest's `directoryName`) are all
|
|
3017
|
+
* attacker-chosen, and `formatError` sanitizes only an error's `cause`, never
|
|
3018
|
+
* its own `message`.
|
|
3019
|
+
*/
|
|
3020
|
+
function renderAssemblyPathEscape(escape, dir, action = "load", provenanceOverride) {
|
|
3021
|
+
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}.`;
|
|
3022
|
+
const base = path.resolve(dir);
|
|
3023
|
+
const realBase = resolveThroughLinks(base) ?? base;
|
|
3024
|
+
if (escape.escape === "symlink") {
|
|
3025
|
+
if (escape.realPath === realBase) return `resolves to '${displaySafe(escape.path)}', a symbolic link to the directory '${displaySafe(base)}' itself rather than to a file inside it. ${provenance}`;
|
|
3026
|
+
return `resolves to '${displaySafe(escape.path)}', which leads through a symbolic link to '${displaySafe(escape.realPath)}', outside '${displaySafe(base)}'. ${provenance}`;
|
|
3027
|
+
}
|
|
3028
|
+
if (escape.path === base) return `names the directory '${displaySafe(base)}' itself rather than a file inside it. ${provenance}`;
|
|
3029
|
+
return `resolves to '${displaySafe(escape.path)}', outside '${displaySafe(base)}'. ${provenance}`;
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
//#endregion
|
|
3033
|
+
//#region src/utils/own-keys.ts
|
|
3034
|
+
/**
|
|
3035
|
+
* Own-key helpers for the bags that reach cdkd through `JSON.parse` — a
|
|
3036
|
+
* `state.json` record, a Cloud Control `Properties` document, a template — and
|
|
3037
|
+
* for the raw SDK readbacks compared against them.
|
|
3038
|
+
*
|
|
3039
|
+
* `JSON.parse` yields a key literally named `__proto__` as an ORDINARY own
|
|
3040
|
+
* data property, and every comparator / rebuild walk over such a bag has to
|
|
3041
|
+
* keep treating it as one. Two idioms silently do not:
|
|
3042
|
+
*
|
|
3043
|
+
* - **Rebuilding onto a `{}` literal.** `out[k] = v` with `k === '__proto__'`
|
|
3044
|
+
* runs `Object.prototype`'s accessor, SETS the node's prototype and DROPS
|
|
3045
|
+
* the key — on BOTH comparison sides when the walk is a canonicalizer, so a
|
|
3046
|
+
* drift at that key is invisible (issue
|
|
3047
|
+
* [#3121](https://github.com/go-to-k/cdkd/issues/3121)), and on the WRITE
|
|
3048
|
+
* path the member vanishes from the payload (issue
|
|
3049
|
+
* [#2899](https://github.com/go-to-k/cdkd/issues/2899)). Rebuild onto
|
|
3050
|
+
* {@link nullPrototypeRecord} (no accessor there, so plain assignment
|
|
3051
|
+
* defines an own key) or, onto an existing plain object, define the key
|
|
3052
|
+
* with {@link defineOwnKey}.
|
|
3053
|
+
* - **`key in bag` membership.** `in` reads the prototype chain, so a key
|
|
3054
|
+
* named `constructor` / `toString` answers `true` for every plain object
|
|
3055
|
+
* and the recursion then walks `Object`'s function. {@link hasOwnKey} asks
|
|
3056
|
+
* the bag alone; {@link ownValue} is the matching read.
|
|
3057
|
+
*
|
|
3058
|
+
* And ONE shape test every such walk needs FIRST: {@link hasPlainPrototype}.
|
|
3059
|
+
* A non-plain object (`Date`, `Uint8Array`, `Map`, a class instance) has no
|
|
3060
|
+
* own enumerable keys, so a key-walk over one FABRICATES `{}` (or an index
|
|
3061
|
+
* map) and any two of them compare equal. Such a value is returned by
|
|
3062
|
+
* IDENTITY and compared as itself.
|
|
3063
|
+
*
|
|
3064
|
+
* Extracted from `src/cli/commands/drift.ts` (issue #2899, swept by PR #3124)
|
|
3065
|
+
* so the analyzer-side canonicalizers (#3121) share ONE spelling rather than a
|
|
3066
|
+
* fourth copy. A LEAF module, deliberately, and it must stay one: a value
|
|
3067
|
+
* import from a module other suites `vi.mock` wholesale reds those suites
|
|
3068
|
+
* with a missing-export failure (which is why `secret-redaction.ts` keeps its
|
|
3069
|
+
* own `hasPlainPrototype` — several drift suites mock that module).
|
|
3070
|
+
*/
|
|
3071
|
+
/** Own-key membership: `in` reads the prototype chain, which a `JSON.parse`d bag never means. */
|
|
3072
|
+
function hasOwnKey(target, key) {
|
|
3073
|
+
return Object.prototype.hasOwnProperty.call(target, key);
|
|
3074
|
+
}
|
|
3075
|
+
/** An own-key read: `undefined` for an inherited name (`__proto__`, `constructor`, ...). */
|
|
3076
|
+
function ownValue(target, key) {
|
|
3077
|
+
return hasOwnKey(target, key) ? target[key] : void 0;
|
|
3078
|
+
}
|
|
3079
|
+
/** An ordinary (writable, enumerable, configurable) own data property — never the prototype. */
|
|
3080
|
+
function defineOwnKey(target, key, value) {
|
|
3081
|
+
Object.defineProperty(target, key, {
|
|
3082
|
+
value,
|
|
3083
|
+
writable: true,
|
|
3084
|
+
enumerable: true,
|
|
3085
|
+
configurable: true
|
|
3086
|
+
});
|
|
3087
|
+
}
|
|
3088
|
+
/**
|
|
3089
|
+
* Does this value carry an ORDINARY object prototype?
|
|
3090
|
+
*
|
|
3091
|
+
* `typeof v === 'object'` admits `Date` / `Map` / `Set` / `Uint8Array` / class
|
|
3092
|
+
* instances, whose own enumerable keys are `[]` — so a key-walk over one
|
|
3093
|
+
* reports "no contradiction" between two values that actually differ, and a
|
|
3094
|
+
* rebuild over one fabricates `{}`. A NULL prototype counts as plain: it is
|
|
3095
|
+
* what {@link nullPrototypeRecord} yields, and a walk must be able to re-walk
|
|
3096
|
+
* its own output.
|
|
3097
|
+
*/
|
|
3098
|
+
function hasPlainPrototype$1(value) {
|
|
3099
|
+
const proto = Object.getPrototypeOf(value);
|
|
3100
|
+
return proto === Object.prototype || proto === null;
|
|
3101
|
+
}
|
|
3102
|
+
/**
|
|
3103
|
+
* A fresh rebuild target with NO prototype, so `out['__proto__'] = v` defines
|
|
3104
|
+
* an own key instead of setting the prototype. `JSON.stringify`,
|
|
3105
|
+
* `Object.keys` / `entries` and `structuredClone` all read it like a plain
|
|
3106
|
+
* object; only `instanceof Object` and a direct `.hasOwnProperty()` method call
|
|
3107
|
+
* do not, and no consumer on the drift path uses either.
|
|
3108
|
+
*
|
|
3109
|
+
* The type parameter spares a caller with a narrower value type an `as` cast at
|
|
3110
|
+
* every call — `nullPrototypeRecord<string>()` for a logical-id-to-path index.
|
|
3111
|
+
* It defaults to `unknown`, so a zero-argument call is unchanged.
|
|
3112
|
+
*/
|
|
3113
|
+
function nullPrototypeRecord() {
|
|
3114
|
+
return Object.create(null);
|
|
3115
|
+
}
|
|
3116
|
+
|
|
2806
3117
|
//#endregion
|
|
2807
3118
|
//#region src/synthesis/stack-messages.ts
|
|
2808
3119
|
/**
|
|
@@ -2833,7 +3144,9 @@ const MESSAGE_ENTRY_TYPES = {
|
|
|
2833
3144
|
function collectStackMessages(assemblyDir, artifact) {
|
|
2834
3145
|
const merged = { ...artifact.metadata ?? {} };
|
|
2835
3146
|
if (artifact.additionalMetadataFile) {
|
|
2836
|
-
const
|
|
3147
|
+
const resolved = resolveAssemblyPath(assemblyDir, artifact.additionalMetadataFile);
|
|
3148
|
+
if (!resolved.contained) throw new SynthesisError(`Stack metadata file '${displaySafe(artifact.additionalMetadataFile)}' ${renderAssemblyPathEscape(resolved, assemblyDir)}`);
|
|
3149
|
+
const metadataPath = resolved.path;
|
|
2837
3150
|
let sideFile;
|
|
2838
3151
|
try {
|
|
2839
3152
|
sideFile = JSON.parse(readFileSync(metadataPath, "utf-8"));
|
|
@@ -2895,9 +3208,160 @@ function processStackMessages(stacks, logger, options = {}) {
|
|
|
2895
3208
|
if (hasWarnings && failAt === "warn") throw new SynthesisError("Found warnings (--strict mode)");
|
|
2896
3209
|
}
|
|
2897
3210
|
|
|
3211
|
+
//#endregion
|
|
3212
|
+
//#region src/synthesis/failed-stages.ts
|
|
3213
|
+
/**
|
|
3214
|
+
* Marks an error that already names the Stage it was raised under, so an outer
|
|
3215
|
+
* Stage does not prepend its own name on the way out. Follows the marker shape
|
|
3216
|
+
* of `markNonRetryable` in `src/deployment/retryable-errors.ts`: a
|
|
3217
|
+
* non-enumerable own property, read by presence.
|
|
3218
|
+
*/
|
|
3219
|
+
const STAGE_SCOPED_MARKER = Symbol.for("cdkd.stageScopedError");
|
|
3220
|
+
/**
|
|
3221
|
+
* Re-raise a refusal caught while reading the CONTENTS of a Stage, with the
|
|
3222
|
+
* Stage named (issue [#3482](https://github.com/go-to-k/cdkd/issues/3482)).
|
|
3223
|
+
*
|
|
3224
|
+
* This is NOT a downgrade: every caller throws what this returns. The refusal
|
|
3225
|
+
* texts name the STACK — the stack a Stage produced carries a physical name
|
|
3226
|
+
* that need not embed the Stage's path, so without this the user is not told
|
|
3227
|
+
* which Stage directory to look in.
|
|
3228
|
+
*
|
|
3229
|
+
* The INNERMOST Stage wins: an error arriving already marked is returned
|
|
3230
|
+
* unchanged, so `Outer` does not restate what `Outer/Inner` said more
|
|
3231
|
+
* precisely. `stagePath` is the RAW path and is rendered here, for the reason
|
|
3232
|
+
* {@link FailedStage.stagePath} gives.
|
|
3233
|
+
*
|
|
3234
|
+
* The caught text goes through `displaySafe` although every throw reachable
|
|
3235
|
+
* from the recursion sanitizes at its own origin today: this catch's error
|
|
3236
|
+
* population is the whole recursive subtree, so the guard belongs to the CATCH
|
|
3237
|
+
* rather than to today's set of throws under it. It is therefore the identity
|
|
3238
|
+
* and a mutation probe on it finds no discrimination — stated so the next
|
|
3239
|
+
* reader gets the reason instead of the puzzle.
|
|
3240
|
+
*
|
|
3241
|
+
* No `cause`: the message already embeds the original's text in full, and
|
|
3242
|
+
* `formatError` renders a cause as a `Caused by:` line, which would print the
|
|
3243
|
+
* same sentence twice.
|
|
3244
|
+
*/
|
|
3245
|
+
function stageScopedError(stagePath, error) {
|
|
3246
|
+
if (error instanceof Error && STAGE_SCOPED_MARKER in error) return error;
|
|
3247
|
+
const message = displaySafe(error instanceof Error ? error.message : String(error));
|
|
3248
|
+
const scoped = new SynthesisError(`Stage ${renderStagePath(stagePath)}: ${message}`);
|
|
3249
|
+
if (error instanceof Error && typeof error.stack === "string") {
|
|
3250
|
+
const headerLines = `${error.name}: ${error.message}`.split("\n").length;
|
|
3251
|
+
const frames = error.stack.split("\n").slice(headerLines).join("\n");
|
|
3252
|
+
if (frames.length > 0) scoped.stack = `${scoped.name}: ${scoped.message}\n${frames}`;
|
|
3253
|
+
}
|
|
3254
|
+
if (Object.isExtensible(scoped)) Object.defineProperty(scoped, STAGE_SCOPED_MARKER, {
|
|
3255
|
+
value: true,
|
|
3256
|
+
enumerable: false,
|
|
3257
|
+
configurable: true,
|
|
3258
|
+
writable: false
|
|
3259
|
+
});
|
|
3260
|
+
return scoped;
|
|
3261
|
+
}
|
|
3262
|
+
/**
|
|
3263
|
+
* Render the sentence a stack-selection failure appends when a stage failed to
|
|
3264
|
+
* load, so the message reports the failed stage instead of answering "not
|
|
3265
|
+
* found" for a stack that may well exist under it.
|
|
3266
|
+
*
|
|
3267
|
+
* Returns `''` — appending nothing — when no stage failed, which is every
|
|
3268
|
+
* ordinary run. Otherwise it opens with `. ` so it reads as its own sentence
|
|
3269
|
+
* after a head that ends in a stack list.
|
|
3270
|
+
*
|
|
3271
|
+
* When one of the selection `patterns` targets a failed stage, that stage is
|
|
3272
|
+
* named as the explanation. Otherwise every failed stage is listed behind a
|
|
3273
|
+
* `Possibly unrelated:` hedge, since a physical stack name carries no stage
|
|
3274
|
+
* path and the link therefore cannot be proven from the pattern alone (see
|
|
3275
|
+
* `patternTargetsStage`). The sentence itself is spelled the same either way,
|
|
3276
|
+
* so one substring finds it.
|
|
3277
|
+
*/
|
|
3278
|
+
function failedStageNote(patterns, failedStages) {
|
|
3279
|
+
if (!failedStages || failedStages.length === 0) return "";
|
|
3280
|
+
const targeted = patterns.length === 0 ? failedStages : failedStages.filter((stage) => patterns.some((pattern) => patternTargetsStage(pattern, stage.stagePath)));
|
|
3281
|
+
const named = targeted.length > 0 ? targeted : failedStages;
|
|
3282
|
+
return ". " + (targeted.length > 0 ? "" : "Possibly unrelated: ") + named.map((stage) => `Stage ${renderStagePath(stage.stagePath)} failed to load, so stacks under it are missing from this list rather than missing from the app: ${stage.reason}`).join(" ");
|
|
3283
|
+
}
|
|
3284
|
+
/**
|
|
3285
|
+
* Render a Stage path into a message.
|
|
3286
|
+
*
|
|
3287
|
+
* `displayIdent` without quotes of ours, and with the cap a legitimately long
|
|
3288
|
+
* hierarchical name needs — the same argument `STACK_REF_MAX_CODE_POINTS`
|
|
3289
|
+
* exists for. See {@link FailedStage.stagePath} for why the stored value stays
|
|
3290
|
+
* raw and only this rendering is sanitized.
|
|
3291
|
+
*/
|
|
3292
|
+
function renderStagePath(stagePath) {
|
|
3293
|
+
return displayIdent(stagePath, { maxCodePoints: STACK_REF_MAX_CODE_POINTS });
|
|
3294
|
+
}
|
|
3295
|
+
/**
|
|
3296
|
+
* Whether a user-supplied selection pattern could name a stack under
|
|
3297
|
+
* `stagePath`.
|
|
3298
|
+
*
|
|
3299
|
+
* Mirrors the routing rule in `src/cli/stack-matcher.ts`: a pattern WITHOUT
|
|
3300
|
+
* `/` is matched against the PHYSICAL CloudFormation stack name, which carries
|
|
3301
|
+
* no stage path at all (`MyStage-MyStack` is not decomposable — a stack may
|
|
3302
|
+
* override its own `stackName`), so such a pattern is never attributed to a
|
|
3303
|
+
* stage. A pattern WITH `/` is a display path, and its leading segments are
|
|
3304
|
+
* compared against the stage's, wildcard by wildcard, so `MyStage/MyStack` and
|
|
3305
|
+
* `MyStage/*` both attribute to `MyStage`.
|
|
3306
|
+
*/
|
|
3307
|
+
function patternTargetsStage(pattern, stagePath) {
|
|
3308
|
+
if (!pattern.includes("/")) return false;
|
|
3309
|
+
const patternSegments = pattern.split("/");
|
|
3310
|
+
const stageSegments = stagePath.split("/");
|
|
3311
|
+
if (patternSegments.length < stageSegments.length) return false;
|
|
3312
|
+
return stageSegments.every((segment, i) => segmentMatches(patternSegments[i], segment));
|
|
3313
|
+
}
|
|
3314
|
+
/**
|
|
3315
|
+
* Wildcard-aware comparison of ONE path segment, matching how
|
|
3316
|
+
* `stackMatchesPattern` expands `*` in `src/cli/stack-matcher.ts`. The pattern
|
|
3317
|
+
* is the USER's own input, not an assembly-supplied value, so it is expanded
|
|
3318
|
+
* there the same way — metacharacters and all, so the two cannot disagree
|
|
3319
|
+
* about what a pattern means.
|
|
3320
|
+
*
|
|
3321
|
+
* The construction is guarded because SPLITTING on `/` can make an invalid
|
|
3322
|
+
* segment out of a valid pattern (`'(*x/y*)'` splits into `'(*x'`), and this
|
|
3323
|
+
* helper runs when the stack list is EMPTY, which is exactly when
|
|
3324
|
+
* `stackMatchesPattern` never evaluates and therefore never raises first. An
|
|
3325
|
+
* unusable pattern is simply not attributed: the note still prints, hedged,
|
|
3326
|
+
* instead of a `SyntaxError` replacing the message the user needed.
|
|
3327
|
+
*/
|
|
3328
|
+
function segmentMatches(patternSegment, segment) {
|
|
3329
|
+
if (!patternSegment.includes("*")) return patternSegment === segment;
|
|
3330
|
+
try {
|
|
3331
|
+
return new RegExp("^" + patternSegment.replace(/\*/g, ".*") + "$").test(segment);
|
|
3332
|
+
} catch {
|
|
3333
|
+
return false;
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
|
|
2898
3337
|
//#endregion
|
|
2899
3338
|
//#region src/synthesis/assembly-reader.ts
|
|
2900
3339
|
/**
|
|
3340
|
+
* The failure's own words, carrying NO value the assembly chose.
|
|
3341
|
+
*
|
|
3342
|
+
* Three branches, and each returns a string cdkd controls:
|
|
3343
|
+
*
|
|
3344
|
+
* - a filesystem failure is reduced to its `code` (`ENOENT`, `EACCES`,
|
|
3345
|
+
* `EISDIR`), because Node's `message` for one embeds the path, which is the
|
|
3346
|
+
* value being contained;
|
|
3347
|
+
* - a `JSON.parse` failure is reduced to `invalid JSON`. V8's `SyntaxError`
|
|
3348
|
+
* carries no path, but it DOES echo a short window of the file's own bytes
|
|
3349
|
+
* verbatim (`Unexpected token '.', ". All 3 st"... is not valid JSON`), and
|
|
3350
|
+
* the file is assembly-chosen too. The snippet buys nothing here: the caller
|
|
3351
|
+
* already names the directory;
|
|
3352
|
+
* - anything else answers `unreadable`, and deliberately does NOT fall back to
|
|
3353
|
+
* the wrapper's own message, which is the text this function exists to
|
|
3354
|
+
* exclude. A fallback fires on an input nobody predicted, which is exactly
|
|
3355
|
+
* when the contained value must not come back.
|
|
3356
|
+
*/
|
|
3357
|
+
function manifestReadFailureText(error) {
|
|
3358
|
+
const cause = error instanceof CdkdError ? error.cause : error;
|
|
3359
|
+
const code = cause?.code;
|
|
3360
|
+
if (typeof code === "string" && code.length > 0) return displaySafe(code);
|
|
3361
|
+
if (cause instanceof SyntaxError) return "invalid JSON";
|
|
3362
|
+
return "unreadable";
|
|
3363
|
+
}
|
|
3364
|
+
/**
|
|
2901
3365
|
* Reads and parses Cloud Assembly from cdk.out directory
|
|
2902
3366
|
*
|
|
2903
3367
|
* EVERY value this class renders into a message or a log line goes through
|
|
@@ -2915,6 +3379,14 @@ function processStackMessages(stacks, logger, options = {}) {
|
|
|
2915
3379
|
* still renders in full and byte-identically. The same rule, for the same
|
|
2916
3380
|
* reason, guards the twin refusals in `src/cli/commands/diff-recursive.ts`
|
|
2917
3381
|
* (go-to-k/cdkd#3243).
|
|
3382
|
+
*
|
|
3383
|
+
* ONE value leaves this class UNSANITIZED: a failed Stage's path
|
|
3384
|
+
* ([#3482](https://github.com/go-to-k/cdkd/issues/3482)), which is a match key
|
|
3385
|
+
* as well as the subject of a sentence. `failed-stages.ts` renders it at each
|
|
3386
|
+
* display site with `displayIdent` — it is an IDENTIFIER interpolated into
|
|
3387
|
+
* prose rather than free-form text quoted as a value, so the denylist's
|
|
3388
|
+
* tolerance of quotes and spaces is a spoof surface there. See
|
|
3389
|
+
* `FailedStage.stagePath`.
|
|
2918
3390
|
*/
|
|
2919
3391
|
var AssemblyReader = class {
|
|
2920
3392
|
logger = getLogger().child("AssemblyReader");
|
|
@@ -2933,9 +3405,38 @@ var AssemblyReader = class {
|
|
|
2933
3405
|
}
|
|
2934
3406
|
}
|
|
2935
3407
|
/**
|
|
3408
|
+
* Read an assembly: all its stacks (recursing into nested assemblies /
|
|
3409
|
+
* Stages) plus the Stages that could not be read at all.
|
|
3410
|
+
*
|
|
3411
|
+
* Only ONE failure is tolerated while walking a Stage — the read of the
|
|
3412
|
+
* Stage's own `manifest.json`, which is the legitimate "this Stage
|
|
3413
|
+
* references a directory that was never synthesized" case. Every REFUSAL
|
|
3414
|
+
* raised while reading a Stage's contents propagates out of the recursion
|
|
3415
|
+
* exactly as it does at the top level (issue
|
|
3416
|
+
* [#3482](https://github.com/go-to-k/cdkd/issues/3482)).
|
|
3417
|
+
*/
|
|
3418
|
+
readAssembly(assemblyDir, manifest, assetOutdir = assemblyDir) {
|
|
3419
|
+
const failedStages = [];
|
|
3420
|
+
return {
|
|
3421
|
+
stacks: this.collectStacks(assemblyDir, manifest, assetOutdir, failedStages),
|
|
3422
|
+
failedStages
|
|
3423
|
+
};
|
|
3424
|
+
}
|
|
3425
|
+
/**
|
|
2936
3426
|
* Get all stacks from assembly (recursively traverses nested assemblies / Stages)
|
|
3427
|
+
*
|
|
3428
|
+
* Discards the failed-Stage records `readAssembly` returns; a caller that
|
|
3429
|
+
* reports on a selection failure wants that method instead.
|
|
3430
|
+
*/
|
|
3431
|
+
getAllStacks(assemblyDir, manifest, assetOutdir = assemblyDir) {
|
|
3432
|
+
return this.readAssembly(assemblyDir, manifest, assetOutdir).stacks;
|
|
3433
|
+
}
|
|
3434
|
+
/**
|
|
3435
|
+
* The recursive half of `readAssembly`. `failedStages` is the accumulator
|
|
3436
|
+
* shared by every level, so a Stage that fails three levels down is still
|
|
3437
|
+
* reported to the top-level caller.
|
|
2937
3438
|
*/
|
|
2938
|
-
|
|
3439
|
+
collectStacks(assemblyDir, manifest, assetOutdir, failedStages) {
|
|
2939
3440
|
if (!manifest.artifacts) {
|
|
2940
3441
|
this.logger.warn("No artifacts found in manifest");
|
|
2941
3442
|
return [];
|
|
@@ -2943,18 +3444,31 @@ var AssemblyReader = class {
|
|
|
2943
3444
|
const assetManifestMap = this.buildAssetManifestMap(assemblyDir, manifest);
|
|
2944
3445
|
const stacks = [];
|
|
2945
3446
|
for (const [artifactId, artifact] of Object.entries(manifest.artifacts)) if (artifact.type === "aws:cloudformation:stack") {
|
|
2946
|
-
const stackInfo = this.extractStackInfo(assemblyDir, artifactId, artifact, manifest, assetManifestMap);
|
|
3447
|
+
const stackInfo = this.extractStackInfo(assemblyDir, artifactId, artifact, manifest, assetManifestMap, assetOutdir);
|
|
2947
3448
|
stacks.push(stackInfo);
|
|
2948
3449
|
} else if (artifact.type === "cdk:cloud-assembly") {
|
|
2949
3450
|
const props = artifact.properties;
|
|
2950
3451
|
if (props?.directoryName) {
|
|
2951
|
-
const
|
|
3452
|
+
const resolved = resolveAssemblyPath(assemblyDir, props.directoryName);
|
|
3453
|
+
if (!resolved.contained) throw new SynthesisError(`Nested assembly ${renderStagePath(props.directoryName)} ${renderAssemblyPathEscape(resolved, assemblyDir)}`);
|
|
3454
|
+
const nestedDir = resolved.path;
|
|
3455
|
+
const stagePath = typeof props.displayName === "string" && props.displayName.length > 0 ? props.displayName : artifactId;
|
|
3456
|
+
let nestedManifest;
|
|
2952
3457
|
try {
|
|
2953
|
-
|
|
2954
|
-
const nestedStacks = this.getAllStacks(nestedDir, nestedManifest);
|
|
2955
|
-
stacks.push(...nestedStacks);
|
|
3458
|
+
nestedManifest = this.readManifest(nestedDir);
|
|
2956
3459
|
} catch (error) {
|
|
2957
|
-
|
|
3460
|
+
const reason = `${manifestReadFailureText(error)} reading ${renderStagePath(props.directoryName)}/manifest.json`;
|
|
3461
|
+
this.logger.warn(`Failed to read nested assembly: ${reason}`);
|
|
3462
|
+
failedStages.push({
|
|
3463
|
+
stagePath,
|
|
3464
|
+
reason
|
|
3465
|
+
});
|
|
3466
|
+
continue;
|
|
3467
|
+
}
|
|
3468
|
+
try {
|
|
3469
|
+
stacks.push(...this.collectStacks(nestedDir, nestedManifest, assetOutdir, failedStages));
|
|
3470
|
+
} catch (error) {
|
|
3471
|
+
throw stageScopedError(stagePath, error);
|
|
2958
3472
|
}
|
|
2959
3473
|
}
|
|
2960
3474
|
}
|
|
@@ -2965,9 +3479,9 @@ var AssemblyReader = class {
|
|
|
2965
3479
|
* Get a specific stack by name
|
|
2966
3480
|
*/
|
|
2967
3481
|
getStack(assemblyDir, manifest, stackName) {
|
|
2968
|
-
const stacks = this.
|
|
3482
|
+
const { stacks, failedStages } = this.readAssembly(assemblyDir, manifest);
|
|
2969
3483
|
const stack = stacks.find((s) => s.stackName === stackName);
|
|
2970
|
-
if (!stack) throw new SynthesisError(`Stack '${displaySafe(stackName)}' not found in assembly. Available: ${stacks.map((s) =>
|
|
3484
|
+
if (!stack) throw new SynthesisError(`Stack '${displaySafe(stackName)}' not found in assembly. ` + (stacks.length > 0 ? `Available: ${stacks.map((s) => displayIdent(s.stackName, { maxCodePoints: STACK_REF_MAX_CODE_POINTS })).join(", ")}` : "The assembly has no stacks") + failedStageNote([stackName], failedStages));
|
|
2971
3485
|
return stack;
|
|
2972
3486
|
}
|
|
2973
3487
|
/**
|
|
@@ -2985,19 +3499,25 @@ var AssemblyReader = class {
|
|
|
2985
3499
|
for (const [artifactId, artifact] of Object.entries(manifest.artifacts)) {
|
|
2986
3500
|
if (artifact.type !== "cdk:asset-manifest") continue;
|
|
2987
3501
|
const props = artifact.properties;
|
|
2988
|
-
if (props?.file)
|
|
3502
|
+
if (props?.file) {
|
|
3503
|
+
const resolved = resolveAssemblyPath(assemblyDir, props.file);
|
|
3504
|
+
if (!resolved.contained) throw new SynthesisError(`Asset manifest artifact '${displaySafe(artifactId)}' has file='${displaySafe(props.file)}' which ${renderAssemblyPathEscape(resolved, assemblyDir)}`);
|
|
3505
|
+
map.set(artifactId, resolved.path);
|
|
3506
|
+
}
|
|
2989
3507
|
}
|
|
2990
3508
|
return map;
|
|
2991
3509
|
}
|
|
2992
3510
|
/**
|
|
2993
3511
|
* Extract stack info from artifact
|
|
2994
3512
|
*/
|
|
2995
|
-
extractStackInfo(assemblyDir, artifactId, artifact, manifest, assetManifestMap) {
|
|
3513
|
+
extractStackInfo(assemblyDir, artifactId, artifact, manifest, assetManifestMap, assetOutdir) {
|
|
2996
3514
|
const props = artifact.properties;
|
|
2997
3515
|
const stackName = props?.stackName || artifactId;
|
|
2998
3516
|
const templateFile = props?.templateFile;
|
|
2999
3517
|
if (!templateFile) throw new SynthesisError(`Stack '${displaySafe(stackName)}' has no templateFile property`);
|
|
3000
|
-
const
|
|
3518
|
+
const resolvedTemplate = resolveAssemblyPath(assemblyDir, templateFile);
|
|
3519
|
+
if (!resolvedTemplate.contained) throw new SynthesisError(`Stack '${displaySafe(stackName)}' has templateFile='${displaySafe(templateFile)}' which ${renderAssemblyPathEscape(resolvedTemplate, assemblyDir)}`);
|
|
3520
|
+
const templatePath = resolvedTemplate.path;
|
|
3001
3521
|
let template;
|
|
3002
3522
|
try {
|
|
3003
3523
|
const content = readFileSync(templatePath, "utf-8");
|
|
@@ -3025,13 +3545,15 @@ var AssemblyReader = class {
|
|
|
3025
3545
|
if (dependencyNames.length > 0) this.logger.debug(`Stack '${displaySafe(stackName)}' depends on: [${dependencyNames.map((n) => displaySafe(n)).join(", ")}]`);
|
|
3026
3546
|
let env;
|
|
3027
3547
|
if (artifact.environment) env = parseEnvironment(artifact.environment);
|
|
3028
|
-
const nestedTemplates =
|
|
3548
|
+
const nestedTemplates = nullPrototypeRecord();
|
|
3029
3549
|
for (const [logicalId, resource] of Object.entries(template.Resources ?? {})) {
|
|
3030
3550
|
if (resource?.Type !== "AWS::CloudFormation::Stack") continue;
|
|
3031
3551
|
const assetPath = resource.Metadata?.["aws:asset:path"];
|
|
3032
3552
|
if (typeof assetPath !== "string" || assetPath.length === 0) continue;
|
|
3033
3553
|
if (isAbsolute(assetPath) || /^[a-zA-Z]:[\\/]/.test(assetPath) || assetPath.startsWith("\\\\")) throw new SynthesisError(`Stack '${displaySafe(stackName)}' nested-stack '${displaySafe(logicalId)}' has Metadata['aws:asset:path']='${displaySafe(assetPath)}' which is absolute. CDK emits relative asset paths for nested templates; an absolute path indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to load.`);
|
|
3034
|
-
|
|
3554
|
+
const resolvedNested = resolveAssemblyPath(assemblyDir, assetPath);
|
|
3555
|
+
if (!resolvedNested.contained) throw new SynthesisError(`Stack '${displaySafe(stackName)}' nested-stack '${displaySafe(logicalId)}' has Metadata['aws:asset:path']='${displaySafe(assetPath)}' which ${renderAssemblyPathEscape(resolvedNested, assemblyDir)}`);
|
|
3556
|
+
nestedTemplates[logicalId] = resolvedNested.path;
|
|
3035
3557
|
}
|
|
3036
3558
|
return {
|
|
3037
3559
|
stackName,
|
|
@@ -3039,6 +3561,7 @@ var AssemblyReader = class {
|
|
|
3039
3561
|
artifactId,
|
|
3040
3562
|
template,
|
|
3041
3563
|
assetManifestPath,
|
|
3564
|
+
assetOutdir,
|
|
3042
3565
|
dependencyNames,
|
|
3043
3566
|
region: env?.region !== "unknown-region" ? env?.region : void 0,
|
|
3044
3567
|
account: env?.account !== "unknown-account" ? env?.account : void 0,
|
|
@@ -6175,13 +6698,14 @@ var Synthesizer = class {
|
|
|
6175
6698
|
if (isPreSynthesizedAssembly(options.app)) {
|
|
6176
6699
|
this.logger.debug(`Using pre-synthesized cloud assembly at ${appPath}`);
|
|
6177
6700
|
const manifest = this.assemblyReader.readManifest(appPath);
|
|
6178
|
-
const stacks = this.assemblyReader.
|
|
6701
|
+
const { stacks, failedStages } = this.assemblyReader.readAssembly(appPath, manifest);
|
|
6179
6702
|
if (!options.deferMacroExpansion) await this.expandMacrosForStacks(stacks, options);
|
|
6180
6703
|
this.logger.debug(`Loaded ${stacks.length} stack(s) from pre-synthesized assembly`);
|
|
6181
6704
|
return {
|
|
6182
6705
|
manifest,
|
|
6183
6706
|
assemblyDir: appPath,
|
|
6184
|
-
stacks
|
|
6707
|
+
stacks,
|
|
6708
|
+
failedStages
|
|
6185
6709
|
};
|
|
6186
6710
|
}
|
|
6187
6711
|
const outputDir = resolve(options.output || "cdk.out");
|
|
@@ -6232,7 +6756,7 @@ var Synthesizer = class {
|
|
|
6232
6756
|
});
|
|
6233
6757
|
const manifest = this.assemblyReader.readManifest(outputDir);
|
|
6234
6758
|
if (!manifest.missing || manifest.missing.length === 0) {
|
|
6235
|
-
const stacks = this.assemblyReader.
|
|
6759
|
+
const { stacks, failedStages } = this.assemblyReader.readAssembly(outputDir, manifest);
|
|
6236
6760
|
if (!options.deferMacroExpansion) await this.expandMacrosForStacks(stacks, options, {
|
|
6237
6761
|
region: explicitRegion,
|
|
6238
6762
|
...accountId && { accountId }
|
|
@@ -6241,7 +6765,8 @@ var Synthesizer = class {
|
|
|
6241
6765
|
return {
|
|
6242
6766
|
manifest,
|
|
6243
6767
|
assemblyDir: outputDir,
|
|
6244
|
-
stacks
|
|
6768
|
+
stacks,
|
|
6769
|
+
failedStages
|
|
6245
6770
|
};
|
|
6246
6771
|
}
|
|
6247
6772
|
const missingKeys = new Set(manifest.missing.map((m) => m.key));
|
|
@@ -6366,6 +6891,24 @@ function setsEqual(a, b) {
|
|
|
6366
6891
|
//#endregion
|
|
6367
6892
|
//#region src/assets/asset-manifest-loader.ts
|
|
6368
6893
|
/**
|
|
6894
|
+
* THE one spelling of "where does this file asset's source live", shared by
|
|
6895
|
+
* {@link AssetManifestLoader.getAssetSourcePath} and `FileAssetPublisher`
|
|
6896
|
+
* (issue [#3489](https://github.com/go-to-k/cdkd/issues/3489)).
|
|
6897
|
+
*
|
|
6898
|
+
* Both used to `join(cdkOutputDir, asset.source.path)` independently. The
|
|
6899
|
+
* manifest is chosen by whoever wrote the assembly, `path.join` folds `..`,
|
|
6900
|
+
* and the publisher ZIPs whatever it finds and `PutObject`s it to a bucket the
|
|
6901
|
+
* same manifest names — so an unchecked `source.path` is an exfiltration
|
|
6902
|
+
* primitive with the caller's own credentials, which is the harm the manifest
|
|
6903
|
+
* FILE's own containment check cites. One function so the loader and the
|
|
6904
|
+
* publisher cannot disagree about which file is published.
|
|
6905
|
+
*/
|
|
6906
|
+
function resolveFileAssetSourcePath(manifestDir, asset, assetOutdir = manifestDir) {
|
|
6907
|
+
const resolved = resolveAssemblyPath(manifestDir, asset.source.path, { containWithin: assetOutdir });
|
|
6908
|
+
if (!resolved.contained) throw new Error(`File asset '${displaySafe(asset.displayName)}' has source.path='${displaySafe(asset.source.path)}' which ${renderAssemblyPathEscape(resolved, assetOutdir, "publish it")}`);
|
|
6909
|
+
return resolved.path;
|
|
6910
|
+
}
|
|
6911
|
+
/**
|
|
6369
6912
|
* Whether a file asset's `source.path` is a CloudFormation template asset
|
|
6370
6913
|
* (the stack template `<Stack>.template.json` or a nested-stack template
|
|
6371
6914
|
* `<Stack>.<Nested>.nested.template.json`). cdkd deploys templates itself and
|
|
@@ -6397,7 +6940,9 @@ var AssetManifestLoader = class {
|
|
|
6397
6940
|
* @returns Asset manifest or null if not found
|
|
6398
6941
|
*/
|
|
6399
6942
|
async loadManifest(cdkOutputDir, stackName) {
|
|
6400
|
-
const
|
|
6943
|
+
const resolved = resolveAssemblyPath(cdkOutputDir, `${stackName}.assets.json`);
|
|
6944
|
+
if (!resolved.contained) throw new Error(`Asset manifest for stack '${displaySafe(stackName)}' ${renderAssemblyPathEscape(resolved, cdkOutputDir)}`);
|
|
6945
|
+
const manifestPath = resolved.path;
|
|
6401
6946
|
try {
|
|
6402
6947
|
this.logger.debug(`Loading asset manifest from: ${manifestPath}`);
|
|
6403
6948
|
const content = await readFile(manifestPath, "utf-8");
|
|
@@ -6433,12 +6978,18 @@ var AssetManifestLoader = class {
|
|
|
6433
6978
|
/**
|
|
6434
6979
|
* Get asset source path (absolute path)
|
|
6435
6980
|
*
|
|
6981
|
+
* Refuses a `source.path` resolving outside `cdkOutputDir` (issue
|
|
6982
|
+
* [#3489](https://github.com/go-to-k/cdkd/issues/3489)). The manifest is
|
|
6983
|
+
* assembly-supplied and `cdkd deploy -a <dir>` consumes a pre-synthesized
|
|
6984
|
+
* one, so a `source.path` of `../../../home/<user>/.aws` would otherwise be
|
|
6985
|
+
* packaged and uploaded to a bucket the SAME manifest names.
|
|
6986
|
+
*
|
|
6436
6987
|
* @param cdkOutputDir CDK output directory
|
|
6437
6988
|
* @param asset File asset
|
|
6438
6989
|
* @returns Absolute path to asset source
|
|
6439
6990
|
*/
|
|
6440
|
-
getAssetSourcePath(cdkOutputDir, asset) {
|
|
6441
|
-
return
|
|
6991
|
+
getAssetSourcePath(cdkOutputDir, asset, assetOutdir) {
|
|
6992
|
+
return resolveFileAssetSourcePath(cdkOutputDir, asset, assetOutdir ?? cdkOutputDir);
|
|
6442
6993
|
}
|
|
6443
6994
|
/**
|
|
6444
6995
|
* Resolve asset destination values (replace ${AWS::AccountId}, ${AWS::Region}, etc.)
|
|
@@ -6530,7 +7081,8 @@ var FileAssetPublisher = class {
|
|
|
6530
7081
|
* @param region AWS region
|
|
6531
7082
|
* @param profile AWS profile (optional)
|
|
6532
7083
|
*/
|
|
6533
|
-
async publish(assetHash, asset, cdkOutputDir, accountId, region, _profile) {
|
|
7084
|
+
async publish(assetHash, asset, cdkOutputDir, accountId, region, _profile, assetOutdir) {
|
|
7085
|
+
const sourcePath = resolveFileAssetSourcePath(cdkOutputDir, asset, assetOutdir ?? cdkOutputDir);
|
|
6534
7086
|
for (const [, dest] of Object.entries(asset.destinations)) {
|
|
6535
7087
|
const bucketName = this.resolvePlaceholders(dest.bucketName, accountId, region);
|
|
6536
7088
|
const objectKey = this.resolvePlaceholders(dest.objectKey, accountId, region);
|
|
@@ -6545,7 +7097,6 @@ var FileAssetPublisher = class {
|
|
|
6545
7097
|
this.logger.debug(`Asset already exists, skipping: s3://${bucketName}/${objectKey}`);
|
|
6546
7098
|
continue;
|
|
6547
7099
|
}
|
|
6548
|
-
const sourcePath = join(cdkOutputDir, asset.source.path);
|
|
6549
7100
|
if (asset.source.packaging === "zip") await this.uploadZip(client, sourcePath, bucketName, objectKey);
|
|
6550
7101
|
else await this.uploadFile(client, sourcePath, bucketName, objectKey);
|
|
6551
7102
|
this.logger.debug(`✅ Published: s3://${bucketName}/${objectKey}`);
|
|
@@ -7865,6 +8416,28 @@ function describeDockerCapturedOutput(error, args, fallback) {
|
|
|
7865
8416
|
//#endregion
|
|
7866
8417
|
//#region src/assets/docker-build.ts
|
|
7867
8418
|
/**
|
|
8419
|
+
* Where a Docker asset's build context lives, refusing a `source.directory`
|
|
8420
|
+
* that resolves outside `cdkOutDir` (issue
|
|
8421
|
+
* [#3489](https://github.com/go-to-k/cdkd/issues/3489)).
|
|
8422
|
+
*
|
|
8423
|
+
* The field is assembly-supplied and used BOTH as the executable's cwd and as
|
|
8424
|
+
* the `docker build` cwd, so an escaping value sends a directory from outside
|
|
8425
|
+
* `cdk.out` to BuildKit and bakes it into the image pushed to a repository the
|
|
8426
|
+
* same manifest names. Both sites used raw `${a}/${b}` concatenation, which
|
|
8427
|
+
* honours `..` exactly as `join` does; this is the one spelling they share.
|
|
8428
|
+
*
|
|
8429
|
+
* It throws through the CALLER's `wrapError` so each consumer keeps its own
|
|
8430
|
+
* typed error class, exactly as this module's other pre-docker refusals do.
|
|
8431
|
+
*
|
|
8432
|
+
* Twin of `resolveFileAssetSourcePath` and `resolveVerboseTemplatePath`.
|
|
8433
|
+
* Exported for unit testing.
|
|
8434
|
+
*/
|
|
8435
|
+
function resolveDockerContextDirectory(manifestDir, directory, wrapError, assetOutdir = manifestDir) {
|
|
8436
|
+
const resolved = resolveAssemblyPath(manifestDir, directory, { containWithin: assetOutdir });
|
|
8437
|
+
if (!resolved.contained) throw wrapError(`asset source.directory='${displaySafe(directory)}' which ${renderAssemblyPathEscape(resolved, assetOutdir, "build it")}`);
|
|
8438
|
+
return resolved.path;
|
|
8439
|
+
}
|
|
8440
|
+
/**
|
|
7868
8441
|
* Build a Docker image from a CDK asset source. Returns the local image
|
|
7869
8442
|
* tag the caller should use for `docker tag` / `docker push` (publisher)
|
|
7870
8443
|
* or `docker run` (local-invoke).
|
|
@@ -7882,10 +8455,11 @@ function describeDockerCapturedOutput(error, args, fallback) {
|
|
|
7882
8455
|
async function buildDockerImage(asset, cdkOutDir, options) {
|
|
7883
8456
|
const source = asset.source;
|
|
7884
8457
|
const logger = getLogger().child("docker-build");
|
|
8458
|
+
const contextDirectory = (directory) => resolveDockerContextDirectory(cdkOutDir, directory, options.wrapError, options.assetOutdir);
|
|
7885
8459
|
if (source.executable && source.executable.length > 0) {
|
|
7886
8460
|
const [cmd, ...args] = source.executable;
|
|
7887
8461
|
if (!cmd) throw options.wrapError("asset source.executable[] is empty");
|
|
7888
|
-
const cwd = source.directory ?
|
|
8462
|
+
const cwd = source.directory ? contextDirectory(source.directory) : cdkOutDir;
|
|
7889
8463
|
const shownExecutable = redactDockerArgvValues(source.executable).join(" ");
|
|
7890
8464
|
logger.debug(`Building Docker image via executable: ${shownExecutable} (cwd=${cwd})`);
|
|
7891
8465
|
let result;
|
|
@@ -7909,9 +8483,9 @@ async function buildDockerImage(asset, cdkOutDir, options) {
|
|
|
7909
8483
|
}
|
|
7910
8484
|
if (!options.tag) throw options.wrapError("buildDockerImage(directory mode) requires options.tag");
|
|
7911
8485
|
const buildArgs = buildDockerBuildCommand(source, options.tag, options.platform);
|
|
7912
|
-
const contextDir =
|
|
8486
|
+
const contextDir = contextDirectory(source.directory);
|
|
7913
8487
|
buildArgs.push(".");
|
|
7914
|
-
logger.debug(`${getDockerCmd()} ${redactDockerArgvValues(buildArgs).join(" ")} (cwd=${contextDir})`);
|
|
8488
|
+
logger.debug(`${getDockerCmd()} ${redactDockerArgvValues(buildArgs).join(" ")} (cwd=${displaySafe(contextDir)})`);
|
|
7915
8489
|
try {
|
|
7916
8490
|
await runDockerStreaming(buildArgs, {
|
|
7917
8491
|
cwd: contextDir,
|
|
@@ -8041,8 +8615,8 @@ var DockerAssetPublisher = class {
|
|
|
8041
8615
|
* via `docker tag` so the downstream `push()` step (which is wired to
|
|
8042
8616
|
* `localTag` at graph-construction time) keeps working unchanged.
|
|
8043
8617
|
*/
|
|
8044
|
-
async build(asset, cdkOutputDir, localTag) {
|
|
8045
|
-
await this.buildImage(asset, cdkOutputDir, localTag);
|
|
8618
|
+
async build(asset, cdkOutputDir, localTag, assetOutdir) {
|
|
8619
|
+
await this.buildImage(asset, cdkOutputDir, localTag, assetOutdir);
|
|
8046
8620
|
}
|
|
8047
8621
|
/**
|
|
8048
8622
|
* Push a pre-built Docker image to ECR (public, used by WorkGraph asset-publish nodes)
|
|
@@ -8098,9 +8672,10 @@ var DockerAssetPublisher = class {
|
|
|
8098
8672
|
* downstream push step finds the image under the deterministic
|
|
8099
8673
|
* `cdkd-asset-<hash>` name it expects.
|
|
8100
8674
|
*/
|
|
8101
|
-
async buildImage(asset, cdkOutputDir, tag) {
|
|
8675
|
+
async buildImage(asset, cdkOutputDir, tag, assetOutdir) {
|
|
8102
8676
|
const actualTag = await buildDockerImage(asset, cdkOutputDir, {
|
|
8103
8677
|
tag,
|
|
8678
|
+
...assetOutdir !== void 0 && { assetOutdir },
|
|
8104
8679
|
wrapError: (stderr) => new AssetError(`Docker build failed: ${stderr}`)
|
|
8105
8680
|
});
|
|
8106
8681
|
if (actualTag !== tag) {
|
|
@@ -9473,6 +10048,7 @@ var AssetPublisher = class {
|
|
|
9473
10048
|
const content = readFileSync(manifestPath, "utf-8");
|
|
9474
10049
|
const manifest = JSON.parse(content);
|
|
9475
10050
|
const cdkOutputDir = manifestPath.replace(/\/[^/]+$/, "");
|
|
10051
|
+
const assetOutdir = options.assetOutdir ?? cdkOutputDir;
|
|
9476
10052
|
const prefix = options.nodePrefix || "";
|
|
9477
10053
|
const redirect = options.redirect;
|
|
9478
10054
|
const nodeIds = [];
|
|
@@ -9489,6 +10065,7 @@ var AssetPublisher = class {
|
|
|
9489
10065
|
hash,
|
|
9490
10066
|
asset,
|
|
9491
10067
|
cdkOutputDir,
|
|
10068
|
+
assetOutdir,
|
|
9492
10069
|
accountId: options.accountId,
|
|
9493
10070
|
region: options.region,
|
|
9494
10071
|
...options.profile && { profile: options.profile }
|
|
@@ -9511,6 +10088,7 @@ var AssetPublisher = class {
|
|
|
9511
10088
|
hash,
|
|
9512
10089
|
asset,
|
|
9513
10090
|
cdkOutputDir,
|
|
10091
|
+
assetOutdir,
|
|
9514
10092
|
localTag
|
|
9515
10093
|
}
|
|
9516
10094
|
});
|
|
@@ -9537,8 +10115,8 @@ var AssetPublisher = class {
|
|
|
9537
10115
|
*/
|
|
9538
10116
|
async executeNode(node) {
|
|
9539
10117
|
const data = node.data;
|
|
9540
|
-
if (data.kind === "file") await this.filePublisher.publish(data.hash, data.asset, data.cdkOutputDir, data.accountId, data.region, data.profile);
|
|
9541
|
-
else if (data.kind === "docker-build") await this.dockerPublisher.build(data.asset, data.cdkOutputDir, data.localTag);
|
|
10118
|
+
if (data.kind === "file") await this.filePublisher.publish(data.hash, data.asset, data.cdkOutputDir, data.accountId, data.region, data.profile, data.assetOutdir);
|
|
10119
|
+
else if (data.kind === "docker-build") await this.dockerPublisher.build(data.asset, data.cdkOutputDir, data.localTag, data.assetOutdir);
|
|
9542
10120
|
else if (data.kind === "docker-publish") await this.dockerPublisher.push(data.asset, data.accountId, data.region, data.localTag);
|
|
9543
10121
|
this.logger.debug(`✅ ${node.id}`);
|
|
9544
10122
|
}
|
|
@@ -22041,6 +22619,156 @@ function refuseMalformedOutputs(state, stackName, region) {
|
|
|
22041
22619
|
throw markNonRetryable(new CdkdError(malformedOutputsRefusalMessage(stackName, region), STATE_RESOURCES_MALFORMED));
|
|
22042
22620
|
}
|
|
22043
22621
|
/**
|
|
22622
|
+
* The stand-in row a read-only view lists in its `unreadable` set for an
|
|
22623
|
+
* `orphans` container it could not read, beside
|
|
22624
|
+
* {@link UNREADABLE_RESOURCES_MAP_ROW}'s row for the resource map.
|
|
22625
|
+
*/
|
|
22626
|
+
const UNREADABLE_ORPHANS_CONTAINER_ROW = "(orphans container)";
|
|
22627
|
+
/**
|
|
22628
|
+
* Whether the `orphans` CONTAINER is the list every reader of it assumes
|
|
22629
|
+
* (issue go-to-k/cdkd#3379).
|
|
22630
|
+
*
|
|
22631
|
+
* `parseStateBody` validates the record root and its schema version and returns
|
|
22632
|
+
* the cast record otherwise unchanged, so a hand-edited or planted
|
|
22633
|
+
* `"orphans": "abc"`, `5`, `{}` or `{"length": 1}` survives the load and every
|
|
22634
|
+
* reader reaches it on a bare `?? []`, `?.length` or `?.map`.
|
|
22635
|
+
*
|
|
22636
|
+
* ABSENT IS READABLE, for the reason {@link hasReadableOutputs}'s note gives
|
|
22637
|
+
* for its own container and one that is sharper here: an absent `orphans` is
|
|
22638
|
+
* the ORDINARY record. A stack that never had a failed deploy has no orphan
|
|
22639
|
+
* list at all, `JSON.stringify` drops the key when it is undefined, and
|
|
22640
|
+
* `orphansAfterRollback` returns `{}` on `previous.orphans === undefined`
|
|
22641
|
+
* WITH nothing newly orphaned — the pair of conditions that keeps such a
|
|
22642
|
+
* record byte-identical. Warning or refusing on absence would fire on
|
|
22643
|
+
* almost every record in a bucket.
|
|
22644
|
+
*
|
|
22645
|
+
* `null` is NOT readable, which is where this parts from a bare `?? []`: that
|
|
22646
|
+
* admits `null` silently, and the shapes this exists to catch are exactly the
|
|
22647
|
+
* ones it cannot see.
|
|
22648
|
+
*
|
|
22649
|
+
* The ENTRIES are a separate question with a separate answer —
|
|
22650
|
+
* `orphans[<i>].properties` is go-to-k/cdkd#3344, and
|
|
22651
|
+
* {@link refuseMalformedResourcePropertiesForOrphan} is the guard there. This
|
|
22652
|
+
* one says only that the container is a list.
|
|
22653
|
+
*/
|
|
22654
|
+
function hasReadableOrphans(state) {
|
|
22655
|
+
return state.orphans === void 0 || Array.isArray(state.orphans);
|
|
22656
|
+
}
|
|
22657
|
+
/**
|
|
22658
|
+
* For a READ-ONLY command: replace an unreadable `orphans` container with an
|
|
22659
|
+
* empty list so the command can report the rest of the record, and return
|
|
22660
|
+
* whether it did, so the caller can warn with
|
|
22661
|
+
* {@link malformedOrphansWarning} and list
|
|
22662
|
+
* {@link UNREADABLE_ORPHANS_CONTAINER_ROW} among what it could not read.
|
|
22663
|
+
*
|
|
22664
|
+
* AT THE LOAD, the placement rule
|
|
22665
|
+
* {@link repairMalformedResourcesForReadOnly}'s note records: `cdkd diff`
|
|
22666
|
+
* reaches the container through `currentState.orphans?.length` before its
|
|
22667
|
+
* adoption preview, so a guard written at the preview leaves the dereference
|
|
22668
|
+
* above it — and for a STRING that dereference is the one that lies, since
|
|
22669
|
+
* `'abc'.length` is 3 and the preview then walks characters.
|
|
22670
|
+
*
|
|
22671
|
+
* READ-ONLY commands only, for the reason {@link refuseMalformedState} gives:
|
|
22672
|
+
* a container laundered into a well-formed empty one is permanent, and for
|
|
22673
|
+
* this container it also erases the only record that resources were left
|
|
22674
|
+
* behind in AWS by an earlier failed deploy.
|
|
22675
|
+
*/
|
|
22676
|
+
function repairMalformedOrphansForReadOnly(state) {
|
|
22677
|
+
if (hasReadableOrphans(state)) return false;
|
|
22678
|
+
state.orphans = [];
|
|
22679
|
+
return true;
|
|
22680
|
+
}
|
|
22681
|
+
/**
|
|
22682
|
+
* The warning a caller of {@link repairMalformedOrphansForReadOnly} emits.
|
|
22683
|
+
*
|
|
22684
|
+
* Its own text rather than any of the three above, the rule this module's
|
|
22685
|
+
* every message follows: what continuing EMPTY costs here is not a diff's
|
|
22686
|
+
* left-hand side or a rendered view's rows but the record's evidence that
|
|
22687
|
+
* resources from an earlier failed deploy are still live in AWS. A reader who
|
|
22688
|
+
* sees no adoption preview and no orphan warning will conclude the stack has
|
|
22689
|
+
* none.
|
|
22690
|
+
*
|
|
22691
|
+
* Identifiers are sanitized and THEN shell-quoted and the command is emitted
|
|
22692
|
+
* LAST and UNWRAPPED, for the reasons {@link safeIdentifier}'s note gives.
|
|
22693
|
+
*/
|
|
22694
|
+
function malformedOrphansWarning(stackName, region) {
|
|
22695
|
+
const stack = safeStackName(stackName);
|
|
22696
|
+
const reg = safeRegion(region);
|
|
22697
|
+
return `State for ${shellQuote(stack)} (${shellQuote(reg)}) has no readable 'orphans' list — the record is malformed or truncated. Readers reach it on a bare '?? []' or '?.length', which admits a string, a number, a plain object and null alike: a string is WALKED, one garbage orphan per character, and the others read as no orphans at all. Continuing with it EMPTY: this view previews no adoption and names no orphan, which is NOT the same as the record holding none — resources from an earlier failed deploy may still be live in AWS. See the stored value with: cdkd state show ${shellQuote(stack)} --stack-region ${shellQuote(reg)} --json`;
|
|
22698
|
+
}
|
|
22699
|
+
/**
|
|
22700
|
+
* The refusal text for a command that can WRITE the record.
|
|
22701
|
+
*
|
|
22702
|
+
* A DIFFERENT text from the warning because the danger is different and worse:
|
|
22703
|
+
* the reshaping reader is also a writer. `orphansAfterRollback` walks the
|
|
22704
|
+
* container with `for...of`, so over `"abc"` it returns a list holding `"c"`,
|
|
22705
|
+
* and `cdkd rollback` SAVES that record — a damaged container rewritten into a
|
|
22706
|
+
* differently damaged one, silently. `cdkd deploy`'s adoption pass assigns
|
|
22707
|
+
* `currentState.orphans` from what it read, and `cdkd destroy` proceeds through
|
|
22708
|
+
* resource deletion to `deleteState` having never reported the orphans it could
|
|
22709
|
+
* not read.
|
|
22710
|
+
*
|
|
22711
|
+
* Identifiers are sanitized and THEN shell-quoted and the command is emitted
|
|
22712
|
+
* LAST and UNWRAPPED, for the reasons {@link safeIdentifier}'s note gives.
|
|
22713
|
+
*/
|
|
22714
|
+
function malformedOrphansRefusalMessage(stackName, region) {
|
|
22715
|
+
const stack = safeStackName(stackName);
|
|
22716
|
+
const reg = safeRegion(region);
|
|
22717
|
+
return `State for ${shellQuote(stack)} (${shellQuote(reg)}) has no readable 'orphans' list — the record is malformed or truncated. This command can WRITE state, so it refuses rather than continuing: a string container is WALKED one character at a time and written back as a list of character-shaped orphan records, and every other unreadable shape reads as no orphans at all — so a run would delete or adopt against a record whose evidence of resources left live in AWS by an earlier failed deploy it never read. Repair or remove the record first, and no cdkd command repairs this container: rewriting it to [] by hand discards the very evidence this refusal is protecting. Inspect the record with: cdkd state show ${shellQuote(stack)} --stack-region ${shellQuote(reg)} --json`;
|
|
22718
|
+
}
|
|
22719
|
+
/**
|
|
22720
|
+
* The DESTROY refusal text for this container (go-to-k/cdkd#3379 review).
|
|
22721
|
+
*
|
|
22722
|
+
* Its own text rather than {@link malformedOrphansRefusalMessage}, for the
|
|
22723
|
+
* reason every text in this module is its own: that one says the container
|
|
22724
|
+
* would be RESHAPED and written back, which is the rollback mechanism, and a
|
|
22725
|
+
* destroy never reshapes it — `buildDestroySnapshot` carries the container
|
|
22726
|
+
* verbatim through `...rest` into every save it makes, and then either REMOVES
|
|
22727
|
+
* the record (the clean path) or writes that snapshot as the final state, with
|
|
22728
|
+
* the container intact, on the error / interrupt / skip arm. What a destroy
|
|
22729
|
+
* does with the container instead is DECIDE: the count feeds `stillEmpty`, and
|
|
22730
|
+
* the orphan warning above it is the operator's only notice that resources from
|
|
22731
|
+
* an earlier failed deploy are still live in AWS.
|
|
22732
|
+
*
|
|
22733
|
+
* It also owes the remedy the sibling destroy refusal owes, and for the same
|
|
22734
|
+
* reason go-to-k/cdkd#3161 raised against refusing a cleanup command at all: an
|
|
22735
|
+
* operator who wants the record gone needs a way to do it. `cdkd state orphan`
|
|
22736
|
+
* is that way and reads no `orphans` container, so pointing at it is sound —
|
|
22737
|
+
* the same exactness split as {@link malformedDestroyResourcesRefusalMessage},
|
|
22738
|
+
* since a name that does not render faithfully must not become a command
|
|
22739
|
+
* against a record that may not be the damaged one.
|
|
22740
|
+
*/
|
|
22741
|
+
function malformedDestroyOrphansRefusalMessage(stackName, region) {
|
|
22742
|
+
const exact = safeStackName(stackName) === stackName && safeRegion(region) === region;
|
|
22743
|
+
return `${`${stackClause(exact ? stackName : void 0, exact ? region : void 0)} has no readable 'orphans' list — the record is malformed or truncated.`} This command DELETES state, so it refuses rather than continuing: an unreadable container counts as no orphans, so the run would proceed through resource deletion to removing state.json while the record's evidence that an earlier failed deploy left resources live in AWS was never read — and that evidence goes with the record. Reading it as EMPTY is that outcome rather than an alternative to it, so there is no repair available here. Repair or remove the record first. ${exact ? `To drop the record deliberately and leave every live resource standing, run 'cdkd state orphan' against the stack and the region THE RECORD'S S3 KEY holds — spelled out as 'cdkd state orphan <stack> --stack-region <region>' rather than pasteable, because that command DELETES a record and the region printed above is the one cdkd was pointed at, not a value cdkd owns. Confirm the key with 'cdkd state list --long' — a legacy record shows none, and for one of those the flag must be OMITTED or it selects nothing. Inspect it with: ${inspectCommand(stackName, region)}` : `This record's stack name or region does NOT render exactly, so this message names no target and offers no command against one. List the records as stored with 'cdkd state list --long' and act on the one whose key matches. Inspect it with: ${inspectCommand(void 0, void 0)}`}`;
|
|
22744
|
+
}
|
|
22745
|
+
/**
|
|
22746
|
+
* The destroy-path twin of {@link refuseMalformedOrphans}, carrying the text
|
|
22747
|
+
* above. Both destroy reads take this one: the entry read and the under-lock
|
|
22748
|
+
* re-read, which is the record `stillEmpty` and `deleteState` act on.
|
|
22749
|
+
*/
|
|
22750
|
+
function refuseMalformedOrphansForDestroy(state, stackName, region) {
|
|
22751
|
+
if (hasReadableOrphans(state)) return;
|
|
22752
|
+
throw markNonRetryable(new CdkdError(malformedDestroyOrphansRefusalMessage(stackName, region), STATE_RESOURCES_MALFORMED));
|
|
22753
|
+
}
|
|
22754
|
+
/**
|
|
22755
|
+
* For a command that can WRITE state: refuse a record whose `orphans` container
|
|
22756
|
+
* cannot be read.
|
|
22757
|
+
*
|
|
22758
|
+
* A SIBLING of {@link refuseMalformedState} and {@link refuseMalformedOutputs}
|
|
22759
|
+
* rather than a widening of either, for the reason that pair already records: a
|
|
22760
|
+
* record can be malformed in one container alone, and the refusal a user sees
|
|
22761
|
+
* must name the container that is broken.
|
|
22762
|
+
*
|
|
22763
|
+
* CALL IT AT THE LOAD, above the first expression that reads the container, and
|
|
22764
|
+
* on a destroy at the UNDER-LOCK re-read as well — that re-read is the record
|
|
22765
|
+
* the run goes on to act on.
|
|
22766
|
+
*/
|
|
22767
|
+
function refuseMalformedOrphans(state, stackName, region) {
|
|
22768
|
+
if (hasReadableOrphans(state)) return;
|
|
22769
|
+
throw markNonRetryable(new CdkdError(malformedOrphansRefusalMessage(stackName, region), STATE_RESOURCES_MALFORMED));
|
|
22770
|
+
}
|
|
22771
|
+
/**
|
|
22044
22772
|
* The DESTROY refusal text (issue
|
|
22045
22773
|
* [#3207](https://github.com/go-to-k/cdkd/issues/3207)).
|
|
22046
22774
|
*
|
|
@@ -33683,7 +34411,7 @@ var CloudControlProvider = class {
|
|
|
33683
34411
|
const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
33684
34412
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
33685
34413
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
33686
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
34414
|
+
const { ASGProvider } = await import("./asg-provider-BwN09Zcn.js").then((n) => n.n);
|
|
33687
34415
|
const asgProvider = new ASGProvider();
|
|
33688
34416
|
return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
|
|
33689
34417
|
}
|
|
@@ -45368,6 +46096,7 @@ var DeployEngine = class DeployEngine {
|
|
|
45368
46096
|
this.healBaseline = currentState.resources ?? {};
|
|
45369
46097
|
refuseMalformedOutputs(currentState, stackName, this.stackRegion);
|
|
45370
46098
|
refuseMalformedResourcesForDeploy(currentState, stackName, this.stackRegion);
|
|
46099
|
+
refuseMalformedOrphans(currentState, stackName, this.stackRegion);
|
|
45371
46100
|
const migrationPending = currentStateData?.migrationPending ?? false;
|
|
45372
46101
|
this.logger.debug(`Loaded current state: ${Object.keys(currentState.resources).length} resources`);
|
|
45373
46102
|
if (this.options.onCurrentStateLoaded) await this.options.onCurrentStateLoaded(stackName, currentStateData?.state);
|
|
@@ -47391,5 +48120,5 @@ var DeployEngine = class DeployEngine {
|
|
|
47391
48120
|
};
|
|
47392
48121
|
|
|
47393
48122
|
//#endregion
|
|
47394
|
-
export { yellow as $,
|
|
47395
|
-
//# sourceMappingURL=deploy-engine-
|
|
48123
|
+
export { yellow as $, AssemblyReader as $i, safeStackName as $n, assertAssetBucketRegion as $r, requireConfigObject as $t, createPreDeleteFinalSnapshot as A, ResourceTimeoutError as Aa, resolveCaptureObservedState as Ai, malformedOutputsRefusalMessage as An, formatLockExpiry as Ar, carriesDynamicReference as At, isStatefulRecreateTargetSync as B, isMarkedNonRetryable as Ba, MIGRATE_TMP_PREFIX as Bi, refuseMalformedOrphansForDestroy as Bn, isReadableBag as Br, isSettledInstanceState as Bt, namesAnyDeclaredParameter as C, LocalInvokeBuildError as Ca, getDockerImageBySourceHash as Ci, isReadableResourceEntry as Cn, LockManager as Cr, slowCcOperationTimeoutMs as Ct, PRE_DELETE_SNAPSHOT_TYPES as D, NestedStackChildDirectDestroyError as Da, getLegacyStateBucketName as Di, malformedOrphanRecordsWarning as Dn, buildForceUnlockCommand as Dr, disableInstanceApiTermination as Dt, ATOMIC_FINAL_SNAPSHOT_TYPES as E, MalformedProducerRecordRefusalError as Ea, getDefaultStateBucketName as Ei, malformedLocalOutputsWarning as En, UNREPRODUCIBLE_LOCK_CLAUSE as Er, deleteSkipReason as Et, extractDeploymentEventError as F, SynthesisError as Fa, stateBucketExistenceConfirmed as Fi, malformedResourcesWarning as Fn, hasReadableExportSet as Fr, isImpossibleEmptyStoredAttribute as Ft, toCfnInteger as G, markRedactedCause as Ga, injectiveKeyPrefix as Gi, refuseMalformedResourcesForDestroy as Gn, stringifyValue as Gr, coerceCfnBoolean as Gt, WARM_THROUGHPUT_MEMBERS as H, isThrottlingError as Ha, uploadCfnTemplate as Hi, refuseMalformedOutputsForDestroy as Hn, orphansCarriedFrom as Hr, normalizeAwsTagsToCfn as Ht, makeSiblingClaimReader as I, formatError as Ia, warnDeprecatedNoPrefixCliFlag as Ii, malformedStateRefusalMessage as In, hasReasonlessBaselineRefusal as Ir, isNamelessDynamicReferenceError as It, bold as J, safeStringify$1 as Ja, decodeListingKey as Ji, repairMalformedOutputsForReadOnly as Jn, createAssetRedirectResolver as Jr, configIntegerRefusal as Jt, toFiniteNumber as K, retryClassificationText as Ka, producerCoordinateKey as Ki, refuseMalformedState as Kn, WorkGraph as Kr, coerceCfnInteger as Kt, planOrphanAdoption as L, isCdkdError as La, ssmResolvedValueType as Li, refuseDivergentRecordRegionForDestroy as Ln, hasUnverifiableParameterRefusal as Lr, isUnboundTemplateParameter as Lt, refusesFinalSnapshot as M, StackHasActiveImportsError as Ma, resolveStateBucketWithDefault as Mi, malformedRenderedContainersWarning as Mn, CUSTOM_RESOURCE_RESPONSE_PREFIX as Mr, cfnRefValueFromPhysicalId as Mt, unsupportedFinalSnapshotError as N, StackTerminationProtectionError as Na, resolveStateBucketWithDefaultAndSource as Ni, malformedResourceEntriesWarning as Nn, DEFAULT_STATE_PREFIX as Nr, coerceParameterTypedValue as Nt, buildFinalSnapshotIdentifier as O, PartialFailureError as Oa, resolveApp as Oi, malformedOrphansRefusalMessage as On, buildLockContentionMessage as Or, isTerminationProtectionPropagationError as Ot, makeCanonicalizePropertiesFn as P, StateError as Pa, resolveUseCdkBootstrapAssets as Pi, malformedResourcePropertiesWarning as Pn, exportNamesCarriedFrom as Pr, getAccountInfo as Pt, red as Q, derivePartitionAndUrlSuffix as Qi, safeRegion as Qn, BOOTSTRAP_MARKER_PREFIX as Qr, requireConfigArray as Qt, MULTI_REGION_RECREATE_BLOCKED_TYPES as R, normalizeAwsError as Ra, CFN_TEMPLATE_BODY_LIMIT as Ri, refuseMalformedNestedChildOutputs as Rn, importableOutputKeys as Rr, parameterTypeMayLoseSecretIdentity as Rt, computeParameterTaint as S, IntrinsicResolutionRefusalError as Sa, AssetManifestLoader as Si, hasReadableResources as Sn, scrubResourceRecord as Sr, isWaitAbandonedError as St, resourcesNamingDeclaredParameter as T, LockError as Ta, synthesisStatusMessage as Ti, malformedExportSourceWarning as Tn, rebuildClientForBucketRegion as Tr, deleteIndeterminateGuards as Tt, coerceWarmThroughput as U, isTransientServerError as Ua, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Ui, refuseMalformedResourceEntries as Un, shouldRetainResource as Ur, resolveExplicitPhysicalId as Ut, renderStatefulReason as V, isRetryableTransientError as Va, findLargeInlineResources as Vi, refuseMalformedOutputs as Vn, orphansAfterRollback as Vr, WAFv2WebACLProvider as Vt, isWarmThroughputDecrease as W, markNonRetryable as Wa, injectiveKey as Wi, refuseMalformedResourcePropertiesForOrphan as Wn, AssetPublisher as Wr, assertRegionMatch as Wt, gray as X, PARTITION_TABLE as Xi, repairMalformedResourcePropertiesForReadOnly as Xn, rewriteTemplateAssetReferences as Xr, readConfigString as Xt, cyan as Y, __exportAll as Ya, expectedOwnerParam as Yi, repairMalformedResourceEntriesForReadOnly as Yn, loadPublishableAssetManifest as Yr, configStringRefusal as Yt, green as Z, canonicalizeRegion as Zi, repairMalformedResourcesForReadOnly as Zn, AssetModeResolver as Zr, replayWarn as Zt, bagHoldsSecretExpression as _, ConfigError as _a, redactDockerArgvValues as _i, UNREADABLE_ORPHANS_CONTAINER_ROW as _n, maskSecretsInError as _r, endCommandInterruptScope as _t, DeploymentEventsStore as a, nullPrototypeRecord as aa, validateAssetBucketName as ai, s3BucketDomainName as an, TemplateParser as ar, secretBearingStateKeyWarning as at, mergeNoChangeOutputs as b, DeployCancelledError as ba, escapeRegExp$1 as bi, hasReadableOrphans as bn, recoverMaskedOutput as br, startInterruptWatch as bt, replayFailedOperations as c, resolveAssemblyPath as ca, buildDockerImage as ci, s3BucketWebsiteUrl as cn, STATE_SOURCED_READBACK_RULES as cr, IAMRoleProvider as ct, updatePartialMessage as d, AwsClients as da, describeDockerExecFailure as di, applyCallerIdentityCredentials as dn, createSecretMasker as dr, ProviderRegistry as dt, failedStageNote as ea, ensureAssetStorage as ei, requireConfigString as en, findActionableSilentDrops as er, collectDeclaredOutputNames as et, updatePartialReason as f, getAwsClients as fa, describeDockerFailure as fi, callerEnvCredentials as fn, dynamicReferenceTokens as fr, wouldReturnToSdkProvider as ft, computeImplicitDeleteEdges as g, CdkdError as ga, partitionSensitiveEnv as gi, STATE_RESOURCES_MALFORMED as gn, markSameGenerationBag as gr, beginCommandInterruptScope as gt, IMPLICIT_DELETE_DEPENDENCIES as h, AssetError as ha, getDockerCmd as hi, INTRINSIC_KEYS as hn, isSingleDynamicReferenceToken as hr, maskerOrIdentity as ht, DeploymentEventsReader as i, hasPlainPrototype$1 as ia, readBootstrapMarkerBody as ii, s3BucketArn as in, DagBuilder as ir, secretBearing as it, isFinalSnapshotError as j, ResourceUpdateNotSupportedError as ja, resolveSkipPrefix as ji, malformedOutputsWarning as jn, shellQuote as jr, carriesFetchableDynamicReference as jt, ccRoutedFinalSnapshotError as k, ProvisioningError as ka, resolveAutoAssetStorage as ki, malformedOrphansWarning as kn, forceQuitRecoveryClause as kr, IntrinsicFunctionResolver as kt, replayRollback as l, clearBucketRegionCache as la, resolveDockerContextDirectory as li, applyRoleArnIfSet as ln, TEMPLATE_SOURCED_RULES as lr, collectInlinePolicyNamesManagedBySiblings as lt, maskingRetryLogger as m, setAwsClients as ma, formatDockerLoginError as mi, DiffCalculator as mn, identityKeyFor as mr, maskDeep as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, defineOwnKey as na, isCrossRegionRedirect as ni, producerRegionsFromState as nn, describeTypeWithThrottleRetry as nr, exportAliasCollisionScrubWarning as nt, planFailedOps as o, ownValue as oa, validateContainerRepoName as oi, s3BucketDualStackDomainName as on, STATE_SOURCED_BASELINE_RULES as or, secretSafeKeyDisplay as ot, withResourceDeadline as p, resetAwsClients as pa, dockerSpawnEnvWithSensitive as pi, envCredentialsAreAssumedRole as pn, errorCauseChain as pr, createMaskedRetryLogger as pt, formatResourceLine as q, describeAwsFailure as qa, producerRecordKey as qi, repairMalformedOrphansForReadOnly as qn, buildAssetRedirectMap as qr, configBooleanRefusal as qt, DeployEngine as r, hasOwnKey as ra, parseBootstrapMarker as ri, withSharedDrainBudget as rn, withRetry as rr, isExportAliasCollision as rt, planRollback as s, renderAssemblyPathEscape as sa, buildDenyExternalAccessPolicy as si, s3BucketRegionalDomainName as sn, STATE_SOURCED_CROSS_GENERATION_RULES as sr, getCurrentResourceSecrets as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, processStackMessages as ta, getBootstrapMarkerKey as ti, classifyReplaySecretRegion as tn, findSilentDropProperties as tr, collectPublishedOutputNames as tt, resolveReplacementOldType as u, resolveBucketRegion as ua, describeDockerCapturedOutput as ui, AWS_CREDENTIAL_ENV_KEYS as un, carriesSecretMask as ur, clearOnUpdateRemoval as ut, isSecretBearingReferenceString as v, CrossAccountSecretRefusalError as va, runDockerForeground as vi, UNREADABLE_RESOURCES_MAP_ROW as vn, maskSecretsInText as vr, interruptWatchListenerCount as vt, reachableDivergentParameters as w, LocalStartServiceError as wa, Synthesizer as wi, malformedExportNamesWarning as wn, S3StateBackend as wr, UNSPECIFIED_SKIP_REASON as wt, bindingSkippedOutputs as x, DynamicReferenceRegionAmbiguousError as xa, stripControlChars as xi, hasReadableOutputs as xn, redactSecretsForState as xr, CloudControlProvider as xt, keptWholeReasonText as y, DependencyError as ya, runDockerStreaming as yi, displayLogicalId as yn, recordMaskOnlyValue as yr, isInterruptedWaitError as yt, isStatefulRecreateTargetForReplace as z, withErrorHandling as za, CFN_TEMPLATE_URL_LIMIT as zi, refuseMalformedOrphans as zn, importableOutputs as zr, refStateLookupFromResource as zt };
|
|
48124
|
+
//# sourceMappingURL=deploy-engine-CMD4BtQe.js.map
|