Skip to content

Commit 2f72fee

Browse files
committed
feat(config): JSON/JSONC-first Config v2
1 parent e95e355 commit 2f72fee

34 files changed

+2867
-302
lines changed

.changeset/short-hoops-peel.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@t-req/core": minor
3+
"@t-req/app": minor
4+
---
5+
6+
Add JSON/JSONC-first Config v2 with deterministic layering (profiles + overrides), safe {env:}/{file:} substitutions, and command-resolver support. Improve cookie persistence safety with serialized writes and add coverage for critical config paths.

apps/webdocs/src/content/docs/concepts/variables.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ X-Request-ID: {{$uuid()}}
8989
X-Timestamp: {{$timestamp()}}
9090
9191
{
92-
"random_value": {{$random(1, 100)}}
92+
"random_value": {{$random([1, 100])}}
9393
}
9494
```
9595

apps/webdocs/src/content/docs/guides/authentication.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ const client = createClient({
6868

6969
```http
7070
GET https://api.example.com/admin
71-
Authorization: Basic {{$basicAuth({{username}}, {{password}})}}
71+
Authorization: Basic {{$basicAuth(["{{username}}", "{{password}}"])}}
7272
```
7373

74-
> **Note:** Variables inside resolver calls are interpolated first. The expression
75-
> `{{$basicAuth({{username}}, {{password}})}}` first becomes `{{$basicAuth(admin, secret)}}`,
74+
> **Note:** Variables inside resolver calls are interpolated first. With JSON-args, the expression
75+
> `{{$basicAuth(["{{username}}", "{{password}}"])}}` becomes `{{$basicAuth(["admin", "secret"])}}`,
7676
> then the resolver is called with literal strings `"admin"` and `"secret"`.
7777
7878
Or pre-encode in the variable:

apps/webdocs/src/content/docs/reference/interpolation.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ interpolate('User: {{user.name}}', {
115115
```
116116
{{$resolverName()}}
117117
{{$resolverName(arg1)}}
118-
{{$resolverName(arg1, arg2)}}
118+
{{$resolverName(["arg1","arg2"])}}
119119
```
120120

121121
```typescript
@@ -129,7 +129,8 @@ const interp = createInterpolator({
129129
},
130130
});
131131

132-
await interp.interpolate('Value: {{$random(1, 10)}}', {});
132+
// Prefer JSON-array args for unambiguous parsing:
133+
await interp.interpolate('Value: {{$random([1, 10])}}', {});
133134
// "Value: 7" (random between 1-10)
134135
```
135136

examples/core/variables.http

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,14 @@ X-Request-ID: {{$uuid()}}
6666
###
6767

6868
### Using random resolver
69-
# Resolvers: $random(min,max)
69+
# Resolvers: prefer JSON-args for unambiguous parsing
70+
# - $random([min,max])
7071
POST {{baseUrl}}/test
7172
Content-Type: application/json
7273

7374
{
74-
"testId": "test-{{$random(1000,9999)}}",
75-
"value": {{$random(1,100)}}
75+
"testId": "test-{{$random([1000,9999])}}",
76+
"value": {{$random([1,100])}}
7677
}
7778

7879
###

packages/app/README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ Uses defaults: bun runtime, bun package manager.
3434

3535
```
3636
my-project/
37-
├── treq.config.ts # Configuration with baseUrl variable
37+
├── treq.jsonc # Project configuration (JSONC-first)
38+
├── .treq/ # Local state (optional, e.g. cookie jar)
3839
├── collection/
3940
│ ├── auth/
4041
│ │ └── login.http # Example POST request
@@ -53,6 +54,9 @@ Execute `.http` files directly from the command line:
5354
# Execute the first request in a file
5455
treq run collection/auth/login.http
5556

57+
# Use a config profile
58+
treq run collection/auth/login.http --profile dev
59+
5660
# Execute a specific request by name
5761
treq run collection/users.http --name "Get User"
5862

@@ -62,7 +66,8 @@ treq run collection/users.http --index 2
6266
# Pass variables
6367
treq run collection/auth/login.http --var email=test@example.com --var password=secret
6468

65-
# Use environment file
69+
# Legacy environment module (kept for compatibility)
70+
# Loads environments/<env>.ts or environments/<env>.js from the workspace
6671
treq run collection/auth/login.http --env dev
6772

6873
# Set timeout (in milliseconds)
@@ -78,7 +83,8 @@ treq run collection/auth/login.http --verbose
7883
|--------|-------------|
7984
| `--name, -n` | Select request by @name directive |
8085
| `--index, -i` | Select request by index (0-based) |
81-
| `--env, -e` | Environment file to load (looks for `.env.<name>`) |
86+
| `--profile, -p` | Config profile to use |
87+
| `--env, -e` | Legacy environment module to load (`environments/<env>.ts` or `environments/<env>.js`) |
8288
| `--var` | Set variable (can be used multiple times) |
8389
| `--timeout, -t` | Request timeout in milliseconds |
8490
| `--workspace, -w` | Workspace root directory |
@@ -123,6 +129,7 @@ treq serve --stdio
123129
| Method | Path | Description |
124130
|--------|------|-------------|
125131
| `GET` | `/health` | Server health and version info |
132+
| `GET` | `/config` | Resolved config summary (supports `?profile=` and `?path=`) |
126133
| `POST` | `/parse` | Parse `.http` file content |
127134
| `POST` | `/execute` | Execute HTTP request |
128135
| `POST` | `/session` | Create new session |
@@ -167,7 +174,7 @@ treq serve --help
167174

168175
The server uses protocol version `1.0`.
169176

170-
`/health` is intentionally lean (OpenCode pattern) and only returns a basic status and server version:
177+
`/health` is intentionally lean and only returns a basic status and server version:
171178

172179
```json
173180
{

packages/app/src/cmd/init.ts

Lines changed: 54 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ export const initCommand: CommandModule<object, InitOptions> = {
3636
}
3737
};
3838

39-
// ============================================================================
40-
// Path Utilities (Bun-native)
41-
// ============================================================================
42-
4339
const SEP = '/';
4440

4541
function basename(p: string): string {
@@ -202,26 +198,18 @@ async function createProjectStructure(projectPath: string, config: ProjectConfig
202198

203199
// Create directories using Bun shell
204200
await $`mkdir -p ${projectPath}`.quiet();
205-
await $`mkdir -p ${join(projectPath, 'environments')}`.quiet();
201+
await $`mkdir -p ${join(projectPath, '.treq')}`.quiet();
206202
await $`mkdir -p ${join(projectPath, 'collection', 'auth')}`.quiet();
207203
await $`mkdir -p ${join(projectPath, 'collection', 'users')}`.quiet();
208204

209205
// Write root files
210-
await Bun.write(join(projectPath, 'treq.config.ts'), generateConfig());
206+
await Bun.write(join(projectPath, 'treq.jsonc'), generateConfig());
211207
await Bun.write(join(projectPath, 'run.ts'), generateRunScript(config.runtime));
212208
await Bun.write(join(projectPath, 'package.json'), generatePackageJson(projectName, config));
213209
await Bun.write(join(projectPath, '.gitignore'), generateGitignore());
214210

215-
// Write environments
216-
await Bun.write(join(projectPath, 'environments', 'dev.ts'), generateDevEnvironment());
217-
await Bun.write(join(projectPath, 'environments', 'prod.ts'), generateProdEnvironment());
218-
219211
// Write collection
220212
await Bun.write(join(projectPath, 'collection', 'auth', 'login.http'), generateLoginRequest());
221-
await Bun.write(
222-
join(projectPath, 'collection', 'users', '_defaults.ts'),
223-
generateFolderDefaults()
224-
);
225213
await Bun.write(
226214
join(projectPath, 'collection', 'users', 'list.http'),
227215
generateListUsersRequest()
@@ -230,13 +218,33 @@ async function createProjectStructure(projectPath: string, config: ProjectConfig
230218
}
231219

232220
export function generateConfig(): string {
233-
return `import { defineConfig } from '@t-req/core/config';
234-
235-
export default defineConfig({
236-
variables: {
237-
baseUrl: '{{baseUrl}}',
221+
return `{
222+
"variables": {
223+
// Default base URL for the included sample requests.
224+
// Switch profiles with: treq run ... --profile dev
225+
"baseUrl": "https://jsonplaceholder.typicode.com"
226+
// Example substitutions:
227+
// "apiKey": "{env:API_KEY}",
228+
// "authToken": "{file:./secrets/token.txt}"
238229
},
239-
});
230+
"defaults": {
231+
"timeoutMs": 30000
232+
},
233+
// Uncomment to persist cookies between runs:
234+
// "cookies": {
235+
// "enabled": true,
236+
// "jarPath": ".treq/cookies.json"
237+
// },
238+
"profiles": {
239+
"dev": {
240+
"variables": { "baseUrl": "http://localhost:3000" },
241+
"defaults": { "validateSSL": false }
242+
},
243+
"prod": {
244+
"variables": { "baseUrl": "https://api.example.com" }
245+
}
246+
}
247+
}
240248
`;
241249
}
242250

@@ -245,11 +253,32 @@ export function generateRunScript(runtime: Runtime): string {
245253

246254
return `${shebang}
247255
import { createClient } from '@t-req/core';
256+
import { resolveProjectConfig } from '@t-req/core/config';
257+
${runtime === 'node' ? "import { createNodeIO } from '@t-req/core/runtime';" : ''}
258+
259+
function getFlagValue(name: string): string | undefined {
260+
const idx = process.argv.indexOf(name);
261+
if (idx === -1) return undefined;
262+
const value = process.argv[idx + 1];
263+
return value && !value.startsWith('-') ? value : undefined;
264+
}
265+
266+
const profile = getFlagValue('--profile') ?? process.env.TREQ_PROFILE;
267+
const { config, meta } = await resolveProjectConfig({
268+
startDir: process.cwd(),
269+
profile: profile || undefined,
270+
});
271+
272+
for (const warning of meta.warnings) {
273+
console.warn(\`Warning: \${warning}\`);
274+
}
248275
249276
const client = createClient({
250-
variables: {
251-
baseUrl: 'https://jsonplaceholder.typicode.com',
252-
},
277+
${runtime === 'node' ? 'io: createNodeIO(),' : ''}
278+
variables: config.variables,
279+
// Map config defaults into the client (timeout + headers/redirects/ssl/proxy).
280+
timeout: config.defaults.timeoutMs,
281+
defaults: config.defaults,
253282
});
254283
255284
// Example: Get a user
@@ -298,36 +327,9 @@ dist/
298327
.env
299328
.env.local
300329
*.log
301-
`;
302-
}
303-
304-
export function generateDevEnvironment(): string {
305-
return `// Development environment
306-
export default {
307-
baseUrl: 'http://localhost:3000',
308-
};
309-
`;
310-
}
311-
312-
export function generateProdEnvironment(): string {
313-
return `// Production environment
314-
export default {
315-
baseUrl: 'https://api.example.com',
316-
};
317-
`;
318-
}
319330
320-
export function generateFolderDefaults(): string {
321-
return `// Folder defaults - requests in this folder inherit these settings
322-
export default {
323-
auth: {
324-
type: 'bearer',
325-
token: '{{authToken}}',
326-
},
327-
headers: {
328-
'X-Custom-Header': 'value',
329-
},
330-
};
331+
# t-req local state
332+
.treq/cookies.json
331333
`;
332334
}
333335

0 commit comments

Comments
 (0)