configorama 0.11.2 → 1.0.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 +429 -123
- package/cli.js +282 -49
- package/index.d.ts +43 -1
- package/package.json +5 -1
- package/src/capabilities.js +59 -0
- package/src/capabilities.test.js +44 -0
- package/src/display.js +70 -7
- package/src/display.test.js +82 -0
- package/src/errors.js +73 -0
- package/src/index.js +91 -1
- package/src/main.js +117 -15
- package/src/resolvers/valueFromEval.js +11 -1
- package/src/resolvers/valueFromFile.js +5 -0
- package/src/resolvers/valueFromGit.js +43 -17
- package/src/resolvers/valueFromOptions.js +5 -4
- package/src/utils/filters/filterArgs.js +57 -0
- package/src/utils/filters/oneOf.js +77 -0
- package/src/utils/introspection/audit.js +78 -0
- package/src/utils/introspection/graph.js +43 -0
- package/src/utils/introspection/model.js +150 -0
- package/src/utils/introspection/model.test.js +93 -0
- package/src/utils/parsing/commentAnnotations.js +107 -0
- package/src/utils/parsing/commentAnnotations.test.js +123 -0
- package/src/utils/parsing/enrichMetadata.js +64 -1
- package/src/utils/parsing/enrichMetadata.test.js +84 -0
- package/src/utils/parsing/extractComment.js +145 -0
- package/src/utils/parsing/extractComment.test.js +182 -0
- package/src/utils/parsing/preProcess.js +2 -1
- package/src/utils/paths/findLineForKey.js +2 -2
- package/src/utils/paths/ignorePaths.js +21 -9
- package/src/utils/redaction/redact.js +78 -0
- package/src/utils/redaction/redact.test.js +38 -0
- package/src/utils/redaction/setupRedaction.js +47 -0
- package/src/utils/redaction/setupRedaction.test.js +68 -0
- package/src/utils/requirements/configRequirements.js +351 -0
- package/src/utils/requirements/configRequirements.test.js +380 -0
- package/src/utils/requirements/serializeRequirements.js +120 -0
- package/src/utils/requirements/serializeRequirements.test.js +211 -0
- package/src/utils/security/evalSafety.js +86 -0
- package/src/utils/security/evalSafety.test.js +61 -0
- package/src/utils/security/safetyPolicy.js +110 -0
- package/src/utils/security/safetyPolicy.test.js +29 -0
- package/src/utils/strings/didYouMean.js +70 -0
- package/src/utils/strings/didYouMean.test.js +52 -0
- package/src/utils/strings/formatFunctionArgs.js +6 -1
- package/src/utils/strings/splitByComma.js +5 -0
- package/src/utils/ui/configWizard.js +208 -34
- package/src/utils/ui/createEditorLink.js +17 -1
- package/src/utils/ui/promptDescriptors.js +196 -0
- package/src/utils/ui/promptDescriptors.test.js +162 -0
- package/src/utils/variables/cleanVariable.js +22 -0
- package/src/utils/variables/getVariableType.js +1 -0
package/README.md
CHANGED
|
@@ -1,8 +1,90 @@
|
|
|
1
1
|
# Configorama
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/configorama)
|
|
4
|
+
[](https://www.npmjs.com/package/configorama)
|
|
5
|
+
[](https://www.npmjs.com/package/configorama)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Resolve dynamic config values from environment variables, CLI flags, files, git data, expressions, and custom sources. Works with YAML, JSON, TOML, INI, HCL, Markdown, JavaScript, and TypeScript.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install configorama
|
|
11
|
+
npx configorama config.yml --stage prod
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Configorama is a framework-agnostic variable engine for configuration files. Use it to resolve a config at runtime, inspect missing values before resolution, audit risky references, draw dependency graphs, run an interactive setup flow, or emit requirements JSON for agents and automation.
|
|
15
|
+
|
|
16
|
+
## TL;DR
|
|
17
|
+
|
|
18
|
+
Deployment configs usually pull values from several places: env vars, CLI flags, local files, generated JavaScript, git metadata, stage-specific maps, and secret stores. Most config parsers stop at parsing, while framework-specific variable systems tend to stay tied to that framework.
|
|
19
|
+
|
|
20
|
+
Configorama loads a config file, finds variable references, resolves them in dependency order, applies filters/functions, and returns a plain JavaScript object. It can also report what the config needs before resolution.
|
|
21
|
+
|
|
22
|
+
Common use cases:
|
|
23
|
+
|
|
24
|
+
| Need | Support | Example |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Resolve values from many sources | Built-in `env`, `option`/`opt`, `self`, `file`, `text`, `git`, `cron`, `eval`, and `if` sources | `${env:API_KEY}`, `${option:stage}`, `${file(./secrets.yml)}` |
|
|
27
|
+
| Keep config portable | Runs outside any framework | Use the same resolver in a CLI, build script, deploy job, or app bootstrap |
|
|
28
|
+
| Prompt for missing inputs | Interactive setup wizard with type-aware prompts and masked secrets | `configorama setup config.yml` |
|
|
29
|
+
| Tell agents what to provide | Requirements JSON with `schemaVersion`, `requirements[]`, and `ask[]` | `configorama inspect config.yml --view requirements` |
|
|
30
|
+
| Inspect before resolving | Full inspection model plus focused `requirements`, `audit`, and `graph` views | `configorama inspect config.yml` |
|
|
31
|
+
| Document variables near the config | `help()` plus comment annotations for descriptions, obtain hints, examples, groups, sensitivity, and deprecation warnings | `# @from Stripe dashboard > Developers > API keys` |
|
|
32
|
+
| Enforce runtime constraints | Type filters and `oneOf(...)` validation | `${option:threads \| Number \| oneOf(1, 2, 4)}` |
|
|
33
|
+
|
|
34
|
+
## Quick Example
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
# config.yml
|
|
38
|
+
service: billing-api
|
|
39
|
+
|
|
40
|
+
# Deployment stage
|
|
41
|
+
stage: ${option:stage | oneOf("dev", "staging", "prod")}
|
|
42
|
+
|
|
43
|
+
secrets:
|
|
44
|
+
# Stripe live secret key
|
|
45
|
+
# @from Stripe dashboard > Developers > API keys
|
|
46
|
+
# @example sk_live_...
|
|
47
|
+
# @sensitive true
|
|
48
|
+
# @group Payments
|
|
49
|
+
stripeSecret: ${env:STRIPE_SECRET_KEY}
|
|
50
|
+
|
|
51
|
+
database:
|
|
52
|
+
host: ${env:DB_HOST, "localhost"}
|
|
53
|
+
port: ${env:DB_PORT, 5432 | Number}
|
|
54
|
+
name: ${self:service}-${self:stage}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Resolve the config
|
|
59
|
+
STRIPE_SECRET_KEY=sk_live_xxx npx configorama config.yml --stage prod
|
|
60
|
+
|
|
61
|
+
# Walk through missing variables interactively
|
|
62
|
+
npx configorama setup config.yml
|
|
63
|
+
|
|
64
|
+
# Print requirements for agents or automation
|
|
65
|
+
npx configorama inspect config.yml --view requirements
|
|
66
|
+
|
|
67
|
+
# Inspect requirements, dependency graph, and audit report together
|
|
68
|
+
npx configorama inspect config.yml
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## What We Added Recently
|
|
72
|
+
|
|
73
|
+
| Area | Added |
|
|
74
|
+
|---|---|
|
|
75
|
+
| Normalized requirements model | `ConfigRequirements` groups occurrences by variable, normalizes `${opt:...}` and `${option:...}` as `variableType: "option"`, and tracks paths, defaults, types, allowed values, sensitivity, and conflicts. |
|
|
76
|
+
| Unified inspection CLI | `configorama inspect config.yml` emits requirements, graph, and audit output without resolving missing values. Use `--view requirements`, `--view audit`, or `--view graph` for one slice. |
|
|
77
|
+
| Requirements JSON | `configorama inspect config.yml --view requirements`, `configorama requirements config.yml`, and `configorama config.yml --requirements` emit `schemaVersion: 1`, `summary`, `requirements[]`, and environment-aware `ask[]`. |
|
|
78
|
+
| Safe inspection | `inspect`, `audit`, and `graph` run in safe mode by default. Use `--unsafe` to opt out or `--safe-root <dir>` to restrict file/text references. |
|
|
79
|
+
| Agent-friendly CLI contract | `configorama capabilities` prints commands, aliases, formats, flags, error codes, and exit codes as JSON. |
|
|
80
|
+
| Path extraction polish | Jq-style paths, `--raw`, and `--copy` make scalar extraction usable in scripts: `configorama -r --copy config.yml .database.host`. |
|
|
81
|
+
| Conflict handling | Conflicting type/default/allowed-value/annotation metadata is deterministic in the wizard. Requirements serialization fails on conflicts so agents get a clean contract. |
|
|
82
|
+
| Setup wizard migration | The wizard now consumes prompt descriptors derived from the requirements model, supports enum selects from `oneOf`, displays annotation details, and redacts sensitive values in setup summaries and setup stdout. |
|
|
83
|
+
| `oneOf(...)` validation | Runtime filter for inline literal sets and resolved list variables, including type-filter-first behavior such as `${option:threads \| Number \| oneOf(1, 2, 4)}`. |
|
|
84
|
+
| More type filters | `Array` and `Object` filters now validate/coerce arrays, comma-separated lists, JSON/JSON5 arrays, and JSON/JSON5 objects. |
|
|
85
|
+
| Option alias | `${option:name}` is supported alongside the existing `${opt:name}` shorthand. |
|
|
86
|
+
| Comment metadata | Leading/inline comments become help fallback; structured tags add `@description`, `@from`, `@example`, `@default`, `@sensitive`, `@group`, and `@deprecated`. |
|
|
87
|
+
| Structured CLI errors | Inspection commands default to JSON errors on stderr; `--error-format human` is available for terminal use. |
|
|
6
88
|
|
|
7
89
|
## Key Features
|
|
8
90
|
|
|
@@ -11,8 +93,10 @@ Configorama extends your configuration with a powerful variable system that reso
|
|
|
11
93
|
- **Async/sync function execution** - Import and execute JavaScript/TypeScript files with argument passing
|
|
12
94
|
- **Self-referencing** - Reference other values within the same config using dot notation
|
|
13
95
|
- **Custom variable sources** - Pluggable architecture to add your own variable resolvers
|
|
14
|
-
- **Filters and functions** - Transform and combine values with built-in or custom operators
|
|
15
|
-
- **
|
|
96
|
+
- **Filters and functions** - Transform, coerce, constrain, and combine values with built-in or custom operators
|
|
97
|
+
- **Inspection modes** - Prompt humans interactively, generate requirements JSON, audit risky references, or output dependency graphs
|
|
98
|
+
- **Comment annotations** - Keep human and agent metadata beside the config value it describes
|
|
99
|
+
- **Metadata extraction** - Analyze configs without resolving missing values, or get full resolution history
|
|
16
100
|
- **Circular dependency detection** - Helpful error messages instead of infinite loops
|
|
17
101
|
- **TypeScript support** - Full type definitions and TypeScript file execution via tsx/ts-node
|
|
18
102
|
|
|
@@ -22,6 +106,9 @@ Configorama extends your configuration with a powerful variable system that reso
|
|
|
22
106
|
<details>
|
|
23
107
|
<summary>Click to expand</summary>
|
|
24
108
|
|
|
109
|
+
- [TL;DR](#tldr)
|
|
110
|
+
- [Quick Example](#quick-example)
|
|
111
|
+
- [What We Added Recently](#what-we-added-recently)
|
|
25
112
|
- [Key Features](#key-features)
|
|
26
113
|
- [Getting Started](#getting-started)
|
|
27
114
|
- [Installation](#installation)
|
|
@@ -57,7 +144,8 @@ Configorama extends your configuration with a powerful variable system that reso
|
|
|
57
144
|
- [API Reference](#api-reference)
|
|
58
145
|
- [Async API](#async-api)
|
|
59
146
|
- [Sync API](#sync-api)
|
|
60
|
-
- [
|
|
147
|
+
- [Library API](#library-api)
|
|
148
|
+
- [Inspect API](#inspect-api)
|
|
61
149
|
- [Format Utilities](#format-utilities)
|
|
62
150
|
- [Markdown Files](#markdown-files)
|
|
63
151
|
- [`buildVariableSyntax(prefix, suffix, excludePatterns?)`](#buildvariablesyntaxprefix-suffix-excludepatterns)
|
|
@@ -72,6 +160,9 @@ Configorama extends your configuration with a powerful variable system that reso
|
|
|
72
160
|
- [Custom Variable Sources](#custom-variable-sources)
|
|
73
161
|
- [Variable Source Types](#variable-source-types)
|
|
74
162
|
- [Creating a Custom Resolver](#creating-a-custom-resolver)
|
|
163
|
+
- [Config Wizard (Experimental)](#config-wizard-experimental)
|
|
164
|
+
- [Agent Requirements JSON](#agent-requirements-json)
|
|
165
|
+
- [Runtime Inspection](#runtime-inspection)
|
|
75
166
|
- [CLI Usage](#cli-usage)
|
|
76
167
|
- [Basic Commands](#basic-commands)
|
|
77
168
|
- [Command Options](#command-options)
|
|
@@ -82,7 +173,6 @@ Configorama extends your configuration with a powerful variable system that reso
|
|
|
82
173
|
- [Writing Tests](#writing-tests)
|
|
83
174
|
- [Deployment](#deployment)
|
|
84
175
|
- [Using with Serverless Framework](#using-with-serverless-framework)
|
|
85
|
-
- [Docker Deployment](#docker-deployment)
|
|
86
176
|
- [CI/CD Integration](#cicd-integration)
|
|
87
177
|
- [Troubleshooting](#troubleshooting)
|
|
88
178
|
- [Common Issues](#common-issues)
|
|
@@ -372,14 +462,14 @@ If your config is slow, please open an issue with the config (or a redacted repr
|
|
|
372
462
|
|
|
373
463
|
## Variable Sources
|
|
374
464
|
|
|
375
|
-
Configorama supports multiple variable sources
|
|
465
|
+
Configorama supports multiple variable sources. All variable syntax follows the pattern `${type:value}` or `${type(value)}`.
|
|
376
466
|
|
|
377
467
|
### Summary Table
|
|
378
468
|
|
|
379
469
|
| Variable | Syntax | Description | Example |
|
|
380
470
|
|----------|-----------------------|------------------------|---------|
|
|
381
471
|
| env | `${env:VAR}` | Environment variables | `${env:NODE_ENV}` |
|
|
382
|
-
|
|
|
472
|
+
| option | `${option:flag}` or `${opt:flag}` | CLI option flags (`opt` is shorthand) | `${option:stage}` |
|
|
383
473
|
| param | `${param:key}` | Parameter values | `${param:domain}` |
|
|
384
474
|
| self | `${key}` or `${self:key}` | Self references | `${database.host}` |
|
|
385
475
|
| file | `${file(path)}` | File references | `${file(./secrets.yml)}` |
|
|
@@ -753,7 +843,7 @@ The `ctx` parameter (always the last argument) provides access to:
|
|
|
753
843
|
|----------|-------------|
|
|
754
844
|
| `originalConfig` | The original unresolved configuration object |
|
|
755
845
|
| `currentConfig` | The current (partially resolved) configuration |
|
|
756
|
-
| `options` | Options passed to configorama (populates `${opt:xyz}` variables) |
|
|
846
|
+
| `options` | Options passed to configorama (populates `${option:xyz}` / `${opt:xyz}` variables) |
|
|
757
847
|
|
|
758
848
|
**TypeScript users can import the type:**
|
|
759
849
|
|
|
@@ -943,7 +1033,7 @@ functions:
|
|
|
943
1033
|
- Support for both sync and async TypeScript functions
|
|
944
1034
|
- Function argument passing via config variables
|
|
945
1035
|
- Full TypeScript interface support
|
|
946
|
-
-
|
|
1036
|
+
- Errors point to the failing dependency
|
|
947
1037
|
|
|
948
1038
|
---
|
|
949
1039
|
|
|
@@ -1176,7 +1266,7 @@ aboveThreshold: ${eval(${value} > ${threshold})} # true
|
|
|
1176
1266
|
|
|
1177
1267
|
### If Expressions
|
|
1178
1268
|
|
|
1179
|
-
Conditional expressions using ternary syntax. This is an alias for `eval` with a
|
|
1269
|
+
Conditional expressions using ternary syntax. This is an alias for `eval` with a clearer name for conditionals.
|
|
1180
1270
|
|
|
1181
1271
|
```yaml
|
|
1182
1272
|
# Basic ternary (condition ? "yes" : "no")
|
|
@@ -1283,6 +1373,65 @@ serviceSlug: ${serviceName | toKebabCase} # 'my-service-name'
|
|
|
1283
1373
|
- `toLowerCase` - Convert to lowercase
|
|
1284
1374
|
- `toKebabCase` - Convert to kebab-case
|
|
1285
1375
|
- `toCamelCase` - Convert to camelCase
|
|
1376
|
+
- `String`, `Number`, `Boolean`, `Array`, `Object`, `Json` - Validate/coerce resolved values
|
|
1377
|
+
- `oneOf(...)` - Restrict a value to inline literals or a resolved list variable
|
|
1378
|
+
- `help('text')` - Attach guidance to a variable for the [config wizard](#config-wizard-experimental); returns the value unchanged
|
|
1379
|
+
|
|
1380
|
+
The `help()` filter is an identity filter: it leaves the value untouched but records prompt/agent description text.
|
|
1381
|
+
|
|
1382
|
+
```yaml
|
|
1383
|
+
apiKey: ${env:API_KEY | help('The Stripe live secret key')}
|
|
1384
|
+
stage: ${option:stage | toUpperCase | help('Deployment stage')}
|
|
1385
|
+
dbPort: ${env:DB_PORT, 5432 | Number | help('The Postgres port')}
|
|
1386
|
+
```
|
|
1387
|
+
|
|
1388
|
+
`oneOf()` is a runtime constraint. It throws if the resolved value is not in the allowed set. Put type filters first when coercion matters:
|
|
1389
|
+
|
|
1390
|
+
```yaml
|
|
1391
|
+
stage: ${option:stage | oneOf('dev', 'staging', 'prod')}
|
|
1392
|
+
threads: ${option:threads | Number | oneOf(1, 2, 4)}
|
|
1393
|
+
|
|
1394
|
+
allowedStages:
|
|
1395
|
+
- dev
|
|
1396
|
+
- prod
|
|
1397
|
+
stageFromList: ${option:stage | oneOf(${self:allowedStages})}
|
|
1398
|
+
```
|
|
1399
|
+
|
|
1400
|
+
`Array` accepts existing arrays, JSON/JSON5 array strings, and comma-separated text. `Object` accepts existing objects and JSON/JSON5 object strings.
|
|
1401
|
+
|
|
1402
|
+
Comments are used as help fallback when `help()` is absent. Precedence is `help()` first, then trailing inline comments, then a leading comment block:
|
|
1403
|
+
|
|
1404
|
+
```yaml
|
|
1405
|
+
# Used by deploy jobs
|
|
1406
|
+
deployToken: ${env:DEPLOY_TOKEN}
|
|
1407
|
+
region: ${option:region, 'us-east-1'} # AWS region
|
|
1408
|
+
```
|
|
1409
|
+
|
|
1410
|
+
Use comment annotations for human and agent metadata. Filters affect runtime values; comments describe values:
|
|
1411
|
+
|
|
1412
|
+
```yaml
|
|
1413
|
+
secrets:
|
|
1414
|
+
# Stripe live secret key
|
|
1415
|
+
# @from Stripe dashboard > Developers > API keys
|
|
1416
|
+
# @example sk_live_...
|
|
1417
|
+
# @default Set in CI or local shell profile
|
|
1418
|
+
# @sensitive true
|
|
1419
|
+
# @group Payments
|
|
1420
|
+
# @deprecated Use STRIPE_RESTRICTED_KEY instead
|
|
1421
|
+
stripeSecret: ${env:STRIPE_SECRET_KEY}
|
|
1422
|
+
```
|
|
1423
|
+
|
|
1424
|
+
Supported annotation tags:
|
|
1425
|
+
|
|
1426
|
+
- `@description ...` - Explicit description; overrides normal comment text and `help()`
|
|
1427
|
+
- `@from ...` - Where to obtain the value; appears as `obtainHint`
|
|
1428
|
+
- `@example ...` - Example value; can appear multiple times
|
|
1429
|
+
- `@default ...` - Documentation-only default hint; does not resolve the variable
|
|
1430
|
+
- `@sensitive true|false` - Override name-based masking detection
|
|
1431
|
+
- `@group ...` - Wizard display group label
|
|
1432
|
+
- `@deprecated ...` - Warning text for requirements JSON and prompt descriptors
|
|
1433
|
+
|
|
1434
|
+
`from()` and `meta()` are not built-in filters. JSON files cannot use comment annotations because JSON has no comments; use JSON5/JSONC or another commented format if metadata is needed.
|
|
1286
1435
|
|
|
1287
1436
|
**Custom filters:**
|
|
1288
1437
|
|
|
@@ -1519,62 +1668,87 @@ const config = configorama.sync('./config.yml', {
|
|
|
1519
1668
|
|
|
1520
1669
|
---
|
|
1521
1670
|
|
|
1522
|
-
###
|
|
1671
|
+
### Library API
|
|
1672
|
+
|
|
1673
|
+
```javascript
|
|
1674
|
+
const configorama = require('configorama')
|
|
1675
|
+
|
|
1676
|
+
const config = await configorama('config.yml')
|
|
1677
|
+
const result = await configorama('config.yml', { returnMetadata: true })
|
|
1678
|
+
const requirements = await configorama.inspect('config.yml', { view: 'requirements' })
|
|
1679
|
+
const graph = await configorama.inspect('config.yml', { view: 'graph', format: 'mermaid' })
|
|
1680
|
+
const configSync = configorama.sync('config.yml')
|
|
1681
|
+
```
|
|
1682
|
+
|
|
1683
|
+
The stable public surface is intentionally small:
|
|
1684
|
+
|
|
1685
|
+
| Method | Use it for |
|
|
1686
|
+
|---|---|
|
|
1687
|
+
| `configorama(fileOrObject, opts)` | Async resolution. Set `returnMetadata: true` when you need `metadata.variables`, `metadata.uniqueVariables`, or `metadata.fileDependencies`. |
|
|
1688
|
+
| `configorama.sync(fileOrObject, opts)` | Synchronous resolution for blocking contexts. |
|
|
1689
|
+
| `configorama.inspect(fileOrObject, opts)` | Pre-resolution inspection. Use `view: "requirements"`, `view: "audit"`, or `view: "graph"` for one projection. |
|
|
1690
|
+
| `configorama.format` | Parser utilities for YAML, JSON/JSON5, TOML, INI, HCL, and Markdown frontmatter. |
|
|
1691
|
+
|
|
1692
|
+
Lower-level helpers still exist for compatibility: `analyze()`, `introspect()`, `audit()`, `graph()`, `buildVariableSyntax()`, and `Configorama`. New code should start with `configorama()`, `configorama.sync()`, or `configorama.inspect()`.
|
|
1693
|
+
|
|
1694
|
+
`returnMetadata: true` remains the right API for tools that need resolved config plus dependency metadata:
|
|
1695
|
+
|
|
1696
|
+
```javascript
|
|
1697
|
+
const result = await configorama('serverless.yml', {
|
|
1698
|
+
returnMetadata: true,
|
|
1699
|
+
allowUnknownVariableTypes: true,
|
|
1700
|
+
allowUnresolvedVariables: true,
|
|
1701
|
+
options: { stage: 'prod' }
|
|
1702
|
+
})
|
|
1703
|
+
|
|
1704
|
+
console.log(result.config)
|
|
1705
|
+
console.log(result.metadata.variables)
|
|
1706
|
+
console.log(result.metadata.uniqueVariables)
|
|
1707
|
+
console.log(result.metadata.fileDependencies.resolvedPaths)
|
|
1708
|
+
console.log(result.metadata.fileDependencies.globPatterns)
|
|
1709
|
+
```
|
|
1710
|
+
|
|
1711
|
+
That metadata shape is the same path used by the newer inspection APIs, so tools can keep consuming it without switching to `inspect()`.
|
|
1712
|
+
|
|
1713
|
+
---
|
|
1714
|
+
|
|
1715
|
+
### Inspect API
|
|
1523
1716
|
|
|
1524
|
-
|
|
1717
|
+
Inspect config structure without resolving missing user inputs.
|
|
1525
1718
|
|
|
1526
1719
|
**Signature:**
|
|
1527
1720
|
|
|
1528
1721
|
```typescript
|
|
1529
|
-
function configorama.
|
|
1722
|
+
function configorama.inspect(
|
|
1530
1723
|
configPathOrObject: string | object,
|
|
1531
|
-
settings?: ConfigoramaSettings
|
|
1532
|
-
|
|
1724
|
+
settings?: ConfigoramaSettings & {
|
|
1725
|
+
view?: 'requirements' | 'audit' | 'graph'
|
|
1726
|
+
format?: 'json' | 'mermaid' | 'dot'
|
|
1727
|
+
}
|
|
1728
|
+
): Promise<object | string>
|
|
1533
1729
|
```
|
|
1534
1730
|
|
|
1535
|
-
|
|
1731
|
+
With no `view`, `inspect()` returns the full model:
|
|
1536
1732
|
|
|
1537
|
-
```
|
|
1538
|
-
|
|
1539
|
-
originalConfig: object // Raw config object
|
|
1540
|
-
variables: Variable[] // All variables found
|
|
1541
|
-
uniqueVariables: Record<string, Variable[]> // Variables grouped by name
|
|
1542
|
-
fileDependencies: string[] // File references
|
|
1543
|
-
}
|
|
1733
|
+
```javascript
|
|
1734
|
+
const model = await configorama.inspect('./config.yml')
|
|
1544
1735
|
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
variableName: string // Name/path (e.g., 'KEY')
|
|
1549
|
-
variablePath: string // Location in config (e.g., 'database.host')
|
|
1550
|
-
defaultValue: any // Default value if provided
|
|
1551
|
-
hasDefault: boolean // Whether default exists
|
|
1552
|
-
}
|
|
1736
|
+
console.log(model.requirements)
|
|
1737
|
+
console.log(model.graph)
|
|
1738
|
+
console.log(model.audit)
|
|
1553
1739
|
```
|
|
1554
1740
|
|
|
1555
|
-
|
|
1741
|
+
Use `view` for one projection:
|
|
1556
1742
|
|
|
1557
1743
|
```javascript
|
|
1558
1744
|
const configorama = require('configorama')
|
|
1559
1745
|
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
console.log(`File dependencies:`, analysis.fileDependencies)
|
|
1564
|
-
|
|
1565
|
-
// List all environment variables required
|
|
1566
|
-
const envVars = analysis.variables
|
|
1567
|
-
.filter(v => v.variableType === 'env' && !v.hasDefault)
|
|
1568
|
-
.map(v => v.variableName)
|
|
1569
|
-
|
|
1570
|
-
console.log('Required env vars:', envVars)
|
|
1746
|
+
const requirements = await configorama.inspect('./config.yml', { view: 'requirements' })
|
|
1747
|
+
const audit = await configorama.inspect('./config.yml', { view: 'audit' })
|
|
1748
|
+
const graph = await configorama.inspect('./config.yml', { view: 'graph', format: 'mermaid' })
|
|
1571
1749
|
```
|
|
1572
1750
|
|
|
1573
|
-
|
|
1574
|
-
- Generate documentation of required environment variables
|
|
1575
|
-
- Validate config structure in CI/CD
|
|
1576
|
-
- Build dependency graphs
|
|
1577
|
-
- Audit external dependencies before resolution
|
|
1751
|
+
The older `analyze()`, `introspect()`, `audit()`, and `graph()` helpers map to the same underlying inspection paths. They are kept for existing consumers.
|
|
1578
1752
|
|
|
1579
1753
|
---
|
|
1580
1754
|
|
|
@@ -1882,7 +2056,7 @@ const config = await configorama(configFile, {
|
|
|
1882
2056
|
|
|
1883
2057
|
| Option | Type | Default | Description |
|
|
1884
2058
|
|--------|------|---------|-------------|
|
|
1885
|
-
| `options` | `object` | `{}` | CLI options/flags to populate `${opt:xyz}` variables |
|
|
2059
|
+
| `options` | `object` | `{}` | CLI options/flags to populate `${option:xyz}` / `${opt:xyz}` variables |
|
|
1886
2060
|
| `syntax` | `string \| RegExp` | `${...}` | Custom variable syntax regex pattern |
|
|
1887
2061
|
| `configDir` | `string` | directory of config file | Working directory for relative file paths |
|
|
1888
2062
|
| `variableSources` | `VariableSource[]` | `[]` | Custom variable sources (see below) |
|
|
@@ -1918,7 +2092,7 @@ const config = await configorama(configFile, {
|
|
|
1918
2092
|
|
|
1919
2093
|
## Custom Variable Sources
|
|
1920
2094
|
|
|
1921
|
-
|
|
2095
|
+
Bring your own variable sources.
|
|
1922
2096
|
|
|
1923
2097
|
### Variable Source Types
|
|
1924
2098
|
|
|
@@ -1926,7 +2100,7 @@ The `source` property defines how the config wizard handles each variable type:
|
|
|
1926
2100
|
|
|
1927
2101
|
| Source | Description | Wizard Behavior | Examples |
|
|
1928
2102
|
|--------|-------------|-----------------|----------|
|
|
1929
|
-
| `'user'` | Values provided by user at runtime | Prompt user for value | `env`, `opt` |
|
|
2103
|
+
| `'user'` | Values provided by user at runtime | Prompt user for value | `env`, `option` / `opt` |
|
|
1930
2104
|
| `'config'` | Values from config files or self-references | Check existence, can create | `self`, `file`, `text` |
|
|
1931
2105
|
| `'remote'` | Values from external services | Fetch, prompt if missing, can write back | `ssm`, `vault`, `consul` |
|
|
1932
2106
|
| `'readonly'` | Computed or system-derived values | Display only, cannot modify | `git`, `cron`, `eval` |
|
|
@@ -1936,7 +2110,7 @@ The `source` property defines how the config wizard handles each variable type:
|
|
|
1936
2110
|
| Variable | Source Type | Description |
|
|
1937
2111
|
|----------|-------------|-------------|
|
|
1938
2112
|
| `${env:VAR}` | `user` | Environment variables |
|
|
1939
|
-
| `${opt:flag}` | `user` | CLI option flags |
|
|
2113
|
+
| `${option:flag}` / `${opt:flag}` | `user` | CLI option flags |
|
|
1940
2114
|
| `${param:key}` | `user` | Parameter values |
|
|
1941
2115
|
| `${self:key}` | `config` | Self references |
|
|
1942
2116
|
| `${file(path)}` | `config` | File references |
|
|
@@ -2065,9 +2239,166 @@ database:
|
|
|
2065
2239
|
|
|
2066
2240
|
---
|
|
2067
2241
|
|
|
2242
|
+
## Config Wizard (Experimental)
|
|
2243
|
+
|
|
2244
|
+
The config wizard walks you through every variable your config needs that isn't resolved yet, prompting for each one and then resolving the config with your answers.
|
|
2245
|
+
|
|
2246
|
+
Trigger it with the `--setup` flag, the `setup` subcommand, or the `setup` library option:
|
|
2247
|
+
|
|
2248
|
+
```bash
|
|
2249
|
+
configorama config.yml --setup
|
|
2250
|
+
configorama setup config.yml
|
|
2251
|
+
```
|
|
2252
|
+
|
|
2253
|
+
```javascript
|
|
2254
|
+
const config = await configorama('config.yml', { setup: true })
|
|
2255
|
+
```
|
|
2256
|
+
|
|
2257
|
+
The wizard groups unresolved variables by source and prompts for each:
|
|
2258
|
+
|
|
2259
|
+
- `${option:...}` / `${opt:...}` - CLI option flags (`opt` is shorthand)
|
|
2260
|
+
- `${env:...}` - environment variables (shows the current `process.env` value if set)
|
|
2261
|
+
- `${self:...}` and dot-prop references - values defined elsewhere in the config
|
|
2262
|
+
|
|
2263
|
+
Variables whose names look sensitive (`secret`, `password`, `token`, `key`, etc.) are prompted with a masked password input. Use comment annotations for new metadata, or annotate any variable with the backward-compatible [`help()` filter](#filters-experimental) to show guidance during the prompt:
|
|
2264
|
+
|
|
2265
|
+
```yaml
|
|
2266
|
+
# Stripe live secret key
|
|
2267
|
+
# @from Stripe dashboard > Developers > API keys
|
|
2268
|
+
# @sensitive true
|
|
2269
|
+
apiKey: ${env:API_KEY}
|
|
2270
|
+
|
|
2271
|
+
stage: ${opt:stage | help('Deployment stage, e.g. dev or prod')}
|
|
2272
|
+
```
|
|
2273
|
+
|
|
2274
|
+
The [Variable Source Types](#variable-source-types) table describes how the wizard treats each source.
|
|
2275
|
+
|
|
2276
|
+
> **Experimental:** the wizard fills in values for the current resolution run only. It does not write your answers back to the config file yet.
|
|
2277
|
+
|
|
2278
|
+
---
|
|
2279
|
+
|
|
2280
|
+
## Agent Requirements JSON
|
|
2281
|
+
|
|
2282
|
+
Use the requirements inspection view when an agent or script needs to know what a config is missing without running the full resolver:
|
|
2283
|
+
|
|
2284
|
+
```bash
|
|
2285
|
+
configorama inspect config.yml --view requirements
|
|
2286
|
+
```
|
|
2287
|
+
|
|
2288
|
+
The older `requirements` command and `--requirements` flag still work:
|
|
2289
|
+
|
|
2290
|
+
```bash
|
|
2291
|
+
configorama requirements config.yml
|
|
2292
|
+
configorama config.yml --requirements
|
|
2293
|
+
```
|
|
2294
|
+
|
|
2295
|
+
All three forms use analyze mode and print JSON. Missing required variables do not fail requirements output. The result is environment-aware: if `process.env.API_KEY` is already set, `${env:API_KEY}` is removed from `ask[]`.
|
|
2296
|
+
|
|
2297
|
+
```json
|
|
2298
|
+
{
|
|
2299
|
+
"schemaVersion": 1,
|
|
2300
|
+
"config": "config.yml",
|
|
2301
|
+
"summary": {
|
|
2302
|
+
"total": 2,
|
|
2303
|
+
"required": 1,
|
|
2304
|
+
"optional": 1,
|
|
2305
|
+
"sensitive": 1
|
|
2306
|
+
},
|
|
2307
|
+
"requirements": [
|
|
2308
|
+
{
|
|
2309
|
+
"name": "API_KEY",
|
|
2310
|
+
"variable": "env:API_KEY",
|
|
2311
|
+
"variableType": "env",
|
|
2312
|
+
"sourceClass": "user",
|
|
2313
|
+
"type": "string",
|
|
2314
|
+
"description": "Stripe live secret key",
|
|
2315
|
+
"descriptionSource": "commentTag",
|
|
2316
|
+
"allowedValues": null,
|
|
2317
|
+
"sensitive": true,
|
|
2318
|
+
"sensitiveSource": "commentTag",
|
|
2319
|
+
"required": true,
|
|
2320
|
+
"default": null,
|
|
2321
|
+
"defaultHint": "Set in CI or local shell profile",
|
|
2322
|
+
"obtainHint": "Stripe dashboard > Developers > API keys",
|
|
2323
|
+
"examples": ["sk_live_..."],
|
|
2324
|
+
"group": "Payments",
|
|
2325
|
+
"deprecationMessage": "Use STRIPE_RESTRICTED_KEY instead",
|
|
2326
|
+
"paths": ["apiKey"],
|
|
2327
|
+
"conflicts": []
|
|
2328
|
+
}
|
|
2329
|
+
],
|
|
2330
|
+
"ask": [
|
|
2331
|
+
{
|
|
2332
|
+
"name": "API_KEY",
|
|
2333
|
+
"variable": "env:API_KEY",
|
|
2334
|
+
"variableType": "env",
|
|
2335
|
+
"type": "string",
|
|
2336
|
+
"sensitive": true,
|
|
2337
|
+
"description": "Stripe live secret key",
|
|
2338
|
+
"obtainHint": "Stripe dashboard > Developers > API keys",
|
|
2339
|
+
"examples": ["sk_live_..."],
|
|
2340
|
+
"defaultHint": "Set in CI or local shell profile",
|
|
2341
|
+
"group": "Payments",
|
|
2342
|
+
"deprecationMessage": "Use STRIPE_RESTRICTED_KEY instead",
|
|
2343
|
+
"paths": ["apiKey"],
|
|
2344
|
+
"how": "Set environment variable API_KEY"
|
|
2345
|
+
}
|
|
2346
|
+
]
|
|
2347
|
+
}
|
|
2348
|
+
```
|
|
2349
|
+
|
|
2350
|
+
`variableType` uses normalized names. CLI flags are reported as `option` for both `${option:stage}` and the backward-compatible `${opt:stage}` shorthand. Concrete missing `${file(...)}` and `${text(...)}` dependencies appear in `ask[]`; dynamic paths such as `${file(./${option:stage}.yml)}` ask for the inner variables first.
|
|
2351
|
+
|
|
2352
|
+
---
|
|
2353
|
+
|
|
2354
|
+
## Runtime Inspection
|
|
2355
|
+
|
|
2356
|
+
`inspect` is the current CLI surface for pre-resolution analysis. It does not resolve missing user inputs. By default it returns the full model:
|
|
2357
|
+
|
|
2358
|
+
```bash
|
|
2359
|
+
configorama inspect config.yml
|
|
2360
|
+
```
|
|
2361
|
+
|
|
2362
|
+
```json
|
|
2363
|
+
{
|
|
2364
|
+
"schemaVersion": 1,
|
|
2365
|
+
"config": "config.yml",
|
|
2366
|
+
"requirements": {},
|
|
2367
|
+
"graph": {},
|
|
2368
|
+
"audit": {}
|
|
2369
|
+
}
|
|
2370
|
+
```
|
|
2371
|
+
|
|
2372
|
+
Use `--view` when a script wants one projection:
|
|
2373
|
+
|
|
2374
|
+
```bash
|
|
2375
|
+
# Required inputs and ask[] contract
|
|
2376
|
+
configorama inspect config.yml --view requirements
|
|
2377
|
+
|
|
2378
|
+
# Static risk report for executable or external surfaces
|
|
2379
|
+
configorama inspect config.yml --view audit
|
|
2380
|
+
|
|
2381
|
+
# Dependency graph as JSON, Mermaid, or Graphviz DOT
|
|
2382
|
+
configorama inspect config.yml --view graph --format json
|
|
2383
|
+
configorama inspect config.yml --view graph --format mermaid
|
|
2384
|
+
configorama inspect config.yml --view graph --format dot
|
|
2385
|
+
```
|
|
2386
|
+
|
|
2387
|
+
Inspection commands default to safe mode, which blocks executable and config-mutating surfaces during analysis. Use `--unsafe` only when you intentionally want the resolver to execute those surfaces during inspection. Use `--safe-root <dir>` or `--file-root <dir>` to restrict file and text references to known directories.
|
|
2388
|
+
|
|
2389
|
+
Agents can discover the CLI contract without scraping this README:
|
|
2390
|
+
|
|
2391
|
+
```bash
|
|
2392
|
+
configorama capabilities
|
|
2393
|
+
```
|
|
2394
|
+
|
|
2395
|
+
That command prints JSON containing the documented commands, compatibility aliases, views, output formats, flags, error codes, and exit codes.
|
|
2396
|
+
|
|
2397
|
+
---
|
|
2398
|
+
|
|
2068
2399
|
## CLI Usage
|
|
2069
2400
|
|
|
2070
|
-
Configorama includes a CLI tool for resolving configs
|
|
2401
|
+
Configorama includes a CLI tool for resolving configs, extracting paths, and inspecting config requirements before resolution.
|
|
2071
2402
|
|
|
2072
2403
|
### Basic Commands
|
|
2073
2404
|
|
|
@@ -2087,6 +2418,29 @@ configorama config.yml --info
|
|
|
2087
2418
|
# Verify config (check for errors without resolving)
|
|
2088
2419
|
configorama config.yml --verify
|
|
2089
2420
|
|
|
2421
|
+
# Walk through unresolved variables interactively (experimental)
|
|
2422
|
+
configorama config.yml --setup
|
|
2423
|
+
configorama setup config.yml
|
|
2424
|
+
|
|
2425
|
+
# Inspect requirements, graph, and audit together
|
|
2426
|
+
configorama inspect config.yml
|
|
2427
|
+
|
|
2428
|
+
# Print agent requirements JSON without resolving missing values
|
|
2429
|
+
configorama inspect config.yml --view requirements
|
|
2430
|
+
|
|
2431
|
+
# Compatibility forms for requirements JSON
|
|
2432
|
+
configorama requirements config.yml
|
|
2433
|
+
configorama config.yml --requirements
|
|
2434
|
+
|
|
2435
|
+
# Audit risky references without resolving missing values
|
|
2436
|
+
configorama inspect config.yml --view audit
|
|
2437
|
+
|
|
2438
|
+
# Print a dependency graph
|
|
2439
|
+
configorama inspect config.yml --view graph --format mermaid
|
|
2440
|
+
|
|
2441
|
+
# Print the machine-readable CLI contract
|
|
2442
|
+
configorama capabilities
|
|
2443
|
+
|
|
2090
2444
|
# Extract a specific path from config
|
|
2091
2445
|
configorama config.yml .database.host
|
|
2092
2446
|
|
|
@@ -2105,20 +2459,37 @@ configorama config.yml --format yaml
|
|
|
2105
2459
|
```text
|
|
2106
2460
|
Usage:
|
|
2107
2461
|
configorama [options] <file> [path]
|
|
2462
|
+
configorama <command> <file> [options]
|
|
2463
|
+
|
|
2464
|
+
Commands:
|
|
2465
|
+
(default) Resolve <file> and print the result
|
|
2466
|
+
inspect <file> Introspect a config without resolving it
|
|
2467
|
+
--view requirements|audit|graph for one slice
|
|
2468
|
+
setup <file> Run the interactive config wizard (experimental)
|
|
2469
|
+
capabilities Print the machine-readable CLI contract as JSON
|
|
2108
2470
|
|
|
2109
2471
|
Options:
|
|
2110
2472
|
-h, --help Show this help message
|
|
2111
2473
|
-v, --version Show version number
|
|
2112
2474
|
-o, --output <file> Write output to file instead of stdout
|
|
2113
|
-
-f, --format <format> Output format: json, yaml,
|
|
2475
|
+
-f, --format <format> Output format: json, yaml, js (resolve);
|
|
2476
|
+
json, mermaid, dot (graph)
|
|
2477
|
+
--view <view> inspect view: requirements, audit, or graph
|
|
2114
2478
|
-r, --raw Print extracted scalar values without JSON quoting
|
|
2115
2479
|
-c, --copy Copy the formatted output to the clipboard
|
|
2116
2480
|
-d, --debug Enable debug mode
|
|
2117
2481
|
-i, --info Show info about the config
|
|
2118
2482
|
-V, --verify Verify the config
|
|
2483
|
+
--safe Block executable/config-mutating surfaces during resolution
|
|
2484
|
+
--unsafe Disable inspect/audit/graph default safe inspection
|
|
2485
|
+
--safe-root <dir> Restrict file/text references to an allowed root
|
|
2486
|
+
--file-root <dir> Alias for --safe-root
|
|
2487
|
+
--error-format <fmt> Error format on stderr: json or human
|
|
2119
2488
|
--param <key=value> Pass parameter values (can be used multiple times)
|
|
2120
2489
|
--allow-unknown Allow unknown variables to pass through
|
|
2121
2490
|
--allow-undefined Allow undefined values in the final output
|
|
2491
|
+
--setup Alias for configorama setup <file>
|
|
2492
|
+
--requirements Compatibility form for requirements JSON
|
|
2122
2493
|
|
|
2123
2494
|
Path Extraction:
|
|
2124
2495
|
configorama config.yml .database.host Extract a nested value
|
|
@@ -2139,6 +2510,8 @@ configorama config.yml .stage --raw
|
|
|
2139
2510
|
|
|
2140
2511
|
`--copy` copies exactly the formatted value that the CLI prints. It uses native clipboard commands where available: `pbcopy` on macOS, `clip` on Windows, and `wl-copy`, `xclip`, or `xsel` on Linux.
|
|
2141
2512
|
|
|
2513
|
+
Structured commands (`inspect`, `requirements`, `audit`, `graph`, and `capabilities`) default to JSON errors on stderr. Use `--error-format human` for boxed terminal errors, or `--error-format json` to force machine-readable errors in resolve mode.
|
|
2514
|
+
|
|
2142
2515
|
### CLI Examples
|
|
2143
2516
|
|
|
2144
2517
|
**Basic resolution:**
|
|
@@ -2424,73 +2797,6 @@ serverless deploy --stage prod --region us-west-2
|
|
|
2424
2797
|
|
|
2425
2798
|
---
|
|
2426
2799
|
|
|
2427
|
-
### Docker Deployment
|
|
2428
|
-
|
|
2429
|
-
**Dockerfile:**
|
|
2430
|
-
|
|
2431
|
-
```dockerfile
|
|
2432
|
-
FROM node:22-alpine
|
|
2433
|
-
|
|
2434
|
-
WORKDIR /app
|
|
2435
|
-
|
|
2436
|
-
# Copy package files
|
|
2437
|
-
COPY package*.json ./
|
|
2438
|
-
|
|
2439
|
-
# Install dependencies
|
|
2440
|
-
RUN npm ci --only=production
|
|
2441
|
-
|
|
2442
|
-
# Copy application files
|
|
2443
|
-
COPY . .
|
|
2444
|
-
|
|
2445
|
-
# Set environment variables (can be overridden at runtime)
|
|
2446
|
-
ENV NODE_ENV=production
|
|
2447
|
-
ENV STAGE=prod
|
|
2448
|
-
|
|
2449
|
-
# Run config resolution at build time (optional)
|
|
2450
|
-
# RUN node -e "require('configorama').sync('./config.yml', { options: { stage: process.env.STAGE } })"
|
|
2451
|
-
|
|
2452
|
-
CMD ["node", "index.js"]
|
|
2453
|
-
```
|
|
2454
|
-
|
|
2455
|
-
**docker-compose.yml:**
|
|
2456
|
-
|
|
2457
|
-
```yaml
|
|
2458
|
-
version: '3.8'
|
|
2459
|
-
|
|
2460
|
-
services:
|
|
2461
|
-
app:
|
|
2462
|
-
build: .
|
|
2463
|
-
environment:
|
|
2464
|
-
- NODE_ENV=production
|
|
2465
|
-
- STAGE=prod
|
|
2466
|
-
- DB_HOST=postgres
|
|
2467
|
-
- DB_PASSWORD=${DB_PASSWORD}
|
|
2468
|
-
- API_KEY=${API_KEY}
|
|
2469
|
-
depends_on:
|
|
2470
|
-
- postgres
|
|
2471
|
-
|
|
2472
|
-
postgres:
|
|
2473
|
-
image: postgres:15
|
|
2474
|
-
environment:
|
|
2475
|
-
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
2476
|
-
```
|
|
2477
|
-
|
|
2478
|
-
**Usage:**
|
|
2479
|
-
|
|
2480
|
-
```bash
|
|
2481
|
-
# Build
|
|
2482
|
-
docker build -t myapp .
|
|
2483
|
-
|
|
2484
|
-
# Run with environment variables
|
|
2485
|
-
docker run \
|
|
2486
|
-
-e DB_HOST=mydb.example.com \
|
|
2487
|
-
-e DB_PASSWORD=secret \
|
|
2488
|
-
-e API_KEY=abc123 \
|
|
2489
|
-
myapp
|
|
2490
|
-
```
|
|
2491
|
-
|
|
2492
|
-
---
|
|
2493
|
-
|
|
2494
2800
|
### CI/CD Integration
|
|
2495
2801
|
|
|
2496
2802
|
**GitHub Actions example (.github/workflows/deploy.yml):**
|
|
@@ -2776,7 +3082,7 @@ Configorama detects circular dependencies and throws a helpful error instead of
|
|
|
2776
3082
|
|
|
2777
3083
|
**Q: Why should I use this?**
|
|
2778
3084
|
|
|
2779
|
-
|
|
3085
|
+
Configs resolve fresh on every run, so values stay in sync with your environment variables, CLI flags, file contents, and custom sources instead of going stale.
|
|
2780
3086
|
|
|
2781
3087
|
---
|
|
2782
3088
|
|