configorama 0.9.17 → 0.10.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/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
- - [What's New](#whats-new)
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
+ A typical 21KB serverless-style config resolves in **~3ms** on warm Node 22.
360
+
361
+ - 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're happy to 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
- ```yaml
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, the 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.json5.parse('{ key: "value", }')
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
- **Parser methods:**
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
- Each parser has:
1496
- - `parse(content)` - Parse string to JavaScript object
1497
- - `stringify(obj)` - Convert JavaScript object to format string (if supported)
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, CloudFormation refs)
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 both config and metadata about variables |
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 when 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 AWS = require('aws-sdk')
1780
- const ssm = new AWS.SSM()
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.getParameter({
2037
+ const result = await ssm.send(new GetParameterCommand({
1794
2038
  Name: paramPath,
1795
2039
  WithDecryption: true
1796
- }).promise()
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 working example of a `source: 'remote'` resolver. It handles multi-region and multi-account credential swapping, plus per-instance client and 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: nodejs18.x
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:18-alpine
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@v3
2477
+ - uses: actions/checkout@v4
2216
2478
 
2217
2479
  - name: Setup Node.js
2218
- uses: actions/setup-node@v3
2480
+ uses: actions/setup-node@v4
2219
2481
  with:
2220
- node-version: '18'
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:18
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:18
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:18
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 AWS = require('aws-sdk')
2524
- const ssm = new AWS.SSM()
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.getParameter({
2789
+ const result = await ssm.send(new GetParameterCommand({
2528
2790
  Name: '/myapp/api-key',
2529
2791
  WithDecryption: true
2530
- }).promise()
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 CloudFormation refs
2598
- // (e.g., Serverless Dashboard, AWS CloudFormation, etc.)
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, so 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 AWS = require('aws-sdk')
2664
- const ssm = new AWS.SSM()
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.getParameter({
2931
+ const result = await ssm.send(new GetParameterCommand({
2668
2932
  Name: paramName,
2669
2933
  WithDecryption: true
2670
- }).promise()
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
- ## What's New
2738
-
2739
- How is this different than the Serverless Framework variable system?
3001
+ ## Comparison vs Serverless Framework Variables
3002
+
3003
+ Configorama was forked from the Serverless Framework variable system and extended. The differences:
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
- 1. **Framework-agnostic** - Use with any tool, not just Serverless Framework
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
- 8. **TypeScript support** - Execute TypeScript files directly:
2777
- ```yaml
2778
- config: ${file(./config.ts)}
2779
- ```
3025
+ ## Alternative Libraries
2780
3026
 
2781
- 9. **Conditional expressions** - If/else logic in configs:
2782
- ```yaml
2783
- memory: ${if(${stage} === 'prod' ? 1024 : 512)}
2784
- ```
3027
+ How configorama compares to other variable-substitution libraries:
2785
3028
 
2786
- 10. **Metadata extraction** - Analyze configs without resolving:
2787
- ```javascript
2788
- const meta = await configorama.analyze('config.yml')
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
- ## Alternative Libraries
3039
+ ## Changelog
2794
3040
 
2795
- - [sls-yaml](https://github.com/01alchemist/sls-yaml) - YAML with variable support
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
- **Mad props to:**
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
- Contributions welcome! Please read the [contributing guidelines](CONTRIBUTING.md) first.
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)