@t-req/core 0.1.0 → 0.2.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 +158 -15
- package/dist/client.d.ts +0 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/config/engine-options.d.ts +48 -0
- package/dist/config/engine-options.d.ts.map +1 -0
- package/dist/config/index.d.ts +7 -3
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +5 -2
- package/dist/config/index.js.map +10 -5
- package/dist/config/jsonc.d.ts +18 -0
- package/dist/config/jsonc.d.ts.map +1 -0
- package/dist/config/load.d.ts +13 -2
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/merge.d.ts +31 -5
- package/dist/config/merge.d.ts.map +1 -1
- package/dist/config/resolve.d.ts +59 -0
- package/dist/config/resolve.d.ts.map +1 -0
- package/dist/config/substitution.d.ts +25 -0
- package/dist/config/substitution.d.ts.map +1 -0
- package/dist/config/types.d.ts +60 -6
- package/dist/config/types.d.ts.map +1 -1
- package/dist/cookies/persistence.d.ts +48 -0
- package/dist/cookies/persistence.d.ts.map +1 -0
- package/dist/cookies/persistence.js +4 -0
- package/dist/cookies/persistence.js.map +10 -0
- package/dist/cookies.js +5 -5
- package/dist/cookies.js.map +2 -2
- package/dist/engine/engine.d.ts +0 -4
- package/dist/engine/engine.d.ts.map +1 -1
- package/dist/engine/index.js +4 -4
- package/dist/engine/index.js.map +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +8 -6
- package/dist/interpolate.d.ts.map +1 -1
- package/dist/resolver/command.d.ts +19 -0
- package/dist/resolver/command.d.ts.map +1 -0
- package/dist/resolver/index.d.ts +2 -0
- package/dist/resolver/index.d.ts.map +1 -0
- package/dist/resolver/index.js +5 -0
- package/dist/resolver/index.js.map +10 -0
- package/dist/runtime/index.js +2 -2
- package/dist/runtime/index.js.map +2 -2
- package/dist/server-client.d.ts +50 -0
- package/dist/server-client.d.ts.map +1 -0
- package/dist/server-metadata.d.ts +13 -0
- package/dist/server-metadata.d.ts.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -7,8 +7,6 @@ HTTP request parsing, execution, and testing. Define requests in `.http` files,
|
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
[](https://www.typescriptlang.org/)
|
|
9
9
|
|
|
10
|
-
**Runtimes:** Node (>=18) and Bun. The engine is also renderer-safe for desktop apps (e.g. Tauri) when you execute from in-memory `.http` content via `runString()`.
|
|
11
|
-
|
|
12
10
|
## Features
|
|
13
11
|
|
|
14
12
|
- **Parse `.http` files** - Standard format used by VS Code REST Client, JetBrains HTTP Client
|
|
@@ -22,8 +20,6 @@ HTTP request parsing, execution, and testing. Define requests in `.http` files,
|
|
|
22
20
|
|
|
23
21
|
**Requests are just code.** No DSL, no hidden state machines. Each `.http` file contains one request, and you orchestrate them with standard JavaScript:
|
|
24
22
|
|
|
25
|
-
> Note: `client.run('./file.http')` reads from disk. In **Node**, pass `io: createNodeIO()` when creating the client. In **Tauri/desktop**, prefer `client.runString()` from the editor buffer, or provide an IO adapter that bridges to your backend.
|
|
26
|
-
|
|
27
23
|
```typescript
|
|
28
24
|
// Login and get token
|
|
29
25
|
const login = await client.run('./auth/login.http');
|
|
@@ -124,6 +120,14 @@ const client = createClient({
|
|
|
124
120
|
// Required in Node to run from files.
|
|
125
121
|
// In Bun, you can omit this and the library will use Bun's filesystem APIs.
|
|
126
122
|
io: createNodeIO(),
|
|
123
|
+
|
|
124
|
+
// Connect to TUI/server for observability (optional)
|
|
125
|
+
// Auto-detected from TREQ_SERVER env var when run from TUI
|
|
126
|
+
server: 'http://localhost:4096',
|
|
127
|
+
|
|
128
|
+
// Optional auth token for server mode
|
|
129
|
+
serverToken: process.env.TREQ_TOKEN,
|
|
130
|
+
|
|
127
131
|
// Variables available to all requests
|
|
128
132
|
variables: {
|
|
129
133
|
baseUrl: 'https://api.example.com',
|
|
@@ -183,6 +187,34 @@ client.setVariables({ a: 1, b: 2 });
|
|
|
183
187
|
console.log(client.getVariables());
|
|
184
188
|
```
|
|
185
189
|
|
|
190
|
+
### Server Mode (TUI / Observability)
|
|
191
|
+
|
|
192
|
+
When you want requests to appear in the TUI or web dashboard, the client can route
|
|
193
|
+
requests through a t-req server instead of executing them locally.
|
|
194
|
+
|
|
195
|
+
**Automatic detection:** When scripts are run from the TUI (via script runner),
|
|
196
|
+
the `TREQ_SERVER` environment variable is automatically injected. Your scripts
|
|
197
|
+
work without any code changes.
|
|
198
|
+
|
|
199
|
+
**Manual configuration:** For scripts run from a separate terminal:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const client = createClient({
|
|
203
|
+
server: 'http://localhost:4096', // or set TREQ_SERVER env var
|
|
204
|
+
variables: { ... }
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Behavior:**
|
|
209
|
+
- No `server` option + no `TREQ_SERVER` → Local mode (direct execution)
|
|
210
|
+
- `server` option OR `TREQ_SERVER` set → Server mode (routed through server)
|
|
211
|
+
- Server mode creates a session and flow for observability
|
|
212
|
+
- Call `client.close()` when done to finalize the flow
|
|
213
|
+
|
|
214
|
+
**Server-specific methods:**
|
|
215
|
+
- `close(): Promise<void>` - Finalize the session/flow
|
|
216
|
+
- `[Symbol.asyncDispose]` - Supports `await using` syntax
|
|
217
|
+
|
|
186
218
|
### Response
|
|
187
219
|
|
|
188
220
|
`client.run()` returns a native [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object:
|
|
@@ -272,7 +304,9 @@ const interp = createInterpolator({
|
|
|
272
304
|
});
|
|
273
305
|
|
|
274
306
|
const result = await interp.interpolate(
|
|
275
|
-
|
|
307
|
+
// Resolver args: prefer JSON-array args for unambiguous parsing.
|
|
308
|
+
// Example: {{$random([1,10])}}
|
|
309
|
+
'KEY={{$env(API_KEY)}}&t={{$timestamp([])}}&r={{$random([1,10])}}',
|
|
276
310
|
{}
|
|
277
311
|
);
|
|
278
312
|
```
|
|
@@ -344,9 +378,125 @@ const engine = createEngine({
|
|
|
344
378
|
await engine.runString('GET https://example.com\n');
|
|
345
379
|
```
|
|
346
380
|
|
|
347
|
-
## Config (
|
|
381
|
+
## Config (JSON/JSONC-first)
|
|
382
|
+
|
|
383
|
+
The CLI/server config system is **JSON/JSONC-first**:
|
|
384
|
+
|
|
385
|
+
- Preferred: `treq.jsonc`, `treq.json`
|
|
386
|
+
- Legacy (deprecated): `treq.config.ts`, `treq.config.js`, `treq.config.mjs`
|
|
387
|
+
|
|
388
|
+
### `treq.jsonc` example
|
|
389
|
+
|
|
390
|
+
```jsonc
|
|
391
|
+
{
|
|
392
|
+
"variables": {
|
|
393
|
+
"baseUrl": "http://localhost:3000",
|
|
394
|
+
"apiKey": "{env:API_KEY}"
|
|
395
|
+
},
|
|
396
|
+
"defaults": {
|
|
397
|
+
"timeoutMs": 30000,
|
|
398
|
+
"headers": {
|
|
399
|
+
"Accept": "application/json"
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
// Uncomment to persist cookies between runs:
|
|
403
|
+
// "cookies": {
|
|
404
|
+
// "enabled": true,
|
|
405
|
+
// "jarPath": ".treq/cookies.json"
|
|
406
|
+
// },
|
|
407
|
+
"profiles": {
|
|
408
|
+
"dev": {
|
|
409
|
+
"variables": { "baseUrl": "http://localhost:3000" },
|
|
410
|
+
"defaults": { "validateSSL": false }
|
|
411
|
+
},
|
|
412
|
+
"prod": {
|
|
413
|
+
"variables": { "baseUrl": "https://api.example.com" }
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Config shape (what each field means)
|
|
348
420
|
|
|
349
|
-
|
|
421
|
+
Top-level keys in `treq.jsonc` / `treq.json`:
|
|
422
|
+
|
|
423
|
+
- **`variables`** (`Record<string, unknown>`): default variables available to `.http` files (`{{var}}`).
|
|
424
|
+
- **`defaults`**:
|
|
425
|
+
- **`timeoutMs`** (`number`, default: `30000`)
|
|
426
|
+
- **`followRedirects`** (`boolean`, default: `true`)
|
|
427
|
+
- **`validateSSL`** (`boolean`, default: `true`)
|
|
428
|
+
- **`proxy`** (`string`, optional)
|
|
429
|
+
- **`headers`** (`Record<string, string>`, default: `{}`): merged as *header defaults* (request headers win).
|
|
430
|
+
- **`cookies`**:
|
|
431
|
+
- **`enabled`** (`boolean`, default: `true`)
|
|
432
|
+
- **`jarPath`** (`string`, optional): enables persistence (path is relative to the project root).
|
|
433
|
+
- **mode** (derived):
|
|
434
|
+
- `disabled` if `enabled=false`
|
|
435
|
+
- `memory` if `enabled=true` and no `jarPath`
|
|
436
|
+
- `persistent` if `enabled=true` and `jarPath` is set
|
|
437
|
+
- **`resolvers`** (`Record<string, Resolver | CommandResolverDef>`):
|
|
438
|
+
- In TS/JS legacy configs, values can be **functions**.
|
|
439
|
+
- In JSON/JSONC configs, define **command resolvers** (see below).
|
|
440
|
+
- **`profiles`** (`Record<string, { variables/defaults/cookies/resolvers }>`): named overlays.
|
|
441
|
+
- **`security`**:
|
|
442
|
+
- **`allowExternalFiles`** (`boolean`, default: `false`): allow `{file:...}` to read outside the workspace.
|
|
443
|
+
|
|
444
|
+
Resolution order (last wins):
|
|
445
|
+
|
|
446
|
+
- **variables**: `file < profile < overrides`
|
|
447
|
+
- **defaults/cookies/resolvers/security**: `file < profile < overrides`
|
|
448
|
+
|
|
449
|
+
### Substitutions
|
|
450
|
+
|
|
451
|
+
- `{env:VAR}` injects `process.env.VAR` (or `""` if missing)
|
|
452
|
+
- `{file:path}` injects the file contents (with `.trimEnd()` applied)
|
|
453
|
+
- Relative paths resolve from the config file directory
|
|
454
|
+
- By default, file reads are workspace-scoped (server-safe); you can opt out via `security.allowExternalFiles`
|
|
455
|
+
|
|
456
|
+
### Command resolvers (JSON/JSONC-friendly plugins)
|
|
457
|
+
|
|
458
|
+
JSON/JSONC can’t represent resolver functions, so you can define command resolvers:
|
|
459
|
+
|
|
460
|
+
```jsonc
|
|
461
|
+
{
|
|
462
|
+
"resolvers": {
|
|
463
|
+
"$hmacSign": {
|
|
464
|
+
"type": "command",
|
|
465
|
+
"command": ["ruby", ".treq/plugins/hmac.rb"],
|
|
466
|
+
"timeoutMs": 2000
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
They run with `cwd = projectRoot` and communicate over NDJSON:
|
|
473
|
+
|
|
474
|
+
- stdin: `{"resolver":"$hmacSign","args":["payload"]}\n`
|
|
475
|
+
- stdout: `{"value":"<string>"}\n`
|
|
476
|
+
|
|
477
|
+
Resolver argument syntax in templates:
|
|
478
|
+
|
|
479
|
+
- Prefer JSON-array args: `{{$hmacSign(["{{body}}"])}}`
|
|
480
|
+
- Fallback: if the text in `(...)` is not valid JSON, it becomes a single string arg
|
|
481
|
+
- v1 restriction: resolver args cannot contain resolver calls (variables are OK)
|
|
482
|
+
|
|
483
|
+
### Resolving config (single source of truth)
|
|
484
|
+
|
|
485
|
+
If you're building tooling (like the CLI/server), use `resolveProjectConfig()` to get a **resolved** engine-ready config plus metadata:
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
import { resolveProjectConfig } from '@t-req/core/config';
|
|
489
|
+
|
|
490
|
+
const { config, meta } = await resolveProjectConfig({
|
|
491
|
+
startDir: process.cwd(),
|
|
492
|
+
profile: 'dev',
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
console.log(meta.configPath, meta.layersApplied, meta.warnings);
|
|
496
|
+
console.log(config.defaults, Object.keys(config.resolvers));
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Legacy TS config (deprecated)
|
|
350
500
|
|
|
351
501
|
```typescript
|
|
352
502
|
// treq.config.ts
|
|
@@ -358,7 +508,7 @@ export default defineConfig({
|
|
|
358
508
|
});
|
|
359
509
|
```
|
|
360
510
|
|
|
361
|
-
|
|
511
|
+
You can still load legacy `treq.config.*` via `loadConfig()`, but new projects should prefer `treq.jsonc`.
|
|
362
512
|
|
|
363
513
|
```typescript
|
|
364
514
|
import { loadConfig, mergeConfig } from '@t-req/core/config';
|
|
@@ -620,13 +770,6 @@ if (!response.ok) {
|
|
|
620
770
|
|
|
621
771
|
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
|
|
622
772
|
|
|
623
|
-
## Publishing
|
|
624
|
-
|
|
625
|
-
This is a scoped package (`@t-req/core`). The repo is configured to publish it as **public**.
|
|
626
|
-
|
|
627
|
-
- **Local publish**: `bun publish --access public`
|
|
628
|
-
- **CI publish**: push a tag like `v0.1.0` (see `.github/workflows/release.yml`)
|
|
629
|
-
|
|
630
773
|
## License
|
|
631
774
|
|
|
632
775
|
MIT
|
package/dist/client.d.ts
CHANGED
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAc,MAAM,SAAS,CAAC;AAGhE,wBAAgB,YAAY,CAAC,MAAM,GAAE,YAAiB,GAAG,MAAM,CAuF9D"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { EngineConfig } from '../engine/engine';
|
|
2
|
+
import type { CookieStore, EngineEvent } from '../runtime/types';
|
|
3
|
+
import type { ResolvedConfig } from './types';
|
|
4
|
+
export type RequestDefaults = {
|
|
5
|
+
timeoutMs: number;
|
|
6
|
+
followRedirects: boolean;
|
|
7
|
+
validateSSL: boolean;
|
|
8
|
+
proxy?: string;
|
|
9
|
+
};
|
|
10
|
+
export type BuildEngineOptionsInput = {
|
|
11
|
+
config: ResolvedConfig;
|
|
12
|
+
cookieStore?: CookieStore;
|
|
13
|
+
onEvent?: (event: EngineEvent) => void;
|
|
14
|
+
};
|
|
15
|
+
export type BuildEngineOptionsResult = {
|
|
16
|
+
engineOptions: EngineConfig;
|
|
17
|
+
requestDefaults: RequestDefaults;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Build engine options from resolved config.
|
|
21
|
+
*
|
|
22
|
+
* This is the centralized helper used by both CLI and server to ensure
|
|
23
|
+
* consistent engine configuration.
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const { engineOptions, requestDefaults } = buildEngineOptions({
|
|
28
|
+
* config,
|
|
29
|
+
* cookieStore,
|
|
30
|
+
* onEvent
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* const engine = createEngine(engineOptions);
|
|
34
|
+
*
|
|
35
|
+
* const response = await engine.runString(content, {
|
|
36
|
+
* variables: config.variables,
|
|
37
|
+
* basePath,
|
|
38
|
+
* timeoutMs: explicitTimeout ?? requestDefaults.timeoutMs,
|
|
39
|
+
* followRedirects: explicitFollowRedirects ?? requestDefaults.followRedirects,
|
|
40
|
+
* validateSSL: explicitValidateSSL ?? requestDefaults.validateSSL
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @param input - Configuration input
|
|
45
|
+
* @returns Engine options and request defaults
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildEngineOptions(input: BuildEngineOptionsInput): BuildEngineOptionsResult;
|
|
48
|
+
//# sourceMappingURL=engine-options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-options.d.ts","sourceRoot":"","sources":["../../src/config/engine-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAM9C,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,cAAc,CAAC;IACvB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,aAAa,EAAE,YAAY,CAAC;IAC5B,eAAe,EAAE,eAAe,CAAC;CAClC,CAAC;AAMF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,wBAAwB,CAmC3F"}
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { defineConfig } from './define';
|
|
2
|
-
export { type
|
|
3
|
-
export {
|
|
4
|
-
export
|
|
2
|
+
export { type BuildEngineOptionsInput, type BuildEngineOptionsResult, buildEngineOptions, type RequestDefaults } from './engine-options';
|
|
3
|
+
export { parseJsonc, stripJsonComments } from './jsonc';
|
|
4
|
+
export { isLegacyFormat, type LoadConfigOptions, loadConfig } from './load';
|
|
5
|
+
export { applyProfile, listProfiles, type MergeConfigInputs, mergeConfig } from './merge';
|
|
6
|
+
export { type ConfigOverrideLayer, DEFAULT_FOLLOW_REDIRECTS, DEFAULT_TIMEOUT_MS, DEFAULT_VALIDATE_SSL, type ResolveProjectConfigOptions, resolveProjectConfig } from './resolve';
|
|
7
|
+
export { applySubstitutions, type SubstitutionOptions } from './substitution';
|
|
8
|
+
export type { CommandResolverDef, ConfigFormat, ConfigMeta, CookiesConfig, LoadedConfig, ResolvedConfig, ResolvedCookiesConfig, ResolvedDefaults, ResolvedProjectConfig, SecurityConfig, TreqConfig, TreqConfigInput, TreqDefaults, TreqProfileInput } from './types';
|
|
5
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,kBAAkB,EAClB,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAExD,OAAO,EAAE,cAAc,EAAE,KAAK,iBAAiB,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAE5E,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE1F,OAAO,EACL,KAAK,mBAAmB,EACxB,wBAAwB,EACxB,kBAAkB,EAClB,oBAAoB,EACpB,KAAK,2BAA2B,EAChC,oBAAoB,EACrB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAG9E,YAAY,EACV,kBAAkB,EAClB,YAAY,EACZ,UAAU,EACV,aAAa,EAEb,YAAY,EAEZ,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,EACrB,cAAc,EAEd,UAAU,EACV,eAAe,EACf,YAAY,EACZ,gBAAgB,EACjB,MAAM,SAAS,CAAC"}
|
package/dist/config/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import{createRequire as s}from"node:module";var jz=s(import.meta.url);function r(z){return z}function i(z){let{config:K,cookieStore:Q,onEvent:Z}=z,$={};if(K.cookies.enabled&&Q)$.cookieStore=Q;if($.headerDefaults=K.defaults.headers,$.resolvers=K.resolvers,Z)$.onEvent=Z;let X={timeoutMs:K.defaults.timeoutMs,followRedirects:K.defaults.followRedirects,validateSSL:K.defaults.validateSSL};if(K.defaults.proxy)X.proxy=K.defaults.proxy;return{engineOptions:$,requestDefaults:X}}function b(z){let K=[],Q="code",Z=0,$=()=>{while(K.length>0){let X=K[K.length-1];if(X===" "||X==="\t"){K.pop();continue}break}};while(Z<z.length){let X=z.charAt(Z),Y=z[Z+1];switch(Q){case"code":if(X==='"')K.push(X),Q="string",Z++;else if(X==="/"&&Y==="/")$(),Q="line-comment",Z+=2;else if(X==="/"&&Y==="*")Q="block-comment",Z+=2;else K.push(X),Z++;break;case"string":if(K.push(X),X==="\\"&&Y!==void 0)K.push(Y),Z+=2;else if(X==='"')Q="code",Z++;else Z++;break;case"line-comment":if(X===`
|
|
2
|
+
`||X==="\r")K.push(X),Q="code";Z++;break;case"block-comment":if(X==="*"&&Y==="/")Q="code",Z+=2;else{if(X===`
|
|
3
|
+
`||X==="\r")K.push(X);Z++}break}}return K.join("")}function T(z){let K=b(z);try{return JSON.parse(K)}catch(Q){if(Q instanceof SyntaxError)throw SyntaxError(`Invalid JSONC: ${Q.message}`);throw Q}}import{access as a,readFile as h}from"node:fs/promises";import*as V from"node:path";import{pathToFileURL as t}from"node:url";var o=[{filename:"treq.jsonc",format:"jsonc"},{filename:"treq.json",format:"json"},{filename:"treq.config.ts",format:"ts"},{filename:"treq.config.js",format:"js"},{filename:"treq.config.mjs",format:"mjs"}];async function D(z){try{return await a(z),!0}catch{return!1}}async function e(z,K,Q){let Z=V.resolve(z),$=Q?V.resolve(Q):void 0;while(!0){if(K){let Y=V.join(Z,K);if(await D(Y)){let G=g(K);return{path:Y,format:G}}}else for(let{filename:Y,format:G}of o){let B=V.join(Z,Y);if(await D(B))return{path:B,format:G}}if($&&Z===$)return;let X=V.dirname(Z);if(X===Z)return;Z=X}}function g(z){if(z.endsWith(".jsonc"))return"jsonc";if(z.endsWith(".json"))return"json";if(z.endsWith(".ts"))return"ts";if(z.endsWith(".mjs"))return"mjs";if(z.endsWith(".js"))return"js";return"json"}function zz(z){let K=V.basename(z);return g(K)}async function Kz(z){let Z=(await import(t(z).href)).default;if(!Z||typeof Z!=="object")throw Error(`Invalid config export from ${z}. Expected default object export.`);return Z}async function Qz(z){let K=await h(z,"utf-8"),Q=T(K);if(!Q||typeof Q!=="object")throw Error(`Invalid JSONC config at ${z}. Expected object.`);return Q}async function Zz(z){let K=await h(z,"utf-8"),Q=JSON.parse(K);if(!Q||typeof Q!=="object")throw Error(`Invalid JSON config at ${z}. Expected object.`);return Q}async function $z(z,K){switch(K){case"jsonc":return await Qz(z);case"json":return await Zz(z);case"ts":case"js":case"mjs":return await Kz(z);default:throw Error(`Unknown config format: ${K}`)}}async function k(z){let K="filename"in z&&z.filename?z.filename:void 0,Q,Z;if("path"in z){if(Q=V.resolve(z.path),Z=zz(Q),!await D(Q))return{config:{}}}else{let X=await e(z.startDir,K,z.stopDir);if(!X)return{config:{}};Q=X.path,Z=X.format}let $=await $z(Q,Z);return{path:Q,config:$,format:Z}}function C(z){return z==="ts"||z==="js"||z==="mjs"}function Xz(z,K){if(!z&&!K)return;if(!z)return K;if(!K)return z;return{...z,...K,headers:{...z.headers??{},...K.headers??{}}}}function x(z,K){let Q={},Z={...z.variables??{},...K.variables??{}};if(Object.keys(Z).length>0)Q.variables=Z;let $={...z.resolvers??{},...K.resolvers??{}};if(Object.keys($).length>0)Q.resolvers=$;let X=Xz(z.defaults,K.defaults);if(X)Q.defaults=X;let Y={...z.cookies??{},...K.cookies??{}};if(Object.keys(Y).length>0)Q.cookies=Y;let G={...z.security??{},...K.security??{}};if(Object.keys(G).length>0)Q.security=G;if(z.profiles)Q.profiles=z.profiles;return Q}function F(z){let K={};if(z.defaults)K=x(K,z.defaults);if(z.file)K=x(K,z.file);if(z.overrides)K=x(K,z.overrides);return K}function R(z,K){if(!K)return z;let Q=z.profiles??{},Z=Q[K];if(!Z){let X=Object.keys(Q),Y=X.length>0?` Available profiles: ${X.join(", ")}`:"";throw Error(`Profile "${K}" not found.${Y}`)}let $={};if(z.variables)$.variables=z.variables;if(z.defaults)$.defaults=z.defaults;if(z.cookies)$.cookies=z.cookies;if(z.resolvers)$.resolvers=z.resolvers;if(z.security)$.security=z.security;return x($,Z)}function Yz(z){let K=z.profiles??{};return Object.keys(K).sort()}import{existsSync as Fz}from"node:fs";import*as H from"node:path";import{spawn as Gz}from"node:child_process";var Hz=2000,qz=500,Bz=1048576,Vz=65536;async function Jz(z,K,Q,Z){let $=z.timeoutMs??Hz;if(z.command.length===0)throw Error(`Resolver "${K}" has empty command array`);let[X,...Y]=z.command;if(!X)throw Error(`Resolver "${K}" has empty command`);return new Promise((G,B)=>{let J=Gz(X,Y,{cwd:Z,stdio:["pipe","pipe","pipe"],env:process.env}),w="",M="",O=0,N=0,L=!1,E=!1,j=!1,P=!1,y=setTimeout(()=>{P=!0,J.kill("SIGTERM"),setTimeout(()=>{if(!j)J.kill("SIGKILL")},qz)},$);J.stdout?.on("data",(W)=>{let U=Bz-O;if(U>0){let I=W.slice(0,U);if(w+=I.toString("utf-8"),O+=I.length,I.length<W.length)L=!0}else L=!0}),J.stderr?.on("data",(W)=>{let U=Vz-N;if(U>0){let I=W.slice(0,U);if(M+=I.toString("utf-8"),N+=I.length,I.length<W.length)E=!0}else E=!0}),J.on("close",(W,U)=>{if(clearTimeout(y),j=!0,P){B(Error(`Resolver "${K}" timed out after ${$}ms${M?`: ${M}`:""}`));return}if(W!==0){let A=U?`killed by signal ${U}`:`exit code ${W}`;B(Error(`Resolver "${K}" failed (${A})${M?`: ${M}`:""}`));return}let I=w.trim();if(!I){B(Error(`Resolver "${K}" returned no output`));return}let v=I.split(/\r?\n/).map((A)=>A.trim()).find((A)=>A.length>0)??"",S;try{S=JSON.parse(v)}catch{let A=L?" (stdout exceeded 1MB limit and was truncated)":"";B(Error(`Resolver "${K}" returned invalid JSON: ${v.slice(0,100)}${A}`));return}if(typeof S.value!=="string"){B(Error(`Resolver "${K}" returned no value (expected { "value": "..." })`));return}G(S.value)}),J.on("error",(W)=>{clearTimeout(y);let U=E?" (stderr exceeded 64KB limit and was truncated)":"";B(Error(`Resolver "${K}" failed to execute: ${W.message}${U}`))});let n={resolver:K,args:Q};J.stdin?.write(`${JSON.stringify(n)}
|
|
4
|
+
`),J.stdin?.end()})}function m(z,K,Q){let Z=Q??"$command";return async(...$)=>{return await Jz(z,Z,$,K)}}function u(z){if(!z||typeof z!=="object")return!1;let K=z;return K.type==="command"&&Array.isArray(K.command)}import{accessSync as Wz,readFileSync as Uz,realpathSync as Iz}from"node:fs";import*as f from"node:os";import*as q from"node:path";var Lz=/\{env:([^}]+)\}/g,Mz=/\{file:([^}]+)\}/g;function Nz(z){if(z.startsWith("~/")||z==="~")return q.join(f.homedir(),z.slice(1));return z}function Az(z,K){let Q=q.resolve(z),Z=q.resolve(K),$=q.relative(Z,Q);if($==="")return!0;if(q.isAbsolute($))return!1;if($.startsWith(".."))return!1;return!0}function c(z){try{return Iz(z)}catch{return q.resolve(z)}}function _z(z){try{return Wz(z),!0}catch{return!1}}function wz(z){return z.replace(Lz,(K,Q)=>{return process.env[Q]??""})}function Oz(z,K){return z.replace(Mz,(Q,Z)=>{let $=Nz(Z.trim());if(!q.isAbsolute($))$=q.resolve(K.configDir,$);if(!_z($))throw Error(`Config substitution failed: {file:${Z}} not found at ${$}`);let X=c(K.workspaceRoot),Y=c($);if(!K.allowExternalFiles){if(!Az(Y,X))throw Error(`Config substitution failed: {file:${Z}} outside workspace (use security.allowExternalFiles to allow)`)}try{return Uz(Y,"utf-8").trimEnd()}catch(G){throw Error(`Config substitution failed: {file:${Z}} could not be read: ${G instanceof Error?G.message:String(G)}`)}})}function xz(z,K){let Q=wz(z);return Q=Oz(Q,K),Q}function _(z,K){if(z===null||z===void 0)return z;if(typeof z==="string")return xz(z,K);if(Array.isArray(z))return z.map((Q)=>_(Q,K));if(typeof z==="object"){let Q={};for(let[Z,$]of Object.entries(z))Q[Z]=_($,K);return Q}return z}var p=30000,d=!0,l=!0;function Ez(z,K){let Q=H.resolve(z),Z=K?H.resolve(K):void 0,{root:$}=H.parse(Q);while(!0){let X=H.join(Q,".git");if(Fz(X))return Q;if(Z&&Q===Z)return;if(Q===$)return;let Y=H.dirname(Q);if(Y===Q)return;Q=Y}}function Sz(z,K,Q){if(z)return H.dirname(z);let Z=Q?H.resolve(Q):void 0;return Ez(K,Z)??Z??H.resolve(K)}function Tz(z){let K=z?.enabled!==!1,Q=z?.jarPath,Z;if(!K)Z="disabled";else if(Q)Z="persistent";else Z="memory";let $={enabled:K,mode:Z};if(Q)$.jarPath=Q;return $}function Dz(z){let K={timeoutMs:z?.timeoutMs??p,followRedirects:z?.followRedirects??d,validateSSL:z?.validateSSL??l,headers:z?.headers??{}};if(z?.proxy)K.proxy=z.proxy;return K}function kz(z,K){if(!z)return{};let Q={};for(let[Z,$]of Object.entries(z))if(typeof $==="function")Q[Z]=$;else if(u($))Q[Z]=m($,K,Z);else throw Error(`Unknown resolver type for "${Z}". Expected function or { type: "command", command: [...] }`);return Q}async function Cz(z){let K=[],Q=[],Z={startDir:z.startDir};if(z.stopDir)Z.stopDir=z.stopDir;let $=await k(Z),X=z.stopDir?H.resolve(z.stopDir):void 0,Y=Sz($.path,z.startDir,X);if($.path&&C($.format))K.push(`${H.basename($.path)} is deprecated; migrate to treq.jsonc`);let G={};if($.config&&Object.keys($.config).length>0)Q.push("file"),G=$.config;if(z.profile)G=R(G,z.profile),Q.push(`profile:${z.profile}`);for(let L of z.overrideLayers??[]){if(!L||!L.overrides||Object.keys(L.overrides).length===0)continue;G=F({file:G,overrides:L.overrides}),Q.push(L.name)}if(z.overrides&&Object.keys(z.overrides).length>0)G=F({file:G,overrides:z.overrides}),Q.push(z.overridesLayerName??"overrides");let B=X??Y,J=$.path?H.dirname($.path):Y,w=G.security?.allowExternalFiles??!1;G=_(G,{configDir:J,workspaceRoot:B,allowExternalFiles:w});let M=kz(G.resolvers,Y),O={projectRoot:Y,variables:G.variables??{},defaults:Dz(G.defaults),cookies:Tz(G.cookies),resolvers:M,security:{allowExternalFiles:G.security?.allowExternalFiles??!1}},N={projectRoot:Y,layersApplied:Q,warnings:K};if($.path)N.configPath=$.path;if($.format)N.format=$.format;if(z.profile)N.profile=z.profile;return{config:O,meta:N}}export{b as stripJsonComments,Cz as resolveProjectConfig,T as parseJsonc,F as mergeConfig,k as loadConfig,Yz as listProfiles,C as isLegacyFormat,r as defineConfig,i as buildEngineOptions,_ as applySubstitutions,R as applyProfile,l as DEFAULT_VALIDATE_SSL,p as DEFAULT_TIMEOUT_MS,d as DEFAULT_FOLLOW_REDIRECTS};
|
|
2
5
|
|
|
3
|
-
//# debugId=
|
|
6
|
+
//# debugId=15FF26925375BC1F64756E2164756E21
|
|
4
7
|
//# sourceMappingURL=index.js.map
|
package/dist/config/index.js.map
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/config/define.ts", "../src/config/load.ts", "../src/config/merge.ts"],
|
|
3
|
+
"sources": ["../src/config/define.ts", "../src/config/engine-options.ts", "../src/config/jsonc.ts", "../src/config/load.ts", "../src/config/merge.ts", "../src/config/resolve.ts", "../src/resolver/command.ts", "../src/config/substitution.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import type { TreqConfig } from './types';\n\n/**\n * Typed identity helper for `treq.config.ts`.\n */\nexport function defineConfig(config: TreqConfig): TreqConfig {\n return config;\n}\n",
|
|
6
|
-
"import {
|
|
7
|
-
"
|
|
6
|
+
"import type { EngineConfig } from '../engine/engine';\nimport type { CookieStore, EngineEvent } from '../runtime/types';\nimport type { ResolvedConfig } from './types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type RequestDefaults = {\n timeoutMs: number;\n followRedirects: boolean;\n validateSSL: boolean;\n proxy?: string;\n};\n\nexport type BuildEngineOptionsInput = {\n config: ResolvedConfig;\n cookieStore?: CookieStore;\n onEvent?: (event: EngineEvent) => void;\n};\n\nexport type BuildEngineOptionsResult = {\n engineOptions: EngineConfig;\n requestDefaults: RequestDefaults;\n};\n\n// ============================================================================\n// Main API\n// ============================================================================\n\n/**\n * Build engine options from resolved config.\n *\n * This is the centralized helper used by both CLI and server to ensure\n * consistent engine configuration.\n *\n * Usage:\n * ```typescript\n * const { engineOptions, requestDefaults } = buildEngineOptions({\n * config,\n * cookieStore,\n * onEvent\n * });\n *\n * const engine = createEngine(engineOptions);\n *\n * const response = await engine.runString(content, {\n * variables: config.variables,\n * basePath,\n * timeoutMs: explicitTimeout ?? requestDefaults.timeoutMs,\n * followRedirects: explicitFollowRedirects ?? requestDefaults.followRedirects,\n * validateSSL: explicitValidateSSL ?? requestDefaults.validateSSL\n * });\n * ```\n *\n * @param input - Configuration input\n * @returns Engine options and request defaults\n */\nexport function buildEngineOptions(input: BuildEngineOptionsInput): BuildEngineOptionsResult {\n const { config, cookieStore, onEvent } = input;\n\n // Build engine options conditionally to handle exactOptionalPropertyTypes\n const engineOptions: EngineConfig = {};\n\n // Only include cookieStore if cookies are enabled and a store is provided\n if (config.cookies.enabled && cookieStore) {\n engineOptions.cookieStore = cookieStore;\n }\n\n // Header defaults from config\n engineOptions.headerDefaults = config.defaults.headers;\n\n // Compiled resolvers (all are functions at this point)\n engineOptions.resolvers = config.resolvers;\n\n // Event handler - only include if provided\n if (onEvent) {\n engineOptions.onEvent = onEvent;\n }\n\n // Build request defaults conditionally\n const requestDefaults: RequestDefaults = {\n timeoutMs: config.defaults.timeoutMs,\n followRedirects: config.defaults.followRedirects,\n validateSSL: config.defaults.validateSSL\n };\n\n // Only include proxy if defined\n if (config.defaults.proxy) {\n requestDefaults.proxy = config.defaults.proxy;\n }\n\n return { engineOptions, requestDefaults };\n}\n",
|
|
7
|
+
"/**\n * JSONC (JSON with Comments) parser\n *\n * Strips single-line (//) and multi-line comments from JSON text\n * while preserving '//' inside string values (e.g., URLs).\n */\n\ntype State = 'code' | 'string' | 'line-comment' | 'block-comment';\n\n/**\n * Strips JSONC comments from a string while preserving URLs and other\n * strings containing '//' or comment-like sequences.\n */\nexport function stripJsonComments(content: string): string {\n const result: string[] = [];\n let state: State = 'code';\n let i = 0;\n\n const trimTrailingWhitespaceOnLine = (): void => {\n // Remove whitespace immediately before a `//` comment so we don't leave\n // trailing spaces on that line. This is safe for JSON since whitespace is\n // insignificant outside strings, and we only do this in `code` state.\n while (result.length > 0) {\n const last = result[result.length - 1];\n if (last === ' ' || last === '\\t') {\n result.pop();\n continue;\n }\n break;\n }\n };\n\n while (i < content.length) {\n // `charAt` avoids non-null assertions and is safe with bounds checks.\n const char = content.charAt(i);\n const next = content[i + 1];\n\n switch (state) {\n case 'code':\n if (char === '\"') {\n // Enter string\n result.push(char);\n state = 'string';\n i++;\n } else if (char === '/' && next === '/') {\n // Enter line comment\n trimTrailingWhitespaceOnLine();\n state = 'line-comment';\n i += 2;\n } else if (char === '/' && next === '*') {\n // Enter block comment\n state = 'block-comment';\n i += 2;\n } else {\n result.push(char);\n i++;\n }\n break;\n\n case 'string':\n result.push(char);\n if (char === '\\\\' && next !== undefined) {\n // Escape sequence - consume next char\n result.push(next);\n i += 2;\n } else if (char === '\"') {\n // Exit string\n state = 'code';\n i++;\n } else {\n i++;\n }\n break;\n\n case 'line-comment':\n if (char === '\\n' || char === '\\r') {\n // Exit line comment, preserve newline\n result.push(char);\n state = 'code';\n }\n i++;\n break;\n\n case 'block-comment':\n if (char === '*' && next === '/') {\n // Exit block comment\n state = 'code';\n i += 2;\n } else {\n // Preserve newlines in block comments for line number accuracy\n if (char === '\\n' || char === '\\r') {\n result.push(char);\n }\n i++;\n }\n break;\n }\n }\n\n return result.join('');\n}\n\n/**\n * Parse JSONC content (JSON with comments) to an object.\n *\n * @throws {SyntaxError} if the content is not valid JSON after stripping comments\n */\nexport function parseJsonc<T = unknown>(content: string): T {\n const stripped = stripJsonComments(content);\n try {\n return JSON.parse(stripped) as T;\n } catch (err) {\n if (err instanceof SyntaxError) {\n throw new SyntaxError(`Invalid JSONC: ${err.message}`);\n }\n throw err;\n }\n}\n",
|
|
8
|
+
"import { access, readFile } from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { parseJsonc } from './jsonc';\nimport type { ConfigFormat, LoadedConfig, TreqConfigInput } from './types';\n\nexport type LoadConfigOptions =\n | { path: string }\n | { startDir: string; filename?: string; stopDir?: string };\n\n// Config file discovery order (preferred first)\nconst CONFIG_FILES: Array<{ filename: string; format: ConfigFormat }> = [\n { filename: 'treq.jsonc', format: 'jsonc' },\n { filename: 'treq.json', format: 'json' },\n { filename: 'treq.config.ts', format: 'ts' },\n { filename: 'treq.config.js', format: 'js' },\n { filename: 'treq.config.mjs', format: 'mjs' }\n];\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Find config file by walking up from startDir.\n * Returns the first matching config file found.\n */\nasync function findUp(\n startDir: string,\n filename: string | undefined,\n stopDir?: string\n): Promise<{ path: string; format: ConfigFormat } | undefined> {\n let dir = path.resolve(startDir);\n const stop = stopDir ? path.resolve(stopDir) : undefined;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n // If specific filename provided, only check that file\n if (filename) {\n const candidate = path.join(dir, filename);\n if (await fileExists(candidate)) {\n // Determine format from extension\n const format = getFormatFromFilename(filename);\n return { path: candidate, format };\n }\n } else {\n // Check all config files in discovery order\n for (const { filename: fname, format } of CONFIG_FILES) {\n const candidate = path.join(dir, fname);\n if (await fileExists(candidate)) {\n return { path: candidate, format };\n }\n }\n }\n\n if (stop && dir === stop) return undefined;\n\n const parent = path.dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n}\n\nfunction getFormatFromFilename(filename: string): ConfigFormat {\n if (filename.endsWith('.jsonc')) return 'jsonc';\n if (filename.endsWith('.json')) return 'json';\n if (filename.endsWith('.ts')) return 'ts';\n if (filename.endsWith('.mjs')) return 'mjs';\n if (filename.endsWith('.js')) return 'js';\n return 'json'; // Default fallback\n}\n\nfunction getFormatFromPath(configPath: string): ConfigFormat {\n const basename = path.basename(configPath);\n return getFormatFromFilename(basename);\n}\n\nasync function importConfigFile(configPath: string): Promise<TreqConfigInput> {\n const url = pathToFileURL(configPath).href;\n const mod = (await import(url)) as { default?: unknown };\n const cfg = mod.default;\n if (!cfg || typeof cfg !== 'object') {\n throw new Error(`Invalid config export from ${configPath}. Expected default object export.`);\n }\n return cfg as TreqConfigInput;\n}\n\nasync function loadJsoncConfigFile(configPath: string): Promise<TreqConfigInput> {\n const content = await readFile(configPath, 'utf-8');\n const parsed = parseJsonc<TreqConfigInput>(content);\n if (!parsed || typeof parsed !== 'object') {\n throw new Error(`Invalid JSONC config at ${configPath}. Expected object.`);\n }\n return parsed;\n}\n\nasync function loadJsonConfigFile(configPath: string): Promise<TreqConfigInput> {\n const content = await readFile(configPath, 'utf-8');\n const parsed = JSON.parse(content) as TreqConfigInput;\n if (!parsed || typeof parsed !== 'object') {\n throw new Error(`Invalid JSON config at ${configPath}. Expected object.`);\n }\n return parsed;\n}\n\nasync function loadConfigByFormat(\n configPath: string,\n format: ConfigFormat\n): Promise<TreqConfigInput> {\n switch (format) {\n case 'jsonc':\n return await loadJsoncConfigFile(configPath);\n case 'json':\n return await loadJsonConfigFile(configPath);\n case 'ts':\n case 'js':\n case 'mjs':\n return await importConfigFile(configPath);\n default:\n throw new Error(`Unknown config format: ${format}`);\n }\n}\n\n/**\n * Load config from an explicit path or by searching upwards.\n *\n * Discovery order (when searching):\n * 1. treq.jsonc (preferred)\n * 2. treq.json\n * 3. treq.config.ts (legacy, deprecated)\n * 4. treq.config.js (legacy, deprecated)\n * 5. treq.config.mjs (legacy, deprecated)\n *\n * Node/Bun only (not renderer-safe).\n */\nexport async function loadConfig(options: LoadConfigOptions): Promise<LoadedConfig> {\n const filename = 'filename' in options && options.filename ? options.filename : undefined;\n\n let configPath: string | undefined;\n let format: ConfigFormat | undefined;\n\n if ('path' in options) {\n configPath = path.resolve(options.path);\n format = getFormatFromPath(configPath);\n if (!(await fileExists(configPath))) {\n return { config: {} };\n }\n } else {\n const found = await findUp(options.startDir, filename, options.stopDir);\n if (!found) {\n return { config: {} };\n }\n configPath = found.path;\n format = found.format;\n }\n\n const config = await loadConfigByFormat(configPath, format);\n return { path: configPath, config, format };\n}\n\n/**\n * Check if a config format is a legacy format (TS/JS).\n */\nexport function isLegacyFormat(format: ConfigFormat | undefined): boolean {\n return format === 'ts' || format === 'js' || format === 'mjs';\n}\n",
|
|
9
|
+
"import type { TreqConfigInput, TreqDefaults, TreqProfileInput } from './types';\n\nexport type MergeConfigInputs = {\n defaults?: TreqConfigInput;\n file?: TreqConfigInput;\n overrides?: TreqConfigInput;\n};\n\n/**\n * Deep merge two defaults objects, with proper header merging.\n */\nfunction mergeDefaults(\n base: TreqDefaults | undefined,\n overlay: TreqDefaults | undefined\n): TreqDefaults | undefined {\n if (!base && !overlay) return undefined;\n if (!base) return overlay;\n if (!overlay) return base;\n\n return {\n ...base,\n ...overlay,\n headers: {\n ...(base.headers ?? {}),\n ...(overlay.headers ?? {})\n }\n };\n}\n\n/**\n * Merge two config inputs with deep merge for defaults.\n */\nfunction mergeTwo(base: TreqConfigInput, overlay: TreqConfigInput): TreqConfigInput {\n const result: TreqConfigInput = {};\n\n // Merge variables\n const mergedVariables = {\n ...(base.variables ?? {}),\n ...(overlay.variables ?? {})\n };\n if (Object.keys(mergedVariables).length > 0) {\n result.variables = mergedVariables;\n }\n\n // Merge resolvers\n const mergedResolvers = {\n ...(base.resolvers ?? {}),\n ...(overlay.resolvers ?? {})\n };\n if (Object.keys(mergedResolvers).length > 0) {\n result.resolvers = mergedResolvers;\n }\n\n // Merge defaults\n const mergedDefaults = mergeDefaults(base.defaults, overlay.defaults);\n if (mergedDefaults) {\n result.defaults = mergedDefaults;\n }\n\n // Merge cookies\n const mergedCookies = {\n ...(base.cookies ?? {}),\n ...(overlay.cookies ?? {})\n };\n if (Object.keys(mergedCookies).length > 0) {\n result.cookies = mergedCookies;\n }\n\n // Merge security\n const mergedSecurity = {\n ...(base.security ?? {}),\n ...(overlay.security ?? {})\n };\n if (Object.keys(mergedSecurity).length > 0) {\n result.security = mergedSecurity;\n }\n\n // Profiles are NOT merged - they come from the base config only\n if (base.profiles) {\n result.profiles = base.profiles;\n }\n\n return result;\n}\n\n/**\n * Merge multiple config sources with \"last wins\" semantics.\n *\n * Order: defaults < file < overrides\n */\nexport function mergeConfig(inputs: MergeConfigInputs): TreqConfigInput {\n let result: TreqConfigInput = {};\n\n if (inputs.defaults) {\n result = mergeTwo(result, inputs.defaults);\n }\n\n if (inputs.file) {\n result = mergeTwo(result, inputs.file);\n }\n\n if (inputs.overrides) {\n result = mergeTwo(result, inputs.overrides);\n }\n\n return result;\n}\n\n/**\n * Apply a profile to a config input.\n *\n * IMPORTANT: This operates on INPUT config, NOT ResolvedConfig.\n * Returns a new TreqConfigInput with the profile's overrides applied.\n *\n * @param input - The config input to apply the profile to\n * @param profileName - The name of the profile to apply\n * @returns The config with profile applied\n * @throws Error if the profile doesn't exist\n */\nexport function applyProfile(input: TreqConfigInput, profileName?: string): TreqConfigInput {\n if (!profileName) {\n return input;\n }\n\n const profiles = input.profiles ?? {};\n const profile = profiles[profileName];\n\n if (!profile) {\n const available = Object.keys(profiles);\n const hint = available.length > 0 ? ` Available profiles: ${available.join(', ')}` : '';\n throw new Error(`Profile \"${profileName}\" not found.${hint}`);\n }\n\n // Create base config without profiles (profiles are not inherited)\n const base: TreqConfigInput = {};\n\n if (input.variables) base.variables = input.variables;\n if (input.defaults) base.defaults = input.defaults;\n if (input.cookies) base.cookies = input.cookies;\n if (input.resolvers) base.resolvers = input.resolvers;\n if (input.security) base.security = input.security;\n // Explicitly NOT including profiles\n\n // Merge profile on top\n return mergeTwo(base, profile);\n}\n\n/**\n * Get sorted list of profile names from a config.\n */\nexport function listProfiles(config: TreqConfigInput): string[] {\n const profiles = config.profiles ?? {};\n return Object.keys(profiles).sort();\n}\n\n/**\n * Merge profile input with deep merge for defaults.\n * Used internally for combining profile with base config.\n */\nexport function mergeProfileInput(\n base: TreqProfileInput,\n overlay: TreqProfileInput\n): TreqProfileInput {\n const result: TreqProfileInput = {};\n\n // Merge variables\n const mergedVariables = {\n ...(base.variables ?? {}),\n ...(overlay.variables ?? {})\n };\n if (Object.keys(mergedVariables).length > 0) {\n result.variables = mergedVariables;\n }\n\n // Merge resolvers\n const mergedResolvers = {\n ...(base.resolvers ?? {}),\n ...(overlay.resolvers ?? {})\n };\n if (Object.keys(mergedResolvers).length > 0) {\n result.resolvers = mergedResolvers;\n }\n\n // Merge defaults\n const mergedDefaults = mergeDefaults(base.defaults, overlay.defaults);\n if (mergedDefaults) {\n result.defaults = mergedDefaults;\n }\n\n // Merge cookies\n const mergedCookies = {\n ...(base.cookies ?? {}),\n ...(overlay.cookies ?? {})\n };\n if (Object.keys(mergedCookies).length > 0) {\n result.cookies = mergedCookies;\n }\n\n return result;\n}\n",
|
|
10
|
+
"import { existsSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { createCommandResolver, isCommandResolverDef } from '../resolver/command';\nimport type { Resolver } from '../types';\nimport { isLegacyFormat, loadConfig } from './load';\nimport { applyProfile, mergeConfig } from './merge';\nimport { applySubstitutions } from './substitution';\nimport type {\n CommandResolverDef,\n ConfigMeta,\n ResolvedConfig,\n ResolvedCookiesConfig,\n ResolvedDefaults,\n ResolvedProjectConfig,\n TreqConfigInput\n} from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nexport const DEFAULT_TIMEOUT_MS = 30_000;\nexport const DEFAULT_FOLLOW_REDIRECTS = true;\nexport const DEFAULT_VALIDATE_SSL = true;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type ConfigOverrideLayer = {\n /**\n * A human-friendly layer name for introspection (e.g. \"env\", \"cli\", \"session\", \"request\").\n */\n name: string;\n overrides: Partial<TreqConfigInput>;\n};\n\nexport type ResolveProjectConfigOptions = {\n /**\n * Directory to start searching for config from.\n */\n startDir: string;\n\n /**\n * Stop searching at this directory (e.g., workspace root).\n * Prevents accidentally picking a parent config outside the intended workspace.\n */\n stopDir?: string;\n\n /**\n * Profile to apply.\n */\n profile?: string;\n\n /**\n * Named override layers to apply, in-order, after profile.\n * Each layer is merged with \"last wins\" semantics.\n */\n overrideLayers?: ConfigOverrideLayer[];\n\n /**\n * Overrides to apply (from CLI flags, environment files, etc.).\n * Applied after profile.\n */\n overrides?: Partial<TreqConfigInput>;\n\n /**\n * Layer name recorded in metadata for the `overrides` option.\n * Defaults to \"overrides\".\n */\n overridesLayerName?: string;\n};\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Find git root directory by walking up from startDir.\n */\nfunction findGitRoot(startDir: string, stopDir?: string): string | undefined {\n let dir = path.resolve(startDir);\n const stop = stopDir ? path.resolve(stopDir) : undefined;\n const { root } = path.parse(dir);\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const gitPath = path.join(dir, '.git');\n if (existsSync(gitPath)) {\n return dir;\n }\n\n if (stop && dir === stop) return undefined;\n\n if (dir === root) return undefined;\n const parent = path.dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n}\n\n/**\n * Determine project root based on config path or fallbacks.\n */\nfunction determineProjectRoot(\n configPath: string | undefined,\n startDir: string,\n workspaceRoot?: string\n): string {\n // If config file found, use its directory\n if (configPath) {\n return path.dirname(configPath);\n }\n\n // Fallback: git root → workspace root → startDir\n const resolvedWorkspace = workspaceRoot ? path.resolve(workspaceRoot) : undefined;\n const gitRoot = findGitRoot(startDir, resolvedWorkspace);\n return gitRoot ?? resolvedWorkspace ?? path.resolve(startDir);\n}\n\n/**\n * Resolve cookie config to ResolvedCookiesConfig.\n */\nfunction resolveCookies(cookies: TreqConfigInput['cookies']): ResolvedCookiesConfig {\n const enabled = cookies?.enabled !== false; // Default: true\n const jarPath = cookies?.jarPath;\n\n let mode: 'disabled' | 'memory' | 'persistent';\n if (!enabled) {\n mode = 'disabled';\n } else if (jarPath) {\n mode = 'persistent';\n } else {\n mode = 'memory';\n }\n\n const result: ResolvedCookiesConfig = {\n enabled,\n mode\n };\n\n // Only include jarPath if defined\n if (jarPath) {\n result.jarPath = jarPath;\n }\n\n return result;\n}\n\n/**\n * Resolve defaults to ResolvedDefaults with all required fields.\n */\nfunction resolveDefaults(defaults: TreqConfigInput['defaults']): ResolvedDefaults {\n const result: ResolvedDefaults = {\n timeoutMs: defaults?.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n followRedirects: defaults?.followRedirects ?? DEFAULT_FOLLOW_REDIRECTS,\n validateSSL: defaults?.validateSSL ?? DEFAULT_VALIDATE_SSL,\n headers: defaults?.headers ?? {}\n };\n\n // Only include proxy if defined\n if (defaults?.proxy) {\n result.proxy = defaults.proxy;\n }\n\n return result;\n}\n\n/**\n * Compile resolvers: convert CommandResolverDef to Resolver functions.\n */\nfunction compileResolvers(\n input: Record<string, Resolver | CommandResolverDef> | undefined,\n projectRoot: string\n): Record<string, Resolver> {\n if (!input) {\n return {};\n }\n\n const result: Record<string, Resolver> = {};\n\n for (const [name, def] of Object.entries(input)) {\n if (typeof def === 'function') {\n // Already a function resolver\n result[name] = def;\n } else if (isCommandResolverDef(def)) {\n // Command resolver - compile to function with name for error messages\n result[name] = createCommandResolver(def, projectRoot, name);\n } else {\n throw new Error(\n `Unknown resolver type for \"${name}\". Expected function or { type: \"command\", command: [...] }`\n );\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Main API\n// ============================================================================\n\n/**\n * Resolve project configuration - the single source of truth for both CLI and server.\n *\n * This function:\n * 1. Discovers config file (treq.jsonc, treq.json, treq.config.ts, etc.)\n * 2. Parses the config\n * 3. Applies substitutions ({env:}, {file:})\n * 4. Applies profile overlay\n * 5. Applies overrides (CLI flags, etc.)\n * 6. Compiles resolvers (CommandResolverDef → Resolver wrapper)\n * 7. Resolves cookies and defaults\n * 8. Returns { config: ResolvedConfig, meta: ConfigMeta }\n *\n * @param options - Configuration options\n * @returns ResolvedProjectConfig with config and metadata\n */\nexport async function resolveProjectConfig(\n options: ResolveProjectConfigOptions\n): Promise<ResolvedProjectConfig> {\n const warnings: string[] = [];\n const layersApplied: string[] = [];\n\n // Step 1: Load config file\n const loadOptions: { startDir: string; stopDir?: string } = {\n startDir: options.startDir\n };\n if (options.stopDir) {\n loadOptions.stopDir = options.stopDir;\n }\n const loaded = await loadConfig(loadOptions);\n\n const resolvedWorkspaceRoot = options.stopDir ? path.resolve(options.stopDir) : undefined;\n\n // Determine project root (locked semantics)\n const projectRoot = determineProjectRoot(loaded.path, options.startDir, resolvedWorkspaceRoot);\n\n // Track warnings for legacy formats\n if (loaded.path && isLegacyFormat(loaded.format)) {\n warnings.push(`${path.basename(loaded.path)} is deprecated; migrate to treq.jsonc`);\n }\n\n // Step 2: Start building merged config\n let mergedConfig: TreqConfigInput = {};\n\n if (loaded.config && Object.keys(loaded.config).length > 0) {\n layersApplied.push('file');\n mergedConfig = loaded.config;\n }\n\n // Step 3: Apply profile if specified\n if (options.profile) {\n mergedConfig = applyProfile(mergedConfig, options.profile);\n layersApplied.push(`profile:${options.profile}`);\n }\n\n // Step 4: Apply named override layers (e.g. session, request, env, cli)\n for (const layer of options.overrideLayers ?? []) {\n if (!layer || !layer.overrides || Object.keys(layer.overrides).length === 0) continue;\n mergedConfig = mergeConfig({\n file: mergedConfig,\n overrides: layer.overrides as TreqConfigInput\n });\n layersApplied.push(layer.name);\n }\n\n // Step 5: Apply overrides (single final layer)\n if (options.overrides && Object.keys(options.overrides).length > 0) {\n mergedConfig = mergeConfig({\n file: mergedConfig,\n overrides: options.overrides as TreqConfigInput\n });\n layersApplied.push(options.overridesLayerName ?? 'overrides');\n }\n\n // Step 6: Apply substitutions to the fully-merged config\n const workspaceRoot = resolvedWorkspaceRoot ?? projectRoot;\n const configDir = loaded.path ? path.dirname(loaded.path) : projectRoot;\n const allowExternalFiles = mergedConfig.security?.allowExternalFiles ?? false;\n\n mergedConfig = applySubstitutions(mergedConfig, {\n configDir,\n workspaceRoot,\n allowExternalFiles\n }) as TreqConfigInput;\n\n // Step 7: Compile resolvers\n const resolvers = compileResolvers(mergedConfig.resolvers, projectRoot);\n\n // Step 8: Build final resolved config\n const config: ResolvedConfig = {\n projectRoot,\n variables: mergedConfig.variables ?? {},\n defaults: resolveDefaults(mergedConfig.defaults),\n cookies: resolveCookies(mergedConfig.cookies),\n resolvers,\n security: {\n allowExternalFiles: mergedConfig.security?.allowExternalFiles ?? false\n }\n };\n\n // Step 9: Build metadata\n const meta: ConfigMeta = {\n projectRoot,\n layersApplied,\n warnings\n };\n\n // Only include optional fields if defined\n if (loaded.path) {\n meta.configPath = loaded.path;\n }\n if (loaded.format) {\n meta.format = loaded.format;\n }\n if (options.profile) {\n meta.profile = options.profile;\n }\n\n return { config, meta };\n}\n",
|
|
11
|
+
"import { spawn } from 'node:child_process';\nimport type { CommandResolverDef } from '../config/types';\nimport type { Resolver } from '../types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_TIMEOUT_MS = 2000;\nconst GRACE_PERIOD_MS = 500;\nconst MAX_STDOUT_BYTES = 1024 * 1024; // 1MB\nconst MAX_STDERR_BYTES = 64 * 1024; // 64KB\n\n// ============================================================================\n// Types\n// ============================================================================\n\ntype ResolverRequest = {\n resolver: string;\n args: string[];\n};\n\ntype ResolverResponse = {\n value: string;\n};\n\n// ============================================================================\n// Command Execution\n// ============================================================================\n\n/**\n * Execute a command resolver and return its value.\n */\nasync function executeCommandResolver(\n def: CommandResolverDef,\n name: string,\n args: string[],\n projectRoot: string\n): Promise<string> {\n const timeoutMs = def.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n if (def.command.length === 0) {\n throw new Error(`Resolver \"${name}\" has empty command array`);\n }\n\n const [cmd, ...cmdArgs] = def.command;\n if (!cmd) {\n throw new Error(`Resolver \"${name}\" has empty command`);\n }\n\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, cmdArgs, {\n cwd: projectRoot,\n stdio: ['pipe', 'pipe', 'pipe'],\n env: process.env\n });\n\n let stdout = '';\n let stderr = '';\n let stdoutBytes = 0;\n let stderrBytes = 0;\n let stdoutTruncated = false;\n let stderrTruncated = false;\n let killed = false;\n let timedOut = false;\n\n // Set up timeout\n const timeoutHandle = setTimeout(() => {\n timedOut = true;\n child.kill('SIGTERM');\n\n // Grace period before SIGKILL\n setTimeout(() => {\n if (!killed) {\n child.kill('SIGKILL');\n }\n }, GRACE_PERIOD_MS);\n }, timeoutMs);\n\n // Handle stdout\n child.stdout?.on('data', (data: Buffer) => {\n const remaining = MAX_STDOUT_BYTES - stdoutBytes;\n if (remaining > 0) {\n const chunk = data.slice(0, remaining);\n stdout += chunk.toString('utf-8');\n stdoutBytes += chunk.length;\n if (chunk.length < data.length) {\n stdoutTruncated = true;\n }\n } else {\n stdoutTruncated = true;\n }\n });\n\n // Handle stderr\n child.stderr?.on('data', (data: Buffer) => {\n const remaining = MAX_STDERR_BYTES - stderrBytes;\n if (remaining > 0) {\n const chunk = data.slice(0, remaining);\n stderr += chunk.toString('utf-8');\n stderrBytes += chunk.length;\n if (chunk.length < data.length) {\n stderrTruncated = true;\n }\n } else {\n stderrTruncated = true;\n }\n });\n\n // Handle close\n child.on('close', (code, signal) => {\n clearTimeout(timeoutHandle);\n killed = true;\n\n if (timedOut) {\n reject(\n new Error(\n `Resolver \"${name}\" timed out after ${timeoutMs}ms${stderr ? `: ${stderr}` : ''}`\n )\n );\n return;\n }\n\n if (code !== 0) {\n const exitInfo = signal ? `killed by signal ${signal}` : `exit code ${code}`;\n reject(new Error(`Resolver \"${name}\" failed (${exitInfo})${stderr ? `: ${stderr}` : ''}`));\n return;\n }\n\n // Parse output as NDJSON\n const trimmed = stdout.trim();\n if (!trimmed) {\n reject(new Error(`Resolver \"${name}\" returned no output`));\n return;\n }\n\n // Take first non-empty line (NDJSON protocol) and tolerate CRLF\n const firstLine =\n trimmed\n .split(/\\r?\\n/)\n .map((l) => l.trim())\n .find((l) => l.length > 0) ?? '';\n\n let response: ResolverResponse;\n try {\n response = JSON.parse(firstLine) as ResolverResponse;\n } catch {\n const suffix = stdoutTruncated ? ' (stdout exceeded 1MB limit and was truncated)' : '';\n reject(\n new Error(`Resolver \"${name}\" returned invalid JSON: ${firstLine.slice(0, 100)}${suffix}`)\n );\n return;\n }\n\n if (typeof response.value !== 'string') {\n reject(new Error(`Resolver \"${name}\" returned no value (expected { \"value\": \"...\" })`));\n return;\n }\n\n resolve(response.value);\n });\n\n // Handle error\n child.on('error', (err) => {\n clearTimeout(timeoutHandle);\n const suffix = stderrTruncated ? ' (stderr exceeded 64KB limit and was truncated)' : '';\n reject(new Error(`Resolver \"${name}\" failed to execute: ${err.message}${suffix}`));\n });\n\n // Write request to stdin\n const request: ResolverRequest = {\n resolver: name,\n args\n };\n\n child.stdin?.write(`${JSON.stringify(request)}\\n`);\n child.stdin?.end();\n });\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Create a Resolver function from a CommandResolverDef.\n *\n * The created resolver executes the external command with the NDJSON protocol:\n * - Writes `{\"resolver\":\"name\",\"args\":[\"arg1\",\"arg2\"]}` to stdin\n * - Expects `{\"value\":\"result\"}` on stdout\n *\n * @param def - The command resolver definition\n * @param name - The resolver name (used for error messages)\n * @param projectRoot - The project root directory (used as cwd)\n */\nexport function createCommandResolver(\n def: CommandResolverDef,\n projectRoot: string,\n name?: string\n): Resolver {\n const resolverName = name ?? '$command';\n return async (...args: string[]): Promise<string> => {\n return await executeCommandResolver(def, resolverName, args, projectRoot);\n };\n}\n\n/**\n * Check if a resolver definition is a command resolver.\n */\nexport function isCommandResolverDef(def: unknown): def is CommandResolverDef {\n if (!def || typeof def !== 'object') return false;\n const obj = def as Record<string, unknown>;\n return obj['type'] === 'command' && Array.isArray(obj['command']);\n}\n",
|
|
12
|
+
"import { accessSync, readFileSync, realpathSync } from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type SubstitutionOptions = {\n /**\n * Directory containing the config file (for relative path resolution)\n */\n configDir: string;\n\n /**\n * Workspace root for security scoping\n */\n workspaceRoot: string;\n\n /**\n * Allow file reads outside workspace (e.g., ~/.treq/token)\n * Default: false\n */\n allowExternalFiles?: boolean;\n};\n\n// ============================================================================\n// Token patterns\n// ============================================================================\n\n// Matches {env:VAR_NAME}\nconst ENV_PATTERN = /\\{env:([^}]+)\\}/g;\n\n// Matches {file:path}\nconst FILE_PATTERN = /\\{file:([^}]+)\\}/g;\n\n// ============================================================================\n// Path utilities\n// ============================================================================\n\nfunction expandHome(p: string): string {\n if (p.startsWith('~/') || p === '~') {\n return path.join(os.homedir(), p.slice(1));\n }\n return p;\n}\n\nfunction isWithinWorkspace(filePath: string, workspaceRoot: string): boolean {\n const resolvedPath = path.resolve(filePath);\n const resolvedWorkspace = path.resolve(workspaceRoot);\n\n // Use relative path check\n const rel = path.relative(resolvedWorkspace, resolvedPath);\n\n // If relative path is empty, it's the workspace root itself\n if (rel === '') return true;\n\n // If relative path is absolute, it's outside\n if (path.isAbsolute(rel)) return false;\n\n // If it starts with '..', it's outside\n if (rel.startsWith('..')) return false;\n\n return true;\n}\n\nfunction realpathSafe(p: string): string {\n try {\n return realpathSync(p);\n } catch {\n return path.resolve(p);\n }\n}\n\nfunction fileExists(p: string): boolean {\n try {\n accessSync(p);\n return true;\n } catch {\n return false;\n }\n}\n\n// ============================================================================\n// Substitution functions\n// ============================================================================\n\nfunction substituteEnvVars(value: string): string {\n return value.replace(ENV_PATTERN, (_, varName: string) => {\n return process.env[varName] ?? '';\n });\n}\n\nfunction substituteFileRefs(value: string, options: SubstitutionOptions): string {\n return value.replace(FILE_PATTERN, (_match, filePath: string) => {\n // Expand ~ to home directory\n let expanded = expandHome(filePath.trim());\n\n // Resolve relative paths from config directory\n if (!path.isAbsolute(expanded)) {\n expanded = path.resolve(options.configDir, expanded);\n }\n\n // Check file exists before attempting to resolve symlinks\n if (!fileExists(expanded)) {\n throw new Error(`Config substitution failed: {file:${filePath}} not found at ${expanded}`);\n }\n\n const workspaceReal = realpathSafe(options.workspaceRoot);\n const fileReal = realpathSafe(expanded);\n\n // Security check: ensure file is within workspace (unless external files allowed)\n if (!options.allowExternalFiles) {\n if (!isWithinWorkspace(fileReal, workspaceReal)) {\n throw new Error(\n `Config substitution failed: {file:${filePath}} outside workspace ` +\n `(use security.allowExternalFiles to allow)`\n );\n }\n }\n\n // Read and return file contents with trimEnd\n try {\n const content = readFileSync(fileReal, 'utf-8');\n return content.trimEnd();\n } catch (err) {\n throw new Error(\n `Config substitution failed: {file:${filePath}} could not be read: ` +\n `${err instanceof Error ? err.message : String(err)}`\n );\n }\n });\n}\n\nfunction substituteString(value: string, options: SubstitutionOptions): string {\n // Apply env substitutions first\n let result = substituteEnvVars(value);\n\n // Then apply file substitutions\n result = substituteFileRefs(result, options);\n\n return result;\n}\n\n// ============================================================================\n// Object traversal\n// ============================================================================\n\n/**\n * Apply substitutions to all string values in an object/array.\n * Only substitutes within string values, not keys.\n *\n * @param obj - The object to process\n * @param options - Substitution options\n * @returns A new object with substitutions applied\n */\nexport function applySubstitutions(obj: unknown, options: SubstitutionOptions): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (typeof obj === 'string') {\n return substituteString(obj, options);\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => applySubstitutions(item, options));\n }\n\n if (typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n // Don't substitute keys, only values\n result[key] = applySubstitutions(value, options);\n }\n return result;\n }\n\n // Primitives (number, boolean) pass through unchanged\n return obj;\n}\n"
|
|
8
13
|
],
|
|
9
|
-
"mappings": "AAKO,SAAS,CAAY,CAAC,EAAgC,CAC3D,OAAO,ECNT,iBAAS,yBACT,4BACA,wBAAS,iBAOT,IAAM,EAAmB,iBAEzB,eAAe,CAAU,CAAC,EAA6B,CACrD,GAAI,CAEF,OADA,MAAM,EAAO,CAAC,EACP,GACP,KAAM,CACN,MAAO,IAIX,eAAe,CAAM,CACnB,EACA,EACA,EAC6B,CAC7B,IAAI,EAAW,UAAQ,CAAQ,EACzB,EAAO,EAAe,UAAQ,CAAO,EAAI,OAG/C,MAAO,GAAM,CACX,IAAM,EAAiB,OAAK,EAAK,CAAQ,EACzC,GAAI,MAAM,EAAW,CAAS,EAAG,OAAO,EAExC,GAAI,GAAQ,IAAQ,EAAM,OAE1B,IAAM,EAAc,UAAQ,CAAG,EAC/B,GAAI,IAAW,EAAK,OACpB,EAAM,GAIV,eAAe,CAAgB,CAAC,EAAyC,CAGvE,IAAM,GADO,MAAa,OADd,EAAc,CAAU,EAAE,OAEtB,QAChB,GAAI,CAAC,GAAO,OAAO,IAAQ,SACzB,MAAU,MAAM,8BAA8B,oCAA6C,EAE7F,OAAO,EAQT,eAAsB,CAAU,CAAC,EAAmD,CAClF,IAAM,EAAW,aAAc,GAAW,EAAQ,SAAW,EAAQ,SAAW,EAE1E,EACJ,SAAU,EACD,UAAQ,EAAQ,IAAI,EACzB,MAAM,EAAO,EAAQ,SAAU,EAAU,EAAQ,OAAO,EAE9D,GAAI,CAAC,EACH,MAAO,CAAE,OAAQ,CAAC,CAAE,EAGtB,MAAO,CAAE,KAAM,EAAY,OAAQ,MAAM,EAAiB,CAAU,CAAE,EC5DjE,SAAS,CAAW,CAAC,EAAuC,CACjE,IAAM,EAAO,EAAO,UAAY,CAAC,EAC3B,EAAO,EAAO,MAAQ,CAAC,EACvB,EAAY,EAAO,WAAa,CAAC,EAEvC,MAAO,CACL,UAAW,IACL,EAAK,WAAa,CAAC,KACnB,EAAK,WAAa,CAAC,KACnB,EAAU,WAAa,CAAC,CAC9B,EACA,UAAW,IACL,EAAK,WAAa,CAAC,KACnB,EAAK,WAAa,CAAC,KACnB,EAAU,WAAa,CAAC,CAC9B,EACA,SAAU,IACJ,EAAK,UAAY,CAAC,KAClB,EAAK,UAAY,CAAC,KAClB,EAAU,UAAY,CAAC,EAC3B,QAAS,IACH,EAAK,UAAU,SAAW,CAAC,KAC3B,EAAK,UAAU,SAAW,CAAC,KAC3B,EAAU,UAAU,SAAW,CAAC,CACtC,CACF,EACA,QAAS,IACH,EAAK,SAAW,CAAC,KACjB,EAAK,SAAW,CAAC,KACjB,EAAU,SAAW,CAAC,CAC5B,CACF",
|
|
10
|
-
"debugId": "
|
|
14
|
+
"mappings": "sEAKO,SAAS,CAAY,CAAC,EAAgC,CAC3D,OAAO,ECoDF,SAAS,CAAkB,CAAC,EAA0D,CAC3F,IAAQ,SAAQ,cAAa,WAAY,EAGnC,EAA8B,CAAC,EAGrC,GAAI,EAAO,QAAQ,SAAW,EAC5B,EAAc,YAAc,EAU9B,GANA,EAAc,eAAiB,EAAO,SAAS,QAG/C,EAAc,UAAY,EAAO,UAG7B,EACF,EAAc,QAAU,EAI1B,IAAM,EAAmC,CACvC,UAAW,EAAO,SAAS,UAC3B,gBAAiB,EAAO,SAAS,gBACjC,YAAa,EAAO,SAAS,WAC/B,EAGA,GAAI,EAAO,SAAS,MAClB,EAAgB,MAAQ,EAAO,SAAS,MAG1C,MAAO,CAAE,gBAAe,iBAAgB,EC/EnC,SAAS,CAAiB,CAAC,EAAyB,CACzD,IAAM,EAAmB,CAAC,EACtB,EAAe,OACf,EAAI,EAEF,EAA+B,IAAY,CAI/C,MAAO,EAAO,OAAS,EAAG,CACxB,IAAM,EAAO,EAAO,EAAO,OAAS,GACpC,GAAI,IAAS,KAAO,IAAS,KAAM,CACjC,EAAO,IAAI,EACX,SAEF,QAIJ,MAAO,EAAI,EAAQ,OAAQ,CAEzB,IAAM,EAAO,EAAQ,OAAO,CAAC,EACvB,EAAO,EAAQ,EAAI,GAEzB,OAAQ,OACD,OACH,GAAI,IAAS,IAEX,EAAO,KAAK,CAAI,EAChB,EAAQ,SACR,IACK,QAAI,IAAS,KAAO,IAAS,IAElC,EAA6B,EAC7B,EAAQ,eACR,GAAK,EACA,QAAI,IAAS,KAAO,IAAS,IAElC,EAAQ,gBACR,GAAK,EAEL,OAAO,KAAK,CAAI,EAChB,IAEF,UAEG,SAEH,GADA,EAAO,KAAK,CAAI,EACZ,IAAS,MAAQ,IAAS,OAE5B,EAAO,KAAK,CAAI,EAChB,GAAK,EACA,QAAI,IAAS,IAElB,EAAQ,OACR,IAEA,SAEF,UAEG,eACH,GAAI,IAAS;AAAA,GAAQ,IAAS,KAE5B,EAAO,KAAK,CAAI,EAChB,EAAQ,OAEV,IACA,UAEG,gBACH,GAAI,IAAS,KAAO,IAAS,IAE3B,EAAQ,OACR,GAAK,EACA,KAEL,GAAI,IAAS;AAAA,GAAQ,IAAS,KAC5B,EAAO,KAAK,CAAI,EAElB,IAEF,OAIN,OAAO,EAAO,KAAK,EAAE,EAQhB,SAAS,CAAuB,CAAC,EAAoB,CAC1D,IAAM,EAAW,EAAkB,CAAO,EAC1C,GAAI,CACF,OAAO,KAAK,MAAM,CAAQ,EAC1B,MAAO,EAAK,CACZ,GAAI,aAAe,YACjB,MAAU,YAAY,kBAAkB,EAAI,SAAS,EAEvD,MAAM,GCnHV,iBAAS,cAAQ,yBACjB,4BACA,wBAAS,iBAST,IAAM,EAAkE,CACtE,CAAE,SAAU,aAAc,OAAQ,OAAQ,EAC1C,CAAE,SAAU,YAAa,OAAQ,MAAO,EACxC,CAAE,SAAU,iBAAkB,OAAQ,IAAK,EAC3C,CAAE,SAAU,iBAAkB,OAAQ,IAAK,EAC3C,CAAE,SAAU,kBAAmB,OAAQ,KAAM,CAC/C,EAEA,eAAe,CAAU,CAAC,EAA6B,CACrD,GAAI,CAEF,OADA,MAAM,EAAO,CAAC,EACP,GACP,KAAM,CACN,MAAO,IAQX,eAAe,CAAM,CACnB,EACA,EACA,EAC6D,CAC7D,IAAI,EAAW,UAAQ,CAAQ,EACzB,EAAO,EAAe,UAAQ,CAAO,EAAI,OAG/C,MAAO,GAAM,CAEX,GAAI,EAAU,CACZ,IAAM,EAAiB,OAAK,EAAK,CAAQ,EACzC,GAAI,MAAM,EAAW,CAAS,EAAG,CAE/B,IAAM,EAAS,EAAsB,CAAQ,EAC7C,MAAO,CAAE,KAAM,EAAW,QAAO,GAInC,aAAa,SAAU,EAAO,YAAY,EAAc,CACtD,IAAM,EAAiB,OAAK,EAAK,CAAK,EACtC,GAAI,MAAM,EAAW,CAAS,EAC5B,MAAO,CAAE,KAAM,EAAW,QAAO,EAKvC,GAAI,GAAQ,IAAQ,EAAM,OAE1B,IAAM,EAAc,UAAQ,CAAG,EAC/B,GAAI,IAAW,EAAK,OACpB,EAAM,GAIV,SAAS,CAAqB,CAAC,EAAgC,CAC7D,GAAI,EAAS,SAAS,QAAQ,EAAG,MAAO,QACxC,GAAI,EAAS,SAAS,OAAO,EAAG,MAAO,OACvC,GAAI,EAAS,SAAS,KAAK,EAAG,MAAO,KACrC,GAAI,EAAS,SAAS,MAAM,EAAG,MAAO,MACtC,GAAI,EAAS,SAAS,KAAK,EAAG,MAAO,KACrC,MAAO,OAGT,SAAS,EAAiB,CAAC,EAAkC,CAC3D,IAAM,EAAgB,WAAS,CAAU,EACzC,OAAO,EAAsB,CAAQ,EAGvC,eAAe,EAAgB,CAAC,EAA8C,CAG5E,IAAM,GADO,MAAa,OADd,EAAc,CAAU,EAAE,OAEtB,QAChB,GAAI,CAAC,GAAO,OAAO,IAAQ,SACzB,MAAU,MAAM,8BAA8B,oCAA6C,EAE7F,OAAO,EAGT,eAAe,EAAmB,CAAC,EAA8C,CAC/E,IAAM,EAAU,MAAM,EAAS,EAAY,OAAO,EAC5C,EAAS,EAA4B,CAAO,EAClD,GAAI,CAAC,GAAU,OAAO,IAAW,SAC/B,MAAU,MAAM,2BAA2B,qBAA8B,EAE3E,OAAO,EAGT,eAAe,EAAkB,CAAC,EAA8C,CAC9E,IAAM,EAAU,MAAM,EAAS,EAAY,OAAO,EAC5C,EAAS,KAAK,MAAM,CAAO,EACjC,GAAI,CAAC,GAAU,OAAO,IAAW,SAC/B,MAAU,MAAM,0BAA0B,qBAA8B,EAE1E,OAAO,EAGT,eAAe,EAAkB,CAC/B,EACA,EAC0B,CAC1B,OAAQ,OACD,QACH,OAAO,MAAM,GAAoB,CAAU,MACxC,OACH,OAAO,MAAM,GAAmB,CAAU,MACvC,SACA,SACA,MACH,OAAO,MAAM,GAAiB,CAAU,UAExC,MAAU,MAAM,0BAA0B,GAAQ,GAgBxD,eAAsB,CAAU,CAAC,EAAmD,CAClF,IAAM,EAAW,aAAc,GAAW,EAAQ,SAAW,EAAQ,SAAW,OAE5E,EACA,EAEJ,GAAI,SAAU,GAGZ,GAFA,EAAkB,UAAQ,EAAQ,IAAI,EACtC,EAAS,GAAkB,CAAU,EACjC,CAAE,MAAM,EAAW,CAAU,EAC/B,MAAO,CAAE,OAAQ,CAAC,CAAE,EAEjB,KACL,IAAM,EAAQ,MAAM,EAAO,EAAQ,SAAU,EAAU,EAAQ,OAAO,EACtE,GAAI,CAAC,EACH,MAAO,CAAE,OAAQ,CAAC,CAAE,EAEtB,EAAa,EAAM,KACnB,EAAS,EAAM,OAGjB,IAAM,EAAS,MAAM,GAAmB,EAAY,CAAM,EAC1D,MAAO,CAAE,KAAM,EAAY,SAAQ,QAAO,EAMrC,SAAS,CAAc,CAAC,EAA2C,CACxE,OAAO,IAAW,MAAQ,IAAW,MAAQ,IAAW,MC9J1D,SAAS,EAAa,CACpB,EACA,EAC0B,CAC1B,GAAI,CAAC,GAAQ,CAAC,EAAS,OACvB,GAAI,CAAC,EAAM,OAAO,EAClB,GAAI,CAAC,EAAS,OAAO,EAErB,MAAO,IACF,KACA,EACH,QAAS,IACH,EAAK,SAAW,CAAC,KACjB,EAAQ,SAAW,CAAC,CAC1B,CACF,EAMF,SAAS,CAAQ,CAAC,EAAuB,EAA2C,CAClF,IAAM,EAA0B,CAAC,EAG3B,EAAkB,IAClB,EAAK,WAAa,CAAC,KACnB,EAAQ,WAAa,CAAC,CAC5B,EACA,GAAI,OAAO,KAAK,CAAe,EAAE,OAAS,EACxC,EAAO,UAAY,EAIrB,IAAM,EAAkB,IAClB,EAAK,WAAa,CAAC,KACnB,EAAQ,WAAa,CAAC,CAC5B,EACA,GAAI,OAAO,KAAK,CAAe,EAAE,OAAS,EACxC,EAAO,UAAY,EAIrB,IAAM,EAAiB,GAAc,EAAK,SAAU,EAAQ,QAAQ,EACpE,GAAI,EACF,EAAO,SAAW,EAIpB,IAAM,EAAgB,IAChB,EAAK,SAAW,CAAC,KACjB,EAAQ,SAAW,CAAC,CAC1B,EACA,GAAI,OAAO,KAAK,CAAa,EAAE,OAAS,EACtC,EAAO,QAAU,EAInB,IAAM,EAAiB,IACjB,EAAK,UAAY,CAAC,KAClB,EAAQ,UAAY,CAAC,CAC3B,EACA,GAAI,OAAO,KAAK,CAAc,EAAE,OAAS,EACvC,EAAO,SAAW,EAIpB,GAAI,EAAK,SACP,EAAO,SAAW,EAAK,SAGzB,OAAO,EAQF,SAAS,CAAW,CAAC,EAA4C,CACtE,IAAI,EAA0B,CAAC,EAE/B,GAAI,EAAO,SACT,EAAS,EAAS,EAAQ,EAAO,QAAQ,EAG3C,GAAI,EAAO,KACT,EAAS,EAAS,EAAQ,EAAO,IAAI,EAGvC,GAAI,EAAO,UACT,EAAS,EAAS,EAAQ,EAAO,SAAS,EAG5C,OAAO,EAcF,SAAS,CAAY,CAAC,EAAwB,EAAuC,CAC1F,GAAI,CAAC,EACH,OAAO,EAGT,IAAM,EAAW,EAAM,UAAY,CAAC,EAC9B,EAAU,EAAS,GAEzB,GAAI,CAAC,EAAS,CACZ,IAAM,EAAY,OAAO,KAAK,CAAQ,EAChC,EAAO,EAAU,OAAS,EAAI,wBAAwB,EAAU,KAAK,IAAI,IAAM,GACrF,MAAU,MAAM,YAAY,gBAA0B,GAAM,EAI9D,IAAM,EAAwB,CAAC,EAE/B,GAAI,EAAM,UAAW,EAAK,UAAY,EAAM,UAC5C,GAAI,EAAM,SAAU,EAAK,SAAW,EAAM,SAC1C,GAAI,EAAM,QAAS,EAAK,QAAU,EAAM,QACxC,GAAI,EAAM,UAAW,EAAK,UAAY,EAAM,UAC5C,GAAI,EAAM,SAAU,EAAK,SAAW,EAAM,SAI1C,OAAO,EAAS,EAAM,CAAO,EAMxB,SAAS,EAAY,CAAC,EAAmC,CAC9D,IAAM,EAAW,EAAO,UAAY,CAAC,EACrC,OAAO,OAAO,KAAK,CAAQ,EAAE,KAAK,ECxJpC,qBAAS,iBACT,4BCDA,gBAAS,4BAQT,IAAM,GAAqB,KACrB,GAAkB,IAClB,GAAmB,QACnB,GAAmB,MAsBzB,eAAe,EAAsB,CACnC,EACA,EACA,EACA,EACiB,CACjB,IAAM,EAAY,EAAI,WAAa,GAEnC,GAAI,EAAI,QAAQ,SAAW,EACzB,MAAU,MAAM,aAAa,4BAA+B,EAG9D,IAAO,KAAQ,GAAW,EAAI,QAC9B,GAAI,CAAC,EACH,MAAU,MAAM,aAAa,sBAAyB,EAGxD,OAAO,IAAI,QAAQ,CAAC,EAAS,IAAW,CACtC,IAAM,EAAQ,GAAM,EAAK,EAAS,CAChC,IAAK,EACL,MAAO,CAAC,OAAQ,OAAQ,MAAM,EAC9B,IAAK,QAAQ,GACf,CAAC,EAEG,EAAS,GACT,EAAS,GACT,EAAc,EACd,EAAc,EACd,EAAkB,GAClB,EAAkB,GAClB,EAAS,GACT,EAAW,GAGT,EAAgB,WAAW,IAAM,CACrC,EAAW,GACX,EAAM,KAAK,SAAS,EAGpB,WAAW,IAAM,CACf,GAAI,CAAC,EACH,EAAM,KAAK,SAAS,GAErB,EAAe,GACjB,CAAS,EAGZ,EAAM,QAAQ,GAAG,OAAQ,CAAC,IAAiB,CACzC,IAAM,EAAY,GAAmB,EACrC,GAAI,EAAY,EAAG,CACjB,IAAM,EAAQ,EAAK,MAAM,EAAG,CAAS,EAGrC,GAFA,GAAU,EAAM,SAAS,OAAO,EAChC,GAAe,EAAM,OACjB,EAAM,OAAS,EAAK,OACtB,EAAkB,GAGpB,OAAkB,GAErB,EAGD,EAAM,QAAQ,GAAG,OAAQ,CAAC,IAAiB,CACzC,IAAM,EAAY,GAAmB,EACrC,GAAI,EAAY,EAAG,CACjB,IAAM,EAAQ,EAAK,MAAM,EAAG,CAAS,EAGrC,GAFA,GAAU,EAAM,SAAS,OAAO,EAChC,GAAe,EAAM,OACjB,EAAM,OAAS,EAAK,OACtB,EAAkB,GAGpB,OAAkB,GAErB,EAGD,EAAM,GAAG,QAAS,CAAC,EAAM,IAAW,CAIlC,GAHA,aAAa,CAAa,EAC1B,EAAS,GAEL,EAAU,CACZ,EACM,MACF,aAAa,sBAAyB,MAAc,EAAS,KAAK,IAAW,IAC/E,CACF,EACA,OAGF,GAAI,IAAS,EAAG,CACd,IAAM,EAAW,EAAS,oBAAoB,IAAW,aAAa,IACtE,EAAW,MAAM,aAAa,cAAiB,KAAY,EAAS,KAAK,IAAW,IAAI,CAAC,EACzF,OAIF,IAAM,EAAU,EAAO,KAAK,EAC5B,GAAI,CAAC,EAAS,CACZ,EAAW,MAAM,aAAa,uBAA0B,CAAC,EACzD,OAIF,IAAM,EACJ,EACG,MAAM,OAAO,EACb,IAAI,CAAC,IAAM,EAAE,KAAK,CAAC,EACnB,KAAK,CAAC,IAAM,EAAE,OAAS,CAAC,GAAK,GAE9B,EACJ,GAAI,CACF,EAAW,KAAK,MAAM,CAAS,EAC/B,KAAM,CACN,IAAM,EAAS,EAAkB,iDAAmD,GACpF,EACM,MAAM,aAAa,6BAAgC,EAAU,MAAM,EAAG,GAAG,IAAI,GAAQ,CAC3F,EACA,OAGF,GAAI,OAAO,EAAS,QAAU,SAAU,CACtC,EAAW,MAAM,aAAa,oDAAuD,CAAC,EACtF,OAGF,EAAQ,EAAS,KAAK,EACvB,EAGD,EAAM,GAAG,QAAS,CAAC,IAAQ,CACzB,aAAa,CAAa,EAC1B,IAAM,EAAS,EAAkB,kDAAoD,GACrF,EAAW,MAAM,aAAa,yBAA4B,EAAI,UAAU,GAAQ,CAAC,EAClF,EAGD,IAAM,EAA2B,CAC/B,SAAU,EACV,MACF,EAEA,EAAM,OAAO,MAAM,GAAG,KAAK,UAAU,CAAO;AAAA,CAAK,EACjD,EAAM,OAAO,IAAI,EAClB,EAkBI,SAAS,CAAqB,CACnC,EACA,EACA,EACU,CACV,IAAM,EAAe,GAAQ,WAC7B,MAAO,UAAU,IAAoC,CACnD,OAAO,MAAM,GAAuB,EAAK,EAAc,EAAM,CAAW,GAOrE,SAAS,CAAoB,CAAC,EAAyC,CAC5E,GAAI,CAAC,GAAO,OAAO,IAAQ,SAAU,MAAO,GAC5C,IAAM,EAAM,EACZ,OAAO,EAAI,OAAY,WAAa,MAAM,QAAQ,EAAI,OAAU,ECpNlE,qBAAS,mBAAY,mBAAc,iBACnC,0BACA,4BA6BA,IAAM,GAAc,mBAGd,GAAe,oBAMrB,SAAS,EAAU,CAAC,EAAmB,CACrC,GAAI,EAAE,WAAW,IAAI,GAAK,IAAM,IAC9B,OAAY,OAAQ,UAAQ,EAAG,EAAE,MAAM,CAAC,CAAC,EAE3C,OAAO,EAGT,SAAS,EAAiB,CAAC,EAAkB,EAAgC,CAC3E,IAAM,EAAoB,UAAQ,CAAQ,EACpC,EAAyB,UAAQ,CAAa,EAG9C,EAAW,WAAS,EAAmB,CAAY,EAGzD,GAAI,IAAQ,GAAI,MAAO,GAGvB,GAAS,aAAW,CAAG,EAAG,MAAO,GAGjC,GAAI,EAAI,WAAW,IAAI,EAAG,MAAO,GAEjC,MAAO,GAGT,SAAS,CAAY,CAAC,EAAmB,CACvC,GAAI,CACF,OAAO,GAAa,CAAC,EACrB,KAAM,CACN,OAAY,UAAQ,CAAC,GAIzB,SAAS,EAAU,CAAC,EAAoB,CACtC,GAAI,CAEF,OADA,GAAW,CAAC,EACL,GACP,KAAM,CACN,MAAO,IAQX,SAAS,EAAiB,CAAC,EAAuB,CAChD,OAAO,EAAM,QAAQ,GAAa,CAAC,EAAG,IAAoB,CACxD,OAAO,QAAQ,IAAI,IAAY,GAChC,EAGH,SAAS,EAAkB,CAAC,EAAe,EAAsC,CAC/E,OAAO,EAAM,QAAQ,GAAc,CAAC,EAAQ,IAAqB,CAE/D,IAAI,EAAW,GAAW,EAAS,KAAK,CAAC,EAGzC,GAAI,CAAM,aAAW,CAAQ,EAC3B,EAAgB,UAAQ,EAAQ,UAAW,CAAQ,EAIrD,GAAI,CAAC,GAAW,CAAQ,EACtB,MAAU,MAAM,qCAAqC,mBAA0B,GAAU,EAG3F,IAAM,EAAgB,EAAa,EAAQ,aAAa,EAClD,EAAW,EAAa,CAAQ,EAGtC,GAAI,CAAC,EAAQ,oBACX,GAAI,CAAC,GAAkB,EAAU,CAAa,EAC5C,MAAU,MACR,qCAAqC,iEAEvC,EAKJ,GAAI,CAEF,OADgB,GAAa,EAAU,OAAO,EAC/B,QAAQ,EACvB,MAAO,EAAK,CACZ,MAAU,MACR,qCAAqC,yBAChC,aAAe,MAAQ,EAAI,QAAU,OAAO,CAAG,GACtD,GAEH,EAGH,SAAS,EAAgB,CAAC,EAAe,EAAsC,CAE7E,IAAI,EAAS,GAAkB,CAAK,EAKpC,OAFA,EAAS,GAAmB,EAAQ,CAAO,EAEpC,EAeF,SAAS,CAAkB,CAAC,EAAc,EAAuC,CACtF,GAAI,IAAQ,MAAQ,IAAQ,OAC1B,OAAO,EAGT,GAAI,OAAO,IAAQ,SACjB,OAAO,GAAiB,EAAK,CAAO,EAGtC,GAAI,MAAM,QAAQ,CAAG,EACnB,OAAO,EAAI,IAAI,CAAC,IAAS,EAAmB,EAAM,CAAO,CAAC,EAG5D,GAAI,OAAO,IAAQ,SAAU,CAC3B,IAAM,EAAkC,CAAC,EACzC,QAAY,EAAK,KAAU,OAAO,QAAQ,CAAG,EAE3C,EAAO,GAAO,EAAmB,EAAO,CAAO,EAEjD,OAAO,EAIT,OAAO,EF9JF,IAAM,EAAqB,MACrB,EAA2B,GAC3B,EAAuB,GAyDpC,SAAS,EAAW,CAAC,EAAkB,EAAsC,CAC3E,IAAI,EAAW,UAAQ,CAAQ,EACzB,EAAO,EAAe,UAAQ,CAAO,EAAI,QACvC,QAAc,QAAM,CAAG,EAG/B,MAAO,GAAM,CACX,IAAM,EAAe,OAAK,EAAK,MAAM,EACrC,GAAI,GAAW,CAAO,EACpB,OAAO,EAGT,GAAI,GAAQ,IAAQ,EAAM,OAE1B,GAAI,IAAQ,EAAM,OAClB,IAAM,EAAc,UAAQ,CAAG,EAC/B,GAAI,IAAW,EAAK,OACpB,EAAM,GAOV,SAAS,EAAoB,CAC3B,EACA,EACA,EACQ,CAER,GAAI,EACF,OAAY,UAAQ,CAAU,EAIhC,IAAM,EAAoB,EAAqB,UAAQ,CAAa,EAAI,OAExE,OADgB,GAAY,EAAU,CAAiB,GACrC,GAA0B,UAAQ,CAAQ,EAM9D,SAAS,EAAc,CAAC,EAA4D,CAClF,IAAM,EAAU,GAAS,UAAY,GAC/B,EAAU,GAAS,QAErB,EACJ,GAAI,CAAC,EACH,EAAO,WACF,QAAI,EACT,EAAO,aAEP,OAAO,SAGT,IAAM,EAAgC,CACpC,UACA,MACF,EAGA,GAAI,EACF,EAAO,QAAU,EAGnB,OAAO,EAMT,SAAS,EAAe,CAAC,EAAyD,CAChF,IAAM,EAA2B,CAC/B,UAAW,GAAU,WAAa,EAClC,gBAAiB,GAAU,iBAAmB,EAC9C,YAAa,GAAU,aAAe,EACtC,QAAS,GAAU,SAAW,CAAC,CACjC,EAGA,GAAI,GAAU,MACZ,EAAO,MAAQ,EAAS,MAG1B,OAAO,EAMT,SAAS,EAAgB,CACvB,EACA,EAC0B,CAC1B,GAAI,CAAC,EACH,MAAO,CAAC,EAGV,IAAM,EAAmC,CAAC,EAE1C,QAAY,EAAM,KAAQ,OAAO,QAAQ,CAAK,EAC5C,GAAI,OAAO,IAAQ,WAEjB,EAAO,GAAQ,EACV,QAAI,EAAqB,CAAG,EAEjC,EAAO,GAAQ,EAAsB,EAAK,EAAa,CAAI,EAE3D,WAAU,MACR,8BAA8B,8DAChC,EAIJ,OAAO,EAuBT,eAAsB,EAAoB,CACxC,EACgC,CAChC,IAAM,EAAqB,CAAC,EACtB,EAA0B,CAAC,EAG3B,EAAsD,CAC1D,SAAU,EAAQ,QACpB,EACA,GAAI,EAAQ,QACV,EAAY,QAAU,EAAQ,QAEhC,IAAM,EAAS,MAAM,EAAW,CAAW,EAErC,EAAwB,EAAQ,QAAe,UAAQ,EAAQ,OAAO,EAAI,OAG1E,EAAc,GAAqB,EAAO,KAAM,EAAQ,SAAU,CAAqB,EAG7F,GAAI,EAAO,MAAQ,EAAe,EAAO,MAAM,EAC7C,EAAS,KAAK,GAAQ,WAAS,EAAO,IAAI,wCAAwC,EAIpF,IAAI,EAAgC,CAAC,EAErC,GAAI,EAAO,QAAU,OAAO,KAAK,EAAO,MAAM,EAAE,OAAS,EACvD,EAAc,KAAK,MAAM,EACzB,EAAe,EAAO,OAIxB,GAAI,EAAQ,QACV,EAAe,EAAa,EAAc,EAAQ,OAAO,EACzD,EAAc,KAAK,WAAW,EAAQ,SAAS,EAIjD,QAAW,KAAS,EAAQ,gBAAkB,CAAC,EAAG,CAChD,GAAI,CAAC,GAAS,CAAC,EAAM,WAAa,OAAO,KAAK,EAAM,SAAS,EAAE,SAAW,EAAG,SAC7E,EAAe,EAAY,CACzB,KAAM,EACN,UAAW,EAAM,SACnB,CAAC,EACD,EAAc,KAAK,EAAM,IAAI,EAI/B,GAAI,EAAQ,WAAa,OAAO,KAAK,EAAQ,SAAS,EAAE,OAAS,EAC/D,EAAe,EAAY,CACzB,KAAM,EACN,UAAW,EAAQ,SACrB,CAAC,EACD,EAAc,KAAK,EAAQ,oBAAsB,WAAW,EAI9D,IAAM,EAAgB,GAAyB,EACzC,EAAY,EAAO,KAAY,UAAQ,EAAO,IAAI,EAAI,EACtD,EAAqB,EAAa,UAAU,oBAAsB,GAExE,EAAe,EAAmB,EAAc,CAC9C,YACA,gBACA,oBACF,CAAC,EAGD,IAAM,EAAY,GAAiB,EAAa,UAAW,CAAW,EAGhE,EAAyB,CAC7B,cACA,UAAW,EAAa,WAAa,CAAC,EACtC,SAAU,GAAgB,EAAa,QAAQ,EAC/C,QAAS,GAAe,EAAa,OAAO,EAC5C,YACA,SAAU,CACR,mBAAoB,EAAa,UAAU,oBAAsB,EACnE,CACF,EAGM,EAAmB,CACvB,cACA,gBACA,UACF,EAGA,GAAI,EAAO,KACT,EAAK,WAAa,EAAO,KAE3B,GAAI,EAAO,OACT,EAAK,OAAS,EAAO,OAEvB,GAAI,EAAQ,QACV,EAAK,QAAU,EAAQ,QAGzB,MAAO,CAAE,SAAQ,MAAK",
|
|
15
|
+
"debugId": "15FF26925375BC1F64756E2164756E21",
|
|
11
16
|
"names": []
|
|
12
17
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONC (JSON with Comments) parser
|
|
3
|
+
*
|
|
4
|
+
* Strips single-line (//) and multi-line comments from JSON text
|
|
5
|
+
* while preserving '//' inside string values (e.g., URLs).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Strips JSONC comments from a string while preserving URLs and other
|
|
9
|
+
* strings containing '//' or comment-like sequences.
|
|
10
|
+
*/
|
|
11
|
+
export declare function stripJsonComments(content: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Parse JSONC content (JSON with comments) to an object.
|
|
14
|
+
*
|
|
15
|
+
* @throws {SyntaxError} if the content is not valid JSON after stripping comments
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseJsonc<T = unknown>(content: string): T;
|
|
18
|
+
//# sourceMappingURL=jsonc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonc.d.ts","sourceRoot":"","sources":["../../src/config/jsonc.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAuFzD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,CAAC,CAU1D"}
|
package/dist/config/load.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LoadedConfig } from './types';
|
|
1
|
+
import type { ConfigFormat, LoadedConfig } from './types';
|
|
2
2
|
export type LoadConfigOptions = {
|
|
3
3
|
path: string;
|
|
4
4
|
} | {
|
|
@@ -7,9 +7,20 @@ export type LoadConfigOptions = {
|
|
|
7
7
|
stopDir?: string;
|
|
8
8
|
};
|
|
9
9
|
/**
|
|
10
|
-
* Load
|
|
10
|
+
* Load config from an explicit path or by searching upwards.
|
|
11
|
+
*
|
|
12
|
+
* Discovery order (when searching):
|
|
13
|
+
* 1. treq.jsonc (preferred)
|
|
14
|
+
* 2. treq.json
|
|
15
|
+
* 3. treq.config.ts (legacy, deprecated)
|
|
16
|
+
* 4. treq.config.js (legacy, deprecated)
|
|
17
|
+
* 5. treq.config.mjs (legacy, deprecated)
|
|
11
18
|
*
|
|
12
19
|
* Node/Bun only (not renderer-safe).
|
|
13
20
|
*/
|
|
14
21
|
export declare function loadConfig(options: LoadConfigOptions): Promise<LoadedConfig>;
|
|
22
|
+
/**
|
|
23
|
+
* Check if a config format is a legacy format (TS/JS).
|
|
24
|
+
*/
|
|
25
|
+
export declare function isLegacyFormat(format: ConfigFormat | undefined): boolean;
|
|
15
26
|
//# sourceMappingURL=load.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../src/config/load.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../src/config/load.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAmB,MAAM,SAAS,CAAC;AAE3E,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAwH9D;;;;;;;;;;;GAWG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC,CAuBlF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,OAAO,CAExE"}
|
package/dist/config/merge.d.ts
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TreqConfigInput, TreqProfileInput } from './types';
|
|
2
2
|
export type MergeConfigInputs = {
|
|
3
|
-
defaults?:
|
|
4
|
-
file?:
|
|
5
|
-
overrides?:
|
|
3
|
+
defaults?: TreqConfigInput;
|
|
4
|
+
file?: TreqConfigInput;
|
|
5
|
+
overrides?: TreqConfigInput;
|
|
6
6
|
};
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Merge multiple config sources with "last wins" semantics.
|
|
9
|
+
*
|
|
10
|
+
* Order: defaults < file < overrides
|
|
11
|
+
*/
|
|
12
|
+
export declare function mergeConfig(inputs: MergeConfigInputs): TreqConfigInput;
|
|
13
|
+
/**
|
|
14
|
+
* Apply a profile to a config input.
|
|
15
|
+
*
|
|
16
|
+
* IMPORTANT: This operates on INPUT config, NOT ResolvedConfig.
|
|
17
|
+
* Returns a new TreqConfigInput with the profile's overrides applied.
|
|
18
|
+
*
|
|
19
|
+
* @param input - The config input to apply the profile to
|
|
20
|
+
* @param profileName - The name of the profile to apply
|
|
21
|
+
* @returns The config with profile applied
|
|
22
|
+
* @throws Error if the profile doesn't exist
|
|
23
|
+
*/
|
|
24
|
+
export declare function applyProfile(input: TreqConfigInput, profileName?: string): TreqConfigInput;
|
|
25
|
+
/**
|
|
26
|
+
* Get sorted list of profile names from a config.
|
|
27
|
+
*/
|
|
28
|
+
export declare function listProfiles(config: TreqConfigInput): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Merge profile input with deep merge for defaults.
|
|
31
|
+
* Used internally for combining profile with base config.
|
|
32
|
+
*/
|
|
33
|
+
export declare function mergeProfileInput(base: TreqProfileInput, overlay: TreqProfileInput): TreqProfileInput;
|
|
8
34
|
//# sourceMappingURL=merge.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../src/config/merge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../src/config/merge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE/E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B,CAAC;AA+EF;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,eAAe,CAgBtE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,eAAe,CA0B1F;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,EAAE,CAG9D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,gBAAgB,GACxB,gBAAgB,CAqClB"}
|