@znemz/cfn-include 2.1.21 → 2.1.23
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/bin/cli.js +27 -33
- package/index.js +39 -38
- package/lib/cache.js +3 -8
- package/lib/cfnclient.js +7 -10
- package/lib/include/api.js +11 -7
- package/lib/include/query.js +8 -11
- package/lib/internals.js +1 -4
- package/lib/parselocation.js +4 -6
- package/lib/promise-utils.js +46 -0
- package/lib/promise.js +7 -10
- package/lib/replaceEnv.js +15 -16
- package/lib/request.js +21 -16
- package/lib/schema.js +11 -11
- package/lib/scope.js +3 -9
- package/lib/utils.js +5 -8
- package/lib/yaml.js +73 -19
- package/package.json +6 -4
package/bin/cli.js
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const exec = require('child_process').execSync;
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const _ = require('lodash');
|
|
6
|
-
const pathParse = require('path-parse');
|
|
7
2
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import _ from 'lodash';
|
|
8
|
+
// path.parse is native in Node.js - no need for path-parse package
|
|
9
|
+
|
|
10
|
+
import include from '../index.js';
|
|
11
|
+
import * as yaml from '../lib/yaml.js';
|
|
12
|
+
import Client from '../lib/cfnclient.js';
|
|
13
|
+
import replaceEnv from '../lib/replaceEnv.js';
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = path.dirname(__filename);
|
|
17
|
+
|
|
18
|
+
// Read package.json using fs instead of import assertion
|
|
19
|
+
const pkg = JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
|
|
13
20
|
|
|
14
21
|
const { env } = process;
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const { hideBin } = await import('yargs/helpers');
|
|
23
|
+
const { default: yargs } = await import('yargs');
|
|
24
|
+
const { hideBin } = await import('yargs/helpers');
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
const opts = yargs(hideBin(process.argv))
|
|
21
27
|
.version(false)
|
|
22
28
|
.command('$0 [path] [options]', pkg.description, (y) =>
|
|
23
29
|
y.positional('path', {
|
|
@@ -60,9 +66,7 @@ const { env } = process;
|
|
|
60
66
|
desc: 'bucket name required for templates larger than 50k',
|
|
61
67
|
},
|
|
62
68
|
context: {
|
|
63
|
-
desc:
|
|
64
|
-
|
|
65
|
-
'template full path. only utilized for stdin when the template is piped to this script',
|
|
69
|
+
desc: 'template full path. only utilized for stdin when the template is piped to this script',
|
|
66
70
|
required: false,
|
|
67
71
|
string: true,
|
|
68
72
|
},
|
|
@@ -79,13 +83,11 @@ const { env } = process;
|
|
|
79
83
|
inject: {
|
|
80
84
|
alias: 'i',
|
|
81
85
|
string: true,
|
|
82
|
-
|
|
83
86
|
desc: `JSON string payload to use for template injection.`,
|
|
84
87
|
coerce: (valStr) => JSON.parse(valStr),
|
|
85
88
|
},
|
|
86
89
|
doLog: {
|
|
87
90
|
boolean: true,
|
|
88
|
-
|
|
89
91
|
desc: `console log out include options in recurse step`,
|
|
90
92
|
},
|
|
91
93
|
'ref-now-ignore-missing': {
|
|
@@ -111,14 +113,14 @@ const { env } = process;
|
|
|
111
113
|
opts.enable = opts.enable.split(',');
|
|
112
114
|
|
|
113
115
|
// Parse ref-now-ignores into an array
|
|
114
|
-
const refNowIgnores = opts['ref-now-ignores'] ? opts['ref-now-ignores'].split(',').map(s => s.trim()) : [];
|
|
116
|
+
const refNowIgnores = opts['ref-now-ignores'] ? opts['ref-now-ignores'].split(',').map((s) => s.trim()) : [];
|
|
115
117
|
|
|
116
118
|
let promise;
|
|
117
119
|
if (opts.path) {
|
|
118
120
|
let location;
|
|
119
121
|
const protocol = opts.path.match(/^\w+:\/\//);
|
|
120
122
|
if (protocol) location = opts.path;
|
|
121
|
-
else if (
|
|
123
|
+
else if (path.parse(opts.path).root) location = `file://${opts.path}`;
|
|
122
124
|
else location = `file://${path.join(process.cwd(), opts.path)}`;
|
|
123
125
|
promise = include({
|
|
124
126
|
url: location,
|
|
@@ -142,9 +144,7 @@ if (opts.path) {
|
|
|
142
144
|
process.exit(1);
|
|
143
145
|
}
|
|
144
146
|
|
|
145
|
-
const location = opts.context
|
|
146
|
-
? path.resolve(opts.context)
|
|
147
|
-
: path.join(process.cwd(), 'template.yml');
|
|
147
|
+
const location = opts.context ? path.resolve(opts.context) : path.join(process.cwd(), 'template.yml');
|
|
148
148
|
|
|
149
149
|
template = opts.enable.includes('env') ? replaceEnv(template) : template;
|
|
150
150
|
|
|
@@ -166,13 +166,13 @@ promise
|
|
|
166
166
|
if (opts.metadata) {
|
|
167
167
|
let stdout;
|
|
168
168
|
try {
|
|
169
|
-
stdout =
|
|
169
|
+
stdout = execSync('git log -n 1 --pretty=%H', {
|
|
170
170
|
stdio: [0, 'pipe', 'ignore'],
|
|
171
171
|
})
|
|
172
172
|
.toString()
|
|
173
173
|
.trim();
|
|
174
174
|
} catch {
|
|
175
|
-
|
|
175
|
+
// ignore git errors
|
|
176
176
|
}
|
|
177
177
|
_.defaultsDeep(template, {
|
|
178
178
|
Metadata: {
|
|
@@ -194,11 +194,7 @@ promise
|
|
|
194
194
|
return template;
|
|
195
195
|
})
|
|
196
196
|
.then((template) => {
|
|
197
|
-
console.log(
|
|
198
|
-
opts.yaml
|
|
199
|
-
? yaml.dump(template, opts)
|
|
200
|
-
: JSON.stringify(template, null, opts.minimize ? null : 2),
|
|
201
|
-
);
|
|
197
|
+
console.log(opts.yaml ? yaml.dump(template, opts) : JSON.stringify(template, null, opts.minimize ? null : 2));
|
|
202
198
|
})
|
|
203
199
|
.catch(function (err) {
|
|
204
200
|
if (typeof err.toString === 'function') console.error(err.toString());
|
|
@@ -206,5 +202,3 @@ promise
|
|
|
206
202
|
console.log(err.stack);
|
|
207
203
|
process.exit(1);
|
|
208
204
|
});
|
|
209
|
-
|
|
210
|
-
})();
|
package/index.js
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const PromiseExt = require('./lib/promise');
|
|
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';
|
|
16
15
|
|
|
17
16
|
const S3 = (opts = {}) => addProxyToClient(new S3Client(opts), { throwOnNoProxy: false });
|
|
18
17
|
|
|
19
18
|
const s3 = S3();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
30
|
|
|
31
31
|
/**
|
|
32
32
|
* @param {object} options
|
|
@@ -53,7 +53,7 @@ const { createChildScope } = require('./lib/scope');
|
|
|
53
53
|
* doEval: opts.doEval, -- allow Fn::Eval to be used
|
|
54
54
|
* })
|
|
55
55
|
*/
|
|
56
|
-
|
|
56
|
+
export default async function (options) {
|
|
57
57
|
let { template } = options;
|
|
58
58
|
options.doEnv = getBoolEnvOpt(options.doEnv, 'CFN_INCLUDE_DO_ENV');
|
|
59
59
|
options.doEval = getBoolEnvOpt(options.doEval, 'CFN_INCLUDE_DO_EVAL');
|
|
@@ -72,7 +72,7 @@ module.exports = async function (options) {
|
|
|
72
72
|
rootTemplate: resolvedTemplate,
|
|
73
73
|
...options,
|
|
74
74
|
});
|
|
75
|
-
}
|
|
75
|
+
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
78
|
* @param {object} base file options
|
|
@@ -310,9 +310,9 @@ async function recurse({ base, scope, cft, rootTemplate, caller, ...opts }) {
|
|
|
310
310
|
delete cft['Fn::DeepMerge'];
|
|
311
311
|
let mergedObj = {};
|
|
312
312
|
if (json && json.length) {
|
|
313
|
-
|
|
313
|
+
for (const j of json) {
|
|
314
314
|
mergedObj = deepMerge(mergedObj, j);
|
|
315
|
-
}
|
|
315
|
+
}
|
|
316
316
|
}
|
|
317
317
|
return recurse({ base, scope, cft: _.defaults(cft, mergedObj), rootTemplate, caller: 'Fn::DeepMerge', ...opts });
|
|
318
318
|
},
|
|
@@ -585,7 +585,7 @@ async function recurse({ base, scope, cft, rootTemplate, caller, ...opts }) {
|
|
|
585
585
|
});
|
|
586
586
|
}
|
|
587
587
|
|
|
588
|
-
return
|
|
588
|
+
return promiseProps(
|
|
589
589
|
_.mapValues(cft, (template, key) => recurse({ base, scope, cft: template, key, rootTemplate, caller: 'recurse:isPlainObject:end', ...opts })),
|
|
590
590
|
);
|
|
591
591
|
}
|
|
@@ -616,15 +616,15 @@ function findAndReplace(scope, object) {
|
|
|
616
616
|
}
|
|
617
617
|
}
|
|
618
618
|
if (Array.isArray(object)) {
|
|
619
|
-
object = object.map(
|
|
619
|
+
object = object.map((item) => findAndReplace(scope, item));
|
|
620
620
|
} else if (_.isPlainObject(object)) {
|
|
621
621
|
object = _.mapKeys(object, function (value, key) {
|
|
622
622
|
return findAndReplace(scope, key);
|
|
623
623
|
});
|
|
624
|
-
Object.keys(object)
|
|
625
|
-
if (key === 'Fn::Map')
|
|
624
|
+
for (const key of Object.keys(object)) {
|
|
625
|
+
if (key === 'Fn::Map') continue;
|
|
626
626
|
object[key] = findAndReplace(scope, object[key]);
|
|
627
|
-
}
|
|
627
|
+
}
|
|
628
628
|
}
|
|
629
629
|
return object;
|
|
630
630
|
}
|
|
@@ -743,7 +743,7 @@ async function fnInclude({ base, scope, cft, ...opts }) {
|
|
|
743
743
|
body = cachedReadFile(absolute).then(procTemplate);
|
|
744
744
|
absolute = `${location.protocol}://${absolute}`;
|
|
745
745
|
} else if (location.protocol === 's3') {
|
|
746
|
-
const basedir =
|
|
746
|
+
const basedir = path.parse(base.path).dir;
|
|
747
747
|
const bucket = location.relative ? base.host : location.host;
|
|
748
748
|
|
|
749
749
|
let key = location.relative ? url.resolve(`${basedir}/`, location.raw) : location.path;
|
|
@@ -759,7 +759,7 @@ async function fnInclude({ base, scope, cft, ...opts }) {
|
|
|
759
759
|
.then((res) => res.Body.toString())
|
|
760
760
|
.then(procTemplate);
|
|
761
761
|
} else if (location.protocol && location.protocol.match(/^https?$/)) {
|
|
762
|
-
const basepath = `${
|
|
762
|
+
const basepath = `${path.parse(base.path).dir}/`;
|
|
763
763
|
|
|
764
764
|
absolute = location.relative
|
|
765
765
|
? url.resolve(`${location.protocol}://${base.host}${basepath}`, location.raw)
|
|
@@ -883,13 +883,14 @@ async function handleIncludeBody({ scope, args, body, absolute }) {
|
|
|
883
883
|
function JSONifyString(string) {
|
|
884
884
|
const lines = [];
|
|
885
885
|
const split = string.toString().split(/(\r?\n)/);
|
|
886
|
-
|
|
886
|
+
for (let idx = 0; idx < split.length; idx++) {
|
|
887
|
+
const line = split[idx];
|
|
887
888
|
if (idx % 2) {
|
|
888
889
|
lines[(idx - 1) / 2] = lines[(idx - 1) / 2] + line;
|
|
889
890
|
} else {
|
|
890
891
|
lines.push(line);
|
|
891
892
|
}
|
|
892
|
-
}
|
|
893
|
+
}
|
|
893
894
|
return lines;
|
|
894
895
|
}
|
|
895
896
|
|
package/lib/cache.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* File content cache to avoid redundant disk I/O.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { readFile } from 'node:fs/promises';
|
|
6
6
|
|
|
7
7
|
// File content cache to avoid re-reading the same files
|
|
8
8
|
const fileCache = new Map();
|
|
@@ -12,7 +12,7 @@ const fileCache = new Map();
|
|
|
12
12
|
* @param {string} absolutePath - Absolute path to the file
|
|
13
13
|
* @returns {Promise<string>} File content as a string
|
|
14
14
|
*/
|
|
15
|
-
async function cachedReadFile(absolutePath) {
|
|
15
|
+
export async function cachedReadFile(absolutePath) {
|
|
16
16
|
if (fileCache.has(absolutePath)) {
|
|
17
17
|
return fileCache.get(absolutePath);
|
|
18
18
|
}
|
|
@@ -24,11 +24,6 @@ async function cachedReadFile(absolutePath) {
|
|
|
24
24
|
/**
|
|
25
25
|
* Clear the file cache (useful for testing)
|
|
26
26
|
*/
|
|
27
|
-
function clearFileCache() {
|
|
27
|
+
export function clearFileCache() {
|
|
28
28
|
fileCache.clear();
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
module.exports = {
|
|
32
|
-
cachedReadFile,
|
|
33
|
-
clearFileCache,
|
|
34
|
-
};
|
package/lib/cfnclient.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} = require('@aws-sdk/client-cloudformation');
|
|
5
|
-
const { S3Client, PutObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3');
|
|
6
|
-
const { addProxyToClient } = require('aws-sdk-v3-proxy');
|
|
1
|
+
import { CloudFormationClient, ValidateTemplateCommand } from '@aws-sdk/client-cloudformation';
|
|
2
|
+
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
|
|
3
|
+
import { addProxyToClient } from 'aws-sdk-v3-proxy';
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import crypto from 'node:crypto';
|
|
10
7
|
|
|
11
8
|
const S3 = (opts = {}) => addProxyToClient(new S3Client(opts));
|
|
12
9
|
const CloudFormation = (opts = {}) => addProxyToClient(new CloudFormationClient(opts));
|
|
@@ -29,7 +26,7 @@ class Client {
|
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
async uploadTemplate(tpl, callback) {
|
|
32
|
-
const key = path.join(this.prefix, `${this.digest(tpl)}.json`);
|
|
29
|
+
const key = path.posix.join(this.prefix, `${this.digest(tpl)}.json`);
|
|
33
30
|
await this.s3.Send(
|
|
34
31
|
new PutObjectCommand({
|
|
35
32
|
Body: tpl,
|
|
@@ -84,4 +81,4 @@ class Client {
|
|
|
84
81
|
}
|
|
85
82
|
}
|
|
86
83
|
|
|
87
|
-
|
|
84
|
+
export default Client;
|
package/lib/include/api.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// NOTE: This module uses the legacy AWS SDK v2 via aws-sdk-proxy
|
|
2
|
+
// It may be unused and could be removed or rewritten for SDK v3
|
|
3
|
+
import AWS from 'aws-sdk-proxy';
|
|
4
|
+
import jmespath from 'jmespath';
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return service[args.action](args.parameters ? args.parameters : {})
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
export default function (args) {
|
|
7
|
+
const service = new AWS[args.service](args.region ? { region: args.region } : null);
|
|
8
|
+
return service[args.action](args.parameters ? args.parameters : {})
|
|
9
|
+
.promise()
|
|
10
|
+
.then(function (res) {
|
|
11
|
+
return args.query ? jmespath.search(res, args.query) : res;
|
|
12
|
+
});
|
|
9
13
|
}
|
package/lib/include/query.js
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import jmespath from 'jmespath';
|
|
3
3
|
|
|
4
4
|
// this exists cause in most cases lodash get is plenty sufficient
|
|
5
|
-
// also this bug / error in jmespath is
|
|
5
|
+
// also this bug / error in jmespath is ridiculous https://github.com/jmespath/jmespath.js/issues/35
|
|
6
6
|
const queryParsers = {
|
|
7
|
-
lodash: (obj, path) => get(obj, path) ||
|
|
8
|
-
jmespath: search,
|
|
9
|
-
default: search,
|
|
7
|
+
lodash: (obj, path) => _.get(obj, path) || '',
|
|
8
|
+
jmespath: jmespath.search,
|
|
9
|
+
default: jmespath.search,
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
queryParsers[
|
|
13
|
-
queryParsers[process.env.CFN_INCLUDE_QUERY_PARSER] || queryParsers.default;
|
|
12
|
+
queryParsers['default'] = queryParsers[process.env.CFN_INCLUDE_QUERY_PARSER] || queryParsers.default;
|
|
14
13
|
|
|
15
|
-
function getParser(type) {
|
|
14
|
+
export function getParser(type) {
|
|
16
15
|
return queryParsers[type] || queryParsers.default;
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
module.exports = { getParser };
|
package/lib/internals.js
CHANGED
package/lib/parselocation.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = function parseLocation(location) {
|
|
4
|
-
if(!location) return {};
|
|
1
|
+
export default function parseLocation(location) {
|
|
2
|
+
if (!location) return {};
|
|
5
3
|
if (!location.match) {
|
|
6
4
|
console.error('location.match is not a function', location);
|
|
7
5
|
}
|
|
8
|
-
|
|
6
|
+
const parsed = location.match(/^(((\w+):)?\/\/)?(.*?)([\\\/](.*))?$/);
|
|
9
7
|
|
|
10
8
|
return {
|
|
11
9
|
protocol: parsed[3],
|
|
12
10
|
host: parsed[4],
|
|
13
11
|
path: parsed[5],
|
|
14
|
-
relative:
|
|
12
|
+
relative: parsed[1] === undefined,
|
|
15
13
|
raw: location,
|
|
16
14
|
};
|
|
17
15
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Promise utilities to replace bluebird.
|
|
3
|
+
* These are drop-in replacements for the bluebird methods we use.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Promise.props replacement - resolves an object of promises.
|
|
8
|
+
* @param {Object} obj - Object with promise values
|
|
9
|
+
* @returns {Promise<Object>} Object with resolved values
|
|
10
|
+
*/
|
|
11
|
+
async function promiseProps(obj) {
|
|
12
|
+
const keys = Object.keys(obj);
|
|
13
|
+
const values = await Promise.all(keys.map((key) => obj[key]));
|
|
14
|
+
const result = {};
|
|
15
|
+
for (let i = 0; i < keys.length; i++) {
|
|
16
|
+
result[keys[i]] = values[i];
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Promise.try replacement - wraps a function to catch sync errors.
|
|
23
|
+
* @param {Function} fn - Function to execute
|
|
24
|
+
* @returns {Promise} Promise that resolves to fn result or rejects on error
|
|
25
|
+
*/
|
|
26
|
+
function promiseTry(fn) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
try {
|
|
29
|
+
resolve(fn());
|
|
30
|
+
} catch (err) {
|
|
31
|
+
reject(err);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Promise.map replacement - maps over array with concurrency.
|
|
38
|
+
* @param {Array} arr - Array to map over
|
|
39
|
+
* @param {Function} fn - Async function to apply
|
|
40
|
+
* @returns {Promise<Array>} Array of resolved values
|
|
41
|
+
*/
|
|
42
|
+
function promiseMap(arr, fn) {
|
|
43
|
+
return Promise.all(arr.map(fn));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { promiseProps, promiseTry, promiseMap };
|
package/lib/promise.js
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
const _ = require('lodash');
|
|
1
|
+
import { promiseTry, promiseMap } from './promise-utils.js';
|
|
3
2
|
|
|
4
3
|
/*
|
|
5
4
|
Maps over objects or iterables just like lodash.
|
|
6
5
|
*/
|
|
7
6
|
const mapWhatever = (promises, cb) =>
|
|
8
|
-
|
|
7
|
+
promiseTry(() =>
|
|
9
8
|
Promise.resolve(promises).then((arrayOrObject) => {
|
|
10
|
-
if (
|
|
11
|
-
return
|
|
9
|
+
if (Array.isArray(arrayOrObject)) {
|
|
10
|
+
return promiseMap(arrayOrObject, cb);
|
|
12
11
|
}
|
|
13
12
|
const size = Object.values(arrayOrObject).length;
|
|
14
|
-
|
|
13
|
+
const entries = Object.entries(arrayOrObject);
|
|
14
|
+
return Promise.all(entries.map(([key, value]) => cb(value, key, size)));
|
|
15
15
|
}),
|
|
16
16
|
);
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
mapWhatever,
|
|
20
|
-
mapX: mapWhatever,
|
|
21
|
-
};
|
|
18
|
+
export { mapWhatever, mapWhatever as mapX };
|
package/lib/replaceEnv.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const passThrough = (template) => template
|
|
2
|
-
const replaceProcessEnv = (template) => replaceEnv(template, process.env, false)
|
|
1
|
+
const passThrough = (template) => template;
|
|
2
|
+
const replaceProcessEnv = (template) => replaceEnv(template, process.env, false);
|
|
3
3
|
|
|
4
4
|
// Cache for compiled regex patterns to avoid re-creating them
|
|
5
5
|
const regexCache = new Map();
|
|
@@ -29,39 +29,38 @@ function getVarRegex(key, withBraces) {
|
|
|
29
29
|
return regexCache.get(cacheKey);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
let processTemplate;
|
|
33
|
+
|
|
34
|
+
function replaceEnv(template, inject = {}, doEnv) {
|
|
33
35
|
processTemplate = doEnv ? replaceProcessEnv : passThrough;
|
|
34
36
|
|
|
35
|
-
if (!template || typeof template !==
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
};
|
|
37
|
+
if (!template || typeof template !== 'string') {
|
|
38
|
+
return processTemplate(template);
|
|
39
|
+
}
|
|
39
40
|
|
|
40
41
|
const keys = Object.keys(inject);
|
|
41
42
|
|
|
42
43
|
for (let i = 0; i < keys.length; i++) {
|
|
43
44
|
const key = keys[i];
|
|
44
|
-
|
|
45
|
+
const val = inject[key];
|
|
45
46
|
|
|
46
47
|
// Use cached regex patterns instead of creating new ones each time
|
|
47
48
|
const bareRegex = getVarRegex(key, false);
|
|
48
49
|
const bracedRegex = getVarRegex(key, true);
|
|
49
|
-
|
|
50
|
+
|
|
50
51
|
// Reset lastIndex for global regex reuse
|
|
51
52
|
bareRegex.lastIndex = 0;
|
|
52
53
|
bracedRegex.lastIndex = 0;
|
|
53
|
-
|
|
54
|
-
template = template
|
|
55
|
-
.replace(bareRegex, val)
|
|
56
|
-
.replace(bracedRegex, val);
|
|
54
|
+
|
|
55
|
+
template = template.replace(bareRegex, val).replace(bracedRegex, val);
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
// return template;
|
|
60
58
|
return processTemplate(template);
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
const IsRegExVar = (str) =>
|
|
61
|
+
const IsRegExVar = (str) => /\$\w+/.test(str) || /\$\{\w+\}/.test(str);
|
|
64
62
|
|
|
65
63
|
replaceEnv.IsRegExVar = IsRegExVar; // hack to be backward compat
|
|
66
64
|
|
|
67
|
-
|
|
65
|
+
export default replaceEnv;
|
|
66
|
+
export { IsRegExVar };
|
package/lib/request.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { parse as urlParse } from 'node:url';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import http from 'node:http';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return new Promise((resolve, reject) =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
export default function request(location) {
|
|
6
|
+
const parsed = urlParse(location);
|
|
7
|
+
const proto = parsed.protocol === 'https:' ? https : http;
|
|
8
|
+
|
|
9
|
+
return new Promise((resolve, reject) =>
|
|
10
|
+
proto
|
|
11
|
+
.get(location, (res) => {
|
|
12
|
+
if (res.statusCode > 299) {
|
|
13
|
+
return reject(new Error('HTTP request failed with status code ' + res.statusCode));
|
|
14
|
+
}
|
|
15
|
+
const rawData = [];
|
|
16
|
+
res.setEncoding('utf8');
|
|
17
|
+
res.on('data', (chunk) => rawData.push(chunk));
|
|
18
|
+
res.on('end', () => resolve(rawData.join('')));
|
|
19
|
+
})
|
|
20
|
+
.on('error', reject),
|
|
21
|
+
);
|
|
22
|
+
}
|
package/lib/schema.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import yaml from 'js-yaml';
|
|
2
|
+
import _ from 'lodash';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const tags = [
|
|
5
5
|
{ short: 'Include', full: 'Fn::Include', type: 'scalar' },
|
|
6
6
|
{ short: 'Include', full: 'Fn::Include', type: 'mapping' },
|
|
7
7
|
{ short: 'Stringify', full: 'Fn::Stringify', type: 'sequence' },
|
|
@@ -73,8 +73,8 @@ var tags = [
|
|
|
73
73
|
kind: fn.type,
|
|
74
74
|
construct: function (obj) {
|
|
75
75
|
if (fn.dotSyntax && _.isString(obj)) {
|
|
76
|
-
|
|
77
|
-
if (indexOfDot
|
|
76
|
+
const indexOfDot = obj.indexOf('.');
|
|
77
|
+
if (indexOfDot !== -1) obj = [obj.substr(0, indexOfDot), obj.substr(indexOfDot + 1)];
|
|
78
78
|
else obj = [obj];
|
|
79
79
|
}
|
|
80
80
|
return _.fromPairs([[fn.full, obj]]);
|
|
@@ -106,12 +106,12 @@ const BANG_AMAZON_FUNCS = [
|
|
|
106
106
|
|
|
107
107
|
const EXPLICIT_AMAZON_FUNCS = BANG_AMAZON_FUNCS.map((f) => `Fn::${f}`);
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
module.exports.EXPLICIT_AMAZON_FUNCS = EXPLICIT_AMAZON_FUNCS;
|
|
112
|
-
module.exports.BANG_AMAZON_FUNCS = BANG_AMAZON_FUNCS;
|
|
109
|
+
const yamlSchema = yaml.DEFAULT_SCHEMA.extend(tags);
|
|
113
110
|
|
|
114
111
|
// Test the function key to make sure it's something
|
|
115
112
|
// we should process.
|
|
116
|
-
|
|
117
|
-
/Fn::.*/.test(testKeyForFunc) && EXPLICIT_AMAZON_FUNCS.
|
|
113
|
+
const isOurExplicitFunction = (testKeyForFunc) =>
|
|
114
|
+
/Fn::.*/.test(testKeyForFunc) && !EXPLICIT_AMAZON_FUNCS.includes(testKeyForFunc);
|
|
115
|
+
|
|
116
|
+
export default yamlSchema;
|
|
117
|
+
export { EXPLICIT_AMAZON_FUNCS, BANG_AMAZON_FUNCS, isOurExplicitFunction };
|
package/lib/scope.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* @param {Object} [additions={}] - Properties to add to the child scope
|
|
15
15
|
* @returns {Object} A new child scope with prototype chain to parent
|
|
16
16
|
*/
|
|
17
|
-
function createChildScope(parent, additions = {}) {
|
|
17
|
+
export function createChildScope(parent, additions = {}) {
|
|
18
18
|
const child = Object.create(parent);
|
|
19
19
|
Object.assign(child, additions);
|
|
20
20
|
return child;
|
|
@@ -30,7 +30,7 @@ function createChildScope(parent, additions = {}) {
|
|
|
30
30
|
* @param {Object} scope - The scope to flatten
|
|
31
31
|
* @returns {Object} A plain object with all inherited properties
|
|
32
32
|
*/
|
|
33
|
-
function scopeToObject(scope) {
|
|
33
|
+
export function scopeToObject(scope) {
|
|
34
34
|
const result = {};
|
|
35
35
|
for (const key in scope) {
|
|
36
36
|
result[key] = scope[key];
|
|
@@ -45,14 +45,8 @@ function scopeToObject(scope) {
|
|
|
45
45
|
* @param {Object} scope - The scope to iterate over
|
|
46
46
|
* @param {Function} callback - Function to call with (value, key)
|
|
47
47
|
*/
|
|
48
|
-
function forEachInScope(scope, callback) {
|
|
48
|
+
export function forEachInScope(scope, callback) {
|
|
49
49
|
for (const key in scope) {
|
|
50
50
|
callback(scope[key], key);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
module.exports = {
|
|
55
|
-
createChildScope,
|
|
56
|
-
scopeToObject,
|
|
57
|
-
forEachInScope,
|
|
58
|
-
};
|
package/lib/utils.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
2
|
|
|
3
3
|
function upperCamelCase(str) {
|
|
4
4
|
assert(typeof str === 'string', 'argument to upper/lowerCamelCase must be a string');
|
|
5
5
|
return str
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
.split(/[\._-\s]+/)
|
|
7
|
+
.map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())
|
|
8
|
+
.join('');
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
function lowerCamelCase(str) {
|
|
@@ -13,7 +13,4 @@ function lowerCamelCase(str) {
|
|
|
13
13
|
return upper.charAt(0).toLowerCase() + upper.slice(1);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
lowerCamelCase,
|
|
18
|
-
upperCamelCase,
|
|
19
|
-
};
|
|
16
|
+
export { lowerCamelCase, upperCamelCase };
|
package/lib/yaml.js
CHANGED
|
@@ -1,22 +1,76 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const yamlSchema = require('./schema');
|
|
1
|
+
import yaml from 'js-yaml';
|
|
2
|
+
import yamlSchema from './schema.js';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Simple JSON minify - strips comments and whitespace.
|
|
6
|
+
* Handles JavaScript-style single-line (//) and multi-line comments.
|
|
7
|
+
* @param {string} json - JSON string potentially with comments
|
|
8
|
+
* @returns {string} Minified JSON without comments
|
|
9
|
+
*/
|
|
10
|
+
function jsonMinify(json) {
|
|
11
|
+
// Remove single-line comments (// ...)
|
|
12
|
+
// Remove multi-line comments (/* ... */)
|
|
13
|
+
// Be careful not to remove // or /* inside strings
|
|
14
|
+
let inString = false;
|
|
15
|
+
let escaped = false;
|
|
16
|
+
let result = '';
|
|
17
|
+
let i = 0;
|
|
18
|
+
|
|
19
|
+
while (i < json.length) {
|
|
20
|
+
const char = json[i];
|
|
21
|
+
const nextChar = json[i + 1];
|
|
22
|
+
|
|
23
|
+
if (inString) {
|
|
24
|
+
result += char;
|
|
25
|
+
if (escaped) {
|
|
26
|
+
escaped = false;
|
|
27
|
+
} else if (char === '\\') {
|
|
28
|
+
escaped = true;
|
|
29
|
+
} else if (char === '"') {
|
|
30
|
+
inString = false;
|
|
31
|
+
}
|
|
32
|
+
i++;
|
|
33
|
+
} else if (char === '"') {
|
|
34
|
+
inString = true;
|
|
35
|
+
result += char;
|
|
36
|
+
i++;
|
|
37
|
+
} else if (char === '/' && nextChar === '/') {
|
|
38
|
+
// Single-line comment - skip until newline
|
|
39
|
+
while (i < json.length && json[i] !== '\n') {
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
} else if (char === '/' && nextChar === '*') {
|
|
43
|
+
// Multi-line comment - skip until */
|
|
44
|
+
i += 2;
|
|
45
|
+
while (i < json.length - 1 && !(json[i] === '*' && json[i + 1] === '/')) {
|
|
46
|
+
i++;
|
|
17
47
|
}
|
|
48
|
+
i += 2; // Skip */
|
|
49
|
+
} else {
|
|
50
|
+
result += char;
|
|
51
|
+
i++;
|
|
18
52
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function load(res) {
|
|
59
|
+
let json;
|
|
60
|
+
try {
|
|
61
|
+
json = yaml.load(res, { schema: yamlSchema });
|
|
62
|
+
} catch (yamlErr) {
|
|
63
|
+
try {
|
|
64
|
+
json = JSON.parse(jsonMinify(res));
|
|
65
|
+
} catch (jsonErr) {
|
|
66
|
+
const err = new Error([yamlErr, jsonErr]);
|
|
67
|
+
err.name = 'SyntaxError';
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return json;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function dump(obj, opts) {
|
|
75
|
+
return yaml.dump(obj, { sortKeys: true, ...opts });
|
|
76
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@znemz/cfn-include",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.23",
|
|
4
4
|
"description": "Preprocessor for CloudFormation templates with support for loops and flexible include statements",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aws",
|
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
"name": "Nicholas McCready",
|
|
22
22
|
"email": "nemtcan@gmail.com"
|
|
23
23
|
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": "./index.js",
|
|
27
|
+
"./lib/*": "./lib/*"
|
|
28
|
+
},
|
|
24
29
|
"bin": {
|
|
25
30
|
"cfn-include": "bin/cli.js"
|
|
26
31
|
},
|
|
@@ -48,14 +53,11 @@
|
|
|
48
53
|
"@znemz/cft-utils": "0.1.33",
|
|
49
54
|
"@znemz/sort-object": "^3.0.4",
|
|
50
55
|
"aws-sdk-v3-proxy": "2.2.0",
|
|
51
|
-
"bluebird": "^3.7.2",
|
|
52
56
|
"deepmerge": "^4.2.2",
|
|
53
57
|
"glob": "^13.0.0",
|
|
54
58
|
"jmespath": "^0.16.0",
|
|
55
59
|
"js-yaml": "^4.1.1",
|
|
56
|
-
"jsonminify": "^0.4.1",
|
|
57
60
|
"lodash": "^4.17.21",
|
|
58
|
-
"path-parse": "~1.0.7",
|
|
59
61
|
"proxy-agent": "6.5.0",
|
|
60
62
|
"yargs": "~18.0.0"
|
|
61
63
|
},
|