configorama 0.10.4 → 0.11.2
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/cli.js +1 -0
- package/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/index.js +1 -0
- package/src/main.js +44 -6
- package/src/parsers/esm.js +1 -16
- package/src/parsers/typescript.js +1 -48
- package/src/resolvers/valueFromCron.js +4 -25
- package/src/resolvers/valueFromFile.js +3 -1
- package/src/utils/paths/ignorePaths.js +1 -0
package/cli.js
CHANGED
package/index.d.ts
CHANGED
|
@@ -75,6 +75,8 @@ interface ConfigoramaSettings {
|
|
|
75
75
|
mergeKeys?: string[]
|
|
76
76
|
/** Map of file paths to override */
|
|
77
77
|
filePathOverrides?: Record<string, string>
|
|
78
|
+
/** Install Configorama CLI signal handlers. Defaults to false for library calls. */
|
|
79
|
+
handleSignalEvents?: boolean
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
interface ConfigoramaResult<T = any> {
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -22,6 +22,7 @@ const { buildVariableSyntax } = require('./utils/variables/variableUtils')
|
|
|
22
22
|
* @property {boolean} [dotEnvDebug] - enable env-stage-loader debug logs when useDotenv/useDotEnv is enabled
|
|
23
23
|
* @property {string[]} [mergeKeys] - keys to merge in arrays of objects
|
|
24
24
|
* @property {Object.<string, string>} [filePathOverrides] - map of file paths to override
|
|
25
|
+
* @property {boolean} [handleSignalEvents] - install Configorama CLI signal handlers. Defaults to false for library calls.
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
28
|
/**
|
package/src/main.js
CHANGED
|
@@ -138,8 +138,8 @@ let DEBUG_TYPE = false
|
|
|
138
138
|
|
|
139
139
|
class Configorama {
|
|
140
140
|
constructor(fileOrObject, opts) {
|
|
141
|
-
/*
|
|
142
|
-
if (opts && !opts.sync) {
|
|
141
|
+
/* CLI-only by default. Library consumers should not get process-level signal handlers. */
|
|
142
|
+
if (opts && opts.handleSignalEvents && !opts.sync) {
|
|
143
143
|
handleSignalEvents()
|
|
144
144
|
}
|
|
145
145
|
|
|
@@ -372,7 +372,12 @@ class Configorama {
|
|
|
372
372
|
description: `Resolves values from files. Supports sub-properties via :key or .key lookup.`,
|
|
373
373
|
match: fileRefSyntax,
|
|
374
374
|
resolver: (varString, o, x, pathValue) => {
|
|
375
|
-
|
|
375
|
+
// Inside ignore-path contexts (e.g. Fn::Sub) inline the file as raw text so
|
|
376
|
+
// embedded CloudFormation refs survive and the body stays a string. Skip
|
|
377
|
+
// raw mode when a :key/.key accessor is present — that needs a parsed value.
|
|
378
|
+
const hasAccessor = /\)\s*[:.]/.test(varString)
|
|
379
|
+
const asRawText = !!(pathValue && this.isIgnorePath(pathValue.path)) && !hasAccessor
|
|
380
|
+
return this.getValueFromFile(varString, { asRawText, context: pathValue })
|
|
376
381
|
},
|
|
377
382
|
},
|
|
378
383
|
|
|
@@ -484,6 +489,16 @@ class Configorama {
|
|
|
484
489
|
)
|
|
485
490
|
this.variablesKnownTypes = variablesKnownTypes
|
|
486
491
|
|
|
492
|
+
// Explicit configorama types that should still resolve inside ignore-path
|
|
493
|
+
// contexts like Fn::Sub (file, text, env, opt, cron, git, user sources, ...).
|
|
494
|
+
// Excludes self/dot.prop refs — those are left verbatim for CloudFormation /
|
|
495
|
+
// downstream Serverless resolution.
|
|
496
|
+
this.subResolvableTypes = combineRegexes(
|
|
497
|
+
/** @type {RegExp[]} */ (this.variableTypes
|
|
498
|
+
.filter((v) => v.type !== 'string' && v.type !== 'self' && v.type !== 'dot.prop' && v.match instanceof RegExp)
|
|
499
|
+
.map((v) => v.match))
|
|
500
|
+
)
|
|
501
|
+
|
|
487
502
|
// Build prefix lookup map for O(1) type detection (perf optimization)
|
|
488
503
|
this._resolverByPrefix = new Map()
|
|
489
504
|
for (const r of this.variableTypes) {
|
|
@@ -1111,9 +1126,23 @@ class Configorama {
|
|
|
1111
1126
|
// #######################
|
|
1112
1127
|
// ## PROPERTY HANDLING ##
|
|
1113
1128
|
// #######################
|
|
1114
|
-
|
|
1129
|
+
isIgnorePath(pathValue) {
|
|
1115
1130
|
return shouldIgnorePath(pathValue, this.ignorePathPatterns)
|
|
1116
1131
|
}
|
|
1132
|
+
// True when the value has a configorama-typed token that resolves even inside an
|
|
1133
|
+
// ignore-path (file/text/env/opt/cron/git/custom) — i.e. not just self/CFN refs.
|
|
1134
|
+
hasSubResolvableToken(value) {
|
|
1135
|
+
if (typeof value !== 'string') return false
|
|
1136
|
+
const matches = this.getMatches(value)
|
|
1137
|
+
if (!isArray(matches)) return false
|
|
1138
|
+
return matches.some((m) => this.subResolvableTypes.test(m.variable))
|
|
1139
|
+
}
|
|
1140
|
+
shouldSkipResolution(pathValue, value) {
|
|
1141
|
+
if (!this.isIgnorePath(pathValue)) return false
|
|
1142
|
+
// Under an ignore path (Fn::Sub etc.) keep resolving configorama's own typed
|
|
1143
|
+
// refs; only skip when nothing but self/CFN refs remain.
|
|
1144
|
+
return !this.hasSubResolvableToken(value)
|
|
1145
|
+
}
|
|
1117
1146
|
|
|
1118
1147
|
/**
|
|
1119
1148
|
* The declaration of a terminal property. This declaration includes the path and value of the
|
|
@@ -1275,7 +1304,7 @@ class Configorama {
|
|
|
1275
1304
|
/* Leave opaque paths verbatim. These often contain non-configorama
|
|
1276
1305
|
`${...}` syntax from CloudFormation, JavaScript, shell, VTL, etc. */
|
|
1277
1306
|
variables = variables.filter((property) => {
|
|
1278
|
-
return !this.shouldSkipResolution(property.path)
|
|
1307
|
+
return !this.shouldSkipResolution(property.path, property.value)
|
|
1279
1308
|
})
|
|
1280
1309
|
/*
|
|
1281
1310
|
console.log(`variables at call count ${this.callCount}`, variables)
|
|
@@ -1563,7 +1592,7 @@ class Configorama {
|
|
|
1563
1592
|
console.log(valueObject)
|
|
1564
1593
|
}
|
|
1565
1594
|
const property = valueObject.value
|
|
1566
|
-
if (this.shouldSkipResolution(valueObject.path)) {
|
|
1595
|
+
if (this.shouldSkipResolution(valueObject.path, property)) {
|
|
1567
1596
|
return Promise.resolve(property)
|
|
1568
1597
|
}
|
|
1569
1598
|
const matches = this.getMatches(property)
|
|
@@ -2166,6 +2195,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2166
2195
|
// Cache joined path to avoid repeated array.join('.') calls
|
|
2167
2196
|
const pathJoined = pathValue && pathValue.length ? pathValue.join('.') : null
|
|
2168
2197
|
|
|
2198
|
+
|
|
2169
2199
|
// Track every call to getValueFromSource for metadata
|
|
2170
2200
|
if (this._trackCalls && pathJoined) {
|
|
2171
2201
|
const pathKey = pathJoined
|
|
@@ -2318,6 +2348,14 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2318
2348
|
// console.log('resolverFunction', resolverFunction)
|
|
2319
2349
|
/** */
|
|
2320
2350
|
|
|
2351
|
+
// Inside ignore-path contexts (Fn::Sub, inline code, VTL templates, ...) leave
|
|
2352
|
+
// self refs, bare config refs, and CloudFormation refs verbatim for CloudFormation
|
|
2353
|
+
// / downstream Serverless to resolve. Everything configorama can resolve on its own
|
|
2354
|
+
// (file/text/env/opt/cron/eval/git/custom/string/number) still resolves.
|
|
2355
|
+
if (this.isIgnorePath(pathValue) && (!found || resolverType === 'self' || resolverType === 'dot.prop')) {
|
|
2356
|
+
return Promise.resolve(encodeUnknown(this.varPrefix + variableString + this.varSuffix))
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2321
2359
|
if (found && resolverFunction) {
|
|
2322
2360
|
/*
|
|
2323
2361
|
console.log(`----------Resolver [${resolverType}]----------------------`)
|
package/src/parsers/esm.js
CHANGED
|
@@ -7,22 +7,7 @@ const path = require('path')
|
|
|
7
7
|
* @returns {Promise<*>} The result of executing the ESM file
|
|
8
8
|
*/
|
|
9
9
|
async function executeESMFile(filePath, opts = {}) {
|
|
10
|
-
|
|
11
|
-
// Use require for now since ESM dynamic import in async context is complex
|
|
12
|
-
// We'll use jiti to handle ESM syntax
|
|
13
|
-
const { createJiti } = require('jiti')
|
|
14
|
-
const jiti = createJiti(__filename, {
|
|
15
|
-
interopDefault: true
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
// Load the ESM file - resolve to absolute path first
|
|
19
|
-
const resolvedPath = path.resolve(filePath)
|
|
20
|
-
let esmModule = jiti(resolvedPath)
|
|
21
|
-
|
|
22
|
-
return esmModule
|
|
23
|
-
} catch (err) {
|
|
24
|
-
throw new Error(`Failed to load ESM file ${filePath}: ${err.message}`)
|
|
25
|
-
}
|
|
10
|
+
return executeESMFileSync(filePath, opts)
|
|
26
11
|
}
|
|
27
12
|
|
|
28
13
|
/**
|
|
@@ -8,54 +8,7 @@ const fs = require('fs')
|
|
|
8
8
|
* @returns {Promise<*>} The exported module from the TypeScript file
|
|
9
9
|
*/
|
|
10
10
|
async function executeTypeScriptFile(filePath, opts = {}) {
|
|
11
|
-
|
|
12
|
-
let useTsx = false
|
|
13
|
-
try {
|
|
14
|
-
require.resolve('tsx/cjs/api')
|
|
15
|
-
useTsx = true
|
|
16
|
-
} catch (err) {
|
|
17
|
-
// Fallback to ts-node if tsx is not available
|
|
18
|
-
try {
|
|
19
|
-
require.resolve('ts-node/register')
|
|
20
|
-
} catch (tsNodeErr) {
|
|
21
|
-
throw new Error(
|
|
22
|
-
'TypeScript support requires either "tsx" or "ts-node" to be installed. ' +
|
|
23
|
-
'Please install one of them:\n' +
|
|
24
|
-
' npm install tsx --save-dev (recommended)\n' +
|
|
25
|
-
' npm install ts-node typescript --save-dev'
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Clear require cache to ensure fresh execution
|
|
31
|
-
const resolvedPath = require.resolve(filePath)
|
|
32
|
-
delete require.cache[resolvedPath]
|
|
33
|
-
|
|
34
|
-
let tsFile
|
|
35
|
-
if (useTsx) {
|
|
36
|
-
// Use tsx for modern, fast TypeScript execution
|
|
37
|
-
// @ts-ignore - tsx doesn't have type declarations
|
|
38
|
-
const { register } = require('tsx/cjs/api')
|
|
39
|
-
const restore = register()
|
|
40
|
-
try {
|
|
41
|
-
tsFile = require(filePath)
|
|
42
|
-
} catch (err) {
|
|
43
|
-
throw new Error(`Failed to load TypeScript file: ${err.message}`)
|
|
44
|
-
} finally {
|
|
45
|
-
restore()
|
|
46
|
-
}
|
|
47
|
-
} else {
|
|
48
|
-
// Fallback to ts-node
|
|
49
|
-
try {
|
|
50
|
-
// @ts-ignore - ts-node is optional peer dependency
|
|
51
|
-
require('ts-node/register')
|
|
52
|
-
tsFile = require(filePath)
|
|
53
|
-
} catch (err) {
|
|
54
|
-
throw new Error(`Failed to load TypeScript file with ts-node: ${err.message}`)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return tsFile
|
|
11
|
+
return executeTypeScriptFileSync(filePath, opts)
|
|
59
12
|
}
|
|
60
13
|
|
|
61
14
|
/**
|
|
@@ -91,34 +91,13 @@ function parseCronExpression(input) {
|
|
|
91
91
|
return `${minute} ${hour} * * *`
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// Parse "every X minutes/hours/days" patterns
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const interval = parseInt(everyMatch[1])
|
|
98
|
-
const unit = everyMatch[2].toLowerCase().replace(/s$/, '') // Remove trailing 's' if present
|
|
99
|
-
|
|
100
|
-
switch (unit) {
|
|
101
|
-
case 'minute':
|
|
102
|
-
return `*/${interval} * * * *`
|
|
103
|
-
case 'hour':
|
|
104
|
-
return `0 */${interval} * * *`
|
|
105
|
-
case 'day':
|
|
106
|
-
return `0 0 */${interval} * *`
|
|
107
|
-
case 'week':
|
|
108
|
-
return `0 0 * * 0/${interval}`
|
|
109
|
-
case 'month':
|
|
110
|
-
return `0 0 1 */${interval} *`
|
|
111
|
-
default:
|
|
112
|
-
throw new Error(`Unsupported interval unit: ${unit}`)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Parse "X minute(s)/hour(s)/day(s)" patterns (e.g., "1 minute", "5 minutes", "1 hour")
|
|
117
|
-
const intervalMatch = normalizedInput.match(/^(\d+) (minute|minutes|hour|hours|day|days|week|weeks|month|months)s?$/i)
|
|
94
|
+
// Parse "every X minutes/hours/days" and bare "X minute(s)/hour(s)/day(s)" patterns
|
|
95
|
+
// (e.g., "every 5 minutes", "1 minute", "5 minutes", "1 hour")
|
|
96
|
+
const intervalMatch = normalizedInput.match(/^(?:every )?(\d+) (minute|minutes|hour|hours|day|days|week|weeks|month|months)s?$/i)
|
|
118
97
|
if (intervalMatch) {
|
|
119
98
|
const interval = parseInt(intervalMatch[1])
|
|
120
99
|
const unit = intervalMatch[2].toLowerCase().replace(/s$/, '') // Remove trailing 's' if present
|
|
121
|
-
|
|
100
|
+
|
|
122
101
|
switch (unit) {
|
|
123
102
|
case 'minute':
|
|
124
103
|
return `*/${interval} * * * *`
|
|
@@ -115,7 +115,9 @@ function parseFileContents(content, filePath) {
|
|
|
115
115
|
*/
|
|
116
116
|
async function getValueFromFile(ctx, variableString, options) {
|
|
117
117
|
const opts = options || {}
|
|
118
|
-
|
|
118
|
+
// Pick syntax from the ref keyword, not the raw-text flag, so a file() ref can
|
|
119
|
+
// also be inlined as raw text (e.g. inside an Fn::Sub) without losing its match.
|
|
120
|
+
const syntax = /^\s*text\(/.test(variableString) ? ctx.textRefSyntax : ctx.fileRefSyntax
|
|
119
121
|
// console.log('From file', `"${variableString}"`)
|
|
120
122
|
let matchedFileString = variableString.match(syntax)[0]
|
|
121
123
|
// console.log('matchedFileString', matchedFileString)
|