specli 0.0.1 → 0.0.2
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 +83 -49
- package/cli.ts +4 -10
- package/package.json +8 -2
- package/src/cli/compile.ts +5 -28
- package/src/cli/derive-name.ts +2 -2
- package/src/cli/exec.ts +1 -1
- package/src/cli/main.ts +12 -27
- package/src/cli/runtime/auth/resolve.ts +10 -2
- package/src/cli/runtime/body-flags.ts +176 -0
- package/src/cli/runtime/execute.ts +17 -22
- package/src/cli/runtime/generated.ts +23 -54
- package/src/cli/runtime/profile/secrets.ts +1 -1
- package/src/cli/runtime/profile/store.ts +1 -1
- package/src/cli/runtime/request.ts +48 -80
- package/src/cli/stable-json.ts +2 -2
- package/src/compiled.ts +13 -15
- package/src/macros/env.ts +0 -4
- package/CLAUDE.md +0 -111
- package/PLAN.md +0 -274
- package/biome.jsonc +0 -1
- package/bun.lock +0 -98
- package/fixtures/openapi-array-items.json +0 -22
- package/fixtures/openapi-auth.json +0 -34
- package/fixtures/openapi-body.json +0 -41
- package/fixtures/openapi-collision.json +0 -21
- package/fixtures/openapi-oauth.json +0 -54
- package/fixtures/openapi-servers.json +0 -35
- package/fixtures/openapi.json +0 -87
- package/scripts/smoke-specs.ts +0 -64
- package/src/cli/auth-requirements.test.ts +0 -27
- package/src/cli/auth-schemes.test.ts +0 -66
- package/src/cli/capabilities.test.ts +0 -94
- package/src/cli/command-id.test.ts +0 -32
- package/src/cli/command-model.test.ts +0 -44
- package/src/cli/naming.test.ts +0 -86
- package/src/cli/operations.test.ts +0 -57
- package/src/cli/params.test.ts +0 -70
- package/src/cli/positional.test.ts +0 -65
- package/src/cli/request-body.test.ts +0 -35
- package/src/cli/runtime/request.test.ts +0 -153
- package/src/cli/server.test.ts +0 -35
- package/tsconfig.json +0 -29
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# specli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
specli turns an OpenAPI spec into a non-interactive, “curl replacement” CLI.
|
|
4
4
|
|
|
5
5
|
It has two modes:
|
|
6
6
|
|
|
@@ -21,7 +21,7 @@ It works well for a large chunk of “typical” OpenAPI 3.x REST specs:
|
|
|
21
21
|
- Path/query/header/cookie parameters.
|
|
22
22
|
- Request bodies via `--data` / `--file`.
|
|
23
23
|
- JSON request body parsing + schema validation.
|
|
24
|
-
-
|
|
24
|
+
- Body field flags matching OpenAPI schema properties (including nested objects with dot notation).
|
|
25
25
|
- Auth injection for common schemes (bearer/basic/apiKey).
|
|
26
26
|
- A deterministic `__schema` output for introspection.
|
|
27
27
|
|
|
@@ -38,25 +38,25 @@ bun install
|
|
|
38
38
|
Inspect what commands will be generated:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
bunx
|
|
41
|
+
bunx specli exec ./fixtures/openapi.json __schema
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Machine-readable schema output:
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
bunx
|
|
47
|
+
bunx specli exec ./fixtures/openapi.json __schema --json
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
Minimal schema output (best for large specs):
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
bunx
|
|
53
|
+
bunx specli exec ./fixtures/openapi.json __schema --json --min
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
Run a generated operation:
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
bunx
|
|
59
|
+
bunx specli exec ./fixtures/openapi.json contacts list --curl
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
## Build a Standalone Executable
|
|
@@ -65,27 +65,27 @@ Use the `compile` command to create a standalone binary with the spec embedded:
|
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
67
|
# compile with auto-derived name (from spec title)
|
|
68
|
-
bunx
|
|
68
|
+
bunx specli compile ./path/to/openapi.yaml
|
|
69
69
|
# → ./dist/my-api (derived from info.title)
|
|
70
70
|
|
|
71
71
|
# compile with explicit name
|
|
72
|
-
bunx
|
|
72
|
+
bunx specli compile ./path/to/openapi.yaml --name myapi
|
|
73
73
|
# → ./dist/myapi
|
|
74
74
|
|
|
75
75
|
# cross-compile (example: linux x64)
|
|
76
|
-
bunx
|
|
76
|
+
bunx specli compile https://api.vercel.com/copper/_openapi.json --target bun-linux-x64 --outfile ./dist/copper-linux
|
|
77
77
|
|
|
78
78
|
# disable runtime config loading for deterministic behavior
|
|
79
|
-
bunx
|
|
79
|
+
bunx specli compile ./path/to/openapi.yaml --no-dotenv --no-bunfig
|
|
80
80
|
|
|
81
81
|
# bake in defaults (these become default flags; runtime flags override)
|
|
82
|
-
bunx
|
|
82
|
+
bunx specli compile https://api.vercel.com/copper/_openapi.json \
|
|
83
83
|
--name copper \
|
|
84
84
|
--server https://api.vercel.com \
|
|
85
85
|
--auth VercelOidc
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
The compiled binary is a root CLI - no `
|
|
88
|
+
The compiled binary is a root CLI - no `specli` prefix needed:
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
91
|
./dist/copper contacts list
|
|
@@ -100,10 +100,10 @@ Notes:
|
|
|
100
100
|
|
|
101
101
|
## CLI Shape
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
specli generates commands of the form:
|
|
104
104
|
|
|
105
105
|
```
|
|
106
|
-
|
|
106
|
+
specli <resource> <action> [...positionals] [options]
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
- `resource` comes from `tags[0]`, `operationId` prefix, or the first path segment (heuristics).
|
|
@@ -119,7 +119,7 @@ Available on the root command:
|
|
|
119
119
|
- `--spec <urlOrPath>`: OpenAPI URL or file path (only needed for compiled binaries to override embedded spec)
|
|
120
120
|
- `--server <url>`: override server/base URL
|
|
121
121
|
- `--server-var <name=value>`: server URL template variable (repeatable)
|
|
122
|
-
- `--profile <name>`: profile name (config under `~/.config/
|
|
122
|
+
- `--profile <name>`: profile name (config under `~/.config/specli`)
|
|
123
123
|
|
|
124
124
|
Auth selection + credentials:
|
|
125
125
|
|
|
@@ -189,9 +189,9 @@ Array parameters are treated as repeatable flags and appended to the query strin
|
|
|
189
189
|
All of these become `?tag=a&tag=b`:
|
|
190
190
|
|
|
191
191
|
```bash
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
specli ... --tag a --tag b
|
|
193
|
+
specli ... --tag a,b
|
|
194
|
+
specli ... --tag '["a","b"]'
|
|
195
195
|
```
|
|
196
196
|
|
|
197
197
|
Implementation notes:
|
|
@@ -207,14 +207,14 @@ If an operation has a `requestBody`, you may provide a body via:
|
|
|
207
207
|
|
|
208
208
|
- `--data <string>`
|
|
209
209
|
- `--file <path>`
|
|
210
|
-
-
|
|
210
|
+
- Body field flags (JSON-only; see below)
|
|
211
211
|
|
|
212
212
|
Rules:
|
|
213
213
|
|
|
214
214
|
- `--data` and `--file` are mutually exclusive.
|
|
215
|
-
-
|
|
215
|
+
- Body field flags cannot be used with `--data` or `--file`.
|
|
216
216
|
- If `requestBody.required` is true and you provide none of the above, the command fails with:
|
|
217
|
-
- `Missing request body. Provide --data, --file, or
|
|
217
|
+
- `Missing request body. Provide --data, --file, or body field flags.`
|
|
218
218
|
|
|
219
219
|
### Content-Type
|
|
220
220
|
|
|
@@ -236,7 +236,7 @@ If `Content-Type` does not include `json`:
|
|
|
236
236
|
|
|
237
237
|
### Schema Validation (Ajv)
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
specli uses Ajv (best-effort, `strict: false`) to validate:
|
|
240
240
|
|
|
241
241
|
- query/header/cookie params
|
|
242
242
|
- JSON request bodies when a requestBody schema is available
|
|
@@ -245,17 +245,18 @@ Validation errors are formatted into a readable multiline message. For `required
|
|
|
245
245
|
|
|
246
246
|
- `/<path> missing required property '<name>'`
|
|
247
247
|
|
|
248
|
-
###
|
|
248
|
+
### Body Field Flags
|
|
249
249
|
|
|
250
|
-
When an operation has a `requestBody` and the preferred schema is a JSON object
|
|
250
|
+
When an operation has a `requestBody` and the preferred schema is a JSON object, specli generates convenience flags that match the property names:
|
|
251
251
|
|
|
252
|
-
- For `string|number|integer`:
|
|
253
|
-
- For `boolean`:
|
|
252
|
+
- For `string|number|integer`: `--<prop> <value>`
|
|
253
|
+
- For `boolean`: `--<prop>` (presence sets it to `true`)
|
|
254
|
+
- For nested objects: `--<parent>.<child> <value>` (dot notation)
|
|
254
255
|
|
|
255
256
|
Example (from `fixtures/openapi-body.json`):
|
|
256
257
|
|
|
257
258
|
```bash
|
|
258
|
-
bunx
|
|
259
|
+
bunx specli exec ./fixtures/openapi-body.json contacts create --name "Ada" --curl
|
|
259
260
|
```
|
|
260
261
|
|
|
261
262
|
Produces a JSON body:
|
|
@@ -264,16 +265,49 @@ Produces a JSON body:
|
|
|
264
265
|
{"name":"Ada"}
|
|
265
266
|
```
|
|
266
267
|
|
|
268
|
+
#### Nested Objects
|
|
269
|
+
|
|
270
|
+
Nested object properties use dot notation. For a schema like:
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"type": "object",
|
|
275
|
+
"properties": {
|
|
276
|
+
"name": { "type": "string" },
|
|
277
|
+
"address": {
|
|
278
|
+
"type": "object",
|
|
279
|
+
"properties": {
|
|
280
|
+
"street": { "type": "string" },
|
|
281
|
+
"city": { "type": "string" }
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
You can use:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
mycli contacts create --name "Ada" --address.street "123 Main St" --address.city "NYC"
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Which produces:
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{"name":"Ada","address":{"street":"123 Main St","city":"NYC"}}
|
|
298
|
+
```
|
|
299
|
+
|
|
267
300
|
Notes / edge cases:
|
|
268
301
|
|
|
269
|
-
-
|
|
270
|
-
- Required fields in the schema are checked in a
|
|
271
|
-
- `Missing required body field 'name'. Provide --
|
|
302
|
+
- Body field flags are only supported for JSON bodies. If you try to use them without a JSON content type, specli errors.
|
|
303
|
+
- Required fields in the schema are checked in a "friendly" way:
|
|
304
|
+
- `Missing required body field 'name'. Provide --name or use --data/--file.`
|
|
305
|
+
- If a body field flag conflicts with an operation parameter flag, the operation parameter takes precedence.
|
|
272
306
|
- Numeric coercion uses `Number(...)` / `parseInt(...)`. Today it does not explicitly reject `NaN` (this is an area to harden).
|
|
273
307
|
|
|
274
308
|
## Servers
|
|
275
309
|
|
|
276
|
-
|
|
310
|
+
specli resolves the request base URL in this order:
|
|
277
311
|
|
|
278
312
|
1. `--server <url>`
|
|
279
313
|
2. profile `server` (if `--profile` is set and the profile has a server)
|
|
@@ -288,7 +322,7 @@ If the chosen server URL has template variables (e.g. `https://{region}.api.exam
|
|
|
288
322
|
|
|
289
323
|
### Supported Scheme Kinds
|
|
290
324
|
|
|
291
|
-
From `components.securitySchemes`,
|
|
325
|
+
From `components.securitySchemes`, specli recognizes:
|
|
292
326
|
|
|
293
327
|
- HTTP bearer (`type: http`, `scheme: bearer`)
|
|
294
328
|
- HTTP basic (`type: http`, `scheme: basic`)
|
|
@@ -312,7 +346,7 @@ This “only if present in current spec” behavior prevents accidental auth lea
|
|
|
312
346
|
Bearer-like schemes (`http-bearer`, `oauth2`, `openIdConnect`):
|
|
313
347
|
|
|
314
348
|
- `--bearer-token <token>` or `--oauth-token <token>`
|
|
315
|
-
- or a profile token stored via `
|
|
349
|
+
- or a profile token stored via `specli auth token ...`
|
|
316
350
|
|
|
317
351
|
Basic auth:
|
|
318
352
|
|
|
@@ -328,8 +362,8 @@ Profiles are for automation.
|
|
|
328
362
|
|
|
329
363
|
Config file:
|
|
330
364
|
|
|
331
|
-
- Read preference: `~/.config/
|
|
332
|
-
- Writes always go to: `~/.config/
|
|
365
|
+
- Read preference: `~/.config/specli/profiles.json`, else `~/.config/specli/profiles.yaml` if present
|
|
366
|
+
- Writes always go to: `~/.config/specli/profiles.json`
|
|
333
367
|
|
|
334
368
|
Secrets:
|
|
335
369
|
|
|
@@ -338,14 +372,14 @@ Secrets:
|
|
|
338
372
|
Commands:
|
|
339
373
|
|
|
340
374
|
```bash
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
375
|
+
specli profile list
|
|
376
|
+
specli profile set --name dev --server https://api.example.com --auth bearerAuth --default
|
|
377
|
+
specli profile use --name dev
|
|
378
|
+
specli profile rm --name dev
|
|
379
|
+
|
|
380
|
+
specli auth token --name dev --set "..."
|
|
381
|
+
specli auth token --name dev --get
|
|
382
|
+
specli auth token --name dev --delete
|
|
349
383
|
```
|
|
350
384
|
|
|
351
385
|
## Output Behavior
|
|
@@ -395,7 +429,7 @@ Prints the method, URL, headers, and body that would be sent without sending the
|
|
|
395
429
|
|
|
396
430
|
## `__schema`
|
|
397
431
|
|
|
398
|
-
`
|
|
432
|
+
`specli __schema` reports:
|
|
399
433
|
|
|
400
434
|
- OpenAPI title/version
|
|
401
435
|
- spec source + computed spec id + fingerprint
|
|
@@ -440,15 +474,15 @@ bun run smoke:specs
|
|
|
440
474
|
Or run ad-hoc smoke tests:
|
|
441
475
|
|
|
442
476
|
```bash
|
|
443
|
-
bunx
|
|
444
|
-
bunx
|
|
477
|
+
bunx specli exec <URL> __schema --json --min > /dev/null
|
|
478
|
+
bunx specli exec <URL> <some-resource> <some-action> --curl
|
|
445
479
|
```
|
|
446
480
|
|
|
447
|
-
Note: Kubernetes publishes a Swagger 2.0 document (`swagger.json`) which is not OpenAPI 3.x.
|
|
481
|
+
Note: Kubernetes publishes a Swagger 2.0 document (`swagger.json`) which is not OpenAPI 3.x. specli currently expects `openapi: "3.x"` and will reject Swagger 2.0 specs.
|
|
448
482
|
|
|
449
483
|
## Limitations (Important)
|
|
450
484
|
|
|
451
|
-
|
|
485
|
+
specli is intentionally v1-simple; common gaps for real-world specs:
|
|
452
486
|
|
|
453
487
|
- OpenAPI 3.x only (Swagger 2.0 not supported).
|
|
454
488
|
- Parameter serialization is simplified:
|
package/cli.ts
CHANGED
|
@@ -8,7 +8,7 @@ function collect(value: string, previous: string[] = []): string[] {
|
|
|
8
8
|
|
|
9
9
|
const program = new Command();
|
|
10
10
|
|
|
11
|
-
program.name("
|
|
11
|
+
program.name("specli").description("Generate CLIs from OpenAPI specs");
|
|
12
12
|
|
|
13
13
|
// ─────────────────────────────────────────────────────────────
|
|
14
14
|
// exec command - runs spec dynamically
|
|
@@ -51,21 +51,15 @@ program
|
|
|
51
51
|
.option("--bytecode", "Enable bytecode compilation")
|
|
52
52
|
.option("--no-dotenv", "Disable .env autoload")
|
|
53
53
|
.option("--no-bunfig", "Disable bunfig.toml autoload")
|
|
54
|
-
.option(
|
|
55
|
-
"--exec-argv <arg>",
|
|
56
|
-
"Embedded process.execArgv (repeatable)",
|
|
57
|
-
collect,
|
|
58
|
-
[],
|
|
59
|
-
)
|
|
60
54
|
.option("--define <k=v>", "Build-time constant (repeatable)", collect, [])
|
|
61
|
-
.option("--server <url>", "Default server URL (
|
|
55
|
+
.option("--server <url>", "Default server URL (embedded)")
|
|
62
56
|
.option(
|
|
63
57
|
"--server-var <k=v>",
|
|
64
|
-
"Default server variable (repeatable)",
|
|
58
|
+
"Default server variable (repeatable, embedded)",
|
|
65
59
|
collect,
|
|
66
60
|
[],
|
|
67
61
|
)
|
|
68
|
-
.option("--auth <scheme>", "Default auth scheme")
|
|
62
|
+
.option("--auth <scheme>", "Default auth scheme (embedded)")
|
|
69
63
|
.action(async (spec, options) => {
|
|
70
64
|
const { compileCommand } = await import("./src/cli/compile.ts");
|
|
71
65
|
await compileCommand(spec, options);
|
package/package.json
CHANGED
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
"name": "specli",
|
|
3
3
|
"module": "index.ts",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.2",
|
|
6
6
|
"bin": {
|
|
7
|
-
"
|
|
7
|
+
"specli": "./cli.ts"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"cli.ts",
|
|
11
|
+
"index.ts",
|
|
12
|
+
"src",
|
|
13
|
+
"!**/*.test.ts"
|
|
14
|
+
],
|
|
9
15
|
"scripts": {
|
|
10
16
|
"lint": "biome ci",
|
|
11
17
|
"lint:check": "biome check --write --unsafe",
|
package/src/cli/compile.ts
CHANGED
|
@@ -8,7 +8,6 @@ export type CompileOptions = {
|
|
|
8
8
|
bytecode?: boolean;
|
|
9
9
|
dotenv?: boolean; // --no-dotenv sets this to false
|
|
10
10
|
bunfig?: boolean; // --no-bunfig sets this to false
|
|
11
|
-
execArgv?: string[];
|
|
12
11
|
define?: string[];
|
|
13
12
|
server?: string;
|
|
14
13
|
serverVar?: string[];
|
|
@@ -37,23 +36,6 @@ export async function compileCommand(
|
|
|
37
36
|
? (options.target as Bun.Build.Target)
|
|
38
37
|
: (`bun-${process.platform}-${process.arch}` as Bun.Build.Target);
|
|
39
38
|
|
|
40
|
-
// Build embedded execArgv for runtime defaults
|
|
41
|
-
const embeddedExecArgv: string[] = [];
|
|
42
|
-
if (options.server) {
|
|
43
|
-
embeddedExecArgv.push(`--server=${options.server}`);
|
|
44
|
-
}
|
|
45
|
-
if (options.serverVar) {
|
|
46
|
-
for (const pair of options.serverVar) {
|
|
47
|
-
embeddedExecArgv.push(`--server-var=${pair}`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (options.auth) {
|
|
51
|
-
embeddedExecArgv.push(`--auth=${options.auth}`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// User-provided exec-argv
|
|
55
|
-
const compileExecArgv = options.execArgv ?? [];
|
|
56
|
-
|
|
57
39
|
// Parse --define pairs
|
|
58
40
|
const define: Record<string, string> = {};
|
|
59
41
|
if (options.define) {
|
|
@@ -78,14 +60,6 @@ export async function compileCommand(
|
|
|
78
60
|
buildArgs.push("--define", `${k}=${v}`);
|
|
79
61
|
}
|
|
80
62
|
|
|
81
|
-
const execArgv =
|
|
82
|
-
embeddedExecArgv.length || compileExecArgv.length
|
|
83
|
-
? [...embeddedExecArgv, ...compileExecArgv]
|
|
84
|
-
: [];
|
|
85
|
-
for (const arg of execArgv) {
|
|
86
|
-
buildArgs.push("--compile-exec-argv", arg);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
63
|
if (options.dotenv === false) buildArgs.push("--no-compile-autoload-dotenv");
|
|
90
64
|
if (options.bunfig === false) buildArgs.push("--no-compile-autoload-bunfig");
|
|
91
65
|
|
|
@@ -97,8 +71,11 @@ export async function compileCommand(
|
|
|
97
71
|
stderr: "pipe",
|
|
98
72
|
env: {
|
|
99
73
|
...process.env,
|
|
100
|
-
|
|
101
|
-
|
|
74
|
+
SPECLI_SPEC: spec,
|
|
75
|
+
SPECLI_NAME: name,
|
|
76
|
+
SPECLI_SERVER: options.server ?? "",
|
|
77
|
+
SPECLI_SERVER_VARS: options.serverVar?.join(",") ?? "",
|
|
78
|
+
SPECLI_AUTH: options.auth ?? "",
|
|
102
79
|
},
|
|
103
80
|
});
|
|
104
81
|
|
package/src/cli/derive-name.ts
CHANGED
|
@@ -12,7 +12,7 @@ const RESERVED_NAMES = [
|
|
|
12
12
|
* Priority:
|
|
13
13
|
* 1. info.title (kebab-cased, sanitized)
|
|
14
14
|
* 2. Host from spec URL (if URL provided)
|
|
15
|
-
* 3. Fallback to "
|
|
15
|
+
* 3. Fallback to "specli"
|
|
16
16
|
*/
|
|
17
17
|
export async function deriveBinaryName(spec: string): Promise<string> {
|
|
18
18
|
try {
|
|
@@ -48,7 +48,7 @@ export async function deriveBinaryName(spec: string): Promise<string> {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// Fallback
|
|
51
|
-
return "
|
|
51
|
+
return "specli";
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
async function loadSpecText(spec: string): Promise<string> {
|
package/src/cli/exec.ts
CHANGED
package/src/cli/main.ts
CHANGED
|
@@ -18,13 +18,16 @@ import { stableStringify } from "./stable-json.ts";
|
|
|
18
18
|
type MainOptions = {
|
|
19
19
|
embeddedSpecText?: string;
|
|
20
20
|
cliName?: string;
|
|
21
|
+
server?: string;
|
|
22
|
+
serverVars?: string[];
|
|
23
|
+
auth?: string;
|
|
21
24
|
};
|
|
22
25
|
|
|
23
26
|
export async function main(argv: string[], options: MainOptions = {}) {
|
|
24
27
|
const program = new Command();
|
|
25
28
|
|
|
26
29
|
program
|
|
27
|
-
.name(options.cliName ?? "
|
|
30
|
+
.name(options.cliName ?? "specli")
|
|
28
31
|
.description("Generate a CLI from an OpenAPI spec")
|
|
29
32
|
.option("--spec <urlOrPath>", "OpenAPI URL or file path")
|
|
30
33
|
.option("--server <url>", "Override server/base URL")
|
|
@@ -39,31 +42,10 @@ export async function main(argv: string[], options: MainOptions = {}) {
|
|
|
39
42
|
.option("--username <username>", "Basic auth username")
|
|
40
43
|
.option("--password <password>", "Basic auth password")
|
|
41
44
|
.option("--api-key <key>", "API key value")
|
|
42
|
-
.option("--profile <name>", "Profile name (stored under ~/.config/
|
|
45
|
+
.option("--profile <name>", "Profile name (stored under ~/.config/specli)")
|
|
43
46
|
.option("--json", "Machine-readable output")
|
|
44
47
|
.showHelpAfterError();
|
|
45
48
|
|
|
46
|
-
// Provide namespaced variants for flags which may collide with operation flags.
|
|
47
|
-
// (Some real-world APIs define parameters named "accept", etc.)
|
|
48
|
-
program
|
|
49
|
-
.option(
|
|
50
|
-
"--oc-header <header>",
|
|
51
|
-
"Extra header (repeatable, namespaced)",
|
|
52
|
-
collectRepeatable,
|
|
53
|
-
)
|
|
54
|
-
.option("--oc-accept <type>", "Override Accept header (namespaced)")
|
|
55
|
-
.option("--oc-status", "Include status in --json output (namespaced)")
|
|
56
|
-
.option("--oc-headers", "Include headers in --json output (namespaced)")
|
|
57
|
-
.option("--oc-dry-run", "Print request without sending (namespaced)")
|
|
58
|
-
.option("--oc-curl", "Print curl command without sending (namespaced)")
|
|
59
|
-
.option("--oc-timeout <ms>", "Request timeout in milliseconds (namespaced)")
|
|
60
|
-
.option("--oc-data <data>", "Inline request body (namespaced)")
|
|
61
|
-
.option("--oc-file <path>", "Request body from file (namespaced)")
|
|
62
|
-
.option(
|
|
63
|
-
"--oc-content-type <type>",
|
|
64
|
-
"Override Content-Type (defaults from OpenAPI) (namespaced)",
|
|
65
|
-
);
|
|
66
|
-
|
|
67
49
|
// If user asks for help and we have no embedded spec and no --spec, show minimal help.
|
|
68
50
|
const spec = getArgValue(argv, "--spec");
|
|
69
51
|
const wantsHelp = hasAnyArg(argv, ["-h", "--help"]);
|
|
@@ -83,7 +65,7 @@ export async function main(argv: string[], options: MainOptions = {}) {
|
|
|
83
65
|
|
|
84
66
|
const profileCmd = program
|
|
85
67
|
.command("profile")
|
|
86
|
-
.description("Manage
|
|
68
|
+
.description("Manage specli profiles");
|
|
87
69
|
|
|
88
70
|
profileCmd
|
|
89
71
|
.command("list")
|
|
@@ -318,9 +300,7 @@ export async function main(argv: string[], options: MainOptions = {}) {
|
|
|
318
300
|
const args = op.pathArgs.length
|
|
319
301
|
? ` ${op.pathArgs.map((a) => `<${a}>`).join(" ")}`
|
|
320
302
|
: "";
|
|
321
|
-
process.stdout.write(
|
|
322
|
-
`- opencli ${op.resource} ${op.action}${args}\n`,
|
|
323
|
-
);
|
|
303
|
+
process.stdout.write(`- specli ${op.resource} ${op.action}${args}\n`);
|
|
324
304
|
}
|
|
325
305
|
}
|
|
326
306
|
});
|
|
@@ -330,6 +310,11 @@ export async function main(argv: string[], options: MainOptions = {}) {
|
|
|
330
310
|
authSchemes: ctx.authSchemes,
|
|
331
311
|
commands: ctx.commands,
|
|
332
312
|
specId: ctx.loaded.id,
|
|
313
|
+
embeddedDefaults: {
|
|
314
|
+
server: options.server,
|
|
315
|
+
serverVars: options.serverVars,
|
|
316
|
+
auth: options.auth,
|
|
317
|
+
},
|
|
333
318
|
});
|
|
334
319
|
|
|
335
320
|
await program.parseAsync(argv);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { AuthScheme } from "../../auth-schemes.ts";
|
|
2
2
|
|
|
3
3
|
export type AuthInputs = {
|
|
4
|
-
profileAuthScheme?: string;
|
|
5
4
|
flagAuthScheme?: string;
|
|
5
|
+
profileAuthScheme?: string;
|
|
6
|
+
embeddedAuthScheme?: string;
|
|
6
7
|
};
|
|
7
8
|
|
|
8
9
|
export function resolveAuthScheme(
|
|
@@ -10,7 +11,7 @@ export function resolveAuthScheme(
|
|
|
10
11
|
required: import("../../auth-requirements.ts").AuthSummary,
|
|
11
12
|
inputs: AuthInputs,
|
|
12
13
|
): string | undefined {
|
|
13
|
-
//
|
|
14
|
+
// Priority: CLI flag > profile > embedded default
|
|
14
15
|
if (inputs.flagAuthScheme) return inputs.flagAuthScheme;
|
|
15
16
|
|
|
16
17
|
if (
|
|
@@ -20,6 +21,13 @@ export function resolveAuthScheme(
|
|
|
20
21
|
return inputs.profileAuthScheme;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
if (
|
|
25
|
+
inputs.embeddedAuthScheme &&
|
|
26
|
+
authSchemes.some((s) => s.key === inputs.embeddedAuthScheme)
|
|
27
|
+
) {
|
|
28
|
+
return inputs.embeddedAuthScheme;
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
// If operation requires exactly one scheme, choose it.
|
|
24
32
|
const alts = required.alternatives;
|
|
25
33
|
if (alts.length === 1 && alts[0]?.length === 1) return alts[0][0]?.key;
|