configorama 0.9.17 → 0.10.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 +335 -90
- package/package.json +7 -4
- package/src/main.js +134 -36
- package/src/resolvers/valueFromFile.js +9 -1
- package/src/utils/parsing/preProcess.js +2 -6
- package/types/src/main.d.ts +4 -0
- package/types/src/main.d.ts.map +1 -1
- package/types/src/resolvers/valueFromFile.d.ts +2 -0
- package/types/src/resolvers/valueFromFile.d.ts.map +1 -1
- package/types/src/utils/parsing/preProcess.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ Configorama extends your configuration with a powerful variable system that reso
|
|
|
22
22
|
<details>
|
|
23
23
|
<summary>Click to expand</summary>
|
|
24
24
|
|
|
25
|
+
- [Key Features](#key-features)
|
|
25
26
|
- [Getting Started](#getting-started)
|
|
26
27
|
- [Installation](#installation)
|
|
27
28
|
- [Quick Start](#quick-start)
|
|
@@ -30,13 +31,19 @@ Configorama extends your configuration with a powerful variable system that reso
|
|
|
30
31
|
- [Resolution Flow](#resolution-flow)
|
|
31
32
|
- [Analyzing Without Resolving](#analyzing-without-resolving)
|
|
32
33
|
- [Getting Metadata](#getting-metadata)
|
|
34
|
+
- [Architecture](#architecture)
|
|
35
|
+
- [Performance](#performance)
|
|
33
36
|
- [Variable Sources](#variable-sources)
|
|
37
|
+
- [Summary Table](#summary-table)
|
|
34
38
|
- [Environment Variables](#environment-variables)
|
|
35
39
|
- [CLI Option Flags](#cli-option-flags)
|
|
36
40
|
- [Parameter Values](#parameter-values)
|
|
37
41
|
- [Self References](#self-references)
|
|
38
42
|
- [File References](#file-references)
|
|
39
43
|
- [Sync/Async File References](#syncasync-file-references)
|
|
44
|
+
- [Passing Arguments to Functions](#passing-arguments-to-functions)
|
|
45
|
+
- [ConfigContext](#configcontext)
|
|
46
|
+
- [Functions Without Arguments](#functions-without-arguments)
|
|
40
47
|
- [TypeScript File References](#typescript-file-references)
|
|
41
48
|
- [Terraform HCL Support](#terraform-hcl-support)
|
|
42
49
|
- [Git References](#git-references)
|
|
@@ -45,11 +52,18 @@ Configorama extends your configuration with a powerful variable system that reso
|
|
|
45
52
|
- [If Expressions](#if-expressions)
|
|
46
53
|
- [Filters (Experimental)](#filters-experimental)
|
|
47
54
|
- [Functions (Experimental)](#functions-experimental)
|
|
55
|
+
- [Bundled Plugins](#bundled-plugins)
|
|
56
|
+
- [CloudFormation](#cloudformation)
|
|
48
57
|
- [API Reference](#api-reference)
|
|
49
58
|
- [Async API](#async-api)
|
|
50
59
|
- [Sync API](#sync-api)
|
|
51
60
|
- [Analyze API](#analyze-api)
|
|
52
61
|
- [Format Utilities](#format-utilities)
|
|
62
|
+
- [Markdown Files](#markdown-files)
|
|
63
|
+
- [`buildVariableSyntax(prefix, suffix, excludePatterns?)`](#buildvariablesyntaxprefix-suffix-excludepatterns)
|
|
64
|
+
- [`Configorama` Class](#configorama-class)
|
|
65
|
+
- [`configorama/parse-file` Subpath](#configoramaparse-file-subpath)
|
|
66
|
+
- [TypeScript Types](#typescript-types)
|
|
53
67
|
- [Configuration Options](#configuration-options)
|
|
54
68
|
- [Custom Variable Syntax](#custom-variable-syntax)
|
|
55
69
|
- [allowUnknownVariableTypes](#allowunknownvariabletypes)
|
|
@@ -61,7 +75,7 @@ Configorama extends your configuration with a powerful variable system that reso
|
|
|
61
75
|
- [CLI Usage](#cli-usage)
|
|
62
76
|
- [Basic Commands](#basic-commands)
|
|
63
77
|
- [Command Options](#command-options)
|
|
64
|
-
- [Examples](#cli-examples)
|
|
78
|
+
- [CLI Examples](#cli-examples)
|
|
65
79
|
- [Testing](#testing)
|
|
66
80
|
- [Running Tests](#running-tests)
|
|
67
81
|
- [Test Structure](#test-structure)
|
|
@@ -79,9 +93,13 @@ Configorama extends your configuration with a powerful variable system that reso
|
|
|
79
93
|
- [Multi-Stage Resolution](#multi-stage-resolution)
|
|
80
94
|
- [Function Arguments and Context](#function-arguments-and-context)
|
|
81
95
|
- [Programmatic Usage](#programmatic-usage)
|
|
82
|
-
- [
|
|
96
|
+
- [Comparison vs Serverless Framework Variables](#comparison-vs-serverless-framework-variables)
|
|
83
97
|
- [Alternative Libraries](#alternative-libraries)
|
|
98
|
+
- [Changelog](#changelog)
|
|
84
99
|
- [Inspiration](#inspiration)
|
|
100
|
+
- [License](#license)
|
|
101
|
+
- [Contributing](#contributing)
|
|
102
|
+
- [Support](#support)
|
|
85
103
|
|
|
86
104
|
</details>
|
|
87
105
|
<!-- end-doc-gen -->
|
|
@@ -297,6 +315,59 @@ console.log(result.resolutionHistory) // Step-by-step resolution for eac
|
|
|
297
315
|
}
|
|
298
316
|
```
|
|
299
317
|
|
|
318
|
+
### Architecture
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
┌──────────────────┐ ┌─────────────────────┐ ┌──────────────────┐
|
|
322
|
+
│ Input │───▶│ Configorama core │───▶│ Output │
|
|
323
|
+
│ │ │ │ │ │
|
|
324
|
+
│ • Config file │ │ parser registry │ │ Resolved config │
|
|
325
|
+
│ • JS/TS object │ │ (yaml, json, toml, │ │ (+ metadata if │
|
|
326
|
+
│ • Inline opts │ │ ini, hcl, md, …) │ │ requested) │
|
|
327
|
+
└──────────────────┘ │ │ └──────────────────┘
|
|
328
|
+
│ preProcess() │
|
|
329
|
+
│ ↓ │
|
|
330
|
+
│ populateObject() │◀───┐ iterates until
|
|
331
|
+
│ ↓ │ │ no variables
|
|
332
|
+
│ resolve leaf vars │────┘ remain
|
|
333
|
+
│ ↓ │
|
|
334
|
+
│ apply filters/funcs│
|
|
335
|
+
│ ↓ │
|
|
336
|
+
│ return │
|
|
337
|
+
└──────────┬──────────┘
|
|
338
|
+
│
|
|
339
|
+
▼
|
|
340
|
+
┌──────────────────────────────────────┐
|
|
341
|
+
│ Variable Sources │
|
|
342
|
+
│ ┌────────┐ ┌────────┐ ┌────────────┐ │
|
|
343
|
+
│ │ env │ │ opt │ │ file/text │ │
|
|
344
|
+
│ │ self │ │ param │ │ git/cron │ │
|
|
345
|
+
│ │ eval │ │ if │ │ + plugins │ │
|
|
346
|
+
│ └────────┘ └────────┘ └────────────┘ │
|
|
347
|
+
│ │
|
|
348
|
+
│ Bundled plugins: │
|
|
349
|
+
│ • plugins/cloudformation │
|
|
350
|
+
│ │
|
|
351
|
+
│ Custom: variableSources: [{…}] │
|
|
352
|
+
└──────────────────────────────────────┘
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Resolution is a **fixed-point loop**: each pass resolves what it can, then `populateObject()` runs again until no `${…}` references remain. Built-in resolvers run first; custom resolvers from `variableSources` are tried in order.
|
|
356
|
+
|
|
357
|
+
### Performance
|
|
358
|
+
|
|
359
|
+
Configorama is fast and stays out of the way at runtime — a typical 21KB serverless-style config resolves in **~3ms** on warm Node 22.
|
|
360
|
+
|
|
361
|
+
- Honest before/after benchmarks against the published `0.9.17` baseline: [`PERF.md`](./PERF.md)
|
|
362
|
+
- Reproducible bench harness: [`scripts/bench.js`](./scripts/bench.js)
|
|
363
|
+
- Run against your own configs:
|
|
364
|
+
```bash
|
|
365
|
+
node scripts/bench.js # local
|
|
366
|
+
node scripts/bench.js /path/to/another/configorama # A/B
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
If your config is slow, please open an issue with the config (or a redacted reproduction) — we can profile and tighten the hot path.
|
|
370
|
+
|
|
300
371
|
---
|
|
301
372
|
|
|
302
373
|
## Variable Sources
|
|
@@ -953,7 +1024,7 @@ See [tests/hclTests](./tests/hclTests) for example Terraform files.
|
|
|
953
1024
|
Access repository information from the current working directory's git data.
|
|
954
1025
|
|
|
955
1026
|
<!-- doc-gen CODE src=tests/gitVariables/gitVariables.yml -->
|
|
956
|
-
```
|
|
1027
|
+
```yml
|
|
957
1028
|
########################
|
|
958
1029
|
# Git Variables
|
|
959
1030
|
########################
|
|
@@ -1290,6 +1361,48 @@ inRange: ${between(${value}, 50, 100)} # true
|
|
|
1290
1361
|
|
|
1291
1362
|
---
|
|
1292
1363
|
|
|
1364
|
+
## Bundled Plugins
|
|
1365
|
+
|
|
1366
|
+
Plugins ship in the repo under `plugins/` and are opt-in — install their peer dependencies, then wire them into `variableSources`. Plugins are *not* required dependencies of `configorama` itself, so consumers who don't need them aren't paying for them.
|
|
1367
|
+
|
|
1368
|
+
### CloudFormation
|
|
1369
|
+
|
|
1370
|
+
Resolves CloudFormation stack output values. Single-region, multi-region, and multi-account.
|
|
1371
|
+
|
|
1372
|
+
```yaml
|
|
1373
|
+
# Default region, default AWS credentials
|
|
1374
|
+
apiUrl: ${cf:my-stack.ApiUrl}
|
|
1375
|
+
|
|
1376
|
+
# Explicit region
|
|
1377
|
+
westUrl: ${cf(us-west-2):west-stack.ApiUrl}
|
|
1378
|
+
|
|
1379
|
+
# Cross-account: 'prod' matches PROD_AWS_ACCESS_KEY_ID env vars
|
|
1380
|
+
prodUrl: ${cf(prod:us-west-2):prod-stack.ApiUrl}
|
|
1381
|
+
```
|
|
1382
|
+
|
|
1383
|
+
```javascript
|
|
1384
|
+
const configorama = require('configorama')
|
|
1385
|
+
const createCloudFormationResolver = require('configorama/plugins/cloudformation')
|
|
1386
|
+
|
|
1387
|
+
const cfResolver = createCloudFormationResolver({
|
|
1388
|
+
defaultRegion: 'us-east-1',
|
|
1389
|
+
})
|
|
1390
|
+
|
|
1391
|
+
const config = await configorama('config.yml', {
|
|
1392
|
+
variableSources: [cfResolver]
|
|
1393
|
+
})
|
|
1394
|
+
```
|
|
1395
|
+
|
|
1396
|
+
Full docs: [`plugins/cloudformation/README.md`](./plugins/cloudformation/README.md) — covers the env-var-prefix alias convention, refcounted credential mutex for parallel-safe deploys, and the `skipResolution` mode for CI metadata extraction.
|
|
1397
|
+
|
|
1398
|
+
Peer dependency (install separately):
|
|
1399
|
+
|
|
1400
|
+
```bash
|
|
1401
|
+
npm install @aws-sdk/client-cloudformation @aws-sdk/credential-providers
|
|
1402
|
+
```
|
|
1403
|
+
|
|
1404
|
+
---
|
|
1405
|
+
|
|
1293
1406
|
## API Reference
|
|
1294
1407
|
|
|
1295
1408
|
### Async API
|
|
@@ -1477,8 +1590,8 @@ const { format } = require('configorama')
|
|
|
1477
1590
|
// Parse YAML
|
|
1478
1591
|
const yamlObj = format.yaml.parse('key: value')
|
|
1479
1592
|
|
|
1480
|
-
// Parse JSON5
|
|
1481
|
-
const jsonObj = format.
|
|
1593
|
+
// Parse JSON (handles JSON5/JSONC too — comments, trailing commas)
|
|
1594
|
+
const jsonObj = format.json.parse('{ key: "value", }')
|
|
1482
1595
|
|
|
1483
1596
|
// Parse TOML
|
|
1484
1597
|
const tomlObj = format.toml.parse('key = "value"')
|
|
@@ -1490,11 +1603,133 @@ const iniObj = format.ini.parse('[section]\nkey=value')
|
|
|
1490
1603
|
const hclObj = await format.hcl.parse('variable "example" { default = "value" }')
|
|
1491
1604
|
```
|
|
1492
1605
|
|
|
1493
|
-
**
|
|
1606
|
+
**Available parsers:** `format.json`, `format.yaml`, `format.toml`, `format.ini`, `format.hcl`, `format.markdown`.
|
|
1607
|
+
|
|
1608
|
+
Each has at minimum a `parse(content)` method; `dump(obj)` / `stringify(obj)` and cross-format converters (e.g. `format.yaml.toJson`, `format.toml.toYaml`) are available where the underlying format supports them. `format.markdown` is a frontmatter parser — see [Markdown Files](#markdown-files) below.
|
|
1609
|
+
|
|
1610
|
+
**Real use cases for `format`:**
|
|
1611
|
+
|
|
1612
|
+
- Parse a config file without resolving variables (just want the structure):
|
|
1613
|
+
```javascript
|
|
1614
|
+
const { format } = require('configorama')
|
|
1615
|
+
const fs = require('fs')
|
|
1616
|
+
const raw = format.yaml.parse(fs.readFileSync('config.yml', 'utf8'))
|
|
1617
|
+
// raw is the YAML structure with ${...} strings intact
|
|
1618
|
+
```
|
|
1619
|
+
- Use the same YAML/TOML/JSON5 parsers as configorama itself in your own tooling, so a file that loads in one place loads identically in the other.
|
|
1620
|
+
|
|
1621
|
+
---
|
|
1622
|
+
|
|
1623
|
+
### Markdown Files
|
|
1624
|
+
|
|
1625
|
+
Markdown configs (`.md`, `.mdx`, `.markdown`, `.mdown`, `.mkdn`, `.mkd`) are parsed as YAML/TOML/JSON frontmatter + body. The frontmatter becomes top-level keys; the body is exposed as `_content` on the resolved config (or `_body` if the frontmatter used that key explicitly).
|
|
1626
|
+
|
|
1627
|
+
```markdown
|
|
1628
|
+
---
|
|
1629
|
+
service: my-service
|
|
1630
|
+
stage: ${opt:stage, 'dev'}
|
|
1631
|
+
---
|
|
1632
|
+
|
|
1633
|
+
# Service Docs
|
|
1634
|
+
This is the body content.
|
|
1635
|
+
```
|
|
1636
|
+
|
|
1637
|
+
Resolves to:
|
|
1638
|
+
|
|
1639
|
+
```javascript
|
|
1640
|
+
{
|
|
1641
|
+
service: 'my-service',
|
|
1642
|
+
stage: 'dev',
|
|
1643
|
+
_content: '# Service Docs\nThis is the body content.'
|
|
1644
|
+
}
|
|
1645
|
+
```
|
|
1646
|
+
|
|
1647
|
+
The body is detached during variable resolution (so `${…}` inside the body text is left alone) and re-attached afterward — only frontmatter keys get variable expansion.
|
|
1648
|
+
|
|
1649
|
+
---
|
|
1650
|
+
|
|
1651
|
+
### `buildVariableSyntax(prefix, suffix, excludePatterns?)`
|
|
1652
|
+
|
|
1653
|
+
Helper for building a properly-escaped regex source string to pass to the `syntax` option. Handles regex-special characters in your delimiters without you having to escape them yourself.
|
|
1654
|
+
|
|
1655
|
+
```javascript
|
|
1656
|
+
const { buildVariableSyntax } = require('configorama')
|
|
1657
|
+
|
|
1658
|
+
// Use {{ ... }} instead of ${ ... }
|
|
1659
|
+
const syntax = buildVariableSyntax('{{', '}}')
|
|
1660
|
+
|
|
1661
|
+
const config = await configorama('config.yml', { syntax })
|
|
1662
|
+
```
|
|
1663
|
+
|
|
1664
|
+
**Parameters:**
|
|
1665
|
+
|
|
1666
|
+
| Param | Type | Default | Description |
|
|
1667
|
+
|---|---|---|---|
|
|
1668
|
+
| `prefix` | `string` | `'${'` | Opening delimiter |
|
|
1669
|
+
| `suffix` | `string` | `'}'` | Closing delimiter |
|
|
1670
|
+
| `excludePatterns` | `string[]` | `['AWS', 'stageVariables']` | Patterns to exclude via negative lookahead (so e.g. `${AWS::Region}` is left untouched by CloudFormation users) |
|
|
1671
|
+
|
|
1672
|
+
---
|
|
1673
|
+
|
|
1674
|
+
### `Configorama` Class
|
|
1675
|
+
|
|
1676
|
+
For advanced use cases (long-lived instances, hooking into init/resolve lifecycle, accessing partial state) the underlying class is exported.
|
|
1677
|
+
|
|
1678
|
+
```javascript
|
|
1679
|
+
const { Configorama } = require('configorama')
|
|
1680
|
+
|
|
1681
|
+
const instance = new Configorama('config.yml', { options: { stage: 'dev' } })
|
|
1682
|
+
await instance.init({ stage: 'dev' })
|
|
1683
|
+
const resolved = await instance.populateObject(instance.config)
|
|
1684
|
+
const metadata = instance.collectVariableMetadata()
|
|
1685
|
+
```
|
|
1686
|
+
|
|
1687
|
+
Most users should prefer the top-level `configorama()` / `.sync()` / `.analyze()` functions, which are thin wrappers around this class.
|
|
1688
|
+
|
|
1689
|
+
---
|
|
1690
|
+
|
|
1691
|
+
### `configorama/parse-file` Subpath
|
|
1692
|
+
|
|
1693
|
+
For tools that want to parse a config file (auto-detecting format from extension or contents) without going through variable resolution:
|
|
1494
1694
|
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1695
|
+
```javascript
|
|
1696
|
+
const { parseFile, parseFileContents } = require('configorama/parse-file')
|
|
1697
|
+
|
|
1698
|
+
// Read from disk
|
|
1699
|
+
const raw = parseFile('./config.yml')
|
|
1700
|
+
// returns the parsed object with ${...} strings intact
|
|
1701
|
+
|
|
1702
|
+
// Or parse already-loaded contents
|
|
1703
|
+
const fromString = parseFileContents({
|
|
1704
|
+
contents: 'service: my-app\nstage: ${opt:stage}',
|
|
1705
|
+
filePath: 'in-memory.yml'
|
|
1706
|
+
})
|
|
1707
|
+
```
|
|
1708
|
+
|
|
1709
|
+
Both are synchronous. Useful for build tools that inspect or rewrite configs before handing them to configorama.
|
|
1710
|
+
|
|
1711
|
+
---
|
|
1712
|
+
|
|
1713
|
+
### TypeScript Types
|
|
1714
|
+
|
|
1715
|
+
Type definitions are bundled (`index.d.ts`). TypeScript users get:
|
|
1716
|
+
|
|
1717
|
+
- Generic typing on the resolved config: `configorama<MyConfig>('config.yml')` returns `Promise<MyConfig>`
|
|
1718
|
+
- Full typing on `ConfigoramaSettings` and `ConfigoramaResult`
|
|
1719
|
+
- Editor autocomplete on all options shown in the [Complete Options Reference](#complete-options-reference)
|
|
1720
|
+
|
|
1721
|
+
```typescript
|
|
1722
|
+
import configorama, { ConfigoramaSettings } from 'configorama'
|
|
1723
|
+
|
|
1724
|
+
interface MyConfig {
|
|
1725
|
+
service: string
|
|
1726
|
+
stage: string
|
|
1727
|
+
database: { host: string; port: number }
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
const config = await configorama<MyConfig>('config.yml', { options: { stage: 'prod' } })
|
|
1731
|
+
// config.database.port is typed as number
|
|
1732
|
+
```
|
|
1498
1733
|
|
|
1499
1734
|
---
|
|
1500
1735
|
|
|
@@ -1583,9 +1818,11 @@ const config = await configorama(configFile, {
|
|
|
1583
1818
|
|
|
1584
1819
|
**Use cases:**
|
|
1585
1820
|
- Multi-stage resolution (local resolution, then cloud provider resolves remaining vars)
|
|
1586
|
-
- Serverless Framework integration (let framework resolve SSM
|
|
1821
|
+
- Serverless Framework integration (let the framework resolve SSM and other refs it owns)
|
|
1587
1822
|
- Gradual migration (allow unknown types during transition period)
|
|
1588
1823
|
|
|
1824
|
+
> CloudFormation refs (`${cf:…}`, `${cf(region):…}`, `${cf(account:region):…}`) are now resolved natively by the bundled [`plugins/cloudformation/`](./plugins/cloudformation/README.md) plugin — no external resolver required.
|
|
1825
|
+
|
|
1589
1826
|
---
|
|
1590
1827
|
|
|
1591
1828
|
### allowUnresolvedVariables
|
|
@@ -1654,10 +1891,17 @@ const config = await configorama(configFile, {
|
|
|
1654
1891
|
| `allowUnknownVariableTypes` | `boolean \| string[]` | `false` | Allow unknown variable types to pass through |
|
|
1655
1892
|
| `allowUnresolvedVariables` | `boolean \| string[]` | `false` | Allow known types that can't resolve to pass through |
|
|
1656
1893
|
| `allowUndefinedValues` | `boolean` | `false` | Allow undefined as a valid end result |
|
|
1657
|
-
| `returnMetadata` | `boolean` | `false` | Return
|
|
1894
|
+
| `returnMetadata` | `boolean` | `false` | Return `{ config, metadata }` instead of just the resolved config |
|
|
1895
|
+
| `returnPreResolvedVariableDetails` | `boolean` | `false` | Return metadata about variables *without* resolving them (used by `analyze()`) |
|
|
1896
|
+
| `useDotEnvFiles` | `boolean` | `false` | Auto-load `.env`, `.env.{stage}`, etc. into `process.env` before resolution (via [env-stage-loader](https://www.npmjs.com/package/env-stage-loader)) |
|
|
1897
|
+
| `dotEnvSilent` | `boolean` | `true` (unless `--verbose`) | Suppress the env-stage-loader log lines when `useDotEnvFiles` is on |
|
|
1898
|
+
| `dotEnvDebug` | `boolean` | `false` | Enable env-stage-loader debug output |
|
|
1899
|
+
| `dynamicArgs` | `object \| Function` | `undefined` | Values passed into `.js`/`.ts` config files when they're imported as the root config |
|
|
1658
1900
|
| `mergeKeys` | `string[]` | `[]` | Keys to merge in arrays of objects |
|
|
1659
1901
|
| `filePathOverrides` | `Record<string, string>` | `{}` | Map of file paths to override (for testing/mocking) |
|
|
1660
1902
|
|
|
1903
|
+
> The config file itself can also set `useDotenv: true` (or `useDotEnv: true`) at the top level to trigger dotenv loading — useful if you want the behavior intrinsic to the config rather than the JS caller.
|
|
1904
|
+
|
|
1661
1905
|
**Legacy options (deprecated):**
|
|
1662
1906
|
|
|
1663
1907
|
| Legacy Option | New Equivalent |
|
|
@@ -1776,8 +2020,8 @@ interface VariableSource {
|
|
|
1776
2020
|
**Advanced example with AWS SSM:**
|
|
1777
2021
|
|
|
1778
2022
|
```javascript
|
|
1779
|
-
const
|
|
1780
|
-
const ssm = new
|
|
2023
|
+
const { SSMClient, GetParameterCommand } = require('@aws-sdk/client-ssm')
|
|
2024
|
+
const ssm = new SSMClient({})
|
|
1781
2025
|
|
|
1782
2026
|
const config = await configorama('config.yml', {
|
|
1783
2027
|
variableSources: [{
|
|
@@ -1790,10 +2034,10 @@ const config = await configorama('config.yml', {
|
|
|
1790
2034
|
const paramPath = variable.replace(/^ssm:/, '')
|
|
1791
2035
|
|
|
1792
2036
|
try {
|
|
1793
|
-
const result = await ssm.
|
|
2037
|
+
const result = await ssm.send(new GetParameterCommand({
|
|
1794
2038
|
Name: paramPath,
|
|
1795
2039
|
WithDecryption: true
|
|
1796
|
-
})
|
|
2040
|
+
}))
|
|
1797
2041
|
|
|
1798
2042
|
return result.Parameter.Value
|
|
1799
2043
|
} catch (err) {
|
|
@@ -1807,6 +2051,8 @@ const config = await configorama('config.yml', {
|
|
|
1807
2051
|
})
|
|
1808
2052
|
```
|
|
1809
2053
|
|
|
2054
|
+
> **See also:** the bundled [`plugins/cloudformation/`](./plugins/cloudformation/README.md) plugin is a production-grade example of a `source: 'remote'` resolver — it handles multi-region, multi-account credential swapping, and per-instance client/output caching.
|
|
2055
|
+
|
|
1810
2056
|
```yaml
|
|
1811
2057
|
# config.yml
|
|
1812
2058
|
database:
|
|
@@ -1959,6 +2205,22 @@ configorama config.yml --verify
|
|
|
1959
2205
|
# - DB_PASSWORD
|
|
1960
2206
|
```
|
|
1961
2207
|
|
|
2208
|
+
**Allow unknown variable types to pass through unresolved:**
|
|
2209
|
+
|
|
2210
|
+
```bash
|
|
2211
|
+
# ${ssm:...} and ${custom:...} stay as literal ${ssm:...} strings
|
|
2212
|
+
# (typical for multi-stage pipelines where another tool resolves them)
|
|
2213
|
+
configorama config.yml --allow-unknown
|
|
2214
|
+
```
|
|
2215
|
+
|
|
2216
|
+
**Allow undefined values in the final output:**
|
|
2217
|
+
|
|
2218
|
+
```bash
|
|
2219
|
+
# Don't error on values that resolved to undefined — emit them as nulls
|
|
2220
|
+
# (useful for downstream tooling that does its own validation)
|
|
2221
|
+
configorama config.yml --allow-undefined
|
|
2222
|
+
```
|
|
2223
|
+
|
|
1962
2224
|
---
|
|
1963
2225
|
|
|
1964
2226
|
## Testing
|
|
@@ -2087,7 +2349,7 @@ service: my-service
|
|
|
2087
2349
|
|
|
2088
2350
|
provider:
|
|
2089
2351
|
name: aws
|
|
2090
|
-
runtime:
|
|
2352
|
+
runtime: nodejs22.x
|
|
2091
2353
|
stage: ${opt:stage, 'dev'}
|
|
2092
2354
|
region: ${opt:region, 'us-east-1'}
|
|
2093
2355
|
|
|
@@ -2134,7 +2396,7 @@ serverless deploy --stage prod --region us-west-2
|
|
|
2134
2396
|
**Dockerfile:**
|
|
2135
2397
|
|
|
2136
2398
|
```dockerfile
|
|
2137
|
-
FROM node:
|
|
2399
|
+
FROM node:22-alpine
|
|
2138
2400
|
|
|
2139
2401
|
WORKDIR /app
|
|
2140
2402
|
|
|
@@ -2212,12 +2474,12 @@ jobs:
|
|
|
2212
2474
|
runs-on: ubuntu-latest
|
|
2213
2475
|
|
|
2214
2476
|
steps:
|
|
2215
|
-
- uses: actions/checkout@
|
|
2477
|
+
- uses: actions/checkout@v4
|
|
2216
2478
|
|
|
2217
2479
|
- name: Setup Node.js
|
|
2218
|
-
uses: actions/setup-node@
|
|
2480
|
+
uses: actions/setup-node@v4
|
|
2219
2481
|
with:
|
|
2220
|
-
node-version: '
|
|
2482
|
+
node-version: '22'
|
|
2221
2483
|
|
|
2222
2484
|
- name: Install dependencies
|
|
2223
2485
|
run: npm ci
|
|
@@ -2251,7 +2513,7 @@ stages:
|
|
|
2251
2513
|
|
|
2252
2514
|
verify-config:
|
|
2253
2515
|
stage: verify
|
|
2254
|
-
image: node:
|
|
2516
|
+
image: node:22
|
|
2255
2517
|
script:
|
|
2256
2518
|
- npm ci
|
|
2257
2519
|
- npx configorama config.yml --verify --stage $CI_ENVIRONMENT_NAME
|
|
@@ -2260,14 +2522,14 @@ verify-config:
|
|
|
2260
2522
|
|
|
2261
2523
|
test:
|
|
2262
2524
|
stage: test
|
|
2263
|
-
image: node:
|
|
2525
|
+
image: node:22
|
|
2264
2526
|
script:
|
|
2265
2527
|
- npm ci
|
|
2266
2528
|
- npm test
|
|
2267
2529
|
|
|
2268
2530
|
deploy-production:
|
|
2269
2531
|
stage: deploy
|
|
2270
|
-
image: node:
|
|
2532
|
+
image: node:22
|
|
2271
2533
|
script:
|
|
2272
2534
|
- npm ci
|
|
2273
2535
|
- npm run deploy
|
|
@@ -2520,14 +2782,14 @@ secrets: ${file(./fetch-secrets.js)}
|
|
|
2520
2782
|
|
|
2521
2783
|
```javascript
|
|
2522
2784
|
// fetch-secrets.js
|
|
2523
|
-
const
|
|
2524
|
-
const ssm = new
|
|
2785
|
+
const { SSMClient, GetParameterCommand } = require('@aws-sdk/client-ssm')
|
|
2786
|
+
const ssm = new SSMClient({})
|
|
2525
2787
|
|
|
2526
2788
|
module.exports = async () => {
|
|
2527
|
-
const result = await ssm.
|
|
2789
|
+
const result = await ssm.send(new GetParameterCommand({
|
|
2528
2790
|
Name: '/myapp/api-key',
|
|
2529
2791
|
WithDecryption: true
|
|
2530
|
-
})
|
|
2792
|
+
}))
|
|
2531
2793
|
|
|
2532
2794
|
return result.Parameter.Value
|
|
2533
2795
|
}
|
|
@@ -2594,13 +2856,15 @@ const partiallyResolved = await configorama('config.yml', {
|
|
|
2594
2856
|
allowUnknownVariableTypes: ['ssm', 'cf']
|
|
2595
2857
|
})
|
|
2596
2858
|
|
|
2597
|
-
// Stage 2: External system resolves SSM and
|
|
2598
|
-
// (e.g., Serverless Dashboard,
|
|
2859
|
+
// Stage 2: External system resolves SSM and any other refs
|
|
2860
|
+
// (e.g., Serverless Dashboard, secrets manager, etc.)
|
|
2599
2861
|
const fullyResolved = await externalResolver(partiallyResolved)
|
|
2600
2862
|
```
|
|
2601
2863
|
|
|
2602
2864
|
**Use case:** Serverless Framework + Serverless Dashboard workflow.
|
|
2603
2865
|
|
|
2866
|
+
> For CloudFormation refs specifically, the bundled [CF plugin](./plugins/cloudformation/README.md) resolves them natively in Stage 1 — you don't need a second pass.
|
|
2867
|
+
|
|
2604
2868
|
---
|
|
2605
2869
|
|
|
2606
2870
|
### Function Arguments and Context
|
|
@@ -2660,14 +2924,14 @@ const config = await configorama('config.yml', {
|
|
|
2660
2924
|
description: 'AWS Systems Manager Parameter Store',
|
|
2661
2925
|
match: /^ssm:/,
|
|
2662
2926
|
resolver: async (variable) => {
|
|
2663
|
-
const
|
|
2664
|
-
const ssm = new
|
|
2927
|
+
const { SSMClient, GetParameterCommand } = require('@aws-sdk/client-ssm')
|
|
2928
|
+
const ssm = new SSMClient({})
|
|
2665
2929
|
|
|
2666
2930
|
const paramName = variable.replace(/^ssm:/, '')
|
|
2667
|
-
const result = await ssm.
|
|
2931
|
+
const result = await ssm.send(new GetParameterCommand({
|
|
2668
2932
|
Name: paramName,
|
|
2669
2933
|
WithDecryption: true
|
|
2670
|
-
})
|
|
2934
|
+
}))
|
|
2671
2935
|
|
|
2672
2936
|
return result.Parameter.Value
|
|
2673
2937
|
}
|
|
@@ -2734,68 +2998,47 @@ timeout: ${selectByEnv(30, 5, ${environment})}
|
|
|
2734
2998
|
|
|
2735
2999
|
---
|
|
2736
3000
|
|
|
2737
|
-
##
|
|
2738
|
-
|
|
2739
|
-
|
|
3001
|
+
## Comparison vs Serverless Framework Variables
|
|
3002
|
+
|
|
3003
|
+
Configorama was forked from the Serverless Framework variable system and extended. Here's what's different:
|
|
3004
|
+
|
|
3005
|
+
| Capability | Serverless | Configorama |
|
|
3006
|
+
|---|---|---|
|
|
3007
|
+
| Framework-agnostic — use outside Serverless | ❌ Serverless-only | ✅ Any tool, any framework |
|
|
3008
|
+
| Pluggable variable sources | ❌ Hardcoded | ✅ Custom resolvers, custom syntax |
|
|
3009
|
+
| `self:` prefix optional in self-refs | ❌ Required | ✅ `${foo.bar}` works without `self:` |
|
|
3010
|
+
| Numbers as defaults | ❌ Coerced to string | ✅ `${env:TIMEOUT, 30}` stays numeric |
|
|
3011
|
+
| Format support | YAML/JSON | YAML, JSON/JSON5/JSONC, TOML, INI, HCL, Markdown, TS, JS |
|
|
3012
|
+
| Filters (pipe transforms) | ❌ | ✅ `${value \| toUpperCase}` |
|
|
3013
|
+
| Built-in functions | ❌ | ✅ `merge()`, custom user functions |
|
|
3014
|
+
| Conditional expressions | ❌ | ✅ `${if(cond ? a : b)}` |
|
|
3015
|
+
| Eval/math expressions | ❌ | ✅ `${eval(2 + 2)}` |
|
|
3016
|
+
| TypeScript file refs | ❌ | ✅ `${file(./config.ts)}` |
|
|
3017
|
+
| Git data refs | ❌ | ✅ `${git:branch}`, `${git:sha1}`, etc. |
|
|
3018
|
+
| Cron expression refs | ❌ | ✅ `${cron(every monday at 9am)}` |
|
|
3019
|
+
| Metadata extraction (analyze without resolving) | ❌ | ✅ `configorama.analyze(...)` |
|
|
3020
|
+
| Multi-account CloudFormation refs | ❌ | ✅ via bundled CF plugin |
|
|
3021
|
+
| Circular dependency detection | ❌ Hangs | ✅ Helpful error |
|
|
2740
3022
|
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
2. **Pluggable** - Add custom variable syntax and sources easily
|
|
2744
|
-
|
|
2745
|
-
3. **Filters** - Transform values before resolution:
|
|
2746
|
-
```yaml
|
|
2747
|
-
key: ${opt:stage | toUpperCase}
|
|
2748
|
-
```
|
|
2749
|
-
|
|
2750
|
-
4. **Cleaner self-references** - No need for `self:` prefix:
|
|
2751
|
-
```yaml
|
|
2752
|
-
keyOne:
|
|
2753
|
-
subKey: hi
|
|
2754
|
-
|
|
2755
|
-
# Before
|
|
2756
|
-
key: ${self:keyOne.subKey}
|
|
2757
|
-
|
|
2758
|
-
# Now (both work)
|
|
2759
|
-
key: ${keyOne.subKey}
|
|
2760
|
-
key: ${self:keyOne.subKey}
|
|
2761
|
-
```
|
|
2762
|
-
|
|
2763
|
-
5. **Numbers as defaults** - Numeric defaults fully supported:
|
|
2764
|
-
```yaml
|
|
2765
|
-
timeout: ${env:TIMEOUT, 30}
|
|
2766
|
-
port: ${opt:port, 3000}
|
|
2767
|
-
```
|
|
2768
|
-
|
|
2769
|
-
6. **Multiple format support** - TOML, YML, JSON, INI, HCL, etc.
|
|
2770
|
-
|
|
2771
|
-
7. **Built-in functions** - Combine and transform values:
|
|
2772
|
-
```yaml
|
|
2773
|
-
merged: ${merge(${obj1}, ${obj2})}
|
|
2774
|
-
```
|
|
3023
|
+
---
|
|
2775
3024
|
|
|
2776
|
-
|
|
2777
|
-
```yaml
|
|
2778
|
-
config: ${file(./config.ts)}
|
|
2779
|
-
```
|
|
3025
|
+
## Alternative Libraries
|
|
2780
3026
|
|
|
2781
|
-
|
|
2782
|
-
```yaml
|
|
2783
|
-
memory: ${if(${stage} === 'prod' ? 1024 : 512)}
|
|
2784
|
-
```
|
|
3027
|
+
How configorama compares to other variable-substitution libraries:
|
|
2785
3028
|
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
3029
|
+
| Library | Formats | Variable sources | Custom resolvers | Async | TypeScript |
|
|
3030
|
+
|---|---|---|---|---|---|
|
|
3031
|
+
| **configorama** | YAML, JSON5, TOML, INI, HCL, MD, TS, JS | env, opt, file, self, git, cron, eval, if, custom | ✅ | ✅ | ✅ |
|
|
3032
|
+
| [sls-yaml](https://github.com/01alchemist/sls-yaml) | YAML | env, opt, file, self | ❌ | ❌ | ❌ |
|
|
3033
|
+
| [yaml-boost](https://github.com/blackflux/yaml-boost) | YAML | env, file, self, function | partial | ❌ | ❌ |
|
|
3034
|
+
| [serverless-merge-config](https://github.com/CruGlobal/serverless-merge-config) | YAML | merge-focused | ❌ | ❌ | ❌ |
|
|
3035
|
+
| [serverless-terraform-variables](https://www.npmjs.com/package/serverless-terraform-variables) | YAML + .tfvars | terraform-focused | ❌ | ❌ | ❌ |
|
|
2790
3036
|
|
|
2791
3037
|
---
|
|
2792
3038
|
|
|
2793
|
-
##
|
|
3039
|
+
## Changelog
|
|
2794
3040
|
|
|
2795
|
-
|
|
2796
|
-
- [yaml-boost](https://github.com/blackflux/yaml-boost) - YAML preprocessing
|
|
2797
|
-
- [serverless-merge-config](https://github.com/CruGlobal/serverless-merge-config) - Merge Serverless configs
|
|
2798
|
-
- [serverless-terraform-variables](https://www.npmjs.com/package/serverless-terraform-variables) - Terraform variable support
|
|
3041
|
+
Version history lives in [CHANGELOG.md](./CHANGELOG.md). It covers everything from 0.9.9 onward; older releases are in `git log`.
|
|
2799
3042
|
|
|
2800
3043
|
---
|
|
2801
3044
|
|
|
@@ -2803,10 +3046,13 @@ How is this different than the Serverless Framework variable system?
|
|
|
2803
3046
|
|
|
2804
3047
|
This is forked from the [Serverless Framework](https://github.com/serverless/serverless/) variable system.
|
|
2805
3048
|
|
|
2806
|
-
|
|
3049
|
+
<details>
|
|
3050
|
+
<summary><strong>Mad props to the original contributors</strong></summary>
|
|
2807
3051
|
|
|
2808
3052
|
[erikerikson](https://github.com/erikerikson), [eahefnawy](https://github.com/eahefnawy), [HyperBrain](https://github.com/HyperBrain), [ac360](https://github.com/ac360), [gcphost](https://github.com/gcphost), [pmuens](https://github.com/pmuens), [horike37](https://github.com/horike37), [lorengordon](https://github.com/lorengordon), [AndrewFarley](https://github.com/AndrewFarley), [tobyhede](https://github.com/tobyhede), [johncmckim](https://github.com/johncmckim), [mangas](https://github.com/mangas), [e-e-e](https://github.com/e-e-e), [BasileTrujillo](https://github.com/BasileTrujillo), [miltador](https://github.com/miltador), [sammarks](https://github.com/sammarks), [RafalWilinski](https://github.com/RafalWilinski), [indieisaconcept](https://github.com/indieisaconcept), [svdgraaf](https://github.com/svdgraaf), [infiniteluke](https://github.com/infiniteluke), [j0k3r](https://github.com/j0k3r), [craigw](https://github.com/craigw), [bsdkurt](https://github.com/bsdkurt), [aoskotsky-amplify](https://github.com/aoskotsky-amplify), and all the other folks who contributed to the variable system.
|
|
2809
3053
|
|
|
3054
|
+
</details>
|
|
3055
|
+
|
|
2810
3056
|
**Additionally these tools were very helpful:**
|
|
2811
3057
|
|
|
2812
3058
|
- [yaml-boost](https://github.com/blackflux/yaml-boost)
|
|
@@ -2821,11 +3067,10 @@ MIT © [David Wells](https://davidwells.io)
|
|
|
2821
3067
|
|
|
2822
3068
|
## Contributing
|
|
2823
3069
|
|
|
2824
|
-
|
|
3070
|
+
Bug reports and reproductions are very welcome — please open an [issue](https://github.com/DavidWells/configorama/issues) with a minimal failing config. PRs are reviewed case-by-case; small targeted fixes with a test case are most likely to land quickly.
|
|
2825
3071
|
|
|
2826
3072
|
## Support
|
|
2827
3073
|
|
|
2828
3074
|
- 🐛 [Report bugs](https://github.com/DavidWells/configorama/issues)
|
|
2829
3075
|
- 💡 [Request features](https://github.com/DavidWells/configorama/issues)
|
|
2830
3076
|
- 📖 [Read the docs](https://github.com/DavidWells/configorama#readme)
|
|
2831
|
-
- 💬 [Join discussions](https://github.com/DavidWells/configorama/discussions)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "configorama",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Variable support for configuration files",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -71,16 +71,19 @@
|
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@cdktf/hcl2json": "^0.21.0",
|
|
73
73
|
"@types/node": "^24.10.1",
|
|
74
|
-
"markdown-magic": "^
|
|
74
|
+
"markdown-magic": "^4.8.0",
|
|
75
75
|
"tsx": "^4.7.0",
|
|
76
76
|
"typescript": "^5.8.3",
|
|
77
77
|
"uvu": "^0.5.6",
|
|
78
78
|
"watchlist": "^0.3.1"
|
|
79
79
|
},
|
|
80
|
+
"overrides": {
|
|
81
|
+
"lodash": "^4.18.1"
|
|
82
|
+
},
|
|
80
83
|
"peerDependencies": {
|
|
81
84
|
"@cdktf/hcl2json": ">=0.20.0",
|
|
82
|
-
"
|
|
83
|
-
"
|
|
85
|
+
"ts-node": ">=10.0.0",
|
|
86
|
+
"tsx": ">=4.0.0"
|
|
84
87
|
},
|
|
85
88
|
"peerDependenciesMeta": {
|
|
86
89
|
"@cdktf/hcl2json": {
|
package/src/main.js
CHANGED
|
@@ -8,7 +8,37 @@ console.log = () => {}
|
|
|
8
8
|
/** */
|
|
9
9
|
/* External dependencies */
|
|
10
10
|
const findUp = require('find-up')
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Pre-order DFS walk that mimics the subset of `traverse(obj).forEach(fn)`
|
|
13
|
+
* we actually use: only `this.path` and `this.update(v)`. ~2x faster than
|
|
14
|
+
* the `traverse` package because it avoids the per-node State object alloc.
|
|
15
|
+
*
|
|
16
|
+
* @param {*} root
|
|
17
|
+
* @param {Function} callback - called with `this = {path, update}` per node
|
|
18
|
+
*/
|
|
19
|
+
function walkAndUpdate(root, callback) {
|
|
20
|
+
function visit(value, path, parent, key) {
|
|
21
|
+
const ctx = {
|
|
22
|
+
path,
|
|
23
|
+
update(newValue) { if (parent !== null) parent[key] = newValue }
|
|
24
|
+
}
|
|
25
|
+
callback.call(ctx, value)
|
|
26
|
+
// Re-read in case callback mutated the slot
|
|
27
|
+
const current = parent === null ? root : parent[key]
|
|
28
|
+
if (current !== null && typeof current === 'object') {
|
|
29
|
+
// Use Object.keys for both arrays and objects so sparse-array holes are
|
|
30
|
+
// skipped, matching the `traverse` package's behavior.
|
|
31
|
+
const keys = Object.keys(current)
|
|
32
|
+
const isArr = Array.isArray(current)
|
|
33
|
+
for (let i = 0; i < keys.length; i++) {
|
|
34
|
+
const k = keys[i]
|
|
35
|
+
const idx = isArr ? Number(k) : k
|
|
36
|
+
visit(current[k], path.concat(idx), current, k)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
visit(root, [], null, null)
|
|
41
|
+
}
|
|
12
42
|
const dotProp = require('dot-prop')
|
|
13
43
|
/* Utils - root */
|
|
14
44
|
const {
|
|
@@ -158,6 +188,22 @@ class Configorama {
|
|
|
158
188
|
this.filterCache = {}
|
|
159
189
|
// Cache for originalValue lookups (perf: avoid repeated dotProp.get)
|
|
160
190
|
this._originalValueCache = new Map()
|
|
191
|
+
// Paths whose current value is a literal with no variables — skip rebuilding
|
|
192
|
+
// their leaf object on every subsequent populateObjectImpl iteration.
|
|
193
|
+
this._resolvedPaths = new Set()
|
|
194
|
+
// Cache raw file contents per absolute path so repeated ${file:...} refs
|
|
195
|
+
// to the same file (e.g., merged twice into different keys) don't reread.
|
|
196
|
+
this._fileContentCache = new Map()
|
|
197
|
+
|
|
198
|
+
// rawOriginalConfig (a pre-preProcess snapshot) is only consumed by metadata
|
|
199
|
+
// display paths. Skipping the cloneDeep when none of those paths are active
|
|
200
|
+
// saves ~10-15ms per init.
|
|
201
|
+
const showFound = this.settings.dynamicArgs && (this.settings.dynamicArgs.list || this.settings.dynamicArgs.info)
|
|
202
|
+
this._needsRawClone = !!(
|
|
203
|
+
this.settings.returnMetadata ||
|
|
204
|
+
this.settings.returnPreResolvedVariableDetails ||
|
|
205
|
+
VERBOSE || SETUP_MODE || showFound
|
|
206
|
+
)
|
|
161
207
|
|
|
162
208
|
this.foundVariables = []
|
|
163
209
|
this.fileRefsFound = []
|
|
@@ -188,6 +234,10 @@ class Configorama {
|
|
|
188
234
|
}
|
|
189
235
|
const variableSyntax = varRegex
|
|
190
236
|
this.variableSyntax = variableSyntax
|
|
237
|
+
// Non-global twin for cheap boolean checks: `.test()` on a global regex
|
|
238
|
+
// would advance lastIndex between calls, and `.match()` on a global regex
|
|
239
|
+
// allocates an array of every match. Use this whenever we only need truthy.
|
|
240
|
+
this.variableSyntaxTest = new RegExp(variableSyntax.source, variableSyntax.flags.replace('g', ''))
|
|
191
241
|
|
|
192
242
|
// Extract variable prefix/suffix from syntax regex for reconstructing variables
|
|
193
243
|
const syntaxWrapper = extractVariableWrapper(variableSyntax.source)
|
|
@@ -200,8 +250,10 @@ class Configorama {
|
|
|
200
250
|
|
|
201
251
|
// Set initial config object to populate
|
|
202
252
|
if (typeof fileOrObject === 'object') {
|
|
203
|
-
// Store truly raw config before any preprocessing
|
|
204
|
-
this.
|
|
253
|
+
// Store truly raw config before any preprocessing (only when needed)
|
|
254
|
+
if (this._needsRawClone) {
|
|
255
|
+
this.rawOriginalConfig = cloneDeep(fileOrObject)
|
|
256
|
+
}
|
|
205
257
|
// Preprocess: convert bare refs in if(), escape help() args
|
|
206
258
|
// Skip fallback fixing for object configs (they handle bare refs differently)
|
|
207
259
|
const processed = preProcess(fileOrObject, this.variableSyntax, this.variableTypes, { skipFallbackFix: true })
|
|
@@ -677,8 +729,10 @@ class Configorama {
|
|
|
677
729
|
/*
|
|
678
730
|
console.log('before preprocess', configObject)
|
|
679
731
|
/** */
|
|
680
|
-
// Store truly raw config before any preprocessing (
|
|
681
|
-
this.
|
|
732
|
+
// Store truly raw config before any preprocessing (only when needed)
|
|
733
|
+
if (this._needsRawClone) {
|
|
734
|
+
this.rawOriginalConfig = cloneDeep(configObject)
|
|
735
|
+
}
|
|
682
736
|
|
|
683
737
|
/* Preprocess step here - escapes ${} in help() args, fixes malformed fallbacks */
|
|
684
738
|
configObject = preProcess(configObject, this.variableSyntax, this.variableTypes)
|
|
@@ -816,7 +870,7 @@ class Configorama {
|
|
|
816
870
|
const originalConfig = this.originalConfig
|
|
817
871
|
|
|
818
872
|
/* If no variables found just return early */
|
|
819
|
-
if (this.originalString && !this.
|
|
873
|
+
if (this.originalString && !this.variableSyntaxTest.test(this.originalString)) {
|
|
820
874
|
if (this._markdownContent !== undefined) {
|
|
821
875
|
this.originalConfig[this._markdownContentKey] = this._markdownContent
|
|
822
876
|
}
|
|
@@ -829,7 +883,7 @@ class Configorama {
|
|
|
829
883
|
/* has hardcoded stage */
|
|
830
884
|
if (
|
|
831
885
|
this.originalConfig && this.originalConfig.provider &&
|
|
832
|
-
this.originalConfig.provider.stage && !this.originalConfig.provider.stage
|
|
886
|
+
this.originalConfig.provider.stage && !this.variableSyntaxTest.test(this.originalConfig.provider.stage)
|
|
833
887
|
) {
|
|
834
888
|
providerStage = this.originalConfig.provider.stage
|
|
835
889
|
}
|
|
@@ -859,7 +913,7 @@ class Configorama {
|
|
|
859
913
|
// console.log('leaves two', leaves)
|
|
860
914
|
// Traverse resolved object and run functions
|
|
861
915
|
// console.log('this.config', this.config)
|
|
862
|
-
|
|
916
|
+
walkAndUpdate(this.config, function (rawValue) {
|
|
863
917
|
/* Pass through unknown variables */
|
|
864
918
|
if (!configoramaOpts.allowUndefinedValues && typeof rawValue === 'undefined') {
|
|
865
919
|
const configValuePath = this.path.join('.')
|
|
@@ -1096,12 +1150,16 @@ class Configorama {
|
|
|
1096
1150
|
mapValues(current, addContext)
|
|
1097
1151
|
}
|
|
1098
1152
|
} else {
|
|
1153
|
+
// Compute path once, then skip work for paths already known to be fully resolved.
|
|
1154
|
+
const thePath = context.length > 1 ? context.join('.') : context[0]
|
|
1155
|
+
if (this._resolvedPaths.has(thePath)) {
|
|
1156
|
+
return results
|
|
1157
|
+
}
|
|
1099
1158
|
// TODO Add values to leaves here
|
|
1100
1159
|
const leaf = {
|
|
1101
1160
|
path: context,
|
|
1102
1161
|
value: current,
|
|
1103
1162
|
}
|
|
1104
|
-
const thePath = leaf.path.length > 1 ? leaf.path.join('.') : leaf.path[0]
|
|
1105
1163
|
// console.log('thePath', thePath)
|
|
1106
1164
|
// console.log('this.originalConfig', this.originalConfig)
|
|
1107
1165
|
|
|
@@ -1113,21 +1171,39 @@ class Configorama {
|
|
|
1113
1171
|
originalValue = cached.value
|
|
1114
1172
|
originalValuePath = cached.originalValuePath
|
|
1115
1173
|
} else {
|
|
1116
|
-
|
|
1117
|
-
//
|
|
1174
|
+
// Walk down originalConfig once using the path array directly. Avoids
|
|
1175
|
+
// dotProp.get's path-segmenting work and the previous O(depth²) loop
|
|
1176
|
+
// that re-joined and re-walked parent paths.
|
|
1177
|
+
const ancestorValues = []
|
|
1178
|
+
let node = this.originalConfig
|
|
1179
|
+
let fullPathReached = true
|
|
1180
|
+
for (let i = 0; i < context.length; i++) {
|
|
1181
|
+
if (node == null || typeof node !== 'object') {
|
|
1182
|
+
fullPathReached = false
|
|
1183
|
+
break
|
|
1184
|
+
}
|
|
1185
|
+
node = node[context[i]]
|
|
1186
|
+
ancestorValues.push(node)
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
const lastIdx = ancestorValues.length - 1
|
|
1190
|
+
originalValue = fullPathReached ? ancestorValues[lastIdx] : undefined
|
|
1191
|
+
|
|
1118
1192
|
if (!originalValue) {
|
|
1119
|
-
//
|
|
1120
|
-
//
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1193
|
+
// Same semantics as the previous "recurse up" loop: walk from the
|
|
1194
|
+
// deepest non-full ancestor toward the root, take the first truthy
|
|
1195
|
+
// ancestor as originalValue, and update originalValuePath each time
|
|
1196
|
+
// we see a defined ancestor along the way.
|
|
1197
|
+
const startIdx = fullPathReached ? lastIdx - 1 : lastIdx
|
|
1198
|
+
for (let i = startIdx; i >= 0; i--) {
|
|
1199
|
+
const ancestor = ancestorValues[i]
|
|
1200
|
+
if (typeof ancestor !== 'undefined') {
|
|
1201
|
+
originalValuePath = i > 0 ? context.slice(0, i + 1).join('.') : context[0]
|
|
1202
|
+
originalValue = ancestor
|
|
1203
|
+
if (ancestor) break
|
|
1127
1204
|
}
|
|
1128
1205
|
}
|
|
1129
1206
|
}
|
|
1130
|
-
// Cache the result
|
|
1131
1207
|
this._originalValueCache.set(thePath, { value: originalValue, originalValuePath })
|
|
1132
1208
|
}
|
|
1133
1209
|
if (originalValuePath) {
|
|
@@ -1150,6 +1226,9 @@ class Configorama {
|
|
|
1150
1226
|
leaf.isFileRef = true
|
|
1151
1227
|
}
|
|
1152
1228
|
}
|
|
1229
|
+
// Pre-compute hasVar so populateVariables doesn't have to re-test every leaf
|
|
1230
|
+
// every iteration. Non-string values can never contain a variable.
|
|
1231
|
+
leaf.hasVar = isString(current) && this.variableSyntaxTest.test(current)
|
|
1153
1232
|
// dotProp.get(this.originalConfig, thePath)
|
|
1154
1233
|
results.push(leaf)
|
|
1155
1234
|
}
|
|
@@ -1167,9 +1246,18 @@ class Configorama {
|
|
|
1167
1246
|
*/
|
|
1168
1247
|
populateVariables(properties) {
|
|
1169
1248
|
// console.log('properties', properties)
|
|
1249
|
+
// hasVar was precomputed in getProperties — no need to re-test the regex here.
|
|
1250
|
+
// Properties whose value is defined and lacks a variable are terminally
|
|
1251
|
+
// resolved: record their path so getProperties can skip them on the next
|
|
1252
|
+
// iteration. Undefined-valued leaves stay eligible — the final
|
|
1253
|
+
// undefined-detection traverse still needs to find them in this.leaves.
|
|
1170
1254
|
let variables = properties.filter((property) => {
|
|
1171
|
-
|
|
1172
|
-
|
|
1255
|
+
if (property.hasVar) return true
|
|
1256
|
+
if (property.value !== undefined) {
|
|
1257
|
+
const p = property.path
|
|
1258
|
+
this._resolvedPaths.add(p.length > 1 ? p.join('.') : p[0])
|
|
1259
|
+
}
|
|
1260
|
+
return false
|
|
1173
1261
|
})
|
|
1174
1262
|
/*
|
|
1175
1263
|
console.log(`variables at call count ${this.callCount}`, variables)
|
|
@@ -1205,6 +1293,15 @@ class Configorama {
|
|
|
1205
1293
|
if (result.value !== result.populated) {
|
|
1206
1294
|
set(target, result.path, result.populated)
|
|
1207
1295
|
}
|
|
1296
|
+
// If the populated value is defined and no longer contains a variable,
|
|
1297
|
+
// mark this path resolved so getProperties skips it on subsequent
|
|
1298
|
+
// iterations. Undefined means resolution failed — keep it eligible so
|
|
1299
|
+
// the final undefined-detection traverse still has a leaf to reference.
|
|
1300
|
+
const populated = result.populated
|
|
1301
|
+
if (populated !== undefined && (typeof populated !== 'string' || !this.variableSyntaxTest.test(populated))) {
|
|
1302
|
+
const p = result.path
|
|
1303
|
+
this._resolvedPaths.add(p.length > 1 ? p.join('.') : p[0])
|
|
1304
|
+
}
|
|
1208
1305
|
})
|
|
1209
1306
|
})
|
|
1210
1307
|
}
|
|
@@ -1354,7 +1451,7 @@ class Configorama {
|
|
|
1354
1451
|
historyEntry.fallbackValues = variableParts.slice(1).map((fallback) => {
|
|
1355
1452
|
const trimmedFallback = fallback.trim()
|
|
1356
1453
|
// Check if it's a variable reference
|
|
1357
|
-
const isVariable =
|
|
1454
|
+
const isVariable = this.variableSyntaxTest.test(trimmedFallback) || this.variablesKnownTypes.test(trimmedFallback)
|
|
1358
1455
|
const fallbackData = {
|
|
1359
1456
|
isVariable: !!isVariable,
|
|
1360
1457
|
fullMatch: trimmedFallback,
|
|
@@ -1592,7 +1689,7 @@ class Configorama {
|
|
|
1592
1689
|
}
|
|
1593
1690
|
|
|
1594
1691
|
/* Handle ${self:custom.ref, ''} with deep values */
|
|
1595
|
-
if (v.match(deepRefSyntax) &&
|
|
1692
|
+
if (v.match(deepRefSyntax) && this.variableSyntaxTest.test(originalSrc) && !v.match(/deep\:(\d*)\..*}$/)) {
|
|
1596
1693
|
// console.log('deep ref syntax')
|
|
1597
1694
|
// console.log('deep var', this.deep)
|
|
1598
1695
|
// console.log('originalSrc', originalSrc)
|
|
@@ -1642,8 +1739,8 @@ class Configorama {
|
|
|
1642
1739
|
/** */
|
|
1643
1740
|
// Handle comma ${opt:stage, dev} and remove extra suffix
|
|
1644
1741
|
if (
|
|
1645
|
-
|
|
1646
|
-
!
|
|
1742
|
+
this.variableSyntaxTest.test(currentMatchedString) &&
|
|
1743
|
+
!this.variableSyntaxTest.test(valueToPopulate) &&
|
|
1647
1744
|
valueToPopulate.match(this.varSuffixPattern)
|
|
1648
1745
|
) {
|
|
1649
1746
|
valueToPopulate = valueToPopulate.replace(this.varSuffixPattern, '')
|
|
@@ -1651,7 +1748,7 @@ class Configorama {
|
|
|
1651
1748
|
|
|
1652
1749
|
// For eval/if expressions, string values need quotes unless already quoted
|
|
1653
1750
|
// BUT don't quote strings that contain variable refs (they need further resolution)
|
|
1654
|
-
if (evalIfPattern.test(property) && !
|
|
1751
|
+
if (evalIfPattern.test(property) && !this.variableSyntaxTest.test(valueToPopulate)) {
|
|
1655
1752
|
const matchIdx = property.indexOf(currentMatchedString)
|
|
1656
1753
|
const charBefore = matchIdx > 0 ? property[matchIdx - 1] : ''
|
|
1657
1754
|
// Always escape quotes in values for eval/if context
|
|
@@ -1689,8 +1786,8 @@ class Configorama {
|
|
|
1689
1786
|
const isNestedInVariable = (
|
|
1690
1787
|
property.trim() !== matchedString.trim() &&
|
|
1691
1788
|
property.indexOf(matchedString) !== -1 &&
|
|
1692
|
-
|
|
1693
|
-
|
|
1789
|
+
this.variableSyntaxTest.test(matchedString) &&
|
|
1790
|
+
this.variableSyntaxTest.test(property)
|
|
1694
1791
|
)
|
|
1695
1792
|
// Only encode for file() or text() references where JSON braces break regex matching
|
|
1696
1793
|
const isFileOrTextRef = /\bfile\s*\(|\btext\s*\(/.test(property)
|
|
@@ -1861,7 +1958,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1861
1958
|
|
|
1862
1959
|
if (
|
|
1863
1960
|
/* Not another variable reference */
|
|
1864
|
-
!
|
|
1961
|
+
!this.variableSyntaxTest.test(prop)
|
|
1865
1962
|
&&
|
|
1866
1963
|
/* Not file or text refs */
|
|
1867
1964
|
!prop.match(fileRefSyntax)
|
|
@@ -1898,7 +1995,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1898
1995
|
typeof valueToPopulate === 'string' &&
|
|
1899
1996
|
!valueToPopulate.match(deepRefSyntax) &&
|
|
1900
1997
|
foundFilters.length &&
|
|
1901
|
-
!
|
|
1998
|
+
!this.variableSyntaxTest.test(property)
|
|
1902
1999
|
) {
|
|
1903
2000
|
runFilters = true
|
|
1904
2001
|
}
|
|
@@ -1977,7 +2074,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1977
2074
|
const secondValue = variableStrings[1]
|
|
1978
2075
|
if (
|
|
1979
2076
|
isString(firstValue) && firstValue.match(this.variablesKnownTypes)
|
|
1980
|
-
&& isString(secondValue) && !secondValue.match(this.variablesKnownTypes) && !
|
|
2077
|
+
&& isString(secondValue) && !secondValue.match(this.variablesKnownTypes) && !this.variableSyntaxTest.test(secondValue)
|
|
1981
2078
|
) {
|
|
1982
2079
|
if (!isSurroundedByQuotes(secondValue) && !/^-?\d+(\.\d+)?$/.test(secondValue) && !startsWithQuotedPipe(secondValue)) {
|
|
1983
2080
|
variableStrings = [firstValue, ensureQuote(secondValue)]
|
|
@@ -2009,7 +2106,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2009
2106
|
|
|
2010
2107
|
extractedValues.forEach((value, index) => {
|
|
2011
2108
|
// console.log('───────────────────────────────> value', value)
|
|
2012
|
-
if (isString(value) &&
|
|
2109
|
+
if (isString(value) && this.variableSyntaxTest.test(value)) {
|
|
2013
2110
|
deepProperties += 1
|
|
2014
2111
|
// console.log('makeDeepVariable overwrite', value)
|
|
2015
2112
|
const deepVariable = this.makeDeepVariable(value, 'via overwrite')
|
|
@@ -2630,7 +2727,8 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2630
2727
|
fileRefSyntax: fileRefSyntax,
|
|
2631
2728
|
textRefSyntax: textRefSyntax,
|
|
2632
2729
|
varPrefix: this.varPrefix,
|
|
2633
|
-
varSuffix: this.varSuffix
|
|
2730
|
+
varSuffix: this.varSuffix,
|
|
2731
|
+
fileContentCache: this._fileContentCache
|
|
2634
2732
|
}
|
|
2635
2733
|
return getValueFromFileResolver(ctx, variableString, options)
|
|
2636
2734
|
}
|
|
@@ -2654,7 +2752,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2654
2752
|
// if there is a deep reference remaining
|
|
2655
2753
|
ret = ret.then((result) => {
|
|
2656
2754
|
// console.log('DEEP RESULT', result)
|
|
2657
|
-
if (isString(result.value) &&
|
|
2755
|
+
if (isString(result.value) && this.variableSyntaxTest.test(result.value)) {
|
|
2658
2756
|
// console.log('makeDeepVariable getValueFromDeep', result.value)
|
|
2659
2757
|
const deepVariable = this.makeDeepVariable(result.value, 'via getValueFromDeep')
|
|
2660
2758
|
return Promise.resolve(appendDeepVariable(deepVariable, deepRef))
|
|
@@ -2759,7 +2857,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2759
2857
|
|
|
2760
2858
|
reducedValue = reducedValue[subProperty]
|
|
2761
2859
|
}
|
|
2762
|
-
if (typeof reducedValue === 'string' &&
|
|
2860
|
+
if (typeof reducedValue === 'string' && this.variableSyntaxTest.test(reducedValue)) {
|
|
2763
2861
|
// console.log('makeDeepVariable reducedValue', reducedValue)
|
|
2764
2862
|
reducedValue = this.makeDeepVariable(reducedValue, 'via getDeeperValue')
|
|
2765
2863
|
}
|
|
@@ -108,6 +108,7 @@ function parseFileContents(content, filePath) {
|
|
|
108
108
|
* @param {RegExp} ctx.textRefSyntax - Regex for text() syntax
|
|
109
109
|
* @param {string} ctx.varPrefix - Variable prefix (e.g., '${')
|
|
110
110
|
* @param {string} ctx.varSuffix - Variable suffix (e.g., '}')
|
|
111
|
+
* @param {Map<string, string>} [ctx.fileContentCache] - Optional per-instance read cache keyed by absolute file path
|
|
111
112
|
* @param {string} variableString - The variable string to resolve
|
|
112
113
|
* @param {object} options - Resolution options
|
|
113
114
|
* @returns {Promise<any>}
|
|
@@ -254,7 +255,14 @@ ${JSON.stringify(options.context, null, 2)}`,
|
|
|
254
255
|
|
|
255
256
|
let valueToPopulate
|
|
256
257
|
|
|
257
|
-
|
|
258
|
+
// Per-instance read cache: identical ${file:...} refs hit fs once per resolve.
|
|
259
|
+
let variableFileContents
|
|
260
|
+
if (ctx.fileContentCache && ctx.fileContentCache.has(fullFilePath)) {
|
|
261
|
+
variableFileContents = ctx.fileContentCache.get(fullFilePath)
|
|
262
|
+
} else {
|
|
263
|
+
variableFileContents = fs.readFileSync(fullFilePath, 'utf-8')
|
|
264
|
+
if (ctx.fileContentCache) ctx.fileContentCache.set(fullFilePath, variableFileContents)
|
|
265
|
+
}
|
|
258
266
|
|
|
259
267
|
/* handle case for referencing raw JS files to inline them */
|
|
260
268
|
if ((argsToPass.length
|
|
@@ -233,12 +233,8 @@ function preProcess(configObject, variableSyntax, variableTypes, options = {}) {
|
|
|
233
233
|
const afterOp = afterRef.substring(op.length).trimStart()
|
|
234
234
|
return afterOp.startsWith('"') || afterOp.startsWith("'")
|
|
235
235
|
}
|
|
236
|
-
// Check if preceded by: "string" op
|
|
237
|
-
|
|
238
|
-
const pattern = new RegExp(`["'][^"']*["']\\s*${o.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`)
|
|
239
|
-
if (pattern.test(beforeRef)) return true
|
|
240
|
-
}
|
|
241
|
-
return false
|
|
236
|
+
// Check if preceded by: "string" op (reuses precededByPatterns)
|
|
237
|
+
return precededByPatterns.some(p => p.test(beforeRef))
|
|
242
238
|
})
|
|
243
239
|
|
|
244
240
|
if (!precededByQuote && !followedByQuote && isComparedToString) {
|
package/types/src/main.d.ts
CHANGED
|
@@ -4,11 +4,15 @@ declare class Configorama {
|
|
|
4
4
|
settings: any;
|
|
5
5
|
filterCache: {};
|
|
6
6
|
_originalValueCache: Map<any, any>;
|
|
7
|
+
_resolvedPaths: Set<any>;
|
|
8
|
+
_fileContentCache: Map<any, any>;
|
|
9
|
+
_needsRawClone: boolean;
|
|
7
10
|
foundVariables: any[];
|
|
8
11
|
fileRefsFound: any[];
|
|
9
12
|
resolutionTracking: {};
|
|
10
13
|
_trackCalls: boolean;
|
|
11
14
|
variableSyntax: RegExp;
|
|
15
|
+
variableSyntaxTest: RegExp;
|
|
12
16
|
varPrefix: string;
|
|
13
17
|
varSuffix: string;
|
|
14
18
|
varPrefixPattern: RegExp;
|
package/types/src/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.js"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.js"],"names":[],"mappings":";AAyIA;IACE,0CAufC;IA/eC,cAkBW;IAuBX,gBAAqB;IAErB,mCAAoC;IAGpC,yBAA+B;IAG/B,iCAAkC;IAMlC,wBAIC;IAED,sBAAwB;IACxB,qBAAuB;IAGvB,uBAA4B;IAE5B,qBAAmD;IAsBnD,uBAAoC;IAIpC,2BAAkG;IAIlG,kBAAqC;IACrC,kBAAqC;IAErC,yBAA+F;IAC/F,yBAAuD;IACvD,kCAAyE;IAMrE,uBAAgD;IAMlD,YAAuB;IAEvB,oBAA0C;IAE1C,gBAAoD;IAOpD,uBAAkC;IAElC,uBAA8B;IAE9B,uBAAkC;IASpC,wBAAmC;IAGnC,mBAqHC;IAwED,4BAA8C;IAG9C,iCAAkC;IAalC,aA2EC;IAUD,oBAEC;IAGD,eAiDC;IAOD,YAAc;IACd,cAAgB;IAChB,kBAAkB;IAGpB;;;;OAIG;IACH,0BAHW,MAAM,GACJ,OAAO,CAQnB;IAED;;;;OAIG;IACH,6BAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CAOvB;IAED;;;;OAIG;IACH,gCAHW,MAAM,GACJ,OAAO,CA2BnB;IAKD;;;;;OAKG;IACH,oBAFa,OAAO,CAAC,GAAG,CAAC,CA+UxB;IA5UC,aAA4B;IAc1B,2BAA4B;IAqB1B,sBAA0C;IAC1C,4BAAkC;IA0SxC;;;OAGG;IACH,2BAFa,MAAM,CAsBlB;IAdC,qBAWE;IAIJ;;;;OAIG;IACH,uCAFa,OAAO,CAAC,GAAG,CAAC,CAIxB;IACD,+CAsBC;IAKD;;;;;;;;;;;;;;;;;;;OAmBG;IACH;;;;;;;;;;;OAWG;IACH,mFAHa;;;;cAZC,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAWD,CAsG9B;IACD;;;OAGG;IACH;;;;;OAKG;IACH,oCAHa,OAAO,CAAC;;;;cA1HP,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAyHgB,CAAC,EAAE,CAsClD;IACD;;;;;OAKG;IACH,iDAFa,OAAO,CAAC,IAAI,CAAC,CAoBzB;IAID;;;;;OAKG;IACH;;;;OAIG;IACH,2BAFa,eAAc;;;;;;;;;;OAAa,CAavC;IACD;;;;;OAKG;IACH,yBAHW;;;;;;;;;;OAAa,gCACX,cAAS,CAOrB;IACD;;;;;;OAMG;IACH,6DAFa,GAAC,CAiLb;IAKD;;;;;;;OAOG;IACH,yDAHa,OAAO,CAAC,GAAG,CAAC,CAiCxB;IACD;;;;OAIG;IAOH;;;;;;OAMG;IACH,wFA2BC;IACD;;;;;;;;;;;OAWG;IACH,8BARG;QAAyB,KAAK,EAAtB,GAAG;QACoB,IAAI,GAA3B,MAAM,EAAE;QACa,cAAc,GAAnC,MAAM;QACc,iBAAiB;KAC7C,6CAEU;QAAC,KAAK,EAAE,GAAG,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,QAAQ;QAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAC,CAsa9J;IAID;;;;;;;;OAQG;IACH,qEAHa,OAAO,CAAC,GAAG,CAAC,CAoExB;IAKD;;;;;;;OAOG;IACH,0FAFa,OAAO,CAAC,GAAG,CAAC,CAgiBxB;IACD,+EA+BC;IACD,yDAkBC;IACD,oEA6BC;IAKD,8CAQC;IACD,kDAyBC;IACD;;;;;;;;;;;;;OAaG;IACH,wEAoDC;IAKD,4BAOC;IACD,sCAqEC;CACF"}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* @param {RegExp} ctx.textRefSyntax - Regex for text() syntax
|
|
15
15
|
* @param {string} ctx.varPrefix - Variable prefix (e.g., '${')
|
|
16
16
|
* @param {string} ctx.varSuffix - Variable suffix (e.g., '}')
|
|
17
|
+
* @param {Map<string, string>} [ctx.fileContentCache] - Optional per-instance read cache keyed by absolute file path
|
|
17
18
|
* @param {string} variableString - The variable string to resolve
|
|
18
19
|
* @param {object} options - Resolution options
|
|
19
20
|
* @returns {Promise<any>}
|
|
@@ -32,6 +33,7 @@ export function getValueFromFile(ctx: {
|
|
|
32
33
|
textRefSyntax: RegExp;
|
|
33
34
|
varPrefix: string;
|
|
34
35
|
varSuffix: string;
|
|
36
|
+
fileContentCache?: Map<string, string>;
|
|
35
37
|
}, variableString: string, options: object): Promise<any>;
|
|
36
38
|
/**
|
|
37
39
|
* Parse file contents based on file extension
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valueFromFile.d.ts","sourceRoot":"","sources":["../../../src/resolvers/valueFromFile.js"],"names":[],"mappings":"AA8FA
|
|
1
|
+
{"version":3,"file":"valueFromFile.d.ts","sourceRoot":"","sources":["../../../src/resolvers/valueFromFile.js"],"names":[],"mappings":"AA8FA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,sCAlBG;IAAoB,UAAU,EAAtB,MAAM;IACK,aAAa;IACZ,cAAc,EAA1B,MAAM;IACM,mBAAmB,EAA/B,MAAM;IACM,aAAa,EAAzB,MAAM;IACM,IAAI,EAAhB,MAAM;IACM,cAAc,EAA1B,MAAM;IACM,MAAM,EAAlB,MAAM;IACQ,cAAc;IAChB,aAAa,EAAzB,MAAM;IACM,aAAa,EAAzB,MAAM;IACM,SAAS,EAArB,MAAM;IACM,SAAS,EAArB,MAAM;IACoB,gBAAgB,GAA1C,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;CAC3B,kBAAQ,MAAM,WACN,MAAM,GACJ,OAAO,CAAC,GAAG,CAAC,CA+VxB;AA5YD;;;;;GAKG;AACH,2CAJW,MAAM,YACN,MAAM,GACJ,GAAC,CAoBb;AAsXD;;;;;;GAMG;AACH,qDAJW,MAAM,qBACN,MAAM,GACJ;IAAE,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,GAAC,IAAI,CAAA;CAAE,CAkBhE;AAED;;;;;;GAMG;AACH,sDALW,MAAM,qBACN,MAAM,yBACN,OAAO,GACL,MAAM,EAAE,CAcpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preProcess.d.ts","sourceRoot":"","sources":["../../../../src/utils/parsing/preProcess.js"],"names":[],"mappings":";AASA;;;;;;;;GAQG;AACH,+DANW,MAAM,mCAGd;IAA0B,eAAe,GAAjC,OAAO;CACf,
|
|
1
|
+
{"version":3,"file":"preProcess.d.ts","sourceRoot":"","sources":["../../../../src/utils/parsing/preProcess.js"],"names":[],"mappings":";AASA;;;;;;;;GAQG;AACH,+DANW,MAAM,mCAGd;IAA0B,eAAe,GAAjC,OAAO;CACf,OA4XF"}
|