@push.rocks/smartconfig 6.0.1 → 6.1.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/.smartconfig.json +13 -7
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/license +21 -0
- package/package.json +10 -6
- package/readme.md +195 -390
- package/ts/00_commitinfo_data.ts +1 -1
package/.smartconfig.json
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"npmci": {
|
|
3
|
-
"globalNpmTools": [],
|
|
4
|
-
"npmAccessLevel": "public"
|
|
5
|
-
},
|
|
6
2
|
"npmts": {
|
|
7
3
|
"testConfig": {
|
|
8
4
|
"parallel": false
|
|
9
5
|
}
|
|
10
6
|
},
|
|
11
|
-
"
|
|
7
|
+
"@git.zone/cli": {
|
|
12
8
|
"projectType": "npm",
|
|
13
9
|
"module": {
|
|
14
10
|
"githost": "code.foss.global",
|
|
@@ -34,9 +30,19 @@
|
|
|
34
30
|
"workflow improvement",
|
|
35
31
|
"persistent storage"
|
|
36
32
|
]
|
|
33
|
+
},
|
|
34
|
+
"release": {
|
|
35
|
+
"registries": [
|
|
36
|
+
"https://verdaccio.lossless.digital",
|
|
37
|
+
"https://registry.npmjs.org"
|
|
38
|
+
],
|
|
39
|
+
"accessLevel": "public"
|
|
37
40
|
}
|
|
38
41
|
},
|
|
39
|
-
"tsdoc": {
|
|
42
|
+
"@git.zone/tsdoc": {
|
|
40
43
|
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
|
44
|
+
},
|
|
45
|
+
"@ship.zone/szci": {
|
|
46
|
+
"globalNpmTools": []
|
|
41
47
|
}
|
|
42
|
-
}
|
|
48
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartconfig',
|
|
6
|
-
version: '6.0
|
|
6
|
+
version: '6.1.0',
|
|
7
7
|
description: 'A comprehensive configuration management library providing key-value storage, environment variable mapping, and tool configuration.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx5QkFBeUI7SUFDL0IsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHFJQUFxSTtDQUNuSixDQUFBIn0=
|
package/license
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Task Venture Capital GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartconfig",
|
|
3
|
-
"version": "6.0
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A comprehensive configuration management library providing key-value storage, environment variable mapping, and tool configuration.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
7
7
|
"typings": "dist_ts/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "(tstest test/ --verbose --logfile --timeout 20)",
|
|
10
|
+
"build": "(tsbuild --web --allowimplicitany)",
|
|
11
|
+
"buildDocs": "tsdoc"
|
|
12
|
+
},
|
|
8
13
|
"repository": {
|
|
9
14
|
"type": "git",
|
|
10
15
|
"url": "https://code.foss.global/push.rocks/smartconfig.git"
|
|
@@ -65,9 +70,8 @@
|
|
|
65
70
|
"workflow improvement",
|
|
66
71
|
"persistent storage"
|
|
67
72
|
],
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
"buildDocs": "tsdoc"
|
|
73
|
+
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977",
|
|
74
|
+
"pnpm": {
|
|
75
|
+
"overrides": {}
|
|
72
76
|
}
|
|
73
|
-
}
|
|
77
|
+
}
|
package/readme.md
CHANGED
|
@@ -1,517 +1,322 @@
|
|
|
1
1
|
# @push.rocks/smartconfig 🚀
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A comprehensive TypeScript configuration management library providing centralized tool configs, persistent key-value storage, and powerful environment variable mapping with automatic type conversions.
|
|
4
|
+
|
|
5
|
+
## Issue Reporting and Security
|
|
6
|
+
|
|
7
|
+
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
|
4
8
|
|
|
5
9
|
## Install 📦
|
|
6
10
|
|
|
7
11
|
```bash
|
|
8
|
-
# Using npm
|
|
9
12
|
npm install @push.rocks/smartconfig --save
|
|
10
|
-
|
|
11
|
-
# Using pnpm (recommended)
|
|
13
|
+
# or
|
|
12
14
|
pnpm add @push.rocks/smartconfig
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
##
|
|
17
|
+
## Quick Start ⚡
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { Smartconfig, AppData, KeyValueStore } from '@push.rocks/smartconfig';
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
// 1. Read tool config from .smartconfig.json
|
|
23
|
+
const sc = new Smartconfig();
|
|
24
|
+
const eslintOpts = sc.dataFor('eslint', { extends: 'standard' });
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
// 2. Map env vars to typed config (with auto-conversion)
|
|
27
|
+
const appData = await AppData.createAndInit<{ port: number; debug: boolean }>({
|
|
28
|
+
envMapping: {
|
|
29
|
+
port: 'PORT',
|
|
30
|
+
debug: 'boolean:DEBUG',
|
|
31
|
+
},
|
|
32
|
+
});
|
|
20
33
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- **⚡ Zero Config**: Works out of the box with sensible defaults
|
|
26
|
-
- **🔄 Reactive**: Built-in change detection and observables
|
|
34
|
+
// 3. Persist data between runs
|
|
35
|
+
const kv = new KeyValueStore({ typeArg: 'userHomeDir', identityArg: 'myapp' });
|
|
36
|
+
await kv.writeKey('lastRun', Date.now());
|
|
37
|
+
```
|
|
27
38
|
|
|
28
|
-
## Core
|
|
39
|
+
## Three Core Classes 🏗️
|
|
29
40
|
|
|
30
|
-
### 1. Smartconfig Configuration
|
|
41
|
+
### 1. `Smartconfig` — Centralized Tool Configuration
|
|
31
42
|
|
|
32
|
-
|
|
43
|
+
Reads a `.smartconfig.json` file from a project directory and merges its contents with your defaults. One file, every tool.
|
|
33
44
|
|
|
34
45
|
```typescript
|
|
35
46
|
import { Smartconfig } from '@push.rocks/smartconfig';
|
|
36
47
|
|
|
37
|
-
|
|
38
|
-
const
|
|
48
|
+
const sc = new Smartconfig(); // uses cwd
|
|
49
|
+
const sc2 = new Smartconfig('/my/project'); // or specify a path
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Get merged configuration for any tool
|
|
44
|
-
const eslintConfig = smartconfig.dataFor<EslintConfig>('eslint', {
|
|
45
|
-
// Default values if not in .smartconfig.json
|
|
46
|
-
extends: 'standard',
|
|
47
|
-
rules: {}
|
|
51
|
+
const prettierConfig = sc.dataFor<PrettierConfig>('prettier', {
|
|
52
|
+
semi: false,
|
|
53
|
+
singleQuote: true,
|
|
48
54
|
});
|
|
49
55
|
```
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
**`.smartconfig.json`** example:
|
|
58
|
+
|
|
52
59
|
```json
|
|
53
60
|
{
|
|
54
|
-
"eslint": {
|
|
55
|
-
"extends": "@company/eslint-config",
|
|
56
|
-
"rules": {
|
|
57
|
-
"no-console": "warn"
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
61
|
"prettier": {
|
|
61
|
-
"semi":
|
|
62
|
-
"
|
|
62
|
+
"semi": true,
|
|
63
|
+
"printWidth": 120
|
|
64
|
+
},
|
|
65
|
+
"eslint": {
|
|
66
|
+
"extends": "@company/eslint-config"
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
```
|
|
66
70
|
|
|
67
|
-
|
|
71
|
+
Values from the file override the defaults you pass in. Missing keys fall back to your defaults.
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
**Properties:**
|
|
74
|
+
- `smartconfigJsonExists: boolean` — whether `.smartconfig.json` was found
|
|
75
|
+
- `smartconfigJsonData: any` — the parsed JSON contents
|
|
76
|
+
|
|
77
|
+
**Methods:**
|
|
78
|
+
- `dataFor<T>(toolName: string, defaults: T): T` — returns merged config
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
### 2. `KeyValueStore<T>` — Persistent Data Storage
|
|
83
|
+
|
|
84
|
+
A generic, typed key-value store that persists JSON to disk (or stays in-memory for tests). Supports change detection via RxJS observables.
|
|
70
85
|
|
|
71
86
|
```typescript
|
|
72
87
|
import { KeyValueStore } from '@push.rocks/smartconfig';
|
|
73
88
|
|
|
74
|
-
interface
|
|
89
|
+
interface Settings {
|
|
75
90
|
username: string;
|
|
76
|
-
|
|
77
|
-
preferences: {
|
|
78
|
-
theme: 'light' | 'dark';
|
|
79
|
-
notifications: boolean;
|
|
80
|
-
};
|
|
91
|
+
theme: 'light' | 'dark';
|
|
81
92
|
}
|
|
82
93
|
|
|
83
|
-
//
|
|
84
|
-
const
|
|
85
|
-
typeArg: 'userHomeDir',
|
|
94
|
+
// Store in ~/.smartconfig/kv/
|
|
95
|
+
const kv = new KeyValueStore<Settings>({
|
|
96
|
+
typeArg: 'userHomeDir',
|
|
86
97
|
identityArg: 'myApp',
|
|
87
|
-
mandatoryKeys: ['username',
|
|
98
|
+
mandatoryKeys: ['username'],
|
|
88
99
|
});
|
|
89
100
|
|
|
90
|
-
|
|
91
|
-
await
|
|
92
|
-
await kvStore.writeKey('preferences', {
|
|
93
|
-
theme: 'dark',
|
|
94
|
-
notifications: true
|
|
95
|
-
});
|
|
101
|
+
await kv.writeKey('username', 'jane');
|
|
102
|
+
await kv.writeKey('theme', 'dark');
|
|
96
103
|
|
|
97
|
-
//
|
|
98
|
-
const
|
|
99
|
-
const allData = await kvStore.readAll();
|
|
104
|
+
const user = await kv.readKey('username'); // 'jane'
|
|
105
|
+
const all = await kv.readAll(); // { username: 'jane', theme: 'dark' }
|
|
100
106
|
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
if (missingKeys.length > 0) {
|
|
104
|
-
console.log('Missing required configuration:', missingKeys);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Wait for keys to be present
|
|
108
|
-
await kvStore.waitForKeysPresent(['apiKey']);
|
|
107
|
+
// React to changes
|
|
108
|
+
kv.changeSubject.subscribe((data) => console.log('changed:', data));
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
**Storage
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
**Storage types:**
|
|
112
|
+
|
|
113
|
+
| `typeArg` | Where it goes | Use case |
|
|
114
|
+
|-----------|--------------|----------|
|
|
115
|
+
| `'userHomeDir'` | `~/.smartconfig/kv/<identity>.json` | CLI tools, per-user state |
|
|
116
|
+
| `'custom'` | Your path (file or directory) | App data, project-local state |
|
|
117
|
+
| `'ephemeral'` | Memory only — nothing on disk | Tests |
|
|
118
|
+
|
|
119
|
+
**Methods:**
|
|
120
|
+
|
|
121
|
+
| Method | Description |
|
|
122
|
+
|--------|------------|
|
|
123
|
+
| `readKey(key)` | Read a single value |
|
|
124
|
+
| `writeKey(key, value)` | Write a single value |
|
|
125
|
+
| `readAll()` | Read everything |
|
|
126
|
+
| `writeAll(obj)` | Merge an object into the store |
|
|
127
|
+
| `deleteKey(key)` | Remove a key |
|
|
128
|
+
| `reset()` | Wipe all keys (synced to disk) |
|
|
129
|
+
| `wipe()` | Delete the backing file entirely |
|
|
130
|
+
| `getMissingMandatoryKeys()` | Returns keys declared mandatory but not yet set |
|
|
131
|
+
| `waitForKeysPresent(keys)` | Returns a Promise that resolves once all listed keys exist |
|
|
132
|
+
| `waitForAndGetKey(key)` | Waits for a key, then returns its value |
|
|
133
|
+
|
|
134
|
+
---
|
|
115
135
|
|
|
116
|
-
### 3. AppData
|
|
136
|
+
### 3. `AppData<T>` — Environment Variable Mapping 🌟
|
|
117
137
|
|
|
118
|
-
The
|
|
138
|
+
The flagship class. Maps environment variables (or hardcoded values) into a typed config object with automatic type conversions, nested object support, and smart storage path selection.
|
|
119
139
|
|
|
120
140
|
```typescript
|
|
121
141
|
import { AppData } from '@push.rocks/smartconfig';
|
|
122
142
|
|
|
123
|
-
interface
|
|
143
|
+
interface Config {
|
|
124
144
|
apiUrl: string;
|
|
125
145
|
apiKey: string;
|
|
126
|
-
port: number;
|
|
127
146
|
features: {
|
|
128
147
|
analytics: boolean;
|
|
129
148
|
payment: boolean;
|
|
130
149
|
};
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
host: string;
|
|
135
|
-
password: string;
|
|
136
|
-
};
|
|
150
|
+
redis: {
|
|
151
|
+
host: string;
|
|
152
|
+
password: string;
|
|
137
153
|
};
|
|
138
154
|
}
|
|
139
155
|
|
|
140
|
-
const appData = await AppData.createAndInit<
|
|
141
|
-
|
|
142
|
-
requiredKeys: ['apiKey', 'apiUrl'],
|
|
156
|
+
const appData = await AppData.createAndInit<Config>({
|
|
157
|
+
requiredKeys: ['apiKey'],
|
|
143
158
|
envMapping: {
|
|
144
|
-
apiUrl: 'API_URL',
|
|
145
|
-
apiKey: 'hard:
|
|
146
|
-
port: 'hard:3000', // Hardcoded number
|
|
159
|
+
apiUrl: 'API_URL', // plain env var
|
|
160
|
+
apiKey: 'hard:dev-key-123', // hardcoded fallback
|
|
147
161
|
features: {
|
|
148
|
-
analytics: 'boolean:ENABLE_ANALYTICS', //
|
|
149
|
-
payment: 'hard_boolean:true'
|
|
162
|
+
analytics: 'boolean:ENABLE_ANALYTICS', // converts "true"/"false" → boolean
|
|
163
|
+
payment: 'hard_boolean:true', // hardcoded boolean
|
|
164
|
+
},
|
|
165
|
+
redis: {
|
|
166
|
+
host: 'REDIS_HOST',
|
|
167
|
+
password: 'base64:REDIS_PASSWORD_B64', // base64-decode at load time
|
|
150
168
|
},
|
|
151
|
-
cache: {
|
|
152
|
-
ttl: 'json:CACHE_CONFIG', // Parse JSON from env var
|
|
153
|
-
redis: {
|
|
154
|
-
host: 'REDIS_HOST',
|
|
155
|
-
password: 'base64:REDIS_PASSWORD_B64' // Decode base64
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
169
|
},
|
|
159
170
|
overwriteObject: {
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
}
|
|
171
|
+
apiUrl: 'http://localhost:3000', // force override after env mapping
|
|
172
|
+
},
|
|
163
173
|
});
|
|
164
174
|
|
|
165
175
|
const store = await appData.getKvStore();
|
|
166
|
-
const
|
|
176
|
+
const url = await store.readKey('apiUrl');
|
|
167
177
|
```
|
|
168
178
|
|
|
169
|
-
|
|
179
|
+
#### Mapping Prefixes
|
|
170
180
|
|
|
171
|
-
|
|
181
|
+
| Prefix | What it does | Example mapping | Result |
|
|
182
|
+
|--------|-------------|-----------------|--------|
|
|
183
|
+
| *(none)* | Raw env var as string | `'MY_VAR'` | `process.env.MY_VAR` |
|
|
184
|
+
| `hard:` | Hardcoded string | `'hard:hello'` | `"hello"` |
|
|
185
|
+
| `boolean:` | Env var → `true`/`false` | `'boolean:FLAG'` | `true` or `false` |
|
|
186
|
+
| `json:` | Env var → `JSON.parse()` | `'json:CONFIG'` | parsed object |
|
|
187
|
+
| `base64:` | Env var → base64 decode | `'base64:SECRET'` | decoded string |
|
|
188
|
+
| `hard_boolean:` | Hardcoded boolean | `'hard_boolean:false'` | `false` |
|
|
189
|
+
| `hard_json:` | Hardcoded JSON | `'hard_json:{"a":1}'` | `{ a: 1 }` |
|
|
190
|
+
| `hard_base64:` | Hardcoded base64 | `'hard_base64:SGVsbG8='` | `"Hello"` |
|
|
172
191
|
|
|
173
|
-
|
|
192
|
+
Suffix detection also works: a mapping ending in `_JSON` or `_BASE64` triggers the corresponding transform automatically.
|
|
174
193
|
|
|
175
|
-
|
|
176
|
-
|--------|-------------|---------|--------|
|
|
177
|
-
| `hard:` | Hardcoded value | `hard:myvalue` | `"myvalue"` |
|
|
178
|
-
| `hard_boolean:` | Hardcoded boolean | `hard_boolean:true` | `true` |
|
|
179
|
-
| `hard_json:` | Hardcoded JSON | `hard_json:{"key":"value"}` | `{key: "value"}` |
|
|
180
|
-
| `hard_base64:` | Hardcoded base64 | `hard_base64:SGVsbG8=` | `"Hello"` |
|
|
181
|
-
| `boolean:` | Env var as boolean | `boolean:FEATURE_FLAG` | `true/false` |
|
|
182
|
-
| `json:` | Parse env var as JSON | `json:CONFIG_JSON` | Parsed object |
|
|
183
|
-
| `base64:` | Decode env var from base64 | `base64:SECRET_B64` | Decoded string |
|
|
194
|
+
#### Boolean Conversion Rules
|
|
184
195
|
|
|
185
|
-
|
|
196
|
+
The `boolean:` prefix (and `hard_boolean:`) recognizes:
|
|
186
197
|
|
|
187
|
-
|
|
198
|
+
- **true**: `"true"`, `"1"`, `"yes"`, `"y"`, `"on"` (case-insensitive)
|
|
199
|
+
- **false**: `"false"`, `"0"`, `"no"`, `"n"`, `"off"` (case-insensitive)
|
|
188
200
|
|
|
189
|
-
|
|
190
|
-
{
|
|
191
|
-
envMapping: {
|
|
192
|
-
// Automatically parsed as JSON if MY_CONFIG_JSON="{"enabled":true}"
|
|
193
|
-
config: 'MY_CONFIG_JSON',
|
|
194
|
-
|
|
195
|
-
// Automatically decoded from base64 if SECRET_KEY_BASE64="SGVsbG8="
|
|
196
|
-
secret: 'SECRET_KEY_BASE64'
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
```
|
|
201
|
+
#### Nested Objects
|
|
200
202
|
|
|
201
|
-
|
|
203
|
+
Mapping values can be objects — they are resolved recursively:
|
|
202
204
|
|
|
203
205
|
```typescript
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// Environment variables with conversion
|
|
215
|
-
features: 'json:FEATURE_FLAGS', // Expects: {"feature1":true,"feature2":false}
|
|
216
|
-
isProduction: 'boolean:IS_PROD', // Expects: "true" or "false"
|
|
217
|
-
apiSecret: 'base64:API_SECRET', // Expects: base64 encoded string
|
|
218
|
-
|
|
219
|
-
// Nested structures
|
|
220
|
-
database: {
|
|
221
|
-
host: 'DB_HOST',
|
|
222
|
-
port: 'hard:5432',
|
|
223
|
-
credentials: {
|
|
224
|
-
user: 'DB_USER',
|
|
225
|
-
password: 'base64:DB_PASSWORD_ENCODED',
|
|
226
|
-
ssl: 'boolean:DB_USE_SSL'
|
|
227
|
-
}
|
|
228
|
-
}
|
|
206
|
+
envMapping: {
|
|
207
|
+
database: {
|
|
208
|
+
host: 'DB_HOST',
|
|
209
|
+
port: 'hard:5432',
|
|
210
|
+
credentials: {
|
|
211
|
+
user: 'DB_USER',
|
|
212
|
+
password: 'base64:DB_PASS_B64',
|
|
213
|
+
ssl: 'boolean:DB_SSL',
|
|
214
|
+
},
|
|
229
215
|
},
|
|
230
|
-
|
|
231
|
-
// Override any env mappings
|
|
232
|
-
overwriteObject: {
|
|
233
|
-
debugMode: true, // Force debug mode regardless of env
|
|
234
|
-
database: {
|
|
235
|
-
host: 'localhost' // Force localhost for development
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### Boolean Conversion Rules
|
|
242
|
-
|
|
243
|
-
AppData intelligently handles boolean conversions:
|
|
244
|
-
|
|
245
|
-
1. **String "true"/"false"**: Converted to boolean
|
|
246
|
-
2. **With `boolean:` prefix**: Any env var value is converted (`"true"` → `true`, anything else → `false`)
|
|
247
|
-
3. **With `hard_boolean:` prefix**: Hardcoded boolean value
|
|
248
|
-
4. **Regular env vars**: Strings remain strings unless prefixed
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
// Environment: FEATURE_A="true", FEATURE_B="yes", FEATURE_C="1"
|
|
252
|
-
{
|
|
253
|
-
envMapping: {
|
|
254
|
-
featureA: 'FEATURE_A', // Result: "true" (string)
|
|
255
|
-
featureB: 'boolean:FEATURE_B', // Result: false (only "true" → true)
|
|
256
|
-
featureC: 'boolean:FEATURE_C', // Result: false (only "true" → true)
|
|
257
|
-
featureD: 'hard_boolean:true' // Result: true (hardcoded)
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
### Static Helper Functions
|
|
263
|
-
|
|
264
|
-
AppData provides convenient static methods for directly accessing and converting environment variables without creating an instance:
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
import { AppData } from '@push.rocks/smartconfig';
|
|
268
|
-
|
|
269
|
-
// Get environment variable as boolean
|
|
270
|
-
const isEnabled = await AppData.valueAsBoolean('FEATURE_ENABLED');
|
|
271
|
-
// Returns: true if "true", false otherwise
|
|
272
|
-
|
|
273
|
-
// Get environment variable as parsed JSON
|
|
274
|
-
interface Config {
|
|
275
|
-
timeout: number;
|
|
276
|
-
retries: number;
|
|
277
216
|
}
|
|
278
|
-
const config = await AppData.valueAsJson<Config>('SERVICE_CONFIG');
|
|
279
|
-
// Returns: Parsed object or undefined
|
|
280
|
-
|
|
281
|
-
// Get environment variable as base64 decoded string
|
|
282
|
-
const secret = await AppData.valueAsBase64('ENCODED_SECRET');
|
|
283
|
-
// Returns: Decoded string or undefined
|
|
284
|
-
|
|
285
|
-
// Get environment variable as string
|
|
286
|
-
const apiUrl = await AppData.valueAsString('API_URL');
|
|
287
|
-
// Returns: String value or undefined
|
|
288
|
-
|
|
289
|
-
// Get environment variable as number
|
|
290
|
-
const port = await AppData.valueAsNumber('PORT');
|
|
291
|
-
// Returns: Number value or undefined
|
|
292
217
|
```
|
|
293
218
|
|
|
294
|
-
|
|
295
|
-
- Quick environment variable access without setup
|
|
296
|
-
- Simple type conversions in utility functions
|
|
297
|
-
- One-off configuration checks
|
|
298
|
-
- Scenarios where you don't need the full AppData instance
|
|
299
|
-
|
|
300
|
-
## Advanced Patterns 🎨
|
|
301
|
-
|
|
302
|
-
### Reactive Configuration
|
|
219
|
+
#### Smart Storage Path
|
|
303
220
|
|
|
304
|
-
|
|
221
|
+
When no `dirPath` is specified, AppData auto-selects:
|
|
305
222
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
identityArg: 'myApp'
|
|
310
|
-
});
|
|
223
|
+
1. `/app/data` — if it exists (containers)
|
|
224
|
+
2. `/data` — if it exists (alternate container path)
|
|
225
|
+
3. `.nogit/appdata` — local dev fallback
|
|
311
226
|
|
|
312
|
-
|
|
313
|
-
kvStore.changeSubject.subscribe((newData) => {
|
|
314
|
-
console.log('Configuration changed:', newData);
|
|
315
|
-
});
|
|
227
|
+
Or pass `ephemeral: true` for zero disk I/O (great for tests).
|
|
316
228
|
|
|
317
|
-
|
|
318
|
-
await kvStore.writeKey('theme', 'dark');
|
|
319
|
-
```
|
|
229
|
+
#### Static Helpers
|
|
320
230
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
Perfect for unit tests - no file system pollution:
|
|
231
|
+
Quick one-shot env var reads without creating an AppData instance:
|
|
324
232
|
|
|
325
233
|
```typescript
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
// All operations work normally, but nothing persists to disk
|
|
332
|
-
await testStore.writeKey('testKey', 'testValue');
|
|
234
|
+
const isEnabled = await AppData.valueAsBoolean('FEATURE_FLAG');
|
|
235
|
+
const config = await AppData.valueAsJson<MyType>('CONFIG_JSON');
|
|
236
|
+
const secret = await AppData.valueAsBase64('ENCODED_SECRET');
|
|
237
|
+
const url = await AppData.valueAsString('API_URL');
|
|
238
|
+
const port = await AppData.valueAsNumber('PORT');
|
|
333
239
|
```
|
|
334
240
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
AppData automatically selects the best storage location:
|
|
241
|
+
#### Instance Methods
|
|
338
242
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
243
|
+
| Method | Description |
|
|
244
|
+
|--------|------------|
|
|
245
|
+
| `getKvStore()` | Returns the underlying `KeyValueStore<T>` |
|
|
246
|
+
| `logMissingKeys()` | Logs and returns any required keys that are missing |
|
|
247
|
+
| `waitForAndGetKey(key)` | Blocks until a key is present, then returns it |
|
|
342
248
|
|
|
343
|
-
|
|
344
|
-
// Automatic path selection
|
|
345
|
-
const appData = await AppData.createAndInit({
|
|
346
|
-
// No dirPath specified - smart detection
|
|
347
|
-
requiredKeys: ['apiKey']
|
|
348
|
-
});
|
|
249
|
+
---
|
|
349
250
|
|
|
350
|
-
|
|
351
|
-
const testData = await AppData.createAndInit({
|
|
352
|
-
ephemeral: true, // No disk persistence
|
|
353
|
-
requiredKeys: ['testKey']
|
|
354
|
-
});
|
|
355
|
-
```
|
|
251
|
+
## Security 🔐
|
|
356
252
|
|
|
357
|
-
|
|
253
|
+
AppData automatically redacts sensitive values in its console logs. Keys matching patterns like `secret`, `token`, `password`, `api`, `auth`, `jwt`, etc. are truncated. JWT tokens (starting with `eyJ`) are also detected and shortened. Your actual stored values are never modified — only log output is redacted.
|
|
358
254
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
```typescript
|
|
362
|
-
const appData = await AppData.createAndInit<Config>({
|
|
363
|
-
requiredKeys: ['apiKey', 'apiUrl']
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
// Wait for specific key
|
|
367
|
-
const apiKey = await appData.waitForAndGetKey('apiKey');
|
|
368
|
-
|
|
369
|
-
// Check missing keys
|
|
370
|
-
const missingKeys = await appData.logMissingKeys();
|
|
371
|
-
// Logs: "The following mandatory keys are missing in the appdata:
|
|
372
|
-
// -> apiKey,
|
|
373
|
-
// -> apiUrl"
|
|
374
|
-
```
|
|
255
|
+
---
|
|
375
256
|
|
|
376
257
|
## Real-World Example 🌍
|
|
377
258
|
|
|
378
|
-
Here's a complete example of a CLI tool using smartconfig:
|
|
379
|
-
|
|
380
259
|
```typescript
|
|
381
260
|
import { Smartconfig, AppData, KeyValueStore } from '@push.rocks/smartconfig';
|
|
382
261
|
|
|
383
262
|
interface CliConfig {
|
|
384
263
|
githubToken: string;
|
|
385
|
-
openaiKey: string;
|
|
386
264
|
model: 'gpt-3' | 'gpt-4';
|
|
387
|
-
cache: {
|
|
388
|
-
enabled: boolean;
|
|
389
|
-
ttl: number;
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
class MyCLI {
|
|
394
|
-
private smartconfig: Smartconfig;
|
|
395
|
-
private appData: AppData<CliConfig>;
|
|
396
|
-
private cache: KeyValueStore<{[key: string]: any}>;
|
|
397
|
-
|
|
398
|
-
async initialize() {
|
|
399
|
-
// Load tool configuration
|
|
400
|
-
this.smartconfig = new Smartconfig();
|
|
401
|
-
const config = this.smartconfig.dataFor<any>('mycli', {
|
|
402
|
-
defaultModel: 'gpt-3'
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
// Setup app data with env mapping
|
|
406
|
-
this.appData = await AppData.createAndInit<CliConfig>({
|
|
407
|
-
requiredKeys: ['githubToken', 'openaiKey'],
|
|
408
|
-
envMapping: {
|
|
409
|
-
githubToken: 'GITHUB_TOKEN',
|
|
410
|
-
openaiKey: 'base64:OPENAI_KEY_ENCODED',
|
|
411
|
-
model: 'hard:gpt-4',
|
|
412
|
-
cache: {
|
|
413
|
-
enabled: 'boolean:ENABLE_CACHE',
|
|
414
|
-
ttl: 'hard:3600'
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
// Initialize cache
|
|
420
|
-
this.cache = new KeyValueStore({
|
|
421
|
-
typeArg: 'userHomeDir',
|
|
422
|
-
identityArg: 'mycli-cache'
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
// Check for missing configuration
|
|
426
|
-
const missingKeys = await this.appData.logMissingKeys();
|
|
427
|
-
if (missingKeys.length > 0) {
|
|
428
|
-
console.error('Please configure the missing keys');
|
|
429
|
-
process.exit(1);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
async run() {
|
|
434
|
-
await this.initialize();
|
|
435
|
-
|
|
436
|
-
const config = await this.appData.getKvStore();
|
|
437
|
-
const settings = await config.readAll();
|
|
438
|
-
|
|
439
|
-
console.log(`Using model: ${settings.model}`);
|
|
440
|
-
console.log(`Cache enabled: ${settings.cache.enabled}`);
|
|
441
|
-
|
|
442
|
-
// Use the configuration...
|
|
443
|
-
}
|
|
265
|
+
cache: { enabled: boolean; ttl: number };
|
|
444
266
|
}
|
|
445
267
|
|
|
446
|
-
//
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
## API Reference 📚
|
|
452
|
-
|
|
453
|
-
### Smartconfig Class
|
|
454
|
-
|
|
455
|
-
```typescript
|
|
456
|
-
new Smartconfig(cwdArg?: string)
|
|
457
|
-
```
|
|
458
|
-
- `cwdArg`: Optional working directory path
|
|
459
|
-
|
|
460
|
-
**Methods:**
|
|
461
|
-
- `dataFor<T>(toolName: string, defaultOptions: T): T` - Get merged configuration
|
|
462
|
-
|
|
463
|
-
### KeyValueStore Class
|
|
268
|
+
// Tool-level config from .smartconfig.json
|
|
269
|
+
const sc = new Smartconfig();
|
|
270
|
+
const toolDefaults = sc.dataFor('mycli', { defaultModel: 'gpt-3' });
|
|
464
271
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
272
|
+
// Env-mapped runtime config
|
|
273
|
+
const appData = await AppData.createAndInit<CliConfig>({
|
|
274
|
+
requiredKeys: ['githubToken'],
|
|
275
|
+
envMapping: {
|
|
276
|
+
githubToken: 'GITHUB_TOKEN',
|
|
277
|
+
model: 'hard:gpt-4',
|
|
278
|
+
cache: {
|
|
279
|
+
enabled: 'boolean:ENABLE_CACHE',
|
|
280
|
+
ttl: 'hard:3600',
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
});
|
|
473
284
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
- `deleteKey(key: string): Promise<void>` - Delete a key
|
|
480
|
-
- `getMissingMandatoryKeys(): Promise<string[]>` - Check missing required keys
|
|
481
|
-
- `waitForKeysPresent(keys: string[]): Promise<void>` - Wait for keys
|
|
285
|
+
// Persistent user-level cache
|
|
286
|
+
const cache = new KeyValueStore({
|
|
287
|
+
typeArg: 'userHomeDir',
|
|
288
|
+
identityArg: 'mycli-cache',
|
|
289
|
+
});
|
|
482
290
|
|
|
483
|
-
|
|
291
|
+
// Check mandatory keys
|
|
292
|
+
const missing = await appData.logMissingKeys();
|
|
293
|
+
if (missing.length > 0) {
|
|
294
|
+
console.error('Missing config — set these env vars and retry.');
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
484
297
|
|
|
485
|
-
|
|
486
|
-
await
|
|
487
|
-
|
|
488
|
-
requiredKeys?: Array<keyof T>;
|
|
489
|
-
ephemeral?: boolean;
|
|
490
|
-
envMapping?: PartialDeep<T>;
|
|
491
|
-
overwriteObject?: PartialDeep<T>;
|
|
492
|
-
})
|
|
298
|
+
const store = await appData.getKvStore();
|
|
299
|
+
const settings = await store.readAll();
|
|
300
|
+
console.log(`Model: ${settings.model}, Cache: ${settings.cache.enabled}`);
|
|
493
301
|
```
|
|
494
302
|
|
|
495
|
-
**Methods:**
|
|
496
|
-
- `getKvStore(): Promise<KeyValueStore<T>>` - Get underlying store
|
|
497
|
-
- `logMissingKeys(): Promise<Array<keyof T>>` - Log and return missing keys
|
|
498
|
-
- `waitForAndGetKey<K>(key: K): Promise<T[K]>` - Wait for and retrieve key
|
|
499
|
-
|
|
500
303
|
## License and Legal Information
|
|
501
304
|
|
|
502
|
-
This repository contains open-source code
|
|
305
|
+
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
|
503
306
|
|
|
504
307
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
|
505
308
|
|
|
506
309
|
### Trademarks
|
|
507
310
|
|
|
508
|
-
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein.
|
|
311
|
+
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
|
312
|
+
|
|
313
|
+
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
|
509
314
|
|
|
510
315
|
### Company Information
|
|
511
316
|
|
|
512
|
-
Task Venture Capital GmbH
|
|
513
|
-
Registered at District
|
|
317
|
+
Task Venture Capital GmbH
|
|
318
|
+
Registered at District Court Bremen HRB 35230 HB, Germany
|
|
514
319
|
|
|
515
|
-
For any legal inquiries or
|
|
320
|
+
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
|
516
321
|
|
|
517
|
-
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
|
322
|
+
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
package/ts/00_commitinfo_data.ts
CHANGED