openapi-sync 5.0.6 → 6.0.0
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/README.md +206 -2
- package/bin/cli.js +396 -27
- package/bin/mcp.js +36 -0
- package/dist/chunk-3VKPQNDD.mjs +22 -0
- package/dist/chunk-L52BXDAC.mjs +24 -0
- package/dist/chunk-TTLQP4UN.mjs +856 -0
- package/dist/chunk-VLACEFT4.mjs +1 -0
- package/dist/index.d.mts +351 -32
- package/dist/index.d.ts +351 -32
- package/dist/index.js +410 -365
- package/dist/index.mjs +1 -854
- package/dist/interactive-init-XBZJKJCH.mjs +1 -0
- package/dist/mcp/server.d.mts +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +913 -0
- package/dist/mcp/server.mjs +4 -0
- package/dist/validate-3G3NZCPG.mjs +11 -0
- package/llms.txt +334 -0
- package/openapi.sync.schema.json +396 -0
- package/package.json +38 -17
- package/dist/chunk-PUWCZVB7.mjs +0 -1
- package/dist/interactive-init-OITE22SZ.mjs +0 -12
package/bin/cli.js
CHANGED
|
@@ -1,39 +1,366 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
// openapi-sync CLI
|
|
5
|
+
//
|
|
6
|
+
// AGENT USAGE GUIDE
|
|
7
|
+
// -----------------
|
|
8
|
+
// All commands support `--json` for machine-readable stdout and `--silent` to
|
|
9
|
+
// suppress progress logs. Exit codes: 0 = success, 1 = config error,
|
|
10
|
+
// 2 = network/spec error, 3 = generation error.
|
|
11
|
+
//
|
|
12
|
+
// Non-interactive init (no prompts, safe for agents):
|
|
13
|
+
// npx openapi-sync init --no-interactive \
|
|
14
|
+
// --api-name petstore \
|
|
15
|
+
// --api-url https://petstore3.swagger.io/api/v3/openapi.json \
|
|
16
|
+
// --output-folder ./src/api \
|
|
17
|
+
// --client-type react-query \
|
|
18
|
+
// --validation-library zod \
|
|
19
|
+
// --config-format typescript
|
|
20
|
+
//
|
|
21
|
+
// Validate config and specs without writing files:
|
|
22
|
+
// npx openapi-sync validate --json
|
|
23
|
+
//
|
|
24
|
+
// List all endpoints:
|
|
25
|
+
// npx openapi-sync list-endpoints --json
|
|
26
|
+
//
|
|
27
|
+
// Sync (generate types + endpoints + validation schemas):
|
|
28
|
+
// npx openapi-sync --json
|
|
29
|
+
//
|
|
30
|
+
// Generate a typed API client:
|
|
31
|
+
// npx openapi-sync generate-client --type react-query --json
|
|
32
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
3
34
|
const OpenApisync = require("../dist/index");
|
|
4
35
|
|
|
5
36
|
const yargs = require("yargs/yargs");
|
|
6
37
|
const { hideBin } = require("yargs/helpers");
|
|
7
38
|
|
|
8
|
-
//
|
|
9
|
-
|
|
39
|
+
// ── Output helpers ───────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Write a JSON result to stdout and exit with the appropriate code.
|
|
43
|
+
* Used when --json flag is present.
|
|
44
|
+
*/
|
|
45
|
+
function jsonExit(data, exitCode = 0) {
|
|
46
|
+
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
47
|
+
process.exit(exitCode);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Determine the exit code from a SyncResult or ValidationResult.
|
|
52
|
+
*/
|
|
53
|
+
function exitCodeFromResult(result) {
|
|
54
|
+
if (result.success === false || result.valid === false) return 1;
|
|
55
|
+
if (result.errors && result.errors.length > 0) return 1;
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── CLI definition ────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
yargs(hideBin(process.argv))
|
|
62
|
+
// ── Global flags ──────────────────────────────────────────────────────────
|
|
63
|
+
.option("json", {
|
|
64
|
+
type: "boolean",
|
|
65
|
+
global: true,
|
|
66
|
+
description:
|
|
67
|
+
"Emit a single JSON object to stdout instead of human-readable logs. " +
|
|
68
|
+
"Use this when calling from scripts or AI agents.",
|
|
69
|
+
default: false,
|
|
70
|
+
})
|
|
71
|
+
.option("silent", {
|
|
72
|
+
type: "boolean",
|
|
73
|
+
global: true,
|
|
74
|
+
description: "Suppress all console output (implied by --json).",
|
|
75
|
+
default: false,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// ── `init` command ─────────────────────────────────────────────────────────
|
|
10
79
|
.command(
|
|
11
80
|
"init",
|
|
12
|
-
"
|
|
81
|
+
"Create an openapi.sync config file (interactive or non-interactive)",
|
|
82
|
+
(yargs) => {
|
|
83
|
+
return yargs
|
|
84
|
+
.option("no-interactive", {
|
|
85
|
+
alias: "y",
|
|
86
|
+
type: "boolean",
|
|
87
|
+
description:
|
|
88
|
+
"Skip all prompts and create config from flags + defaults. " +
|
|
89
|
+
"Use this from scripts or AI agents.",
|
|
90
|
+
default: false,
|
|
91
|
+
})
|
|
92
|
+
// All wizard fields available as flags so agents can pass everything:
|
|
93
|
+
.option("api-name", {
|
|
94
|
+
type: "string",
|
|
95
|
+
description: "Name for this API (e.g. petstore)",
|
|
96
|
+
})
|
|
97
|
+
.option("api-url", {
|
|
98
|
+
type: "string",
|
|
99
|
+
description: "URL to the OpenAPI spec (JSON or YAML)",
|
|
100
|
+
})
|
|
101
|
+
.option("api-file", {
|
|
102
|
+
type: "string",
|
|
103
|
+
description: "Path to a local OpenAPI spec file",
|
|
104
|
+
})
|
|
105
|
+
.option("output-folder", {
|
|
106
|
+
type: "string",
|
|
107
|
+
description: "Output folder for generated files",
|
|
108
|
+
default: "./src/api",
|
|
109
|
+
})
|
|
110
|
+
.option("config-format", {
|
|
111
|
+
type: "string",
|
|
112
|
+
choices: ["typescript", "json", "javascript"],
|
|
113
|
+
description: "Config file format",
|
|
114
|
+
default: "typescript",
|
|
115
|
+
})
|
|
116
|
+
.option("client-type", {
|
|
117
|
+
type: "string",
|
|
118
|
+
choices: ["react-query", "swr", "fetch", "axios", "rtk-query"],
|
|
119
|
+
description: "API client type to generate",
|
|
120
|
+
})
|
|
121
|
+
.option("validation-library", {
|
|
122
|
+
type: "string",
|
|
123
|
+
choices: ["zod", "yup", "joi"],
|
|
124
|
+
description: "Runtime validation library",
|
|
125
|
+
})
|
|
126
|
+
.option("folder-split", {
|
|
127
|
+
type: "boolean",
|
|
128
|
+
description: "Organize generated code into folders by OpenAPI tags",
|
|
129
|
+
default: false,
|
|
130
|
+
})
|
|
131
|
+
.option("types-prefix", {
|
|
132
|
+
type: "string",
|
|
133
|
+
description: "Prefix for generated TypeScript interface names",
|
|
134
|
+
default: "I",
|
|
135
|
+
})
|
|
136
|
+
.option("use-operation-id", {
|
|
137
|
+
type: "boolean",
|
|
138
|
+
description: "Use operationId from spec for type/function names",
|
|
139
|
+
default: true,
|
|
140
|
+
})
|
|
141
|
+
.option("exclude-tags", {
|
|
142
|
+
type: "string",
|
|
143
|
+
description: "Comma-separated tags to exclude (e.g. deprecated,internal)",
|
|
144
|
+
})
|
|
145
|
+
.option("show-curl", {
|
|
146
|
+
type: "boolean",
|
|
147
|
+
description: "Include cURL examples in generated docs",
|
|
148
|
+
default: true,
|
|
149
|
+
})
|
|
150
|
+
.option("refetch-interval", {
|
|
151
|
+
type: "number",
|
|
152
|
+
description: "Auto-refetch interval in ms (0 to disable)",
|
|
153
|
+
default: 0,
|
|
154
|
+
})
|
|
155
|
+
.option("run-sync", {
|
|
156
|
+
type: "boolean",
|
|
157
|
+
description: "Run initial sync after creating config",
|
|
158
|
+
default: false,
|
|
159
|
+
})
|
|
160
|
+
.example(
|
|
161
|
+
"$0 init",
|
|
162
|
+
"Interactive wizard (human use)"
|
|
163
|
+
)
|
|
164
|
+
.example(
|
|
165
|
+
"$0 init --no-interactive --api-name petstore --api-url https://petstore3.swagger.io/api/v3/openapi.json",
|
|
166
|
+
"Non-interactive setup (agent use)"
|
|
167
|
+
)
|
|
168
|
+
.example(
|
|
169
|
+
"$0 init --no-interactive --api-name myapi --api-url https://api.example.com/openapi.json --client-type react-query --validation-library zod --json",
|
|
170
|
+
"Full non-interactive setup with JSON output"
|
|
171
|
+
);
|
|
172
|
+
},
|
|
173
|
+
async (argv) => {
|
|
174
|
+
const noInteractive = argv["no-interactive"] || argv.y;
|
|
175
|
+
|
|
176
|
+
if (noInteractive) {
|
|
177
|
+
// ── Non-interactive path (agent-safe) ─────────────────────────────
|
|
178
|
+
const silent = argv.silent || argv.json;
|
|
179
|
+
|
|
180
|
+
const apiName = argv["api-name"] || "myapi";
|
|
181
|
+
const apiSource = argv["api-url"] || argv["api-file"];
|
|
182
|
+
|
|
183
|
+
if (!apiSource) {
|
|
184
|
+
const err = {
|
|
185
|
+
success: false,
|
|
186
|
+
error: {
|
|
187
|
+
code: "CONFIG_INVALID",
|
|
188
|
+
message:
|
|
189
|
+
"You must provide --api-url or --api-file when using --no-interactive.",
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
if (argv.json) return jsonExit(err, 1);
|
|
193
|
+
console.error(`❌ ${err.error.message}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const { nonInteractiveInit } = require("../dist/Openapi-sync/interactive-init");
|
|
198
|
+
|
|
199
|
+
const result = await nonInteractiveInit({
|
|
200
|
+
apiName,
|
|
201
|
+
apiSource,
|
|
202
|
+
outputFolder: argv["output-folder"],
|
|
203
|
+
configFormat: argv["config-format"],
|
|
204
|
+
clientType: argv["client-type"],
|
|
205
|
+
validationLibrary: argv["validation-library"],
|
|
206
|
+
folderSplit: argv["folder-split"],
|
|
207
|
+
typesPrefix: argv["types-prefix"],
|
|
208
|
+
useOperationId: argv["use-operation-id"],
|
|
209
|
+
excludeTags: argv["exclude-tags"]
|
|
210
|
+
? argv["exclude-tags"].split(",").map((t) => t.trim())
|
|
211
|
+
: [],
|
|
212
|
+
showCurl: argv["show-curl"],
|
|
213
|
+
refetchInterval: argv["refetch-interval"] || undefined,
|
|
214
|
+
runSync: argv["run-sync"],
|
|
215
|
+
silent,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (argv.json) {
|
|
219
|
+
return jsonExit(result, result.success ? 0 : 1);
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
// ── Interactive wizard path (human use) ───────────────────────────
|
|
223
|
+
await OpenApisync.InteractiveInit();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
// ── `validate` command ─────────────────────────────────────────────────────
|
|
229
|
+
.command(
|
|
230
|
+
"validate",
|
|
231
|
+
"Validate the config file and all API specs without writing any files. " +
|
|
232
|
+
"Safe to run repeatedly — no side effects.",
|
|
13
233
|
() => {},
|
|
14
|
-
async () => {
|
|
15
|
-
|
|
234
|
+
async (argv) => {
|
|
235
|
+
const silent = argv.silent || argv.json;
|
|
236
|
+
|
|
237
|
+
const result = await OpenApisync.ValidateConfig({ silent });
|
|
238
|
+
|
|
239
|
+
if (argv.json) {
|
|
240
|
+
return jsonExit(result, result.valid ? 0 : 1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
process.exit(result.valid ? 0 : 1);
|
|
16
244
|
}
|
|
17
245
|
)
|
|
246
|
+
|
|
247
|
+
// ── `list-endpoints` command ───────────────────────────────────────────────
|
|
18
248
|
.command(
|
|
19
|
-
|
|
20
|
-
"
|
|
249
|
+
"list-endpoints",
|
|
250
|
+
"List all endpoints discovered from your OpenAPI specs. No files are written.",
|
|
21
251
|
(yargs) => {
|
|
22
|
-
return yargs
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
252
|
+
return yargs
|
|
253
|
+
.option("api", {
|
|
254
|
+
alias: "a",
|
|
255
|
+
type: "string",
|
|
256
|
+
description: "Limit to a specific API from your config",
|
|
257
|
+
})
|
|
258
|
+
.option("tags", {
|
|
259
|
+
type: "string",
|
|
260
|
+
description: "Comma-separated tags to filter by",
|
|
261
|
+
})
|
|
262
|
+
.example("$0 list-endpoints --json", "List all endpoints as JSON")
|
|
263
|
+
.example(
|
|
264
|
+
"$0 list-endpoints --api petstore --tags pet --json",
|
|
265
|
+
"List pet-tagged endpoints for petstore"
|
|
266
|
+
);
|
|
267
|
+
},
|
|
268
|
+
async (argv) => {
|
|
269
|
+
const silent = argv.silent || argv.json;
|
|
270
|
+
const tags = argv.tags
|
|
271
|
+
? argv.tags.split(",").map((t) => t.trim())
|
|
272
|
+
: undefined;
|
|
273
|
+
|
|
274
|
+
const result = await OpenApisync.ListEndpoints({
|
|
275
|
+
apiName: argv.api,
|
|
276
|
+
tags,
|
|
277
|
+
silent,
|
|
26
278
|
});
|
|
279
|
+
|
|
280
|
+
if (argv.json) {
|
|
281
|
+
return jsonExit(result, 0);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Human-readable output
|
|
285
|
+
for (const [api, endpoints] of Object.entries(result)) {
|
|
286
|
+
console.log(`\n📋 ${api} (${endpoints.length} endpoints)`);
|
|
287
|
+
for (const ep of endpoints) {
|
|
288
|
+
const tags = ep.tags?.length ? ` [${ep.tags.join(", ")}]` : "";
|
|
289
|
+
console.log(` ${ep.method.padEnd(7)} ${ep.path}${tags}`);
|
|
290
|
+
if (ep.summary) console.log(` ${ep.summary}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
// ── `sync` / default command ───────────────────────────────────────────────
|
|
297
|
+
.command(
|
|
298
|
+
["$0", "sync"],
|
|
299
|
+
"Sync OpenAPI specifications and generate types, endpoints, and validation schemas",
|
|
300
|
+
(yargs) => {
|
|
301
|
+
return yargs
|
|
302
|
+
.option("refreshinterval", {
|
|
303
|
+
alias: "ri",
|
|
304
|
+
type: "number",
|
|
305
|
+
description: "Interval in ms to auto-refetch specifications",
|
|
306
|
+
})
|
|
307
|
+
.option("dry-run", {
|
|
308
|
+
type: "boolean",
|
|
309
|
+
description:
|
|
310
|
+
"Show what files would be written without actually writing them",
|
|
311
|
+
default: false,
|
|
312
|
+
})
|
|
313
|
+
.example("$0", "Sync all APIs")
|
|
314
|
+
.example("$0 --json", "Sync and emit JSON result")
|
|
315
|
+
.example("$0 --dry-run --json", "Preview what would be written");
|
|
27
316
|
},
|
|
28
|
-
(argv) => {
|
|
29
|
-
|
|
317
|
+
async (argv) => {
|
|
318
|
+
if (argv["dry-run"]) {
|
|
319
|
+
// Dry-run: validate first to count endpoints, then report what would be written
|
|
320
|
+
const silent = argv.silent || argv.json;
|
|
321
|
+
const validation = await OpenApisync.ValidateConfig({ silent });
|
|
322
|
+
|
|
323
|
+
const dryRunResult = {
|
|
324
|
+
dryRun: true,
|
|
325
|
+
valid: validation.valid,
|
|
326
|
+
apis: validation.apis,
|
|
327
|
+
message: validation.valid
|
|
328
|
+
? "Dry run complete. Run without --dry-run to write files."
|
|
329
|
+
: "Validation failed. Fix errors before syncing.",
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
if (argv.json) {
|
|
333
|
+
return jsonExit(dryRunResult, validation.valid ? 0 : 1);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (validation.valid) {
|
|
337
|
+
console.log("\n✅ Dry run: config and specs are valid.");
|
|
338
|
+
console.log(" Run without --dry-run to write files.\n");
|
|
339
|
+
} else {
|
|
340
|
+
console.error("\n❌ Dry run: validation failed — see errors above.\n");
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const silent = argv.silent || argv.json;
|
|
347
|
+
const result = await OpenApisync.Init({
|
|
30
348
|
refetchInterval: argv.refreshinterval,
|
|
349
|
+
silent,
|
|
31
350
|
});
|
|
351
|
+
|
|
352
|
+
if (argv.json) {
|
|
353
|
+
return jsonExit(result, exitCodeFromResult(result));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!result.success) process.exit(1);
|
|
32
357
|
}
|
|
33
358
|
)
|
|
359
|
+
|
|
360
|
+
// ── `generate-client` command ──────────────────────────────────────────────
|
|
34
361
|
.command(
|
|
35
362
|
"generate-client",
|
|
36
|
-
"Generate API client from OpenAPI
|
|
363
|
+
"Generate a type-safe API client from your OpenAPI specs",
|
|
37
364
|
(yargs) => {
|
|
38
365
|
return yargs
|
|
39
366
|
.option("type", {
|
|
@@ -68,17 +395,22 @@ const argv = yargs(hideBin(process.argv))
|
|
|
68
395
|
type: "string",
|
|
69
396
|
description: "Base URL for API requests",
|
|
70
397
|
})
|
|
398
|
+
.option("dry-run", {
|
|
399
|
+
type: "boolean",
|
|
400
|
+
description: "Show what files would be written without writing them",
|
|
401
|
+
default: false,
|
|
402
|
+
})
|
|
71
403
|
.example(
|
|
72
|
-
"$0 generate-client --type fetch",
|
|
73
|
-
"Generate fetch client
|
|
404
|
+
"$0 generate-client --type fetch --json",
|
|
405
|
+
"Generate fetch client and emit JSON result"
|
|
74
406
|
)
|
|
75
407
|
.example(
|
|
76
408
|
"$0 generate-client --type react-query --api petstore",
|
|
77
|
-
"Generate React Query hooks for petstore
|
|
409
|
+
"Generate React Query hooks for petstore"
|
|
78
410
|
)
|
|
79
411
|
.example(
|
|
80
412
|
"$0 generate-client --type axios --tags pets,users",
|
|
81
|
-
"Generate axios client
|
|
413
|
+
"Generate axios client filtered by tags"
|
|
82
414
|
)
|
|
83
415
|
.example(
|
|
84
416
|
"$0 generate-client --type swr --endpoints getPetById,createPet",
|
|
@@ -86,26 +418,63 @@ const argv = yargs(hideBin(process.argv))
|
|
|
86
418
|
);
|
|
87
419
|
},
|
|
88
420
|
async (argv) => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
421
|
+
if (argv["dry-run"]) {
|
|
422
|
+
// Validate and list what would be generated
|
|
423
|
+
const silent = argv.silent || argv.json;
|
|
424
|
+
const endpoints = await OpenApisync.ListEndpoints({
|
|
425
|
+
apiName: argv.api,
|
|
426
|
+
tags: argv.tags,
|
|
427
|
+
silent,
|
|
428
|
+
});
|
|
429
|
+
const dryRunResult = {
|
|
430
|
+
dryRun: true,
|
|
431
|
+
type: argv.type,
|
|
432
|
+
apis: endpoints,
|
|
433
|
+
message: `Would generate a ${argv.type} client. Run without --dry-run to write files.`,
|
|
434
|
+
};
|
|
435
|
+
if (argv.json) return jsonExit(dryRunResult, 0);
|
|
436
|
+
console.log(`\n✅ Dry run: would generate ${argv.type} client.`);
|
|
437
|
+
for (const [api, eps] of Object.entries(endpoints)) {
|
|
438
|
+
console.log(` ${api}: ${eps.length} endpoints`);
|
|
439
|
+
}
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
92
442
|
|
|
93
|
-
|
|
443
|
+
// First sync to get fresh types
|
|
444
|
+
const silent = argv.silent || argv.json;
|
|
445
|
+
const silent2 = silent;
|
|
446
|
+
await OpenApisync.Init({ silent: silent2 });
|
|
94
447
|
|
|
95
|
-
|
|
96
|
-
await OpenApisync.GenerateClient({
|
|
448
|
+
const result = await OpenApisync.GenerateClient({
|
|
97
449
|
type: argv.type,
|
|
98
450
|
apiName: argv.api,
|
|
99
451
|
tags: argv.tags,
|
|
100
452
|
endpoints: argv.endpoints,
|
|
101
453
|
outputDir: argv.output,
|
|
102
454
|
baseURL: argv["base-url"],
|
|
455
|
+
silent,
|
|
103
456
|
});
|
|
457
|
+
|
|
458
|
+
if (argv.json) {
|
|
459
|
+
return jsonExit(result, exitCodeFromResult(result));
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (!result.success) process.exit(1);
|
|
104
463
|
}
|
|
105
464
|
)
|
|
106
|
-
|
|
107
|
-
.example("$0", "
|
|
108
|
-
.example(
|
|
465
|
+
|
|
466
|
+
.example("$0 init", "Interactive setup wizard (human)")
|
|
467
|
+
.example(
|
|
468
|
+
"$0 init --no-interactive --api-name petstore --api-url https://petstore3.swagger.io/api/v3/openapi.json",
|
|
469
|
+
"Non-interactive init (agent)"
|
|
470
|
+
)
|
|
471
|
+
.example("$0 validate --json", "Validate config + specs (no file writes)")
|
|
472
|
+
.example("$0 list-endpoints --json", "List all endpoints as JSON")
|
|
473
|
+
.example("$0 --json", "Sync and get JSON result")
|
|
474
|
+
.example(
|
|
475
|
+
"$0 generate-client --type react-query --json",
|
|
476
|
+
"Generate React Query hooks with JSON output"
|
|
477
|
+
)
|
|
109
478
|
.help()
|
|
110
479
|
.alias("help", "h")
|
|
111
480
|
.version()
|
package/bin/mcp.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* openapi-sync MCP Server entry point.
|
|
5
|
+
*
|
|
6
|
+
* Starts the Model Context Protocol server over stdio so that AI agents
|
|
7
|
+
* (Claude Desktop, Cursor, Copilot, etc.) can call openapi-sync operations
|
|
8
|
+
* as structured tool invocations.
|
|
9
|
+
*
|
|
10
|
+
* Usage — add to Claude Desktop config
|
|
11
|
+
* (~/ Library/Application Support/Claude/claude_desktop_config.json):
|
|
12
|
+
*
|
|
13
|
+
* {
|
|
14
|
+
* "mcpServers": {
|
|
15
|
+
* "openapi-sync": {
|
|
16
|
+
* "command": "npx",
|
|
17
|
+
* "args": ["-y", "openapi-sync-mcp"],
|
|
18
|
+
* "cwd": "/path/to/your/project"
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* Usage — add to Cursor config (.cursor/mcp.json in project root):
|
|
24
|
+
*
|
|
25
|
+
* {
|
|
26
|
+
* "mcpServers": {
|
|
27
|
+
* "openapi-sync": {
|
|
28
|
+
* "command": "npx",
|
|
29
|
+
* "args": ["-y", "openapi-sync-mcp"],
|
|
30
|
+
* "cwd": "${workspaceFolder}"
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
require("../dist/mcp/server.js");
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {d,f}from'./chunk-VLACEFT4.mjs';import F from'prompts';import s from'fs';import a from'path';function k(){return f(this,null,function*(){try{let e=yield F([{type:"select",name:"configFormat",message:"What configuration format would you like to use?",choices:[{title:"TypeScript (openapi.sync.ts)",value:"typescript"},{title:"JSON (openapi.sync.json)",value:"json"},{title:"JavaScript (openapi.sync.js)",value:"javascript"}],initial:0},{type:"select",name:"apiSource",message:"Where is your OpenAPI specification?",choices:[{title:"URL (e.g., https://api.example.com/openapi.json)",value:"url"},{title:"Local file",value:"file"}],initial:0},{type:t=>t==="url"?"text":null,name:"apiUrl",message:"Enter the OpenAPI specification URL:",validate:t=>t.startsWith("http://")||t.startsWith("https://")?!0:"Please enter a valid URL starting with http:// or https://"},{type:(t,c)=>c.apiSource==="file"?"text":null,name:"apiFile",message:"Enter the path to your OpenAPI file:",validate:t=>t.trim()!==""?!0:"Please enter a valid file path"},{type:"text",name:"apiName",message:"What would you like to name this API?",initial:"myapi",validate:t=>/^[a-zA-Z0-9_-]+$/.test(t)?!0:"API name must contain only letters, numbers, hyphens, and underscores"},{type:"text",name:"outputFolder",message:"Where should generated files be saved?",initial:"./src/api",validate:t=>{if(!t||t.trim()==="")return "Output folder cannot be empty";let c=a.normalize(t);if(c==="/"||c==="C:\\"||c==="\\")return "Cannot use filesystem root directory. Please use a project subfolder like './src/api'";if(a.isAbsolute(t)){let m=process.cwd();if(!t.startsWith(m))return "\u26A0\uFE0F Warning: Using absolute path outside project directory. Recommended: use relative path like './src/api'"}return !0}},{type:"confirm",name:"enableFolderSplit",message:"Organize generated code into folders by OpenAPI tags?",initial:!1},{type:"confirm",name:"generateClient",message:"Generate API client code?",initial:!0},{type:t=>t?"select":null,name:"clientType",message:"Which client type would you like?",choices:[{title:"React Query (Recommended for React)",value:"react-query"},{title:"RTK Query (Redux Toolkit)",value:"rtk-query"},{title:"SWR",value:"swr"},{title:"Fetch API",value:"fetch"},{title:"Axios",value:"axios"}],initial:0},{type:"confirm",name:"enableValidation",message:"Enable runtime validation schemas?",initial:!0},{type:t=>t?"select":null,name:"validationLibrary",message:"Which validation library?",choices:[{title:"Zod (Recommended)",value:"zod"},{title:"Yup",value:"yup"},{title:"Joi",value:"joi"}],initial:0},{type:"confirm",name:"enableCustomCode",message:"Enable custom code preservation?",initial:!0},{type:"confirm",name:"typesUseOperationId",message:"Use operationId from OpenAPI spec for type names?",initial:!0},{type:"text",name:"typesPrefix",message:"Prefix for TypeScript interface names (leave empty for none):",initial:"I"},{type:"confirm",name:"excludeEndpointsByTags",message:"Exclude endpoints by tags (e.g., deprecated, internal)?",initial:!1},{type:t=>t?"text":null,name:"excludeTags",message:"Enter tags to exclude (comma-separated):",initial:"deprecated,internal"},{type:"confirm",name:"showCurlInDocs",message:"Include cURL examples in generated documentation?",initial:!0},{type:"number",name:"refetchInterval",message:"Refetch interval in milliseconds (0 to disable auto-refresh):",initial:5e3,min:0},{type:"confirm",name:"runSync",message:"Run initial sync after setup?",initial:!0}],{onCancel:()=>{throw r||process.exit(0),new Error("Setup cancelled")}}),n={refetchInterval:e.refetchInterval||void 0,folder:e.outputFolder,api:{[e.apiName]:e.apiUrl||e.apiFile}};e.enableFolderSplit&&(n.folderSplit={byTags:!0}),n.types={name:{prefix:e.typesPrefix||"",useOperationId:e.typesUseOperationId}},n.endpoints={name:{useOperationId:e.typesUseOperationId},doc:{showCurl:e.showCurlInDocs}},e.excludeEndpointsByTags&&e.excludeTags&&(n.endpoints.exclude={tags:e.excludeTags.split(",").map(t=>t.trim())}),e.generateClient&&e.clientType&&(n.clientGeneration={enabled:!0,type:e.clientType,outputDir:a.join(e.outputFolder,e.apiName,"client")},e.clientType==="react-query"?n.clientGeneration.reactQuery={version:5,mutations:!0}:e.clientType==="swr"&&(n.clientGeneration.swr={mutations:!0})),e.enableValidation&&e.validationLibrary&&(n.validations={library:e.validationLibrary}),e.enableCustomCode&&(n.customCode={enabled:!0,position:"bottom"});let g,i;e.configFormat==="json"?(i="openapi.sync.json",g=JSON.stringify(n,null,2)):e.configFormat==="typescript"?(i="openapi.sync.ts",g=`import { IConfig } from "openapi-sync";
|
|
2
|
+
|
|
3
|
+
const config: IConfig = ${JSON.stringify(n,null,2)};
|
|
4
|
+
|
|
5
|
+
export default config;
|
|
6
|
+
`):(i="openapi.sync.js",g=`module.exports = ${JSON.stringify(n,null,2)};
|
|
7
|
+
`);let d=a.join(process.cwd(),i);if(s.existsSync(d)&&!(yield F({type:"confirm",name:"value",message:`${i} already exists. Overwrite?`,initial:!1})).value)throw r||process.exit(0),new Error("Configuration file not created");s.writeFileSync(d,g,"utf-8"),r||`${i}`;let l=a.isAbsolute(e.outputFolder)?e.outputFolder:a.join(process.cwd(),e.outputFolder);if(!s.existsSync(l))try{s.mkdirSync(l,{recursive:!0}),r||`${e.outputFolder}`;}catch(t){r||`${t.message}`;}let y=a.join(process.cwd(),".gitignore");if(s.existsSync(y)&&(s.readFileSync(y,"utf-8").includes(e.outputFolder)||s.appendFileSync(y,`
|
|
8
|
+
# OpenAPI Sync generated files
|
|
9
|
+
${e.outputFolder}
|
|
10
|
+
`)),r||(`${i}`,e.generateClient&&e.clientType&&(e.clientType==="axios"||e.clientType==="react-query"||e.clientType==="swr"||e.clientType),e.enableValidation&&e.validationLibrary&&(e.generateClient,`${e.validationLibrary}`)),e.runSync&&!r)try{let{Init:t,GenerateClient:c}=yield import('./index.mjs');yield t({refetchInterval:e.refetchInterval}),e.generateClient&&e.clientType&&(yield c({type:e.clientType,apiName:e.apiName}));}catch(t){console.error(`
|
|
11
|
+
\u274C Error during sync:`,t.message),t.stack&&process.env.DEBUG&&console.error(t.stack);}else r||(e.generateClient||e.enableValidation,e.generateClient&&e.clientType&&(e.generateClient||e.enableValidation,`${e.clientType}`));r||(`${e.outputFolder}${e.apiName}`,e.generateClient&&`${e.outputFolder}${e.apiName}`,e.generateClient&&e.clientType&&`${e.clientType}`);}catch(e){throw r||(console.error(`
|
|
12
|
+
\u274C Error during setup:`,e.message),process.exit(1)),e}})}function G(e){return f(this,null,function*(){let{apiName:n,apiSource:g,outputFolder:i="./src/api",configFormat:d="typescript",clientType:l,validationLibrary:y,folderSplit:t=false,typesPrefix:c="I",useOperationId:m=true,excludeTags:I=[],showCurl:T=true,refetchInterval:v,runSync:O=false,silent:w=false}=e,h=w?{log:()=>{},warn:()=>{},error:()=>{}}:{log:console.log,warn:console.warn,error:console.error},u=[],o={folder:i,api:{[n]:g}};v&&v>0&&(o.refetchInterval=v),t&&(o.folderSplit={byTags:true}),o.types={name:{prefix:c,useOperationId:m}},o.endpoints={name:{useOperationId:m},doc:{showCurl:T}},I.length>0&&(o.endpoints.exclude={tags:I}),l&&(o.clientGeneration={enabled:true,type:l,outputDir:a.join(i,n,"client")},l==="react-query"?o.clientGeneration.reactQuery={version:5,mutations:true}:l==="swr"&&(o.clientGeneration.swr={mutations:true})),y&&(o.validations={library:y}),o.customCode={enabled:true,position:"bottom"};let b,p;d==="json"?(p="openapi.sync.json",b=JSON.stringify(o,null,2)):d==="typescript"?(p="openapi.sync.ts",b=`import { IConfig } from "openapi-sync";
|
|
13
|
+
|
|
14
|
+
const config: IConfig = ${JSON.stringify(o,null,2)};
|
|
15
|
+
|
|
16
|
+
export default config;
|
|
17
|
+
`):(p="openapi.sync.js",b=`module.exports = ${JSON.stringify(o,null,2)};
|
|
18
|
+
`);let P=a.join(process.cwd(),p);try{s.writeFileSync(P,b,"utf-8"),h.log(`
|
|
19
|
+
\u2705 Config written: ${p}`);}catch(f){return u.push(`Failed to write config: ${f.message}`),{success:false,configFile:p,message:"Config write failed.",errors:u}}let C=a.isAbsolute(i)?i:a.join(process.cwd(),i);if(!s.existsSync(C))try{s.mkdirSync(C,{recursive:!0});}catch(f){}if(O){h.log(`
|
|
20
|
+
\u{1F504} Running initial sync...`);try{let{Init:f,GenerateClient:j}=yield import('./index.mjs');yield f({silent:w}),l&&(yield j({type:l,apiName:n,silent:w})),h.log("\u2705 Sync complete.");}catch(f){u.push(`Sync failed: ${f.message}`);}}let S=u.length===0?`Config created: ${p}. Run \`npx openapi-sync\` to generate types.`:`Config created with errors (${u.length}).`;return h.log(`
|
|
21
|
+
${S}
|
|
22
|
+
`),{success:u.length===0,configFile:p,message:S,errors:u}})}var r,N=d(()=>{r=process.env.NODE_ENV==="test"||process.env.JEST_WORKER_ID!==void 0;});export{k as a,G as b,N as c};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {d}from'./chunk-VLACEFT4.mjs';import*as f from'js-yaml';var N,y,G=d(()=>{N=/^[A-Za-z_$][A-Za-z0-9_$]*$/,y=/[A-Za-z0-9_$]/;});function J(t,a){return a.split(".").reduce((r,n)=>r&&r[n]!==void 0?r[n]:void 0,t)}var w,M,z,E,D,k,I,O,T,C,U,B=d(()=>{G();w=t=>["object"].includes(typeof t)&&!(t instanceof Blob),M=t=>{try{return f.load(t),!0}catch(a){let o=a;if(o instanceof f.YAMLException)return false;throw o}},z=t=>{if(M(t)){let a=f.load(t),o=JSON.stringify(a,null,2);return JSON.parse(o)}},E=t=>t.substring(0,1).toUpperCase()+t.substring(1),D=(t,a)=>{let o=t.split("/"),r=`${E(a)}`,n=[];return o.forEach(s=>{if(s[0]==="{"&&s[s.length-1]==="}"){let e=s.replace(/{/,"").replace(/}/,"");n.push(e),s=`$${e}`;}else if(s[0]==="<"&&s[s.length-1]===">"){let e=s.replace(/</,"").replace(/>/,"");n.push(e),s=`$${e}`;}else if(s[0]===":"){let e=s.replace(/:/,"");n.push(e),s=`$${e}`;}let c="";s.split("").forEach(e=>{let i=e;y.test(e)||(i="/"),c+=i;}),c.split("/").forEach(e=>{r+=E(e);});}),{name:r,variables:n,pathParts:o}},k=(t,a=1)=>{let o="{",r=Object.keys(t);for(let n=0;n<r.length;n++){let s=r[n],c=t[s];if(o+=`
|
|
2
|
+
`+" ".repeat(a)+s+": ",Array.isArray(c)){o+="[";for(let e=0;e<c.length;e++){let i=c[e];typeof i=="object"&&i!==null?o+=k(i,a+1):o+=typeof i=="string"?`"${i}"`:i,e<c.length-1&&(o+=", ");}o+="]";}else typeof c=="object"&&c!==null?o+=""+k(c,a+1):o+=c.split(`
|
|
3
|
+
`).filter(e=>e.trim()!=="").join(`
|
|
4
|
+
${" ".repeat(a)}`);n<r.length-1&&(o+=", ");}return o+=`
|
|
5
|
+
${" ".repeat(a-1)}}`,o},I=(t,a=1)=>{let o=t.replace(/\/\*\*?[\s\S]*?\*\//g,r=>r.replace(/^\/\*\*?\s*/,"").replace(/\s*\*\/$/,"").split(`
|
|
6
|
+
`).map(s=>s.replace(/^\s*\*\s?/,"").trim()).filter(s=>s.length>0).map(s=>`// ${s}`).join(`
|
|
7
|
+
`));return `
|
|
8
|
+
\`\`\`typescript
|
|
9
|
+
${" ".repeat(a)} ${o.split(`
|
|
10
|
+
`).filter(r=>r.trim()!=="").join(`
|
|
11
|
+
${" ".repeat(a)} `)}
|
|
12
|
+
\`\`\``};O=(t,a="CUSTOM CODE")=>{let r=["//","#"].map(e=>({start:`${e} \u{1F512} ${a} START`,end:`${e} \u{1F512} ${a} END`})),n={beforeGenerated:"",afterGenerated:""},s=0,c=[];for(;s<t.length;){let e=null,i=-1;for(let d of r){let b=t.indexOf(d.start,s);b!==-1&&(i===-1||b<i)&&(i=b,e=d);}if(i===-1||!e)break;let p=t.indexOf(e.end,i);if(p===-1)break;let l=p+e.end.length,$=t.substring(l,l+100),u=$.match(/^\s*\n\s*((?:\/\/|#)\s*=+)/);if(u){let d=$.indexOf(`
|
|
13
|
+
`,u.index+1);d!==-1?l+=d+1:l+=u[0].length;}let g=i,S=t.substring(Math.max(0,i-200),i),A=/(^|\n)\s*(?:\/\/|#)\s*=+/g,h,m=-1;for(;(h=A.exec(S))!==null;)m=h.index+h[1].length;m!==-1&&(g=Math.max(0,i-200)+m);let j=t.substring(g,l);c.push({start:g,end:l,content:j}),s=l;}return c.length>0&&(t.substring(0,c[0].start).split(`
|
|
14
|
+
`).filter(p=>{let l=p.trim();return l.length>0&&!l.startsWith("//")&&!l.startsWith("#")}).join("").length===0?(n.beforeGenerated=c[0].content,c.length>1&&(n.afterGenerated=c[1].content)):(n.afterGenerated=c[0].content,c.length>1&&!n.beforeGenerated&&(n.beforeGenerated=c[1].content))),n},T=(t,a="CUSTOM CODE",o=true,r="//")=>{let n=o?`${r} ${t==="top"?"Add your custom code below this line":"Add your custom code above this line"}
|
|
15
|
+
${r} This section will be preserved during regeneration
|
|
16
|
+
`:"";return `${r} ${"=".repeat(60)}
|
|
17
|
+
${r} \u{1F512} ${a} START
|
|
18
|
+
${n}${r} ${"=".repeat(60)}
|
|
19
|
+
|
|
20
|
+
${r} \u{1F512} ${a} END
|
|
21
|
+
${r} ${"=".repeat(60)}`},C=(t,a,o)=>{if(!t)return t;let r=new RegExp(`^\\s*//\\s*(?:={3,}|\u{1F512}\\s+${a}\\s+(?:START|END)|Add your custom code (?:below|above) this line|This section will be preserved during regeneration)\\s*$`);return t.split(`
|
|
22
|
+
`).map(n=>r.test(n)?n.replace(/^\s*\/\//,o):n).join(`
|
|
23
|
+
`)},U=(t,a,o={})=>{let{position:r="bottom",markerText:n="CUSTOM CODE",includeInstructions:s=true,commentPrefix:c="//"}=o,e={beforeGenerated:"",afterGenerated:""};a&&(e=O(a,n),e.beforeGenerated=C(e.beforeGenerated,n,c),e.afterGenerated=C(e.afterGenerated,n,c)),!e.beforeGenerated&&!e.afterGenerated&&((r==="top"||r==="both")&&(e.beforeGenerated=T("top",n,s,c)),(r==="bottom"||r==="both")&&(e.afterGenerated=T("bottom",n,s,c)));let i=[];return e.beforeGenerated&&(i.push(e.beforeGenerated),i.push("")),i.push(t),e.afterGenerated&&(i.push(""),i.push(e.afterGenerated)),i.join(`
|
|
24
|
+
`)};});export{N as a,y as b,G as c,w as d,M as e,z as f,E as g,D as h,k as i,I as j,J as k,O as l,T as m,U as n,B as o};
|