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.
Files changed (42) hide show
  1. package/README.md +83 -49
  2. package/cli.ts +4 -10
  3. package/package.json +8 -2
  4. package/src/cli/compile.ts +5 -28
  5. package/src/cli/derive-name.ts +2 -2
  6. package/src/cli/exec.ts +1 -1
  7. package/src/cli/main.ts +12 -27
  8. package/src/cli/runtime/auth/resolve.ts +10 -2
  9. package/src/cli/runtime/body-flags.ts +176 -0
  10. package/src/cli/runtime/execute.ts +17 -22
  11. package/src/cli/runtime/generated.ts +23 -54
  12. package/src/cli/runtime/profile/secrets.ts +1 -1
  13. package/src/cli/runtime/profile/store.ts +1 -1
  14. package/src/cli/runtime/request.ts +48 -80
  15. package/src/cli/stable-json.ts +2 -2
  16. package/src/compiled.ts +13 -15
  17. package/src/macros/env.ts +0 -4
  18. package/CLAUDE.md +0 -111
  19. package/PLAN.md +0 -274
  20. package/biome.jsonc +0 -1
  21. package/bun.lock +0 -98
  22. package/fixtures/openapi-array-items.json +0 -22
  23. package/fixtures/openapi-auth.json +0 -34
  24. package/fixtures/openapi-body.json +0 -41
  25. package/fixtures/openapi-collision.json +0 -21
  26. package/fixtures/openapi-oauth.json +0 -54
  27. package/fixtures/openapi-servers.json +0 -35
  28. package/fixtures/openapi.json +0 -87
  29. package/scripts/smoke-specs.ts +0 -64
  30. package/src/cli/auth-requirements.test.ts +0 -27
  31. package/src/cli/auth-schemes.test.ts +0 -66
  32. package/src/cli/capabilities.test.ts +0 -94
  33. package/src/cli/command-id.test.ts +0 -32
  34. package/src/cli/command-model.test.ts +0 -44
  35. package/src/cli/naming.test.ts +0 -86
  36. package/src/cli/operations.test.ts +0 -57
  37. package/src/cli/params.test.ts +0 -70
  38. package/src/cli/positional.test.ts +0 -65
  39. package/src/cli/request-body.test.ts +0 -35
  40. package/src/cli/runtime/request.test.ts +0 -153
  41. package/src/cli/server.test.ts +0 -35
  42. package/tsconfig.json +0 -29
@@ -1,70 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import { deriveParamSpecs } from "./params.ts";
4
- import type { NormalizedOperation } from "./types.ts";
5
-
6
- describe("deriveParamSpecs", () => {
7
- test("derives basic types + flags", () => {
8
- const op: NormalizedOperation = {
9
- key: "GET /contacts",
10
- method: "GET",
11
- path: "/contacts",
12
- tags: [],
13
- parameters: [
14
- {
15
- in: "query",
16
- name: "limit",
17
- required: false,
18
- schema: {
19
- type: "integer",
20
- format: "int32",
21
- enum: ["1", "2"],
22
- },
23
- },
24
- {
25
- in: "header",
26
- name: "X-Request-Id",
27
- required: false,
28
- schema: { type: "string" },
29
- },
30
- ],
31
- };
32
-
33
- const specs = deriveParamSpecs(op);
34
- expect(specs).toHaveLength(2);
35
-
36
- const limit = specs.find((p) => p.name === "limit");
37
- expect(limit?.kind).toBe("flag");
38
- expect(limit?.flag).toBe("--limit");
39
- expect(limit?.type).toBe("integer");
40
- expect(limit?.format).toBe("int32");
41
- expect(limit?.enum).toEqual(["1", "2"]);
42
-
43
- const reqId = specs.find((p) => p.name === "X-Request-Id");
44
- expect(reqId?.kind).toBe("flag");
45
- expect(reqId?.flag).toBe("--x-request-id");
46
- expect(reqId?.type).toBe("string");
47
- });
48
-
49
- test("derives array item types", () => {
50
- const op: NormalizedOperation = {
51
- key: "GET /things",
52
- method: "GET",
53
- path: "/things",
54
- tags: [],
55
- parameters: [
56
- {
57
- in: "query",
58
- name: "ids",
59
- required: false,
60
- schema: { type: "array", items: { type: "integer" } },
61
- },
62
- ],
63
- };
64
-
65
- const specs = deriveParamSpecs(op);
66
- expect(specs).toHaveLength(1);
67
- expect(specs[0]?.type).toBe("array");
68
- expect(specs[0]?.itemType).toBe("integer");
69
- });
70
- });
@@ -1,65 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import type { ActionShapeForCli } from "./positional.ts";
3
- import { deriveFlags, derivePositionals } from "./positional.ts";
4
-
5
- describe("derivePositionals", () => {
6
- test("returns ordered positionals from pathArgs", () => {
7
- const action: ActionShapeForCli = {
8
- pathArgs: ["id"],
9
- params: [
10
- {
11
- kind: "positional",
12
- in: "path",
13
- name: "id",
14
- flag: "--id",
15
- required: true,
16
- type: "string",
17
- },
18
- ],
19
- };
20
-
21
- const pos = derivePositionals(action);
22
- expect(pos).toEqual([
23
- {
24
- name: "id",
25
- required: true,
26
- type: "string",
27
- format: undefined,
28
- enum: undefined,
29
- description: undefined,
30
- },
31
- ]);
32
- });
33
- });
34
-
35
- describe("deriveFlags", () => {
36
- test("returns only flag params", () => {
37
- const action: ActionShapeForCli = {
38
- pathArgs: [],
39
- params: [
40
- {
41
- kind: "flag",
42
- in: "query",
43
- name: "limit",
44
- flag: "--limit",
45
- required: false,
46
- type: "integer",
47
- },
48
- ],
49
- };
50
-
51
- const flags = deriveFlags(action);
52
- expect(flags.flags).toEqual([
53
- {
54
- in: "query",
55
- name: "limit",
56
- flag: "--limit",
57
- required: false,
58
- description: undefined,
59
- type: "integer",
60
- format: undefined,
61
- enum: undefined,
62
- },
63
- ]);
64
- });
65
- });
@@ -1,35 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import { deriveRequestBodyInfo } from "./request-body.ts";
4
- import type { NormalizedOperation } from "./types.ts";
5
-
6
- describe("deriveRequestBodyInfo", () => {
7
- test("summarizes content types and convenience flags", () => {
8
- const op: NormalizedOperation = {
9
- key: "POST /contacts",
10
- method: "POST",
11
- path: "/contacts",
12
- tags: [],
13
- parameters: [],
14
- requestBody: {
15
- required: true,
16
- contentTypes: ["application/x-www-form-urlencoded", "application/json"],
17
- schemasByContentType: {
18
- "application/json": { type: "object" },
19
- "application/x-www-form-urlencoded": { type: "object" },
20
- },
21
- },
22
- };
23
-
24
- const info = deriveRequestBodyInfo(op);
25
- expect(info?.required).toBe(true);
26
- expect(info?.hasJson).toBe(true);
27
- expect(info?.hasFormUrlEncoded).toBe(true);
28
- expect(info?.hasMultipart).toBe(false);
29
- expect(info?.content.map((c) => c.contentType)).toEqual([
30
- "application/json",
31
- "application/x-www-form-urlencoded",
32
- ]);
33
- expect(info?.preferredSchema).toEqual({ type: "object" });
34
- });
35
- });
@@ -1,153 +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.ts";
6
-
7
- import { buildRequest } from "./request.ts";
8
- import { createAjv, formatAjvErrors } from "./validate/index.ts";
9
-
10
- function makeAction(partial?: Partial<CommandAction>): CommandAction {
11
- return {
12
- id: "test",
13
- key: "POST /contacts",
14
- action: "create",
15
- pathArgs: [],
16
- method: "POST",
17
- path: "/contacts",
18
- tags: [],
19
- style: "rest",
20
- positionals: [],
21
- flags: [],
22
- params: [],
23
- auth: { alternatives: [] },
24
- requestBody: {
25
- required: true,
26
- content: [
27
- {
28
- contentType: "application/json",
29
- required: true,
30
- schemaType: "object",
31
- },
32
- ],
33
- hasJson: true,
34
- hasFormUrlEncoded: false,
35
- hasMultipart: false,
36
- bodyFlags: ["--data", "--file"],
37
- preferredContentType: "application/json",
38
- preferredSchema: undefined,
39
- },
40
- requestBodySchema: {
41
- type: "object",
42
- properties: {
43
- name: { type: "string" },
44
- },
45
- required: ["name"],
46
- },
47
- ...partial,
48
- };
49
- }
50
-
51
- describe("buildRequest (requestBody)", () => {
52
- test("builds body from expanded --body-* flags", async () => {
53
- const prevHome = process.env.HOME;
54
- const home = `${tmpdir()}/opencli-test-${crypto.randomUUID()}`;
55
- process.env.HOME = home;
56
-
57
- try {
58
- const { request, curl } = await buildRequest({
59
- specId: "spec",
60
- action: makeAction(),
61
- positionalValues: [],
62
- flagValues: { __body: { bodyName: "A" } },
63
- globals: {},
64
- servers: [
65
- { url: "https://api.example.com", variables: [], variableNames: [] },
66
- ],
67
- authSchemes: [],
68
- });
69
-
70
- expect(request.headers.get("Content-Type")).toBe("application/json");
71
- expect(await request.clone().text()).toBe('{"name":"A"}');
72
- expect(curl).toContain("--data");
73
- expect(curl).toContain('{"name":"A"}');
74
- } finally {
75
- process.env.HOME = prevHome;
76
- }
77
- });
78
-
79
- test("throws when requestBody is required but missing", async () => {
80
- const prevHome = process.env.HOME;
81
- const home = `${tmpdir()}/opencli-test-${crypto.randomUUID()}`;
82
- process.env.HOME = home;
83
-
84
- try {
85
- await expect(() =>
86
- buildRequest({
87
- specId: "spec",
88
- action: makeAction(),
89
- positionalValues: [],
90
- flagValues: {},
91
- globals: {},
92
- servers: [
93
- {
94
- url: "https://api.example.com",
95
- variables: [],
96
- variableNames: [],
97
- },
98
- ],
99
- authSchemes: [],
100
- }),
101
- ).toThrow(
102
- "Missing request body. Provide --data, --file, or --body-* flags.",
103
- );
104
- } finally {
105
- process.env.HOME = prevHome;
106
- }
107
- });
108
-
109
- test("throws friendly error for missing required expanded field", async () => {
110
- const prevHome = process.env.HOME;
111
- const home = `${tmpdir()}/opencli-test-${crypto.randomUUID()}`;
112
- process.env.HOME = home;
113
-
114
- try {
115
- await expect(() =>
116
- buildRequest({
117
- specId: "spec",
118
- action: makeAction(),
119
- positionalValues: [],
120
- flagValues: { __body: { bodyFoo: "bar" } },
121
- globals: {},
122
- servers: [
123
- {
124
- url: "https://api.example.com",
125
- variables: [],
126
- variableNames: [],
127
- },
128
- ],
129
- authSchemes: [],
130
- }),
131
- ).toThrow(
132
- "Missing required body field 'name'. Provide --body-name or use --data/--file.",
133
- );
134
- } finally {
135
- process.env.HOME = prevHome;
136
- }
137
- });
138
- });
139
-
140
- describe("formatAjvErrors", () => {
141
- test("pretty prints required errors", () => {
142
- const ajv = createAjv();
143
- const validate = ajv.compile({
144
- type: "object",
145
- properties: { name: { type: "string" } },
146
- required: ["name"],
147
- });
148
-
149
- validate({});
150
- const msg = formatAjvErrors(validate.errors);
151
- expect(msg).toBe("/ missing required property 'name'");
152
- });
153
- });
@@ -1,35 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import { listServers } from "./server.ts";
4
- import type { OpenApiDoc } from "./types.ts";
5
-
6
- describe("listServers", () => {
7
- test("extracts server variables from template", () => {
8
- const doc: OpenApiDoc = {
9
- openapi: "3.0.3",
10
- servers: [
11
- {
12
- url: "https://{region}.api.example.com/{basePath}",
13
- variables: {
14
- region: {
15
- default: "us",
16
- enum: ["us", "eu"],
17
- },
18
- basePath: {
19
- default: "v1",
20
- },
21
- },
22
- } as const,
23
- ],
24
- };
25
-
26
- const servers = listServers(doc);
27
- expect(servers).toHaveLength(1);
28
- expect(servers[0]?.variableNames).toEqual(["region", "basePath"]);
29
- expect(servers[0]?.variables.map((v) => v.name)).toEqual([
30
- "region",
31
- "basePath",
32
- ]);
33
- expect(servers[0]?.variables[0]?.enum).toEqual(["us", "eu"]);
34
- });
35
- });
package/tsconfig.json DELETED
@@ -1,29 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- // Environment setup & latest features
4
- "lib": ["ESNext"],
5
- "target": "ESNext",
6
- "module": "Preserve",
7
- "moduleDetection": "force",
8
- "jsx": "react-jsx",
9
- "allowJs": true,
10
-
11
- // Bundler mode
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "noEmit": true,
16
-
17
- // Best practices
18
- "strict": true,
19
- "skipLibCheck": true,
20
- "noFallthroughCasesInSwitch": true,
21
- "noUncheckedIndexedAccess": true,
22
- "noImplicitOverride": true,
23
-
24
- // Some stricter flags (disabled by default)
25
- "noUnusedLocals": false,
26
- "noUnusedParameters": false,
27
- "noPropertyAccessFromIndexSignature": false
28
- }
29
- }