@znemz/cfn-include 2.1.23 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +196 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/index.d.ts +19 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +697 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/lib/cache.d.ts +12 -0
  10. package/dist/lib/cache.d.ts.map +1 -0
  11. package/{lib → dist/lib}/cache.js +8 -13
  12. package/dist/lib/cache.js.map +1 -0
  13. package/dist/lib/cfnclient.d.ts +17 -0
  14. package/dist/lib/cfnclient.d.ts.map +1 -0
  15. package/dist/lib/cfnclient.js +69 -0
  16. package/dist/lib/cfnclient.js.map +1 -0
  17. package/dist/lib/include/query.d.ts +4 -0
  18. package/dist/lib/include/query.d.ts.map +1 -0
  19. package/dist/lib/include/query.js +14 -0
  20. package/dist/lib/include/query.js.map +1 -0
  21. package/dist/lib/internals.d.ts +10 -0
  22. package/dist/lib/internals.d.ts.map +1 -0
  23. package/dist/lib/internals.js +145 -0
  24. package/dist/lib/internals.js.map +1 -0
  25. package/dist/lib/parselocation.d.ts +3 -0
  26. package/dist/lib/parselocation.d.ts.map +1 -0
  27. package/dist/lib/parselocation.js +19 -0
  28. package/dist/lib/parselocation.js.map +1 -0
  29. package/dist/lib/promise-utils.d.ts +19 -0
  30. package/dist/lib/promise-utils.d.ts.map +1 -0
  31. package/dist/lib/promise-utils.js +36 -0
  32. package/dist/lib/promise-utils.js.map +1 -0
  33. package/dist/lib/promise.d.ts +6 -0
  34. package/dist/lib/promise.d.ts.map +1 -0
  35. package/dist/lib/promise.js +16 -0
  36. package/dist/lib/promise.js.map +1 -0
  37. package/dist/lib/replaceEnv.d.ts +8 -0
  38. package/dist/lib/replaceEnv.d.ts.map +1 -0
  39. package/dist/lib/replaceEnv.js +48 -0
  40. package/dist/lib/replaceEnv.js.map +1 -0
  41. package/dist/lib/request.d.ts +2 -0
  42. package/dist/lib/request.d.ts.map +1 -0
  43. package/dist/lib/request.js +19 -0
  44. package/dist/lib/request.js.map +1 -0
  45. package/dist/lib/schema.d.ts +9 -0
  46. package/dist/lib/schema.d.ts.map +1 -0
  47. package/dist/lib/schema.js +115 -0
  48. package/dist/lib/schema.js.map +1 -0
  49. package/dist/lib/scope.d.ts +23 -0
  50. package/dist/lib/scope.d.ts.map +1 -0
  51. package/dist/lib/scope.js +36 -0
  52. package/dist/lib/scope.js.map +1 -0
  53. package/dist/lib/utils.d.ts +3 -0
  54. package/dist/lib/utils.d.ts.map +1 -0
  55. package/dist/lib/utils.js +13 -0
  56. package/dist/lib/utils.js.map +1 -0
  57. package/dist/lib/yaml.d.ts +8 -0
  58. package/dist/lib/yaml.d.ts.map +1 -0
  59. package/dist/lib/yaml.js +74 -0
  60. package/dist/lib/yaml.js.map +1 -0
  61. package/dist/types/index.d.ts +6 -0
  62. package/dist/types/index.d.ts.map +1 -0
  63. package/dist/types/index.js +6 -0
  64. package/dist/types/index.js.map +1 -0
  65. package/dist/types/options.d.ts +88 -0
  66. package/dist/types/options.d.ts.map +1 -0
  67. package/dist/types/options.js +5 -0
  68. package/dist/types/options.js.map +1 -0
  69. package/dist/types/template.d.ts +290 -0
  70. package/dist/types/template.d.ts.map +1 -0
  71. package/dist/types/template.js +5 -0
  72. package/dist/types/template.js.map +1 -0
  73. package/package.json +23 -10
  74. package/bin/cli.js +0 -204
  75. package/index.js +0 -899
  76. package/lib/cfnclient.js +0 -84
  77. package/lib/include/api.js +0 -13
  78. package/lib/include/query.js +0 -16
  79. package/lib/internals.js +0 -171
  80. package/lib/parselocation.js +0 -15
  81. package/lib/promise-utils.js +0 -46
  82. package/lib/promise.js +0 -18
  83. package/lib/replaceEnv.js +0 -66
  84. package/lib/request.js +0 -22
  85. package/lib/schema.js +0 -117
  86. package/lib/scope.js +0 -52
  87. package/lib/utils.js +0 -16
  88. package/lib/yaml.js +0 -76
package/index.js DELETED
@@ -1,899 +0,0 @@
1
- import url from 'node:url';
2
- import path from 'node:path';
3
- 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
-
9
- // path.parse is native in Node.js - no need for path-parse package
10
- import deepMerge from 'deepmerge';
11
- import { isTaggableResource } from '@znemz/cft-utils/src/resources/taggable.js';
12
-
13
- import request from './lib/request.js';
14
- import * as PromiseExt from './lib/promise.js';
15
-
16
- const S3 = (opts = {}) => addProxyToClient(new S3Client(opts), { throwOnNoProxy: false });
17
-
18
- const s3 = S3();
19
- import * as yaml from './lib/yaml.js';
20
- import { getParser } from './lib/include/query.js';
21
- import parseLocation from './lib/parselocation.js';
22
- import replaceEnv from './lib/replaceEnv.js';
23
-
24
- import { lowerCamelCase, upperCamelCase } from './lib/utils.js';
25
- import { isOurExplicitFunction } from './lib/schema.js';
26
- import { getAwsPseudoParameters, buildResourceArn } from './lib/internals.js';
27
- import { cachedReadFile } from './lib/cache.js';
28
- import { createChildScope } from './lib/scope.js';
29
- import { promiseProps } from './lib/promise-utils.js';
30
-
31
- /**
32
- * @param {object} options
33
- * @param {object} [options.template] JSON|Yaml Object Document
34
- * optional and can be derived from url
35
- * @param {string} [options.url] '(file|s3):///SOME_FILE_PATH.(json|yaml)'
36
- * @param {boolean} options.doEnv inject environment from process.env as well
37
- * @param {boolean} options.doEval allow Fn::Eval to be used (js eval)
38
- * @param {Object.<string, string>} [options.inject] object to
39
- * inject { KEY: Value } from where ${KEY}
40
- * is substituted with Value
41
- * @param {boolean} [options.doLog] log all arguments at the include recurse level
42
- * @param {Array<string>} [options.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
43
- * @param {boolean} [options.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
44
- * @param {object} [options.rootTemplate] the root template object for resource lookups in Fn::RefNow
45
- *
46
- * Example: Load off off file system
47
- * include({
48
- * template: yaml.load(template),
49
- * url: `file://${location}`,
50
- * doEnv: opts.enable === 'env',
51
- * inject: opts.inject,
52
- * doLog: opts.doLog,
53
- * doEval: opts.doEval, -- allow Fn::Eval to be used
54
- * })
55
- */
56
- export default async function (options) {
57
- let { template } = options;
58
- options.doEnv = getBoolEnvOpt(options.doEnv, 'CFN_INCLUDE_DO_ENV');
59
- options.doEval = getBoolEnvOpt(options.doEval, 'CFN_INCLUDE_DO_EVAL');
60
-
61
- const base = parseLocation(options.url);
62
- const scope = options.scope || {};
63
- if (base.relative) throw new Error('url cannot be relative');
64
- template = !template
65
- ? fnInclude({ base, scope, cft: options.url, ...options })
66
- : template;
67
- // Resolve template if it's a promise to extract the root template for reference lookups
68
- const resolvedTemplate = await Promise.resolve(template);
69
- return recurse({
70
- base, scope,
71
- cft: resolvedTemplate,
72
- rootTemplate: resolvedTemplate,
73
- ...options,
74
- });
75
- }
76
-
77
- /**
78
- * @param {object} base file options
79
- * @param {string} base.protocol 'file' | 's3
80
- * @param {string} [base.host] url to download file from
81
- * @param {string} base.path 'SOME_FILE_PATH.(json|yaml)',
82
- * @param {boolean} [base.relative] is the file path relative?
83
- * @param {string} [base.raw] '(file|s3):///SOME_FILE_PATH.(json|yaml)'
84
- * @param {object} [scope] initally {} and optional eventually defined
85
- * derrived in Fn::Map for template find and replace
86
- * IE:
87
- * TEMPLATE:
88
- * {
89
- * "Fn::Map": [
90
- * [1, 2], {
91
- * "Value": "_"
92
- * }
93
- * ]
94
- *}
95
- * yields:
96
- * { scope: { _: 1 } }
97
- * @param {Document|String|Promise<DocumentString>} cft
98
- * Document object is recursed at every Field level and eventually reduced to a string
99
- * @param {Object} opts
100
- * @param {boolean} opts.doEnv inject environment from process.env as well
101
- * @param {Object} opts.inject object to inject { KEY: Value } from where ${KEY}
102
- * is subtituted with Value
103
- * @param {boolean} opts.doLog log all arguments at the include recurse level
104
- * @param {Array<string>} [opts.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
105
- * @param {boolean} [opts.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
106
- * @param {object} [rootTemplate] the root template object for resource lookups in Fn::RefNow
107
- * @param {String} [caller] internal use only for logging, to aid in who called recurse Fn::Include, Fn::RefNow, etc..
108
- */
109
- async function recurse({ base, scope, cft, rootTemplate, caller, ...opts }) {
110
- if (opts.doLog) {
111
- console.log({ base, scope, cft, rootTemplate, caller, ...opts });
112
- }
113
- // Use Object.create() for O(1) child scope creation instead of O(n) clone
114
- scope = createChildScope(scope);
115
- if (Array.isArray(cft)) {
116
- return Promise.all(cft.map((o) => recurse({ base, scope, cft: o, rootTemplate, caller: 'recurse:isArray', ...opts })));
117
- }
118
- if (_.isPlainObject(cft)) {
119
- if (cft['Fn::Map']) {
120
- const args = cft['Fn::Map'];
121
- const [list] = args;
122
- const body = args[args.length - 1];
123
- let placeholder = args[1],
124
- idx,
125
- sz,
126
- hasindex = false,
127
- hassize = false;
128
- if (Array.isArray(placeholder)) {
129
- // multiple placeholders
130
- idx = placeholder[1];
131
- hasindex = true;
132
- if (placeholder.length > 2) {
133
- sz = placeholder[2];
134
- hassize = true;
135
- }
136
- placeholder = placeholder[0];
137
- }
138
- if (args.length === 2) {
139
- placeholder = '_';
140
- }
141
- return PromiseExt.mapX(recurse({ base, scope, cft: list, rootTemplate, caller: 'Fn::Map', ...opts }), (replace, key) => {
142
- // Use Object.create() for O(1) child scope creation instead of O(n) clone
143
- const additions = { [placeholder]: replace };
144
- if (hasindex) {
145
- additions[idx] = key;
146
- }
147
- const childScope = createChildScope(scope, additions);
148
- const replaced = findAndReplace(childScope, _.cloneDeep(body));
149
- return recurse({ base, scope: childScope, cft: replaced, rootTemplate, caller: 'Fn::Map', ...opts });
150
- }).then((_cft) => {
151
- if (hassize) {
152
- _cft = findAndReplace({ [sz]: _cft.length }, _cft);
153
- }
154
- return recurse({ base, scope, cft: _cft, rootTemplate, caller: 'Fn::Map', ...opts });
155
- });
156
- }
157
- if (cft['Fn::Length']) {
158
- if (Array.isArray(cft['Fn::Length'])) {
159
- return cft['Fn::Length'].length;
160
- }
161
- return recurse({ base, scope, cft: cft['Fn::Length'], rootTemplate, caller: 'Fn::Length', ...opts }).then((x) => {
162
- if (Array.isArray(x)) {
163
- return x.length;
164
- }
165
- return 0;
166
- });
167
- }
168
- if (cft['Fn::Include']) {
169
- return fnInclude({ base, scope, cft: cft['Fn::Include'], ...opts })
170
- .then(function (json) {
171
- if (!_.isPlainObject(json)) return json;
172
- delete cft['Fn::Include'];
173
- _.defaults(cft, json);
174
- return cft;
175
- })
176
- .then((_cft) => findAndReplace(scope, _cft))
177
- .then((t) => recurse({ base, scope, cft: t, rootTemplate, caller: 'Fn::Include', ...opts }));
178
- }
179
- if (cft['Fn::Flatten']) {
180
- return recurse({ base, scope, cft: cft['Fn::Flatten'], rootTemplate, caller: 'Fn::Flatten', ...opts }).then(function (json) {
181
- return json.flat();
182
- });
183
- }
184
- if (cft['Fn::FlattenDeep']) {
185
- return recurse({ base, scope, cft: cft['Fn::FlattenDeep'], rootTemplate, caller: 'Fn::FlattenDeep', ...opts }).then(
186
- function (json) {
187
- return json.flat(Infinity);
188
- },
189
- );
190
- }
191
- if (cft['Fn::Uniq']) {
192
- return recurse({ base, scope, cft: cft['Fn::Uniq'], rootTemplate, caller: 'Fn::Uniq', ...opts }).then(function (json) {
193
- return [...new Set(json)];
194
- });
195
- }
196
- if (cft['Fn::Compact']) {
197
- return recurse({ base, scope, cft: cft['Fn::Compact'], rootTemplate, caller: 'Fn::Compact', ...opts }).then(function (json) {
198
- return json.filter(Boolean);
199
- });
200
- }
201
- if (cft['Fn::Concat']) {
202
- return recurse({ base, scope, cft: cft['Fn::Concat'], rootTemplate, caller: 'Fn::Concat', ...opts }).then(function (json) {
203
- return _.concat(...json);
204
- });
205
- }
206
- if (cft['Fn::Sort']) {
207
- return recurse({ base, scope, cft: cft['Fn::Sort'], rootTemplate, caller: 'Fn::Sort', ...opts }).then(function (array) {
208
- return array.sort();
209
- });
210
- }
211
- if (cft['Fn::SortedUniq']) {
212
- return recurse({ base, scope, cft: cft['Fn::SortedUniq'], rootTemplate, caller: 'Fn::SortedUniq', ...opts }).then(
213
- function (array) {
214
- return _.sortedUniq(array.sort());
215
- },
216
- );
217
- }
218
- if (cft['Fn::SortBy']) {
219
- return recurse({ base, scope, cft: cft['Fn::SortBy'], rootTemplate, caller: 'Fn::SortBy', ...opts }).then(function ({
220
- list,
221
- iteratees,
222
- }) {
223
- return _.sortBy(list, iteratees);
224
- });
225
- }
226
- if (cft['Fn::SortObject']) {
227
- return recurse({ base, scope, cft: cft['Fn::SortObject'], rootTemplate, caller: 'Fn::SortObject', ...opts }).then(function ({
228
-
229
- object,
230
- options,
231
- ...rest // allow object to be optional (implied)
232
- }) {
233
- return sortObject(object || rest, options);
234
- });
235
- }
236
- if (cft['Fn::Without']) {
237
- return recurse({ base, scope, cft: cft['Fn::Without'], rootTemplate, caller: 'Fn::Without', ...opts }).then(function (json) {
238
- json = Array.isArray(json) ? { list: json[0], withouts: json[1] } : json;
239
- return _.without(json.list, ...json.withouts);
240
- });
241
- }
242
- if (cft['Fn::Omit']) {
243
- return recurse({ base, scope, cft: cft['Fn::Omit'], rootTemplate, caller: 'Fn::Omit', ...opts }).then(function (json) {
244
- json = Array.isArray(json) ? { object: json[0], omits: json[1] } : json;
245
- return _.omit(json.object, json.omits);
246
- });
247
- }
248
- if (cft['Fn::OmitEmpty']) {
249
- return recurse({ base, scope, cft: cft['Fn::OmitEmpty'], rootTemplate, caller: 'Fn::OmitEmpty', ...opts }).then(
250
- function (json) {
251
- // omit falsy values except false, and 0
252
- return _.omitBy(json, (v) => !v && v !== false && v !== 0);
253
- },
254
- );
255
- }
256
- if (cft['Fn::Eval']) {
257
- if (!opts.doEval) {
258
- return Promise.reject(new Error('Fn::Eval is not allowed doEval is falsy'));
259
- }
260
- return recurse({ base, scope, cft: cft['Fn::Eval'], rootTemplate, caller: 'Fn::Eval', ...opts }).then(function (json) {
261
- // **WARNING** you have now enabled god mode
262
-
263
- let { state, script, inject, doLog } = json;
264
- script = replaceEnv(script, _.merge(_.cloneDeep(opts.inject), inject), opts.doEnv);
265
- if (doLog) {
266
-
267
- console.log({ state, script, inject });
268
- }
269
-
270
- return eval(script);
271
- });
272
- }
273
- if (cft['Fn::Filenames']) {
274
- return recurse({ base, scope, cft: cft['Fn::Filenames'], rootTemplate, caller: 'Fn::Filenames', ...opts }).then(
275
- async function (json) {
276
- json = _.isPlainObject(json) ? { ...json } : { location: json };
277
- if (json.doLog) {
278
-
279
- console.log(json);
280
- }
281
- const location = parseLocation(json.location);
282
-
283
- if (!_.isEmpty(location) && !location.protocol) {
284
- location.protocol = base.protocol;
285
- }
286
- if (location.protocol === 'file') {
287
- const absolute = location.relative
288
- ? path.join(path.dirname(base.path), location.host, location.path || '')
289
- : [location.host, location.path].join('');
290
- const globs = (await glob(absolute)).sort();
291
- if (json.omitExtension) {
292
- return globs.map((f) => path.basename(f, path.extname(f)));
293
- }
294
- return globs;
295
- }
296
- return 'Unsupported File Type';
297
- },
298
- );
299
- }
300
- if (cft['Fn::Merge']) {
301
- return recurse({ base, scope, cft: cft['Fn::Merge'], rootTemplate, caller: 'Fn::Merge', ...opts }).then(function (json) {
302
- delete cft['Fn::Merge'];
303
-
304
- return recurse({ base, scope, cft: _.defaults(cft, _.merge.apply(_, json)), rootTemplate, caller: 'Fn::Merge', ...opts });
305
- });
306
- }
307
- if (cft['Fn::DeepMerge']) {
308
- return recurse({ base, scope, cft: cft['Fn::DeepMerge'], rootTemplate, caller: 'Fn::DeepMerge', ...opts }).then(
309
- function (json) {
310
- delete cft['Fn::DeepMerge'];
311
- let mergedObj = {};
312
- if (json && json.length) {
313
- for (const j of json) {
314
- mergedObj = deepMerge(mergedObj, j);
315
- }
316
- }
317
- return recurse({ base, scope, cft: _.defaults(cft, mergedObj), rootTemplate, caller: 'Fn::DeepMerge', ...opts });
318
- },
319
- );
320
- }
321
- if (cft['Fn::ObjectKeys']) {
322
- return recurse({ base, scope, cft: cft['Fn::ObjectKeys'], rootTemplate, caller: 'Fn::ObjectKeys', ...opts }).then((json) =>
323
- Object.keys(json),
324
- );
325
- }
326
- if (cft['Fn::ObjectValues']) {
327
- return recurse({ base, scope, cft: cft['Fn::ObjectValues'], rootTemplate, caller: 'Fn::ObjectValues', ...opts }).then((json) =>
328
- Object.values(json),
329
- );
330
- }
331
- if (cft['Fn::Stringify']) {
332
- return recurse({ base, scope, cft: cft['Fn::Stringify'], rootTemplate, caller: 'Fn::Stringify', ...opts }).then(
333
- function (json) {
334
- return JSON.stringify(json);
335
- },
336
- );
337
- }
338
- if (cft['Fn::StringSplit']) {
339
- return recurse({ base, scope, cft: cft['Fn::StringSplit'], rootTemplate, caller: 'Fn::StringSplit', ...opts }).then(
340
- ({ string, separator, doLog }) => {
341
- if (!string) {
342
- string = '';
343
- }
344
-
345
- if (doLog) console.log({ string, separator });
346
- if (!separator) {
347
- separator = ',';
348
- }
349
- return string.split(separator);
350
- },
351
- );
352
- }
353
- if (cft['Fn::UpperCamelCase']) {
354
- return upperCamelCase(cft['Fn::UpperCamelCase']);
355
- }
356
- if (cft['Fn::LowerCamelCase']) {
357
- return lowerCamelCase(cft['Fn::LowerCamelCase']);
358
- }
359
-
360
- if (cft['Fn::GetEnv']) {
361
- const args = cft['Fn::GetEnv'];
362
- if (Array.isArray(args)) {
363
- const val = process.env[args[0]];
364
- return val === undefined ? args[1] : val;
365
- }
366
- const val = process.env[args];
367
- if (val === undefined) {
368
- throw new Error(`environmental variable ${args} is undefined`);
369
- }
370
- return val;
371
- }
372
-
373
- if (cft['Fn::Outputs']) {
374
- const outputs = await recurse({ base, scope, cft: cft['Fn::Outputs'], caller: 'Fn::Outputs', ...opts });
375
- const result = {};
376
-
377
- for (const output in outputs) {
378
- const val = outputs[output];
379
- const exp = {
380
- Export: { Name: { 'Fn::Sub': '${AWS::StackName}:' + output } },
381
- };
382
- if (!Array.isArray(val) && typeof val === 'object') {
383
- result[output] = {
384
- Value: { 'Fn::Sub': val.Value },
385
- Condition: val.Condition,
386
- ...exp,
387
- };
388
- } else {
389
- result[output] = {
390
- Value: { 'Fn::Sub': val },
391
- ...exp,
392
- };
393
- }
394
- }
395
- return result;
396
- }
397
-
398
- if (cft['Fn::Sequence']) {
399
- const outputs = await recurse({ base, scope, cft: cft['Fn::Sequence'], caller: 'Fn::Sequence', ...opts });
400
-
401
- let [start, stop, step = 1] = outputs;
402
- const isString = typeof start === 'string';
403
- if (isString) {
404
- start = start.charCodeAt(0);
405
- stop = stop.charCodeAt(0);
406
- }
407
- const seq = Array.from(
408
- { length: Math.floor((stop - start) / step) + 1 },
409
- (__, i) => start + i * step,
410
- );
411
- return isString ? seq.map((i) => String.fromCharCode(i)) : seq;
412
- }
413
-
414
- if (cft['Fn::IfEval']) {
415
- if (!opts.doEval) {
416
- return Promise.reject(new Error('Fn::IfEval is not allowed doEval is falsy'));
417
- }
418
- return recurse({ base, scope, cft: cft['Fn::IfEval'], rootTemplate, caller: 'Fn::IfEval', ...opts }).then(function (json) {
419
-
420
- let { truthy, falsy, evalCond, inject, doLog } = json;
421
- if (!evalCond) {
422
- return Promise.reject(new Error('Fn::IfEval evalCond is required'));
423
- }
424
- evalCond = `(${evalCond})`;
425
- if (!falsy) {
426
- falsy = '';
427
- }
428
- if (!truthy) {
429
- truthy = '';
430
- }
431
-
432
- evalCond = replaceEnv(evalCond, _.merge(_.cloneDeep(opts.inject), inject), opts.doEnv);
433
- truthy = replaceEnv(truthy, _.merge(_.cloneDeep(opts.inject), inject), opts.doEnv);
434
- if (falsy) {
435
- falsy = replaceEnv(falsy, _.merge(_.cloneDeep(opts.inject), inject), opts.doEnv);
436
- }
437
-
438
-
439
- const condResult = eval(evalCond);
440
-
441
- if (doLog) {
442
-
443
- console.log({ truthy, falsy, inject, evalCond, condResult });
444
- }
445
-
446
- if (condResult) {
447
- return recurse({ base, scope, cft: truthy, rootTemplate, caller: 'Fn::IfEval', ...opts });
448
- }
449
- return recurse({ base, scope, cft: falsy, rootTemplate, caller: 'Fn::IfEval', ...opts });
450
- });
451
- }
452
- if (cft['Fn::JoinNow']) {
453
- return recurse({ base, scope, cft: cft['Fn::JoinNow'], rootTemplate, caller: 'Fn::JoinNow', ...opts }).then((array) => {
454
- // keeps with same format as Fn::Join ~ more complex
455
- // vs let [delimitter, ...toJoinArray] = array;
456
- let [delimitter, toJoinArray] = array;
457
- delimitter = replaceEnv(delimitter, opts.inject, opts.doEnv);
458
- return toJoinArray.join(delimitter);
459
- });
460
- }
461
- if (cft['Fn::SubNow']) {
462
- return recurse({ base, scope, cft: cft['Fn::SubNow'], rootTemplate, caller: 'Fn::SubNow', ...opts }).then((input) => {
463
- let template = input;
464
- let variables = {};
465
-
466
- // Handle both string and [string, variables] formats
467
- if (Array.isArray(input)) {
468
- [template, variables] = input;
469
- }
470
-
471
- // Merge variables with inject and AWS pseudo-parameters
472
- const allVariables = {
473
- ...getAwsPseudoParameters(),
474
- ...opts.inject,
475
- ...variables,
476
- };
477
-
478
- // Perform substitution for ${VarName} pattern
479
- let result = template.toString();
480
- _.forEach(allVariables, (value, key) => {
481
- const regex = new RegExp(`\\$\\{${_.escapeRegExp(key)}\\}`, 'g');
482
- result = result.replace(regex, String(value));
483
- });
484
-
485
- return result;
486
- });
487
- }
488
- if (cft['Fn::RefNow']) {
489
- // console.log("outter");
490
- // console.log(opts);
491
- // Handle both simple string and object format for Fn::RefNow
492
- // e.g., Fn::RefNow: "MyRole" or Fn::RefNow: { Ref: "MyRole" }
493
- return recurse({ base, scope, cft: cft['Fn::RefNow'], rootTemplate, caller: 'Fn::RefNow', ...opts }).then((refInput) => {
494
- // console.log("inner");
495
- // console.log(opts);
496
- let refName = refInput;
497
- let refOptions = {};
498
-
499
- // Parse the input - could be string or object
500
- if (_.isPlainObject(refInput)) {
501
- refName = refInput.Ref || refInput.ref;
502
- refOptions = _.omit(refInput, ['Ref', 'ref']);
503
- }
504
-
505
- // Check if this ref name is in the ignores list
506
- if (opts.refNowIgnores && opts.refNowIgnores.includes(refName)) {
507
- return { Ref: refName };
508
- }
509
-
510
- // Merge AWS pseudo-parameters with inject and scope variables
511
- const allRefs = {
512
- ...getAwsPseudoParameters(),
513
- ...process.env,
514
- ...opts.inject,
515
- ...scope,
516
- };
517
-
518
- // Try to resolve the reference
519
- if (refName in allRefs) {
520
- return allRefs[refName];
521
- }
522
-
523
- // Check if this is a LogicalResourceId in the Resources section
524
- if (rootTemplate && rootTemplate.Resources) {
525
- const resources = rootTemplate.Resources;
526
- if (refName in resources) {
527
- const resource = resources[refName];
528
- const resourceType = resource.Type;
529
- const properties = resource.Properties || {};
530
-
531
- // Determine return type based on key name
532
- // If the key ends with "Name" (e.g., RoleName, BucketName), return name/identifier
533
- // Otherwise return ARN (default)
534
- let returnType = 'arn';
535
- if (opts.key && opts.key.endsWith('Name')) {
536
- returnType = 'name';
537
- }
538
-
539
- // Try to build an ARN or name for this resource
540
- // Merge ref options with global options (ref options take precedence)
541
- const resourceOptions = {
542
- returnType,
543
- ...opts.refNowReturnType ? { returnType: opts.refNowReturnType } : {},
544
- ...refOptions,
545
- };
546
- const result = buildResourceArn(resourceType, properties, allRefs, resourceOptions);
547
- if (result) {
548
- return result;
549
- }
550
- }
551
- }
552
-
553
- // If not found and refNowIgnoreMissing is true, return Ref syntax; otherwise throw error
554
- if (opts.refNowIgnoreMissing) {
555
- return { Ref: refName };
556
- }
557
-
558
- // Default behavior: throw an error (backward compatible)
559
- throw new Error(`Unable to resolve Ref for logical name: ${refName}`);
560
- });
561
- }
562
- if (cft['Fn::ApplyTags']) {
563
- return recurse({ base, scope, cft: cft['Fn::ApplyTags'], rootTemplate, caller: 'Fn::ApplyTags', ...opts }).then((json) => {
564
- let { tags, Tags, resources } = json;
565
- tags = tags || Tags; // allow for both caseing
566
- const promises = [];
567
- _.each(resources, (val, id) => {
568
- promises.push(
569
- isTaggableResource(val.Type).then((isTaggable) => {
570
- if (isTaggable) {
571
- resources[id] = deepMerge(
572
- {
573
- Properties: {
574
- Tags: tags,
575
- },
576
- },
577
- val,
578
- );
579
- }
580
- return resources[id];
581
- }),
582
- );
583
- });
584
- return Promise.all(promises).then(() => resources);
585
- });
586
- }
587
-
588
- return promiseProps(
589
- _.mapValues(cft, (template, key) => recurse({ base, scope, cft: template, key, rootTemplate, caller: 'recurse:isPlainObject:end', ...opts })),
590
- );
591
- }
592
-
593
- if (cft === undefined) {
594
- return null;
595
- }
596
- return replaceEnv(cft, opts.inject, opts.doEnv);
597
- }
598
-
599
- function findAndReplace(scope, object) {
600
- if (typeof object === 'string') {
601
- // Use for...in to walk prototype chain (Object.create() based scopes)
602
- for (const find in scope) {
603
- if (object === find) {
604
- object = scope[find];
605
- }
606
- }
607
- }
608
- if (typeof object === 'string') {
609
- // Use for...in to walk prototype chain (Object.create() based scopes)
610
- for (const find in scope) {
611
- const replace = scope[find];
612
- const regex = new RegExp(`\\\${${find}}`, 'g');
613
- if (find !== '_' && object.match(regex)) {
614
- object = object.replace(regex, replace);
615
- }
616
- }
617
- }
618
- if (Array.isArray(object)) {
619
- object = object.map((item) => findAndReplace(scope, item));
620
- } else if (_.isPlainObject(object)) {
621
- object = _.mapKeys(object, function (value, key) {
622
- return findAndReplace(scope, key);
623
- });
624
- for (const key of Object.keys(object)) {
625
- if (key === 'Fn::Map') continue;
626
- object[key] = findAndReplace(scope, object[key]);
627
- }
628
- }
629
- return object;
630
- }
631
-
632
- function interpolate(lines, context) {
633
- return lines.map(function (line) {
634
- const parts = [];
635
- line
636
- .split(/({{\w+?}})/g)
637
- .map(function (_line) {
638
- const match = _line.match(/^{{(\w+)}}$/);
639
- const value = match ? context[match[1]] : undefined;
640
- if (!match) return _line;
641
- if (value === undefined) {
642
- return '';
643
- }
644
- return value;
645
- })
646
- .forEach(function (part) {
647
- const last = parts[parts.length - 1];
648
- if (_.isPlainObject(part) || _.isPlainObject(last) || !parts.length) {
649
- parts.push(part);
650
- } else if (parts.length) {
651
- parts[parts.length - 1] = last + part;
652
- }
653
- });
654
- return parts.filter(function (part) {
655
- return part !== '';
656
- });
657
- });
658
- }
659
-
660
- function fnIncludeOptsFromArray(cft, opts) {
661
- // Array 1, location, Array 2 Query, Array 3 Optional Parser (default lodash)
662
- const [location, query, parser = 'lodash'] = cft;
663
- cft = { location, query, parser, ...opts };
664
- return cft;
665
- }
666
-
667
- function fnIncludeOpts(cft, opts) {
668
- if (_.isPlainObject(cft)) {
669
- cft = _.merge(cft, _.cloneDeep(opts));
670
- } else if (Array.isArray(cft)) {
671
- cft = fnIncludeOptsFromArray(cft, opts);
672
- } else {
673
- // should be string{
674
- const splits = cft.split('|');
675
- if (splits.length > 1) {
676
- cft = fnIncludeOptsFromArray(splits, opts);
677
- } else {
678
- cft = { location: cft, ...opts };
679
- }
680
- }
681
-
682
- cft = _.defaults(cft, { type: 'json' });
683
- return cft;
684
- }
685
-
686
- /**
687
- * Process includes for template documents
688
- * @param {object} base file options (protocol, host, path, relative, raw)
689
- * @param {object} scope variable scope for Fn::Map substitutions
690
- * @param {Document|String|Array} cft include specification (location, query, parser, type, etc.)
691
- * @param {Object} opts processing options
692
- * @param {boolean} [opts.doEnv] inject environment from process.env
693
- * @param {boolean} [opts.doEval] allow Fn::Eval to be used
694
- * @param {Object} [opts.inject] object to inject { KEY: Value }
695
- * @param {boolean} [opts.doLog] log all arguments at the include recurse level
696
- * @param {Array<string>} [opts.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
697
- * @param {boolean} [opts.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
698
- * @param {object} [opts.rootTemplate] the root template object for resource lookups in Fn::RefNow
699
- * @returns {Promise} resolved include content
700
- */
701
- async function fnInclude({ base, scope, cft, ...opts }) {
702
- let procTemplate = async (template, inject = cft.inject, doEnv = opts.doEnv) =>
703
- replaceEnv(template, inject, doEnv);
704
- const handleInjectSetup = () => {
705
- if (cft.inject) {
706
- const origProcTemplate = procTemplate;
707
- procTemplate = async (template) => {
708
- try {
709
- const inject = await recurse({ base, scope, cft: cft.inject, ...opts });
710
- const processed = await origProcTemplate(template, inject, opts.doEnv);
711
- return replaceEnv(processed, inject, opts.doEnv);
712
- } catch {
713
- return '';
714
- }
715
- };
716
- }
717
- };
718
- handleInjectSetup();
719
- cft = fnIncludeOpts(cft, opts);
720
-
721
- if (cft.doLog) {
722
- console.log({ base, scope, args: cft, ...opts });
723
- }
724
- // console.log(args)
725
- let body;
726
- let absolute;
727
- const location = parseLocation(cft.location);
728
- if (!_.isEmpty(location) && !location.protocol) location.protocol = base.protocol;
729
- if (location.protocol === 'file') {
730
- absolute = location.relative
731
- ? path.join(path.dirname(base.path), location.host, location.path || '')
732
- : [location.host, location.path].join('');
733
-
734
- // allow script to resolve their own, __dirname via ${CFN_INCLUDE_DIRNAME}
735
- cft.inject = { CFN_INCLUDE_DIRNAME: path.dirname(absolute), ...cft.inject };
736
-
737
- handleInjectSetup();
738
- if (isGlob(cft, absolute)) {
739
- const paths = (await glob(absolute)).sort();
740
- const template = yaml.load(paths.map((_p) => `- Fn::Include: file://${_p}`).join('\n'));
741
- return recurse({ base, scope, cft: template, rootTemplate: template, ...opts });
742
- }
743
- body = cachedReadFile(absolute).then(procTemplate);
744
- absolute = `${location.protocol}://${absolute}`;
745
- } else if (location.protocol === 's3') {
746
- const basedir = path.parse(base.path).dir;
747
- const bucket = location.relative ? base.host : location.host;
748
-
749
- let key = location.relative ? url.resolve(`${basedir}/`, location.raw) : location.path;
750
- key = key.replace(/^\//, '');
751
- absolute = `${location.protocol}://${[bucket, key].join('/')}`;
752
- body = s3
753
- .send(
754
- new GetObjectCommand({
755
- Bucket: bucket,
756
- Key: key,
757
- }),
758
- )
759
- .then((res) => res.Body.toString())
760
- .then(procTemplate);
761
- } else if (location.protocol && location.protocol.match(/^https?$/)) {
762
- const basepath = `${path.parse(base.path).dir}/`;
763
-
764
- absolute = location.relative
765
- ? url.resolve(`${location.protocol}://${base.host}${basepath}`, location.raw)
766
- : location.raw;
767
-
768
- body = request(absolute).then(procTemplate);
769
- }
770
- return handleIncludeBody({ scope, args: cft, body, absolute });
771
- }
772
-
773
- function isGlob(args, str) {
774
- return args.isGlob || /.*\*/.test(str);
775
- }
776
-
777
- /**
778
- * Process the resolved include body content
779
- * @param {object} config configuration object
780
- * @param {object} config.scope variable scope for substitutions
781
- * @param {Object} config.args include arguments (type, query, parser, inject, doEnv, doEval, doLog, refNowIgnores, refNowIgnoreMissing, rootTemplate)
782
- * @param {Promise} config.body promise resolving to the file content
783
- * @param {string} config.absolute absolute path/URL to the included resource
784
- * @param {string} [config.args.type='json'] type of content to process (json, string, literal)
785
- * @param {Object|string|Array} [config.args.query] query to apply to the processed template
786
- * @param {string} [config.args.parser='lodash'] parser to use for querying (lodash, jsonpath)
787
- * @param {Object} [config.args.context] context object for literal interpolation
788
- * @param {Object} [config.args.inject] object to inject { KEY: Value }
789
- * @param {boolean} [config.args.doEnv] inject environment from process.env
790
- * @param {boolean} [config.args.doEval] allow Fn::Eval to be used
791
- * @param {boolean} [config.args.doLog] log all arguments at the include recurse level
792
- * @param {Array<string>} [config.args.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
793
- * @param {boolean} [config.args.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
794
- *
795
- * rootTeamplate is not an argument here cause it's loaded via arg.type
796
- * @returns {Promise} processed template content
797
- */
798
- async function handleIncludeBody({ scope, args, body, absolute }) {
799
- const procTemplate = (temp) => replaceEnv(temp, args.inject, args.doEnv);
800
- try {
801
- switch (args.type) {
802
- case 'json': {
803
- let b = await body;
804
- b = procTemplate(b);
805
- const rootTemplate = yaml.load(b);
806
- const caller = 'handleIncludeBody:json';
807
- // recurse all the way down and work your way out
808
- const loopTemplate = (temp) => {
809
- return recurse({
810
- base: parseLocation(absolute),
811
- scope,
812
- cft: temp,
813
- caller,
814
- rootTemplate,
815
- doEnv: args.doEnv,
816
- doEval: args.doEval,
817
- doLog: args.doLog,
818
- inject: args.inject,
819
- refNowIgnores: args.refNowIgnores,
820
- refNowIgnoreMissing: args.refNowIgnoreMissing, // note we can't use ...args here because of circular ref
821
- }).then((_temp) => {
822
- if (!_temp || !Object.keys(_temp).length) {
823
- return _temp;
824
- }
825
- // ONLY RECURSE IF it is one of our funcs.
826
- if (isOurExplicitFunction(Object.keys(_temp)[0])) {
827
- return loopTemplate(_temp);
828
- }
829
- return _temp;
830
- });
831
- };
832
-
833
- return loopTemplate(rootTemplate).then(async (temp) => {
834
- if (!args.query) {
835
- return temp;
836
- }
837
- // once fully recursed we can query the resultant template
838
- const query = typeof args.query === 'string'
839
- ? replaceEnv(args.query, args.inject, args.doEnv)
840
- : await recurse({
841
- base: parseLocation(absolute),
842
- scope,
843
- cft: args.query,
844
- caller,
845
- rootTemplate,
846
- doEnv: args.doEnv,
847
- doLog: args.doLog,
848
- inject: args.inject,
849
- refNowIgnores: args.refNowIgnores,
850
- refNowIgnoreMissing: args.refNowIgnoreMissing,
851
- });
852
- return getParser(args.parser)(temp, query);
853
- });
854
- }
855
- case 'string': {
856
- const template = await body;
857
- return procTemplate(template);
858
- }
859
- case 'literal': {
860
- return body.then(function (template) {
861
- template = procTemplate(template);
862
- let lines = JSONifyString(template);
863
- if (_.isPlainObject(args.context)) {
864
- lines = interpolate(lines, args.context);
865
- }
866
- return {
867
- 'Fn::Join': ['', lines.flat()],
868
- };
869
- });
870
- }
871
- default:
872
- throw new Error(`Unknown template type to process type: ${args.type}.`);
873
- }
874
- } catch (e) {
875
- // if the location matches replaceEnv.IsRegExVar then swallow error and retun ''
876
- if ((replaceEnv.IsRegExVar(absolute) && args.ignoreMissingVar) || args.ignoreMissingFile) {
877
- return '';
878
- }
879
- throw e;
880
- }
881
- }
882
-
883
- function JSONifyString(string) {
884
- const lines = [];
885
- const split = string.toString().split(/(\r?\n)/);
886
- for (let idx = 0; idx < split.length; idx++) {
887
- const line = split[idx];
888
- if (idx % 2) {
889
- lines[(idx - 1) / 2] = lines[(idx - 1) / 2] + line;
890
- } else {
891
- lines.push(line);
892
- }
893
- }
894
- return lines;
895
- }
896
-
897
- function getBoolEnvOpt(opt, envKey) {
898
- return process.env[envKey] ? !!process.env[envKey] : opt;
899
- }