@znemz/cfn-include 2.1.20 → 2.1.22

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  const exec = require('child_process').execSync;
4
4
  const path = require('path');
5
5
  const _ = require('lodash');
6
- const pathParse = require('path-parse');
6
+ // path.parse is native in Node.js - no need for path-parse package
7
7
 
8
8
  const include = require('../index');
9
9
  const yaml = require('../lib/yaml');
@@ -118,7 +118,7 @@ if (opts.path) {
118
118
  let location;
119
119
  const protocol = opts.path.match(/^\w+:\/\//);
120
120
  if (protocol) location = opts.path;
121
- else if (pathParse(opts.path).root) location = `file://${opts.path}`;
121
+ else if (path.parse(opts.path).root) location = `file://${opts.path}`;
122
122
  else location = `file://${path.join(process.cwd(), opts.path)}`;
123
123
  promise = include({
124
124
  url: location,
package/index.js CHANGED
@@ -2,12 +2,11 @@ const url = require('url');
2
2
  const path = require('path');
3
3
  const _ = require('lodash');
4
4
  const { glob } = require('glob');
5
- const Promise = require('bluebird');
6
5
  const sortObject = require('@znemz/sort-object');
7
6
  const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
8
7
  const { addProxyToClient } = require('aws-sdk-v3-proxy');
9
8
 
10
- const pathParse = require('path-parse');
9
+ // path.parse is native in Node.js - no need for path-parse package
11
10
  const deepMerge = require('deepmerge');
12
11
  const { isTaggableResource } = require('@znemz/cft-utils/src/resources/taggable');
13
12
 
@@ -26,6 +25,8 @@ const { lowerCamelCase, upperCamelCase } = require('./lib/utils');
26
25
  const { isOurExplicitFunction } = require('./lib/schema');
27
26
  const { getAwsPseudoParameters, buildResourceArn } = require('./lib/internals');
28
27
  const { cachedReadFile } = require('./lib/cache');
28
+ const { createChildScope } = require('./lib/scope');
29
+ const { promiseProps } = require('./lib/promise-utils');
29
30
 
30
31
  /**
31
32
  * @param {object} options
@@ -109,7 +110,8 @@ async function recurse({ base, scope, cft, rootTemplate, caller, ...opts }) {
109
110
  if (opts.doLog) {
110
111
  console.log({ base, scope, cft, rootTemplate, caller, ...opts });
111
112
  }
112
- scope = _.clone(scope);
113
+ // Use Object.create() for O(1) child scope creation instead of O(n) clone
114
+ scope = createChildScope(scope);
113
115
  if (Array.isArray(cft)) {
114
116
  return Promise.all(cft.map((o) => recurse({ base, scope, cft: o, rootTemplate, caller: 'recurse:isArray', ...opts })));
115
117
  }
@@ -137,13 +139,14 @@ async function recurse({ base, scope, cft, rootTemplate, caller, ...opts }) {
137
139
  placeholder = '_';
138
140
  }
139
141
  return PromiseExt.mapX(recurse({ base, scope, cft: list, rootTemplate, caller: 'Fn::Map', ...opts }), (replace, key) => {
140
- scope = _.clone(scope);
141
- scope[placeholder] = replace;
142
+ // Use Object.create() for O(1) child scope creation instead of O(n) clone
143
+ const additions = { [placeholder]: replace };
142
144
  if (hasindex) {
143
- scope[idx] = key;
145
+ additions[idx] = key;
144
146
  }
145
- const replaced = findAndReplace(scope, _.cloneDeep(body));
146
- return recurse({ base, scope, cft: replaced, rootTemplate, caller: 'Fn::Map', ...opts });
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 });
147
150
  }).then((_cft) => {
148
151
  if (hassize) {
149
152
  _cft = findAndReplace({ [sz]: _cft.length }, _cft);
@@ -582,7 +585,7 @@ async function recurse({ base, scope, cft, rootTemplate, caller, ...opts }) {
582
585
  });
583
586
  }
584
587
 
585
- return Promise.props(
588
+ return promiseProps(
586
589
  _.mapValues(cft, (template, key) => recurse({ base, scope, cft: template, key, rootTemplate, caller: 'recurse:isPlainObject:end', ...opts })),
587
590
  );
588
591
  }
@@ -594,20 +597,23 @@ async function recurse({ base, scope, cft, rootTemplate, caller, ...opts }) {
594
597
  }
595
598
 
596
599
  function findAndReplace(scope, object) {
597
- if (_.isString(object)) {
598
- _.forEach(scope, function (replace, find) {
600
+ if (typeof object === 'string') {
601
+ // Use for...in to walk prototype chain (Object.create() based scopes)
602
+ for (const find in scope) {
599
603
  if (object === find) {
600
- object = replace;
604
+ object = scope[find];
601
605
  }
602
- });
606
+ }
603
607
  }
604
- if (_.isString(object)) {
605
- _.forEach(scope, function (replace, find) {
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];
606
612
  const regex = new RegExp(`\\\${${find}}`, 'g');
607
613
  if (find !== '_' && object.match(regex)) {
608
614
  object = object.replace(regex, replace);
609
615
  }
610
- });
616
+ }
611
617
  }
612
618
  if (Array.isArray(object)) {
613
619
  object = object.map(_.bind(findAndReplace, this, scope));
@@ -615,7 +621,7 @@ function findAndReplace(scope, object) {
615
621
  object = _.mapKeys(object, function (value, key) {
616
622
  return findAndReplace(scope, key);
617
623
  });
618
- _.keys(object).forEach(function (key) {
624
+ Object.keys(object).forEach(function (key) {
619
625
  if (key === 'Fn::Map') return;
620
626
  object[key] = findAndReplace(scope, object[key]);
621
627
  });
@@ -737,7 +743,7 @@ async function fnInclude({ base, scope, cft, ...opts }) {
737
743
  body = cachedReadFile(absolute).then(procTemplate);
738
744
  absolute = `${location.protocol}://${absolute}`;
739
745
  } else if (location.protocol === 's3') {
740
- const basedir = pathParse(base.path).dir;
746
+ const basedir = path.parse(base.path).dir;
741
747
  const bucket = location.relative ? base.host : location.host;
742
748
 
743
749
  let key = location.relative ? url.resolve(`${basedir}/`, location.raw) : location.path;
@@ -753,7 +759,7 @@ async function fnInclude({ base, scope, cft, ...opts }) {
753
759
  .then((res) => res.Body.toString())
754
760
  .then(procTemplate);
755
761
  } else if (location.protocol && location.protocol.match(/^https?$/)) {
756
- const basepath = `${pathParse(base.path).dir}/`;
762
+ const basepath = `${path.parse(base.path).dir}/`;
757
763
 
758
764
  absolute = location.relative
759
765
  ? url.resolve(`${location.protocol}://${base.host}${basepath}`, location.raw)
@@ -0,0 +1,50 @@
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
+ keys.forEach((key, i) => {
16
+ result[key] = 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
+ module.exports = {
47
+ promiseProps,
48
+ promiseTry,
49
+ promiseMap,
50
+ };
package/lib/promise.js CHANGED
@@ -1,17 +1,17 @@
1
- const Promise = require('bluebird');
2
- const _ = require('lodash');
1
+ const { promiseTry, promiseMap } = require('./promise-utils');
3
2
 
4
3
  /*
5
4
  Maps over objects or iterables just like lodash.
6
5
  */
7
6
  const mapWhatever = (promises, cb) =>
8
- Promise.try(() =>
7
+ promiseTry(() =>
9
8
  Promise.resolve(promises).then((arrayOrObject) => {
10
- if (_.isArray(arrayOrObject)) {
11
- return Promise.map(arrayOrObject, cb);
9
+ if (Array.isArray(arrayOrObject)) {
10
+ return promiseMap(arrayOrObject, cb);
12
11
  }
13
12
  const size = Object.values(arrayOrObject).length;
14
- return Promise.all(_.map(arrayOrObject, (value, key) => cb(value, key, size)));
13
+ const entries = Object.entries(arrayOrObject);
14
+ return Promise.all(entries.map(([key, value]) => cb(value, key, size)));
15
15
  }),
16
16
  );
17
17
 
package/lib/scope.js ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Scope helper functions for lazy prototype-chain based scope management.
3
+ *
4
+ * Instead of _.clone(scope) which copies O(n) properties each time,
5
+ * we use Object.create(scope) which creates a child scope in O(1) time
6
+ * that inherits from the parent via the prototype chain.
7
+ */
8
+
9
+ /**
10
+ * Create a child scope that inherits from the parent.
11
+ * Uses Object.create() for O(1) creation instead of cloning.
12
+ *
13
+ * @param {Object} parent - The parent scope to inherit from
14
+ * @param {Object} [additions={}] - Properties to add to the child scope
15
+ * @returns {Object} A new child scope with prototype chain to parent
16
+ */
17
+ function createChildScope(parent, additions = {}) {
18
+ const child = Object.create(parent);
19
+ Object.assign(child, additions);
20
+ return child;
21
+ }
22
+
23
+ /**
24
+ * Convert a prototype-chain scope to a plain object.
25
+ * Uses for...in to walk the entire prototype chain.
26
+ *
27
+ * Useful when we need to pass scope to functions that don't
28
+ * walk the prototype chain (e.g., Object.keys, _.forEach).
29
+ *
30
+ * @param {Object} scope - The scope to flatten
31
+ * @returns {Object} A plain object with all inherited properties
32
+ */
33
+ function scopeToObject(scope) {
34
+ const result = {};
35
+ for (const key in scope) {
36
+ result[key] = scope[key];
37
+ }
38
+ return result;
39
+ }
40
+
41
+ /**
42
+ * Iterate over all properties in a scope, including inherited ones.
43
+ * This is a replacement for _.forEach that walks the prototype chain.
44
+ *
45
+ * @param {Object} scope - The scope to iterate over
46
+ * @param {Function} callback - Function to call with (value, key)
47
+ */
48
+ function forEachInScope(scope, callback) {
49
+ for (const key in scope) {
50
+ callback(scope[key], key);
51
+ }
52
+ }
53
+
54
+ module.exports = {
55
+ createChildScope,
56
+ scopeToObject,
57
+ forEachInScope,
58
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@znemz/cfn-include",
3
- "version": "2.1.20",
3
+ "version": "2.1.22",
4
4
  "description": "Preprocessor for CloudFormation templates with support for loops and flexible include statements",
5
5
  "keywords": [
6
6
  "aws",
@@ -48,14 +48,12 @@
48
48
  "@znemz/cft-utils": "0.1.33",
49
49
  "@znemz/sort-object": "^3.0.4",
50
50
  "aws-sdk-v3-proxy": "2.2.0",
51
- "bluebird": "^3.7.2",
52
51
  "deepmerge": "^4.2.2",
53
52
  "glob": "^13.0.0",
54
53
  "jmespath": "^0.16.0",
55
54
  "js-yaml": "^4.1.1",
56
55
  "jsonminify": "^0.4.1",
57
56
  "lodash": "^4.17.21",
58
- "path-parse": "~1.0.7",
59
57
  "proxy-agent": "6.5.0",
60
58
  "yargs": "~18.0.0"
61
59
  },