specli 0.0.18 → 0.0.19

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 (71) hide show
  1. package/dist/cli/compile.js +137 -61
  2. package/dist/cli.d.ts +0 -1
  3. package/dist/cli.js +0 -1
  4. package/package.json +1 -2
  5. package/src/ai/tools.test.ts +0 -83
  6. package/src/ai/tools.ts +0 -211
  7. package/src/cli/auth-requirements.test.ts +0 -27
  8. package/src/cli/auth-requirements.ts +0 -91
  9. package/src/cli/auth-schemes.test.ts +0 -66
  10. package/src/cli/auth-schemes.ts +0 -187
  11. package/src/cli/capabilities.test.ts +0 -94
  12. package/src/cli/capabilities.ts +0 -88
  13. package/src/cli/command-id.test.ts +0 -32
  14. package/src/cli/command-id.ts +0 -16
  15. package/src/cli/command-index.ts +0 -19
  16. package/src/cli/command-model.test.ts +0 -44
  17. package/src/cli/command-model.ts +0 -128
  18. package/src/cli/compile.ts +0 -109
  19. package/src/cli/crypto.ts +0 -9
  20. package/src/cli/derive-name.ts +0 -101
  21. package/src/cli/exec.ts +0 -72
  22. package/src/cli/main.ts +0 -255
  23. package/src/cli/naming.test.ts +0 -86
  24. package/src/cli/naming.ts +0 -224
  25. package/src/cli/operations.test.ts +0 -57
  26. package/src/cli/operations.ts +0 -152
  27. package/src/cli/params.test.ts +0 -70
  28. package/src/cli/params.ts +0 -71
  29. package/src/cli/pluralize.ts +0 -41
  30. package/src/cli/positional.test.ts +0 -65
  31. package/src/cli/positional.ts +0 -75
  32. package/src/cli/request-body.test.ts +0 -35
  33. package/src/cli/request-body.ts +0 -94
  34. package/src/cli/runtime/argv.ts +0 -14
  35. package/src/cli/runtime/auth/resolve.ts +0 -59
  36. package/src/cli/runtime/body-flags.test.ts +0 -261
  37. package/src/cli/runtime/body-flags.ts +0 -176
  38. package/src/cli/runtime/body.ts +0 -24
  39. package/src/cli/runtime/collect.ts +0 -6
  40. package/src/cli/runtime/compat.ts +0 -89
  41. package/src/cli/runtime/context.ts +0 -62
  42. package/src/cli/runtime/execute.ts +0 -147
  43. package/src/cli/runtime/generated.ts +0 -242
  44. package/src/cli/runtime/headers.ts +0 -37
  45. package/src/cli/runtime/index.ts +0 -3
  46. package/src/cli/runtime/profile/secrets.ts +0 -83
  47. package/src/cli/runtime/profile/store.ts +0 -100
  48. package/src/cli/runtime/request.test.ts +0 -375
  49. package/src/cli/runtime/request.ts +0 -390
  50. package/src/cli/runtime/server-url.ts +0 -45
  51. package/src/cli/runtime/template.ts +0 -26
  52. package/src/cli/runtime/validate/ajv.ts +0 -13
  53. package/src/cli/runtime/validate/coerce.test.ts +0 -98
  54. package/src/cli/runtime/validate/coerce.ts +0 -71
  55. package/src/cli/runtime/validate/error.ts +0 -29
  56. package/src/cli/runtime/validate/index.ts +0 -4
  57. package/src/cli/runtime/validate/schema.ts +0 -54
  58. package/src/cli/schema-shape.ts +0 -36
  59. package/src/cli/schema.ts +0 -76
  60. package/src/cli/server.test.ts +0 -55
  61. package/src/cli/server.ts +0 -167
  62. package/src/cli/spec-id.ts +0 -12
  63. package/src/cli/spec-loader.ts +0 -58
  64. package/src/cli/stable-json.ts +0 -35
  65. package/src/cli/strings.ts +0 -21
  66. package/src/cli/types.ts +0 -59
  67. package/src/cli.ts +0 -94
  68. package/src/compiled.ts +0 -24
  69. package/src/macros/env.ts +0 -21
  70. package/src/macros/spec.ts +0 -17
  71. package/src/macros/version.ts +0 -14
@@ -1,109 +0,0 @@
1
- import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
- import { deriveBinaryName } from "./derive-name.js";
4
-
5
- // Resolve the path to compiled.ts relative to this file's location
6
- // At runtime this file is at dist/cli/compile.js, so we go up two levels to package root
7
- // then into src/compiled.ts (which must be included in the published package)
8
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
- const compiledEntrypoint = path.resolve(__dirname, "../../src/compiled.ts");
10
-
11
- export type CompileOptions = {
12
- name?: string;
13
- outfile?: string;
14
- target?: string;
15
- minify?: boolean;
16
- bytecode?: boolean;
17
- dotenv?: boolean; // --no-dotenv sets this to false
18
- bunfig?: boolean; // --no-bunfig sets this to false
19
- define?: string[];
20
- server?: string;
21
- serverVar?: string[];
22
- auth?: string;
23
- };
24
-
25
- function parseKeyValue(input: string): { key: string; value: string } {
26
- const idx = input.indexOf("=");
27
- if (idx === -1)
28
- throw new Error(`Invalid --define '${input}', expected key=value`);
29
- const key = input.slice(0, idx).trim();
30
- const value = input.slice(idx + 1).trim();
31
- if (!key) throw new Error(`Invalid --define '${input}', missing key`);
32
- return { key, value };
33
- }
34
-
35
- export async function compileCommand(
36
- spec: string,
37
- options: CompileOptions,
38
- ): Promise<void> {
39
- // Derive name from spec if not provided
40
- const name = options.name ?? (await deriveBinaryName(spec));
41
- const outfile = options.outfile ?? `./out/${name}`;
42
-
43
- const target = options.target
44
- ? (options.target as Bun.Build.Target)
45
- : (`bun-${process.platform}-${process.arch}` as Bun.Build.Target);
46
-
47
- // Parse --define pairs
48
- const define: Record<string, string> = {};
49
- if (options.define) {
50
- for (const pair of options.define) {
51
- const { key, value } = parseKeyValue(pair);
52
- define[key] = JSON.stringify(value);
53
- }
54
- }
55
-
56
- // Build command args
57
- const buildArgs = [
58
- "build",
59
- "--compile",
60
- `--outfile=${outfile}`,
61
- `--target=${target}`,
62
- ];
63
-
64
- if (options.minify) buildArgs.push("--minify");
65
- if (options.bytecode) buildArgs.push("--bytecode");
66
-
67
- for (const [k, v] of Object.entries(define)) {
68
- buildArgs.push("--define", `${k}=${v}`);
69
- }
70
-
71
- if (options.dotenv === false) buildArgs.push("--no-compile-autoload-dotenv");
72
- if (options.bunfig === false) buildArgs.push("--no-compile-autoload-bunfig");
73
-
74
- buildArgs.push(compiledEntrypoint);
75
-
76
- // Only set env vars that have actual values - avoid empty strings
77
- // because the macros will embed them and they will override defaults.
78
- const buildEnv: Record<string, string> = {
79
- ...process.env,
80
- SPECLI_SPEC: spec,
81
- SPECLI_NAME: name,
82
- };
83
- if (options.server) buildEnv.SPECLI_SERVER = options.server;
84
- if (options.serverVar?.length)
85
- buildEnv.SPECLI_SERVER_VARS = options.serverVar.join(",");
86
- if (options.auth) buildEnv.SPECLI_AUTH = options.auth;
87
-
88
- const proc = Bun.spawn({
89
- cmd: ["bun", ...buildArgs],
90
- stdout: "pipe",
91
- stderr: "pipe",
92
- env: buildEnv,
93
- });
94
-
95
- const output = await new Response(proc.stdout).text();
96
- const error = await new Response(proc.stderr).text();
97
- const code = await proc.exited;
98
-
99
- if (output) process.stdout.write(output);
100
- if (error) process.stderr.write(error);
101
- if (code !== 0) {
102
- process.exitCode = code;
103
- return;
104
- }
105
-
106
- process.stdout.write(`ok: built ${outfile}\n`);
107
- process.stdout.write(`target: ${target}\n`);
108
- process.stdout.write(`name: ${name}\n`);
109
- }
package/src/cli/crypto.ts DELETED
@@ -1,9 +0,0 @@
1
- export async function sha256Hex(text: string): Promise<string> {
2
- const data = new TextEncoder().encode(text);
3
- const hash = await crypto.subtle.digest("SHA-256", data);
4
- const bytes = new Uint8Array(hash);
5
-
6
- let out = "";
7
- for (const b of bytes) out += b.toString(16).padStart(2, "0");
8
- return out;
9
- }
@@ -1,101 +0,0 @@
1
- const RESERVED_NAMES = [
2
- "exec",
3
- "compile",
4
- "profile",
5
- "auth",
6
- "help",
7
- "version",
8
- ];
9
-
10
- /**
11
- * Derives a clean binary name from an OpenAPI spec.
12
- * Priority:
13
- * 1. info.title (kebab-cased, sanitized)
14
- * 2. Host from spec URL (if URL provided)
15
- * 3. Fallback to "specli"
16
- */
17
- export async function deriveBinaryName(spec: string): Promise<string> {
18
- try {
19
- // Load spec to extract title
20
- const text = await loadSpecText(spec);
21
- const doc = parseSpec(text);
22
-
23
- const title = doc?.info?.title;
24
- if (title && typeof title === "string") {
25
- const name = sanitizeName(title);
26
- if (name) return name;
27
- }
28
- } catch {
29
- // Fall through to URL-based derivation
30
- }
31
-
32
- // Try to derive from URL host
33
- if (/^https?:\/\//i.test(spec)) {
34
- try {
35
- const url = new URL(spec);
36
- const hostParts = url.hostname.split(".");
37
- // Use first meaningful segment (skip www, api prefixes)
38
- const meaningful = hostParts.find(
39
- (p) => p !== "www" && p !== "api" && p.length > 2,
40
- );
41
- if (meaningful) {
42
- const name = sanitizeName(meaningful);
43
- if (name) return name;
44
- }
45
- } catch {
46
- // Invalid URL, fall through
47
- }
48
- }
49
-
50
- // Fallback
51
- return "specli";
52
- }
53
-
54
- async function loadSpecText(spec: string): Promise<string> {
55
- if (/^https?:\/\//i.test(spec)) {
56
- const res = await fetch(spec);
57
- if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);
58
- return res.text();
59
- }
60
- return Bun.file(spec).text();
61
- }
62
-
63
- function parseSpec(text: string): { info?: { title?: string } } | null {
64
- try {
65
- const trimmed = text.trimStart();
66
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
67
- return JSON.parse(text);
68
- }
69
- // Use Bun's YAML parser
70
- const { YAML } = globalThis.Bun ?? {};
71
- if (YAML?.parse) {
72
- return YAML.parse(text) as { info?: { title?: string } };
73
- }
74
- // Fallback: only JSON supported
75
- return null;
76
- } catch {
77
- return null;
78
- }
79
- }
80
-
81
- /**
82
- * Convert title to valid binary name:
83
- * - kebab-case
84
- * - lowercase
85
- * - remove invalid chars
86
- * - max 32 chars
87
- * - avoid reserved names
88
- */
89
- function sanitizeName(input: string): string {
90
- let name = input
91
- .toLowerCase()
92
- .replace(/[^a-z0-9]+/g, "-") // Replace non-alphanumeric with dash
93
- .replace(/^-+|-+$/g, "") // Trim leading/trailing dashes
94
- .slice(0, 32); // Limit length
95
-
96
- if (RESERVED_NAMES.includes(name)) {
97
- name = `${name}-cli`;
98
- }
99
-
100
- return name;
101
- }
package/src/cli/exec.ts DELETED
@@ -1,72 +0,0 @@
1
- import { main } from "./main.js";
2
-
3
- export type ExecOptions = {
4
- server?: string;
5
- serverVar?: string[];
6
- auth?: string;
7
- bearerToken?: string;
8
- oauthToken?: string;
9
- username?: string;
10
- password?: string;
11
- apiKey?: string;
12
- profile?: string;
13
- json?: boolean;
14
- };
15
-
16
- export async function execCommand(
17
- spec: string,
18
- options: ExecOptions,
19
- commandArgs: string[],
20
- ): Promise<void> {
21
- // commandArgs includes the spec as first element, filter it out
22
- // to get the remaining args (resource, action, etc.)
23
- const remainingArgs = commandArgs.slice(1);
24
-
25
- // Reconstruct argv for main():
26
- // [node, script, --spec, <spec>, ...options, ...remainingArgs]
27
- const argv = [
28
- process.argv[0] ?? "bun",
29
- process.argv[1] ?? "specli",
30
- "--spec",
31
- spec,
32
- ];
33
-
34
- // Add common options back as flags
35
- if (options.server) {
36
- argv.push("--server", options.server);
37
- }
38
- if (options.serverVar) {
39
- for (const v of options.serverVar) {
40
- argv.push("--server-var", v);
41
- }
42
- }
43
- if (options.auth) {
44
- argv.push("--auth", options.auth);
45
- }
46
- if (options.bearerToken) {
47
- argv.push("--bearer-token", options.bearerToken);
48
- }
49
- if (options.oauthToken) {
50
- argv.push("--oauth-token", options.oauthToken);
51
- }
52
- if (options.username) {
53
- argv.push("--username", options.username);
54
- }
55
- if (options.password) {
56
- argv.push("--password", options.password);
57
- }
58
- if (options.apiKey) {
59
- argv.push("--api-key", options.apiKey);
60
- }
61
- if (options.profile) {
62
- argv.push("--profile", options.profile);
63
- }
64
- if (options.json) {
65
- argv.push("--json");
66
- }
67
-
68
- // Append remaining args (subcommand + its args)
69
- argv.push(...remainingArgs);
70
-
71
- await main(argv);
72
- }
package/src/cli/main.ts DELETED
@@ -1,255 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { Command } from "commander";
5
-
6
- import { getArgValue, hasAnyArg } from "./runtime/argv.js";
7
- import { collectRepeatable } from "./runtime/collect.js";
8
-
9
- /**
10
- * Reads the version from package.json at runtime.
11
- * Used when running in non-compiled mode.
12
- */
13
- function getPackageVersion(): string {
14
- try {
15
- const currentDir = dirname(fileURLToPath(import.meta.url));
16
- const packageJsonPath = join(currentDir, "../../package.json");
17
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
18
- return packageJson.version ?? "0.0.0";
19
- } catch {
20
- return "0.0.0";
21
- }
22
- }
23
-
24
- import { readStdinText } from "./runtime/compat.js";
25
- import { buildRuntimeContext } from "./runtime/context.js";
26
- import { addGeneratedCommands } from "./runtime/generated.js";
27
- import { deleteToken, getToken, setToken } from "./runtime/profile/secrets.js";
28
- import {
29
- readProfiles,
30
- upsertProfile,
31
- writeProfiles,
32
- } from "./runtime/profile/store.js";
33
- import { toMinimalSchemaOutput } from "./schema.js";
34
- import { stableStringify } from "./stable-json.js";
35
-
36
- type MainOptions = {
37
- embeddedSpecText?: string;
38
- cliName?: string;
39
- server?: string;
40
- serverVars?: string[];
41
- auth?: string;
42
- version?: string;
43
- };
44
-
45
- export async function main(argv: string[], options: MainOptions = {}) {
46
- const program = new Command();
47
-
48
- // Get version - use embedded version if available, otherwise read from package.json
49
- const cliVersion = options.version ?? getPackageVersion();
50
-
51
- program
52
- .name(options.cliName ?? "specli")
53
- .description("Generate a CLI from an OpenAPI spec")
54
- .version(cliVersion, "-v, --version", "Output the version number")
55
- .option("--spec <urlOrPath>", "OpenAPI URL or file path")
56
- .option("--server <url>", "Override server/base URL")
57
- .option(
58
- "--server-var <name=value>",
59
- "Server URL template variable (repeatable)",
60
- collectRepeatable,
61
- )
62
- .option("--auth <scheme>", "Select auth scheme by key")
63
- .option("--bearer-token <token>", "Bearer token (Authorization: Bearer)")
64
- .option("--oauth-token <token>", "OAuth token (alias of bearer)")
65
- .option("--username <username>", "Basic auth username")
66
- .option("--password <password>", "Basic auth password")
67
- .option("--api-key <key>", "API key value")
68
- .option("--json", "Machine-readable output")
69
- .showHelpAfterError();
70
-
71
- // If user asks for help and we have no embedded spec and no --spec, show minimal help.
72
- const spec = getArgValue(argv, "--spec");
73
- const wantsHelp = hasAnyArg(argv, ["-h", "--help"]);
74
- if (!spec && !options.embeddedSpecText && wantsHelp) {
75
- program.addHelpText(
76
- "after",
77
- "\nTo see generated commands, run with --spec <url|path>.\n",
78
- );
79
- program.parse(argv);
80
- return;
81
- }
82
-
83
- const ctx = await buildRuntimeContext({
84
- spec,
85
- embeddedSpecText: options.embeddedSpecText,
86
- });
87
-
88
- // Simple auth commands
89
- const defaultProfileName = "default";
90
-
91
- program
92
- .command("login [token]")
93
- .description("Store a bearer token for authentication")
94
- .action(async (tokenArg: string | undefined, _opts, command) => {
95
- const globals = command.optsWithGlobals() as { json?: boolean };
96
-
97
- let token = tokenArg;
98
-
99
- // If no token argument, try to read from stdin (for piping)
100
- if (!token) {
101
- const isTTY = process.stdin.isTTY;
102
- if (isTTY) {
103
- // Interactive mode - prompt user
104
- process.stdout.write("Enter token: ");
105
- const reader = process.stdin;
106
- const chunks: Buffer[] = [];
107
- for await (const chunk of reader) {
108
- chunks.push(chunk);
109
- // Read one line only
110
- if (chunk.includes(10)) break; // newline
111
- }
112
- token = Buffer.concat(chunks).toString().trim();
113
- } else {
114
- // Piped input - use cross-runtime stdin reading
115
- const text = await readStdinText();
116
- token = text.trim();
117
- }
118
- }
119
-
120
- if (!token) {
121
- throw new Error(
122
- "No token provided. Usage: login <token> or echo $TOKEN | login",
123
- );
124
- }
125
-
126
- // Ensure default profile exists
127
- const file = await readProfiles();
128
- if (!file.profiles.find((p) => p.name === defaultProfileName)) {
129
- const updated = upsertProfile(file, { name: defaultProfileName });
130
- await writeProfiles({ ...updated, defaultProfile: defaultProfileName });
131
- } else if (!file.defaultProfile) {
132
- await writeProfiles({ ...file, defaultProfile: defaultProfileName });
133
- }
134
-
135
- await setToken(ctx.loaded.id, defaultProfileName, token);
136
-
137
- if (globals.json) {
138
- process.stdout.write(`${JSON.stringify({ ok: true })}\n`);
139
- return;
140
- }
141
- process.stdout.write("ok: logged in\n");
142
- });
143
-
144
- program
145
- .command("logout")
146
- .description("Clear stored authentication token")
147
- .action(async (_opts, command) => {
148
- const globals = command.optsWithGlobals() as { json?: boolean };
149
-
150
- const deleted = await deleteToken(ctx.loaded.id, defaultProfileName);
151
-
152
- if (globals.json) {
153
- process.stdout.write(`${JSON.stringify({ ok: deleted })}\n`);
154
- return;
155
- }
156
- process.stdout.write(
157
- deleted ? "ok: logged out\n" : "ok: not logged in\n",
158
- );
159
- });
160
-
161
- program
162
- .command("whoami")
163
- .description("Show current authentication status")
164
- .action(async (_opts, command) => {
165
- const globals = command.optsWithGlobals() as { json?: boolean };
166
-
167
- const token = await getToken(ctx.loaded.id, defaultProfileName);
168
- const hasToken = Boolean(token);
169
-
170
- // Mask the token for display (show first 8 and last 4 chars)
171
- let maskedToken: string | null = null;
172
- if (token && token.length > 16) {
173
- maskedToken = `${token.slice(0, 8)}...${token.slice(-4)}`;
174
- } else if (token) {
175
- maskedToken = `${token.slice(0, 4)}...`;
176
- }
177
-
178
- if (globals.json) {
179
- process.stdout.write(
180
- `${JSON.stringify({ authenticated: hasToken, token: maskedToken })}\n`,
181
- );
182
- return;
183
- }
184
-
185
- if (hasToken) {
186
- process.stdout.write(`authenticated: yes\n`);
187
- process.stdout.write(`token: ${maskedToken}\n`);
188
- } else {
189
- process.stdout.write(`authenticated: no\n`);
190
- process.stdout.write(`Run 'login <token>' to authenticate.\n`);
191
- }
192
- });
193
-
194
- program
195
- .command("__schema")
196
- .description("Print indexed operations (machine-readable when --json)")
197
- .option("--pretty", "Pretty-print JSON when used with --json")
198
- .option("--min", "Minimal JSON output (commands + metadata only)")
199
- .action(async (_opts, command) => {
200
- const flags = command.optsWithGlobals() as {
201
- json?: boolean;
202
- pretty?: boolean;
203
- min?: boolean;
204
- };
205
-
206
- if (flags.json) {
207
- const pretty = Boolean(flags.pretty);
208
- const payload = flags.min
209
- ? toMinimalSchemaOutput(ctx.schema)
210
- : ctx.schema;
211
- const text = stableStringify(payload, { space: pretty ? 2 : 0 });
212
- process.stdout.write(`${text}\n`);
213
- return;
214
- }
215
-
216
- process.stdout.write(`${ctx.schema.openapi.title ?? "(untitled)"}\n`);
217
- process.stdout.write(`OpenAPI: ${ctx.schema.openapi.version}\n`);
218
- process.stdout.write(
219
- `Spec: ${ctx.schema.spec.id} (${ctx.schema.spec.source})\n`,
220
- );
221
- process.stdout.write(`Fingerprint: ${ctx.schema.spec.fingerprint}\n`);
222
- process.stdout.write(`Servers: ${ctx.schema.servers.length}\n`);
223
- process.stdout.write(`Auth Schemes: ${ctx.schema.authSchemes.length}\n`);
224
- process.stdout.write(`Operations: ${ctx.schema.operations.length}\n`);
225
-
226
- for (const op of ctx.schema.operations) {
227
- const id = op.operationId ? ` (${op.operationId})` : "";
228
- process.stdout.write(`- ${op.method} ${op.path}${id}\n`);
229
- }
230
-
231
- if (ctx.schema.planned?.length) {
232
- process.stdout.write("\nPlanned commands:\n");
233
- for (const op of ctx.schema.planned) {
234
- const args = op.pathArgs.length
235
- ? ` ${op.pathArgs.map((a) => `<${a}>`).join(" ")}`
236
- : "";
237
- process.stdout.write(`- specli ${op.resource} ${op.action}${args}\n`);
238
- }
239
- }
240
- });
241
-
242
- addGeneratedCommands(program, {
243
- servers: ctx.servers,
244
- authSchemes: ctx.authSchemes,
245
- commands: ctx.commands,
246
- specId: ctx.loaded.id,
247
- embeddedDefaults: {
248
- server: options.server,
249
- serverVars: options.serverVars,
250
- auth: options.auth,
251
- },
252
- });
253
-
254
- await program.parseAsync(argv);
255
- }
@@ -1,86 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { planOperation } from "./naming.js";
3
- import type { NormalizedOperation } from "./types.js";
4
-
5
- describe("planOperation", () => {
6
- test("REST: GET /contacts -> contacts list", () => {
7
- const op: NormalizedOperation = {
8
- key: "GET /contacts",
9
- method: "GET",
10
- path: "/contacts",
11
- operationId: "Contacts.List",
12
- tags: ["Contacts"],
13
- parameters: [],
14
- };
15
-
16
- const planned = planOperation(op);
17
- expect(planned.style).toBe("rest");
18
- expect(planned.resource).toBe("contacts");
19
- expect(planned.action).toBe("list");
20
- expect(planned.pathArgs).toEqual([]);
21
- });
22
-
23
- test("REST: singleton /ping stays ping and prefers operationId action", () => {
24
- const op: NormalizedOperation = {
25
- key: "GET /ping",
26
- method: "GET",
27
- path: "/ping",
28
- operationId: "Ping.Get",
29
- tags: [],
30
- parameters: [],
31
- };
32
-
33
- const planned = planOperation(op);
34
- expect(planned.style).toBe("rest");
35
- expect(planned.resource).toBe("ping");
36
- expect(planned.action).toBe("get");
37
- });
38
-
39
- test("REST: singular path pluralizes to contacts", () => {
40
- const op: NormalizedOperation = {
41
- key: "GET /contact/{id}",
42
- method: "GET",
43
- path: "/contact/{id}",
44
- tags: [],
45
- parameters: [],
46
- };
47
-
48
- const planned = planOperation(op);
49
- expect(planned.style).toBe("rest");
50
- expect(planned.resource).toBe("contacts");
51
- expect(planned.action).toBe("get");
52
- expect(planned.pathArgs).toEqual(["id"]);
53
- });
54
-
55
- test("RPC: POST /Contacts.List -> contacts list", () => {
56
- const op: NormalizedOperation = {
57
- key: "POST /Contacts.List",
58
- method: "POST",
59
- path: "/Contacts.List",
60
- operationId: "Contacts.List",
61
- tags: [],
62
- parameters: [],
63
- };
64
-
65
- const planned = planOperation(op);
66
- expect(planned.style).toBe("rpc");
67
- expect(planned.resource).toBe("contacts");
68
- expect(planned.action).toBe("list");
69
- });
70
-
71
- test("RPC: Retrieve canonicalizes to get", () => {
72
- const op: NormalizedOperation = {
73
- key: "POST /Contacts.Retrieve",
74
- method: "POST",
75
- path: "/Contacts.Retrieve",
76
- operationId: "Contacts.Retrieve",
77
- tags: [],
78
- parameters: [],
79
- };
80
-
81
- const planned = planOperation(op);
82
- expect(planned.style).toBe("rpc");
83
- expect(planned.resource).toBe("contacts");
84
- expect(planned.action).toBe("get");
85
- });
86
- });