@znemz/cfn-include 2.1.8 → 2.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -44,6 +44,8 @@ For example, [`Fn::Include`](#fninclude) provides a convenient way to include fi
44
44
  - [Fn::Eval](#fneval)
45
45
  - [Fn::IfEval](#fnifeval)
46
46
  - [Fn::JoinNow](#fnjoinnow)
47
+ - [Fn::SubNow](#fsubnow)
48
+ - [Fn::RefNow](#frefnow)
47
49
  - [Fn::ApplyTags](#fnapplytags)
48
50
  - [Fn::Outputs](#fnoutputs)
49
51
  - [More Examples](#more-examples)
@@ -1071,6 +1073,134 @@ Fn::JoinNow:
1071
1073
  arn:aws:s3:::c1-acme-iam-cache-engine-${AWS::AccountId}-us-east-1$CFT_STACK_SUFFIX
1072
1074
  ```
1073
1075
 
1076
+ ## Fn::SubNow
1077
+
1078
+ `Fn::SubNow` performs immediate string substitution similar to AWS CloudFormation's `Fn::Sub`, but evaluates during template preprocessing rather than at stack creation time. It supports variable substitution using `${VariableName}` syntax and AWS pseudo-parameters.
1079
+
1080
+ The function supports two input formats:
1081
+
1082
+ **String format:**
1083
+ ```yaml
1084
+ Fn::SubNow: "arn:aws:s3:::bucket-${BucketSuffix}"
1085
+ ```
1086
+
1087
+ **Array format with variables:**
1088
+ ```yaml
1089
+ Fn::SubNow:
1090
+ - "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${LogGroupName}"
1091
+ - LogGroupName: /aws/lambda/my-function
1092
+ ```
1093
+
1094
+ **Supported AWS pseudo-parameters:**
1095
+ - `${AWS::AccountId}` - AWS Account ID
1096
+ - `${AWS::Region}` - AWS Region
1097
+ - `${AWS::StackName}` - Stack name
1098
+ - `${AWS::StackId}` - Stack ID
1099
+ - `${AWS::Partition}` - AWS Partition (e.g., 'aws')
1100
+ - `${AWS::URLSuffix}` - URL suffix (e.g., 'amazonaws.com')
1101
+
1102
+ Variables can be provided via:
1103
+ 1. The `inject` option passed to `cfn-include`
1104
+ 2. Explicit variables in the array format (takes precedence over `inject`)
1105
+ 3. Environment variables when using the `doEnv` option
1106
+
1107
+ **Example with environment variables:**
1108
+ ```yaml
1109
+ BucketName: !SubNow "my-bucket-${Environment}-${AWS::Region}"
1110
+ ```
1111
+
1112
+ With `CFN_INCLUDE_DO_ENV=true` and environment variable `ENVIRONMENT=prod`, this resolves to:
1113
+ ```yaml
1114
+ BucketName: "my-bucket-prod-us-east-1"
1115
+ ```
1116
+
1117
+ ## Fn::RefNow
1118
+
1119
+ `Fn::RefNow` resolves a reference immediately during template preprocessing, similar to AWS CloudFormation's `Fn::Ref` but evaluated at processing time rather than stack creation time. It resolves references to parameters, variables, and AWS pseudo-parameters.
1120
+
1121
+ **Basic syntax:**
1122
+ ```yaml
1123
+ BucketRef:
1124
+ Fn::RefNow: BucketName
1125
+ ```
1126
+
1127
+ or using YAML tag syntax:
1128
+ ```yaml
1129
+ BucketRef: !RefNow BucketName
1130
+ ```
1131
+
1132
+ **Supported AWS pseudo-parameters:**
1133
+ - `AWS::AccountId` - AWS Account ID (environment: `AWS_ACCOUNT_ID` or `AWS_ACCOUNT_NUM`, fallback: `${AWS::AccountId}`)
1134
+ - `AWS::Region` - AWS Region (environment: `AWS_REGION`, fallback: `${AWS::Region}`)
1135
+ - `AWS::StackName` - Stack name (environment: `AWS_STACK_NAME`, fallback: `${AWS::StackName}`)
1136
+ - `AWS::StackId` - Stack ID (environment: `AWS_STACK_ID`, fallback: `${AWS::StackId}`)
1137
+ - `AWS::Partition` - AWS Partition (environment: `AWS_PARTITION`, default: `'aws'`)
1138
+ - `AWS::URLSuffix` - URL suffix (environment: `AWS_URL_SUFFIX`, default: `'amazonaws.com'`)
1139
+ - `AWS::NotificationARNs` - SNS topic ARNs for notifications (environment: `AWS_NOTIFICATION_ARNS`, fallback: `${AWS::NotificationARNs}`)
1140
+
1141
+ **Reference resolution priority:**
1142
+ 1. AWS pseudo-parameters (with environment variable fallbacks)
1143
+ 2. Variables from the `inject` option
1144
+ 3. Variables from the current scope (useful with `Fn::Map`)
1145
+
1146
+ **Reference indirection:**
1147
+ If a resolved reference is a string, it will be treated as a reference name and resolved again. This enables reference chaining, useful when using `Fn::RefNow` with `Fn::Map`:
1148
+
1149
+ ```yaml
1150
+ Fn::Map:
1151
+ - [BucketVar1, BucketVar2]
1152
+ - BucketName:
1153
+ Fn::RefNow: _
1154
+ ```
1155
+
1156
+ With `inject: { BucketVar1: "my-bucket-1", BucketVar2: "my-bucket-2" }`, this via
1157
+
1158
+ `$ Bucket1=my-bucket-1 BucketVar2=my-bucket-2 cnf-include examples/refNow.yml --enable env,eval` via exports / env
1159
+
1160
+ or
1161
+
1162
+ `$ cnf-include examples/refNow.yml --enable env,eval --inject '{"BucketVar1":"my-bucket-1","BucketVar2":"my-bucket-2"}'` via inject
1163
+
1164
+ resolves to:
1165
+ ```yaml
1166
+ BucketVar1: my-bucket-1
1167
+ BucketVar2: my-bucket-2
1168
+ ```
1169
+
1170
+ The `_` placeholder resolves to `"BucketVar1"` or `"BucketVar2"`, which are then resolved again to their actual values.
1171
+
1172
+ **Example with injected variables:**
1173
+ ```yaml
1174
+ Resources:
1175
+ Bucket:
1176
+ Type: AWS::S3::Bucket
1177
+ Properties:
1178
+ BucketName:
1179
+ Fn::RefNow: BucketName
1180
+ ```
1181
+
1182
+ With `inject: { BucketName: "my-app-bucket" }`, this resolves to:
1183
+ ```yaml
1184
+ BucketName: "my-app-bucket"
1185
+ ```
1186
+
1187
+ **Example with AWS pseudo-parameters:**
1188
+ ```yaml
1189
+ LogGroup:
1190
+ Fn::RefNow: AWS::StackName
1191
+ ```
1192
+
1193
+ With `AWS_STACK_NAME=my-stack`, this resolves to:
1194
+ ```yaml
1195
+ LogGroup: "my-stack"
1196
+ ```
1197
+
1198
+ **Unresolved AWS pseudo-parameters:**
1199
+ If an AWS pseudo-parameter is not set via environment variables, it falls back to a placeholder string (e.g., `${AWS::AccountId}`). Only `AWS::Partition` and `AWS::URLSuffix` have hardcoded defaults since they are rarely environment-specific.
1200
+
1201
+ **Error handling:**
1202
+ If a reference cannot be resolved, `Fn::RefNow` will throw an error. Ensure all referenced parameters and variables are available via inject, scope, or environment variables.
1203
+
1074
1204
  ## Fn::ApplyTags
1075
1205
 
1076
1206
  See [ApplyTags test file](t/tests/applyTags.yml).
package/index.js CHANGED
@@ -90,6 +90,18 @@ module.exports = async function (options) {
90
90
  * is subtituted with Value
91
91
  * @param {boolean} opts.doLog log all arguments at the include recurse level
92
92
  */
93
+ function getAwsPseudoParameters() {
94
+ return {
95
+ 'AWS::AccountId': process.env.AWS_ACCOUNT_ID || process.env.AWS_ACCOUNT_NUM || '${AWS::AccountId}',
96
+ 'AWS::Partition': process.env.AWS_PARTITION || 'aws',
97
+ 'AWS::Region': process.env.AWS_REGION || '${AWS::Region}',
98
+ 'AWS::StackId': process.env.AWS_STACK_ID || '${AWS::StackId}',
99
+ 'AWS::StackName': process.env.AWS_STACK_NAME || '${AWS::StackName}',
100
+ 'AWS::URLSuffix': process.env.AWS_URL_SUFFIX || 'amazonaws.com',
101
+ 'AWS::NotificationARNs': process.env.AWS_NOTIFICATION_ARNS || '${AWS::NotificationARNs}',
102
+ };
103
+ }
104
+
93
105
  async function recurse({ base, scope, cft, ...opts }) {
94
106
  if (opts.doLog) {
95
107
 
@@ -441,6 +453,54 @@ async function recurse({ base, scope, cft, ...opts }) {
441
453
  return toJoinArray.join(delimitter);
442
454
  });
443
455
  }
456
+ if (cft['Fn::SubNow']) {
457
+ return recurse({ base, scope, cft: cft['Fn::SubNow'], ...opts }).then((input) => {
458
+ let template = input;
459
+ let variables = {};
460
+
461
+ // Handle both string and [string, variables] formats
462
+ if (Array.isArray(input)) {
463
+ [template, variables] = input;
464
+ }
465
+
466
+ // Merge variables with inject and AWS pseudo-parameters
467
+ const allVariables = {
468
+ ...getAwsPseudoParameters(),
469
+ ...opts.inject,
470
+ ...variables,
471
+ };
472
+
473
+ // Perform substitution for ${VarName} pattern
474
+ let result = template.toString();
475
+ _.forEach(allVariables, (value, key) => {
476
+ const regex = new RegExp(`\\$\\{${_.escapeRegExp(key)}\\}`, 'g');
477
+ result = result.replace(regex, String(value));
478
+ });
479
+
480
+ return result;
481
+ });
482
+ }
483
+ if (cft['Fn::RefNow']) {
484
+ return recurse({ base, scope, cft: cft['Fn::RefNow'], ...opts }).then((logicalName) => {
485
+ let refName = logicalName;
486
+
487
+ // Merge AWS pseudo-parameters with inject and scope variables
488
+ const allRefs = {
489
+ ...getAwsPseudoParameters(),
490
+ ...process.env,
491
+ ...opts.inject,
492
+ ...scope,
493
+ };
494
+
495
+ // Try to resolve the reference
496
+ if (refName in allRefs) {
497
+ return allRefs[refName];
498
+ }
499
+
500
+ // If not found, throw an error
501
+ throw new Error(`Unable to resolve Ref for logical name: ${refName}`);
502
+ });
503
+ }
444
504
  if (cft['Fn::ApplyTags']) {
445
505
  return recurse({ base, scope, cft: cft['Fn::ApplyTags'], ...opts }).then((json) => {
446
506
  let { tags, Tags, resources } = json;
package/lib/schema.js CHANGED
@@ -35,6 +35,9 @@ var tags = [
35
35
  { short: 'Eval', full: 'Fn::Eval', type: 'sequence' },
36
36
  { short: 'IfEval', full: 'Fn::IfEval', type: 'mapping' },
37
37
  { short: 'JoinNow', full: 'Fn::JoinNow', type: 'scalar' },
38
+ { short: 'SubNow', full: 'Fn::SubNow', type: 'scalar' },
39
+ { short: 'SubNow', full: 'Fn::SubNow', type: 'sequence' },
40
+ { short: 'RefNow', full: 'Fn::RefNow', type: 'scalar' },
38
41
  { short: 'ApplyTags', full: 'Fn::ApplyTags', type: 'mapping' },
39
42
 
40
43
  // http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@znemz/cfn-include",
3
- "version": "2.1.8",
3
+ "version": "2.1.9",
4
4
  "description": "Preprocessor for CloudFormation templates with support for loops and flexible include statements",
5
5
  "keywords": [
6
6
  "aws",
@@ -42,7 +42,7 @@
42
42
  "dependencies": {
43
43
  "@aws-sdk/client-cloudformation": "^3.637.0",
44
44
  "@aws-sdk/client-s3": "^3.637.0",
45
- "@znemz/cft-utils": "0.1.23",
45
+ "@znemz/cft-utils": "0.1.30",
46
46
  "@znemz/sort-object": "^3.0.4",
47
47
  "aws-sdk-v3-proxy": "2.2.0",
48
48
  "bluebird": "^3.7.2",
@@ -70,7 +70,10 @@
70
70
  "npm-run-all": "4.1.5",
71
71
  "prettier": "3",
72
72
  "serve": "14.2.5",
73
- "sort-package-json": "3.5.0"
73
+ "sort-package-json": "3.6.0"
74
+ },
75
+ "engines": {
76
+ "node": ">=20.19"
74
77
  },
75
78
  "originalAuthor": {
76
79
  "name": "Moritz Onken",