@znemz/cfn-include 2.1.12 → 2.1.13

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 (4) hide show
  1. package/README.md +119 -1
  2. package/index.js +114 -225
  3. package/lib/internals.js +174 -0
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -93,7 +93,9 @@ Options:
93
93
  * `--enable` different options / toggles: ['env','eval'] [string] [choices: 'env','eval','env.eval' etc...]
94
94
  * `env` pre-process env vars and inject into templates as they are processed looks for $KEY or ${KEY} matches
95
95
  * `-i, --inject` JSON string payload to use for template injection. (Takes precedence over process.env (if enabled) injection and will be merged on top of process.env)
96
- * `--doLog` console log out include options in recurse step.
96
+ * `--doLog` console log out include options in recurse step. Shows caller parameter to aid debugging nested function calls.
97
+ * `--ref-now-ignore-missing` do not fail if `Fn::RefNow` reference cannot be resolved; instead return in standard CloudFormation `Ref` syntax
98
+ * `--ref-now-ignores <names>` comma-separated list of reference names to ignore if not found (e.g., `OptionalRef1,OptionalRef2`)
97
99
  `cfn-include` also accepts a template passed from stdin
98
100
 
99
101
  ```
@@ -1370,6 +1372,122 @@ Options are query parameters.
1370
1372
 
1371
1373
  - `validate=false` do not validate template [true]
1372
1374
 
1375
+ ## Developer Documentation
1376
+
1377
+ ### Debug Logging with doLog
1378
+
1379
+ The `cfn-include` preprocessor supports comprehensive debug logging for tracing template processing. When enabled, the `doLog` option logs all arguments at each recursion level in the template processing pipeline.
1380
+
1381
+ #### Enabling doLog
1382
+
1383
+ **CLI:**
1384
+ ```bash
1385
+ cfn-include template.yaml --doLog
1386
+ ```
1387
+
1388
+ **Programmatic:**
1389
+ ```javascript
1390
+ include({
1391
+ template: myTemplate,
1392
+ url: 'file:///path/to/template.yaml',
1393
+ doLog: true
1394
+ })
1395
+ ```
1396
+
1397
+ #### Understanding Caller Parameter
1398
+
1399
+ The `recurse` function now includes a `caller` parameter to help identify which function triggered each recursion step. This is invaluable for debugging complex templates with nested function calls. The caller parameter provides a trace path like:
1400
+
1401
+ - `recurse:isArray` - Processing an array
1402
+ - `Fn::Map` - Inside an `Fn::Map` function
1403
+ - `Fn::Include` - Inside an `Fn::Include` function
1404
+ - `recurse:isPlainObject:end` - Final plain object processing
1405
+ - `handleIncludeBody:json` - JSON body being processed
1406
+
1407
+ When `doLog` is enabled, the console output will show the caller for each recursion:
1408
+ ```javascript
1409
+ {
1410
+ base: {...},
1411
+ scope: {...},
1412
+ cft: {...},
1413
+ rootTemplate: {...},
1414
+ caller: "Fn::Map",
1415
+ doEnv: false,
1416
+ doEval: false,
1417
+ ...
1418
+ }
1419
+ ```
1420
+
1421
+ This makes it easy to trace execution flow through nested `Fn::Include`, `Fn::Map`, `Fn::RefNow`, and other functions.
1422
+
1423
+ #### Example Debug Output
1424
+
1425
+ ```bash
1426
+ $ cfn-include examples/base.template --doLog | head -50
1427
+ {
1428
+ base: {
1429
+ protocol: 'file',
1430
+ host: '/Users/SOME_USER/code',
1431
+ path: '/examples/base.template'
1432
+ },
1433
+ scope: {},
1434
+ cft: { AWSTemplateFormatVersion: '2010-09-09', ... },
1435
+ rootTemplate: { AWSTemplateFormatVersion: '2010-09-09', ... },
1436
+ caller: 'recurse:isPlainObject:end',
1437
+ doEnv: false,
1438
+ doEval: false,
1439
+ inject: undefined,
1440
+ doLog: true
1441
+ }
1442
+ ```
1443
+
1444
+ ### Fn::RefNow Improvements
1445
+
1446
+ #### CLI Options for Reference Resolution
1447
+
1448
+ Two new CLI options control how unresolved `Fn::RefNow` references are handled:
1449
+
1450
+ - `--ref-now-ignore-missing`: Do not fail if a reference cannot be resolved. Instead, return the reference in CloudFormation's standard `Ref` syntax, allowing CloudFormation to resolve it at stack creation time.
1451
+
1452
+ - `--ref-now-ignores <names>`: Comma-separated list of specific reference names to ignore if not found. Useful for optional references.
1453
+
1454
+ **Example usage:**
1455
+ ```bash
1456
+ # Ignore all unresolved references
1457
+ cfn-include template.yaml --ref-now-ignore-missing
1458
+
1459
+ # Ignore specific references
1460
+ cfn-include template.yaml --ref-now-ignores "OptionalParam,CustomRef"
1461
+
1462
+ # Combine both
1463
+ cfn-include template.yaml --ref-now-ignore-missing --ref-now-ignores "SpecificRef"
1464
+ ```
1465
+
1466
+ #### rootTemplate Parameter
1467
+
1468
+ The `recurse` function now receives the complete `rootTemplate` for all recursion calls. This enables `Fn::RefNow` to resolve references to CloudFormation resources defined anywhere in the template, even when processing deeply nested includes or function results.
1469
+
1470
+ ### Fn::SubNow and Fn::JoinNow
1471
+
1472
+ New intrinsic functions for immediate string substitution and joining:
1473
+
1474
+ - `Fn::SubNow` - Performs immediate string substitution similar to `Fn::Sub`, but evaluates at template processing time
1475
+ - `Fn::JoinNow` - Joins array elements into a string at template processing time
1476
+
1477
+ See the main documentation sections above for detailed usage.
1478
+
1479
+ ### Template Processing Pipeline
1480
+
1481
+ The template processing follows this call chain for better debugging:
1482
+
1483
+ 1. Entry point calls `recurse()` with `caller: undefined`
1484
+ 2. Array elements call `recurse()` with `caller: 'recurse:isArray'`
1485
+ 3. Each `Fn::*` function calls `recurse()` with `caller: 'Fn::FunctionName'`
1486
+ 4. Final plain object recursion uses `caller: 'recurse:isPlainObject:end'`
1487
+ 5. Include body processing uses `caller: 'handleIncludeBody:json'`
1488
+
1489
+ When combined with `--doLog`, this provides complete visibility into how `cfn-include` processes your templates.
1490
+
1373
1491
  To compile the synopsis run the following command.
1374
1492
 
1375
1493
  ```
package/index.js CHANGED
@@ -25,6 +25,7 @@ const replaceEnv = require('./lib/replaceEnv');
25
25
 
26
26
  const { lowerCamelCase, upperCamelCase } = require('./lib/utils');
27
27
  const { isOurExplicitFunction } = require('./lib/schema');
28
+ const { getAwsPseudoParameters, buildResourceArn } = require('./lib/internals');
28
29
 
29
30
  /**
30
31
  * @param {object} options
@@ -37,6 +38,9 @@ const { isOurExplicitFunction } = require('./lib/schema');
37
38
  * inject { KEY: Value } from where ${KEY}
38
39
  * is substituted with Value
39
40
  * @param {boolean} [options.doLog] log all arguments at the include recurse level
41
+ * @param {Array<string>} [options.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
42
+ * @param {boolean} [options.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
43
+ * @param {object} [options.rootTemplate] the root template object for resource lookups in Fn::RefNow
40
44
  *
41
45
  * Example: Load off off file system
42
46
  * include({
@@ -61,7 +65,12 @@ module.exports = async function (options) {
61
65
  : template;
62
66
  // Resolve template if it's a promise to extract the root template for reference lookups
63
67
  const resolvedTemplate = await Promise.resolve(template);
64
- return recurse({ base, scope, cft: resolvedTemplate, rootTemplate: resolvedTemplate, ...options });
68
+ return recurse({
69
+ base, scope,
70
+ cft: resolvedTemplate,
71
+ rootTemplate: resolvedTemplate,
72
+ ...options,
73
+ });
65
74
  };
66
75
 
67
76
  /**
@@ -91,185 +100,18 @@ module.exports = async function (options) {
91
100
  * @param {Object} opts.inject object to inject { KEY: Value } from where ${KEY}
92
101
  * is subtituted with Value
93
102
  * @param {boolean} opts.doLog log all arguments at the include recurse level
103
+ * @param {Array<string>} [opts.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
104
+ * @param {boolean} [opts.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
105
+ * @param {object} [rootTemplate] the root template object for resource lookups in Fn::RefNow
106
+ * @param {String} [caller] internal use only for logging, to aid in who called recurse Fn::Include, Fn::RefNow, etc..
94
107
  */
95
- function getAwsPseudoParameters() {
96
- return {
97
- 'AWS::AccountId': process.env.AWS_ACCOUNT_ID || process.env.AWS_ACCOUNT_NUM || '${AWS::AccountId}',
98
- 'AWS::Partition': process.env.AWS_PARTITION || 'aws',
99
- 'AWS::Region': process.env.AWS_REGION || '${AWS::Region}',
100
- 'AWS::StackId': process.env.AWS_STACK_ID || '${AWS::StackId}',
101
- 'AWS::StackName': process.env.AWS_STACK_NAME || '${AWS::StackName}',
102
- 'AWS::URLSuffix': process.env.AWS_URL_SUFFIX || 'amazonaws.com',
103
- 'AWS::NotificationARNs': process.env.AWS_NOTIFICATION_ARNS || '${AWS::NotificationARNs}',
104
- };
105
- }
106
-
107
- /**
108
- * Build an ARN for a CloudFormation resource based on its Type and Properties
109
- * Supports both ARN construction and property name resolution
110
- * @param {string} resourceType - The CloudFormation resource type (e.g., 'AWS::IAM::ManagedPolicy')
111
- * @param {object} properties - The resource properties
112
- * @param {object} pseudoParams - AWS pseudo-parameters including AccountId, Region
113
- * @param {object} options - Optional configuration
114
- * @param {string} options.returnType - 'arn' (default) or 'name' to return the resource name/identifier
115
- * @returns {string|null} - The ARN, name, or null if not applicable
116
- */
117
- function buildResourceArn(resourceType, properties = {}, pseudoParams = {}, options = {}) {
118
- const accountId = pseudoParams['AWS::AccountId'] || '${AWS::AccountId}';
119
- const region = pseudoParams['AWS::Region'] || '${AWS::Region}';
120
- const partition = pseudoParams['AWS::Partition'] || 'aws';
121
- const returnType = options.returnType || 'arn';
122
-
123
- // Handle AWS::IAM::ManagedPolicy
124
- if (resourceType === 'AWS::IAM::ManagedPolicy') {
125
- const { ManagedPolicyName, Path } = properties;
126
- if (ManagedPolicyName) {
127
- if (returnType === 'name') {
128
- return ManagedPolicyName;
129
- }
130
- const path = Path || '/';
131
- return `arn:${partition}:iam::${accountId}:policy${path}${ManagedPolicyName}`;
132
- }
133
- }
134
-
135
- // Handle AWS::IAM::Role
136
- if (resourceType === 'AWS::IAM::Role') {
137
- const { RoleName, Path } = properties;
138
- if (RoleName) {
139
- if (returnType === 'name') {
140
- return RoleName;
141
- }
142
- const path = Path || '/';
143
- return `arn:${partition}:iam::${accountId}:role${path}${RoleName}`;
144
- }
145
- }
146
-
147
- // Handle AWS::S3::Bucket
148
- if (resourceType === 'AWS::S3::Bucket') {
149
- const { BucketName } = properties;
150
- if (BucketName) {
151
- if (returnType === 'name') {
152
- return BucketName;
153
- }
154
- return `arn:${partition}:s3:::${BucketName}`;
155
- }
156
- }
157
-
158
- // Handle AWS::Lambda::Function
159
- if (resourceType === 'AWS::Lambda::Function') {
160
- const { FunctionName } = properties;
161
- if (FunctionName) {
162
- if (returnType === 'name') {
163
- return FunctionName;
164
- }
165
- return `arn:${partition}:lambda:${region}:${accountId}:function:${FunctionName}`;
166
- }
167
- }
168
-
169
- // Handle AWS::SQS::Queue
170
- if (resourceType === 'AWS::SQS::Queue') {
171
- const { QueueName } = properties;
172
- if (QueueName) {
173
- if (returnType === 'name') {
174
- return QueueName;
175
- }
176
- return `arn:${partition}:sqs:${region}:${accountId}:${QueueName}`;
177
- }
178
- }
179
-
180
- // Handle AWS::SNS::Topic
181
- if (resourceType === 'AWS::SNS::Topic') {
182
- const { TopicName } = properties;
183
- if (TopicName) {
184
- if (returnType === 'name') {
185
- return TopicName;
186
- }
187
- return `arn:${partition}:sns:${region}:${accountId}:${TopicName}`;
188
- }
189
- }
190
-
191
- // Handle AWS::DynamoDB::Table
192
- if (resourceType === 'AWS::DynamoDB::Table') {
193
- const { TableName } = properties;
194
- if (TableName) {
195
- if (returnType === 'name') {
196
- return TableName;
197
- }
198
- return `arn:${partition}:dynamodb:${region}:${accountId}:table/${TableName}`;
199
- }
200
- }
201
-
202
- // Handle AWS::RDS::DBInstance
203
- if (resourceType === 'AWS::RDS::DBInstance') {
204
- const { DBInstanceIdentifier } = properties;
205
- if (DBInstanceIdentifier) {
206
- if (returnType === 'name') {
207
- return DBInstanceIdentifier;
208
- }
209
- return `arn:${partition}:rds:${region}:${accountId}:db:${DBInstanceIdentifier}`;
210
- }
211
- }
212
-
213
- // Handle AWS::EC2::SecurityGroup
214
- if (resourceType === 'AWS::EC2::SecurityGroup') {
215
- const { GroupName } = properties;
216
- if (GroupName) {
217
- if (returnType === 'name') {
218
- return GroupName;
219
- }
220
- // Security groups need to be referenced by ID in most cases, not ARN
221
- // This returns the name for reference purposes
222
- return GroupName;
223
- }
224
- }
225
-
226
- // Handle AWS::IAM::InstanceProfile
227
- if (resourceType === 'AWS::IAM::InstanceProfile') {
228
- const { InstanceProfileName, Path } = properties;
229
- if (InstanceProfileName) {
230
- if (returnType === 'name') {
231
- return InstanceProfileName;
232
- }
233
- const path = Path || '/';
234
- return `arn:${partition}:iam::${accountId}:instance-profile${path}${InstanceProfileName}`;
235
- }
236
- }
237
-
238
- // Handle AWS::KMS::Key
239
- if (resourceType === 'AWS::KMS::Key') {
240
- // KMS keys are referenced by KeyId or KeyArn in properties
241
- const { KeyId } = properties;
242
- if (KeyId) {
243
- if (returnType === 'name') {
244
- return KeyId;
245
- }
246
- return `arn:${partition}:kms:${region}:${accountId}:key/${KeyId}`;
247
- }
248
- }
249
-
250
- // Handle AWS::SecretsManager::Secret
251
- if (resourceType === 'AWS::SecretsManager::Secret') {
252
- const { Name } = properties;
253
- if (Name) {
254
- if (returnType === 'name') {
255
- return Name;
256
- }
257
- return `arn:${partition}:secretsmanager:${region}:${accountId}:secret:${Name}`;
258
- }
259
- }
260
-
261
- // Add more resource types as needed
262
- return null;
263
- }
264
-
265
- async function recurse({ base, scope, cft, ...opts }) {
108
+ async function recurse({ base, scope, cft, rootTemplate, caller, ...opts }) {
266
109
  if (opts.doLog) {
267
-
268
- console.log({ base, scope, cft, ...opts });
110
+ console.log({ base, scope, cft, rootTemplate, caller, ...opts });
269
111
  }
270
112
  scope = _.clone(scope);
271
113
  if (_.isArray(cft)) {
272
- return Promise.all(cft.map((o) => recurse({ base, scope, cft: o, ...opts })));
114
+ return Promise.all(cft.map((o) => recurse({ base, scope, cft: o, rootTemplate, caller: 'recurse:isArray', ...opts })));
273
115
  }
274
116
  if (_.isPlainObject(cft)) {
275
117
  if (cft['Fn::Map']) {
@@ -294,26 +136,26 @@ async function recurse({ base, scope, cft, ...opts }) {
294
136
  if (args.length === 2) {
295
137
  placeholder = '_';
296
138
  }
297
- return PromiseExt.mapX(recurse({ base, scope, cft: list, ...opts }), (replace, key) => {
139
+ return PromiseExt.mapX(recurse({ base, scope, cft: list, rootTemplate, caller: 'Fn::Map', ...opts }), (replace, key) => {
298
140
  scope = _.clone(scope);
299
141
  scope[placeholder] = replace;
300
142
  if (hasindex) {
301
143
  scope[idx] = key;
302
144
  }
303
145
  const replaced = findAndReplace(scope, _.cloneDeep(body));
304
- return recurse({ base, scope, cft: replaced, ...opts });
146
+ return recurse({ base, scope, cft: replaced, rootTemplate, caller: 'Fn::Map', ...opts });
305
147
  }).then((_cft) => {
306
148
  if (hassize) {
307
149
  _cft = findAndReplace({ [sz]: _cft.length }, _cft);
308
150
  }
309
- return recurse({ base, scope, cft: _cft, ...opts });
151
+ return recurse({ base, scope, cft: _cft, rootTemplate, caller: 'Fn::Map', ...opts });
310
152
  });
311
153
  }
312
154
  if (cft['Fn::Length']) {
313
155
  if (Array.isArray(cft['Fn::Length'])) {
314
156
  return cft['Fn::Length'].length;
315
157
  }
316
- return recurse({ base, scope, cft: cft['Fn::Length'], ...opts }).then((x) => {
158
+ return recurse({ base, scope, cft: cft['Fn::Length'], rootTemplate, caller: 'Fn::Length', ...opts }).then((x) => {
317
159
  if (Array.isArray(x)) {
318
160
  return x.length;
319
161
  }
@@ -329,49 +171,49 @@ async function recurse({ base, scope, cft, ...opts }) {
329
171
  return cft;
330
172
  })
331
173
  .then((_cft) => findAndReplace(scope, _cft))
332
- .then((t) => recurse({ base, scope, cft: t, ...opts }));
174
+ .then((t) => recurse({ base, scope, cft: t, rootTemplate, caller: 'Fn::Include', ...opts }));
333
175
  }
334
176
  if (cft['Fn::Flatten']) {
335
- return recurse({ base, scope, cft: cft['Fn::Flatten'], ...opts }).then(function (json) {
177
+ return recurse({ base, scope, cft: cft['Fn::Flatten'], rootTemplate, caller: 'Fn::Flatten', ...opts }).then(function (json) {
336
178
  return _.flatten(json);
337
179
  });
338
180
  }
339
181
  if (cft['Fn::FlattenDeep']) {
340
- return recurse({ base, scope, cft: cft['Fn::FlattenDeep'], ...opts }).then(
182
+ return recurse({ base, scope, cft: cft['Fn::FlattenDeep'], rootTemplate, caller: 'Fn::FlattenDeep', ...opts }).then(
341
183
  function (json) {
342
184
  return _.flattenDeep(json);
343
185
  },
344
186
  );
345
187
  }
346
188
  if (cft['Fn::Uniq']) {
347
- return recurse({ base, scope, cft: cft['Fn::Uniq'], ...opts }).then(function (json) {
189
+ return recurse({ base, scope, cft: cft['Fn::Uniq'], rootTemplate, caller: 'Fn::Uniq', ...opts }).then(function (json) {
348
190
  return _.uniq(json);
349
191
  });
350
192
  }
351
193
  if (cft['Fn::Compact']) {
352
- return recurse({ base, scope, cft: cft['Fn::Compact'], ...opts }).then(function (json) {
194
+ return recurse({ base, scope, cft: cft['Fn::Compact'], rootTemplate, caller: 'Fn::Compact', ...opts }).then(function (json) {
353
195
  return _.compact(json);
354
196
  });
355
197
  }
356
198
  if (cft['Fn::Concat']) {
357
- return recurse({ base, scope, cft: cft['Fn::Concat'], ...opts }).then(function (json) {
199
+ return recurse({ base, scope, cft: cft['Fn::Concat'], rootTemplate, caller: 'Fn::Concat', ...opts }).then(function (json) {
358
200
  return _.concat(...json);
359
201
  });
360
202
  }
361
203
  if (cft['Fn::Sort']) {
362
- return recurse({ base, scope, cft: cft['Fn::Sort'], ...opts }).then(function (array) {
204
+ return recurse({ base, scope, cft: cft['Fn::Sort'], rootTemplate, caller: 'Fn::Sort', ...opts }).then(function (array) {
363
205
  return array.sort();
364
206
  });
365
207
  }
366
208
  if (cft['Fn::SortedUniq']) {
367
- return recurse({ base, scope, cft: cft['Fn::SortedUniq'], ...opts }).then(
209
+ return recurse({ base, scope, cft: cft['Fn::SortedUniq'], rootTemplate, caller: 'Fn::SortedUniq', ...opts }).then(
368
210
  function (array) {
369
211
  return _.sortedUniq(array.sort());
370
212
  },
371
213
  );
372
214
  }
373
215
  if (cft['Fn::SortBy']) {
374
- return recurse({ base, scope, cft: cft['Fn::SortBy'], ...opts }).then(function ({
216
+ return recurse({ base, scope, cft: cft['Fn::SortBy'], rootTemplate, caller: 'Fn::SortBy', ...opts }).then(function ({
375
217
  list,
376
218
  iteratees,
377
219
  }) {
@@ -379,7 +221,7 @@ async function recurse({ base, scope, cft, ...opts }) {
379
221
  });
380
222
  }
381
223
  if (cft['Fn::SortObject']) {
382
- return recurse({ base, scope, cft: cft['Fn::SortObject'], ...opts }).then(function ({
224
+ return recurse({ base, scope, cft: cft['Fn::SortObject'], rootTemplate, caller: 'Fn::SortObject', ...opts }).then(function ({
383
225
 
384
226
  object,
385
227
  options,
@@ -389,19 +231,19 @@ async function recurse({ base, scope, cft, ...opts }) {
389
231
  });
390
232
  }
391
233
  if (cft['Fn::Without']) {
392
- return recurse({ base, scope, cft: cft['Fn::Without'], ...opts }).then(function (json) {
234
+ return recurse({ base, scope, cft: cft['Fn::Without'], rootTemplate, caller: 'Fn::Without', ...opts }).then(function (json) {
393
235
  json = Array.isArray(json) ? { list: json[0], withouts: json[1] } : json;
394
236
  return _.without(json.list, ...json.withouts);
395
237
  });
396
238
  }
397
239
  if (cft['Fn::Omit']) {
398
- return recurse({ base, scope, cft: cft['Fn::Omit'], ...opts }).then(function (json) {
240
+ return recurse({ base, scope, cft: cft['Fn::Omit'], rootTemplate, caller: 'Fn::Omit', ...opts }).then(function (json) {
399
241
  json = Array.isArray(json) ? { object: json[0], omits: json[1] } : json;
400
242
  return _.omit(json.object, json.omits);
401
243
  });
402
244
  }
403
245
  if (cft['Fn::OmitEmpty']) {
404
- return recurse({ base, scope, cft: cft['Fn::OmitEmpty'], ...opts }).then(
246
+ return recurse({ base, scope, cft: cft['Fn::OmitEmpty'], rootTemplate, caller: 'Fn::OmitEmpty', ...opts }).then(
405
247
  function (json) {
406
248
  // omit falsy values except false, and 0
407
249
  return _.omitBy(json, (v) => !v && v !== false && v !== 0);
@@ -412,7 +254,7 @@ async function recurse({ base, scope, cft, ...opts }) {
412
254
  if (!opts.doEval) {
413
255
  return Promise.reject(new Error('Fn::Eval is not allowed doEval is falsy'));
414
256
  }
415
- return recurse({ base, scope, cft: cft['Fn::Eval'], ...opts }).then(function (json) {
257
+ return recurse({ base, scope, cft: cft['Fn::Eval'], rootTemplate, caller: 'Fn::Eval', ...opts }).then(function (json) {
416
258
  // **WARNING** you have now enabled god mode
417
259
 
418
260
  let { state, script, inject, doLog } = json;
@@ -426,7 +268,7 @@ async function recurse({ base, scope, cft, ...opts }) {
426
268
  });
427
269
  }
428
270
  if (cft['Fn::Filenames']) {
429
- return recurse({ base, scope, cft: cft['Fn::Filenames'], ...opts }).then(
271
+ return recurse({ base, scope, cft: cft['Fn::Filenames'], rootTemplate, caller: 'Fn::Filenames', ...opts }).then(
430
272
  function (json) {
431
273
  json = _.isPlainObject(json) ? { ...json } : { location: json };
432
274
  if (json.doLog) {
@@ -453,14 +295,14 @@ async function recurse({ base, scope, cft, ...opts }) {
453
295
  );
454
296
  }
455
297
  if (cft['Fn::Merge']) {
456
- return recurse({ base, scope, cft: cft['Fn::Merge'], ...opts }).then(function (json) {
298
+ return recurse({ base, scope, cft: cft['Fn::Merge'], rootTemplate, caller: 'Fn::Merge', ...opts }).then(function (json) {
457
299
  delete cft['Fn::Merge'];
458
300
 
459
- return recurse({ base, scope, cft: _.defaults(cft, _.merge.apply(_, json)), ...opts });
301
+ return recurse({ base, scope, cft: _.defaults(cft, _.merge.apply(_, json)), rootTemplate, caller: 'Fn::Merge', ...opts });
460
302
  });
461
303
  }
462
304
  if (cft['Fn::DeepMerge']) {
463
- return recurse({ base, scope, cft: cft['Fn::DeepMerge'], ...opts }).then(
305
+ return recurse({ base, scope, cft: cft['Fn::DeepMerge'], rootTemplate, caller: 'Fn::DeepMerge', ...opts }).then(
464
306
  function (json) {
465
307
  delete cft['Fn::DeepMerge'];
466
308
  let mergedObj = {};
@@ -469,29 +311,29 @@ async function recurse({ base, scope, cft, ...opts }) {
469
311
  mergedObj = deepMerge(mergedObj, j);
470
312
  });
471
313
  }
472
- return recurse({ base, scope, cft: _.defaults(cft, mergedObj), ...opts });
314
+ return recurse({ base, scope, cft: _.defaults(cft, mergedObj), rootTemplate, caller: 'Fn::DeepMerge', ...opts });
473
315
  },
474
316
  );
475
317
  }
476
318
  if (cft['Fn::ObjectKeys']) {
477
- return recurse({ base, scope, cft: cft['Fn::ObjectKeys'], ...opts }).then((json) =>
319
+ return recurse({ base, scope, cft: cft['Fn::ObjectKeys'], rootTemplate, caller: 'Fn::ObjectKeys', ...opts }).then((json) =>
478
320
  Object.keys(json),
479
321
  );
480
322
  }
481
323
  if (cft['Fn::ObjectValues']) {
482
- return recurse({ base, scope, cft: cft['Fn::ObjectValues'], ...opts }).then((json) =>
324
+ return recurse({ base, scope, cft: cft['Fn::ObjectValues'], rootTemplate, caller: 'Fn::ObjectValues', ...opts }).then((json) =>
483
325
  Object.values(json),
484
326
  );
485
327
  }
486
328
  if (cft['Fn::Stringify']) {
487
- return recurse({ base, scope, cft: cft['Fn::Stringify'], ...opts }).then(
329
+ return recurse({ base, scope, cft: cft['Fn::Stringify'], rootTemplate, caller: 'Fn::Stringify', ...opts }).then(
488
330
  function (json) {
489
331
  return JSON.stringify(json);
490
332
  },
491
333
  );
492
334
  }
493
335
  if (cft['Fn::StringSplit']) {
494
- return recurse({ base, scope, cft: cft['Fn::StringSplit'], ...opts }).then(
336
+ return recurse({ base, scope, cft: cft['Fn::StringSplit'], rootTemplate, caller: 'Fn::StringSplit', ...opts }).then(
495
337
  ({ string, separator, doLog }) => {
496
338
  if (!string) {
497
339
  string = '';
@@ -526,7 +368,7 @@ async function recurse({ base, scope, cft, ...opts }) {
526
368
  }
527
369
 
528
370
  if (cft['Fn::Outputs']) {
529
- const outputs = await recurse({ base, scope, cft: cft['Fn::Outputs'], ...opts });
371
+ const outputs = await recurse({ base, scope, cft: cft['Fn::Outputs'], caller: 'Fn::Outputs', ...opts });
530
372
  const result = {};
531
373
 
532
374
  for (const output in outputs) {
@@ -551,7 +393,7 @@ async function recurse({ base, scope, cft, ...opts }) {
551
393
  }
552
394
 
553
395
  if (cft['Fn::Sequence']) {
554
- const outputs = await recurse({ base, scope, cft: cft['Fn::Sequence'], ...opts });
396
+ const outputs = await recurse({ base, scope, cft: cft['Fn::Sequence'], caller: 'Fn::Sequence', ...opts });
555
397
 
556
398
  let [start, stop, step = 1] = outputs;
557
399
  const isString = typeof start === 'string';
@@ -570,7 +412,7 @@ async function recurse({ base, scope, cft, ...opts }) {
570
412
  if (!opts.doEval) {
571
413
  return Promise.reject(new Error('Fn::IfEval is not allowed doEval is falsy'));
572
414
  }
573
- return recurse({ base, scope, cft: cft['Fn::IfEval'], ...opts }).then(function (json) {
415
+ return recurse({ base, scope, cft: cft['Fn::IfEval'], rootTemplate, caller: 'Fn::IfEval', ...opts }).then(function (json) {
574
416
 
575
417
  let { truthy, falsy, evalCond, inject, doLog } = json;
576
418
  if (!evalCond) {
@@ -599,13 +441,13 @@ async function recurse({ base, scope, cft, ...opts }) {
599
441
  }
600
442
 
601
443
  if (condResult) {
602
- return recurse({ base, scope, cft: truthy, ...opts });
444
+ return recurse({ base, scope, cft: truthy, rootTemplate, caller: 'Fn::IfEval', ...opts });
603
445
  }
604
- return recurse({ base, scope, cft: falsy, ...opts });
446
+ return recurse({ base, scope, cft: falsy, rootTemplate, caller: 'Fn::IfEval', ...opts });
605
447
  });
606
448
  }
607
449
  if (cft['Fn::JoinNow']) {
608
- return recurse({ base, scope, cft: cft['Fn::JoinNow'], ...opts }).then((array) => {
450
+ return recurse({ base, scope, cft: cft['Fn::JoinNow'], rootTemplate, caller: 'Fn::JoinNow', ...opts }).then((array) => {
609
451
  // keeps with same format as Fn::Join ~ more complex
610
452
  // vs let [delimitter, ...toJoinArray] = array;
611
453
  let [delimitter, toJoinArray] = array;
@@ -614,7 +456,7 @@ async function recurse({ base, scope, cft, ...opts }) {
614
456
  });
615
457
  }
616
458
  if (cft['Fn::SubNow']) {
617
- return recurse({ base, scope, cft: cft['Fn::SubNow'], ...opts }).then((input) => {
459
+ return recurse({ base, scope, cft: cft['Fn::SubNow'], rootTemplate, caller: 'Fn::SubNow', ...opts }).then((input) => {
618
460
  let template = input;
619
461
  let variables = {};
620
462
 
@@ -641,9 +483,13 @@ async function recurse({ base, scope, cft, ...opts }) {
641
483
  });
642
484
  }
643
485
  if (cft['Fn::RefNow']) {
486
+ // console.log("outter");
487
+ // console.log(opts);
644
488
  // Handle both simple string and object format for Fn::RefNow
645
489
  // e.g., Fn::RefNow: "MyRole" or Fn::RefNow: { Ref: "MyRole" }
646
- return recurse({ base, scope, cft: cft['Fn::RefNow'], ...opts }).then((refInput) => {
490
+ return recurse({ base, scope, cft: cft['Fn::RefNow'], rootTemplate, caller: 'Fn::RefNow', ...opts }).then((refInput) => {
491
+ // console.log("inner");
492
+ // console.log(opts);
647
493
  let refName = refInput;
648
494
  let refOptions = {};
649
495
 
@@ -672,13 +518,13 @@ async function recurse({ base, scope, cft, ...opts }) {
672
518
  }
673
519
 
674
520
  // Check if this is a LogicalResourceId in the Resources section
675
- if (opts.rootTemplate && _.isPlainObject(opts.rootTemplate)) {
676
- const resources = opts.rootTemplate.Resources || {};
521
+ if (rootTemplate && rootTemplate.Resources) {
522
+ const resources = rootTemplate.Resources;
677
523
  if (refName in resources) {
678
524
  const resource = resources[refName];
679
525
  const resourceType = resource.Type;
680
526
  const properties = resource.Properties || {};
681
-
527
+
682
528
  // Determine return type based on key name
683
529
  // If the key ends with "Name" (e.g., RoleName, BucketName), return name/identifier
684
530
  // Otherwise return ARN (default)
@@ -686,10 +532,10 @@ async function recurse({ base, scope, cft, ...opts }) {
686
532
  if (opts.key && opts.key.endsWith('Name')) {
687
533
  returnType = 'name';
688
534
  }
689
-
535
+
690
536
  // Try to build an ARN or name for this resource
691
537
  // Merge ref options with global options (ref options take precedence)
692
- const resourceOptions = {
538
+ const resourceOptions = {
693
539
  returnType,
694
540
  ...opts.refNowReturnType ? { returnType: opts.refNowReturnType } : {},
695
541
  ...refOptions,
@@ -711,7 +557,7 @@ async function recurse({ base, scope, cft, ...opts }) {
711
557
  });
712
558
  }
713
559
  if (cft['Fn::ApplyTags']) {
714
- return recurse({ base, scope, cft: cft['Fn::ApplyTags'], ...opts }).then((json) => {
560
+ return recurse({ base, scope, cft: cft['Fn::ApplyTags'], rootTemplate, caller: 'Fn::ApplyTags', ...opts }).then((json) => {
715
561
  let { tags, Tags, resources } = json;
716
562
  tags = tags || Tags; // allow for both caseing
717
563
  const promises = [];
@@ -737,7 +583,7 @@ async function recurse({ base, scope, cft, ...opts }) {
737
583
  }
738
584
 
739
585
  return Promise.props(
740
- _.mapValues(cft, (template, key) => recurse({ base, scope, cft: template, key, ...opts })),
586
+ _.mapValues(cft, (template, key) => recurse({ base, scope, cft: template, key, rootTemplate, caller: 'recurse:isPlainObject:end', ...opts })),
741
587
  );
742
588
  }
743
589
 
@@ -831,6 +677,21 @@ function fnIncludeOpts(cft, opts) {
831
677
  return cft;
832
678
  }
833
679
 
680
+ /**
681
+ * Process includes for template documents
682
+ * @param {object} base file options (protocol, host, path, relative, raw)
683
+ * @param {object} scope variable scope for Fn::Map substitutions
684
+ * @param {Document|String|Array} cft include specification (location, query, parser, type, etc.)
685
+ * @param {Object} opts processing options
686
+ * @param {boolean} [opts.doEnv] inject environment from process.env
687
+ * @param {boolean} [opts.doEval] allow Fn::Eval to be used
688
+ * @param {Object} [opts.inject] object to inject { KEY: Value }
689
+ * @param {boolean} [opts.doLog] log all arguments at the include recurse level
690
+ * @param {Array<string>} [opts.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
691
+ * @param {boolean} [opts.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
692
+ * @param {object} [opts.rootTemplate] the root template object for resource lookups in Fn::RefNow
693
+ * @returns {Promise} resolved include content
694
+ */
834
695
  async function fnInclude({ base, scope, cft, ...opts }) {
835
696
  let procTemplate = async (template, inject = cft.inject, doEnv = opts.doEnv) =>
836
697
  replaceEnv(template, inject, doEnv);
@@ -852,7 +713,6 @@ async function fnInclude({ base, scope, cft, ...opts }) {
852
713
  cft = fnIncludeOpts(cft, opts);
853
714
 
854
715
  if (cft.doLog) {
855
-
856
716
  console.log({ base, scope, args: cft, ...opts });
857
717
  }
858
718
  // console.log(args)
@@ -872,14 +732,14 @@ async function fnInclude({ base, scope, cft, ...opts }) {
872
732
  if (isGlob(cft, absolute)) {
873
733
  const paths = globSync(absolute).sort();
874
734
  const template = yaml.load(paths.map((_p) => `- Fn::Include: file://${_p}`).join('\n'));
875
- return recurse({ base, scope, cft: template, ...opts });
735
+ return recurse({ base, scope, cft: template, rootTemplate: template, ...opts });
876
736
  }
877
737
  body = readFile(absolute).then(String).then(procTemplate);
878
738
  absolute = `${location.protocol}://${absolute}`;
879
739
  } else if (location.protocol === 's3') {
880
740
  const basedir = pathParse(base.path).dir;
881
741
  const bucket = location.relative ? base.host : location.host;
882
-
742
+
883
743
  let key = location.relative ? url.resolve(`${basedir}/`, location.raw) : location.path;
884
744
  key = key.replace(/^\//, '');
885
745
  absolute = `${location.protocol}://${[bucket, key].join('/')}`;
@@ -894,11 +754,11 @@ async function fnInclude({ base, scope, cft, ...opts }) {
894
754
  .then(procTemplate);
895
755
  } else if (location.protocol && location.protocol.match(/^https?$/)) {
896
756
  const basepath = `${pathParse(base.path).dir}/`;
897
-
757
+
898
758
  absolute = location.relative
899
759
  ? url.resolve(`${location.protocol}://${base.host}${basepath}`, location.raw)
900
760
  : location.raw;
901
-
761
+
902
762
  body = request(absolute).then(procTemplate);
903
763
  }
904
764
  return handleIncludeBody({ scope, args: cft, body, absolute });
@@ -908,6 +768,27 @@ function isGlob(args, str) {
908
768
  return args.isGlob || /.*\*/.test(str);
909
769
  }
910
770
 
771
+ /**
772
+ * Process the resolved include body content
773
+ * @param {object} config configuration object
774
+ * @param {object} config.scope variable scope for substitutions
775
+ * @param {Object} config.args include arguments (type, query, parser, inject, doEnv, doEval, doLog, refNowIgnores, refNowIgnoreMissing, rootTemplate)
776
+ * @param {Promise} config.body promise resolving to the file content
777
+ * @param {string} config.absolute absolute path/URL to the included resource
778
+ * @param {string} [config.args.type='json'] type of content to process (json, string, literal)
779
+ * @param {Object|string|Array} [config.args.query] query to apply to the processed template
780
+ * @param {string} [config.args.parser='lodash'] parser to use for querying (lodash, jsonpath)
781
+ * @param {Object} [config.args.context] context object for literal interpolation
782
+ * @param {Object} [config.args.inject] object to inject { KEY: Value }
783
+ * @param {boolean} [config.args.doEnv] inject environment from process.env
784
+ * @param {boolean} [config.args.doEval] allow Fn::Eval to be used
785
+ * @param {boolean} [config.args.doLog] log all arguments at the include recurse level
786
+ * @param {Array<string>} [config.args.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
787
+ * @param {boolean} [config.args.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
788
+ *
789
+ * rootTeamplate is not an argument here cause it's loaded via arg.type
790
+ * @returns {Promise} processed template content
791
+ */
911
792
  async function handleIncludeBody({ scope, args, body, absolute }) {
912
793
  const procTemplate = (temp) => replaceEnv(temp, args.inject, args.doEnv);
913
794
  try {
@@ -915,18 +796,22 @@ async function handleIncludeBody({ scope, args, body, absolute }) {
915
796
  case 'json': {
916
797
  let b = await body;
917
798
  b = procTemplate(b);
918
- const template = yaml.load(b);
919
-
799
+ const rootTemplate = yaml.load(b);
800
+ const caller = 'handleIncludeBody:json';
920
801
  // recurse all the way down and work your way out
921
802
  const loopTemplate = (temp) => {
922
803
  return recurse({
923
804
  base: parseLocation(absolute),
924
805
  scope,
925
806
  cft: temp,
807
+ caller,
808
+ rootTemplate,
926
809
  doEnv: args.doEnv,
927
810
  doEval: args.doEval,
928
811
  doLog: args.doLog,
929
812
  inject: args.inject,
813
+ refNowIgnores: args.refNowIgnores,
814
+ refNowIgnoreMissing: args.refNowIgnoreMissing, // note we can't use ...args here because of circular ref
930
815
  }).then((_temp) => {
931
816
  if (!_temp || !Object.keys(_temp).length) {
932
817
  return _temp;
@@ -939,7 +824,7 @@ async function handleIncludeBody({ scope, args, body, absolute }) {
939
824
  });
940
825
  };
941
826
 
942
- return loopTemplate(template).then(async (temp) => {
827
+ return loopTemplate(rootTemplate).then(async (temp) => {
943
828
  if (!args.query) {
944
829
  return temp;
945
830
  }
@@ -950,9 +835,13 @@ async function handleIncludeBody({ scope, args, body, absolute }) {
950
835
  base: parseLocation(absolute),
951
836
  scope,
952
837
  cft: args.query,
838
+ caller,
839
+ rootTemplate,
953
840
  doEnv: args.doEnv,
954
841
  doLog: args.doLog,
955
842
  inject: args.inject,
843
+ refNowIgnores: args.refNowIgnores,
844
+ refNowIgnoreMissing: args.refNowIgnoreMissing,
956
845
  });
957
846
  return getParser(args.parser)(temp, query);
958
847
  });
@@ -0,0 +1,174 @@
1
+ function getAwsPseudoParameters() {
2
+ return {
3
+ 'AWS::AccountId': process.env.AWS_ACCOUNT_ID || process.env.AWS_ACCOUNT_NUM || '${AWS::AccountId}',
4
+ 'AWS::Partition': process.env.AWS_PARTITION || 'aws',
5
+ 'AWS::Region': process.env.AWS_REGION || '${AWS::Region}',
6
+ 'AWS::StackId': process.env.AWS_STACK_ID || '${AWS::StackId}',
7
+ 'AWS::StackName': process.env.AWS_STACK_NAME || '${AWS::StackName}',
8
+ 'AWS::URLSuffix': process.env.AWS_URL_SUFFIX || 'amazonaws.com',
9
+ 'AWS::NotificationARNs': process.env.AWS_NOTIFICATION_ARNS || '${AWS::NotificationARNs}',
10
+ };
11
+ }
12
+
13
+ /**
14
+ * Build an ARN for a CloudFormation resource based on its Type and Properties
15
+ * Supports both ARN construction and property name resolution
16
+ * @param {string} resourceType - The CloudFormation resource type (e.g., 'AWS::IAM::ManagedPolicy')
17
+ * @param {object} properties - The resource properties
18
+ * @param {object} pseudoParams - AWS pseudo-parameters including AccountId, Region
19
+ * @param {object} options - Optional configuration
20
+ * @param {string} options.returnType - 'arn' (default) or 'name' to return the resource name/identifier
21
+ * @returns {string|null} - The ARN, name, or null if not applicable
22
+ */
23
+ function buildResourceArn(resourceType, properties = {}, pseudoParams = {}, options = {}) {
24
+ const accountId = pseudoParams['AWS::AccountId'] || '${AWS::AccountId}';
25
+ const region = pseudoParams['AWS::Region'] || '${AWS::Region}';
26
+ const partition = pseudoParams['AWS::Partition'] || 'aws';
27
+ const returnType = options.returnType || 'arn';
28
+
29
+ // Handle AWS::IAM::ManagedPolicy
30
+ if (resourceType === 'AWS::IAM::ManagedPolicy') {
31
+ const { ManagedPolicyName, Path } = properties;
32
+ if (ManagedPolicyName) {
33
+ if (returnType === 'name') {
34
+ return ManagedPolicyName;
35
+ }
36
+ const path = Path || '/';
37
+ return `arn:${partition}:iam::${accountId}:policy${path}${ManagedPolicyName}`;
38
+ }
39
+ }
40
+
41
+ // Handle AWS::IAM::Role
42
+ if (resourceType === 'AWS::IAM::Role') {
43
+ const { RoleName, Path } = properties;
44
+ if (RoleName) {
45
+ if (returnType === 'name') {
46
+ return RoleName;
47
+ }
48
+ const path = Path || '/';
49
+ return `arn:${partition}:iam::${accountId}:role${path}${RoleName}`;
50
+ }
51
+ }
52
+
53
+ // Handle AWS::S3::Bucket
54
+ if (resourceType === 'AWS::S3::Bucket') {
55
+ const { BucketName } = properties;
56
+ if (BucketName) {
57
+ if (returnType === 'name') {
58
+ return BucketName;
59
+ }
60
+ return `arn:${partition}:s3:::${BucketName}`;
61
+ }
62
+ }
63
+
64
+ // Handle AWS::Lambda::Function
65
+ if (resourceType === 'AWS::Lambda::Function') {
66
+ const { FunctionName } = properties;
67
+ if (FunctionName) {
68
+ if (returnType === 'name') {
69
+ return FunctionName;
70
+ }
71
+ return `arn:${partition}:lambda:${region}:${accountId}:function:${FunctionName}`;
72
+ }
73
+ }
74
+
75
+ // Handle AWS::SQS::Queue
76
+ if (resourceType === 'AWS::SQS::Queue') {
77
+ const { QueueName } = properties;
78
+ if (QueueName) {
79
+ if (returnType === 'name') {
80
+ return QueueName;
81
+ }
82
+ return `arn:${partition}:sqs:${region}:${accountId}:${QueueName}`;
83
+ }
84
+ }
85
+
86
+ // Handle AWS::SNS::Topic
87
+ if (resourceType === 'AWS::SNS::Topic') {
88
+ const { TopicName } = properties;
89
+ if (TopicName) {
90
+ if (returnType === 'name') {
91
+ return TopicName;
92
+ }
93
+ return `arn:${partition}:sns:${region}:${accountId}:${TopicName}`;
94
+ }
95
+ }
96
+
97
+ // Handle AWS::DynamoDB::Table
98
+ if (resourceType === 'AWS::DynamoDB::Table') {
99
+ const { TableName } = properties;
100
+ if (TableName) {
101
+ if (returnType === 'name') {
102
+ return TableName;
103
+ }
104
+ return `arn:${partition}:dynamodb:${region}:${accountId}:table/${TableName}`;
105
+ }
106
+ }
107
+
108
+ // Handle AWS::RDS::DBInstance
109
+ if (resourceType === 'AWS::RDS::DBInstance') {
110
+ const { DBInstanceIdentifier } = properties;
111
+ if (DBInstanceIdentifier) {
112
+ if (returnType === 'name') {
113
+ return DBInstanceIdentifier;
114
+ }
115
+ return `arn:${partition}:rds:${region}:${accountId}:db:${DBInstanceIdentifier}`;
116
+ }
117
+ }
118
+
119
+ // Handle AWS::EC2::SecurityGroup
120
+ if (resourceType === 'AWS::EC2::SecurityGroup') {
121
+ const { GroupName } = properties;
122
+ if (GroupName) {
123
+ if (returnType === 'name') {
124
+ return GroupName;
125
+ }
126
+ // Security groups need to be referenced by ID in most cases, not ARN
127
+ // This returns the name for reference purposes
128
+ return GroupName;
129
+ }
130
+ }
131
+
132
+ // Handle AWS::IAM::InstanceProfile
133
+ if (resourceType === 'AWS::IAM::InstanceProfile') {
134
+ const { InstanceProfileName, Path } = properties;
135
+ if (InstanceProfileName) {
136
+ if (returnType === 'name') {
137
+ return InstanceProfileName;
138
+ }
139
+ const path = Path || '/';
140
+ return `arn:${partition}:iam::${accountId}:instance-profile${path}${InstanceProfileName}`;
141
+ }
142
+ }
143
+
144
+ // Handle AWS::KMS::Key
145
+ if (resourceType === 'AWS::KMS::Key') {
146
+ // KMS keys are referenced by KeyId or KeyArn in properties
147
+ const { KeyId } = properties;
148
+ if (KeyId) {
149
+ if (returnType === 'name') {
150
+ return KeyId;
151
+ }
152
+ return `arn:${partition}:kms:${region}:${accountId}:key/${KeyId}`;
153
+ }
154
+ }
155
+
156
+ // Handle AWS::SecretsManager::Secret
157
+ if (resourceType === 'AWS::SecretsManager::Secret') {
158
+ const { Name } = properties;
159
+ if (Name) {
160
+ if (returnType === 'name') {
161
+ return Name;
162
+ }
163
+ return `arn:${partition}:secretsmanager:${region}:${accountId}:secret:${Name}`;
164
+ }
165
+ }
166
+
167
+ // Add more resource types as needed
168
+ return null;
169
+ }
170
+
171
+ module.exports = {
172
+ getAwsPseudoParameters,
173
+ buildResourceArn,
174
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@znemz/cfn-include",
3
- "version": "2.1.12",
3
+ "version": "2.1.13",
4
4
  "description": "Preprocessor for CloudFormation templates with support for loops and flexible include statements",
5
5
  "keywords": [
6
6
  "aws",