argsbarg 6.1.3 → 6.1.5
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/CHANGELOG.md +19 -1
- package/README.md +1 -1
- package/docs/http-server.md +22 -17
- package/examples/full-example/docs/cli-schema.json +9 -9
- package/examples/full-example/docs/cli.md +9 -9
- package/examples/full-example/docs/http.md +20 -20
- package/examples/full-example/docs/openapi.json +334 -14
- package/examples/servers.ts +2 -2
- package/index.d.ts +6 -1
- package/package.json +1 -1
- package/src/builtins/http.ts +3 -1
- package/src/core/types.ts +6 -1
- package/src/core/validate.ts +43 -0
- package/src/docs/http-guide.ts +15 -11
- package/src/http/openapi.ts +113 -5
- package/src/http/paths.ts +39 -0
- package/src/http/readiness.ts +1 -1
- package/src/http/result.ts +6 -6
- package/src/http/routes.ts +22 -9
- package/src/http/server.ts +8 -12
- package/src/test/integration/http.test.ts +165 -28
|
@@ -5,10 +5,285 @@
|
|
|
5
5
|
"version": "1.0.0",
|
|
6
6
|
"description": "Argsbarg full example reference app"
|
|
7
7
|
},
|
|
8
|
+
"tags": [
|
|
9
|
+
{
|
|
10
|
+
"name": "health",
|
|
11
|
+
"description": "Server health probes"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "echo",
|
|
15
|
+
"description": "Echo a message (MCP-friendly leaf)."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "render-json",
|
|
19
|
+
"description": "Echo a JSON message (schema-first JSON leaf demo)."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "status",
|
|
23
|
+
"description": "Show app version."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "workspaces",
|
|
27
|
+
"description": "Workspace collection and CRUD."
|
|
28
|
+
}
|
|
29
|
+
],
|
|
8
30
|
"paths": {
|
|
9
|
-
"/
|
|
31
|
+
"/health": {
|
|
32
|
+
"get": {
|
|
33
|
+
"tags": [
|
|
34
|
+
"health"
|
|
35
|
+
],
|
|
36
|
+
"operationId": "health",
|
|
37
|
+
"summary": "Liveness probe (alias of /health/liveness)",
|
|
38
|
+
"responses": {
|
|
39
|
+
"200": {
|
|
40
|
+
"description": "Server is listening",
|
|
41
|
+
"content": {
|
|
42
|
+
"application/json; charset=utf-8": {
|
|
43
|
+
"schema": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"properties": {
|
|
46
|
+
"ok": {
|
|
47
|
+
"type": "boolean",
|
|
48
|
+
"const": true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"required": [
|
|
52
|
+
"ok"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"/health/liveness": {
|
|
62
|
+
"get": {
|
|
63
|
+
"tags": [
|
|
64
|
+
"health"
|
|
65
|
+
],
|
|
66
|
+
"operationId": "health_liveness",
|
|
67
|
+
"summary": "Liveness probe",
|
|
68
|
+
"responses": {
|
|
69
|
+
"200": {
|
|
70
|
+
"description": "Server is listening",
|
|
71
|
+
"content": {
|
|
72
|
+
"application/json; charset=utf-8": {
|
|
73
|
+
"schema": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"properties": {
|
|
76
|
+
"ok": {
|
|
77
|
+
"type": "boolean",
|
|
78
|
+
"const": true
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"required": [
|
|
82
|
+
"ok"
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"/health/readiness": {
|
|
92
|
+
"get": {
|
|
93
|
+
"tags": [
|
|
94
|
+
"health"
|
|
95
|
+
],
|
|
96
|
+
"operationId": "health_readiness",
|
|
97
|
+
"summary": "Readiness probe",
|
|
98
|
+
"description": "Config file, required app config, and optional program.readiness checks.",
|
|
99
|
+
"responses": {
|
|
100
|
+
"200": {
|
|
101
|
+
"description": "Ready to serve traffic",
|
|
102
|
+
"content": {
|
|
103
|
+
"application/json; charset=utf-8": {
|
|
104
|
+
"schema": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"ok": {
|
|
108
|
+
"type": "boolean"
|
|
109
|
+
},
|
|
110
|
+
"checks": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"properties": {
|
|
113
|
+
"config_file": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"properties": {
|
|
116
|
+
"ok": {
|
|
117
|
+
"type": "boolean"
|
|
118
|
+
},
|
|
119
|
+
"error": {
|
|
120
|
+
"type": "string"
|
|
121
|
+
},
|
|
122
|
+
"missing": {
|
|
123
|
+
"type": "array",
|
|
124
|
+
"items": {
|
|
125
|
+
"type": "string"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"required": [
|
|
130
|
+
"ok"
|
|
131
|
+
]
|
|
132
|
+
},
|
|
133
|
+
"config_required": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"properties": {
|
|
136
|
+
"ok": {
|
|
137
|
+
"type": "boolean"
|
|
138
|
+
},
|
|
139
|
+
"error": {
|
|
140
|
+
"type": "string"
|
|
141
|
+
},
|
|
142
|
+
"missing": {
|
|
143
|
+
"type": "array",
|
|
144
|
+
"items": {
|
|
145
|
+
"type": "string"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
"required": [
|
|
150
|
+
"ok"
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
"custom": {
|
|
154
|
+
"type": "object",
|
|
155
|
+
"properties": {
|
|
156
|
+
"ok": {
|
|
157
|
+
"type": "boolean"
|
|
158
|
+
},
|
|
159
|
+
"error": {
|
|
160
|
+
"type": "string"
|
|
161
|
+
},
|
|
162
|
+
"missing": {
|
|
163
|
+
"type": "array",
|
|
164
|
+
"items": {
|
|
165
|
+
"type": "string"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
"required": [
|
|
170
|
+
"ok"
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"required": [
|
|
175
|
+
"config_file",
|
|
176
|
+
"config_required",
|
|
177
|
+
"custom"
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
"required": [
|
|
182
|
+
"ok",
|
|
183
|
+
"checks"
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
"503": {
|
|
190
|
+
"description": "Not ready",
|
|
191
|
+
"content": {
|
|
192
|
+
"application/json; charset=utf-8": {
|
|
193
|
+
"schema": {
|
|
194
|
+
"type": "object",
|
|
195
|
+
"properties": {
|
|
196
|
+
"ok": {
|
|
197
|
+
"type": "boolean"
|
|
198
|
+
},
|
|
199
|
+
"checks": {
|
|
200
|
+
"type": "object",
|
|
201
|
+
"properties": {
|
|
202
|
+
"config_file": {
|
|
203
|
+
"type": "object",
|
|
204
|
+
"properties": {
|
|
205
|
+
"ok": {
|
|
206
|
+
"type": "boolean"
|
|
207
|
+
},
|
|
208
|
+
"error": {
|
|
209
|
+
"type": "string"
|
|
210
|
+
},
|
|
211
|
+
"missing": {
|
|
212
|
+
"type": "array",
|
|
213
|
+
"items": {
|
|
214
|
+
"type": "string"
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
"required": [
|
|
219
|
+
"ok"
|
|
220
|
+
]
|
|
221
|
+
},
|
|
222
|
+
"config_required": {
|
|
223
|
+
"type": "object",
|
|
224
|
+
"properties": {
|
|
225
|
+
"ok": {
|
|
226
|
+
"type": "boolean"
|
|
227
|
+
},
|
|
228
|
+
"error": {
|
|
229
|
+
"type": "string"
|
|
230
|
+
},
|
|
231
|
+
"missing": {
|
|
232
|
+
"type": "array",
|
|
233
|
+
"items": {
|
|
234
|
+
"type": "string"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
"required": [
|
|
239
|
+
"ok"
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
"custom": {
|
|
243
|
+
"type": "object",
|
|
244
|
+
"properties": {
|
|
245
|
+
"ok": {
|
|
246
|
+
"type": "boolean"
|
|
247
|
+
},
|
|
248
|
+
"error": {
|
|
249
|
+
"type": "string"
|
|
250
|
+
},
|
|
251
|
+
"missing": {
|
|
252
|
+
"type": "array",
|
|
253
|
+
"items": {
|
|
254
|
+
"type": "string"
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
"required": [
|
|
259
|
+
"ok"
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"required": [
|
|
264
|
+
"config_file",
|
|
265
|
+
"config_required",
|
|
266
|
+
"custom"
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
"required": [
|
|
271
|
+
"ok",
|
|
272
|
+
"checks"
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
"/echo": {
|
|
10
282
|
"post": {
|
|
11
|
-
"
|
|
283
|
+
"tags": [
|
|
284
|
+
"echo"
|
|
285
|
+
],
|
|
286
|
+
"operationId": "_echo",
|
|
12
287
|
"summary": "Echo a message (MCP-friendly leaf).",
|
|
13
288
|
"responses": {
|
|
14
289
|
"201": {
|
|
@@ -115,9 +390,12 @@
|
|
|
115
390
|
}
|
|
116
391
|
}
|
|
117
392
|
},
|
|
118
|
-
"/
|
|
393
|
+
"/render-json": {
|
|
119
394
|
"post": {
|
|
120
|
-
"
|
|
395
|
+
"tags": [
|
|
396
|
+
"render-json"
|
|
397
|
+
],
|
|
398
|
+
"operationId": "_render-json",
|
|
121
399
|
"summary": "Echo a JSON message (schema-first JSON leaf demo).",
|
|
122
400
|
"responses": {
|
|
123
401
|
"201": {
|
|
@@ -202,12 +480,36 @@
|
|
|
202
480
|
}
|
|
203
481
|
}
|
|
204
482
|
}
|
|
483
|
+
},
|
|
484
|
+
"requestBody": {
|
|
485
|
+
"required": true,
|
|
486
|
+
"content": {
|
|
487
|
+
"application/json; charset=utf-8": {
|
|
488
|
+
"schema": {
|
|
489
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
490
|
+
"type": "object",
|
|
491
|
+
"properties": {
|
|
492
|
+
"message": {
|
|
493
|
+
"type": "string",
|
|
494
|
+
"description": "Message to echo back."
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
"required": [
|
|
498
|
+
"message"
|
|
499
|
+
],
|
|
500
|
+
"additionalProperties": false
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
205
504
|
}
|
|
206
505
|
}
|
|
207
506
|
},
|
|
208
|
-
"/
|
|
507
|
+
"/status": {
|
|
209
508
|
"post": {
|
|
210
|
-
"
|
|
509
|
+
"tags": [
|
|
510
|
+
"status"
|
|
511
|
+
],
|
|
512
|
+
"operationId": "_status",
|
|
211
513
|
"summary": "Show app version.",
|
|
212
514
|
"responses": {
|
|
213
515
|
"201": {
|
|
@@ -322,9 +624,12 @@
|
|
|
322
624
|
}
|
|
323
625
|
}
|
|
324
626
|
},
|
|
325
|
-
"/
|
|
627
|
+
"/workspaces": {
|
|
326
628
|
"get": {
|
|
327
|
-
"
|
|
629
|
+
"tags": [
|
|
630
|
+
"workspaces"
|
|
631
|
+
],
|
|
632
|
+
"operationId": "_workspaces",
|
|
328
633
|
"summary": "List workspaces.",
|
|
329
634
|
"responses": {
|
|
330
635
|
"200": {
|
|
@@ -413,7 +718,10 @@
|
|
|
413
718
|
"parameters": []
|
|
414
719
|
},
|
|
415
720
|
"post": {
|
|
416
|
-
"
|
|
721
|
+
"tags": [
|
|
722
|
+
"workspaces"
|
|
723
|
+
],
|
|
724
|
+
"operationId": "_workspaces",
|
|
417
725
|
"summary": "Create a workspace.",
|
|
418
726
|
"responses": {
|
|
419
727
|
"201": {
|
|
@@ -522,9 +830,12 @@
|
|
|
522
830
|
}
|
|
523
831
|
}
|
|
524
832
|
},
|
|
525
|
-
"/
|
|
833
|
+
"/workspaces/{id}": {
|
|
526
834
|
"get": {
|
|
527
|
-
"
|
|
835
|
+
"tags": [
|
|
836
|
+
"workspaces"
|
|
837
|
+
],
|
|
838
|
+
"operationId": "_workspaces_id",
|
|
528
839
|
"summary": "Get one workspace.",
|
|
529
840
|
"responses": {
|
|
530
841
|
"200": {
|
|
@@ -622,7 +933,10 @@
|
|
|
622
933
|
]
|
|
623
934
|
},
|
|
624
935
|
"put": {
|
|
625
|
-
"
|
|
936
|
+
"tags": [
|
|
937
|
+
"workspaces"
|
|
938
|
+
],
|
|
939
|
+
"operationId": "_workspaces_id",
|
|
626
940
|
"summary": "Replace a workspace.",
|
|
627
941
|
"responses": {
|
|
628
942
|
"200": {
|
|
@@ -741,7 +1055,10 @@
|
|
|
741
1055
|
}
|
|
742
1056
|
},
|
|
743
1057
|
"patch": {
|
|
744
|
-
"
|
|
1058
|
+
"tags": [
|
|
1059
|
+
"workspaces"
|
|
1060
|
+
],
|
|
1061
|
+
"operationId": "_workspaces_id",
|
|
745
1062
|
"summary": "Patch a workspace name.",
|
|
746
1063
|
"responses": {
|
|
747
1064
|
"200": {
|
|
@@ -860,7 +1177,10 @@
|
|
|
860
1177
|
}
|
|
861
1178
|
},
|
|
862
1179
|
"delete": {
|
|
863
|
-
"
|
|
1180
|
+
"tags": [
|
|
1181
|
+
"workspaces"
|
|
1182
|
+
],
|
|
1183
|
+
"operationId": "_workspaces_id",
|
|
864
1184
|
"summary": "Delete a workspace.",
|
|
865
1185
|
"responses": {
|
|
866
1186
|
"204": {
|
package/examples/servers.ts
CHANGED
|
@@ -4,10 +4,10 @@ This example shows the smallest end-to-end CLI+MCP+API setup.
|
|
|
4
4
|
It includes one command, a couple of options, and a direct call to the runtime so
|
|
5
5
|
readers can copy the pattern into their own scripts quickly.
|
|
6
6
|
|
|
7
|
-
Demonstrates: `servers.ts hello`, MCP tool `hello`, and `POST /
|
|
7
|
+
Demonstrates: `servers.ts hello`, MCP tool `hello`, and `POST /hello`.
|
|
8
8
|
|
|
9
9
|
Ex API Call:
|
|
10
|
-
curl -s -X POST http://127.0.0.1:3000/
|
|
10
|
+
curl -s -X POST http://127.0.0.1:3000/hello \
|
|
11
11
|
-H 'content-type: application/json' \
|
|
12
12
|
-d '{"name":"alice"}'
|
|
13
13
|
Ex API Response:
|
package/index.d.ts
CHANGED
|
@@ -353,6 +353,11 @@ export interface CliHttpServerConfig {
|
|
|
353
353
|
host?: string;
|
|
354
354
|
/** Listen port (default: `3000`). */
|
|
355
355
|
port?: number;
|
|
356
|
+
/**
|
|
357
|
+
* URL prefix for user command routes (default: `""` — routes at server root, e.g. `/workspaces`).
|
|
358
|
+
* Set to `"/api"` for `/api/workspaces`-style paths.
|
|
359
|
+
*/
|
|
360
|
+
pathPrefix?: string;
|
|
356
361
|
/** Honor `X-Forwarded-For` for client IP in hooks and logs. */
|
|
357
362
|
trustProxy?: boolean;
|
|
358
363
|
/** HTTP error response defaults. */
|
|
@@ -744,7 +749,7 @@ export type CliProgram = CliNode & {
|
|
|
744
749
|
docs?: CliDocsConfig;
|
|
745
750
|
/** Invoke and error hooks for user commands on CLI, HTTP, and MCP. */
|
|
746
751
|
hooks?: CliProgramHooks;
|
|
747
|
-
/** Optional readiness probe for HTTP/MCP `GET /health/
|
|
752
|
+
/** Optional readiness probe for HTTP/MCP `GET /health/readiness` only. */
|
|
748
753
|
readiness?: (ctx: ReadinessContext) => boolean | Promise<boolean>;
|
|
749
754
|
/** Framework logging (stderr + optional file). */
|
|
750
755
|
log?: CliLogConfig;
|
package/package.json
CHANGED
package/src/builtins/http.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type CliRouter,
|
|
8
8
|
} from "~/core/types.ts";
|
|
9
9
|
import { docsEnabled } from "~/docs/resolve.ts";
|
|
10
|
+
import { httpUserPathGlob, resolveHttpPathPrefix } from "~/http/paths.ts";
|
|
10
11
|
import { resolveHttpListenAddress } from "~/http/server.ts";
|
|
11
12
|
import { resolveCapabilities } from "~/runtime/capabilities.ts";
|
|
12
13
|
|
|
@@ -34,10 +35,11 @@ const HTTP_SERVE_OPTIONS: CliOption[] = [
|
|
|
34
35
|
export function cliBuiltinHttpCommand(program: CliProgram): CliRouter {
|
|
35
36
|
const caps = resolveCapabilities(program);
|
|
36
37
|
const { hostname, port } = resolveHttpListenAddress(program);
|
|
38
|
+
const userGlob = httpUserPathGlob(resolveHttpPathPrefix(program));
|
|
37
39
|
const lines = [
|
|
38
40
|
`HTTP tool server on http://${hostname}:${port}.`,
|
|
39
41
|
"",
|
|
40
|
-
|
|
42
|
+
`Endpoints: GET /health, GET /health/readiness, GET /openapi.json, GET /swagger, ${userGlob}`,
|
|
41
43
|
"",
|
|
42
44
|
];
|
|
43
45
|
if (caps.configure) {
|
package/src/core/types.ts
CHANGED
|
@@ -246,6 +246,11 @@ export interface CliHttpServerConfig {
|
|
|
246
246
|
host?: string;
|
|
247
247
|
/** Listen port (default: `3000`). */
|
|
248
248
|
port?: number;
|
|
249
|
+
/**
|
|
250
|
+
* URL prefix for user command routes (default: `""` — routes at server root, e.g. `/workspaces`).
|
|
251
|
+
* Set to `"/api"` for `/api/workspaces`-style paths.
|
|
252
|
+
*/
|
|
253
|
+
pathPrefix?: string;
|
|
249
254
|
/** Honor `X-Forwarded-For` for client IP in hooks and logs. */
|
|
250
255
|
trustProxy?: boolean;
|
|
251
256
|
/** HTTP error response defaults. */
|
|
@@ -669,7 +674,7 @@ export type CliProgram = CliNode & {
|
|
|
669
674
|
docs?: CliDocsConfig;
|
|
670
675
|
/** Invoke and error hooks for user commands on CLI, HTTP, and MCP. */
|
|
671
676
|
hooks?: CliProgramHooks;
|
|
672
|
-
/** Optional readiness probe for HTTP/MCP `GET /health/
|
|
677
|
+
/** Optional readiness probe for HTTP/MCP `GET /health/readiness` only. */
|
|
673
678
|
readiness?: (ctx: ReadinessContext) => boolean | Promise<boolean>;
|
|
674
679
|
/** Framework logging (stderr + optional file). */
|
|
675
680
|
log?: CliLogConfig;
|
package/src/core/validate.ts
CHANGED
|
@@ -5,6 +5,7 @@ This module validates CLI schemas before execution.
|
|
|
5
5
|
import { AGENT_PAIRS, MCP_KEYS, mcpServerRequiredForArtifact } from "~/configure/artifacts/target-registry.ts";
|
|
6
6
|
import { reservedDocsTopicResourceUris } from "~/docs/mcp-resources.ts";
|
|
7
7
|
import { DOCS_BUILTIN_TOPIC_KEYS, docsEnabled } from "~/docs/resolve.ts";
|
|
8
|
+
import { HTTP_RESERVED_TOP_LEVEL_SEGMENTS } from "~/http/paths.ts";
|
|
8
9
|
import { resolveMcpSchemaUri } from "~/mcp/tools.ts";
|
|
9
10
|
import { reservedCommandNames, resolveCapabilities } from "~/runtime/capabilities.ts";
|
|
10
11
|
import { validateFormatValue } from "./formats.ts";
|
|
@@ -180,6 +181,8 @@ export function cliValidateProgram(program: CliProgram): void {
|
|
|
180
181
|
throw new CliSchemaValidationError("httpServer requires enabled: true; omit httpServer to disable HTTP API");
|
|
181
182
|
}
|
|
182
183
|
|
|
184
|
+
validateHttpPathPrefix(program);
|
|
185
|
+
|
|
183
186
|
if (docsEnabled(program) && program.docs?.topics !== undefined) {
|
|
184
187
|
validateDocsConfig(program.docs);
|
|
185
188
|
}
|
|
@@ -212,6 +215,46 @@ function isParamRouterKey(key: string): boolean {
|
|
|
212
215
|
return key.startsWith(":");
|
|
213
216
|
}
|
|
214
217
|
|
|
218
|
+
/** Validates `httpServer.pathPrefix` and reserved top-level command keys. */
|
|
219
|
+
function validateHttpPathPrefix(program: CliProgram): void {
|
|
220
|
+
if (!program.httpServer?.enabled) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const raw = program.httpServer.pathPrefix;
|
|
224
|
+
if (raw !== undefined && raw !== "") {
|
|
225
|
+
if (!raw.startsWith("/")) {
|
|
226
|
+
throw new CliSchemaValidationError(`httpServer.pathPrefix must start with / (got ${JSON.stringify(raw)})`);
|
|
227
|
+
}
|
|
228
|
+
if (raw.length > 1 && raw.endsWith("/")) {
|
|
229
|
+
throw new CliSchemaValidationError(`httpServer.pathPrefix must not end with / (got ${JSON.stringify(raw)})`);
|
|
230
|
+
}
|
|
231
|
+
if (raw.includes("//")) {
|
|
232
|
+
throw new CliSchemaValidationError("httpServer.pathPrefix must not contain //");
|
|
233
|
+
}
|
|
234
|
+
if (raw === "/health" || raw === "/swagger" || raw === "/openapi.json" || raw === "/tools") {
|
|
235
|
+
throw new CliSchemaValidationError(
|
|
236
|
+
`httpServer.pathPrefix must not be a framework path (got ${JSON.stringify(raw)})`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (!isCliRouter(program)) {
|
|
242
|
+
if (isCliLeaf(program) && HTTP_RESERVED_TOP_LEVEL_SEGMENTS.has(program.key)) {
|
|
243
|
+
throw new CliSchemaValidationError(
|
|
244
|
+
`Reserved HTTP program key when httpServer.pathPrefix is empty: ${program.key}`,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
for (const child of program.commands) {
|
|
250
|
+
if (HTTP_RESERVED_TOP_LEVEL_SEGMENTS.has(child.key)) {
|
|
251
|
+
throw new CliSchemaValidationError(
|
|
252
|
+
`Reserved HTTP command name when httpServer.pathPrefix is empty: ${child.key} (set httpServer.pathPrefix or rename)`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
215
258
|
function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
216
259
|
if (!isRoot) {
|
|
217
260
|
const rogue = node as CliProgram;
|
package/src/docs/http-guide.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { defaultConfigEntryTitle } from "~/config/entry.ts";
|
|
|
2
2
|
import { displayAppConfigPath } from "~/config/file.ts";
|
|
3
3
|
import { collectOptionDefs } from "~/core/parse.ts";
|
|
4
4
|
import { CliOptionKind, type CliProgram } from "~/core/types.ts";
|
|
5
|
+
import { httpUserPathGlob, resolveHttpPathPrefix } from "~/http/paths.ts";
|
|
5
6
|
import { collectHttpRoutes } from "~/http/routes.ts";
|
|
6
7
|
import { resolveHttpListenAddress } from "~/http/server.ts";
|
|
7
8
|
|
|
@@ -27,6 +28,9 @@ export function generateHttpGuide(root: CliProgram): string {
|
|
|
27
28
|
const routes = collectHttpRoutes(root);
|
|
28
29
|
const { hostname, port } = resolveHttpListenAddress(root);
|
|
29
30
|
const baseUrl = `http://${hostname}:${port}`;
|
|
31
|
+
const pathPrefix = resolveHttpPathPrefix(root);
|
|
32
|
+
const userGlob = httpUserPathGlob(pathPrefix);
|
|
33
|
+
const sampleWorkspaces = `${pathPrefix}/workspaces`;
|
|
30
34
|
|
|
31
35
|
const lines: string[] = [
|
|
32
36
|
`# HTTP API (${root.key})`,
|
|
@@ -47,23 +51,23 @@ export function generateHttpGuide(root: CliProgram): string {
|
|
|
47
51
|
"",
|
|
48
52
|
"| Method | Path | Purpose |",
|
|
49
53
|
"| --- | --- | --- |",
|
|
50
|
-
"| `GET` | `/health` or `/health/
|
|
51
|
-
"| `GET` | `/health/
|
|
54
|
+
"| `GET` | `/health` or `/health/liveness` | Liveness check |",
|
|
55
|
+
"| `GET` | `/health/readiness` | Readiness (config + `program.readiness`) |",
|
|
52
56
|
"| `GET` | `/openapi.json` | OpenAPI 3.1 REST paths |",
|
|
53
|
-
"| `GET` | `/
|
|
54
|
-
|
|
57
|
+
"| `GET` | `/swagger` | Interactive Swagger UI API reference |",
|
|
58
|
+
`| * | \`${userGlob}\` | Invoke user commands (method per route) |`,
|
|
55
59
|
"| `OPTIONS` | `*` | CORS preflight |",
|
|
56
60
|
"",
|
|
57
|
-
|
|
61
|
+
`Discover paths from \`openapi.json\` (\`${userGlob}\`). Query binds options; POST/PUT/PATCH body binds options and \`inputSchema\` fields.`,
|
|
58
62
|
"",
|
|
59
63
|
"## Examples",
|
|
60
64
|
"",
|
|
61
65
|
"```bash",
|
|
62
66
|
`curl -s ${baseUrl}/health`,
|
|
63
|
-
`curl -s ${baseUrl}/health/
|
|
67
|
+
`curl -s ${baseUrl}/health/readiness`,
|
|
64
68
|
`curl -s ${baseUrl}/openapi.json`,
|
|
65
|
-
`curl -s ${baseUrl}
|
|
66
|
-
`curl -s -X POST ${baseUrl}
|
|
69
|
+
`curl -s ${baseUrl}${sampleWorkspaces}`,
|
|
70
|
+
`curl -s -X POST ${baseUrl}${sampleWorkspaces} \\`,
|
|
67
71
|
' -H "content-type: application/json" \\',
|
|
68
72
|
` -d '{"name":"qa2"}'`,
|
|
69
73
|
"```",
|
|
@@ -111,7 +115,7 @@ export function generateHttpGuide(root: CliProgram): string {
|
|
|
111
115
|
"",
|
|
112
116
|
"POST/PUT/PATCH bodies are a flat JSON object keyed by long option and positional names (hyphenated option names are valid keys).",
|
|
113
117
|
"",
|
|
114
|
-
`For HTTP clients, use **\`GET /openapi.json\`** (or **\`GET /
|
|
118
|
+
`For HTTP clients, use **\`GET /openapi.json\`** (or **\`GET /swagger\`**) for per-route request shapes.`,
|
|
115
119
|
"",
|
|
116
120
|
"Varargs positionals accept a JSON array of strings (not a comma-separated string).",
|
|
117
121
|
"Options with `format: comma-list` accept a comma-separated string or JSON array.",
|
|
@@ -123,11 +127,11 @@ export function generateHttpGuide(root: CliProgram): string {
|
|
|
123
127
|
"",
|
|
124
128
|
"The HTTP API is described in OpenAPI 3.1.",
|
|
125
129
|
"",
|
|
126
|
-
`- **Browse** — [${baseUrl}/
|
|
130
|
+
`- **Browse** — [${baseUrl}/swagger](${baseUrl}/swagger) (Swagger UI; loads \`/openapi.json\`)`,
|
|
127
131
|
`- **Fetch** — \`curl -s ${baseUrl}/openapi.json\``,
|
|
128
132
|
`- **Save offline** — \`${root.key} docs openapi --save\` → \`./docs/openapi.json\` (or \`just docgen\` in app repos)`,
|
|
129
133
|
"",
|
|
130
|
-
|
|
134
|
+
`Use the spec to discover REST paths and request/response shapes before calling \`${userGlob}\`.`,
|
|
131
135
|
"",
|
|
132
136
|
);
|
|
133
137
|
|