learn-secrets-sdk 1.3.0 → 1.5.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 +192 -72
- package/bin/learn-secrets.js +20 -0
- package/dist/chunk-AS6G7JYX.mjs +1 -0
- package/dist/cli/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/cli/env-resolver-4ASC2IMR.mjs +66 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +838 -0
- package/dist/env-resolver-Y4SFGOKB.mjs +1 -0
- package/dist/index.d.mts +385 -0
- package/dist/index.d.ts +385 -5
- package/dist/index.global.js +7 -0
- package/dist/index.mjs +7 -0
- package/package.json +25 -7
- package/secrets.template.json +42 -0
- package/dist/client.d.ts +0 -65
- package/dist/client.js +0 -172
- package/dist/credentials.d.ts +0 -24
- package/dist/credentials.js +0 -86
- package/dist/env-resolver.d.ts +0 -23
- package/dist/env-resolver.js +0 -73
- package/dist/index.js +0 -4
- package/dist/management.d.ts +0 -51
- package/dist/management.js +0 -191
- package/dist/types.d.ts +0 -123
- package/dist/types.js +0 -29
package/README.md
CHANGED
|
@@ -1,51 +1,102 @@
|
|
|
1
1
|
# learn-secrets-sdk
|
|
2
2
|
|
|
3
|
-
secure api proxy
|
|
3
|
+
secure api proxy for static sites. call external apis (openai, anthropic, stripe, etc.) without exposing api keys.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**zero-config:** no tokens or credentials in your code. authentication via origin header.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## quick start (cli)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
* add api key in secrets → api_keys
|
|
11
|
-
* create sdk token in settings → sdk tokens
|
|
12
|
-
* configure allowed origins (e.g., `example.com`, `*.example.com`)
|
|
13
|
-
* copy app id from sidebar
|
|
9
|
+
### 1. install cli
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g learn-secrets-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 2. authenticate
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
learn-secrets login
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 3. initialize project
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
learn-secrets init --project YOUR_PROJECT_ID --origins yourdomain.com,localhost --env .env
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
auto-detects api keys from `.env` and uploads them. configures origins for your site.
|
|
28
|
+
|
|
29
|
+
**get project id:** visit [ctklearn.carsontkempf.workers.dev](https://ctklearn.carsontkempf.workers.dev) → copy from sidebar
|
|
30
|
+
|
|
31
|
+
### 4. install sdk in project
|
|
16
32
|
|
|
17
33
|
```bash
|
|
18
34
|
npm install learn-secrets-sdk
|
|
19
35
|
```
|
|
20
36
|
|
|
21
|
-
###
|
|
37
|
+
### 5. use in code
|
|
22
38
|
|
|
23
39
|
```typescript
|
|
24
40
|
import { SecretsSDK } from 'learn-secrets-sdk';
|
|
25
41
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
token: 'sk_live_...'
|
|
29
|
-
});
|
|
42
|
+
// zero-config - no credentials needed
|
|
43
|
+
const sdk = new SecretsSDK();
|
|
30
44
|
|
|
31
|
-
// call openai
|
|
32
|
-
const response = await sdk.post('
|
|
45
|
+
// call openai securely
|
|
46
|
+
const response = await sdk.post('openai', '/v1/chat/completions', {
|
|
33
47
|
model: 'gpt-4',
|
|
34
48
|
messages: [{ role: 'user', content: 'hello' }]
|
|
35
49
|
});
|
|
36
50
|
```
|
|
37
51
|
|
|
52
|
+
**how it works:**
|
|
53
|
+
* browser sends `Origin` header automatically
|
|
54
|
+
* proxy validates origin matches configured domains
|
|
55
|
+
* api key injected server-side
|
|
56
|
+
* no tokens or credentials in client code
|
|
57
|
+
|
|
38
58
|
## features
|
|
39
59
|
|
|
40
|
-
* **
|
|
41
|
-
* **
|
|
60
|
+
* **zero-config** - no tokens, appid, or credentials in code
|
|
61
|
+
* **origin-based auth** - automatic browser security via origin header
|
|
62
|
+
* **api keys secured** - stored server-side, never exposed to clients
|
|
63
|
+
* **auto-detection** - cli detects openai, anthropic, stripe, github, google
|
|
42
64
|
* **works anywhere** - cloudflare pages, netlify, vercel, any static host
|
|
43
65
|
* **rate limiting** - 100 req/min per domain built-in
|
|
44
|
-
* **typescript
|
|
66
|
+
* **typescript** - full type definitions included
|
|
67
|
+
|
|
68
|
+
## cli commands
|
|
69
|
+
|
|
70
|
+
### login
|
|
71
|
+
```bash
|
|
72
|
+
learn-secrets login
|
|
73
|
+
```
|
|
74
|
+
authenticate via browser oauth. credentials saved to `~/.learn/credentials.json`.
|
|
75
|
+
|
|
76
|
+
### init
|
|
77
|
+
```bash
|
|
78
|
+
learn-secrets init \
|
|
79
|
+
--project abc123xyz \ # your project id
|
|
80
|
+
--origins example.com,localhost \ # allowed domains
|
|
81
|
+
--env .env # path to .env file
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
reads .env, detects api keys, uploads to project, configures origins.
|
|
85
|
+
|
|
86
|
+
**auto-detects:**
|
|
87
|
+
* `OPENAI_API_KEY` → openai provider
|
|
88
|
+
* `ANTHROPIC_API_KEY` → anthropic provider
|
|
89
|
+
* `STRIPE_SECRET_KEY` → stripe provider
|
|
90
|
+
* `GITHUB_TOKEN` → github provider
|
|
91
|
+
* `*_API_KEY` → custom provider
|
|
92
|
+
|
|
93
|
+
full cli docs: [cli reference](https://ctklearn.carsontkempf.workers.dev/docs/sdk/cli)
|
|
45
94
|
|
|
46
95
|
## api methods
|
|
47
96
|
|
|
48
97
|
```typescript
|
|
98
|
+
const sdk = new SecretsSDK();
|
|
99
|
+
|
|
49
100
|
// http methods
|
|
50
101
|
await sdk.get(keyName, endpoint, headers?)
|
|
51
102
|
await sdk.post(keyName, endpoint, body, headers?)
|
|
@@ -63,14 +114,19 @@ await sdk.call(keyName, endpoint, {
|
|
|
63
114
|
|
|
64
115
|
## constructor options
|
|
65
116
|
|
|
117
|
+
**zero-config (recommended):**
|
|
66
118
|
```typescript
|
|
67
|
-
new SecretsSDK(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
119
|
+
const sdk = new SecretsSDK();
|
|
120
|
+
```
|
|
121
|
+
uses origin header for authentication. no credentials needed.
|
|
122
|
+
|
|
123
|
+
**advanced options:**
|
|
124
|
+
```typescript
|
|
125
|
+
const sdk = new SecretsSDK({
|
|
126
|
+
baseUrl?: string, // custom proxy url (default: production)
|
|
127
|
+
timeout?: number, // request timeout ms (default: 30000)
|
|
128
|
+
retryOn429?: boolean // auto-retry on rate limit (default: true)
|
|
129
|
+
});
|
|
74
130
|
```
|
|
75
131
|
|
|
76
132
|
## error handling
|
|
@@ -78,17 +134,21 @@ new SecretsSDK({
|
|
|
78
134
|
```typescript
|
|
79
135
|
import { SecretsSDK, SecretsSDKError, OriginMismatchError, RateLimitError } from 'learn-secrets-sdk';
|
|
80
136
|
|
|
137
|
+
const sdk = new SecretsSDK();
|
|
138
|
+
|
|
81
139
|
try {
|
|
82
|
-
const response = await sdk.post('
|
|
140
|
+
const response = await sdk.post('openai', '/v1/chat/completions', data);
|
|
83
141
|
} catch (error) {
|
|
84
142
|
if (error instanceof OriginMismatchError) {
|
|
85
|
-
// domain not in allowed origins
|
|
143
|
+
// domain not configured in allowed origins
|
|
144
|
+
console.error('add domain to origins in dashboard');
|
|
86
145
|
} else if (error instanceof RateLimitError) {
|
|
87
146
|
// exceeded 100 req/min
|
|
88
|
-
console.log('retry after:', error.retryAfter);
|
|
147
|
+
console.log('retry after:', error.retryAfter, 'seconds');
|
|
89
148
|
} else if (error instanceof SecretsSDKError) {
|
|
90
149
|
// other api errors
|
|
91
150
|
console.log('status:', error.status);
|
|
151
|
+
console.log('message:', error.message);
|
|
92
152
|
}
|
|
93
153
|
}
|
|
94
154
|
```
|
|
@@ -96,34 +156,46 @@ try {
|
|
|
96
156
|
## usage tracking
|
|
97
157
|
|
|
98
158
|
```typescript
|
|
99
|
-
const sdk = new SecretsSDK(
|
|
159
|
+
const sdk = new SecretsSDK();
|
|
100
160
|
|
|
101
|
-
await sdk.post('
|
|
161
|
+
await sdk.post('openai', '/v1/chat/completions', data);
|
|
102
162
|
|
|
103
163
|
// check rate limit status
|
|
104
164
|
const usage = sdk.getUsage();
|
|
105
165
|
console.log('remaining:', usage.remaining); // requests left this minute
|
|
166
|
+
console.log('limit:', usage.limit); // 100
|
|
106
167
|
console.log('resets at:', usage.reset); // unix timestamp
|
|
107
168
|
```
|
|
108
169
|
|
|
109
170
|
## examples
|
|
110
171
|
|
|
111
|
-
### vanilla
|
|
172
|
+
### vanilla javascript
|
|
112
173
|
|
|
113
174
|
```html
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
175
|
+
<!DOCTYPE html>
|
|
176
|
+
<html>
|
|
177
|
+
<body>
|
|
178
|
+
<button onclick="callAPI()">call ai</button>
|
|
179
|
+
<div id="response"></div>
|
|
180
|
+
|
|
181
|
+
<script type="module">
|
|
182
|
+
import { SecretsSDK } from './node_modules/learn-secrets-sdk/dist/index.js';
|
|
183
|
+
|
|
184
|
+
// zero-config
|
|
185
|
+
const sdk = new SecretsSDK();
|
|
186
|
+
|
|
187
|
+
window.callAPI = async function() {
|
|
188
|
+
const result = await sdk.post('openai', '/v1/chat/completions', {
|
|
189
|
+
model: 'gpt-4',
|
|
190
|
+
messages: [{ role: 'user', content: 'hello' }]
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
document.getElementById('response').textContent =
|
|
194
|
+
result.choices[0].message.content;
|
|
195
|
+
};
|
|
196
|
+
</script>
|
|
197
|
+
</body>
|
|
198
|
+
</html>
|
|
127
199
|
```
|
|
128
200
|
|
|
129
201
|
### react
|
|
@@ -133,17 +205,18 @@ import { useMemo } from 'react';
|
|
|
133
205
|
import { SecretsSDK } from 'learn-secrets-sdk';
|
|
134
206
|
|
|
135
207
|
function App() {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
token: import.meta.env.VITE_SDK_TOKEN
|
|
139
|
-
}), []);
|
|
208
|
+
// zero-config sdk
|
|
209
|
+
const sdk = useMemo(() => new SecretsSDK(), []);
|
|
140
210
|
|
|
141
211
|
const handleClick = async () => {
|
|
142
|
-
const result = await sdk.post('
|
|
212
|
+
const result = await sdk.post('openai', '/v1/chat/completions', {
|
|
143
213
|
model: 'gpt-4',
|
|
144
214
|
messages: [{ role: 'user', content: 'hello' }]
|
|
145
215
|
});
|
|
216
|
+
console.log(result.choices[0].message.content);
|
|
146
217
|
};
|
|
218
|
+
|
|
219
|
+
return <button onClick={handleClick}>call ai</button>;
|
|
147
220
|
}
|
|
148
221
|
```
|
|
149
222
|
|
|
@@ -153,15 +226,12 @@ function App() {
|
|
|
153
226
|
import { ref } from 'vue';
|
|
154
227
|
import { SecretsSDK } from 'learn-secrets-sdk';
|
|
155
228
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
token: import.meta.env.VITE_SDK_TOKEN
|
|
159
|
-
});
|
|
160
|
-
|
|
229
|
+
// zero-config sdk
|
|
230
|
+
const sdk = new SecretsSDK();
|
|
161
231
|
const response = ref('');
|
|
162
232
|
|
|
163
233
|
async function callAPI() {
|
|
164
|
-
const result = await sdk.post('
|
|
234
|
+
const result = await sdk.post('openai', '/v1/chat/completions', {
|
|
165
235
|
model: 'gpt-4',
|
|
166
236
|
messages: [{ role: 'user', content: 'hello' }]
|
|
167
237
|
});
|
|
@@ -169,37 +239,87 @@ async function callAPI() {
|
|
|
169
239
|
}
|
|
170
240
|
```
|
|
171
241
|
|
|
242
|
+
### next.js
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
'use client';
|
|
246
|
+
|
|
247
|
+
import { useCallback, useMemo } from 'react';
|
|
248
|
+
import { SecretsSDK } from 'learn-secrets-sdk';
|
|
249
|
+
|
|
250
|
+
export default function ChatPage() {
|
|
251
|
+
// zero-config sdk
|
|
252
|
+
const sdk = useMemo(() => new SecretsSDK(), []);
|
|
253
|
+
|
|
254
|
+
const chat = useCallback(async (message: string) => {
|
|
255
|
+
const result = await sdk.post('openai', '/v1/chat/completions', {
|
|
256
|
+
model: 'gpt-4',
|
|
257
|
+
messages: [{ role: 'user', content: message }]
|
|
258
|
+
});
|
|
259
|
+
return result.choices[0].message.content;
|
|
260
|
+
}, [sdk]);
|
|
261
|
+
|
|
262
|
+
// ... rest of component
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
172
266
|
## supported providers
|
|
173
267
|
|
|
174
|
-
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
268
|
+
auto-configured base urls when using cli:
|
|
269
|
+
* openai (`OPENAI_API_KEY`)
|
|
270
|
+
* anthropic (`ANTHROPIC_API_KEY`)
|
|
271
|
+
* stripe (`STRIPE_SECRET_KEY`)
|
|
272
|
+
* github (`GITHUB_TOKEN`)
|
|
273
|
+
* google (`GOOGLE_API_KEY`)
|
|
274
|
+
|
|
275
|
+
custom providers supported - configure base url in dashboard.
|
|
178
276
|
|
|
179
277
|
## how it works
|
|
180
278
|
|
|
181
279
|
```
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
280
|
+
1. cli uploads api keys to proxy server
|
|
281
|
+
2. cli configures allowed origins (yourdomain.com, localhost, etc.)
|
|
282
|
+
3. your static site imports sdk: new SecretsSDK()
|
|
283
|
+
4. browser automatically sends Origin header
|
|
284
|
+
5. proxy validates origin matches configured list
|
|
285
|
+
6. proxy injects api key server-side
|
|
286
|
+
7. external api called with key
|
|
287
|
+
8. response returned to browser
|
|
189
288
|
```
|
|
190
289
|
|
|
290
|
+
**security:**
|
|
291
|
+
* api keys never sent to browser
|
|
292
|
+
* origin header enforced by browser (cannot be spoofed)
|
|
293
|
+
* stolen code useless from unauthorized domains
|
|
294
|
+
|
|
191
295
|
## security
|
|
192
296
|
|
|
193
297
|
* **api keys never exposed** - stored and injected server-side only
|
|
194
|
-
* **
|
|
298
|
+
* **origin-based auth** - browser origin header enforced, can't be faked
|
|
299
|
+
* **no credentials in code** - zero-config means no tokens to leak
|
|
195
300
|
* **rate limiting** - 100 req/min per domain prevents abuse
|
|
196
|
-
* **instant
|
|
301
|
+
* **instant updates** - change origins in dashboard anytime
|
|
197
302
|
|
|
198
303
|
## documentation
|
|
199
304
|
|
|
200
|
-
* [quick start guide](https://
|
|
201
|
-
* [
|
|
202
|
-
* [
|
|
305
|
+
* [quick start guide](https://ctklearn.carsontkempf.workers.dev/docs/sdk/quick-start)
|
|
306
|
+
* [cli reference](https://ctklearn.carsontkempf.workers.dev/docs/sdk/cli)
|
|
307
|
+
* [full api reference](https://ctklearn.carsontkempf.workers.dev/docs/sdk/secrets-proxy)
|
|
308
|
+
* [dashboard](https://ctklearn.carsontkempf.workers.dev)
|
|
309
|
+
|
|
310
|
+
## troubleshooting
|
|
311
|
+
|
|
312
|
+
**403 origin not allowed:**
|
|
313
|
+
* run `learn-secrets init` with correct `--origins`
|
|
314
|
+
* or update origins in dashboard: settings → sdk tokens
|
|
315
|
+
|
|
316
|
+
**404 api key not found:**
|
|
317
|
+
* verify key name matches .env variable (e.g., `OPENAI_API_KEY` → `openai`)
|
|
318
|
+
* re-run `learn-secrets init` to upload keys
|
|
319
|
+
|
|
320
|
+
**429 rate limit:**
|
|
321
|
+
* 100 requests per minute per domain
|
|
322
|
+
* implement throttling or caching in your app
|
|
203
323
|
|
|
204
324
|
## license
|
|
205
325
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Entry point for the learn-secrets CLI
|
|
4
|
+
// This file is executed when running: learn-secrets <command>
|
|
5
|
+
|
|
6
|
+
// Check Node version
|
|
7
|
+
const nodeVersion = process.versions.node;
|
|
8
|
+
const majorVersion = parseInt(nodeVersion.split('.')[0], 10);
|
|
9
|
+
|
|
10
|
+
if (majorVersion < 16) {
|
|
11
|
+
console.error('Error: Node.js 16 or higher is required');
|
|
12
|
+
console.error(`You are running Node.js ${nodeVersion}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Run the CLI (ESM module)
|
|
17
|
+
import('../dist/cli/index.mjs').catch((err) => {
|
|
18
|
+
console.error('Failed to load CLI:', err.message);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var f=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(r,t)=>(typeof require<"u"?require:r)[t]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});function i(e){return e.replace(/\$\{([A-Z0-9_]+)\}|\$([A-Z0-9_]+)/g,(r,t,o)=>{let s=t||o,n=process.env[s];if(n===void 0)throw new Error(`Environment variable ${s} is not defined`);return n})}function c(e){let r={name:i(e.name),provider:i(e.provider),api_key:i(e.api_key)};return e.base_url&&(r.base_url=i(e.base_url)),e.auth_header&&(r.auth_header=i(e.auth_header)),e.auth_prefix&&(r.auth_prefix=i(e.auth_prefix)),r}function p(e){return e.map(c)}function g(e){let r=f("fs"),o=f("path").resolve(e);if(!r.existsSync(o))throw new Error(`Config file not found: ${o}`);let s=r.readFileSync(o,"utf-8"),n=JSON.parse(s);if(!n.version)throw new Error('Config file missing "version" field');if(!n.project)throw new Error('Config file missing "project" field');if(!Array.isArray(n.secrets))throw new Error('Config file missing "secrets" array');let a=p(n.secrets);return{version:n.version,project:n.project,secrets:a}}export{i as a,c as b,p as c,g as d};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
__require
|
|
10
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__require
|
|
3
|
+
} from "./chunk-Y6FXYEAI.mjs";
|
|
4
|
+
|
|
5
|
+
// src/env-resolver.ts
|
|
6
|
+
function resolveEnvString(value) {
|
|
7
|
+
return value.replace(/\$\{([A-Z0-9_]+)\}|\$([A-Z0-9_]+)/g, (match, var1, var2) => {
|
|
8
|
+
const varName = var1 || var2;
|
|
9
|
+
const envValue = process.env[varName];
|
|
10
|
+
if (envValue === void 0) {
|
|
11
|
+
throw new Error(`Environment variable ${varName} is not defined`);
|
|
12
|
+
}
|
|
13
|
+
return envValue;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function resolveEnvInSecret(secret) {
|
|
17
|
+
const resolved = {
|
|
18
|
+
name: resolveEnvString(secret.name),
|
|
19
|
+
provider: resolveEnvString(secret.provider),
|
|
20
|
+
api_key: resolveEnvString(secret.api_key)
|
|
21
|
+
};
|
|
22
|
+
if (secret.base_url) {
|
|
23
|
+
resolved.base_url = resolveEnvString(secret.base_url);
|
|
24
|
+
}
|
|
25
|
+
if (secret.auth_header) {
|
|
26
|
+
resolved.auth_header = resolveEnvString(secret.auth_header);
|
|
27
|
+
}
|
|
28
|
+
if (secret.auth_prefix) {
|
|
29
|
+
resolved.auth_prefix = resolveEnvString(secret.auth_prefix);
|
|
30
|
+
}
|
|
31
|
+
return resolved;
|
|
32
|
+
}
|
|
33
|
+
function resolveEnvInSecrets(secrets) {
|
|
34
|
+
return secrets.map(resolveEnvInSecret);
|
|
35
|
+
}
|
|
36
|
+
function loadSecretsConfig(configPath) {
|
|
37
|
+
const fs = __require("fs");
|
|
38
|
+
const path = __require("path");
|
|
39
|
+
const fullPath = path.resolve(configPath);
|
|
40
|
+
if (!fs.existsSync(fullPath)) {
|
|
41
|
+
throw new Error(`Config file not found: ${fullPath}`);
|
|
42
|
+
}
|
|
43
|
+
const configData = fs.readFileSync(fullPath, "utf-8");
|
|
44
|
+
const config = JSON.parse(configData);
|
|
45
|
+
if (!config.version) {
|
|
46
|
+
throw new Error('Config file missing "version" field');
|
|
47
|
+
}
|
|
48
|
+
if (!config.project) {
|
|
49
|
+
throw new Error('Config file missing "project" field');
|
|
50
|
+
}
|
|
51
|
+
if (!Array.isArray(config.secrets)) {
|
|
52
|
+
throw new Error('Config file missing "secrets" array');
|
|
53
|
+
}
|
|
54
|
+
const resolvedSecrets = resolveEnvInSecrets(config.secrets);
|
|
55
|
+
return {
|
|
56
|
+
version: config.version,
|
|
57
|
+
project: config.project,
|
|
58
|
+
secrets: resolvedSecrets
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
loadSecretsConfig,
|
|
63
|
+
resolveEnvInSecret,
|
|
64
|
+
resolveEnvInSecrets,
|
|
65
|
+
resolveEnvString
|
|
66
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|