specli 0.0.1 → 0.0.3
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 +76 -86
- 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 +41 -91
- package/src/cli/runtime/generated.ts +28 -93
- package/src/cli/runtime/profile/secrets.ts +1 -1
- package/src/cli/runtime/profile/store.ts +1 -1
- package/src/cli/runtime/request.ts +42 -152
- 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:
|
|
@@ -201,79 +201,69 @@ Implementation notes:
|
|
|
201
201
|
|
|
202
202
|
## Request Bodies
|
|
203
203
|
|
|
204
|
-
###
|
|
204
|
+
### Body Field Flags
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
When an operation has a `requestBody` and the preferred schema is a JSON object, specli generates convenience flags that match the property names:
|
|
207
207
|
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
-
|
|
208
|
+
- For `string|number|integer`: `--<prop> <value>`
|
|
209
|
+
- For `boolean`: `--<prop>` (presence sets it to `true`)
|
|
210
|
+
- For nested objects: `--<parent>.<child> <value>` (dot notation)
|
|
211
211
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
- `--data` and `--file` are mutually exclusive.
|
|
215
|
-
- Expanded `--body-*` flags cannot be used with `--data` or `--file`.
|
|
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 --body-* flags.`
|
|
218
|
-
|
|
219
|
-
### Content-Type
|
|
220
|
-
|
|
221
|
-
`Content-Type` is chosen as:
|
|
222
|
-
|
|
223
|
-
1. `--content-type` (explicit override)
|
|
224
|
-
2. The preferred content type derived from the OpenAPI requestBody (prefers `application/json` when present)
|
|
225
|
-
|
|
226
|
-
### JSON Parsing + Normalization
|
|
227
|
-
|
|
228
|
-
If the selected `Content-Type` includes `json`:
|
|
229
|
-
|
|
230
|
-
- `--data`/`--file` content is parsed as either JSON or YAML
|
|
231
|
-
- the request is sent as normalized JSON (`JSON.stringify(parsed)`)
|
|
232
|
-
|
|
233
|
-
If `Content-Type` does not include `json`:
|
|
234
|
-
|
|
235
|
-
- the body is treated as a raw string
|
|
236
|
-
|
|
237
|
-
### Schema Validation (Ajv)
|
|
238
|
-
|
|
239
|
-
OpenCLI uses Ajv (best-effort, `strict: false`) to validate:
|
|
212
|
+
Example (from `fixtures/openapi-body.json`):
|
|
240
213
|
|
|
241
|
-
|
|
242
|
-
|
|
214
|
+
```bash
|
|
215
|
+
bunx specli exec ./fixtures/openapi-body.json contacts create --name "Ada" --curl
|
|
216
|
+
```
|
|
243
217
|
|
|
244
|
-
|
|
218
|
+
Produces a JSON body:
|
|
245
219
|
|
|
246
|
-
|
|
220
|
+
```json
|
|
221
|
+
{"name":"Ada"}
|
|
222
|
+
```
|
|
247
223
|
|
|
248
|
-
|
|
224
|
+
#### Nested Objects
|
|
249
225
|
|
|
250
|
-
|
|
226
|
+
Nested object properties use dot notation. For a schema like:
|
|
251
227
|
|
|
252
|
-
|
|
253
|
-
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"type": "object",
|
|
231
|
+
"properties": {
|
|
232
|
+
"name": { "type": "string" },
|
|
233
|
+
"address": {
|
|
234
|
+
"type": "object",
|
|
235
|
+
"properties": {
|
|
236
|
+
"street": { "type": "string" },
|
|
237
|
+
"city": { "type": "string" }
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
254
243
|
|
|
255
|
-
|
|
244
|
+
You can use:
|
|
256
245
|
|
|
257
246
|
```bash
|
|
258
|
-
|
|
247
|
+
mycli contacts create --name "Ada" --address.street "123 Main St" --address.city "NYC"
|
|
259
248
|
```
|
|
260
249
|
|
|
261
|
-
|
|
250
|
+
Which produces:
|
|
262
251
|
|
|
263
252
|
```json
|
|
264
|
-
{"name":"Ada"}
|
|
253
|
+
{"name":"Ada","address":{"street":"123 Main St","city":"NYC"}}
|
|
265
254
|
```
|
|
266
255
|
|
|
267
256
|
Notes / edge cases:
|
|
268
257
|
|
|
269
|
-
-
|
|
270
|
-
- Required fields in the schema are checked
|
|
271
|
-
- `Missing required body field 'name'. Provide --
|
|
272
|
-
-
|
|
258
|
+
- Body field flags are only supported for JSON bodies.
|
|
259
|
+
- Required fields in the schema are checked with friendly error messages:
|
|
260
|
+
- `Missing required body field 'name'. Provide --name.`
|
|
261
|
+
- If a body field flag would conflict with an operation parameter flag or `--curl`, the operation parameter takes precedence.
|
|
262
|
+
- Numeric coercion uses `Number(...)` / `parseInt(...)`.
|
|
273
263
|
|
|
274
264
|
## Servers
|
|
275
265
|
|
|
276
|
-
|
|
266
|
+
specli resolves the request base URL in this order:
|
|
277
267
|
|
|
278
268
|
1. `--server <url>`
|
|
279
269
|
2. profile `server` (if `--profile` is set and the profile has a server)
|
|
@@ -288,7 +278,7 @@ If the chosen server URL has template variables (e.g. `https://{region}.api.exam
|
|
|
288
278
|
|
|
289
279
|
### Supported Scheme Kinds
|
|
290
280
|
|
|
291
|
-
From `components.securitySchemes`,
|
|
281
|
+
From `components.securitySchemes`, specli recognizes:
|
|
292
282
|
|
|
293
283
|
- HTTP bearer (`type: http`, `scheme: bearer`)
|
|
294
284
|
- HTTP basic (`type: http`, `scheme: basic`)
|
|
@@ -312,7 +302,7 @@ This “only if present in current spec” behavior prevents accidental auth lea
|
|
|
312
302
|
Bearer-like schemes (`http-bearer`, `oauth2`, `openIdConnect`):
|
|
313
303
|
|
|
314
304
|
- `--bearer-token <token>` or `--oauth-token <token>`
|
|
315
|
-
- or a profile token stored via `
|
|
305
|
+
- or a profile token stored via `specli auth token ...`
|
|
316
306
|
|
|
317
307
|
Basic auth:
|
|
318
308
|
|
|
@@ -328,8 +318,8 @@ Profiles are for automation.
|
|
|
328
318
|
|
|
329
319
|
Config file:
|
|
330
320
|
|
|
331
|
-
- Read preference: `~/.config/
|
|
332
|
-
- Writes always go to: `~/.config/
|
|
321
|
+
- Read preference: `~/.config/specli/profiles.json`, else `~/.config/specli/profiles.yaml` if present
|
|
322
|
+
- Writes always go to: `~/.config/specli/profiles.json`
|
|
333
323
|
|
|
334
324
|
Secrets:
|
|
335
325
|
|
|
@@ -338,14 +328,14 @@ Secrets:
|
|
|
338
328
|
Commands:
|
|
339
329
|
|
|
340
330
|
```bash
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
331
|
+
specli profile list
|
|
332
|
+
specli profile set --name dev --server https://api.example.com --auth bearerAuth --default
|
|
333
|
+
specli profile use --name dev
|
|
334
|
+
specli profile rm --name dev
|
|
335
|
+
|
|
336
|
+
specli auth token --name dev --set "..."
|
|
337
|
+
specli auth token --name dev --get
|
|
338
|
+
specli auth token --name dev --delete
|
|
349
339
|
```
|
|
350
340
|
|
|
351
341
|
## Output Behavior
|
|
@@ -395,7 +385,7 @@ Prints the method, URL, headers, and body that would be sent without sending the
|
|
|
395
385
|
|
|
396
386
|
## `__schema`
|
|
397
387
|
|
|
398
|
-
`
|
|
388
|
+
`specli __schema` reports:
|
|
399
389
|
|
|
400
390
|
- OpenAPI title/version
|
|
401
391
|
- spec source + computed spec id + fingerprint
|
|
@@ -440,15 +430,15 @@ bun run smoke:specs
|
|
|
440
430
|
Or run ad-hoc smoke tests:
|
|
441
431
|
|
|
442
432
|
```bash
|
|
443
|
-
bunx
|
|
444
|
-
bunx
|
|
433
|
+
bunx specli exec <URL> __schema --json --min > /dev/null
|
|
434
|
+
bunx specli exec <URL> <some-resource> <some-action> --curl
|
|
445
435
|
```
|
|
446
436
|
|
|
447
|
-
Note: Kubernetes publishes a Swagger 2.0 document (`swagger.json`) which is not OpenAPI 3.x.
|
|
437
|
+
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
438
|
|
|
449
439
|
## Limitations (Important)
|
|
450
440
|
|
|
451
|
-
|
|
441
|
+
specli is intentionally v1-simple; common gaps for real-world specs:
|
|
452
442
|
|
|
453
443
|
- OpenAPI 3.x only (Swagger 2.0 not supported).
|
|
454
444
|
- 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.3",
|
|
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;
|