configorama 0.6.18 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -2
- package/cli.js +1 -0
- package/index.d.ts +90 -37
- package/package.json +8 -2
- package/src/index.js +42 -14
- package/src/main.js +135 -62
- package/src/parsers/index.js +10 -0
- package/src/parsers/typescript.js +20 -43
- package/src/parsers/yaml.js +35 -2
- package/src/resolvers/valueFromFile.js +87 -24
- package/src/resolvers/valueFromGit.js +11 -3
- package/src/utils/encoders/js-fixes.js +44 -0
- package/src/utils/parsing/mergeByKeys.js +4 -3
- package/src/utils/parsing/parse.js +4 -2
- package/src/utils/parsing/preProcess.js +42 -25
- package/src/utils/parsing/preProcess.test.js +214 -0
- package/src/utils/paths/getFullFilePath.js +1 -1
- package/src/utils/resolution/preResolveVariable.js +1 -0
- package/src/utils/strings/quoteUtils.js +2 -2
- package/src/utils/strings/splitCsv.js +1 -1
- package/src/utils/ui/logs.js +4 -4
- package/src/utils/validation/warnIfNotFound.js +3 -3
- package/src/utils/variables/findNestedVariables.js +2 -2
- package/src/utils/variables/variableUtils.js +43 -0
- package/src/utils/variables/variableUtils.test.js +38 -1
- package/types/cli.d.ts +3 -0
- package/types/cli.d.ts.map +1 -0
- package/types/src/functions/md5.d.ts +3 -0
- package/types/src/functions/md5.d.ts.map +1 -0
- package/types/src/index.d.ts +100 -0
- package/types/src/index.d.ts.map +1 -0
- package/types/src/main.d.ts +275 -0
- package/types/src/main.d.ts.map +1 -0
- package/types/src/parsers/esm.d.ts +15 -0
- package/types/src/parsers/esm.d.ts.map +1 -0
- package/types/src/parsers/hcl.d.ts +1 -0
- package/types/src/parsers/hcl.d.ts.map +1 -0
- package/types/src/parsers/index.d.ts +18 -0
- package/types/src/parsers/index.d.ts.map +1 -0
- package/types/src/parsers/ini.d.ts +6 -0
- package/types/src/parsers/ini.d.ts.map +1 -0
- package/types/src/parsers/json5.d.ts +10 -0
- package/types/src/parsers/json5.d.ts.map +1 -0
- package/types/src/parsers/toml.d.ts +7 -0
- package/types/src/parsers/toml.d.ts.map +1 -0
- package/types/src/parsers/typescript.d.ts +15 -0
- package/types/src/parsers/typescript.d.ts.map +1 -0
- package/types/src/parsers/yaml.d.ts +45 -0
- package/types/src/parsers/yaml.d.ts.map +1 -0
- package/types/src/resolvers/valueFromCron.d.ts +14 -0
- package/types/src/resolvers/valueFromCron.d.ts.map +1 -0
- package/types/src/resolvers/valueFromEnv.d.ts +8 -0
- package/types/src/resolvers/valueFromEnv.d.ts.map +1 -0
- package/types/src/resolvers/valueFromEval.d.ts +7 -0
- package/types/src/resolvers/valueFromEval.d.ts.map +1 -0
- package/types/src/resolvers/valueFromFile.d.ts +58 -0
- package/types/src/resolvers/valueFromFile.d.ts.map +1 -0
- package/types/src/resolvers/valueFromGit.d.ts +11 -0
- package/types/src/resolvers/valueFromGit.d.ts.map +1 -0
- package/types/src/resolvers/valueFromNumber.d.ts +6 -0
- package/types/src/resolvers/valueFromNumber.d.ts.map +1 -0
- package/types/src/resolvers/valueFromOptions.d.ts +9 -0
- package/types/src/resolvers/valueFromOptions.d.ts.map +1 -0
- package/types/src/resolvers/valueFromSelf.d.ts +1 -0
- package/types/src/resolvers/valueFromSelf.d.ts.map +1 -0
- package/types/src/resolvers/valueFromString.d.ts +6 -0
- package/types/src/resolvers/valueFromString.d.ts.map +1 -0
- package/types/src/sync.d.ts +3 -0
- package/types/src/sync.d.ts.map +1 -0
- package/types/src/utils/PromiseTracker.d.ts +19 -0
- package/types/src/utils/PromiseTracker.d.ts.map +1 -0
- package/types/src/utils/encoders/index.d.ts +2 -0
- package/types/src/utils/encoders/index.d.ts.map +1 -0
- package/types/src/utils/encoders/js-fixes.d.ts +23 -0
- package/types/src/utils/encoders/js-fixes.d.ts.map +1 -0
- package/types/src/utils/encoders/unknown-values.d.ts +18 -0
- package/types/src/utils/encoders/unknown-values.d.ts.map +1 -0
- package/types/src/utils/handleSignalEvents.d.ts +3 -0
- package/types/src/utils/handleSignalEvents.d.ts.map +1 -0
- package/types/src/utils/lodash.d.ts +4 -0
- package/types/src/utils/lodash.d.ts.map +1 -0
- package/types/src/utils/parsing/arrayToJsonPath.d.ts +5 -0
- package/types/src/utils/parsing/arrayToJsonPath.d.ts.map +1 -0
- package/types/src/utils/parsing/cloudformationSchema.d.ts +2 -0
- package/types/src/utils/parsing/cloudformationSchema.d.ts.map +1 -0
- package/types/src/utils/parsing/enrichMetadata.d.ts +17 -0
- package/types/src/utils/parsing/enrichMetadata.d.ts.map +1 -0
- package/types/src/utils/parsing/mergeByKeys.d.ts +5 -0
- package/types/src/utils/parsing/mergeByKeys.d.ts.map +1 -0
- package/types/src/utils/parsing/parse.d.ts +54 -0
- package/types/src/utils/parsing/parse.d.ts.map +1 -0
- package/types/src/utils/parsing/preProcess.d.ts +10 -0
- package/types/src/utils/parsing/preProcess.d.ts.map +1 -0
- package/types/src/utils/paths/filePathUtils.d.ts +32 -0
- package/types/src/utils/paths/filePathUtils.d.ts.map +1 -0
- package/types/src/utils/paths/findLineForKey.d.ts +12 -0
- package/types/src/utils/paths/findLineForKey.d.ts.map +1 -0
- package/types/src/utils/paths/findProjectRoot.d.ts +2 -0
- package/types/src/utils/paths/findProjectRoot.d.ts.map +1 -0
- package/types/src/utils/paths/getFullFilePath.d.ts +25 -0
- package/types/src/utils/paths/getFullFilePath.d.ts.map +1 -0
- package/types/src/utils/paths/resolveAlias.d.ts +14 -0
- package/types/src/utils/paths/resolveAlias.d.ts.map +1 -0
- package/types/src/utils/regex/index.d.ts +14 -0
- package/types/src/utils/regex/index.d.ts.map +1 -0
- package/types/src/utils/resolution/preResolveVariable.d.ts +51 -0
- package/types/src/utils/resolution/preResolveVariable.d.ts.map +1 -0
- package/types/src/utils/strings/bracketMatcher.d.ts +25 -0
- package/types/src/utils/strings/bracketMatcher.d.ts.map +1 -0
- package/types/src/utils/strings/formatFunctionArgs.d.ts +3 -0
- package/types/src/utils/strings/formatFunctionArgs.d.ts.map +1 -0
- package/types/src/utils/strings/quoteUtils.d.ts +31 -0
- package/types/src/utils/strings/quoteUtils.d.ts.map +1 -0
- package/types/src/utils/strings/replaceAll.d.ts +9 -0
- package/types/src/utils/strings/replaceAll.d.ts.map +1 -0
- package/types/src/utils/strings/splitByComma.d.ts +2 -0
- package/types/src/utils/strings/splitByComma.d.ts.map +1 -0
- package/types/src/utils/strings/splitCsv.d.ts +10 -0
- package/types/src/utils/strings/splitCsv.d.ts.map +1 -0
- package/types/src/utils/strings/textUtils.d.ts +15 -0
- package/types/src/utils/strings/textUtils.d.ts.map +1 -0
- package/types/src/utils/ui/chalk.d.ts +70 -0
- package/types/src/utils/ui/chalk.d.ts.map +1 -0
- package/types/src/utils/ui/configWizard.d.ts +67 -0
- package/types/src/utils/ui/configWizard.d.ts.map +1 -0
- package/types/src/utils/ui/createEditorLink.d.ts +11 -0
- package/types/src/utils/ui/createEditorLink.d.ts.map +1 -0
- package/types/src/utils/ui/deep-log.d.ts +3 -0
- package/types/src/utils/ui/deep-log.d.ts.map +1 -0
- package/types/src/utils/ui/logs.d.ts +2 -0
- package/types/src/utils/ui/logs.d.ts.map +1 -0
- package/types/src/utils/validation/warnIfNotFound.d.ts +15 -0
- package/types/src/utils/validation/warnIfNotFound.d.ts.map +1 -0
- package/types/src/utils/variables/appendDeepVariable.d.ts +3 -0
- package/types/src/utils/variables/appendDeepVariable.d.ts.map +1 -0
- package/types/src/utils/variables/cleanVariable.d.ts +3 -0
- package/types/src/utils/variables/cleanVariable.d.ts.map +1 -0
- package/types/src/utils/variables/findNestedVariables.d.ts +23 -0
- package/types/src/utils/variables/findNestedVariables.d.ts.map +1 -0
- package/types/src/utils/variables/getVariableType.d.ts +8 -0
- package/types/src/utils/variables/getVariableType.d.ts.map +1 -0
- package/types/src/utils/variables/variableUtils.d.ts +21 -0
- package/types/src/utils/variables/variableUtils.d.ts.map +1 -0
|
@@ -2,10 +2,10 @@ const path = require('path')
|
|
|
2
2
|
const fs = require('fs')
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Load TypeScript file and return its export (without executing)
|
|
6
6
|
* @param {string} filePath - Full path to the TypeScript file
|
|
7
|
-
* @param {Object} opts - Additional options
|
|
8
|
-
* @returns {Promise<*>} The
|
|
7
|
+
* @param {Object} opts - Additional options (unused, kept for API compat)
|
|
8
|
+
* @returns {Promise<*>} The exported module from the TypeScript file
|
|
9
9
|
*/
|
|
10
10
|
async function executeTypeScriptFile(filePath, opts = {}) {
|
|
11
11
|
// Check if tsx is available first (preferred)
|
|
@@ -34,6 +34,7 @@ async function executeTypeScriptFile(filePath, opts = {}) {
|
|
|
34
34
|
let tsFile
|
|
35
35
|
if (useTsx) {
|
|
36
36
|
// Use tsx for modern, fast TypeScript execution
|
|
37
|
+
// @ts-ignore - tsx doesn't have type declarations
|
|
37
38
|
const { register } = require('tsx/cjs/api')
|
|
38
39
|
const restore = register()
|
|
39
40
|
try {
|
|
@@ -46,6 +47,7 @@ async function executeTypeScriptFile(filePath, opts = {}) {
|
|
|
46
47
|
} else {
|
|
47
48
|
// Fallback to ts-node
|
|
48
49
|
try {
|
|
50
|
+
// @ts-ignore - ts-node is optional peer dependency
|
|
49
51
|
require('ts-node/register')
|
|
50
52
|
tsFile = require(filePath)
|
|
51
53
|
} catch (err) {
|
|
@@ -53,34 +55,19 @@ async function executeTypeScriptFile(filePath, opts = {}) {
|
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
let tsArgs = opts.dynamicArgs || {}
|
|
60
|
-
if (tsArgs && typeof tsArgs === 'function') {
|
|
61
|
-
tsArgs = tsArgs()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const result = tsFile(tsArgs)
|
|
66
|
-
|
|
67
|
-
// Handle promises
|
|
68
|
-
if (result && typeof result.then === 'function') {
|
|
69
|
-
return await result
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return result
|
|
73
|
-
} catch (err) {
|
|
74
|
-
throw new Error(`Error executing TypeScript function: ${err.message}`)
|
|
75
|
-
}
|
|
58
|
+
// Handle ES module default exports
|
|
59
|
+
if (tsFile && typeof tsFile === 'object' && 'default' in tsFile) {
|
|
60
|
+
tsFile = tsFile.default
|
|
76
61
|
}
|
|
62
|
+
|
|
63
|
+
return tsFile
|
|
77
64
|
}
|
|
78
65
|
|
|
79
66
|
/**
|
|
80
|
-
*
|
|
67
|
+
* Load TypeScript file synchronously and return its export
|
|
81
68
|
* @param {string} filePath - Full path to the TypeScript file
|
|
82
|
-
* @param {Object} opts - Additional options
|
|
83
|
-
* @returns {*} The
|
|
69
|
+
* @param {Object} opts - Additional options (unused, kept for API compat)
|
|
70
|
+
* @returns {*} The exported module from the TypeScript file
|
|
84
71
|
*/
|
|
85
72
|
function executeTypeScriptFileSync(filePath, opts = {}) {
|
|
86
73
|
// Check if tsx is available first (preferred)
|
|
@@ -109,6 +96,7 @@ function executeTypeScriptFileSync(filePath, opts = {}) {
|
|
|
109
96
|
let tsFile
|
|
110
97
|
if (useTsx) {
|
|
111
98
|
// Use tsx for modern, fast TypeScript execution
|
|
99
|
+
// @ts-ignore - tsx doesn't have type declarations
|
|
112
100
|
const { register } = require('tsx/cjs/api')
|
|
113
101
|
const restore = register()
|
|
114
102
|
try {
|
|
@@ -121,6 +109,7 @@ function executeTypeScriptFileSync(filePath, opts = {}) {
|
|
|
121
109
|
} else {
|
|
122
110
|
// Fallback to ts-node
|
|
123
111
|
try {
|
|
112
|
+
// @ts-ignore - ts-node is optional peer dependency
|
|
124
113
|
require('ts-node/register')
|
|
125
114
|
tsFile = require(filePath)
|
|
126
115
|
} catch (err) {
|
|
@@ -128,24 +117,12 @@ function executeTypeScriptFileSync(filePath, opts = {}) {
|
|
|
128
117
|
}
|
|
129
118
|
}
|
|
130
119
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
let tsArgs = opts.dynamicArgs || {}
|
|
135
|
-
if (tsArgs && typeof tsArgs === 'function') {
|
|
136
|
-
tsArgs = tsArgs()
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
const result = tsFile(tsArgs)
|
|
141
|
-
|
|
142
|
-
// Note: For sync execution, we don't await promises
|
|
143
|
-
// If the function returns a promise, it will be resolved by the calling code
|
|
144
|
-
return result
|
|
145
|
-
} catch (err) {
|
|
146
|
-
throw new Error(`Error executing TypeScript function: ${err.message}`)
|
|
147
|
-
}
|
|
120
|
+
// Handle ES module default exports
|
|
121
|
+
if (tsFile && typeof tsFile === 'object' && 'default' in tsFile) {
|
|
122
|
+
tsFile = tsFile.default
|
|
148
123
|
}
|
|
124
|
+
|
|
125
|
+
return tsFile
|
|
149
126
|
}
|
|
150
127
|
|
|
151
128
|
module.exports = {
|
package/src/parsers/yaml.js
CHANGED
|
@@ -3,7 +3,12 @@ const TOML = require('./toml')
|
|
|
3
3
|
const JSON = require('./json5')
|
|
4
4
|
const { findOutermostVariables, findOutermostBracesDepthFirst } = require('../utils/strings/bracketMatcher')
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Loader for custom CF syntax
|
|
8
|
+
* @param {string|Buffer} contents - YAML content to load
|
|
9
|
+
* @param {Object} [options] - YAML load options
|
|
10
|
+
* @returns {{data: Object|null, error: Error|null}} Parsed data and error if any
|
|
11
|
+
*/
|
|
7
12
|
function load(contents, options) {
|
|
8
13
|
let data
|
|
9
14
|
let error
|
|
@@ -15,6 +20,12 @@ function load(contents, options) {
|
|
|
15
20
|
return { data, error }
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Parse YAML content into JavaScript object
|
|
25
|
+
* @param {string} ymlContents - YAML string to parse
|
|
26
|
+
* @returns {Object} Parsed YAML object
|
|
27
|
+
* @throws {Error} If YAML parsing fails
|
|
28
|
+
*/
|
|
18
29
|
function parse(ymlContents) {
|
|
19
30
|
// Get document, or throw exception on error
|
|
20
31
|
let ymlObject = {}
|
|
@@ -26,6 +37,12 @@ function parse(ymlContents) {
|
|
|
26
37
|
return ymlObject
|
|
27
38
|
}
|
|
28
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Convert JavaScript object to YAML string
|
|
42
|
+
* @param {Object} object - Object to convert to YAML
|
|
43
|
+
* @returns {string} YAML string representation
|
|
44
|
+
* @throws {Error} If conversion fails
|
|
45
|
+
*/
|
|
29
46
|
function dump(object) {
|
|
30
47
|
let yml
|
|
31
48
|
try {
|
|
@@ -38,6 +55,12 @@ function dump(object) {
|
|
|
38
55
|
return yml
|
|
39
56
|
}
|
|
40
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Convert YAML content to TOML format
|
|
60
|
+
* @param {string} ymlContents - YAML string to convert
|
|
61
|
+
* @returns {string} TOML string representation
|
|
62
|
+
* @throws {Error} If conversion fails
|
|
63
|
+
*/
|
|
41
64
|
function toToml(ymlContents) {
|
|
42
65
|
let toml
|
|
43
66
|
try {
|
|
@@ -48,6 +71,12 @@ function toToml(ymlContents) {
|
|
|
48
71
|
return toml
|
|
49
72
|
}
|
|
50
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Convert YAML content to JSON format
|
|
76
|
+
* @param {string} ymlContents - YAML string to convert
|
|
77
|
+
* @returns {string} JSON string representation
|
|
78
|
+
* @throws {Error} If conversion fails
|
|
79
|
+
*/
|
|
51
80
|
function toJson(ymlContents) {
|
|
52
81
|
let json
|
|
53
82
|
try {
|
|
@@ -61,12 +90,16 @@ function toJson(ymlContents) {
|
|
|
61
90
|
// Alias for backward compatibility
|
|
62
91
|
const matchOutermostBraces = findOutermostBracesDepthFirst
|
|
63
92
|
|
|
64
|
-
|
|
65
93
|
// https://regex101.com/r/XIltbc/1
|
|
66
94
|
const KEY_OBJECT = /^[ \t]*[^":\s]*:\s+\{/gm
|
|
67
95
|
|
|
68
96
|
const INNER_ARRAY = /\[(?:[^\[\]])*\]/g
|
|
69
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Pre-process YAML string to handle nested variables and CloudFormation syntax
|
|
100
|
+
* @param {string} [ymlStr=''] - YAML string to pre-process
|
|
101
|
+
* @returns {string} Pre-processed YAML string
|
|
102
|
+
*/
|
|
70
103
|
function preProcess(ymlStr = '') {
|
|
71
104
|
/*
|
|
72
105
|
return ymlStr
|
|
@@ -7,7 +7,7 @@ const { splitCsv } = require('../utils/strings/splitCsv')
|
|
|
7
7
|
const { resolveFilePathFromMatch, resolveFilePath } = require('../utils/paths/getFullFilePath')
|
|
8
8
|
const { findNestedVariables } = require('../utils/variables/findNestedVariables')
|
|
9
9
|
const { makeBox } = require('@davidwells/box-logger')
|
|
10
|
-
const { encodeJsSyntax } = require('../utils/encoders/js-fixes')
|
|
10
|
+
const { encodeJsSyntax, decodeJsonInVariable, hasEncodedJson } = require('../utils/encoders/js-fixes')
|
|
11
11
|
|
|
12
12
|
/* File Parsers */
|
|
13
13
|
const YAML = require('../parsers/yaml')
|
|
@@ -15,6 +15,29 @@ const TOML = require('../parsers/toml')
|
|
|
15
15
|
const INI = require('../parsers/ini')
|
|
16
16
|
const JSON5 = require('../parsers/json5')
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Recursively clean encoded JSON from an object
|
|
20
|
+
* @param {*} obj - Object to clean
|
|
21
|
+
* @returns {*} Cleaned object
|
|
22
|
+
*/
|
|
23
|
+
function cleanEncodedJson(obj) {
|
|
24
|
+
if (!obj) return obj
|
|
25
|
+
if (typeof obj === 'string') {
|
|
26
|
+
return decodeJsonInVariable(obj)
|
|
27
|
+
}
|
|
28
|
+
if (Array.isArray(obj)) {
|
|
29
|
+
return obj.map(cleanEncodedJson)
|
|
30
|
+
}
|
|
31
|
+
if (typeof obj === 'object') {
|
|
32
|
+
const cleaned = {}
|
|
33
|
+
for (const key of Object.keys(obj)) {
|
|
34
|
+
cleaned[key] = cleanEncodedJson(obj[key])
|
|
35
|
+
}
|
|
36
|
+
return cleaned
|
|
37
|
+
}
|
|
38
|
+
return obj
|
|
39
|
+
}
|
|
40
|
+
|
|
18
41
|
/**
|
|
19
42
|
* Parse file contents based on file extension
|
|
20
43
|
* @param {string} content - Raw file contents
|
|
@@ -86,6 +109,17 @@ async function getValueFromFile(ctx, variableString, options) {
|
|
|
86
109
|
matchedFileString = argsFound[0]
|
|
87
110
|
argsToPass = argsFound.filter((arg, i) => {
|
|
88
111
|
return i !== 0
|
|
112
|
+
}).map((arg) => {
|
|
113
|
+
// Decode base64-encoded JSON objects passed as args
|
|
114
|
+
if (hasEncodedJson(arg)) {
|
|
115
|
+
const decoded = decodeJsonInVariable(arg)
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(decoded)
|
|
118
|
+
} catch (e) {
|
|
119
|
+
return decoded
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return arg
|
|
89
123
|
})
|
|
90
124
|
}
|
|
91
125
|
}
|
|
@@ -137,9 +171,8 @@ async function getValueFromFile(ctx, variableString, options) {
|
|
|
137
171
|
|
|
138
172
|
ctx.fileRefsFound.push(fileRefEntry)
|
|
139
173
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
fileExtension = fileExtension[fileExtension.length - 1].toLowerCase()
|
|
174
|
+
const fileExtParts = resolvedPath.split('.')
|
|
175
|
+
const fileExtension = fileExtParts[fileExtParts.length - 1].toLowerCase()
|
|
143
176
|
|
|
144
177
|
// Validate file exists
|
|
145
178
|
if (!exists) {
|
|
@@ -168,14 +201,14 @@ async function getValueFromFile(ctx, variableString, options) {
|
|
|
168
201
|
const errorMsg = makeBox({
|
|
169
202
|
title: `File Not Found in ${originalVar}`,
|
|
170
203
|
minWidth: '100%',
|
|
171
|
-
|
|
204
|
+
content: `Variable ${variableString} cannot resolve due to missing file.
|
|
172
205
|
|
|
173
206
|
File not found ${fullFilePath}
|
|
174
207
|
|
|
175
208
|
Default fallback value will be used if provided.
|
|
176
209
|
|
|
177
210
|
${JSON.stringify(options.context, null, 2)}`,
|
|
178
|
-
|
|
211
|
+
})
|
|
179
212
|
console.log(errorMsg)
|
|
180
213
|
}
|
|
181
214
|
// TODO maybe reject. YAML does not allow for null/undefined values
|
|
@@ -197,11 +230,17 @@ ${JSON.stringify(options.context, null, 2)}`,
|
|
|
197
230
|
return Promise.resolve(valueToPopulate)
|
|
198
231
|
}
|
|
199
232
|
|
|
233
|
+
// Clean encoded JSON from currentConfig for cleaner context
|
|
234
|
+
const cleanedCurrentConfig = cleanEncodedJson(ctx.config)
|
|
235
|
+
|
|
200
236
|
// Build context for executable files
|
|
201
237
|
const valueForFunction = {
|
|
238
|
+
options: ctx.opts.options || {},
|
|
202
239
|
originalConfig: ctx.originalConfig,
|
|
203
|
-
|
|
204
|
-
|
|
240
|
+
currentConfig: cleanedCurrentConfig,
|
|
241
|
+
argsToPass,
|
|
242
|
+
// maybe helper fns
|
|
243
|
+
// maybe the lib instance itself for nested lookups
|
|
205
244
|
}
|
|
206
245
|
|
|
207
246
|
// Process JS files
|
|
@@ -230,8 +269,14 @@ ${JSON.stringify(options.context, null, 2)}`,
|
|
|
230
269
|
try {
|
|
231
270
|
const tsFile = await executeTypeScriptFile(fullFilePath, { dynamicArgs: () => argsToPass })
|
|
232
271
|
let returnValueFunction = tsFile.config || tsFile.default || tsFile
|
|
233
|
-
|
|
272
|
+
// For default export functions with :property syntax, keep the function and use deep properties
|
|
273
|
+
// For named exports (non-function module), look up the named export
|
|
274
|
+
let includeFirstProperty = false
|
|
275
|
+
if (moduleName && typeof returnValueFunction !== 'function') {
|
|
234
276
|
returnValueFunction = tsFile[moduleName]
|
|
277
|
+
} else if (moduleName && typeof returnValueFunction === 'function') {
|
|
278
|
+
// Default export function with property access - include first property in path
|
|
279
|
+
includeFirstProperty = true
|
|
235
280
|
}
|
|
236
281
|
|
|
237
282
|
return processExecutableFile({
|
|
@@ -243,7 +288,8 @@ ${JSON.stringify(options.context, null, 2)}`,
|
|
|
243
288
|
matchedFileString,
|
|
244
289
|
relativePath,
|
|
245
290
|
fileType: 'TypeScript',
|
|
246
|
-
getDeeperValue: ctx.getDeeperValue
|
|
291
|
+
getDeeperValue: ctx.getDeeperValue,
|
|
292
|
+
includeFirstProperty
|
|
247
293
|
})
|
|
248
294
|
} catch (err) {
|
|
249
295
|
return Promise.reject(new Error(`Error processing TypeScript file: ${err.message}`))
|
|
@@ -257,8 +303,14 @@ ${JSON.stringify(options.context, null, 2)}`,
|
|
|
257
303
|
try {
|
|
258
304
|
const esmFile = await executeESMFile(fullFilePath, { dynamicArgs: () => argsToPass })
|
|
259
305
|
let returnValueFunction = esmFile.config || esmFile.default || esmFile
|
|
260
|
-
|
|
306
|
+
// For default export functions with :property syntax, keep the function and use deep properties
|
|
307
|
+
// For named exports (non-function module), look up the named export
|
|
308
|
+
let includeFirstProperty = false
|
|
309
|
+
if (moduleName && typeof returnValueFunction !== 'function') {
|
|
261
310
|
returnValueFunction = esmFile[moduleName]
|
|
311
|
+
} else if (moduleName && typeof returnValueFunction === 'function') {
|
|
312
|
+
// Default export function with property access - include first property in path
|
|
313
|
+
includeFirstProperty = true
|
|
262
314
|
}
|
|
263
315
|
|
|
264
316
|
return processExecutableFile({
|
|
@@ -270,7 +322,8 @@ ${JSON.stringify(options.context, null, 2)}`,
|
|
|
270
322
|
matchedFileString,
|
|
271
323
|
relativePath,
|
|
272
324
|
fileType: 'ESM',
|
|
273
|
-
getDeeperValue: ctx.getDeeperValue
|
|
325
|
+
getDeeperValue: ctx.getDeeperValue,
|
|
326
|
+
includeFirstProperty
|
|
274
327
|
})
|
|
275
328
|
} catch (err) {
|
|
276
329
|
return Promise.reject(new Error(`Error processing ESM file: ${err.message}`))
|
|
@@ -295,15 +348,15 @@ ${JSON.stringify(options.context, null, 2)}`,
|
|
|
295
348
|
}
|
|
296
349
|
// console.log('deep', variableString)
|
|
297
350
|
// console.log('matchedFileString', matchedFileString)
|
|
298
|
-
|
|
351
|
+
const deepPropertiesStr = variableString.replace(matchedFileString, '')
|
|
299
352
|
// Support both : and . as the separator for sub properties
|
|
300
|
-
const firstChar =
|
|
353
|
+
const firstChar = deepPropertiesStr.substring(0, 1)
|
|
301
354
|
if (firstChar !== ':' && firstChar !== '.') {
|
|
302
355
|
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}" sub properties
|
|
303
|
-
Please use ":" or "." to reference sub properties. ${
|
|
356
|
+
Please use ":" or "." to reference sub properties. ${deepPropertiesStr}`
|
|
304
357
|
return Promise.reject(new Error(errorMessage))
|
|
305
358
|
}
|
|
306
|
-
deepProperties =
|
|
359
|
+
const deepProperties = deepPropertiesStr.slice(1).split('.')
|
|
307
360
|
return ctx.getDeeperValue(deepProperties, valueToPopulate)
|
|
308
361
|
}
|
|
309
362
|
|
|
@@ -360,13 +413,21 @@ function parseModuleReference(variableString, matchedFileString) {
|
|
|
360
413
|
* Extracts deep properties from variable string after file match
|
|
361
414
|
* @param {string} variableString - The full variable string
|
|
362
415
|
* @param {string} matchedFileString - The matched file path portion
|
|
416
|
+
* @param {boolean} [includeFirstProperty=false] - Include first property (for default exports)
|
|
363
417
|
* @returns {string[]} Array of property keys to traverse
|
|
364
418
|
*/
|
|
365
|
-
function extractDeepProperties(variableString, matchedFileString) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
419
|
+
function extractDeepProperties(variableString, matchedFileString, includeFirstProperty = false) {
|
|
420
|
+
const deepPropertiesStr = variableString.replace(matchedFileString, '')
|
|
421
|
+
if (!deepPropertiesStr || deepPropertiesStr === '') {
|
|
422
|
+
return []
|
|
423
|
+
}
|
|
424
|
+
const deepProperties = deepPropertiesStr.slice(1).split('.')
|
|
425
|
+
// For named exports, skip first property (it's the module name)
|
|
426
|
+
// For default exports, keep all properties
|
|
427
|
+
if (!includeFirstProperty) {
|
|
428
|
+
deepProperties.splice(0, 1)
|
|
429
|
+
}
|
|
430
|
+
return deepProperties.map((prop) => trim(prop)).filter(Boolean)
|
|
370
431
|
}
|
|
371
432
|
|
|
372
433
|
/**
|
|
@@ -381,6 +442,7 @@ function extractDeepProperties(variableString, matchedFileString) {
|
|
|
381
442
|
* @param {string} params.relativePath - Relative file path for errors
|
|
382
443
|
* @param {string} params.fileType - Type of file (javascript/TypeScript/ESM)
|
|
383
444
|
* @param {Function} params.getDeeperValue - Function to resolve nested values
|
|
445
|
+
* @param {boolean} [params.includeFirstProperty=false] - Include first property in path (for default exports)
|
|
384
446
|
* @returns {Promise<any>}
|
|
385
447
|
*/
|
|
386
448
|
async function processExecutableFile({
|
|
@@ -392,7 +454,8 @@ async function processExecutableFile({
|
|
|
392
454
|
matchedFileString,
|
|
393
455
|
relativePath,
|
|
394
456
|
fileType,
|
|
395
|
-
getDeeperValue
|
|
457
|
+
getDeeperValue,
|
|
458
|
+
includeFirstProperty = false
|
|
396
459
|
}) {
|
|
397
460
|
if (typeof returnValueFunction !== 'function') {
|
|
398
461
|
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
@@ -400,10 +463,10 @@ Check if your ${fileType} is exporting a function that returns a value.`
|
|
|
400
463
|
return Promise.reject(new Error(errorMessage))
|
|
401
464
|
}
|
|
402
465
|
|
|
403
|
-
const valueToPopulate = returnValueFunction.call(fileModule,
|
|
466
|
+
const valueToPopulate = returnValueFunction.call(fileModule, ...argsToPass, valueForFunction)
|
|
404
467
|
|
|
405
468
|
return Promise.resolve(valueToPopulate).then((valueToPopulateResolved) => {
|
|
406
|
-
const deepProperties = extractDeepProperties(variableString, matchedFileString)
|
|
469
|
+
const deepProperties = extractDeepProperties(variableString, matchedFileString, includeFirstProperty)
|
|
407
470
|
return getDeeperValue(deepProperties, valueToPopulateResolved).then((deepValueToPopulateResolved) => {
|
|
408
471
|
if (typeof deepValueToPopulateResolved === 'undefined') {
|
|
409
472
|
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
@@ -10,13 +10,19 @@ const { findProjectRoot } = require('../utils/paths/findProjectRoot')
|
|
|
10
10
|
const GIT_PREFIX = 'git'
|
|
11
11
|
const gitVariableSyntax = RegExp(/^git:/g)
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Execute a shell command
|
|
15
|
+
* @param {string} cmd - Command to execute
|
|
16
|
+
* @param {import('child_process').ExecOptions} [options] - Exec options
|
|
17
|
+
* @returns {Promise<string>}
|
|
18
|
+
*/
|
|
13
19
|
async function _exec(cmd, options = { timeout: 1000 }) {
|
|
14
20
|
return new Promise((resolve, reject) => {
|
|
15
21
|
childProcess.exec(cmd, options, (err, stdout) => {
|
|
16
22
|
if (err) {
|
|
17
23
|
return reject(err)
|
|
18
24
|
}
|
|
19
|
-
return resolve(stdout.trim())
|
|
25
|
+
return resolve(String(stdout).trim())
|
|
20
26
|
})
|
|
21
27
|
})
|
|
22
28
|
}
|
|
@@ -213,8 +219,10 @@ const cache = new Map()
|
|
|
213
219
|
|
|
214
220
|
/**
|
|
215
221
|
* Gets the last Git commit timestamp for a file
|
|
216
|
-
* @param {string}
|
|
217
|
-
* @
|
|
222
|
+
* @param {string} _file - Path to the file to check
|
|
223
|
+
* @param {string} cwd - Working directory
|
|
224
|
+
* @param {boolean} [throwOnMissing] - Whether to throw on missing file
|
|
225
|
+
* @returns {Promise<string|undefined>} The commit timestamp ISO string or undefined if not in Git
|
|
218
226
|
*/
|
|
219
227
|
async function getGitTimestamp(_file, cwd, throwOnMissing = true) {
|
|
220
228
|
// Validate file path to prevent command injection
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
const PAREN_OPEN_PLACEHOLDER = '__PH_PAREN_OPEN__'
|
|
2
2
|
const OPEN_PAREN_PLACEHOLDER_PATTERN = /__PH_PAREN_OPEN__/g
|
|
3
3
|
|
|
4
|
+
const JSON_ENCODED_PREFIX = '__JSON_B64__'
|
|
5
|
+
const JSON_ENCODED_PATTERN = /__JSON_B64__([A-Za-z0-9+/=]+)__/g
|
|
6
|
+
|
|
4
7
|
function encodeJsSyntax(value = '') {
|
|
5
8
|
return value.replace(/\(/g, PAREN_OPEN_PLACEHOLDER)
|
|
6
9
|
}
|
|
@@ -14,9 +17,50 @@ function hasParenthesesPlaceholder(value = '') {
|
|
|
14
17
|
return OPEN_PAREN_PLACEHOLDER_PATTERN.test(value)
|
|
15
18
|
}
|
|
16
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Encode a JSON object to base64 for safe embedding in variable strings
|
|
22
|
+
* @param {object} obj - Object to encode
|
|
23
|
+
* @returns {string} Encoded string like __JSON_B64__eyJmb28iOiJiYXIifQ==__
|
|
24
|
+
*/
|
|
25
|
+
function encodeJsonForVariable(obj) {
|
|
26
|
+
const jsonStr = JSON.stringify(obj)
|
|
27
|
+
const b64 = Buffer.from(jsonStr).toString('base64')
|
|
28
|
+
return `${JSON_ENCODED_PREFIX}${b64}__`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Decode base64-encoded JSON from variable strings
|
|
33
|
+
* @param {string} value - String potentially containing encoded JSON
|
|
34
|
+
* @returns {string} String with encoded JSON decoded back to JSON strings
|
|
35
|
+
*/
|
|
36
|
+
function decodeJsonInVariable(value) {
|
|
37
|
+
if (!value || typeof value !== 'string') return value
|
|
38
|
+
return value.replace(JSON_ENCODED_PATTERN, (match, b64) => {
|
|
39
|
+
try {
|
|
40
|
+
const jsonStr = Buffer.from(b64, 'base64').toString('utf8')
|
|
41
|
+
return jsonStr
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return match
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if string contains encoded JSON
|
|
50
|
+
* @param {string} value - String to check
|
|
51
|
+
* @returns {boolean}
|
|
52
|
+
*/
|
|
53
|
+
function hasEncodedJson(value) {
|
|
54
|
+
if (!value || typeof value !== 'string') return false
|
|
55
|
+
return value.includes(JSON_ENCODED_PREFIX)
|
|
56
|
+
}
|
|
57
|
+
|
|
17
58
|
module.exports = {
|
|
18
59
|
OPEN_PAREN_PLACEHOLDER_PATTERN,
|
|
19
60
|
hasParenthesesPlaceholder,
|
|
20
61
|
encodeJsSyntax,
|
|
21
62
|
decodeJsSyntax,
|
|
63
|
+
encodeJsonForVariable,
|
|
64
|
+
decodeJsonInVariable,
|
|
65
|
+
hasEncodedJson,
|
|
22
66
|
}
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
function mergeByKeys(data, path, keysToMerge) {
|
|
5
5
|
if (!data) return {}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
|
|
7
|
+
// Handle empty path - operate on root data
|
|
8
|
+
const items = path ? path.split('.').reduce((obj, key) => obj?.[key], data) : data
|
|
9
|
+
if (!Array.isArray(items)) return data
|
|
9
10
|
|
|
10
11
|
const result = {}
|
|
11
12
|
const mergeAll = !keysToMerge || !Array.isArray(keysToMerge) || keysToMerge.length === 0
|
|
@@ -31,12 +31,12 @@ function parseFileContents({ contents, filePath, varRegex, dynamicArgs }) {
|
|
|
31
31
|
|
|
32
32
|
if (fileType.match(/\.(yml|yaml)/i)) {
|
|
33
33
|
try {
|
|
34
|
-
const ymlText = YAML.preProcess(contents
|
|
34
|
+
const ymlText = YAML.preProcess(contents)
|
|
35
35
|
configObject = YAML.parse(ymlText)
|
|
36
36
|
} catch (err) {
|
|
37
37
|
// Attempt to fix cloudformation refs
|
|
38
38
|
if (err.message.match(/YAMLException/)) {
|
|
39
|
-
const ymlText = YAML.preProcess(contents
|
|
39
|
+
const ymlText = YAML.preProcess(contents)
|
|
40
40
|
const result = YAML.load(ymlText, {
|
|
41
41
|
filename: filePath,
|
|
42
42
|
schema: cloudFormationSchema.schema,
|
|
@@ -82,6 +82,8 @@ function parseFileContents({ contents, filePath, varRegex, dynamicArgs }) {
|
|
|
82
82
|
configObject = (typeof configObject.config === 'function') ? configObject.config(jsArgs) : configObject.config
|
|
83
83
|
} else if (configObject.default) {
|
|
84
84
|
configObject = (typeof configObject.default === 'function') ? configObject.default(jsArgs) : configObject.default
|
|
85
|
+
} else if (typeof configObject === 'function') {
|
|
86
|
+
configObject = configObject(jsArgs)
|
|
85
87
|
}
|
|
86
88
|
// console.log('parseFileContents configObject', configObject)
|
|
87
89
|
} catch (err) {
|