specli 0.0.18 → 0.0.20

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.
Files changed (75) hide show
  1. package/README.md +3 -1
  2. package/dist/ai/tools.d.ts +6 -5
  3. package/dist/ai/tools.js +7 -20
  4. package/dist/ai/tools.test.js +7 -14
  5. package/dist/cli/compile.js +137 -61
  6. package/dist/cli.d.ts +0 -1
  7. package/dist/cli.js +0 -1
  8. package/package.json +1 -2
  9. package/src/ai/tools.test.ts +0 -83
  10. package/src/ai/tools.ts +0 -211
  11. package/src/cli/auth-requirements.test.ts +0 -27
  12. package/src/cli/auth-requirements.ts +0 -91
  13. package/src/cli/auth-schemes.test.ts +0 -66
  14. package/src/cli/auth-schemes.ts +0 -187
  15. package/src/cli/capabilities.test.ts +0 -94
  16. package/src/cli/capabilities.ts +0 -88
  17. package/src/cli/command-id.test.ts +0 -32
  18. package/src/cli/command-id.ts +0 -16
  19. package/src/cli/command-index.ts +0 -19
  20. package/src/cli/command-model.test.ts +0 -44
  21. package/src/cli/command-model.ts +0 -128
  22. package/src/cli/compile.ts +0 -109
  23. package/src/cli/crypto.ts +0 -9
  24. package/src/cli/derive-name.ts +0 -101
  25. package/src/cli/exec.ts +0 -72
  26. package/src/cli/main.ts +0 -255
  27. package/src/cli/naming.test.ts +0 -86
  28. package/src/cli/naming.ts +0 -224
  29. package/src/cli/operations.test.ts +0 -57
  30. package/src/cli/operations.ts +0 -152
  31. package/src/cli/params.test.ts +0 -70
  32. package/src/cli/params.ts +0 -71
  33. package/src/cli/pluralize.ts +0 -41
  34. package/src/cli/positional.test.ts +0 -65
  35. package/src/cli/positional.ts +0 -75
  36. package/src/cli/request-body.test.ts +0 -35
  37. package/src/cli/request-body.ts +0 -94
  38. package/src/cli/runtime/argv.ts +0 -14
  39. package/src/cli/runtime/auth/resolve.ts +0 -59
  40. package/src/cli/runtime/body-flags.test.ts +0 -261
  41. package/src/cli/runtime/body-flags.ts +0 -176
  42. package/src/cli/runtime/body.ts +0 -24
  43. package/src/cli/runtime/collect.ts +0 -6
  44. package/src/cli/runtime/compat.ts +0 -89
  45. package/src/cli/runtime/context.ts +0 -62
  46. package/src/cli/runtime/execute.ts +0 -147
  47. package/src/cli/runtime/generated.ts +0 -242
  48. package/src/cli/runtime/headers.ts +0 -37
  49. package/src/cli/runtime/index.ts +0 -3
  50. package/src/cli/runtime/profile/secrets.ts +0 -83
  51. package/src/cli/runtime/profile/store.ts +0 -100
  52. package/src/cli/runtime/request.test.ts +0 -375
  53. package/src/cli/runtime/request.ts +0 -390
  54. package/src/cli/runtime/server-url.ts +0 -45
  55. package/src/cli/runtime/template.ts +0 -26
  56. package/src/cli/runtime/validate/ajv.ts +0 -13
  57. package/src/cli/runtime/validate/coerce.test.ts +0 -98
  58. package/src/cli/runtime/validate/coerce.ts +0 -71
  59. package/src/cli/runtime/validate/error.ts +0 -29
  60. package/src/cli/runtime/validate/index.ts +0 -4
  61. package/src/cli/runtime/validate/schema.ts +0 -54
  62. package/src/cli/schema-shape.ts +0 -36
  63. package/src/cli/schema.ts +0 -76
  64. package/src/cli/server.test.ts +0 -55
  65. package/src/cli/server.ts +0 -167
  66. package/src/cli/spec-id.ts +0 -12
  67. package/src/cli/spec-loader.ts +0 -58
  68. package/src/cli/stable-json.ts +0 -35
  69. package/src/cli/strings.ts +0 -21
  70. package/src/cli/types.ts +0 -59
  71. package/src/cli.ts +0 -94
  72. package/src/compiled.ts +0 -24
  73. package/src/macros/env.ts +0 -21
  74. package/src/macros/spec.ts +0 -17
  75. package/src/macros/version.ts +0 -14
@@ -1,375 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import { tmpdir } from "node:os";
4
-
5
- import type { CommandAction } from "../command-model.js";
6
-
7
- import { generateBodyFlags } from "./body-flags.js";
8
- import { buildRequest } from "./request.js";
9
- import { createAjv, formatAjvErrors } from "./validate/index.js";
10
-
11
- function makeAction(partial?: Partial<CommandAction>): CommandAction {
12
- return {
13
- id: "test",
14
- key: "POST /contacts",
15
- action: "create",
16
- pathArgs: [],
17
- method: "POST",
18
- path: "/contacts",
19
- tags: [],
20
- style: "rest",
21
- positionals: [],
22
- flags: [],
23
- params: [],
24
- auth: { alternatives: [] },
25
- requestBody: {
26
- required: true,
27
- content: [
28
- {
29
- contentType: "application/json",
30
- required: true,
31
- schemaType: "object",
32
- },
33
- ],
34
- hasJson: true,
35
- hasFormUrlEncoded: false,
36
- hasMultipart: false,
37
- bodyFlags: ["--data", "--file"],
38
- preferredContentType: "application/json",
39
- preferredSchema: undefined,
40
- },
41
- requestBodySchema: {
42
- type: "object",
43
- properties: {
44
- name: { type: "string" },
45
- },
46
- required: ["name"],
47
- },
48
- ...partial,
49
- };
50
- }
51
-
52
- describe("buildRequest (requestBody)", () => {
53
- test("builds body from expanded body flags", async () => {
54
- const prevHome = process.env.HOME;
55
- const home = `${tmpdir()}/specli-test-${crypto.randomUUID()}`;
56
- process.env.HOME = home;
57
-
58
- try {
59
- const action = makeAction();
60
- const bodyFlagDefs = generateBodyFlags(
61
- action.requestBodySchema,
62
- new Set(),
63
- );
64
-
65
- const { request, curl } = await buildRequest({
66
- specId: "spec",
67
- action,
68
- positionalValues: [],
69
- flagValues: { name: "A" }, // --name A
70
- globals: {},
71
- servers: [
72
- { url: "https://api.example.com", variables: [], variableNames: [] },
73
- ],
74
- authSchemes: [],
75
- bodyFlagDefs,
76
- });
77
-
78
- expect(request.headers.get("Content-Type")).toBe("application/json");
79
- expect(await request.clone().text()).toBe('{"name":"A"}');
80
- expect(curl).toContain("--data");
81
- expect(curl).toContain('{"name":"A"}');
82
- } finally {
83
- process.env.HOME = prevHome;
84
- }
85
- });
86
-
87
- test("throws when requestBody is required but missing", async () => {
88
- const prevHome = process.env.HOME;
89
- const home = `${tmpdir()}/specli-test-${crypto.randomUUID()}`;
90
- process.env.HOME = home;
91
-
92
- try {
93
- const action = makeAction();
94
- const bodyFlagDefs = generateBodyFlags(
95
- action.requestBodySchema,
96
- new Set(),
97
- );
98
-
99
- await expect(() =>
100
- buildRequest({
101
- specId: "spec",
102
- action,
103
- positionalValues: [],
104
- flagValues: {},
105
- globals: {},
106
- servers: [
107
- {
108
- url: "https://api.example.com",
109
- variables: [],
110
- variableNames: [],
111
- },
112
- ],
113
- authSchemes: [],
114
- bodyFlagDefs,
115
- }),
116
- ).toThrow("Required: --name");
117
- } finally {
118
- process.env.HOME = prevHome;
119
- }
120
- });
121
-
122
- test("throws friendly error for missing required expanded field", async () => {
123
- const prevHome = process.env.HOME;
124
- const home = `${tmpdir()}/specli-test-${crypto.randomUUID()}`;
125
- process.env.HOME = home;
126
-
127
- try {
128
- // Schema with two fields, one required
129
- const action = makeAction({
130
- requestBodySchema: {
131
- type: "object",
132
- properties: {
133
- name: { type: "string" },
134
- email: { type: "string" },
135
- },
136
- required: ["name"],
137
- },
138
- });
139
- const bodyFlagDefs = generateBodyFlags(
140
- action.requestBodySchema,
141
- new Set(),
142
- );
143
-
144
- // Provide email but not name (the required one)
145
- await expect(() =>
146
- buildRequest({
147
- specId: "spec",
148
- action,
149
- positionalValues: [],
150
- flagValues: { email: "test@example.com" }, // --email (but missing --name)
151
- globals: {},
152
- servers: [
153
- {
154
- url: "https://api.example.com",
155
- variables: [],
156
- variableNames: [],
157
- },
158
- ],
159
- authSchemes: [],
160
- bodyFlagDefs,
161
- }),
162
- ).toThrow("Missing required fields: --name");
163
- } finally {
164
- process.env.HOME = prevHome;
165
- }
166
- });
167
-
168
- test("builds nested object from dot notation flags", async () => {
169
- const prevHome = process.env.HOME;
170
- const home = `${tmpdir()}/specli-test-${crypto.randomUUID()}`;
171
- process.env.HOME = home;
172
-
173
- try {
174
- const action = makeAction({
175
- requestBodySchema: {
176
- type: "object",
177
- properties: {
178
- name: { type: "string" },
179
- address: {
180
- type: "object",
181
- properties: {
182
- street: { type: "string" },
183
- city: { type: "string" },
184
- },
185
- },
186
- },
187
- required: ["name"],
188
- },
189
- });
190
- const bodyFlagDefs = generateBodyFlags(
191
- action.requestBodySchema,
192
- new Set(),
193
- );
194
-
195
- // Dot notation: --address.street and --address.city should create nested object
196
- const { request } = await buildRequest({
197
- specId: "spec",
198
- action,
199
- positionalValues: [],
200
- flagValues: {
201
- name: "Ada",
202
- "address.street": "123 Main St", // Commander keeps dots in keys
203
- "address.city": "NYC",
204
- },
205
- globals: {},
206
- servers: [
207
- { url: "https://api.example.com", variables: [], variableNames: [] },
208
- ],
209
- authSchemes: [],
210
- bodyFlagDefs,
211
- });
212
-
213
- const body = JSON.parse(await request.clone().text());
214
- expect(body).toEqual({
215
- name: "Ada",
216
- address: {
217
- street: "123 Main St",
218
- city: "NYC",
219
- },
220
- });
221
- } finally {
222
- process.env.HOME = prevHome;
223
- }
224
- });
225
- });
226
-
227
- describe("buildRequest (query parameters)", () => {
228
- test("builds query string from flag values", async () => {
229
- const prevHome = process.env.HOME;
230
- const home = `${tmpdir()}/specli-test-${crypto.randomUUID()}`;
231
- process.env.HOME = home;
232
-
233
- try {
234
- const action: CommandAction = {
235
- id: "test",
236
- key: "GET /contacts",
237
- action: "list",
238
- pathArgs: [],
239
- method: "GET",
240
- path: "/contacts",
241
- tags: [],
242
- style: "rest",
243
- positionals: [],
244
- flags: [
245
- {
246
- flag: "--limit",
247
- name: "limit",
248
- in: "query",
249
- type: "integer",
250
- required: false,
251
- },
252
- {
253
- flag: "--name",
254
- name: "name",
255
- in: "query",
256
- type: "string",
257
- required: false,
258
- },
259
- ],
260
- params: [
261
- {
262
- kind: "flag",
263
- flag: "--limit",
264
- name: "limit",
265
- in: "query",
266
- required: false,
267
- type: "integer",
268
- },
269
- {
270
- kind: "flag",
271
- flag: "--name",
272
- name: "name",
273
- in: "query",
274
- required: false,
275
- type: "string",
276
- },
277
- ],
278
- auth: { alternatives: [] },
279
- };
280
-
281
- const { request } = await buildRequest({
282
- specId: "spec",
283
- action,
284
- positionalValues: [],
285
- flagValues: { limit: 10, name: "andrew" },
286
- globals: {},
287
- servers: [
288
- { url: "https://api.example.com", variables: [], variableNames: [] },
289
- ],
290
- authSchemes: [],
291
- });
292
-
293
- expect(request.method).toBe("GET");
294
- expect(request.url).toBe(
295
- "https://api.example.com/contacts?limit=10&name=andrew",
296
- );
297
- } finally {
298
- process.env.HOME = prevHome;
299
- }
300
- });
301
-
302
- test("handles array query parameters", async () => {
303
- const prevHome = process.env.HOME;
304
- const home = `${tmpdir()}/specli-test-${crypto.randomUUID()}`;
305
- process.env.HOME = home;
306
-
307
- try {
308
- const action: CommandAction = {
309
- id: "test",
310
- key: "GET /contacts",
311
- action: "list",
312
- pathArgs: [],
313
- method: "GET",
314
- path: "/contacts",
315
- tags: [],
316
- style: "rest",
317
- positionals: [],
318
- flags: [
319
- {
320
- flag: "--tag",
321
- name: "tag",
322
- in: "query",
323
- type: "array",
324
- itemType: "string",
325
- required: false,
326
- },
327
- ],
328
- params: [
329
- {
330
- kind: "flag",
331
- flag: "--tag",
332
- name: "tag",
333
- in: "query",
334
- required: false,
335
- type: "array",
336
- },
337
- ],
338
- auth: { alternatives: [] },
339
- };
340
-
341
- const { request } = await buildRequest({
342
- specId: "spec",
343
- action,
344
- positionalValues: [],
345
- flagValues: { tag: ["vip", "active"] },
346
- globals: {},
347
- servers: [
348
- { url: "https://api.example.com", variables: [], variableNames: [] },
349
- ],
350
- authSchemes: [],
351
- });
352
-
353
- expect(request.url).toBe(
354
- "https://api.example.com/contacts?tag=vip&tag=active",
355
- );
356
- } finally {
357
- process.env.HOME = prevHome;
358
- }
359
- });
360
- });
361
-
362
- describe("formatAjvErrors", () => {
363
- test("pretty prints required errors", () => {
364
- const ajv = createAjv();
365
- const validate = ajv.compile({
366
- type: "object",
367
- properties: { name: { type: "string" } },
368
- required: ["name"],
369
- });
370
-
371
- validate({});
372
- const msg = formatAjvErrors(validate.errors);
373
- expect(msg).toBe("/ missing required property 'name'");
374
- });
375
- });