@kong/spec-renderer 1.111.3 → 1.111.4-pr.942.12e41ef.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 CHANGED
@@ -15,6 +15,7 @@ An online API specification editor is available at [api-documentation.dev](https
15
15
  - [Example for html/script](#-example-for-htmlscript)
16
16
  - [Props](#props)
17
17
  - [v-model](#v-model)
18
+ - [CLI](#cli)
18
19
  - [Contributing & Local Development](#contributing--local-development)
19
20
  - [Development Sandbox](#development-sandbox)
20
21
  - [Build and Preview the Development Sandbox](#build-and-preview-the-development-sandbox)
@@ -23,6 +24,7 @@ An online API specification editor is available at [api-documentation.dev](https
23
24
  - [Build for production](#build-for-production)
24
25
  - [Committing Changes](#committing-changes)
25
26
  - [Enforcing Commit Format](#enforcing-commit-format)
27
+ - [Agentic Reviewer](#agentic-reviewer)
26
28
  - [Approvals](#approvals)
27
29
  - [Package Publishing](#package-publishing)
28
30
  - [Third-party packages and Thank You](#third-party-packages-and-thank-you)
@@ -225,6 +227,27 @@ As of now only `SpecRenderer` as single component is supported for this. Let us
225
227
 
226
228
  [Check out the `SpecRendererProps` interface](./src/types/spec-renderer.ts) for all props valid for the `SpecRenderer` component.
227
229
 
230
+ ## CLI
231
+
232
+ Preview an OpenAPI or AsyncAPI spec (from a local file or remote URL), rendered with this package's own UI, right on your machine - no host app or project setup required. The preview reloads automatically when you save changes to the spec file.
233
+
234
+ ### Via `npx` or `pnpm dlx`
235
+
236
+ ```sh
237
+ npx @kong/spec-renderer preview ./openapi.yaml
238
+ ```
239
+
240
+ ### Via kongctl Extension
241
+
242
+ The CLI is also available as a [kongctl](https://github.com/Kong/kongctl) extension:
243
+
244
+ ```sh
245
+ kongctl install extension kong/spec-renderer
246
+ kongctl preview spec ./openapi.yaml
247
+ ```
248
+
249
+ See the [CLI docs](./cli/README.md) for full usage, flags, and troubleshooting.
250
+
228
251
  ## Contributing & Local Development
229
252
 
230
253
  To get started, install the package dependencies
@@ -306,6 +329,10 @@ This will trigger the Commitizen interactive prompt for building your commit mes
306
329
 
307
330
  Additionally, CI will use `commitlint` to validate the commits associated with a PR in the `Lint and Validate` job.
308
331
 
332
+ ### Agentic reviewer
333
+
334
+ [View the reference for the `/muthur` agentic reviewer](./docs/agentic-reviewer.md)
335
+
309
336
  ### Approvals
310
337
 
311
338
  - All pull requests require review and approval from authorized team members.
@@ -0,0 +1,134 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { watch } from 'chokidar';
4
+ import open from 'open';
5
+ import { loadSpecSource } from '../spec-source.js';
6
+ import { resolveRenderOptions } from '../resolve-render-options.js';
7
+ import { findOpenPort } from '../find-open-port.js';
8
+ import { startPreviewServer } from '../server.js';
9
+ import { hyperlink, info, pc, printPreviewBanner } from '../ui.js';
10
+ const DEFAULT_PORT = 4757;
11
+ /**
12
+ * Runs the `preview` command: loads the spec, starts the local preview
13
+ * server, watches the spec file for changes (when it's a local file), and
14
+ * opens the browser.
15
+ *
16
+ * Resolves once the server is up; the process is kept alive by the running
17
+ * server and, when applicable, the file watcher.
18
+ */
19
+ export async function runPreviewCommand(specArg, flags) {
20
+ const source = await loadSpecSource(specArg);
21
+ let specText = source.text;
22
+ const requestedPort = await findOpenPort(flags.port ?? DEFAULT_PORT);
23
+ const previewServer = await startPreviewServer({
24
+ port: requestedPort,
25
+ getSpecText: () => specText,
26
+ config: resolveRenderOptions(flags),
27
+ });
28
+ // Use the port the server actually bound to, not the requested one - they
29
+ // differ when the requested port is `0` (OS picks any free port).
30
+ const url = `http://localhost:${previewServer.port}`;
31
+ const isWatching = source.watch && !!source.watchPath;
32
+ // A local file gets a `file://` link target; a remote spec's URL is
33
+ // already one, so it can link to itself.
34
+ const specLinkTarget = source.watchPath ? pathToFileURL(source.watchPath).href : specArg;
35
+ printPreviewBanner([
36
+ { label: 'Spec', value: pc.bold(pc.underline(hyperlink(specArg, specLinkTarget))) },
37
+ { label: 'URL', value: pc.bold(pc.underline(hyperlink(url, url))) },
38
+ { label: 'Reload', value: isWatching ? pc.green('watching for changes') : pc.yellow('not available (remote URL)') },
39
+ ]);
40
+ console.log();
41
+ info('Press Ctrl+C to stop');
42
+ let watcher;
43
+ if (source.watch && source.watchPath) {
44
+ const watchPath = source.watchPath;
45
+ // Guards against a slower, now-stale read completing after a newer one -
46
+ // e.g. two rapid saves in quick succession - and clobbering fresher content.
47
+ let latestChangeToken = 0;
48
+ watcher = watch(watchPath, {
49
+ // Without this, chokidar's initial scan on startup fires an `add` event
50
+ // for the already-existing watched file - which our `add` handler below
51
+ // (added to handle editors that recreate the file in place) would
52
+ // otherwise mistake for a real change and reload immediately on launch.
53
+ ignoreInitial: true,
54
+ // Some editors/tools write a file's contents in multiple chunks before
55
+ // the save is complete; without this, a `change` event can fire mid-write
56
+ // and be read as truncated/invalid content, surfacing a bogus parse error
57
+ // for what was actually a valid save.
58
+ awaitWriteFinish: {
59
+ stabilityThreshold: 200,
60
+ pollInterval: 20,
61
+ },
62
+ });
63
+ // Some editors (e.g. Vim's default writebackup save) rename the original
64
+ // file away and create a brand-new file at the same path, rather than
65
+ // writing in place. When the gap between that rename and the new file's
66
+ // creation exceeds chokidar's atomic-write dedup window, this surfaces as
67
+ // a genuine `unlink` followed by a separate `add`, instead of a single
68
+ // `change` - so both are treated as reload triggers, and a pending
69
+ // "file deleted" warning is held briefly to give a same-path `add` a
70
+ // chance to arrive first before assuming the file is actually gone.
71
+ let pendingUnlinkWarning;
72
+ const reloadFromDisk = () => {
73
+ const changeToken = ++latestChangeToken;
74
+ readFile(watchPath, 'utf-8')
75
+ .then((text) => {
76
+ if (changeToken !== latestChangeToken) {
77
+ return;
78
+ }
79
+ specText = text;
80
+ previewServer.broadcastReload();
81
+ console.log(`${pc.magenta('↻')} Spec changed, reloading preview...`);
82
+ })
83
+ .catch((readError) => {
84
+ console.error(`${pc.red('✖')} Failed to reload spec: ${readError instanceof Error ? readError.message : String(readError)}`);
85
+ });
86
+ };
87
+ watcher.on('change', reloadFromDisk);
88
+ watcher.on('add', () => {
89
+ if (pendingUnlinkWarning) {
90
+ clearTimeout(pendingUnlinkWarning);
91
+ pendingUnlinkWarning = undefined;
92
+ }
93
+ reloadFromDisk();
94
+ });
95
+ watcher.on('unlink', () => {
96
+ pendingUnlinkWarning = setTimeout(() => {
97
+ pendingUnlinkWarning = undefined;
98
+ console.error(`${pc.red('✖')} Spec file was deleted or moved - live reload has stopped. The preview will keep showing its last-known content.`);
99
+ }, 500);
100
+ });
101
+ watcher.on('error', (watchError) => {
102
+ console.error(`${pc.red('✖')} File watcher error: ${watchError instanceof Error ? watchError.message : String(watchError)}`);
103
+ });
104
+ }
105
+ let isShuttingDown = false;
106
+ const shutdown = () => {
107
+ if (isShuttingDown) {
108
+ return;
109
+ }
110
+ isShuttingDown = true;
111
+ void Promise.resolve(watcher?.close())
112
+ .then(() => previewServer.close())
113
+ .catch((shutdownError) => {
114
+ console.error(`${pc.red('✖')} Error during shutdown: ${shutdownError instanceof Error ? shutdownError.message : String(shutdownError)}`);
115
+ })
116
+ .finally(() => process.exit(0));
117
+ };
118
+ // Registered before opening the browser so the server/watcher always have a
119
+ // clean shutdown path via Ctrl+C, even if the browser launch below fails.
120
+ process.on('SIGINT', shutdown);
121
+ process.on('SIGTERM', shutdown);
122
+ if (flags.open !== false) {
123
+ try {
124
+ await open(url);
125
+ }
126
+ catch (openError) {
127
+ // Opening the browser is a convenience, not essential to the preview
128
+ // working - warn and keep the server/watcher running rather than
129
+ // failing the whole command.
130
+ console.error(`${pc.red('✖')} Failed to open the browser automatically: ${openError instanceof Error ? openError.message : String(openError)}`);
131
+ }
132
+ }
133
+ }
134
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.js","sourceRoot":"","sources":["../../../cli/commands/preview.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { createServer } from 'node:net';
2
+ /**
3
+ * Checks whether a port is free to bind on the local host by briefly binding
4
+ * to it and releasing it.
5
+ */
6
+ export function isPortFree(port) {
7
+ return new Promise((resolve) => {
8
+ const server = createServer();
9
+ server.once('error', () => resolve(false));
10
+ server.once('listening', () => server.close(() => resolve(true)));
11
+ server.listen(port);
12
+ });
13
+ }
14
+ /**
15
+ * Finds the first free port starting at `startPort`, checking up to
16
+ * `maxAttempts` consecutive ports.
17
+ */
18
+ export async function findOpenPort(startPort, checkIsPortFree = isPortFree, maxAttempts = 10) {
19
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
20
+ const candidate = startPort + attempt;
21
+ if (await checkIsPortFree(candidate)) {
22
+ return candidate;
23
+ }
24
+ }
25
+ throw new Error(`Could not find an open port after checking ${maxAttempts} ports starting at ${startPort}`);
26
+ }
27
+ //# sourceMappingURL=find-open-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-open-port.js","sourceRoot":"","sources":["../../cli/find-open-port.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAEvC;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAA;QAE7B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;QAC1C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACrB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,SAAiB,EACjB,kBAAsD,UAAU,EAChE,WAAW,GAAG,EAAE;IAEhB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,MAAM,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;QAErC,IAAI,MAAM,eAAe,CAAC,SAAS,CAAC,EAAE,CAAC;YACrC,OAAO,SAAS,CAAA;QAClB,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,8CAA8C,WAAW,sBAAsB,SAAS,EAAE,CAAC,CAAA;AAC7G,CAAC"}
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import { createProgram } from './program.js';
3
+ import { error as printError } from './ui.js';
4
+ try {
5
+ await createProgram().parseAsync(process.argv);
6
+ }
7
+ catch (error) {
8
+ printError(error instanceof Error ? error.message : String(error));
9
+ process.exitCode = 1;
10
+ }
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,KAAK,IAAI,UAAU,EAAE,MAAM,SAAS,CAAA;AAE7C,IAAI,CAAC;IACH,MAAM,aAAa,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAChD,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,UAAU,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;IAClE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;AACtB,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { InvalidArgumentError } from 'commander';
2
+ /**
3
+ * Builds a commander option parser for a non-negative integer flag.
4
+ *
5
+ * Throws a commander `InvalidArgumentError` for non-numeric or out-of-range
6
+ * input, which commander turns into a clean CLI error - rather than letting
7
+ * `Number.parseInt` silently produce `NaN` (which `??` does not treat as
8
+ * nullish, and which `JSON.stringify` silently serializes to `null`).
9
+ */
10
+ export function parseIntFlag(flagName, options = {}) {
11
+ return (value) => {
12
+ // `Number.parseInt` stops at the first non-digit rather than requiring
13
+ // the whole string to be numeric (e.g. `parseInt('5000abc', 10) === 5000`,
14
+ // `parseInt('2.9', 10) === 2`) - reject anything that isn't purely
15
+ // digits up front so trailing garbage or a decimal point can't silently
16
+ // produce a value the user never typed.
17
+ if (!/^\d+$/.test(value.trim())) {
18
+ const bound = options.max === undefined ? '' : ` up to ${options.max}`;
19
+ throw new InvalidArgumentError(`${flagName} must be a non-negative integer${bound}.`);
20
+ }
21
+ const parsed = Number.parseInt(value, 10);
22
+ if (options.max !== undefined && parsed > options.max) {
23
+ throw new InvalidArgumentError(`${flagName} must be a non-negative integer up to ${options.max}.`);
24
+ }
25
+ return parsed;
26
+ };
27
+ }
28
+ //# sourceMappingURL=parse-int-flag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-int-flag.js","sourceRoot":"","sources":["../../cli/parse-int-flag.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAA;AAOhD;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,UAA+B,EAAE;IAC9E,OAAO,CAAC,KAAa,EAAU,EAAE;QAC/B,uEAAuE;QACvE,2EAA2E;QAC3E,mEAAmE;QACnE,wEAAwE;QACxE,wCAAwC;QACxC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAChC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,OAAO,CAAC,GAAG,EAAE,CAAA;YAEtE,MAAM,IAAI,oBAAoB,CAAC,GAAG,QAAQ,kCAAkC,KAAK,GAAG,CAAC,CAAA;QACvF,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;QAEzC,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;YACtD,MAAM,IAAI,oBAAoB,CAAC,GAAG,QAAQ,yCAAyC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAA;QACpG,CAAC;QAED,OAAO,MAAM,CAAA;IACf,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Fetches the spec/config and assigns them onto the `<kong-spec-renderer>`
3
+ * element, then registers (and upgrades) the custom element - deliberately
4
+ * in that order.
5
+ *
6
+ * Vue's `defineCustomElement` reads any already-present own properties on the
7
+ * element as its *initial* prop values at construction/upgrade time; props
8
+ * assigned afterwards (e.g. `currentPath`) reach the underlying component
9
+ * reactively for props it actively watches, but some - like `currentPath` -
10
+ * are only snapshotted into local refs once at setup and never re-synced.
11
+ * Registering the element only after every property (spec, config, including
12
+ * `currentPath`) is already set ensures the initial render picks up the
13
+ * right values, rather than defaults, the first and only time it matters.
14
+ */
15
+ async function loadAndApply() {
16
+ const el = document.getElementById('preview');
17
+ if (!el) {
18
+ return;
19
+ }
20
+ const [specResponse, configResponse] = await Promise.all([fetch('/spec'), fetch('/config')]);
21
+ const spec = await specResponse.text();
22
+ const config = await configResponse.json();
23
+ el.spec = spec;
24
+ for (const [key, value] of Object.entries(config)) {
25
+ el[key] = value;
26
+ }
27
+ const requestedPath = typeof config.currentPath === 'string' ? config.currentPath : undefined;
28
+ // Matches how the component's own click-driven navigation builds the URL
29
+ // for each `navigationType` (there's no `basePath` here - the CLI's preview
30
+ // page is always served from `/`).
31
+ const toAddressBarUrl = (path) => config.navigationType === 'hash' ? `#${path}` : path;
32
+ if (requestedPath && requestedPath !== '/') {
33
+ // The component only pushes address-bar updates from its own click-driven
34
+ // navigation, not for an initial `currentPath` prop supplied externally -
35
+ // reflect the requested deep link ourselves so the URL matches what's
36
+ // actually rendered. Optimistic: rolled back below if the path turns out
37
+ // to be invalid.
38
+ history.replaceState({}, '', toAddressBarUrl(requestedPath));
39
+ // `--path` has to match this library's internal node URI scheme
40
+ // (`/operations/{operationId}`, `/schemas/{SchemaName}`), not the raw OAS
41
+ // path - there's no other feedback if it doesn't match anything, so warn
42
+ // loudly rather than silently rendering a blank document pane.
43
+ el.addEventListener('path-not-found', ((event) => {
44
+ console.warn(`[kong-spec-renderer] --path "${String(event.detail?.[0] ?? requestedPath)}" doesn't match any operation or schema in this spec. ` +
45
+ 'Expected format: "/operations/{operationId}" or "/schemas/{SchemaName}" - check the spec\'s operationId/schema names, not its raw OAS paths. ' +
46
+ 'Falling back to the overview.');
47
+ history.replaceState({}, '', '/');
48
+ // `currentPath` is only read once, at the component's initial setup -
49
+ // an invalid path can't be corrected by reassigning the property on
50
+ // the existing element, so replace it with a fresh one instead.
51
+ const fallback = document.createElement(el.tagName);
52
+ fallback.id = el.id;
53
+ fallback.spec = spec;
54
+ for (const [key, value] of Object.entries(config)) {
55
+ fallback[key] = key === 'currentPath' ? '/' : value;
56
+ }
57
+ el.replaceWith(fallback);
58
+ }));
59
+ }
60
+ // A variable (rather than a string literal) module specifier so TypeScript
61
+ // treats this as an untyped dynamic import instead of trying to resolve
62
+ // `/assets/...` at typecheck time - it only exists once the CLI serves it.
63
+ const bundlePath = '/assets/kong-spec-renderer.web-component.es.js';
64
+ const { registerKongSpecRenderer } = await import(bundlePath);
65
+ registerKongSpecRenderer();
66
+ }
67
+ void loadAndApply();
68
+ const RECONNECT_DELAY_MS = 1000;
69
+ /**
70
+ * Connects the reload-signal WebSocket, reconnecting after a delay if the
71
+ * connection drops (e.g. the CLI process restarted, or the machine slept)
72
+ * so live-reload keeps working without requiring a manual page refresh.
73
+ */
74
+ function connect() {
75
+ const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
76
+ const socket = new WebSocket(`${protocol}://${location.host}`);
77
+ socket.addEventListener('message', (event) => {
78
+ const message = JSON.parse(event.data);
79
+ if (message.type === 'reload') {
80
+ location.reload();
81
+ }
82
+ });
83
+ socket.addEventListener('close', () => {
84
+ setTimeout(connect, RECONNECT_DELAY_MS);
85
+ });
86
+ }
87
+ connect();
88
+ export {};
89
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../cli/preview-page/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <title>Spec Preview - @kong/spec-renderer</title>
8
+ <link rel="stylesheet" href="/assets/spec-renderer.css" />
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link
12
+ href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap"
13
+ rel="stylesheet">
14
+ <style>
15
+ html,
16
+ body {
17
+ margin: 0;
18
+ height: 100%;
19
+ font-family: 'Inter', Roboto, Helvetica, sans-serif;
20
+ }
21
+
22
+ kong-spec-renderer {
23
+ display: block;
24
+ height: 100dvh;
25
+ }
26
+ </style>
27
+ </head>
28
+
29
+ <body>
30
+ <kong-spec-renderer id="preview"></kong-spec-renderer>
31
+ <script type="module" src="/client.js"></script>
32
+ </body>
33
+
34
+ </html>
@@ -0,0 +1,105 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { Command, Option } from 'commander';
5
+ import { runPreviewCommand } from './commands/preview.js';
6
+ import { error as printError } from './ui.js';
7
+ import { parseIntFlag } from './parse-int-flag.js';
8
+ /**
9
+ * Walks up from `startDir` to find the nearest `package.json` belonging to
10
+ * `@kong/spec-renderer` itself, rather than assuming a fixed directory depth
11
+ * - this module runs both compiled (`dist/cli/program.js`, two levels below
12
+ * the repo root) and directly from source (`cli/program.ts`, one level
13
+ * below), so a hardcoded `join(dir, '..', '..')` is only correct for one of
14
+ * those and silently resolves to the wrong file (or throws ENOENT) for the
15
+ * other. Candidates are validated (not just checked for existence) since a
16
+ * marker `package.json` with no `version` field (e.g. a `{"type": "module"}`
17
+ * dual-package hazard file, a pattern some build tools drop into `dist/`)
18
+ * could otherwise be accepted, breaking `program.version(undefined, ...)`.
19
+ */
20
+ function findPackageVersion(startDir) {
21
+ let dir = startDir;
22
+ for (;;) {
23
+ const candidate = join(dir, 'package.json');
24
+ if (existsSync(candidate)) {
25
+ const parsed = JSON.parse(readFileSync(candidate, 'utf-8'));
26
+ if (typeof parsed === 'object' && parsed !== null
27
+ && 'name' in parsed && parsed.name === '@kong/spec-renderer'
28
+ && 'version' in parsed && typeof parsed.version === 'string') {
29
+ return parsed.version;
30
+ }
31
+ }
32
+ const parent = dirname(dir);
33
+ if (parent === dir) {
34
+ throw new Error(`Could not find @kong/spec-renderer's package.json walking up from ${startDir}`);
35
+ }
36
+ dir = parent;
37
+ }
38
+ }
39
+ const DOCS_URL = 'https://github.com/Kong/spec-renderer/blob/main/cli/README.md';
40
+ /**
41
+ * Builds the commander program without parsing `process.argv`, so it can be
42
+ * imported and introspected (e.g. by the kongctl extension manifest
43
+ * generator) without invoking the CLI.
44
+ */
45
+ export function createProgram() {
46
+ // The CLI isn't versioned independently - it always reports the package's
47
+ // own version, which is what actually gets published/installed.
48
+ const currentDir = dirname(fileURLToPath(import.meta.url));
49
+ const version = findPackageVersion(currentDir);
50
+ const program = new Command();
51
+ // Flags are registered in the order that groups them logically for readers
52
+ // of this file, not alphabetically - sort them for `--help` output instead so
53
+ // they're easy to scan regardless of definition order. `showGlobalOptions`
54
+ // surfaces `-V/--version` (registered on the top-level program, not the
55
+ // `preview` subcommand) in `preview --help` too - otherwise it's easy to
56
+ // assume it doesn't exist, since usage is always through the subcommand.
57
+ program.configureHelp({ sortOptions: true, showGlobalOptions: true });
58
+ program
59
+ .name('kong-spec-renderer')
60
+ .description('Preview an OpenAPI/AsyncAPI spec (local file or remote URL) using @kong/spec-renderer')
61
+ .version(version, '-V, --version', 'output the current version')
62
+ .addHelpText('after', `\nDocs: ${DOCS_URL}`);
63
+ program
64
+ .command('preview')
65
+ .description('Preview a local spec file or remote spec URL using the spec renderer.')
66
+ .argument('<spec>', 'path to a local spec file, or a URL to a remote spec')
67
+ .option('--port <port>', 'port to run the preview server on', parseIntFlag('--port', { max: 65535 }))
68
+ .option('--no-open', 'do not automatically open the preview in a browser')
69
+ .option('--hide-internal', 'hide internal operations from the table of contents')
70
+ .option('--hide-deprecated', 'hide deprecated operations from the table of contents')
71
+ .option('--hide-schemas', 'hide schemas (models) from the table of contents')
72
+ .option('--hide-try-it', 'hide the "Try it" UI')
73
+ .option('--hide-insomnia-try-it', 'hide the "Insomnia" option within the "Try it" UI')
74
+ .option('--with-credentials', 'send credentials when resolving external (http) $refs within the spec')
75
+ .option('--max-expanded-depth <depth>', 'maximum depth to expand nested schema properties by default', parseIntFlag('--max-expanded-depth'))
76
+ .option('--verbose', 'log spec parsing stages to the browser console, for troubleshooting (alias: --trace-parsing)')
77
+ .addOption(new Option('--trace-parsing', 'alias for --verbose').hideHelp())
78
+ .option('--no-content-scrolling', 'navigate between operations/schemas one at a time instead of scrolling through them continuously (has no effect above 1200 combined operations/schemas, where this is always enforced)')
79
+ .option('--no-custom-server-url', 'do not let the preview add a custom server URL to the servers list')
80
+ .option('--show-navigation-buttons', 'show prev/next buttons at the bottom of each operation - useful with --no-content-scrolling')
81
+ .option('--hide-download-button', 'hide the spec download button')
82
+ .option('--enable-operation-links', 'show a permalink icon on each operation that copies its URL to the clipboard')
83
+ .option('--hide-powered-by', 'hide the "Powered by" branding in the table of contents')
84
+ .option('--path <path>', 'open the preview at a specific operation/schema, e.g. /operations/getPets or /schemas/Pet (not the raw OAS path)')
85
+ .addOption(new Option('--navigation-type <type>', 'how the current operation/schema is tracked in the URL - "path" breaks a browser refresh at a deep link, since the server has no route fallback for it')
86
+ .choices(['path', 'hash'])
87
+ .default('hash'))
88
+ .addHelpText('after', `\nDocs: ${DOCS_URL}`)
89
+ .action(async (spec, raw) => {
90
+ const flags = {
91
+ ...raw,
92
+ allowContentScrolling: raw.contentScrolling,
93
+ currentPath: raw.path,
94
+ };
95
+ try {
96
+ await runPreviewCommand(spec, flags);
97
+ }
98
+ catch (error) {
99
+ printError(error instanceof Error ? error.message : String(error));
100
+ process.exitCode = 1;
101
+ }
102
+ });
103
+ return program;
104
+ }
105
+ //# sourceMappingURL=program.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"program.js","sourceRoot":"","sources":["../../cli/program.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Maps parsed `preview` CLI flags to the `SpecRendererProps` subset the preview
3
+ * page renders with.
4
+ *
5
+ * `controlAddressBar` is always hardcoded true - the CLI, not the user, owns
6
+ * the single-page preview environment that prop governs. `navigationType`
7
+ * defaults to `'hash'` rather than the component's own `'path'` default -
8
+ * with `'path'`, the CLI's server would need to fall back to the preview page
9
+ * for arbitrary unmatched routes to support a browser refresh at a deep link,
10
+ * which it doesn't do, so refreshing 404s; `'hash'` keeps the path client-side
11
+ * only, so a refresh always hits `/`. `showPoweredBy` defaults to `true` here
12
+ * even though the component prop itself defaults to `false` - the CLI wants
13
+ * branding visible unless a user opts out with `--hide-powered-by`.
14
+ * `hideNavigationButtons` defaults to `true` (hidden), matching the
15
+ * component's own default, and only flips to `false` when
16
+ * `--show-navigation-buttons` is explicitly passed - mainly useful alongside
17
+ * `--no-content-scrolling`, where the prev/next buttons are otherwise the
18
+ * only in-page way to move between operations.
19
+ */
20
+ export function resolveRenderOptions(flags) {
21
+ return {
22
+ hideInternal: flags.hideInternal ?? false,
23
+ hideDeprecated: flags.hideDeprecated ?? false,
24
+ hideSchemas: flags.hideSchemas ?? false,
25
+ hideTryIt: flags.hideTryIt ?? false,
26
+ hideInsomniaTryIt: flags.hideInsomniaTryIt ?? false,
27
+ ...(flags.maxExpandedDepth === undefined ? {} : { maxExpandedDepth: flags.maxExpandedDepth }),
28
+ traceParsing: (flags.verbose ?? false) || (flags.traceParsing ?? false),
29
+ withCredentials: flags.withCredentials ?? false,
30
+ allowContentScrolling: flags.allowContentScrolling ?? true,
31
+ allowCustomServerUrl: flags.customServerUrl ?? true,
32
+ hideNavigationButtons: !(flags.showNavigationButtons ?? false),
33
+ hideDownloadButton: flags.hideDownloadButton ?? false,
34
+ enableOperationLinks: flags.enableOperationLinks ?? false,
35
+ showPoweredBy: !flags.hidePoweredBy,
36
+ navigationType: flags.navigationType ?? 'hash',
37
+ controlAddressBar: true,
38
+ ...(flags.currentPath === undefined ? {} : { currentPath: flags.currentPath }),
39
+ };
40
+ }
41
+ //# sourceMappingURL=resolve-render-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-render-options.js","sourceRoot":"","sources":["../../cli/resolve-render-options.ts"],"names":[],"mappings":"AA2DA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAsB;IACzD,OAAO;QACL,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,KAAK;QACzC,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,KAAK;QAC7C,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,KAAK;QACvC,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,KAAK;QACnC,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,IAAI,KAAK;QACnD,GAAG,CAAC,KAAK,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,EAAE,CAAC;QAC7F,YAAY,EAAE,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC;QACvE,eAAe,EAAE,KAAK,CAAC,eAAe,IAAI,KAAK;QAC/C,qBAAqB,EAAE,KAAK,CAAC,qBAAqB,IAAI,IAAI;QAC1D,oBAAoB,EAAE,KAAK,CAAC,eAAe,IAAI,IAAI;QACnD,qBAAqB,EAAE,CAAC,CAAC,KAAK,CAAC,qBAAqB,IAAI,KAAK,CAAC;QAC9D,kBAAkB,EAAE,KAAK,CAAC,kBAAkB,IAAI,KAAK;QACrD,oBAAoB,EAAE,KAAK,CAAC,oBAAoB,IAAI,KAAK;QACzD,aAAa,EAAE,CAAC,KAAK,CAAC,aAAa;QACnC,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,MAAM;QAC9C,iBAAiB,EAAE,IAAI;QACvB,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;KAC/E,CAAA;AACH,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Classifies a CLI spec argument as a local file path or a remote URL.
3
+ *
4
+ * Only `http:`/`https:` URLs are treated as remote sources; anything else
5
+ * (including a value that isn't a valid URL at all) is treated as a local
6
+ * file path.
7
+ */
8
+ export function resolveSpecSource(arg) {
9
+ try {
10
+ const url = new URL(arg);
11
+ if (url.protocol === 'http:' || url.protocol === 'https:') {
12
+ return { kind: 'url', value: arg };
13
+ }
14
+ }
15
+ catch {
16
+ // Not a parseable URL - fall through to treating it as a file path.
17
+ }
18
+ return { kind: 'file', value: arg };
19
+ }
20
+ //# sourceMappingURL=resolve-spec-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-spec-source.js","sourceRoot":"","sources":["../../cli/resolve-spec-source.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;QACxB,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC1D,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAA;QACpC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oEAAoE;IACtE,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAA;AACrC,CAAC"}
@@ -0,0 +1,137 @@
1
+ import { createServer as createHttpServer } from 'node:http';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { dirname, extname, join, relative } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { WebSocketServer } from 'ws';
6
+ const currentDir = dirname(fileURLToPath(import.meta.url));
7
+ // The preview server ships inside the package's own dist/ output (dist/cli/server.js),
8
+ // so the already-built web-component bundle sits one directory up.
9
+ const DEFAULT_DIST_DIR = dirname(currentDir);
10
+ const DEFAULT_PREVIEW_PAGE_DIR = join(currentDir, 'preview-page');
11
+ const CONTENT_TYPES = {
12
+ '.js': 'text/javascript',
13
+ '.mjs': 'text/javascript',
14
+ '.css': 'text/css',
15
+ '.map': 'application/json',
16
+ '.wasm': 'application/wasm',
17
+ '.json': 'application/json',
18
+ };
19
+ /**
20
+ * Resolves an `/assets/*` request path against `distDir`, returning
21
+ * `undefined` if the path would escape it.
22
+ *
23
+ * The web-component bundle is code-split (syntax-highlighting language
24
+ * grammars/themes, etc.), so the browser fetches an open-ended set of
25
+ * relatively-imported chunk files from `dist/` at runtime - not just the
26
+ * entry bundle and its stylesheet.
27
+ */
28
+ function resolveDistAsset(distDir, requestPath) {
29
+ const candidate = join(distDir, requestPath);
30
+ if (relative(distDir, candidate).startsWith('..')) {
31
+ return undefined;
32
+ }
33
+ return candidate;
34
+ }
35
+ /**
36
+ * Starts the CLI's local preview server: a minimal static file server for the
37
+ * preview page and the package's own built web-component assets, plus a
38
+ * WebSocket channel used to tell the page to reload when the watched spec
39
+ * file changes.
40
+ */
41
+ export async function startPreviewServer(options) {
42
+ const distDir = options.distDir ?? DEFAULT_DIST_DIR;
43
+ const previewPageDir = options.previewPageDir ?? DEFAULT_PREVIEW_PAGE_DIR;
44
+ const server = createHttpServer((req, res) => {
45
+ void handleRequest(req.url, options, distDir, previewPageDir).then(({ status, body, contentType }) => {
46
+ res.writeHead(status, contentType ? { 'content-type': contentType } : undefined);
47
+ res.end(body);
48
+ }).catch((error) => {
49
+ res.writeHead(500, { 'content-type': 'text/plain' });
50
+ res.end(`Internal server error: ${error instanceof Error ? error.message : String(error)}`);
51
+ });
52
+ });
53
+ const wss = new WebSocketServer({ server });
54
+ // Without an 'error' listener, a failed listen() (e.g. EADDRINUSE from a
55
+ // TOCTOU race against find-open-port.ts's earlier probe) throws and crashes
56
+ // the process instead of rejecting cleanly through the CLI's own error path.
57
+ // `ws`'s WebSocketServer re-emits the underlying http.Server's 'error' event
58
+ // on itself, so the listener has to guard `wss`, not just `server` - an
59
+ // unhandled 'error' on the WebSocketServer instance crashes just the same.
60
+ await new Promise((resolve, reject) => {
61
+ const onListenError = (error) => reject(error);
62
+ server.once('error', onListenError);
63
+ wss.once('error', onListenError);
64
+ server.listen(options.port, () => {
65
+ server.off('error', onListenError);
66
+ wss.off('error', onListenError);
67
+ resolve();
68
+ });
69
+ });
70
+ // A runtime error after startup (e.g. a transient socket error) should be
71
+ // logged, not left to crash the process as an unhandled 'error' event.
72
+ server.on('error', (error) => {
73
+ console.error(`Preview server error: ${error instanceof Error ? error.message : String(error)}`);
74
+ });
75
+ wss.on('error', (error) => {
76
+ console.error(`Preview server error: ${error instanceof Error ? error.message : String(error)}`);
77
+ });
78
+ const address = server.address();
79
+ // `address()` returns a string for a Unix socket/pipe, never the case here
80
+ // since we always listen on a numeric port - but guard rather than assume.
81
+ const port = typeof address === 'object' && address !== null ? address.port : options.port;
82
+ return {
83
+ server,
84
+ port,
85
+ broadcastReload: () => {
86
+ const message = JSON.stringify({ type: 'reload' });
87
+ for (const client of wss.clients) {
88
+ if (client.readyState === client.OPEN) {
89
+ client.send(message);
90
+ }
91
+ }
92
+ },
93
+ close: () => new Promise((resolve, reject) => {
94
+ // wss.close()/server.close() only invoke their callback once every
95
+ // connection has ended on its own - with a preview tab left open,
96
+ // that never happens. Force-terminate everything so shutdown is prompt
97
+ // regardless of what's still connected.
98
+ for (const client of wss.clients) {
99
+ client.terminate();
100
+ }
101
+ server.closeAllConnections();
102
+ wss.close(() => {
103
+ server.close((error) => error ? reject(error) : resolve());
104
+ });
105
+ }),
106
+ };
107
+ }
108
+ async function handleRequest(url, options, distDir, previewPageDir) {
109
+ if (url === '/spec') {
110
+ return { status: 200, body: options.getSpecText(), contentType: 'text/plain; charset=utf-8' };
111
+ }
112
+ if (url === '/config') {
113
+ return { status: 200, body: JSON.stringify(options.config), contentType: 'application/json' };
114
+ }
115
+ if (url === '/' || url === '/index.html') {
116
+ const body = await readFile(join(previewPageDir, 'index.html'), 'utf-8');
117
+ return { status: 200, body, contentType: 'text/html; charset=utf-8' };
118
+ }
119
+ if (url === '/client.js') {
120
+ const body = await readFile(join(previewPageDir, 'client.js'));
121
+ return { status: 200, body, contentType: 'text/javascript' };
122
+ }
123
+ if (url?.startsWith('/assets/')) {
124
+ const assetPath = resolveDistAsset(distDir, url.slice('/assets/'.length));
125
+ if (assetPath) {
126
+ try {
127
+ const body = await readFile(assetPath);
128
+ return { status: 200, body, contentType: CONTENT_TYPES[extname(assetPath)] ?? 'application/octet-stream' };
129
+ }
130
+ catch {
131
+ // Fall through to 404 below - e.g. the file doesn't exist.
132
+ }
133
+ }
134
+ }
135
+ return { status: 404, body: 'Not found', contentType: 'text/plain' };
136
+ }
137
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../cli/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { resolveSpecSource } from './resolve-spec-source.js';
4
+ /**
5
+ * Reads a spec's text from either a local file or a remote URL.
6
+ *
7
+ * Local files are watchable for live-reload; remote URLs are fetched once
8
+ * and are not watched (there is no local file to observe for changes).
9
+ */
10
+ export async function loadSpecSource(arg) {
11
+ const source = resolveSpecSource(arg);
12
+ if (source.kind === 'file') {
13
+ const watchPath = resolve(source.value);
14
+ const text = await readFile(watchPath, 'utf-8');
15
+ return { text, watch: true, watchPath };
16
+ }
17
+ const response = await fetch(source.value);
18
+ if (!response.ok) {
19
+ throw new Error(`Failed to fetch spec from ${source.value}: ${response.status} ${response.statusText}`);
20
+ }
21
+ return { text: await response.text(), watch: false };
22
+ }
23
+ //# sourceMappingURL=spec-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-source.js","sourceRoot":"","sources":["../../cli/spec-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAU5D;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW;IAC9C,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAA;IAErC,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC3B,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACvC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QAE/C,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;IACzC,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAE1C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,6BAA6B,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;IACzG,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;AACtD,CAAC"}
package/dist/cli/ui.js ADDED
@@ -0,0 +1,107 @@
1
+ import { createColors } from 'picocolors';
2
+ // picocolors' default export auto-detects color support and disables it for
3
+ // a non-TTY stdout (e.g. output piped to a file or another process) - the
4
+ // preview banner/log lines are always meant to be colored when read in a
5
+ // terminal, so force it on regardless of how stdout is connected. `NO_COLOR`
6
+ // is still honored, since it's the standard explicit user opt-out. Exported
7
+ // so other CLI modules (e.g. `commands/preview.ts`) share the same forced
8
+ // instance rather than importing picocolors' auto-detecting default directly.
9
+ export const pc = createColors(!process.env.NO_COLOR);
10
+ const BOX_TITLE = 'Kong Spec Renderer - Preview';
11
+ // Caps the box at a width that fits comfortably in a typical terminal - a
12
+ // long spec path/URL wraps onto continuation lines instead of pushing the
13
+ // box wider than the terminal, which breaks the border rendering.
14
+ const MAX_CONTENT_WIDTH = 96;
15
+ // eslint-disable-next-line no-control-regex -- matches ANSI SGR escape codes and OSC 8 hyperlink wrappers, which are control characters by definition
16
+ const ANSI_PATTERN = /\x1B\[[0-9;]*m|\x1B\]8;[^\x07]*\x07/g;
17
+ /** Visible length of a string, ignoring ANSI color escape codes and OSC 8 hyperlink wrappers. */
18
+ export function visibleLength(text) {
19
+ return text.replace(ANSI_PATTERN, '').length;
20
+ }
21
+ /**
22
+ * Wraps `text` in an OSC 8 terminal hyperlink pointing at `url`, so it's
23
+ * clickable in terminals that support it (iTerm2, Kitty, Windows Terminal,
24
+ * VS Code's integrated terminal, etc.) - falls back to plain, unlinked text
25
+ * everywhere else with no visible difference.
26
+ */
27
+ export function hyperlink(text, url) {
28
+ return `\x1B]8;;${url}\x07${text}\x1B]8;;\x07`;
29
+ }
30
+ /** Strips ANSI color escape codes from a string. */
31
+ function stripAnsi(text) {
32
+ return text.replace(ANSI_PATTERN, '');
33
+ }
34
+ /** Hard-wraps plain text into chunks of at most `width` characters. */
35
+ function wrapPlain(text, width) {
36
+ if (text.length <= width) {
37
+ return [text];
38
+ }
39
+ const chunks = [];
40
+ for (let i = 0; i < text.length; i += width) {
41
+ chunks.push(text.slice(i, i + width));
42
+ }
43
+ return chunks;
44
+ }
45
+ /**
46
+ * Builds the lines of a bordered summary box (spec source, URL, live-reload
47
+ * status), aligned by visible length.
48
+ *
49
+ * `value` may contain ANSI color codes - widths are computed from visible
50
+ * length so colored values don't throw off the box's alignment. A value
51
+ * longer than fits within `maxContentWidth` wraps onto continuation lines;
52
+ * since wrapping a colored value risks splitting mid-escape-sequence, an
53
+ * over-long value wraps as plain text instead (long values are spec paths/
54
+ * URLs, which aren't colored today anyway). Split out from
55
+ * `printPreviewBanner` so the alignment math is unit-testable without
56
+ * capturing console output or mocking terminal width.
57
+ */
58
+ export function buildBannerLines(rows, maxContentWidth = MAX_CONTENT_WIDTH) {
59
+ const labelWidth = Math.max(...rows.map((row) => row.label.length));
60
+ const valueWidth = Math.max(20, maxContentWidth - labelWidth - 2);
61
+ const wrappedRows = rows.map((row) => ({
62
+ label: row.label,
63
+ valueLines: visibleLength(row.value) <= valueWidth
64
+ ? [row.value]
65
+ : wrapPlain(stripAnsi(row.value), valueWidth),
66
+ }));
67
+ const contentWidth = Math.max(BOX_TITLE.length, ...wrappedRows.flatMap((row) => row.valueLines.map((line) => labelWidth + 2 + visibleLength(line))));
68
+ const horizontal = '─'.repeat(contentWidth + 2);
69
+ const lines = [
70
+ pc.cyan(`┌${horizontal}┐`),
71
+ `${pc.cyan('│')} ${pc.bold(BOX_TITLE.padEnd(contentWidth))} ${pc.cyan('│')}`,
72
+ pc.cyan(`├${horizontal}┤`),
73
+ ];
74
+ for (const row of wrappedRows) {
75
+ row.valueLines.forEach((line, index) => {
76
+ const label = index === 0 ? row.label.padEnd(labelWidth) : ' '.repeat(labelWidth);
77
+ const padding = ' '.repeat(contentWidth - labelWidth - 2 - visibleLength(line));
78
+ lines.push(`${pc.cyan('│')} ${pc.dim(label)} ${line}${padding} ${pc.cyan('│')}`);
79
+ });
80
+ }
81
+ lines.push(pc.cyan(`└${horizontal}┘`));
82
+ return lines;
83
+ }
84
+ /**
85
+ * The box's content width, capped to the actual terminal width when known
86
+ * (interactive terminals report `process.stdout.columns`; piped/non-TTY
87
+ * output does not, so `MAX_CONTENT_WIDTH` is used as a sane default).
88
+ */
89
+ function terminalAwareMaxWidth() {
90
+ const terminalWidth = process.stdout.columns;
91
+ if (!terminalWidth) {
92
+ return MAX_CONTENT_WIDTH;
93
+ }
94
+ // Leave room for the box's own border/padding characters (`│ ` + ` │`).
95
+ return Math.max(20, Math.min(MAX_CONTENT_WIDTH, terminalWidth - 4));
96
+ }
97
+ /** Prints a bordered summary box for the running preview server. */
98
+ export function printPreviewBanner(rows) {
99
+ for (const line of buildBannerLines(rows, terminalAwareMaxWidth())) {
100
+ console.log(line);
101
+ }
102
+ }
103
+ export const success = (message) => console.log(`${pc.green('✔')} ${message}`);
104
+ export const info = (message) => console.log(`${pc.cyan('i')} ${message}`);
105
+ export const warn = (message) => console.log(`${pc.yellow('⚠')} ${pc.yellow(message)}`);
106
+ export const error = (message) => console.error(`${pc.red('✖')} ${pc.red(message)}`);
107
+ //# sourceMappingURL=ui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../cli/ui.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@kong/spec-renderer",
3
- "version": "1.111.3",
3
+ "version": "1.111.4-pr.942.12e41ef.0",
4
4
  "description": "Kong's open-source spec renderer",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "main": "./dist/kong-spec-renderer.cjs",
8
8
  "module": "./dist/kong-spec-renderer.es.js",
9
+ "bin": {
10
+ "kong-spec-renderer": "./dist/cli/index.js"
11
+ },
9
12
  "files": [
10
13
  "dist"
11
14
  ],
@@ -29,26 +32,6 @@
29
32
  "publishConfig": {
30
33
  "access": "public"
31
34
  },
32
- "scripts": {
33
- "dev": "cross-env USE_SANDBOX=true vite",
34
- "build": "pnpm run typecheck && pnpm run build:package && pnpm run build:webcomponent && pnpm run build:types && pnpm run build:aliases",
35
- "build:package": "vite build -m production",
36
- "build:webcomponent": "cross-env VITE_AS_WEB_COMPONENT='true' vite build -m production",
37
- "build:types": "vue-tsc -p './tsconfig.build.json' --emitDeclarationOnly",
38
- "build:aliases": "tsc-alias -p './tsconfig.build.json'",
39
- "build:analyzer": "BUILD_VISUALIZER='true' vite build -m production",
40
- "build:sandbox": "cross-env USE_SANDBOX=true vite build -m production",
41
- "preview": "pnpm run build:sandbox && cross-env USE_SANDBOX=true vite preview",
42
- "typecheck": "vue-tsc -p './tsconfig.build.json' --noEmit",
43
- "stylelint": "stylelint --allow-empty-input './src/**/*.{css,scss,vue}'",
44
- "stylelint:fix": "stylelint --allow-empty-input './src/**/*.{css,scss,sass,vue}' --fix",
45
- "lint": "eslint",
46
- "lint:fix": "eslint --fix",
47
- "test": "cross-env FORCE_COLOR=1 vitest run",
48
- "test:open": "cross-env FORCE_COLOR=1 vitest --ui",
49
- "semantic-release": "semantic-release",
50
- "commit": "cz"
51
- },
52
35
  "peerDependencies": {
53
36
  "vue": ">= 3.3.13 < 4"
54
37
  },
@@ -65,13 +48,18 @@
65
48
  "@stoplight/yaml": "^4.3.0",
66
49
  "@vueuse/core": "^14.3.0",
67
50
  "allof-merge": "^0.6.8",
51
+ "chokidar": "^5.0.0",
52
+ "commander": "^15.0.0",
68
53
  "flatted": "^3.4.2",
69
54
  "form-urlencoded": "^6.1.7",
70
55
  "httpsnippet": "^3.0.10",
71
56
  "lodash-es": "^4.18.1",
72
57
  "markdown-it": "^14.3.0",
58
+ "open": "^11.0.0",
59
+ "picocolors": "^1.1.1",
73
60
  "sanitize-html": "^2.17.6",
74
- "shiki": "^3.23.0"
61
+ "shiki": "^3.23.0",
62
+ "ws": "^8.21.1"
75
63
  },
76
64
  "resolutions": {
77
65
  "openapi3-ts": "4.6.0"
@@ -86,6 +74,7 @@
86
74
  "@kong/eslint-config-kong-ui": "1.7.1",
87
75
  "@kong/stylelint-plugin-design-tokens": "1.0.1",
88
76
  "@semantic-release/changelog": "^6.0.3",
77
+ "@semantic-release/exec": "^6.0.3",
89
78
  "@semantic-release/git": "^10.0.1",
90
79
  "@stylistic/stylelint-plugin": "^5.2.1",
91
80
  "@types/jsdom": "^28.0.3",
@@ -93,6 +82,7 @@
93
82
  "@types/markdown-it": "^14.1.2",
94
83
  "@types/node": "^24.13.3",
95
84
  "@types/sanitize-html": "^2.16.1",
85
+ "@types/ws": "^8.18.1",
96
86
  "@vitejs/plugin-vue": "^6.0.8",
97
87
  "@vitest/ui": "^3.2.7",
98
88
  "@vue/test-utils": "^2.4.11",
@@ -128,62 +118,8 @@
128
118
  "vitest": "4.1.10",
129
119
  "vue": "^3.5.39",
130
120
  "vue-router": "^4.6.4",
131
- "vue-tsc": "^3.3.7"
132
- },
133
- "pnpm": {
134
- "onlyBuiltDependencies": [
135
- "@evilmartians/lefthook",
136
- "@parcel/watcher",
137
- "esbuild",
138
- "vue-demi"
139
- ],
140
- "overrides": {
141
- "openapi3-ts": "4.6.0",
142
- "jsonpath-plus@<10.3.0": ">=10.4.0",
143
- "elliptic@<=6.6.0": ">=6.6.1",
144
- "@babel/helpers@<8.0.0": ">=8.0.0",
145
- "cross-spawn@>=7.0.0 <7.0.5": ">=7.0.6",
146
- "brace-expansion@<=5.0.7": ">=5.0.9",
147
- "pbkdf2@<=3.1.2": ">=3.1.6",
148
- "sha.js@<=2.4.11": ">=2.4.12",
149
- "cipher-base@<=1.0.4": ">=1.0.7",
150
- "glob@>=10.2.0 <10.5.0": ">=13.0.6",
151
- "diff@>=4.0.0 <4.0.4": ">=4.0.4",
152
- "@isaacs/brace-expansion@<=5.0.0": ">=5.0.1",
153
- "mdast-util-to-hast@>=13.0.0 <13.2.1": ">=13.2.1",
154
- "qs@>=6.7.0 <=6.15.1": ">=6.15.3",
155
- "bn.js@>=5.0.0 <5.2.3": ">=5.2.5",
156
- "bn.js@<4.12.3": ">=5.2.5",
157
- "rollup@>=4.0.0 <4.59.0": ">=4.62.2",
158
- "minimatch@<10.2.3": ">=10.2.5",
159
- "ajv@<6.14.0": "^6.15.0",
160
- "immutable@>=5.0.0 <5.1.8": ">=5.1.9",
161
- "flatted@<=3.4.1": ">=3.4.2",
162
- "undici@<6.24.0": ">=8.5.0",
163
- "undici@>=7.0.0 <7.29.0": "7.29.0",
164
- "undici@>=8.0.0 <8.9.0": ">=8.9.0",
165
- "handlebars@>=4.0.0 <4.7.9": ">=4.7.9",
166
- "picomatch@<2.3.2": ">=4.0.5",
167
- "lodash-es@<=4.17.23": ">=4.18.0",
168
- "lodash@<=4.17.23": ">=4.18.0",
169
- "yaml@>=2.0.0 <2.8.3": ">=2.9.0",
170
- "fast-uri@<=3.1.1": ">=3.1.4",
171
- "fast-uri@>=4.0.0 <4.1.2": ">=4.1.2",
172
- "uuid@<11.1.1": ">=11.1.1",
173
- "vitest@<3.2.6": ">=3.2.7",
174
- "js-cookie@<=3.0.5": ">=3.0.8",
175
- "tmp@<0.2.6": ">=0.2.7",
176
- "esbuild@>=0.27.3 <0.28.1": ">=0.28.1",
177
- "sanitize-html@=2.17.3": ">=2.17.6",
178
- "ws@>=8.0.0 <8.21.0": ">=8.21.1",
179
- "form-data@>=4.0.0 <4.0.6": ">=4.0.6",
180
- "vite@>=7.0.0 <=7.3.4": ">=7.3.6",
181
- "@babel/core@<8.0.0": ">=8.0.1",
182
- "markdown-it@<=14.1.1": ">=14.2.0",
183
- "linkify-it@<=5.0.0": ">=5.0.2",
184
- "js-yaml@>=4.0.0 <=4.1.1": ">=4.3.0",
185
- "js-yaml@>=5.0.0 <=5.2.1": ">=5.2.2"
186
- }
121
+ "vue-tsc": "^3.3.7",
122
+ "yaml": "^2.9.0"
187
123
  },
188
124
  "repository": {
189
125
  "type": "git",
@@ -231,7 +167,23 @@
231
167
  ]
232
168
  }
233
169
  ],
234
- "@semantic-release/github"
170
+ [
171
+ "@semantic-release/exec",
172
+ {
173
+ "prepareCmd": "scripts/build-kongctl-extension.sh ${nextRelease.version}"
174
+ }
175
+ ],
176
+ [
177
+ "@semantic-release/github",
178
+ {
179
+ "assets": [
180
+ {
181
+ "path": "dist/kongctl-ext-spec-preview-universal.tar.gz",
182
+ "label": "kongctl-ext-spec-preview (universal, requires Node.js)"
183
+ }
184
+ ]
185
+ }
186
+ ]
235
187
  ]
236
188
  },
237
189
  "engines": {
@@ -251,5 +203,28 @@
251
203
  "jiraAppend": "]"
252
204
  }
253
205
  },
254
- "packageManager": "pnpm@10.33.4"
255
- }
206
+ "scripts": {
207
+ "dev": "cross-env USE_SANDBOX=true vite",
208
+ "build": "pnpm run typecheck && pnpm run build:package && pnpm run build:webcomponent && pnpm run build:types && pnpm run build:aliases && pnpm run build:cli",
209
+ "build:package": "vite build -m production",
210
+ "build:webcomponent": "cross-env VITE_AS_WEB_COMPONENT='true' vite build -m production",
211
+ "build:types": "vue-tsc -p './tsconfig.build.json' --emitDeclarationOnly",
212
+ "build:aliases": "tsc-alias -p './tsconfig.build.json'",
213
+ "build:cli": "tsc -p './tsconfig.cli.build.json' && node ./scripts/build-cli-assets.js && node ./scripts/generate-kongctl-manifest.mjs",
214
+ "generate:kongctl-manifest": "pnpm run build:cli",
215
+ "build:analyzer": "BUILD_VISUALIZER='true' vite build -m production",
216
+ "build:sandbox": "cross-env USE_SANDBOX=true vite build -m production",
217
+ "preview": "pnpm run build:sandbox && cross-env USE_SANDBOX=true vite preview",
218
+ "typecheck": "vue-tsc -p './tsconfig.build.json' --noEmit && pnpm run typecheck:cli && pnpm run typecheck:scripts",
219
+ "typecheck:cli": "tsc -p './tsconfig.cli.json' --noEmit",
220
+ "typecheck:scripts": "tsc -p './tsconfig.scripts.json' --noEmit",
221
+ "stylelint": "stylelint --allow-empty-input './src/**/*.{css,scss,vue}'",
222
+ "stylelint:fix": "stylelint --allow-empty-input './src/**/*.{css,scss,sass,vue}' --fix",
223
+ "lint": "eslint",
224
+ "lint:fix": "eslint --fix",
225
+ "test": "cross-env FORCE_COLOR=1 vitest run",
226
+ "test:open": "cross-env FORCE_COLOR=1 vitest --ui",
227
+ "semantic-release": "semantic-release",
228
+ "commit": "cz"
229
+ }
230
+ }