@znemz/cfn-include 4.1.3 → 4.1.5
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/index.d.ts.map +1 -1
- package/dist/index.js +39 -664
- package/dist/index.js.map +1 -1
- package/dist/lib/functions/fn-array-ops.d.ts +11 -0
- package/dist/lib/functions/fn-array-ops.d.ts.map +1 -0
- package/dist/lib/functions/fn-array-ops.js +75 -0
- package/dist/lib/functions/fn-array-ops.js.map +1 -0
- package/dist/lib/functions/fn-include.d.ts +6 -0
- package/dist/lib/functions/fn-include.d.ts.map +1 -0
- package/dist/lib/functions/fn-include.js +201 -0
- package/dist/lib/functions/fn-include.js.map +1 -0
- package/dist/lib/functions/fn-length.d.ts +3 -0
- package/dist/lib/functions/fn-length.d.ts.map +1 -0
- package/dist/lib/functions/fn-length.js +13 -0
- package/dist/lib/functions/fn-length.js.map +1 -0
- package/dist/lib/functions/fn-map.d.ts +3 -0
- package/dist/lib/functions/fn-map.d.ts.map +1 -0
- package/dist/lib/functions/fn-map.js +44 -0
- package/dist/lib/functions/fn-map.js.map +1 -0
- package/dist/lib/functions/fn-misc.d.ts +10 -0
- package/dist/lib/functions/fn-misc.d.ts.map +1 -0
- package/dist/lib/functions/fn-misc.js +216 -0
- package/dist/lib/functions/fn-misc.js.map +1 -0
- package/dist/lib/functions/fn-object-ops.d.ts +9 -0
- package/dist/lib/functions/fn-object-ops.d.ts.map +1 -0
- package/dist/lib/functions/fn-object-ops.js +70 -0
- package/dist/lib/functions/fn-object-ops.js.map +1 -0
- package/dist/lib/functions/fn-string-ops.d.ts +8 -0
- package/dist/lib/functions/fn-string-ops.d.ts.map +1 -0
- package/dist/lib/functions/fn-string-ops.js +68 -0
- package/dist/lib/functions/fn-string-ops.js.map +1 -0
- package/dist/lib/functions/helpers.d.ts +6 -0
- package/dist/lib/functions/helpers.d.ts.map +1 -0
- package/dist/lib/functions/helpers.js +76 -0
- package/dist/lib/functions/helpers.js.map +1 -0
- package/dist/lib/functions/registry.d.ts +8 -0
- package/dist/lib/functions/registry.d.ts.map +1 -0
- package/dist/lib/functions/registry.js +47 -0
- package/dist/lib/functions/registry.js.map +1 -0
- package/dist/lib/functions/types.d.ts +42 -0
- package/dist/lib/functions/types.d.ts.map +1 -0
- package/dist/lib/functions/types.js +2 -0
- package/dist/lib/functions/types.js.map +1 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,26 +1,46 @@
|
|
|
1
|
-
import url from 'node:url';
|
|
2
|
-
import path from 'node:path';
|
|
3
1
|
import _ from 'lodash';
|
|
4
|
-
import { glob } from 'glob';
|
|
5
|
-
import sortObject from '@znemz/sort-object';
|
|
6
|
-
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
7
|
-
import { addProxyToClient } from 'aws-sdk-v3-proxy';
|
|
8
|
-
import deepMerge from 'deepmerge';
|
|
9
|
-
import { isTaggableResource } from '@znemz/cft-utils/src/resources/taggable.js';
|
|
10
|
-
import request from './lib/request.js';
|
|
11
|
-
import * as PromiseExt from './lib/promise.js';
|
|
12
|
-
import * as yaml from './lib/yaml.js';
|
|
13
|
-
import { getParser } from './lib/include/query.js';
|
|
14
2
|
import parseLocation from './lib/parselocation.js';
|
|
15
3
|
import replaceEnv from './lib/replaceEnv.js';
|
|
16
|
-
import { lowerCamelCase, upperCamelCase } from './lib/utils.js';
|
|
17
|
-
import { isOurExplicitFunction } from './lib/schema.js';
|
|
18
|
-
import { getAwsPseudoParameters, buildResourceArn } from './lib/internals.js';
|
|
19
|
-
import { cachedReadFile } from './lib/cache.js';
|
|
20
4
|
import { createChildScope } from './lib/scope.js';
|
|
21
5
|
import { promiseProps } from './lib/promise-utils.js';
|
|
22
|
-
|
|
23
|
-
|
|
6
|
+
import { buildRegistry } from './lib/functions/registry.js';
|
|
7
|
+
import { getBoolEnvOpt } from './lib/functions/helpers.js';
|
|
8
|
+
import { MAX_RECURSE_DEPTH } from './lib/functions/types.js';
|
|
9
|
+
// Build registry with recurse reference
|
|
10
|
+
let registry;
|
|
11
|
+
async function recurse(ctx) {
|
|
12
|
+
const { base, cft, rootTemplate, caller, depth = 0, ...opts } = ctx;
|
|
13
|
+
let { scope } = ctx;
|
|
14
|
+
if (depth > MAX_RECURSE_DEPTH) {
|
|
15
|
+
throw new Error(`Maximum recursion depth (${MAX_RECURSE_DEPTH}) exceeded at caller: ${caller}`);
|
|
16
|
+
}
|
|
17
|
+
if (opts.doLog) {
|
|
18
|
+
console.log({ base, scope, cft, rootTemplate, caller, ...opts });
|
|
19
|
+
}
|
|
20
|
+
scope = createChildScope(scope);
|
|
21
|
+
const nextDepth = depth + 1;
|
|
22
|
+
if (Array.isArray(cft)) {
|
|
23
|
+
return Promise.all(cft.map((o) => recurse({ base, scope, cft: o, rootTemplate, caller: 'recurse:isArray', depth: nextDepth, ...opts })));
|
|
24
|
+
}
|
|
25
|
+
if (_.isPlainObject(cft)) {
|
|
26
|
+
const obj = cft;
|
|
27
|
+
// Dispatch to registered handler
|
|
28
|
+
for (const fnName of Object.keys(obj)) {
|
|
29
|
+
const handler = registry.handlers[fnName];
|
|
30
|
+
if (handler) {
|
|
31
|
+
return handler({ ...ctx, scope, depth: nextDepth });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Process remaining properties (no Fn:: match)
|
|
35
|
+
return promiseProps(_.mapValues(obj, (template, key) => recurse({ base, scope, cft: template, key, rootTemplate, caller: 'recurse:isPlainObject:end', depth: nextDepth, ...opts })));
|
|
36
|
+
}
|
|
37
|
+
if (cft === undefined) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return replaceEnv(cft, opts.inject, opts.doEnv);
|
|
41
|
+
}
|
|
42
|
+
// Initialize registry
|
|
43
|
+
registry = buildRegistry(recurse);
|
|
24
44
|
/**
|
|
25
45
|
* Main entry point for cfn-include template processing
|
|
26
46
|
*/
|
|
@@ -33,7 +53,7 @@ export default async function include(options) {
|
|
|
33
53
|
if (base.relative)
|
|
34
54
|
throw new Error('url cannot be relative');
|
|
35
55
|
const processedTemplate = !template
|
|
36
|
-
? fnInclude({ ...options, base, scope, cft: options.url, doEnv, doEval })
|
|
56
|
+
? registry.fnInclude({ ...options, base, scope, cft: options.url, doEnv, doEval })
|
|
37
57
|
: template;
|
|
38
58
|
const resolvedTemplate = await Promise.resolve(processedTemplate);
|
|
39
59
|
return recurse({
|
|
@@ -49,649 +69,4 @@ export default async function include(options) {
|
|
|
49
69
|
refNowIgnoreMissing: options.refNowIgnoreMissing,
|
|
50
70
|
});
|
|
51
71
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Recursively process CloudFormation template, handling all Fn:: intrinsics
|
|
54
|
-
*/
|
|
55
|
-
async function recurse(ctx) {
|
|
56
|
-
const { base, cft, rootTemplate, caller, ...opts } = ctx;
|
|
57
|
-
let { scope } = ctx;
|
|
58
|
-
if (opts.doLog) {
|
|
59
|
-
console.log({ base, scope, cft, rootTemplate, caller, ...opts });
|
|
60
|
-
}
|
|
61
|
-
scope = createChildScope(scope);
|
|
62
|
-
if (Array.isArray(cft)) {
|
|
63
|
-
return Promise.all(cft.map((o) => recurse({ base, scope, cft: o, rootTemplate, caller: 'recurse:isArray', ...opts })));
|
|
64
|
-
}
|
|
65
|
-
if (_.isPlainObject(cft)) {
|
|
66
|
-
const obj = cft;
|
|
67
|
-
if (obj['Fn::Map']) {
|
|
68
|
-
return handleFnMap({ base, scope, cft: obj, rootTemplate, ...opts });
|
|
69
|
-
}
|
|
70
|
-
if (obj['Fn::Length']) {
|
|
71
|
-
const arg = obj['Fn::Length'];
|
|
72
|
-
if (Array.isArray(arg)) {
|
|
73
|
-
return arg.length;
|
|
74
|
-
}
|
|
75
|
-
const result = await recurse({ base, scope, cft: arg, rootTemplate, caller: 'Fn::Length', ...opts });
|
|
76
|
-
return Array.isArray(result) ? result.length : 0;
|
|
77
|
-
}
|
|
78
|
-
if (obj['Fn::Include']) {
|
|
79
|
-
const json = await fnInclude({ base, scope, cft: obj['Fn::Include'], ...opts });
|
|
80
|
-
if (!_.isPlainObject(json))
|
|
81
|
-
return json;
|
|
82
|
-
delete obj['Fn::Include'];
|
|
83
|
-
_.defaults(obj, json);
|
|
84
|
-
const replaced = findAndReplace(scope, obj);
|
|
85
|
-
return recurse({ base, scope, cft: replaced, rootTemplate, caller: 'Fn::Include', ...opts });
|
|
86
|
-
}
|
|
87
|
-
if (obj['Fn::Flatten']) {
|
|
88
|
-
const json = await recurse({ base, scope, cft: obj['Fn::Flatten'], rootTemplate, caller: 'Fn::Flatten', ...opts });
|
|
89
|
-
return json.flat();
|
|
90
|
-
}
|
|
91
|
-
if (obj['Fn::FlattenDeep']) {
|
|
92
|
-
const json = await recurse({ base, scope, cft: obj['Fn::FlattenDeep'], rootTemplate, caller: 'Fn::FlattenDeep', ...opts });
|
|
93
|
-
return json.flat(Infinity);
|
|
94
|
-
}
|
|
95
|
-
if (obj['Fn::Uniq']) {
|
|
96
|
-
const json = await recurse({ base, scope, cft: obj['Fn::Uniq'], rootTemplate, caller: 'Fn::Uniq', ...opts });
|
|
97
|
-
return [...new Set(json)];
|
|
98
|
-
}
|
|
99
|
-
if (obj['Fn::Compact']) {
|
|
100
|
-
const json = await recurse({ base, scope, cft: obj['Fn::Compact'], rootTemplate, caller: 'Fn::Compact', ...opts });
|
|
101
|
-
return json.filter(Boolean);
|
|
102
|
-
}
|
|
103
|
-
if (obj['Fn::Concat']) {
|
|
104
|
-
const json = await recurse({ base, scope, cft: obj['Fn::Concat'], rootTemplate, caller: 'Fn::Concat', ...opts });
|
|
105
|
-
return _.concat(...json);
|
|
106
|
-
}
|
|
107
|
-
if (obj['Fn::Sort']) {
|
|
108
|
-
const array = await recurse({ base, scope, cft: obj['Fn::Sort'], rootTemplate, caller: 'Fn::Sort', ...opts });
|
|
109
|
-
return array.sort();
|
|
110
|
-
}
|
|
111
|
-
if (obj['Fn::SortedUniq']) {
|
|
112
|
-
const array = await recurse({ base, scope, cft: obj['Fn::SortedUniq'], rootTemplate, caller: 'Fn::SortedUniq', ...opts });
|
|
113
|
-
return _.sortedUniq(array.sort());
|
|
114
|
-
}
|
|
115
|
-
if (obj['Fn::SortBy']) {
|
|
116
|
-
const { list, iteratees } = await recurse({ base, scope, cft: obj['Fn::SortBy'], rootTemplate, caller: 'Fn::SortBy', ...opts });
|
|
117
|
-
return _.sortBy(list, iteratees);
|
|
118
|
-
}
|
|
119
|
-
if (obj['Fn::SortObject']) {
|
|
120
|
-
const result = await recurse({ base, scope, cft: obj['Fn::SortObject'], rootTemplate, caller: 'Fn::SortObject', ...opts });
|
|
121
|
-
const { object, options: sortOpts, ...rest } = result;
|
|
122
|
-
return sortObject(object || rest, sortOpts);
|
|
123
|
-
}
|
|
124
|
-
if (obj['Fn::Without']) {
|
|
125
|
-
const json = await recurse({ base, scope, cft: obj['Fn::Without'], rootTemplate, caller: 'Fn::Without', ...opts });
|
|
126
|
-
const normalized = Array.isArray(json) ? { list: json[0], withouts: json[1] } : json;
|
|
127
|
-
return _.without(normalized.list, ...normalized.withouts);
|
|
128
|
-
}
|
|
129
|
-
if (obj['Fn::Omit']) {
|
|
130
|
-
const json = await recurse({ base, scope, cft: obj['Fn::Omit'], rootTemplate, caller: 'Fn::Omit', ...opts });
|
|
131
|
-
const normalized = Array.isArray(json) ? { object: json[0], omits: json[1] } : json;
|
|
132
|
-
return _.omit(normalized.object, normalized.omits);
|
|
133
|
-
}
|
|
134
|
-
if (obj['Fn::OmitEmpty']) {
|
|
135
|
-
const json = await recurse({ base, scope, cft: obj['Fn::OmitEmpty'], rootTemplate, caller: 'Fn::OmitEmpty', ...opts });
|
|
136
|
-
return _.omitBy(json, (v) => !v && v !== false && v !== 0);
|
|
137
|
-
}
|
|
138
|
-
if (obj['Fn::Eval']) {
|
|
139
|
-
if (!opts.doEval) {
|
|
140
|
-
return Promise.reject(new Error('Fn::Eval is not allowed doEval is falsy'));
|
|
141
|
-
}
|
|
142
|
-
const json = await recurse({ base, scope, cft: obj['Fn::Eval'], rootTemplate, caller: 'Fn::Eval', ...opts });
|
|
143
|
-
let { script } = json;
|
|
144
|
-
const { state, inject, doLog } = json;
|
|
145
|
-
script = replaceEnv(script, _.merge(_.cloneDeep(opts.inject), inject), opts.doEnv);
|
|
146
|
-
if (doLog) {
|
|
147
|
-
console.log({ state, script, inject });
|
|
148
|
-
}
|
|
149
|
-
// eslint-disable-next-line no-eval
|
|
150
|
-
return eval(script);
|
|
151
|
-
}
|
|
152
|
-
if (obj['Fn::Filenames']) {
|
|
153
|
-
return handleFnFilenames({ base, scope, cft: obj['Fn::Filenames'], rootTemplate, ...opts });
|
|
154
|
-
}
|
|
155
|
-
if (obj['Fn::Merge']) {
|
|
156
|
-
const json = await recurse({ base, scope, cft: obj['Fn::Merge'], rootTemplate, caller: 'Fn::Merge', ...opts });
|
|
157
|
-
delete obj['Fn::Merge'];
|
|
158
|
-
return recurse({ base, scope, cft: _.defaults(obj, _.merge.apply(_, json)), rootTemplate, caller: 'Fn::Merge', ...opts });
|
|
159
|
-
}
|
|
160
|
-
if (obj['Fn::DeepMerge']) {
|
|
161
|
-
const json = await recurse({ base, scope, cft: obj['Fn::DeepMerge'], rootTemplate, caller: 'Fn::DeepMerge', ...opts });
|
|
162
|
-
delete obj['Fn::DeepMerge'];
|
|
163
|
-
let mergedObj = {};
|
|
164
|
-
if (json?.length) {
|
|
165
|
-
for (const j of json) {
|
|
166
|
-
mergedObj = deepMerge(mergedObj, j);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return recurse({ base, scope, cft: _.defaults(obj, mergedObj), rootTemplate, caller: 'Fn::DeepMerge', ...opts });
|
|
170
|
-
}
|
|
171
|
-
if (obj['Fn::ObjectKeys']) {
|
|
172
|
-
const json = await recurse({ base, scope, cft: obj['Fn::ObjectKeys'], rootTemplate, caller: 'Fn::ObjectKeys', ...opts });
|
|
173
|
-
return Object.keys(json);
|
|
174
|
-
}
|
|
175
|
-
if (obj['Fn::ObjectValues']) {
|
|
176
|
-
const json = await recurse({ base, scope, cft: obj['Fn::ObjectValues'], rootTemplate, caller: 'Fn::ObjectValues', ...opts });
|
|
177
|
-
return Object.values(json);
|
|
178
|
-
}
|
|
179
|
-
if (obj['Fn::Stringify']) {
|
|
180
|
-
const json = await recurse({ base, scope, cft: obj['Fn::Stringify'], rootTemplate, caller: 'Fn::Stringify', ...opts });
|
|
181
|
-
return JSON.stringify(json);
|
|
182
|
-
}
|
|
183
|
-
if (obj['Fn::StringSplit']) {
|
|
184
|
-
const { string = '', separator = ',', doLog } = await recurse({ base, scope, cft: obj['Fn::StringSplit'], rootTemplate, caller: 'Fn::StringSplit', ...opts });
|
|
185
|
-
if (doLog)
|
|
186
|
-
console.log({ string, separator });
|
|
187
|
-
return string.split(separator);
|
|
188
|
-
}
|
|
189
|
-
if (obj['Fn::UpperCamelCase']) {
|
|
190
|
-
return upperCamelCase(obj['Fn::UpperCamelCase']);
|
|
191
|
-
}
|
|
192
|
-
if (obj['Fn::LowerCamelCase']) {
|
|
193
|
-
return lowerCamelCase(obj['Fn::LowerCamelCase']);
|
|
194
|
-
}
|
|
195
|
-
if (obj['Fn::GetEnv']) {
|
|
196
|
-
const args = obj['Fn::GetEnv'];
|
|
197
|
-
if (Array.isArray(args)) {
|
|
198
|
-
const val = process.env[args[0]];
|
|
199
|
-
return val === undefined ? args[1] : val;
|
|
200
|
-
}
|
|
201
|
-
const val = process.env[args];
|
|
202
|
-
if (val === undefined) {
|
|
203
|
-
throw new Error(`environmental variable ${args} is undefined`);
|
|
204
|
-
}
|
|
205
|
-
return val;
|
|
206
|
-
}
|
|
207
|
-
if (obj['Fn::Outputs']) {
|
|
208
|
-
return handleFnOutputs({ base, scope, cft: obj['Fn::Outputs'], rootTemplate, ...opts });
|
|
209
|
-
}
|
|
210
|
-
if (obj['Fn::Sequence']) {
|
|
211
|
-
return handleFnSequence({ base, scope, cft: obj['Fn::Sequence'], rootTemplate, ...opts });
|
|
212
|
-
}
|
|
213
|
-
if (obj['Fn::IfEval']) {
|
|
214
|
-
return handleFnIfEval({ base, scope, cft: obj['Fn::IfEval'], rootTemplate, ...opts });
|
|
215
|
-
}
|
|
216
|
-
if (obj['Fn::JoinNow']) {
|
|
217
|
-
const array = await recurse({ base, scope, cft: obj['Fn::JoinNow'], rootTemplate, caller: 'Fn::JoinNow', ...opts });
|
|
218
|
-
let [delimiter, toJoinArray] = array;
|
|
219
|
-
delimiter = replaceEnv(delimiter, opts.inject, opts.doEnv);
|
|
220
|
-
return toJoinArray.join(delimiter);
|
|
221
|
-
}
|
|
222
|
-
if (obj['Fn::SubNow']) {
|
|
223
|
-
return handleFnSubNow({ base, scope, cft: obj['Fn::SubNow'], rootTemplate, ...opts });
|
|
224
|
-
}
|
|
225
|
-
if (obj['Fn::RefNow']) {
|
|
226
|
-
return handleFnRefNow({ base, scope, cft: obj['Fn::RefNow'], rootTemplate, ...opts });
|
|
227
|
-
}
|
|
228
|
-
if (obj['Fn::ApplyTags']) {
|
|
229
|
-
return handleFnApplyTags({ base, scope, cft: obj['Fn::ApplyTags'], rootTemplate, ...opts });
|
|
230
|
-
}
|
|
231
|
-
// Process remaining properties
|
|
232
|
-
return promiseProps(_.mapValues(obj, (template, key) => recurse({ base, scope, cft: template, key, rootTemplate, caller: 'recurse:isPlainObject:end', ...opts })));
|
|
233
|
-
}
|
|
234
|
-
if (cft === undefined) {
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
return replaceEnv(cft, opts.inject, opts.doEnv);
|
|
238
|
-
}
|
|
239
|
-
// Handler functions for complex Fn:: intrinsics
|
|
240
|
-
async function handleFnMap(ctx) {
|
|
241
|
-
const { base, scope, cft, rootTemplate, ...opts } = ctx;
|
|
242
|
-
const obj = cft;
|
|
243
|
-
const args = obj['Fn::Map'];
|
|
244
|
-
const [list] = args;
|
|
245
|
-
const body = args[args.length - 1];
|
|
246
|
-
let placeholder = args[1];
|
|
247
|
-
let idx;
|
|
248
|
-
let sz;
|
|
249
|
-
let hasindex = false;
|
|
250
|
-
let hassize = false;
|
|
251
|
-
if (Array.isArray(placeholder)) {
|
|
252
|
-
idx = placeholder[1];
|
|
253
|
-
hasindex = true;
|
|
254
|
-
if (placeholder.length > 2) {
|
|
255
|
-
sz = placeholder[2];
|
|
256
|
-
hassize = true;
|
|
257
|
-
}
|
|
258
|
-
placeholder = placeholder[0];
|
|
259
|
-
}
|
|
260
|
-
if (args.length === 2) {
|
|
261
|
-
placeholder = '_';
|
|
262
|
-
}
|
|
263
|
-
let result = await PromiseExt.mapX(recurse({ base, scope, cft: list, rootTemplate, caller: 'Fn::Map', ...opts }), (replace, key) => {
|
|
264
|
-
const additions = { [placeholder]: replace };
|
|
265
|
-
if (hasindex && idx) {
|
|
266
|
-
additions[idx] = key;
|
|
267
|
-
}
|
|
268
|
-
const childScope = createChildScope(scope, additions);
|
|
269
|
-
const replaced = findAndReplace(childScope, _.cloneDeep(body));
|
|
270
|
-
return recurse({ base, scope: childScope, cft: replaced, rootTemplate, caller: 'Fn::Map', ...opts });
|
|
271
|
-
});
|
|
272
|
-
if (hassize && sz) {
|
|
273
|
-
result = findAndReplace({ [sz]: result.length }, result);
|
|
274
|
-
}
|
|
275
|
-
return recurse({ base, scope, cft: result, rootTemplate, caller: 'Fn::Map', ...opts });
|
|
276
|
-
}
|
|
277
|
-
async function handleFnFilenames(ctx) {
|
|
278
|
-
const { base, scope, cft, rootTemplate, ...opts } = ctx;
|
|
279
|
-
const json = await recurse({ base, scope, cft, rootTemplate, caller: 'Fn::Filenames', ...opts });
|
|
280
|
-
const normalized = _.isPlainObject(json) ? { ...json } : { location: json };
|
|
281
|
-
const { location: loc, omitExtension, doLog } = normalized;
|
|
282
|
-
if (doLog)
|
|
283
|
-
console.log(normalized);
|
|
284
|
-
const location = parseLocation(loc);
|
|
285
|
-
if (!_.isEmpty(location) && !location.protocol) {
|
|
286
|
-
location.protocol = base.protocol;
|
|
287
|
-
}
|
|
288
|
-
if (location.protocol === 'file') {
|
|
289
|
-
const absolute = location.relative
|
|
290
|
-
? path.join(path.dirname(base.path || ''), location.host || '', location.path || '')
|
|
291
|
-
: [location.host, location.path].join('');
|
|
292
|
-
const globs = (await glob(absolute)).sort();
|
|
293
|
-
if (omitExtension) {
|
|
294
|
-
return globs.map((f) => path.basename(f, path.extname(f)));
|
|
295
|
-
}
|
|
296
|
-
return globs;
|
|
297
|
-
}
|
|
298
|
-
return 'Unsupported File Type';
|
|
299
|
-
}
|
|
300
|
-
async function handleFnOutputs(ctx) {
|
|
301
|
-
const { base, scope, cft, ...opts } = ctx;
|
|
302
|
-
const outputs = await recurse({ base, scope, cft, caller: 'Fn::Outputs', ...opts });
|
|
303
|
-
const result = {};
|
|
304
|
-
for (const output in outputs) {
|
|
305
|
-
const val = outputs[output];
|
|
306
|
-
const exp = {
|
|
307
|
-
Export: { Name: { 'Fn::Sub': '${AWS::StackName}:' + output } },
|
|
308
|
-
};
|
|
309
|
-
if (!Array.isArray(val) && typeof val === 'object' && val !== null) {
|
|
310
|
-
const objVal = val;
|
|
311
|
-
result[output] = {
|
|
312
|
-
Value: { 'Fn::Sub': objVal.Value },
|
|
313
|
-
Condition: objVal.Condition,
|
|
314
|
-
...exp,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
else {
|
|
318
|
-
result[output] = {
|
|
319
|
-
Value: { 'Fn::Sub': val },
|
|
320
|
-
...exp,
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
return result;
|
|
325
|
-
}
|
|
326
|
-
async function handleFnSequence(ctx) {
|
|
327
|
-
const { base, scope, cft, ...opts } = ctx;
|
|
328
|
-
const outputs = await recurse({ base, scope, cft, caller: 'Fn::Sequence', ...opts });
|
|
329
|
-
let [start, stop, step = 1] = outputs;
|
|
330
|
-
const isString = typeof start === 'string';
|
|
331
|
-
if (isString) {
|
|
332
|
-
start = start.charCodeAt(0);
|
|
333
|
-
stop = stop.charCodeAt(0);
|
|
334
|
-
}
|
|
335
|
-
const seq = Array.from({ length: Math.floor((stop - start) / step) + 1 }, (__, i) => start + i * step);
|
|
336
|
-
return isString ? seq.map((i) => String.fromCharCode(i)) : seq;
|
|
337
|
-
}
|
|
338
|
-
async function handleFnIfEval(ctx) {
|
|
339
|
-
const { base, scope, cft, rootTemplate, ...opts } = ctx;
|
|
340
|
-
if (!opts.doEval) {
|
|
341
|
-
return Promise.reject(new Error('Fn::IfEval is not allowed doEval is falsy'));
|
|
342
|
-
}
|
|
343
|
-
const json = await recurse({ base, scope, cft, rootTemplate, caller: 'Fn::IfEval', ...opts });
|
|
344
|
-
let { truthy = '', falsy = '', evalCond, inject, doLog } = json;
|
|
345
|
-
if (!evalCond) {
|
|
346
|
-
return Promise.reject(new Error('Fn::IfEval evalCond is required'));
|
|
347
|
-
}
|
|
348
|
-
evalCond = `(${evalCond})`;
|
|
349
|
-
evalCond = replaceEnv(evalCond, _.merge(_.cloneDeep(opts.inject), inject), opts.doEnv);
|
|
350
|
-
truthy = replaceEnv(truthy, _.merge(_.cloneDeep(opts.inject), inject), opts.doEnv);
|
|
351
|
-
if (falsy) {
|
|
352
|
-
falsy = replaceEnv(falsy, _.merge(_.cloneDeep(opts.inject), inject), opts.doEnv);
|
|
353
|
-
}
|
|
354
|
-
// eslint-disable-next-line no-eval
|
|
355
|
-
const condResult = eval(evalCond);
|
|
356
|
-
if (doLog) {
|
|
357
|
-
console.log({ truthy, falsy, inject, evalCond, condResult });
|
|
358
|
-
}
|
|
359
|
-
if (condResult) {
|
|
360
|
-
return recurse({ base, scope, cft: truthy, rootTemplate, caller: 'Fn::IfEval', ...opts });
|
|
361
|
-
}
|
|
362
|
-
return recurse({ base, scope, cft: falsy, rootTemplate, caller: 'Fn::IfEval', ...opts });
|
|
363
|
-
}
|
|
364
|
-
async function handleFnSubNow(ctx) {
|
|
365
|
-
const { base, scope, cft, rootTemplate, ...opts } = ctx;
|
|
366
|
-
const input = await recurse({ base, scope, cft, rootTemplate, caller: 'Fn::SubNow', ...opts });
|
|
367
|
-
let template = input;
|
|
368
|
-
let variables = {};
|
|
369
|
-
if (Array.isArray(input)) {
|
|
370
|
-
[template, variables] = input;
|
|
371
|
-
}
|
|
372
|
-
const allVariables = {
|
|
373
|
-
...getAwsPseudoParameters(),
|
|
374
|
-
...opts.inject,
|
|
375
|
-
...variables,
|
|
376
|
-
};
|
|
377
|
-
let result = template.toString();
|
|
378
|
-
_.forEach(allVariables, (value, key) => {
|
|
379
|
-
const regex = new RegExp(`\\$\\{${_.escapeRegExp(key)}\\}`, 'g');
|
|
380
|
-
result = result.replace(regex, String(value));
|
|
381
|
-
});
|
|
382
|
-
return result;
|
|
383
|
-
}
|
|
384
|
-
async function handleFnRefNow(ctx) {
|
|
385
|
-
const { base, scope, cft, rootTemplate, ...opts } = ctx;
|
|
386
|
-
const refInput = await recurse({ base, scope, cft, rootTemplate, caller: 'Fn::RefNow', ...opts });
|
|
387
|
-
let refName = refInput;
|
|
388
|
-
let refOptions = {};
|
|
389
|
-
if (_.isPlainObject(refInput)) {
|
|
390
|
-
const obj = refInput;
|
|
391
|
-
refName = obj.Ref || obj.ref || '';
|
|
392
|
-
refOptions = _.omit(obj, ['Ref', 'ref']);
|
|
393
|
-
}
|
|
394
|
-
if (opts.refNowIgnores?.includes(refName)) {
|
|
395
|
-
return { Ref: refName };
|
|
396
|
-
}
|
|
397
|
-
const allRefs = {
|
|
398
|
-
...getAwsPseudoParameters(),
|
|
399
|
-
...process.env,
|
|
400
|
-
...opts.inject,
|
|
401
|
-
...scope,
|
|
402
|
-
};
|
|
403
|
-
if (refName in allRefs) {
|
|
404
|
-
return allRefs[refName];
|
|
405
|
-
}
|
|
406
|
-
if (rootTemplate?.Resources) {
|
|
407
|
-
const resources = rootTemplate.Resources;
|
|
408
|
-
if (refName in resources) {
|
|
409
|
-
const resource = resources[refName];
|
|
410
|
-
const resourceType = resource.Type;
|
|
411
|
-
const properties = resource.Properties || {};
|
|
412
|
-
let returnType = 'arn';
|
|
413
|
-
if (opts.key?.endsWith('Name')) {
|
|
414
|
-
returnType = 'name';
|
|
415
|
-
}
|
|
416
|
-
const resourceOptions = {
|
|
417
|
-
returnType,
|
|
418
|
-
...(opts.refNowReturnType ? { returnType: opts.refNowReturnType } : {}),
|
|
419
|
-
...refOptions,
|
|
420
|
-
};
|
|
421
|
-
const result = buildResourceArn(resourceType, properties, allRefs, resourceOptions);
|
|
422
|
-
if (result) {
|
|
423
|
-
return result;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
if (opts.refNowIgnoreMissing) {
|
|
428
|
-
return { Ref: refName };
|
|
429
|
-
}
|
|
430
|
-
throw new Error(`Unable to resolve Ref for logical name: ${refName}`);
|
|
431
|
-
}
|
|
432
|
-
async function handleFnApplyTags(ctx) {
|
|
433
|
-
const { base, scope, cft, rootTemplate, ...opts } = ctx;
|
|
434
|
-
const json = await recurse({ base, scope, cft, rootTemplate, caller: 'Fn::ApplyTags', ...opts });
|
|
435
|
-
let { tags, Tags, resources } = json;
|
|
436
|
-
tags = tags || Tags;
|
|
437
|
-
const promises = [];
|
|
438
|
-
_.each(resources, (val, id) => {
|
|
439
|
-
promises.push(isTaggableResource(val.Type).then((isTaggable) => {
|
|
440
|
-
if (isTaggable) {
|
|
441
|
-
resources[id] = deepMerge({
|
|
442
|
-
Properties: {
|
|
443
|
-
Tags: tags,
|
|
444
|
-
},
|
|
445
|
-
}, val);
|
|
446
|
-
}
|
|
447
|
-
return resources[id];
|
|
448
|
-
}));
|
|
449
|
-
});
|
|
450
|
-
await Promise.all(promises);
|
|
451
|
-
return resources;
|
|
452
|
-
}
|
|
453
|
-
// Helper functions
|
|
454
|
-
function findAndReplace(scope, object) {
|
|
455
|
-
let result = object;
|
|
456
|
-
if (typeof result === 'string') {
|
|
457
|
-
for (const find in scope) {
|
|
458
|
-
if (result === find) {
|
|
459
|
-
result = scope[find];
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
if (typeof result === 'string') {
|
|
464
|
-
for (const find in scope) {
|
|
465
|
-
const replace = scope[find];
|
|
466
|
-
const regex = new RegExp(`\\\${${find}}`, 'g');
|
|
467
|
-
if (find !== '_' && result.match(regex)) {
|
|
468
|
-
result = result.replace(regex, String(replace));
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
if (Array.isArray(result)) {
|
|
473
|
-
result = result.map((item) => findAndReplace(scope, item));
|
|
474
|
-
}
|
|
475
|
-
else if (_.isPlainObject(result)) {
|
|
476
|
-
result = _.mapKeys(result, (value, key) => findAndReplace(scope, key));
|
|
477
|
-
for (const key of Object.keys(result)) {
|
|
478
|
-
if (key === 'Fn::Map')
|
|
479
|
-
continue;
|
|
480
|
-
result[key] = findAndReplace(scope, result[key]);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
return result;
|
|
484
|
-
}
|
|
485
|
-
function interpolate(lines, context) {
|
|
486
|
-
return lines.map((line) => {
|
|
487
|
-
const parts = [];
|
|
488
|
-
line
|
|
489
|
-
.split(/({{\w+?}})/g)
|
|
490
|
-
.map((_line) => {
|
|
491
|
-
const match = _line.match(/^{{(\w+)}}$/);
|
|
492
|
-
const value = match ? context[match[1]] : undefined;
|
|
493
|
-
if (!match)
|
|
494
|
-
return _line;
|
|
495
|
-
if (value === undefined)
|
|
496
|
-
return '';
|
|
497
|
-
return value;
|
|
498
|
-
})
|
|
499
|
-
.forEach((part) => {
|
|
500
|
-
const last = parts[parts.length - 1];
|
|
501
|
-
if (_.isPlainObject(part) || _.isPlainObject(last) || !parts.length) {
|
|
502
|
-
parts.push(part);
|
|
503
|
-
}
|
|
504
|
-
else if (parts.length) {
|
|
505
|
-
parts[parts.length - 1] = String(last) + part;
|
|
506
|
-
}
|
|
507
|
-
});
|
|
508
|
-
return parts.filter((part) => part !== '');
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
function fnIncludeOptsFromArray(cft, opts) {
|
|
512
|
-
const [location, query, parser = 'lodash'] = cft;
|
|
513
|
-
return { location, query, parser, ...opts };
|
|
514
|
-
}
|
|
515
|
-
function fnIncludeOpts(cft, opts) {
|
|
516
|
-
if (_.isPlainObject(cft)) {
|
|
517
|
-
return _.merge(cft, _.cloneDeep(opts));
|
|
518
|
-
}
|
|
519
|
-
else if (Array.isArray(cft)) {
|
|
520
|
-
return fnIncludeOptsFromArray(cft, opts);
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
523
|
-
const splits = cft.split('|');
|
|
524
|
-
if (splits.length > 1) {
|
|
525
|
-
return fnIncludeOptsFromArray(splits, opts);
|
|
526
|
-
}
|
|
527
|
-
return { location: cft, ...opts };
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
async function fnInclude(ctx) {
|
|
531
|
-
const { base, scope, cft: cftArg, ...opts } = ctx;
|
|
532
|
-
let cft = fnIncludeOpts(cftArg, opts);
|
|
533
|
-
cft = _.defaults(cft, { type: 'json' });
|
|
534
|
-
let procTemplate = async (template, inject = cft.inject, doEnv = opts.doEnv) => replaceEnv(template, inject, doEnv);
|
|
535
|
-
const handleInjectSetup = () => {
|
|
536
|
-
if (cft.inject) {
|
|
537
|
-
const origProcTemplate = procTemplate;
|
|
538
|
-
procTemplate = async (template) => {
|
|
539
|
-
try {
|
|
540
|
-
const inject = (await recurse({ base, scope, cft: cft.inject, ...opts }));
|
|
541
|
-
const processed = await origProcTemplate(template, inject, opts.doEnv);
|
|
542
|
-
return replaceEnv(processed, inject, opts.doEnv);
|
|
543
|
-
}
|
|
544
|
-
catch {
|
|
545
|
-
return '';
|
|
546
|
-
}
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
|
-
};
|
|
550
|
-
handleInjectSetup();
|
|
551
|
-
if (cft.doLog) {
|
|
552
|
-
console.log({ base, scope, args: cft, ...opts });
|
|
553
|
-
}
|
|
554
|
-
let body;
|
|
555
|
-
let absolute = '';
|
|
556
|
-
const location = parseLocation(cft.location);
|
|
557
|
-
if (!_.isEmpty(location) && !location.protocol) {
|
|
558
|
-
location.protocol = base.protocol;
|
|
559
|
-
}
|
|
560
|
-
if (location.protocol === 'file') {
|
|
561
|
-
absolute = location.relative
|
|
562
|
-
? path.join(path.dirname(base.path || ''), location.host || '', location.path || '')
|
|
563
|
-
: [location.host, location.path].join('');
|
|
564
|
-
cft.inject = { CFN_INCLUDE_DIRNAME: path.dirname(absolute), ...cft.inject };
|
|
565
|
-
handleInjectSetup();
|
|
566
|
-
if (isGlob(cft, absolute)) {
|
|
567
|
-
const paths = (await glob(absolute)).sort();
|
|
568
|
-
const template = yaml.load(paths.map((_p) => `- Fn::Include: file://${_p}`).join('\n'));
|
|
569
|
-
return recurse({ base, scope, cft: template, rootTemplate: template, ...opts });
|
|
570
|
-
}
|
|
571
|
-
body = cachedReadFile(absolute).then(procTemplate);
|
|
572
|
-
absolute = `${location.protocol}://${absolute}`;
|
|
573
|
-
}
|
|
574
|
-
else if (location.protocol === 's3') {
|
|
575
|
-
const basedir = path.parse(base.path || '').dir;
|
|
576
|
-
const bucket = location.relative ? base.host : location.host;
|
|
577
|
-
let key = location.relative ? url.resolve(`${basedir}/`, location.raw || '') : location.path;
|
|
578
|
-
key = (key || '').replace(/^\//, '');
|
|
579
|
-
absolute = `${location.protocol}://${[bucket, key].join('/')}`;
|
|
580
|
-
body = s3
|
|
581
|
-
.send(new GetObjectCommand({
|
|
582
|
-
Bucket: bucket,
|
|
583
|
-
Key: key,
|
|
584
|
-
}))
|
|
585
|
-
.then((res) => res.Body?.transformToString() || '')
|
|
586
|
-
.then(procTemplate);
|
|
587
|
-
}
|
|
588
|
-
else if (location.protocol?.match(/^https?$/)) {
|
|
589
|
-
const basepath = `${path.parse(base.path || '').dir}/`;
|
|
590
|
-
absolute = location.relative
|
|
591
|
-
? url.resolve(`${location.protocol}://${base.host}${basepath}`, location.raw || '')
|
|
592
|
-
: location.raw || '';
|
|
593
|
-
body = request(absolute).then(procTemplate);
|
|
594
|
-
}
|
|
595
|
-
return handleIncludeBody({ scope, args: cft, body: body, absolute });
|
|
596
|
-
}
|
|
597
|
-
function isGlob(args, str) {
|
|
598
|
-
return args.isGlob || /.*\*/.test(str);
|
|
599
|
-
}
|
|
600
|
-
async function handleIncludeBody(config) {
|
|
601
|
-
const { scope, args, body, absolute } = config;
|
|
602
|
-
const procTemplate = (temp) => replaceEnv(temp, args.inject, args.doEnv);
|
|
603
|
-
try {
|
|
604
|
-
switch (args.type) {
|
|
605
|
-
case 'json': {
|
|
606
|
-
let b = await body;
|
|
607
|
-
b = procTemplate(b);
|
|
608
|
-
const rootTemplate = yaml.load(b);
|
|
609
|
-
const caller = 'handleIncludeBody:json';
|
|
610
|
-
const loopTemplate = (temp) => {
|
|
611
|
-
return recurse({
|
|
612
|
-
base: parseLocation(absolute),
|
|
613
|
-
scope,
|
|
614
|
-
cft: temp,
|
|
615
|
-
caller,
|
|
616
|
-
rootTemplate,
|
|
617
|
-
doEnv: args.doEnv,
|
|
618
|
-
doEval: args.doEval,
|
|
619
|
-
doLog: args.doLog,
|
|
620
|
-
inject: args.inject,
|
|
621
|
-
refNowIgnores: args.refNowIgnores,
|
|
622
|
-
refNowIgnoreMissing: args.refNowIgnoreMissing,
|
|
623
|
-
}).then((_temp) => {
|
|
624
|
-
if (!_temp || !Object.keys(_temp).length) {
|
|
625
|
-
return _temp;
|
|
626
|
-
}
|
|
627
|
-
if (isOurExplicitFunction(Object.keys(_temp)[0])) {
|
|
628
|
-
return loopTemplate(_temp);
|
|
629
|
-
}
|
|
630
|
-
return _temp;
|
|
631
|
-
});
|
|
632
|
-
};
|
|
633
|
-
return loopTemplate(rootTemplate).then(async (temp) => {
|
|
634
|
-
if (!args.query) {
|
|
635
|
-
return temp;
|
|
636
|
-
}
|
|
637
|
-
const query = typeof args.query === 'string'
|
|
638
|
-
? replaceEnv(args.query, args.inject, args.doEnv)
|
|
639
|
-
: await recurse({
|
|
640
|
-
base: parseLocation(absolute),
|
|
641
|
-
scope,
|
|
642
|
-
cft: args.query,
|
|
643
|
-
caller,
|
|
644
|
-
rootTemplate,
|
|
645
|
-
doEnv: args.doEnv,
|
|
646
|
-
doLog: args.doLog,
|
|
647
|
-
inject: args.inject,
|
|
648
|
-
refNowIgnores: args.refNowIgnores,
|
|
649
|
-
refNowIgnoreMissing: args.refNowIgnoreMissing,
|
|
650
|
-
});
|
|
651
|
-
return getParser(args.parser)(temp, query);
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
case 'string': {
|
|
655
|
-
const template = await body;
|
|
656
|
-
return procTemplate(template);
|
|
657
|
-
}
|
|
658
|
-
case 'literal': {
|
|
659
|
-
const template = await body;
|
|
660
|
-
const processed = procTemplate(template);
|
|
661
|
-
let lines = JSONifyString(processed);
|
|
662
|
-
if (_.isPlainObject(args.context)) {
|
|
663
|
-
lines = interpolate(lines, args.context);
|
|
664
|
-
}
|
|
665
|
-
return {
|
|
666
|
-
'Fn::Join': ['', lines.flat()],
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
default:
|
|
670
|
-
throw new Error(`Unknown template type to process type: ${args.type}.`);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
catch (e) {
|
|
674
|
-
if ((replaceEnv.IsRegExVar(absolute) && args.ignoreMissingVar) || args.ignoreMissingFile) {
|
|
675
|
-
return '';
|
|
676
|
-
}
|
|
677
|
-
throw e;
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
function JSONifyString(string) {
|
|
681
|
-
const lines = [];
|
|
682
|
-
const split = string.toString().split(/(\r?\n)/);
|
|
683
|
-
for (let idx = 0; idx < split.length; idx++) {
|
|
684
|
-
const line = split[idx];
|
|
685
|
-
if (idx % 2) {
|
|
686
|
-
lines[(idx - 1) / 2] = lines[(idx - 1) / 2] + line;
|
|
687
|
-
}
|
|
688
|
-
else {
|
|
689
|
-
lines.push(line);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
return lines;
|
|
693
|
-
}
|
|
694
|
-
function getBoolEnvOpt(opt, envKey) {
|
|
695
|
-
return process.env[envKey] ? !!process.env[envKey] : !!opt;
|
|
696
|
-
}
|
|
697
72
|
//# sourceMappingURL=index.js.map
|