@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 +27 -0
- package/dist/cli/commands/preview.js +134 -0
- package/dist/cli/commands/preview.js.map +1 -0
- package/dist/cli/find-open-port.js +27 -0
- package/dist/cli/find-open-port.js.map +1 -0
- package/dist/cli/index.js +11 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/parse-int-flag.js +28 -0
- package/dist/cli/parse-int-flag.js.map +1 -0
- package/dist/cli/preview-page/client.js +89 -0
- package/dist/cli/preview-page/client.js.map +1 -0
- package/dist/cli/preview-page/index.html +34 -0
- package/dist/cli/program.js +105 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/resolve-render-options.js +41 -0
- package/dist/cli/resolve-render-options.js.map +1 -0
- package/dist/cli/resolve-spec-source.js +20 -0
- package/dist/cli/resolve-spec-source.js.map +1 -0
- package/dist/cli/server.js +137 -0
- package/dist/cli/server.js.map +1 -0
- package/dist/cli/spec-source.js +23 -0
- package/dist/cli/spec-source.js.map +1 -0
- package/dist/cli/ui.js +107 -0
- package/dist/cli/ui.js.map +1 -0
- package/package.json +56 -81
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
|
+
"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
|
-
|
|
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
|
-
"
|
|
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
|
+
}
|