@schmock/cli 2.4.1 → 2.5.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.
@@ -0,0 +1,38 @@
1
+ import type * as Schmock from "@schmock/core";
2
+ import type { NodeRequestLike, NodeResponseLike } from "@schmock/core";
3
+ /**
4
+ * The parts of Node's request and response the bridge uses. Structural, as
5
+ * core declares them, so a request typed by any `@types/node` copy fits.
6
+ */
7
+ type NodeRequest = NodeRequestLike;
8
+ type NodeResponse = NodeResponseLike;
9
+ export interface CliRequestContext {
10
+ /** The mock this request is served by, resolved when it arrives. */
11
+ readonly mock: Schmock.CallableMockInstance;
12
+ /** Whether `/schmock-admin/*` is the admin API rather than mock routes. */
13
+ readonly admin: boolean;
14
+ readonly cors: boolean;
15
+ readonly adminToken: string | undefined;
16
+ }
17
+ /**
18
+ * Serve one CLI request through core's Node bridge, which parses it (400 for a
19
+ * bad Host or target, 405 with `allow` for a verb Schmock does not route),
20
+ * collects the body (400 when it is malformed, 413 over core's 10 MB default,
21
+ * the limit `mock.listen()` uses) and writes every answer. Only what the CLI
22
+ * adds is decided here: the CORS preflight, the admin API, and which answers
23
+ * carry CORS headers.
24
+ *
25
+ * The preflight and the admin API never use the body, so they are answered
26
+ * before it is read: an oversized, malformed or stalled upload cannot turn an
27
+ * admin 401 into a 413 or 400, hold the socket before the refusal, or fail an
28
+ * authorized admin action on a body it ignores. The admin answer therefore
29
+ * also reads the mock as it is on arrival, not after an upload a watch reload
30
+ * may have overtaken.
31
+ *
32
+ * The admission is taken on arrival, before the body uploads: a watch reload
33
+ * resets the previous mock as soon as it swaps in the new one, and an
34
+ * admission snapshots routes, plugins and state, so a request already under
35
+ * way keeps being served by the mock it arrived at.
36
+ */
37
+ export declare function handleCliRequest(req: NodeRequest, res: NodeResponse, context: CliRequestContext): Promise<void>;
38
+ export {};
@@ -0,0 +1,15 @@
1
+ import type * as Schmock from "@schmock/core";
2
+ /**
3
+ * Read a `--seed` manifest.
4
+ *
5
+ * Every entry shape is checked explicitly and anything unrecognised throws:
6
+ * silently dropping a malformed entry used to start a server whose collections
7
+ * were quietly empty. File entries resolve relative to the manifest rather than
8
+ * the process CWD, and may not escape the manifest directory.
9
+ *
10
+ * @throws ResourceLimitError when the manifest exceeds `MAX_SEED_MANIFEST_BYTES`
11
+ * @throws SchmockError `OPENAPI_INVALID_OPTION` for a manifest that is not a
12
+ * JSON object or holds an entry that is not an array, a file path inside the
13
+ * manifest directory, or `{ "count": <number> }`
14
+ */
15
+ export declare function loadSeedFile(seedPath: string): Schmock.SeedConfig;
@@ -0,0 +1,14 @@
1
+ import type { CliOptions, CliServer } from "./types.js";
2
+ /** Default ceiling on how long a graceful close waits for in-flight requests. */
3
+ export declare const SHUTDOWN_GRACE_MS = 5000;
4
+ export declare function serverUrl(address: {
5
+ hostname: string;
6
+ port: number;
7
+ }): string;
8
+ /**
9
+ * Start a mock server for an OpenAPI spec, as the `schmock` command does.
10
+ *
11
+ * @throws SchmockError `INVALID_CONFIG` for a blank hostname or an unusable
12
+ * admin token, before anything is bound
13
+ */
14
+ export declare function createCliServer(options: CliOptions): Promise<CliServer>;
@@ -0,0 +1,28 @@
1
+ import type { Server } from "node:http";
2
+ /**
3
+ * Options for `createCliServer`, one per `schmock` flag. An alias of the
4
+ * ambient `Schmock.CliOptions` rather than a copy, so the documented flags
5
+ * and the accepted options cannot drift apart.
6
+ */
7
+ export type CliOptions = Schmock.CliOptions;
8
+ /**
9
+ * A running CLI server. Kept separate from the ambient `Schmock.CliServer`,
10
+ * whose `server` is a browser-safe subset: this one exposes the exact Node.js
11
+ * `Server`.
12
+ */
13
+ export interface CliServer {
14
+ server: Server;
15
+ port: number;
16
+ hostname: string;
17
+ /**
18
+ * The bearer token this server requires on `/schmock-admin/*`. Present only
19
+ * when admin is enabled; supply it as `Authorization: Bearer <token>`.
20
+ */
21
+ adminToken?: string;
22
+ /**
23
+ * Stop watching, stop accepting, and settle once the socket is released —
24
+ * within {@link CliOptions.shutdownGraceMs}. Memoized: every call observes
25
+ * the same shutdown, so closing twice is safe and resolves twice.
26
+ */
27
+ close(): Promise<void>;
28
+ }
@@ -0,0 +1,46 @@
1
+ import type * as Schmock from "@schmock/core";
2
+ import type { CliOptions } from "./types.js";
3
+ /**
4
+ * The one mutable cell a reload writes to. The socket, the admin token and the
5
+ * request handler all outlive it, so swapping the mock is the entire reload.
6
+ */
7
+ export interface MockHolder {
8
+ mock: Schmock.CallableMockInstance;
9
+ }
10
+ export interface WatchHandle {
11
+ /** Resolves once the watcher is shut and any in-flight reload has settled. */
12
+ close(): Promise<void>;
13
+ }
14
+ interface ReloadInput {
15
+ readonly holder: MockHolder;
16
+ /** Build a replacement mock from the files as they are now. */
17
+ readonly rebuild: () => Promise<Schmock.CallableMockInstance>;
18
+ }
19
+ export interface WatchInput extends ReloadInput {
20
+ /** The spec, `--seed` and `--refs-external` decide what is watched. */
21
+ readonly options: CliOptions;
22
+ /** Where the server listens, for the reload banner. */
23
+ readonly url: string;
24
+ }
25
+ /**
26
+ * Watch the spec (and everything else a reload reads) and hot-swap the mock
27
+ * behind the live server on changes.
28
+ *
29
+ * The watch is on each file's DIRECTORY, not the file itself. `fs.watch` on a
30
+ * file follows its inode, so the first atomic editor save — write a sibling
31
+ * temp file, rename it over the target, which is what vim, JetBrains and VS
32
+ * Code all do — leaves the watcher bound to the replaced inode and silently
33
+ * deaf to every later edit. A directory watch sees the rename, keeps seeing
34
+ * later in-place writes, and re-arms for free when a file is deleted and
35
+ * recreated. It is non-recursive, so a large tree under a watched directory
36
+ * costs nothing. One watcher serves each directory, and the set is re-derived
37
+ * after every reload so a seed manifest that names new files is followed.
38
+ *
39
+ * Paths are resolved with `resolve`, deliberately NOT `realpathSync`: a
40
+ * symlinked spec must keep watching the directory the user actually named.
41
+ * (Consequence: an editor saving the symlink's TARGET, in another directory,
42
+ * fires no event. Watching the target instead would break the far commoner
43
+ * case of a linked spec edited in place.)
44
+ */
45
+ export declare function startWatch(input: WatchInput): WatchHandle;
46
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@schmock/cli",
3
3
  "description": "CLI for running Schmock mock servers from OpenAPI specs",
4
- "version": "2.4.1",
4
+ "version": "2.5.0",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -40,8 +40,8 @@
40
40
  "test": "vitest run",
41
41
  "test:watch": "vitest --watch",
42
42
  "test:bdd": "vitest run --config vitest.config.bdd.ts",
43
- "lint": "biome check src/*.ts",
44
- "lint:fix": "biome check --write --unsafe src/*.ts",
43
+ "lint": "biome check src/",
44
+ "lint:fix": "biome check --write --unsafe src/",
45
45
  "check:publish": "publint && attw --pack --ignore-rules cjs-resolves-to-esm"
46
46
  },
47
47
  "license": "MIT",
@@ -49,8 +49,8 @@
49
49
  "node": "^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0"
50
50
  },
51
51
  "dependencies": {
52
- "@schmock/core": "^2.4.1",
53
- "@schmock/openapi": "^2.4.1"
52
+ "@schmock/core": "^2.5.0",
53
+ "@schmock/openapi": "^2.5.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@amiceli/vitest-cucumber": "^7.0.0",
package/dist/bin.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/cli.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAkB,MAAM,WAAW,CAAC;AAWxD,OAAO,KAAK,KAAK,OAAO,MAAM,eAAe,CAAC;AAgB9C,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;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qEAAqE;IACrE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA6ID;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAwCjE;AA4jBD,wBAAsB,eAAe,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAsB7E;AAED,qGAAqG;AACrG,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CASxD;AAqED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CA2D3E;AAwKD,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAyFvD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC"}