@schmock/cli 1.1.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/dist/cli.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { Server } from "node:http";
2
+ export interface CliOptions {
3
+ spec: string;
4
+ port?: number;
5
+ hostname?: string;
6
+ seed?: string;
7
+ cors?: boolean;
8
+ debug?: boolean;
9
+ }
10
+ export interface CliServer {
11
+ server: Server;
12
+ port: number;
13
+ hostname: string;
14
+ close(): void;
15
+ }
16
+ export declare function createCliServer(options: CliOptions): Promise<CliServer>;
17
+ export declare function parseCliArgs(args: string[]): CliOptions & {
18
+ help: boolean;
19
+ };
20
+ export declare function run(args: string[]): Promise<void>;
21
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAOxC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,IAAI,IAAI,CAAC;CACf;AAiCD,wBAAsB,eAAe,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA2G7E;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CA2B3E;AAeD,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCvD"}
package/dist/cli.js ADDED
@@ -0,0 +1,183 @@
1
+ /// <reference path="../../../types/schmock.d.ts" />
2
+ import { readFileSync } from "node:fs";
3
+ import { createServer } from "node:http";
4
+ import { parseArgs } from "node:util";
5
+ import { schmock, toHttpMethod } from "@schmock/core";
6
+ import { openapi } from "@schmock/openapi";
7
+ const CORS_HEADERS = {
8
+ "access-control-allow-origin": "*",
9
+ "access-control-allow-methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
10
+ "access-control-allow-headers": "Content-Type, Authorization",
11
+ };
12
+ function isSeedSource(value) {
13
+ return (Array.isArray(value) ||
14
+ typeof value === "string" ||
15
+ (typeof value === "object" && value !== null && "count" in value));
16
+ }
17
+ function loadSeedFile(seedPath) {
18
+ const raw = readFileSync(seedPath, "utf-8");
19
+ const parsed = JSON.parse(raw);
20
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
21
+ throw new Error(`Seed file must contain a JSON object, got: ${typeof parsed}`);
22
+ }
23
+ const result = {};
24
+ for (const [key, value] of Object.entries(parsed)) {
25
+ if (isSeedSource(value)) {
26
+ result[key] = value;
27
+ }
28
+ }
29
+ return result;
30
+ }
31
+ export async function createCliServer(options) {
32
+ const mock = schmock({ debug: options.debug });
33
+ const openapiOptions = {
34
+ spec: options.spec,
35
+ };
36
+ if (options.seed) {
37
+ openapiOptions.seed = loadSeedFile(options.seed);
38
+ }
39
+ const plugin = await openapi(openapiOptions);
40
+ mock.pipe(plugin);
41
+ const hostname = options.hostname ?? "127.0.0.1";
42
+ const port = options.port ?? 3000;
43
+ const cors = options.cors ?? false;
44
+ const httpServer = createServer((req, res) => {
45
+ // Handle CORS preflight
46
+ if (cors && req.method === "OPTIONS") {
47
+ res.writeHead(204, CORS_HEADERS);
48
+ res.end();
49
+ return;
50
+ }
51
+ const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
52
+ const method = toHttpMethod(req.method ?? "GET");
53
+ const path = url.pathname;
54
+ const headers = {};
55
+ for (const [key, value] of Object.entries(req.headers)) {
56
+ if (typeof value === "string") {
57
+ headers[key] = value;
58
+ }
59
+ }
60
+ const query = {};
61
+ url.searchParams.forEach((value, key) => {
62
+ query[key] = value;
63
+ });
64
+ const chunks = [];
65
+ req.on("data", (chunk) => chunks.push(chunk));
66
+ req.on("end", () => {
67
+ const raw = Buffer.concat(chunks).toString();
68
+ let body;
69
+ const contentType = headers["content-type"] ?? "";
70
+ if (raw && contentType.includes("json")) {
71
+ try {
72
+ body = JSON.parse(raw);
73
+ }
74
+ catch {
75
+ body = raw;
76
+ }
77
+ }
78
+ else if (raw) {
79
+ body = raw;
80
+ }
81
+ void mock
82
+ .handle(method, path, { headers, body, query })
83
+ .then((schmockResponse) => {
84
+ const responseHeaders = {
85
+ ...schmockResponse.headers,
86
+ };
87
+ if (cors) {
88
+ Object.assign(responseHeaders, CORS_HEADERS);
89
+ }
90
+ if (!responseHeaders["content-type"] &&
91
+ schmockResponse.body !== undefined &&
92
+ typeof schmockResponse.body !== "string") {
93
+ responseHeaders["content-type"] = "application/json";
94
+ }
95
+ const responseBody = schmockResponse.body === undefined
96
+ ? undefined
97
+ : typeof schmockResponse.body === "string"
98
+ ? schmockResponse.body
99
+ : JSON.stringify(schmockResponse.body);
100
+ res.writeHead(schmockResponse.status, responseHeaders);
101
+ res.end(responseBody);
102
+ });
103
+ });
104
+ });
105
+ return new Promise((resolve, reject) => {
106
+ httpServer.on("error", reject);
107
+ httpServer.listen(port, hostname, () => {
108
+ const addr = httpServer.address();
109
+ const actualPort = addr !== null && typeof addr === "object" ? addr.port : port;
110
+ resolve({
111
+ server: httpServer,
112
+ port: actualPort,
113
+ hostname,
114
+ close() {
115
+ httpServer.close();
116
+ },
117
+ });
118
+ });
119
+ });
120
+ }
121
+ export function parseCliArgs(args) {
122
+ const { values, positionals } = parseArgs({
123
+ args,
124
+ options: {
125
+ spec: { type: "string" },
126
+ port: { type: "string" },
127
+ hostname: { type: "string" },
128
+ seed: { type: "string" },
129
+ cors: { type: "boolean", default: false },
130
+ debug: { type: "boolean", default: false },
131
+ help: { type: "boolean", short: "h", default: false },
132
+ },
133
+ strict: true,
134
+ allowPositionals: true,
135
+ });
136
+ const spec = values.spec ?? positionals[0] ?? "";
137
+ return {
138
+ spec,
139
+ port: values.port ? Number(values.port) : undefined,
140
+ hostname: values.hostname,
141
+ seed: values.seed,
142
+ cors: values.cors,
143
+ debug: values.debug,
144
+ help: values.help ?? false,
145
+ };
146
+ }
147
+ const USAGE = `Usage: schmock <spec> [options]
148
+ schmock --spec <path> [options]
149
+
150
+ Options:
151
+ --spec <path> OpenAPI/Swagger spec file (or pass as first argument)
152
+ --port <number> Port to listen on (default: 3000)
153
+ --hostname <host> Hostname to bind to (default: 127.0.0.1)
154
+ --seed <path> JSON file with seed data
155
+ --cors Enable CORS for all responses
156
+ --debug Enable debug logging
157
+ -h, --help Show this help message
158
+ `;
159
+ export async function run(args) {
160
+ const options = parseCliArgs(args);
161
+ if (options.help) {
162
+ process.stderr.write(USAGE);
163
+ return;
164
+ }
165
+ if (!options.spec) {
166
+ process.stderr.write("Error: --spec is required\n\n");
167
+ process.stderr.write(USAGE);
168
+ process.exitCode = 1;
169
+ return;
170
+ }
171
+ const cliServer = await createCliServer(options);
172
+ process.stderr.write(`Schmock server running on http://${cliServer.hostname}:${cliServer.port}\n`);
173
+ process.stderr.write(`Spec: ${options.spec}\n`);
174
+ if (options.cors) {
175
+ process.stderr.write("CORS: enabled\n");
176
+ }
177
+ const shutdown = () => {
178
+ process.stderr.write("\nShutting down...\n");
179
+ cliServer.close();
180
+ };
181
+ process.on("SIGINT", shutdown);
182
+ process.on("SIGTERM", shutdown);
183
+ }
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bun
2
+ export type { CliOptions, CliServer } from "./cli.js";
3
+ export { createCliServer, parseCliArgs, run } from "./cli.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAIA,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC"}