argsbarg 6.1.4 → 6.1.6
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 +16 -2
- package/README.md +1 -1
- package/docs/http-server.md +21 -16
- 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 +18 -18
- package/examples/full-example/docs/openapi.json +284 -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 +13 -9
- package/src/http/openapi.ts +25 -17
- package/src/http/paths.ts +39 -0
- package/src/http/readiness.ts +1 -1
- package/src/http/routes.ts +22 -9
- package/src/http/server.ts +7 -11
- package/src/test/integration/http.test.ts +71 -37
|
@@ -5,10 +5,256 @@
|
|
|
5
5
|
"version": "1.0.0",
|
|
6
6
|
"description": "Argsbarg full example reference app"
|
|
7
7
|
},
|
|
8
|
+
"tags": [
|
|
9
|
+
{
|
|
10
|
+
"name": "health",
|
|
11
|
+
"description": "Orchestrator health probes — liveness (online) vs readiness (online + checks passed)."
|
|
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/liveness": {
|
|
32
|
+
"get": {
|
|
33
|
+
"tags": [
|
|
34
|
+
"health"
|
|
35
|
+
],
|
|
36
|
+
"operationId": "health_liveness",
|
|
37
|
+
"summary": "Liveness probe",
|
|
38
|
+
"description": "Returns 200 when the HTTP server is online and accepting requests. Does not run config or readiness checks — use for orchestrator liveness probes only.",
|
|
39
|
+
"responses": {
|
|
40
|
+
"200": {
|
|
41
|
+
"description": "Server is online",
|
|
42
|
+
"content": {
|
|
43
|
+
"application/json; charset=utf-8": {
|
|
44
|
+
"schema": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"properties": {
|
|
47
|
+
"ok": {
|
|
48
|
+
"type": "boolean",
|
|
49
|
+
"const": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"required": [
|
|
53
|
+
"ok"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"/health/readiness": {
|
|
63
|
+
"get": {
|
|
64
|
+
"tags": [
|
|
65
|
+
"health"
|
|
66
|
+
],
|
|
67
|
+
"operationId": "health_readiness",
|
|
68
|
+
"summary": "Readiness probe",
|
|
69
|
+
"description": "Returns 200 when the server is online and all readiness checks pass (config file, required app config, and optional program.readiness). Returns 503 when any check fails — use for orchestrator readiness probes before routing traffic.",
|
|
70
|
+
"responses": {
|
|
71
|
+
"200": {
|
|
72
|
+
"description": "Online and ready to serve traffic",
|
|
73
|
+
"content": {
|
|
74
|
+
"application/json; charset=utf-8": {
|
|
75
|
+
"schema": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": {
|
|
78
|
+
"ok": {
|
|
79
|
+
"type": "boolean"
|
|
80
|
+
},
|
|
81
|
+
"checks": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"properties": {
|
|
84
|
+
"config_file": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"ok": {
|
|
88
|
+
"type": "boolean"
|
|
89
|
+
},
|
|
90
|
+
"error": {
|
|
91
|
+
"type": "string"
|
|
92
|
+
},
|
|
93
|
+
"missing": {
|
|
94
|
+
"type": "array",
|
|
95
|
+
"items": {
|
|
96
|
+
"type": "string"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"required": [
|
|
101
|
+
"ok"
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
"config_required": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"ok": {
|
|
108
|
+
"type": "boolean"
|
|
109
|
+
},
|
|
110
|
+
"error": {
|
|
111
|
+
"type": "string"
|
|
112
|
+
},
|
|
113
|
+
"missing": {
|
|
114
|
+
"type": "array",
|
|
115
|
+
"items": {
|
|
116
|
+
"type": "string"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"required": [
|
|
121
|
+
"ok"
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
"custom": {
|
|
125
|
+
"type": "object",
|
|
126
|
+
"properties": {
|
|
127
|
+
"ok": {
|
|
128
|
+
"type": "boolean"
|
|
129
|
+
},
|
|
130
|
+
"error": {
|
|
131
|
+
"type": "string"
|
|
132
|
+
},
|
|
133
|
+
"missing": {
|
|
134
|
+
"type": "array",
|
|
135
|
+
"items": {
|
|
136
|
+
"type": "string"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"required": [
|
|
141
|
+
"ok"
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"required": [
|
|
146
|
+
"config_file",
|
|
147
|
+
"config_required",
|
|
148
|
+
"custom"
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"required": [
|
|
153
|
+
"ok",
|
|
154
|
+
"checks"
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"503": {
|
|
161
|
+
"description": "Online but not ready (one or more checks failed)",
|
|
162
|
+
"content": {
|
|
163
|
+
"application/json; charset=utf-8": {
|
|
164
|
+
"schema": {
|
|
165
|
+
"type": "object",
|
|
166
|
+
"properties": {
|
|
167
|
+
"ok": {
|
|
168
|
+
"type": "boolean"
|
|
169
|
+
},
|
|
170
|
+
"checks": {
|
|
171
|
+
"type": "object",
|
|
172
|
+
"properties": {
|
|
173
|
+
"config_file": {
|
|
174
|
+
"type": "object",
|
|
175
|
+
"properties": {
|
|
176
|
+
"ok": {
|
|
177
|
+
"type": "boolean"
|
|
178
|
+
},
|
|
179
|
+
"error": {
|
|
180
|
+
"type": "string"
|
|
181
|
+
},
|
|
182
|
+
"missing": {
|
|
183
|
+
"type": "array",
|
|
184
|
+
"items": {
|
|
185
|
+
"type": "string"
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
"required": [
|
|
190
|
+
"ok"
|
|
191
|
+
]
|
|
192
|
+
},
|
|
193
|
+
"config_required": {
|
|
194
|
+
"type": "object",
|
|
195
|
+
"properties": {
|
|
196
|
+
"ok": {
|
|
197
|
+
"type": "boolean"
|
|
198
|
+
},
|
|
199
|
+
"error": {
|
|
200
|
+
"type": "string"
|
|
201
|
+
},
|
|
202
|
+
"missing": {
|
|
203
|
+
"type": "array",
|
|
204
|
+
"items": {
|
|
205
|
+
"type": "string"
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
"required": [
|
|
210
|
+
"ok"
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
"custom": {
|
|
214
|
+
"type": "object",
|
|
215
|
+
"properties": {
|
|
216
|
+
"ok": {
|
|
217
|
+
"type": "boolean"
|
|
218
|
+
},
|
|
219
|
+
"error": {
|
|
220
|
+
"type": "string"
|
|
221
|
+
},
|
|
222
|
+
"missing": {
|
|
223
|
+
"type": "array",
|
|
224
|
+
"items": {
|
|
225
|
+
"type": "string"
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
"required": [
|
|
230
|
+
"ok"
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
"required": [
|
|
235
|
+
"config_file",
|
|
236
|
+
"config_required",
|
|
237
|
+
"custom"
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
"required": [
|
|
242
|
+
"ok",
|
|
243
|
+
"checks"
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
"/echo": {
|
|
10
253
|
"post": {
|
|
11
|
-
"
|
|
254
|
+
"tags": [
|
|
255
|
+
"echo"
|
|
256
|
+
],
|
|
257
|
+
"operationId": "_echo",
|
|
12
258
|
"summary": "Echo a message (MCP-friendly leaf).",
|
|
13
259
|
"responses": {
|
|
14
260
|
"201": {
|
|
@@ -115,9 +361,12 @@
|
|
|
115
361
|
}
|
|
116
362
|
}
|
|
117
363
|
},
|
|
118
|
-
"/
|
|
364
|
+
"/render-json": {
|
|
119
365
|
"post": {
|
|
120
|
-
"
|
|
366
|
+
"tags": [
|
|
367
|
+
"render-json"
|
|
368
|
+
],
|
|
369
|
+
"operationId": "_render-json",
|
|
121
370
|
"summary": "Echo a JSON message (schema-first JSON leaf demo).",
|
|
122
371
|
"responses": {
|
|
123
372
|
"201": {
|
|
@@ -226,9 +475,12 @@
|
|
|
226
475
|
}
|
|
227
476
|
}
|
|
228
477
|
},
|
|
229
|
-
"/
|
|
478
|
+
"/status": {
|
|
230
479
|
"post": {
|
|
231
|
-
"
|
|
480
|
+
"tags": [
|
|
481
|
+
"status"
|
|
482
|
+
],
|
|
483
|
+
"operationId": "_status",
|
|
232
484
|
"summary": "Show app version.",
|
|
233
485
|
"responses": {
|
|
234
486
|
"201": {
|
|
@@ -343,9 +595,12 @@
|
|
|
343
595
|
}
|
|
344
596
|
}
|
|
345
597
|
},
|
|
346
|
-
"/
|
|
598
|
+
"/workspaces": {
|
|
347
599
|
"get": {
|
|
348
|
-
"
|
|
600
|
+
"tags": [
|
|
601
|
+
"workspaces"
|
|
602
|
+
],
|
|
603
|
+
"operationId": "_workspaces",
|
|
349
604
|
"summary": "List workspaces.",
|
|
350
605
|
"responses": {
|
|
351
606
|
"200": {
|
|
@@ -434,7 +689,10 @@
|
|
|
434
689
|
"parameters": []
|
|
435
690
|
},
|
|
436
691
|
"post": {
|
|
437
|
-
"
|
|
692
|
+
"tags": [
|
|
693
|
+
"workspaces"
|
|
694
|
+
],
|
|
695
|
+
"operationId": "_workspaces",
|
|
438
696
|
"summary": "Create a workspace.",
|
|
439
697
|
"responses": {
|
|
440
698
|
"201": {
|
|
@@ -543,9 +801,12 @@
|
|
|
543
801
|
}
|
|
544
802
|
}
|
|
545
803
|
},
|
|
546
|
-
"/
|
|
804
|
+
"/workspaces/{id}": {
|
|
547
805
|
"get": {
|
|
548
|
-
"
|
|
806
|
+
"tags": [
|
|
807
|
+
"workspaces"
|
|
808
|
+
],
|
|
809
|
+
"operationId": "_workspaces_id",
|
|
549
810
|
"summary": "Get one workspace.",
|
|
550
811
|
"responses": {
|
|
551
812
|
"200": {
|
|
@@ -643,7 +904,10 @@
|
|
|
643
904
|
]
|
|
644
905
|
},
|
|
645
906
|
"put": {
|
|
646
|
-
"
|
|
907
|
+
"tags": [
|
|
908
|
+
"workspaces"
|
|
909
|
+
],
|
|
910
|
+
"operationId": "_workspaces_id",
|
|
647
911
|
"summary": "Replace a workspace.",
|
|
648
912
|
"responses": {
|
|
649
913
|
"200": {
|
|
@@ -762,7 +1026,10 @@
|
|
|
762
1026
|
}
|
|
763
1027
|
},
|
|
764
1028
|
"patch": {
|
|
765
|
-
"
|
|
1029
|
+
"tags": [
|
|
1030
|
+
"workspaces"
|
|
1031
|
+
],
|
|
1032
|
+
"operationId": "_workspaces_id",
|
|
766
1033
|
"summary": "Patch a workspace name.",
|
|
767
1034
|
"responses": {
|
|
768
1035
|
"200": {
|
|
@@ -881,7 +1148,10 @@
|
|
|
881
1148
|
}
|
|
882
1149
|
},
|
|
883
1150
|
"delete": {
|
|
884
|
-
"
|
|
1151
|
+
"tags": [
|
|
1152
|
+
"workspaces"
|
|
1153
|
+
],
|
|
1154
|
+
"operationId": "_workspaces_id",
|
|
885
1155
|
"summary": "Delete a workspace.",
|
|
886
1156
|
"responses": {
|
|
887
1157
|
"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/liveness, 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
|
|
51
|
-
"| `GET` | `/health/
|
|
54
|
+
"| `GET` | `/health/liveness` | Liveness — server is online and accepting requests |",
|
|
55
|
+
"| `GET` | `/health/readiness` | Readiness — online plus config and `program.readiness` checks passed |",
|
|
52
56
|
"| `GET` | `/openapi.json` | OpenAPI 3.1 REST paths |",
|
|
53
57
|
"| `GET` | `/swagger` | Interactive Swagger UI API reference |",
|
|
54
|
-
|
|
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
|
-
`curl -s ${baseUrl}/health`,
|
|
63
|
-
`curl -s ${baseUrl}/health/
|
|
66
|
+
`curl -s ${baseUrl}/health/liveness`,
|
|
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
|
"```",
|
|
@@ -127,7 +131,7 @@ export function generateHttpGuide(root: CliProgram): string {
|
|
|
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
|
|
package/src/http/openapi.ts
CHANGED
|
@@ -150,35 +150,35 @@ function jsonResponseEntry(description: string, schema: Record<string, unknown>)
|
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
function livenessGetOp(
|
|
153
|
+
function livenessGetOp(): Record<string, unknown> {
|
|
154
154
|
return {
|
|
155
155
|
tags: [HEALTH_TAG],
|
|
156
|
-
operationId,
|
|
157
|
-
summary,
|
|
156
|
+
operationId: "health_liveness",
|
|
157
|
+
summary: "Liveness probe",
|
|
158
|
+
description:
|
|
159
|
+
"Returns 200 when the HTTP server is online and accepting requests. Does not run config or readiness checks — use for orchestrator liveness probes only.",
|
|
158
160
|
responses: {
|
|
159
|
-
"200": jsonResponseEntry("Server is
|
|
161
|
+
"200": jsonResponseEntry("Server is online", livenessResponseSchema),
|
|
160
162
|
},
|
|
161
163
|
};
|
|
162
164
|
}
|
|
163
165
|
|
|
164
|
-
/** Framework health probe paths served alongside
|
|
166
|
+
/** Framework health probe paths served alongside user command routes. */
|
|
165
167
|
function buildHealthPaths(): Record<string, unknown> {
|
|
166
168
|
return {
|
|
167
|
-
"/health": {
|
|
168
|
-
get: livenessGetOp(
|
|
169
|
+
"/health/liveness": {
|
|
170
|
+
get: livenessGetOp(),
|
|
169
171
|
},
|
|
170
|
-
"/health/
|
|
171
|
-
get: livenessGetOp("health_live", "Liveness probe"),
|
|
172
|
-
},
|
|
173
|
-
"/health/ready": {
|
|
172
|
+
"/health/readiness": {
|
|
174
173
|
get: {
|
|
175
174
|
tags: [HEALTH_TAG],
|
|
176
|
-
operationId: "
|
|
175
|
+
operationId: "health_readiness",
|
|
177
176
|
summary: "Readiness probe",
|
|
178
|
-
description:
|
|
177
|
+
description:
|
|
178
|
+
"Returns 200 when the server is online and all readiness checks pass (config file, required app config, and optional program.readiness). Returns 503 when any check fails — use for orchestrator readiness probes before routing traffic.",
|
|
179
179
|
responses: {
|
|
180
|
-
"200": jsonResponseEntry("
|
|
181
|
-
"503": jsonResponseEntry("
|
|
180
|
+
"200": jsonResponseEntry("Online and ready to serve traffic", readinessResponseSchema),
|
|
181
|
+
"503": jsonResponseEntry("Online but not ready (one or more checks failed)", readinessResponseSchema),
|
|
182
182
|
},
|
|
183
183
|
},
|
|
184
184
|
},
|
|
@@ -200,7 +200,7 @@ function findTopLevelCommand(program: CliProgram, key: string): CliNode | undefi
|
|
|
200
200
|
return program.commands.find((c) => c.key === key);
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
/** OpenAPI tags for user
|
|
203
|
+
/** OpenAPI tags for user command routes, one per top-level command. */
|
|
204
204
|
function collectCommandTags(program: CliProgram, routes: HttpRoute[]): { name: string; description?: string }[] {
|
|
205
205
|
const names = [...new Set(routes.map((route) => topLevelCommandKey(route, program)))].sort();
|
|
206
206
|
return names.map((name) => {
|
|
@@ -278,7 +278,15 @@ export function generateOpenApi(program: CliProgram): Record<string, unknown> {
|
|
|
278
278
|
description: program.description,
|
|
279
279
|
},
|
|
280
280
|
...(program.httpServer?.enabled
|
|
281
|
-
? {
|
|
281
|
+
? {
|
|
282
|
+
tags: [
|
|
283
|
+
{
|
|
284
|
+
name: HEALTH_TAG,
|
|
285
|
+
description: "Orchestrator health probes — liveness (online) vs readiness (online + checks passed).",
|
|
286
|
+
},
|
|
287
|
+
...commandTags,
|
|
288
|
+
],
|
|
289
|
+
}
|
|
282
290
|
: {}),
|
|
283
291
|
paths,
|
|
284
292
|
};
|