@vivero/stoma-cli 0.1.0-rc.4 → 0.1.0-rc.7
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/CHANGELOG.md +144 -0
- package/dist/bin.js.map +1 -1
- package/package.json +3 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# @vivero/stoma-cli
|
|
2
|
+
|
|
3
|
+
## 0.1.0-rc.7
|
|
4
|
+
### Patch Changes
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
- [`cd6bd3a`](https://github.com/vivero-dev/stoma/commit/cd6bd3a619bc30c3369cd9e5546c6ceed537ceaa) Thanks [@JonathanBennett](https://github.com/JonathanBennett)! - ### @vivero/stoma
|
|
9
|
+
|
|
10
|
+
**Exports & TypeDoc**
|
|
11
|
+
|
|
12
|
+
- Export missing config types from the barrel: `MockConfig`, `ProxyPolicyConfig`, `ApiKeyAuthConfig`, `BasicAuthConfig`, `CorsConfig`, `RegexPatternRule`, `ValidationResult`, `ExtractClientIpOptions`, and `RequiredKeys`
|
|
13
|
+
- Export `ValidationResult` interface from the `request-validation` policy (was internal-only)
|
|
14
|
+
- Export `RequiredKeys` utility type from the SDK (marked `@internal`)
|
|
15
|
+
- Add `tsdoc.json` so TypeDoc recognises `@security` and `@module` block tags without warnings
|
|
16
|
+
- Fix broken `{@link Response}` reference in `errorToResponse` JSDoc (changed to inline code)
|
|
17
|
+
- Fix `{@link noopDebugLogger}` reference in `policyDebug` JSDoc (changed to prose)
|
|
18
|
+
- Update `README.md` architecture link from local `ARCHITECTURE.md` to the docs site
|
|
19
|
+
|
|
20
|
+
**Examples**
|
|
21
|
+
|
|
22
|
+
- Fix all upstream targets to use the correct origin (`https://stoma.vivero.dev`) with explicit `rewritePath` functions — previously some examples pointed at a non-existent `/demo-api` path on the upstream
|
|
23
|
+
- Rework `basic/gateway.ts` to use a catch-all `/*` route with better inline documentation and playground-friendly paths
|
|
24
|
+
- Fix `route-scopes` example: rename `/projects/*` to `/products/*` to match the demo API
|
|
25
|
+
- Fix `shadow-release` example: add `rewritePath` for the primary upstream
|
|
26
|
+
- Fix `cache-resilience` and `webhook-firewall` examples with correct path rewrites
|
|
27
|
+
- Add biome suppression comments for legitimate `any` usage in the `cloudflare-worker` browser rendering example (no DOM lib available in the Workers TS config)
|
|
28
|
+
|
|
29
|
+
**Lint & Types**
|
|
30
|
+
|
|
31
|
+
- Suppress `noBannedTypes` lint warning on the `RequiredKeys` utility type (`{}` is intentional for optional-key detection)
|
|
32
|
+
- Suppress `noExplicitAny` in the merge config test (stub adapter in test)
|
|
33
|
+
- Bump wrangler dev dependency from `^4.65.0` to `^4.68.0`
|
|
34
|
+
|
|
35
|
+
### @vivero/stoma-cli
|
|
36
|
+
|
|
37
|
+
- Reformat test assertions in `resolve-security.test.ts` and `wrap.test.ts` for consistency
|
|
38
|
+
|
|
39
|
+
### Docs site
|
|
40
|
+
|
|
41
|
+
- Add `llms.txt` support with four endpoints: `/llms.txt` (index with section links), `/llms-full.txt` (complete docs), `/llms-small.txt` (abridged, no API reference), and `/llms/{section}.txt` (10 per-section pages)
|
|
42
|
+
- Add `robots.txt` with sitemap reference
|
|
43
|
+
- Restore the Node.js deployment guide (`deploy/node/index.mdx`) that went missing
|
|
44
|
+
- Fix OG image meta tags to use absolute URLs instead of relative paths
|
|
45
|
+
- Add `og:image:type` meta tag
|
|
46
|
+
- Fill in `site.webmanifest` name fields (`Stoma — Declarative API Gateway`)
|
|
47
|
+
- Move Monaco editor TS compiler config to `beforeMount` so diagnostics are correct on first load
|
|
48
|
+
- Suppress top-level await false-positive diagnostics (TS 1375 & 1378) in the playground editor
|
|
49
|
+
- Register `@security` as a custom TypeDoc block tag in `astro.config.mjs`
|
|
50
|
+
- Updated dependencies [[`cd6bd3a`](https://github.com/vivero-dev/stoma/commit/cd6bd3a619bc30c3369cd9e5546c6ceed537ceaa)]:
|
|
51
|
+
- @vivero/stoma@0.1.0-rc.11
|
|
52
|
+
|
|
53
|
+
## 0.1.0-rc.6
|
|
54
|
+
### Patch Changes
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
- [`c6f0576`](https://github.com/vivero-dev/stoma/commit/c6f05763d6f2ba3e8a3c6845258d57e6f8c1d693) Thanks [@JonathanBennett](https://github.com/JonathanBennett)! - fix: resolve `workspace:*` protocols in published packages
|
|
59
|
+
|
|
60
|
+
Replaces `changeset publish` with a custom publish script (`scripts/publish.mjs`) that uses `yarn pack` to resolve `workspace:*` protocols and apply `publishConfig` overrides, then `npm publish <tarball>` for OIDC trusted publishing with provenance. The prepack/postpack workaround scripts have been removed.
|
|
61
|
+
- Updated dependencies [[`c6f0576`](https://github.com/vivero-dev/stoma/commit/c6f05763d6f2ba3e8a3c6845258d57e6f8c1d693)]:
|
|
62
|
+
- @vivero/stoma@0.1.0-rc.10
|
|
63
|
+
|
|
64
|
+
## 0.1.0-rc.5
|
|
65
|
+
### Patch Changes
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
- [`c6f0576`](https://github.com/vivero-dev/stoma/commit/c6f05763d6f2ba3e8a3c6845258d57e6f8c1d693) Thanks [@JonathanBennett](https://github.com/JonathanBennett)! - fix: publish via `yarn npm publish` to correctly resolve `workspace:*` protocols
|
|
70
|
+
|
|
71
|
+
Replaces `changeset publish` (which used `npm publish <dir>` and never resolved workspace protocols) with a custom publish script that runs `yarn npm publish` from each package directory. Yarn natively handles `workspace:*` resolution and `publishConfig` field overrides, so the prepack/postpack workaround scripts have been removed.
|
|
72
|
+
- Updated dependencies [[`c6f0576`](https://github.com/vivero-dev/stoma/commit/c6f05763d6f2ba3e8a3c6845258d57e6f8c1d693)]:
|
|
73
|
+
- @vivero/stoma@0.1.0-rc.9
|
|
74
|
+
|
|
75
|
+
## 0.1.0-rc.4
|
|
76
|
+
### Patch Changes
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
- [`cfa0a04`](https://github.com/vivero-dev/stoma/commit/cfa0a040eda481782cc34131b2e4b99015e74cea) Thanks [@JonathanBennett](https://github.com/JonathanBennett)! - Add CLI test coverage and split monorepo/package READMEs
|
|
81
|
+
|
|
82
|
+
### CLI tests
|
|
83
|
+
|
|
84
|
+
Add 26 tests across 3 files covering the playground wrapper, OAuth relay, gateway resolution security boundary, and TypeScript transpilation end-to-end:
|
|
85
|
+
|
|
86
|
+
- **Playground routing invariants**: `/__playground` returns HTML, `/registry` returns exact JSON, non-playground paths always pass through to the gateway
|
|
87
|
+
- **Send proxy contract**: response shape (`status`, `statusText`, `headers`, `body`, `elapsed`), header forwarding, error handling, body stripping for GET
|
|
88
|
+
- **OAuth relay**: interception only fires for navigation requests to callback routes with query params; XSS prevention via `<` escaping
|
|
89
|
+
- **Security boundary**: remote URLs without `--trust-remote` always reject with security warning
|
|
90
|
+
- **TS transpilation e2e**: loads real `.ts` fixture through esbuild, verifies the gateway's fetch handler produces correct responses
|
|
91
|
+
|
|
92
|
+
### README
|
|
93
|
+
|
|
94
|
+
- Root README is now a monorepo landing page with package table, quick start, and dev/release instructions
|
|
95
|
+
- Gateway package (`packages/gateway`) retains the full library README for npm
|
|
96
|
+
- Updated dependencies [[`cfa0a04`](https://github.com/vivero-dev/stoma/commit/cfa0a040eda481782cc34131b2e4b99015e74cea)]:
|
|
97
|
+
- @vivero/stoma@0.1.0-rc.8
|
|
98
|
+
|
|
99
|
+
## 0.1.0-rc.3
|
|
100
|
+
### Patch Changes
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
- [`a414008`](https://github.com/vivero-dev/stoma/commit/a41400882fbe51f9f5ac7f623dec52f0e2ca1dd6) Thanks [@JonathanBennett](https://github.com/JonathanBennett)! - Fix npm publish pipeline to correctly resolve workspace protocols and apply publishConfig overrides
|
|
105
|
+
|
|
106
|
+
Adds prepack/postpack lifecycle scripts that prepare package.json for `npm publish` by resolving `workspace:*` to real versions and applying `publishConfig` field overrides (main, types, exports, bin). This replaces the previous `@changesets/cli` yarn patch approach and restores compatibility with GitHub Actions OIDC tokens for npm authentication and provenance.
|
|
107
|
+
- Updated dependencies [[`a414008`](https://github.com/vivero-dev/stoma/commit/a41400882fbe51f9f5ac7f623dec52f0e2ca1dd6)]:
|
|
108
|
+
- @vivero/stoma@0.1.0-rc.7
|
|
109
|
+
|
|
110
|
+
## 0.1.0-rc.2
|
|
111
|
+
### Patch Changes
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
- [`ca21e7a`](https://github.com/vivero-dev/stoma/commit/ca21e7ad128b83c6f3398d30098644761e0e3501) Thanks [@JonathanBennett](https://github.com/JonathanBennett)! - Fix esbuild resolution of transitive dependencies when bundling gateway files
|
|
116
|
+
|
|
117
|
+
The CLI's TypeScript transpiler now includes the `node_modules` of `@vivero/stoma` itself in esbuild's resolve paths, so transitive dependencies like `@vivero/stoma-core` are found even when not hoisted to the top-level `node_modules`.
|
|
118
|
+
|
|
119
|
+
## 0.1.0-rc.1
|
|
120
|
+
### Minor Changes
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
- [`44f78e4`](https://github.com/vivero-dev/stoma/commit/44f78e462b92d6f719c6622e3e1126a6d343920f) Thanks [@JonathanBennett](https://github.com/JonathanBennett)! - Initial release of `@vivero/stoma-cli`
|
|
125
|
+
|
|
126
|
+
Local development server and interactive playground for Stoma API gateways.
|
|
127
|
+
|
|
128
|
+
### Features
|
|
129
|
+
|
|
130
|
+
- **`stoma run <file>`**: Load a gateway file (TypeScript or JS) and serve it on a local Node.js HTTP server via `@hono/node-server`. TypeScript files are automatically bundled with esbuild — no build step or `tsconfig` required.
|
|
131
|
+
- **Remote gateway files**: `stoma run https://... --trust-remote` fetches, transpiles, and serves a gateway from a URL.
|
|
132
|
+
- **Flexible export resolution**: Supports `export default createGateway(...)`, async factory functions, `createPlaygroundGateway` named exports, and bare Hono apps.
|
|
133
|
+
- **Interactive playground** (`--playground`): Serves a browser UI at `/__playground` with a two-pane layout — request builder on the left, response viewer on the right. Includes:
|
|
134
|
+
- Route chips for quick path selection (wildcard paths like `/api/*` display as `/api/` to avoid 404s)
|
|
135
|
+
- Structured header key-value table with add/remove per row
|
|
136
|
+
- Response tabs: Pretty (syntax-highlighted JSON), Raw, and Headers (with count badge)
|
|
137
|
+
- Token store: OAuth tokens persisted in localStorage, auto-detected from response bodies, and auto-applied as `Authorization: Bearer` headers
|
|
138
|
+
- OAuth popup flow: redirect detection, authorization popup, callback relay via `postMessage`, and a banner prompting the user to complete the exchange
|
|
139
|
+
- **CLI options**: `--port`, `--host`, `--debug` (gateway debug logging), `--verbose` (CLI output), graceful shutdown on SIGINT/SIGTERM.
|
|
140
|
+
|
|
141
|
+
### Patch Changes
|
|
142
|
+
|
|
143
|
+
- Updated dependencies [[`c14161a`](https://github.com/vivero-dev/stoma/commit/c14161a0846ef1991bb0fa71337435e6366579a7)]:
|
|
144
|
+
- @vivero/stoma@0.1.0-rc.6
|
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/commands/run.ts","../src/gateway/resolve.ts","../src/playground/html.ts","../src/playground/oauth-relay.ts","../src/playground/wrap.ts","../src/server/serve.ts","../src/utils/logger.ts","../src/utils/version.ts","../src/bin.ts"],"sourcesContent":["import { Builtins, Cli } from \"clipanion\";\nimport { RunCommand } from \"./commands/index.js\";\nimport { getVersion } from \"./utils/version.js\";\n\nexport function createCli() {\n const cli = new Cli({\n binaryLabel: \"stoma\",\n binaryName: \"stoma\",\n binaryVersion: getVersion(),\n });\n\n cli.register(Builtins.HelpCommand);\n cli.register(Builtins.VersionCommand);\n cli.register(RunCommand);\n\n return cli;\n}\n\nexport async function runCli(argv: string[]) {\n const cli = createCli();\n await cli.runExit(argv);\n}\n","import path from \"node:path\";\nimport { Command, Option } from \"clipanion\";\nimport { resolveGateway } from \"../gateway/resolve.js\";\nimport { wrapWithPlayground } from \"../playground/wrap.js\";\nimport { startServer } from \"../server/serve.js\";\nimport { createLogger } from \"../utils/logger.js\";\n\nexport class RunCommand extends Command {\n static override paths = [[\"run\"]];\n\n static override usage = Command.Usage({\n description: \"Start a local HTTP server for a Stoma gateway file\",\n details: `\n Loads a compiled gateway JS file (or TypeScript with tsx) and serves it\n on a local Node.js HTTP server.\n\n The file must export a gateway instance, a factory function, or a\n \\`createPlaygroundGateway\\` named export.\n `,\n examples: [\n [\"Run a compiled gateway file\", \"stoma run ./my-gateway.js\"],\n [\"Run on a custom port\", \"stoma run ./my-gateway.js --port 3000\"],\n [\"Run with debug logging\", \"stoma run ./my-gateway.js --debug\"],\n [\n \"Run a remote gateway file\",\n \"stoma run https://example.com/gateway.ts --trust-remote\",\n ],\n ],\n });\n\n file = Option.String({ required: true });\n\n port = Option.String(\"--port,-p\", \"8787\", {\n description: \"Port to listen on (default: 8787)\",\n });\n\n host = Option.String(\"--host,-H\", \"localhost\", {\n description: \"Hostname to bind to (default: localhost)\",\n });\n\n debug = Option.Boolean(\"--debug,-d\", false, {\n description: \"Enable gateway debug logging\",\n });\n\n verbose = Option.Boolean(\"--verbose,-v\", false, {\n description: \"Verbose CLI output\",\n });\n\n playground = Option.Boolean(\"--playground\", false, {\n description: \"Serve interactive playground UI at /__playground\",\n });\n\n trustRemote = Option.Boolean(\"--trust-remote\", false, {\n description:\n \"Allow loading gateway files from remote URLs (downloads and executes code — use only with trusted sources)\",\n });\n\n async execute() {\n const log = createLogger(this.verbose);\n const isRemote =\n this.file.startsWith(\"http://\") || this.file.startsWith(\"https://\");\n const filePath = isRemote ? this.file : path.resolve(this.file);\n const portNum = parseInt(this.port, 10);\n\n if (Number.isNaN(portNum) || portNum < 0 || portNum > 65535) {\n log.error(`Invalid port: ${this.port}`);\n return 1;\n }\n\n log.info(`Loading gateway from ${filePath}`);\n\n let gateway;\n try {\n gateway = await resolveGateway(filePath, {\n debug: this.debug,\n trustRemote: this.trustRemote,\n });\n } catch (err) {\n log.error(\n `Failed to load gateway: ${err instanceof Error ? err.message : String(err)}`\n );\n return 1;\n }\n\n log.info(\n `Gateway \"${gateway.name}\" loaded (${gateway.routeCount} route${gateway.routeCount === 1 ? \"\" : \"s\"})`\n );\n\n if (this.verbose && gateway._registry) {\n for (const route of gateway._registry.routes) {\n const methods = Array.isArray(route.methods)\n ? route.methods.join(\",\")\n : \"ALL\";\n log.verbose(` ${methods.padEnd(8)} ${route.path}`);\n }\n }\n\n try {\n const fetch =\n this.playground && gateway._registry\n ? wrapWithPlayground(gateway.app.fetch, gateway._registry)\n : gateway.app.fetch;\n\n const server = await startServer({\n fetch,\n port: portNum,\n hostname: this.host,\n });\n\n log.info(\n `Gateway \"${gateway.name}\" listening on http://${this.host}:${portNum}`\n );\n\n if (this.playground) {\n log.info(`Playground: http://${this.host}:${portNum}/__playground`);\n }\n\n if (gateway._registry) {\n for (const route of gateway._registry.routes) {\n const methods = Array.isArray(route.methods)\n ? route.methods.join(\",\")\n : \"ALL\";\n log.info(` ${methods.padEnd(8)} ${route.path}`);\n }\n }\n\n await new Promise<void>((resolve) => {\n let shuttingDown = false;\n\n const shutdown = () => {\n if (shuttingDown) {\n log.info(\"\\nForce exit\");\n process.exit(1);\n }\n shuttingDown = true;\n log.info(\"\\nShutting down...\");\n\n // Force exit after 3s if graceful shutdown stalls\n const forceExit = setTimeout(() => {\n log.error(\"Graceful shutdown timed out, forcing exit\");\n process.exit(1);\n }, 3_000);\n forceExit.unref();\n\n server.closeAllConnections();\n server.close(() => {\n clearTimeout(forceExit);\n resolve();\n });\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n });\n\n // Clipanion's runExit() only sets process.exitCode — it doesn't\n // call process.exit(). If @hono/node-server leaves lingering handles\n // the event loop never drains and the process hangs.\n return process.exit(0) as never;\n } catch (err) {\n log.error(\n `Server error: ${err instanceof Error ? err.message : String(err)}`\n );\n return 1;\n }\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { mkdtemp, rm, unlink, writeFile } from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { build } from \"esbuild\";\nimport type { GatewayInstance } from \"./types.js\";\n\nexport interface ResolveOptions {\n debug?: boolean;\n /** Allow fetching gateway files from remote URLs. */\n trustRemote?: boolean;\n}\n\nconst TS_EXTENSIONS = [\".ts\", \".tsx\", \".mts\"];\n\n/**\n * Dynamic-import a gateway file and resolve its export to a GatewayInstance.\n *\n * TypeScript files are automatically transpiled via esbuild before importing.\n * Remote URLs (http/https) are supported when `trustRemote` is set.\n */\nexport async function resolveGateway(\n filePathOrUrl: string,\n options: ResolveOptions = {}\n): Promise<GatewayInstance> {\n if (isRemoteUrl(filePathOrUrl)) {\n if (!options.trustRemote) {\n throw new Error(\n \"Remote URLs require the --trust-remote flag.\\n\" +\n \"This will download and execute code from the URL. Only use with trusted sources.\"\n );\n }\n return resolveRemoteGateway(filePathOrUrl, options);\n }\n\n if (!existsSync(filePathOrUrl)) {\n throw new Error(`File not found: ${filePathOrUrl}`);\n }\n\n return resolveLocalFile(filePathOrUrl, options);\n}\n\nasync function resolveLocalFile(\n filePath: string,\n options: ResolveOptions\n): Promise<GatewayInstance> {\n const isTypeScript = TS_EXTENSIONS.some((ext) => filePath.endsWith(ext));\n\n let mod: Record<string, unknown>;\n\n if (isTypeScript) {\n mod = await importTypeScript(filePath);\n } else {\n try {\n mod = await import(pathToFileURL(filePath).href);\n } catch (err) {\n throw new Error(\n `Failed to import: ${filePath}\\n${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n return resolveFromModule(mod, options);\n}\n\nfunction isRemoteUrl(input: string): boolean {\n return input.startsWith(\"http://\") || input.startsWith(\"https://\");\n}\n\n/**\n * Fetch a remote gateway file to a temp directory, resolve it, then clean up.\n */\nasync function resolveRemoteGateway(\n url: string,\n options: ResolveOptions\n): Promise<GatewayInstance> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(\n `Failed to fetch remote gateway: ${res.status} ${res.statusText}`\n );\n }\n\n const filename = filenameFromUrl(url, res.headers.get(\"content-type\"));\n const tmpDir = await mkdtemp(path.join(tmpdir(), \"stoma-remote-\"));\n const tmpPath = path.join(tmpDir, filename);\n\n try {\n await writeFile(tmpPath, await res.text(), \"utf-8\");\n return await resolveLocalFile(tmpPath, options);\n } finally {\n await unlink(tmpPath).catch(() => {});\n await rm(tmpDir, { recursive: true, force: true }).catch(() => {});\n }\n}\n\n/**\n * Derive a filename (with extension) from a URL path and optional content-type.\n * Falls back to `.ts` so esbuild can transpile unknown sources.\n */\nfunction filenameFromUrl(url: string, contentType: string | null): string {\n const pathname = new URL(url).pathname;\n const basename = path.basename(pathname);\n\n // If the URL has a recognisable extension, use it\n if (/\\.(ts|tsx|mts|js|mjs|cjs)$/.test(basename)) {\n return basename;\n }\n\n // Infer from content-type\n if (contentType?.includes(\"javascript\")) {\n return \"gateway.mjs\";\n }\n if (contentType?.includes(\"typescript\")) {\n return \"gateway.ts\";\n }\n\n // Default to TypeScript — esbuild can handle both TS and JS\n return basename ? `${basename}.ts` : \"gateway.ts\";\n}\n\n/**\n * Get the node_modules search paths from the CLI's own install location.\n * Uses Node's own resolution algorithm via createRequire, so it works\n * correctly regardless of monorepo hoisting, Yarn PnP, or flat installs.\n */\nfunction getCliNodePaths(): string[] {\n const require = createRequire(import.meta.url);\n const paths = (require.resolve.paths(\"@vivero/stoma\") ?? []).filter(\n existsSync\n );\n\n // Also include the node_modules of @vivero/stoma itself, so transitive\n // deps like @vivero/stoma-core are resolvable even if not hoisted.\n try {\n const stomaEntry = require.resolve(\"@vivero/stoma\");\n const stomaDir = path.dirname(stomaEntry);\n // Walk up to find the package root (contains package.json)\n let dir = stomaDir;\n while (dir !== path.dirname(dir)) {\n if (existsSync(path.join(dir, \"package.json\"))) {\n const nested = path.join(dir, \"node_modules\");\n if (existsSync(nested) && !paths.includes(nested)) {\n paths.unshift(nested);\n }\n break;\n }\n dir = path.dirname(dir);\n }\n } catch {\n // @vivero/stoma not resolvable — will error later during build\n }\n\n return paths;\n}\n\n/**\n * Transpile a TypeScript file with esbuild and import the result.\n *\n * Bundles all dependencies into a self-contained JS file so the output\n * runs without any npm install — matching how the docs editor compiles\n * gateway configs. The temp file is written next to the source and\n * cleaned up after import.\n *\n * Uses `nodePaths` from the CLI's own install so that `@vivero/stoma`\n * and `hono` are always available, even when the gateway file lives\n * outside any project (e.g. ~/Downloads/).\n */\nasync function importTypeScript(\n filePath: string\n): Promise<Record<string, unknown>> {\n const tmpFile = filePath.replace(/\\.tsx?$/, `.stoma-tmp-${Date.now()}.mjs`);\n\n try {\n const result = await build({\n entryPoints: [filePath],\n bundle: true,\n format: \"esm\",\n platform: \"node\",\n target: \"node20\",\n nodePaths: getCliNodePaths(),\n write: false,\n logLevel: \"silent\",\n });\n\n if (!result.outputFiles?.length) {\n throw new Error(\"TypeScript transpilation produced no output\");\n }\n\n await writeFile(tmpFile, result.outputFiles[0].text, \"utf-8\");\n return await import(pathToFileURL(tmpFile).href);\n } catch (err) {\n if (\n err instanceof Error &&\n err.message.includes(\"TypeScript transpilation\")\n ) {\n throw err;\n }\n throw new Error(\n `Failed to transpile TypeScript: ${filePath}\\n${err instanceof Error ? err.message : String(err)}`\n );\n } finally {\n await unlink(tmpFile).catch(() => {});\n }\n}\n\n/**\n * Resolve a GatewayInstance from a module's exports.\n *\n * Resolution order:\n * 1. `mod.createPlaygroundGateway` (function) — backward compat with editor snippets\n * 2. `mod.default` (function) — call as async factory\n * 3. `mod.default` (object with `.app` + `._registry`) — GatewayInstance directly\n * 4. `mod.default` (object with `.fetch`) — bare Hono app, wrap in minimal instance\n * 5. Throw descriptive error\n */\nexport async function resolveFromModule(\n mod: Record<string, unknown>,\n _options: ResolveOptions = {}\n): Promise<GatewayInstance> {\n // 1. Named export: createPlaygroundGateway()\n if (typeof mod.createPlaygroundGateway === \"function\") {\n const result = await mod.createPlaygroundGateway();\n return asGatewayInstance(result, \"createPlaygroundGateway()\");\n }\n\n // 2. Default export: factory function\n if (typeof mod.default === \"function\") {\n const result = await mod.default();\n return asGatewayInstance(result, \"default()\");\n }\n\n // 3. Default export: GatewayInstance directly\n if (isGatewayInstance(mod.default)) {\n return mod.default;\n }\n\n // 4. Default export: bare Hono app (has .fetch)\n if (isHonoApp(mod.default)) {\n return {\n app: mod.default,\n name: \"unnamed-gateway\",\n routeCount: 0,\n _registry: { routes: [], policies: [], gatewayName: \"unnamed-gateway\" },\n };\n }\n\n throw new Error(\n \"Could not resolve a gateway from the module exports.\\n\\n\" +\n \"The file must export a gateway in one of these forms:\\n\" +\n \" export default createGateway({ ... })\\n\" +\n \" export default async function() { return createGateway({ ... }) }\\n\" +\n \" export function createPlaygroundGateway() { return createGateway({ ... }) }\\n\" +\n \" export default app // a Hono app with a .fetch method\"\n );\n}\n\nfunction isGatewayInstance(value: unknown): value is GatewayInstance {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"app\" in value &&\n \"_registry\" in value\n );\n}\n\nfunction isHonoApp(\n value: unknown\n): value is { fetch: (req: Request) => Response | Promise<Response> } {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"fetch\" in value &&\n typeof (value as Record<string, unknown>).fetch === \"function\"\n );\n}\n\nfunction asGatewayInstance(value: unknown, source: string): GatewayInstance {\n if (isGatewayInstance(value)) {\n return value;\n }\n if (isHonoApp(value)) {\n return {\n app: value,\n name: \"unnamed-gateway\",\n routeCount: 0,\n _registry: { routes: [], policies: [], gatewayName: \"unnamed-gateway\" },\n };\n }\n throw new Error(\n `${source} did not return a valid gateway instance. ` +\n \"Expected an object with .app and ._registry properties, or a Hono app with a .fetch method.\"\n );\n}\n","import type { GatewayRegistry } from \"../gateway/types.js\";\n\nexport function playgroundHtml(registry: GatewayRegistry): string {\n const registryJson = JSON.stringify(registry).replace(/</g, \"\\\\u003c\");\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>${esc(registry.gatewayName)} — Stoma Playground</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n:root{\n --bg:#1e1e1e;--bg2:#252526;--bg3:#2d2d2d;\n --border:#3c3c3c;--text:#d4d4d4;--muted:#969696;--dim:#555;\n --teal:#4ec9b0;--blue:#0e639c;--blue2:#1177bb;\n --yellow:#dcdcaa;--red:#f48771;--blue-text:#569cd6;\n --mono:\"Cascadia Code\",\"Fira Code\",\"JetBrains Mono\",\"SF Mono\",monospace;\n --sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;\n}\nhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--sans)}\nbody{display:flex;flex-direction:column;overflow:hidden}\n\n/* Header */\n.hdr{display:flex;align-items:center;justify-content:space-between;height:3rem;padding:0 1rem;background:var(--bg2);border-bottom:1px solid var(--border);flex-shrink:0}\n.hdr-left{display:flex;align-items:center;gap:.5rem}\n.logo{font-weight:700;font-size:.875rem;color:var(--teal);text-decoration:none}\n.hdr-div{color:var(--dim);font-size:.875rem}\n.hdr-title{font-size:.8125rem;color:var(--muted)}\n\n/* Routes bar */\n.routes-bar{padding:.5rem 1rem;display:flex;flex-wrap:wrap;gap:.25rem;flex-shrink:0;border-bottom:1px solid var(--border);background:var(--bg)}\n.chip{all:unset;display:inline-flex;align-items:center;gap:.375rem;height:1.5rem;padding:0 .5rem;border-radius:.25rem;border:1px solid var(--border);background:var(--bg3);font-family:var(--mono);font-size:.6875rem;cursor:pointer;user-select:none;transition:border-color .15s,background .15s}\n.chip:hover{border-color:var(--blue);background:#333}\n.method{font-size:.5625rem;font-weight:700;letter-spacing:.04em;padding:.0625rem .25rem;border-radius:.125rem}\n.m-get{background:var(--teal);color:var(--bg)}\n.m-post{background:var(--blue-text);color:#fff}\n.m-put,.m-patch{background:var(--yellow);color:var(--bg)}\n.m-delete{background:var(--red);color:var(--bg)}\n\n/* Workspace — two pane split */\n.workspace{flex:1;display:flex;gap:.75rem;padding:.75rem 1rem;overflow:hidden}\n.pane-left{flex:1;display:flex;flex-direction:column;gap:.625rem;overflow-y:auto;min-width:0}\n.pane-right{flex:1;display:flex;flex-direction:column;min-width:0;overflow:hidden}\n\n/* Token Store */\n.token-store{border:1px solid var(--border);border-radius:.25rem;background:var(--bg2);overflow:hidden;flex-shrink:0}\n.token-header{display:flex;align-items:center;justify-content:space-between;padding:.375rem .625rem;background:var(--bg3);border-bottom:1px solid var(--border)}\n.token-title{font-size:.625rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}\n.token-item{display:flex;align-items:center;gap:.375rem;padding:.25rem .625rem;border-bottom:1px solid rgba(60,60,60,.2)}\n.token-item:last-child{border-bottom:none}\n.token-name{font-size:.6875rem;font-family:var(--mono);color:var(--teal);min-width:6rem;flex-shrink:0}\n.token-val{font-size:.6875rem;font-family:var(--mono);color:var(--dim);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.token-actions{display:flex;gap:.25rem;flex-shrink:0}\n.token-btn{all:unset;display:inline-flex;align-items:center;height:1.25rem;padding:0 .375rem;border-radius:.1875rem;font-size:.5625rem;font-weight:600;cursor:pointer;transition:background .15s;user-select:none}\n.token-btn-use{background:var(--blue);color:#fff}\n.token-btn-use:hover{background:var(--blue2)}\n.token-btn-del{background:var(--bg3);color:var(--muted);border:1px solid var(--border)}\n.token-btn-del:hover{background:#3a3a3a}\n\n/* Form */\n.form-row{display:flex;gap:.375rem}\n.sel{width:5.5rem;height:1.75rem;padding:0 .375rem;border-radius:.25rem;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:.75rem;font-family:var(--mono);cursor:pointer;outline:none;flex-shrink:0}\n.sel:focus{border-color:var(--blue)}\n.inp{flex:1;height:1.75rem;padding:0 .5rem;border-radius:.25rem;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:.75rem;font-family:var(--mono);outline:none;min-width:0}\n.inp:focus{border-color:var(--blue)}\n.btn{all:unset;display:inline-flex;align-items:center;height:1.75rem;padding:0 .75rem;border-radius:.25rem;background:var(--blue);color:#fff;font-size:.75rem;font-weight:600;cursor:pointer;transition:background .15s;user-select:none;white-space:nowrap}\n.btn:hover{background:var(--blue2)}\n.btn:disabled{opacity:.4;cursor:not-allowed}\n\n.form-sections{display:flex;flex-direction:column;gap:.375rem;margin-top:.375rem}\n.field{display:flex;flex-direction:column;gap:.125rem}\n.lbl{font-size:.5625rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}\n.lbl-row{display:flex;align-items:center;justify-content:space-between}\n.sm-btn{all:unset;font-size:.5625rem;font-weight:600;color:var(--blue-text);cursor:pointer;user-select:none}\n.sm-btn:hover{text-decoration:underline}\n.ta{width:100%;padding:.375rem .5rem;border-radius:.25rem;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:.6875rem;font-family:var(--mono);line-height:1.4;resize:vertical;outline:none;flex:1;min-height:3.5rem}\n.ta:focus{border-color:var(--blue)}\n.ta::placeholder{color:var(--dim)}\n\n/* Header rows */\n.hdr-rows{display:flex;flex-direction:column;gap:.25rem}\n.hdr-row{display:flex;gap:.25rem;align-items:center}\n.hdr-input{height:1.5rem;padding:0 .375rem;border-radius:.25rem;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:.6875rem;font-family:var(--mono);outline:none}\n.hdr-input:focus{border-color:var(--blue)}\n.hdr-name{width:10rem;flex-shrink:0}\n.hdr-val{flex:1;min-width:0}\n.hdr-del{all:unset;display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:.25rem;font-size:.75rem;color:var(--dim);cursor:pointer;flex-shrink:0}\n.hdr-del:hover{color:var(--red);background:rgba(244,135,113,.1)}\n\n/* Callback Banner */\n.callback-banner{padding:.625rem .75rem;background:#1a2332;border:1px solid var(--blue-text);border-radius:.25rem;font-size:.75rem;color:var(--blue-text);flex-shrink:0}\n.callback-banner strong{color:#fff}\n\n/* Response panel */\n.res{flex:1;display:flex;flex-direction:column;border:1px solid var(--border);border-radius:.25rem;background:var(--bg2);overflow:hidden}\n.res-empty{padding:2rem;text-align:center;color:var(--dim);font-size:.8125rem}\n.res-top{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:var(--bg2);border-bottom:1px solid var(--border);flex-shrink:0}\n.badge{display:inline-flex;align-items:center;height:1.25rem;padding:0 .375rem;border-radius:.1875rem;font-family:var(--mono);font-size:.6875rem;font-weight:700;line-height:1}\n.b-2xx{background:var(--teal);color:var(--bg)}\n.b-3xx{background:var(--blue-text);color:#fff}\n.b-4xx{background:var(--yellow);color:var(--bg)}\n.b-5xx{background:var(--red);color:var(--bg)}\n.timing{font-size:.75rem;font-family:var(--mono);color:var(--muted)}\n\n/* Response tabs */\n.res-tabs{display:flex;background:var(--bg3);border-bottom:1px solid var(--border);flex-shrink:0}\n.res-tab{all:unset;padding:.375rem .75rem;font-size:.6875rem;font-weight:600;color:var(--muted);cursor:pointer;border-bottom:2px solid transparent;transition:color .15s,border-color .15s;user-select:none}\n.res-tab:hover{color:var(--text)}\n.res-tab.active{color:var(--teal);border-bottom-color:var(--teal)}\n.res-tab-count{font-weight:400;color:var(--dim);margin-left:.25rem}\n\n/* Tab content */\n.res-tab-content{flex:1;overflow:auto;display:none}\n.res-tab-content.active{display:block}\n.res-body{padding:.5rem .75rem}\n.res-body pre{font-family:var(--mono);font-size:.6875rem;line-height:1.5;white-space:pre-wrap;word-break:break-word;margin:0}\n.htable{width:100%;border-collapse:collapse;font-size:.6875rem;font-family:var(--mono)}\n.htable td{padding:.25rem .75rem;border-bottom:1px solid rgba(60,60,60,.2);vertical-align:top}\n.htable td:first-child{color:var(--muted);white-space:nowrap;width:1%;padding-right:.375rem}\n.htable td:last-child{word-break:break-all}\n\n.res-error{padding:1rem;color:var(--red);font-family:var(--mono);font-size:.8125rem;text-align:center;word-break:break-word}\n\n/* Redirect banner (OAuth) */\n.res-redirect{padding:.75rem;background:#1a2332;border-bottom:1px solid var(--border);flex-shrink:0}\n.res-redirect-title{font-size:.75rem;font-weight:700;color:var(--blue-text);margin-bottom:.25rem}\n.res-redirect-url{font-size:.6875rem;font-family:var(--mono);color:var(--muted);word-break:break-all;margin-bottom:.5rem}\n.res-redirect-btn{all:unset;display:inline-flex;align-items:center;height:1.5rem;padding:0 .625rem;border-radius:.25rem;background:var(--blue-text);color:#fff;font-size:.6875rem;font-weight:600;cursor:pointer;transition:background .15s;user-select:none}\n.res-redirect-btn:hover{background:#3d8fd6}\n.res-redirect-notice{font-size:.5625rem;color:var(--dim);margin-top:.375rem}\n\n/* JSON syntax highlighting */\n.j-key{color:#9cdcfe}\n.j-str{color:#ce9178}\n.j-num{color:#b5cea8}\n.j-lit{color:#569cd6}\n</style>\n</head>\n<body>\n\n<header class=\"hdr\">\n <div class=\"hdr-left\">\n <span class=\"logo\">Stoma</span>\n <span class=\"hdr-div\">/</span>\n <span class=\"hdr-title\" id=\"gw-name\"></span>\n </div>\n</header>\n\n<div class=\"routes-bar\" id=\"routes\"></div>\n\n<div class=\"workspace\">\n <!-- Left pane: request -->\n <div class=\"pane-left\">\n <div class=\"token-store\" id=\"token-store\" style=\"display:none\">\n <div class=\"token-header\">\n <span class=\"token-title\">Saved Tokens</span>\n <button class=\"sm-btn\" id=\"clear-tokens\" type=\"button\">Clear All</button>\n </div>\n <div id=\"token-list\"></div>\n </div>\n\n <form id=\"form\" autocomplete=\"off\">\n <div class=\"form-row\">\n <select class=\"sel\" id=\"method\">\n <option>GET</option><option>POST</option><option>PUT</option><option>PATCH</option><option>DELETE</option>\n </select>\n <input class=\"inp\" id=\"path\" placeholder=\"/path\" value=\"/\" />\n <button class=\"btn\" type=\"submit\" id=\"send\">Send</button>\n </div>\n <div class=\"form-sections\">\n <div class=\"field\">\n <div class=\"lbl-row\">\n <label class=\"lbl\">Headers</label>\n <button class=\"sm-btn\" id=\"add-header\" type=\"button\">+ Add</button>\n </div>\n <div class=\"hdr-rows\" id=\"header-rows\"></div>\n </div>\n <div class=\"field\" style=\"flex:1;display:flex;flex-direction:column\">\n <label class=\"lbl\" for=\"body\">Body</label>\n <textarea class=\"ta\" id=\"body\" placeholder='{\"key\":\"value\"}'></textarea>\n </div>\n </div>\n </form>\n\n <div class=\"callback-banner\" id=\"callback-banner\" style=\"display:none\"></div>\n </div>\n\n <!-- Right pane: response -->\n <div class=\"pane-right\">\n <div class=\"res\" id=\"response\">\n <div class=\"res-empty\">Send a request to see the response</div>\n </div>\n </div>\n</div>\n\n<script>\nvar registry = ${registryJson};\ndocument.getElementById(\"gw-name\").textContent = registry.gatewayName || \"Playground\";\n\n// ── Route chips ──\n\nvar routesEl = document.getElementById(\"routes\");\nfor (var ri = 0; ri < registry.routes.length; ri++) {\n var route = registry.routes[ri];\n for (var mi = 0; mi < route.methods.length; mi++) {\n (function(m, path) {\n var chip = document.createElement(\"button\");\n chip.type = \"button\";\n chip.className = \"chip\";\n chip.innerHTML = '<span class=\"method m-' + m.toLowerCase() + '\">' + m + '</span><span>' + esc(path) + '</span>';\n chip.onclick = function() {\n document.getElementById(\"method\").value = m;\n document.getElementById(\"path\").value = path;\n document.getElementById(\"form\").requestSubmit();\n };\n routesEl.appendChild(chip);\n })(route.methods[mi], route.path.replace(/\\\\*$/, \"\"));\n }\n}\n\n// ── Header table management ──\n\nvar headerRowsEl = document.getElementById(\"header-rows\");\n\nfunction addHeaderRow(name, value) {\n var row = document.createElement(\"div\");\n row.className = \"hdr-row\";\n var nameInp = document.createElement(\"input\");\n nameInp.className = \"hdr-input hdr-name\";\n nameInp.placeholder = \"Header name\";\n nameInp.value = name || \"\";\n var valInp = document.createElement(\"input\");\n valInp.className = \"hdr-input hdr-val\";\n valInp.placeholder = \"Value\";\n valInp.value = value || \"\";\n var del = document.createElement(\"button\");\n del.type = \"button\";\n del.className = \"hdr-del\";\n del.textContent = \"\\\\u00d7\";\n del.onclick = function() { row.remove(); };\n row.appendChild(nameInp);\n row.appendChild(valInp);\n row.appendChild(del);\n headerRowsEl.appendChild(row);\n return row;\n}\n\nfunction getHeaders() {\n var headers = {};\n var rows = headerRowsEl.querySelectorAll(\".hdr-row\");\n for (var i = 0; i < rows.length; i++) {\n var inputs = rows[i].querySelectorAll(\"input\");\n var n = inputs[0].value.trim();\n var v = inputs[1].value.trim();\n if (n) headers[n] = v;\n }\n return headers;\n}\n\nfunction setHeader(name, value) {\n var rows = headerRowsEl.querySelectorAll(\".hdr-row\");\n var lowerName = name.toLowerCase();\n for (var i = 0; i < rows.length; i++) {\n var inputs = rows[i].querySelectorAll(\"input\");\n if (inputs[0].value.trim().toLowerCase() === lowerName) {\n inputs[1].value = value;\n return;\n }\n }\n addHeaderRow(name, value);\n}\n\ndocument.getElementById(\"add-header\").onclick = function() { addHeaderRow(); };\n\n// ── Token store (localStorage) ──\n\nvar TOKEN_KEY = \"stoma-playground-tokens\";\n\nfunction loadTokens() {\n try {\n var raw = localStorage.getItem(TOKEN_KEY);\n return raw ? JSON.parse(raw) : [];\n } catch (e) { return []; }\n}\n\nfunction persistTokens(tokens) {\n try { localStorage.setItem(TOKEN_KEY, JSON.stringify(tokens)); } catch (e) {}\n}\n\nfunction saveToken(label, value) {\n var tokens = loadTokens();\n for (var i = 0; i < tokens.length; i++) {\n if (tokens[i].label === label) {\n tokens[i].value = value;\n persistTokens(tokens);\n renderTokenStore();\n return;\n }\n }\n tokens.push({ label: label, value: value });\n persistTokens(tokens);\n renderTokenStore();\n}\n\nfunction removeToken(idx) {\n var tokens = loadTokens();\n tokens.splice(idx, 1);\n persistTokens(tokens);\n renderTokenStore();\n}\n\nfunction clearTokens() {\n persistTokens([]);\n renderTokenStore();\n}\n\nfunction applyToken(idx) {\n var tokens = loadTokens();\n if (tokens[idx]) setHeader(\"Authorization\", \"Bearer \" + tokens[idx].value);\n}\n\nfunction renderTokenStore() {\n var tokens = loadTokens();\n var storeEl = document.getElementById(\"token-store\");\n var listEl = document.getElementById(\"token-list\");\n if (!tokens.length) {\n storeEl.style.display = \"none\";\n listEl.innerHTML = \"\";\n return;\n }\n storeEl.style.display = \"\";\n var html = \"\";\n for (var i = 0; i < tokens.length; i++) {\n var shortVal = tokens[i].value.length > 40 ? tokens[i].value.slice(0, 40) + \"\\\\u2026\" : tokens[i].value;\n html +=\n '<div class=\"token-item\">' +\n '<span class=\"token-name\">' + esc(tokens[i].label) + '</span>' +\n '<span class=\"token-val\">' + esc(shortVal) + '</span>' +\n '<span class=\"token-actions\">' +\n '<button class=\"token-btn token-btn-use\" data-idx=\"' + i + '\">Use</button>' +\n '<button class=\"token-btn token-btn-del\" data-idx=\"' + i + '\">\\\\u00d7</button>' +\n '</span>' +\n '</div>';\n }\n listEl.innerHTML = html;\n listEl.querySelectorAll(\".token-btn-use\").forEach(function(btn) {\n btn.onclick = function() { applyToken(Number(btn.dataset.idx)); };\n });\n listEl.querySelectorAll(\".token-btn-del\").forEach(function(btn) {\n btn.onclick = function() { removeToken(Number(btn.dataset.idx)); };\n });\n}\n\ndocument.getElementById(\"clear-tokens\").onclick = clearTokens;\n\n// Restore tokens on load\nrenderTokenStore();\nvar initTokens = loadTokens();\nif (initTokens.length) applyToken(initTokens.length - 1);\n\n// ── Token auto-detection from responses ──\n\nfunction detectAndSaveTokens(data) {\n if (!data || !data.body) return;\n try {\n var parsed = JSON.parse(data.body);\n var token = parsed.access_token || parsed.token;\n if (typeof token === \"string\" && token.length > 0) {\n var label = parsed.token_type ? parsed.token_type + \"_token\" : \"access_token\";\n saveToken(label, token);\n setHeader(\"Authorization\", \"Bearer \" + token);\n }\n } catch (e) {}\n}\n\n// ── Send request ──\n//\n// Direct fetch to the gateway (same origin). For redirect responses the\n// browser returns an opaque response (redirect:\"manual\"), so we fall back\n// to the server-side proxy which can read the full 3xx details.\n\nfunction sendViaProxy(method, path, headers, bodyText) {\n return fetch(\"/__playground/send\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ method: method, path: path, headers: headers, body: bodyText || undefined }),\n }).then(function(proxyRes) {\n if (!proxyRes.ok) {\n return proxyRes.json().catch(function() { return null; }).then(function(err) {\n renderError(err && err.error ? err.error : \"Request failed: \" + proxyRes.status);\n });\n }\n return proxyRes.json().then(function(data) {\n if (data.error) renderError(data.error);\n else { renderResponse(data); detectAndSaveTokens(data); }\n });\n });\n}\n\ndocument.getElementById(\"form\").addEventListener(\"submit\", function(e) {\n e.preventDefault();\n var callbackBanner = document.getElementById(\"callback-banner\");\n callbackBanner.style.display = \"none\";\n\n var method = document.getElementById(\"method\").value;\n var path = document.getElementById(\"path\").value;\n var bodyText = document.getElementById(\"body\").value;\n var sendBtn = document.getElementById(\"send\");\n var headers = getHeaders();\n\n sendBtn.disabled = true;\n sendBtn.textContent = \"Sending\\\\u2026\";\n\n var start = performance.now();\n var init = { method: method, headers: headers, redirect: \"manual\" };\n if (bodyText && method !== \"GET\" && method !== \"HEAD\") init.body = bodyText;\n\n fetch(path, init).then(function(res) {\n // Opaque redirect — fall back to proxy for full 3xx details\n if (res.type === \"opaqueredirect\") return null;\n\n var status = res.status;\n var statusText = res.statusText;\n var resHeaders = {};\n res.headers.forEach(function(v, k) { resHeaders[k] = v; });\n\n return res.text().then(function(bodyStr) {\n return { status: status, statusText: statusText, headers: resHeaders, body: bodyStr, elapsed: performance.now() - start };\n });\n }).catch(function() {\n return null;\n }).then(function(data) {\n if (data) {\n renderResponse(data);\n detectAndSaveTokens(data);\n return;\n }\n return sendViaProxy(method, path, headers, bodyText);\n }).catch(function(err) {\n renderError(err.message || \"Request failed\");\n }).finally(function() {\n sendBtn.disabled = false;\n sendBtn.textContent = \"Send\";\n });\n});\n\n// ── Render helpers ──\n\nfunction escHtml(s) {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n}\n\nfunction highlightJson(prettyStr) {\n var s = escHtml(prettyStr);\n // Keys\n s = s.replace(/\"([^\"\\\\\\\\]*(?:\\\\\\\\.[^\"\\\\\\\\]*)*)\"s*:/g, '<span class=\"j-key\">\"$1\"</span>:');\n // String values (after : or in arrays after , or [)\n s = s.replace(/:\\\\s*\"([^\"\\\\\\\\]*(?:\\\\\\\\.[^\"\\\\\\\\]*)*)\"/g, ': <span class=\"j-str\">\"$1\"</span>');\n // Numbers\n s = s.replace(/:\\\\s*(-?\\\\d+(?:\\\\.\\\\d+)?(?:[eE][+-]?\\\\d+)?)/g, ': <span class=\"j-num\">$1</span>');\n // Booleans and null\n s = s.replace(/:\\\\s*(true|false|null)\\\\b/g, ': <span class=\"j-lit\">$1</span>');\n return s;\n}\n\nfunction renderResponse(ref) {\n var status = ref.status, statusText = ref.statusText, headers = ref.headers, body = ref.body, elapsed = ref.elapsed;\n var cls = status < 300 ? \"b-2xx\" : status < 400 ? \"b-3xx\" : status < 500 ? \"b-4xx\" : \"b-5xx\";\n\n var rawBody = body || \"\";\n var prettyBody = rawBody;\n var isJson = false;\n try {\n prettyBody = JSON.stringify(JSON.parse(rawBody), null, 2);\n isJson = true;\n } catch (e) {}\n\n var headerCount = 0;\n var headerRows = \"\";\n for (var k in headers) {\n if (headers.hasOwnProperty(k)) {\n headerCount++;\n headerRows += \"<tr><td>\" + esc(k) + \"</td><td>\" + esc(String(headers[k])) + \"</td></tr>\";\n }\n }\n\n // Redirect banner (OAuth flows)\n var redirectBanner = \"\";\n var locationUrl = headers && headers.location;\n if (status >= 300 && status < 400 && locationUrl && !locationUrl.startsWith(\"/\")) {\n lastRedirectUrl = locationUrl;\n redirectBanner =\n '<div class=\"res-redirect\">' +\n '<div class=\"res-redirect-title\">Redirect Detected</div>' +\n '<div class=\"res-redirect-url\">' + esc(locationUrl) + '</div>' +\n '<button class=\"res-redirect-btn\" onclick=\"openAuthPopup(lastRedirectUrl)\">Open Authorization</button>' +\n '<div class=\"res-redirect-notice\">Opens in a popup. After authorizing, the callback parameters will be sent back here.</div>' +\n '</div>';\n }\n\n var prettyContent = isJson\n ? highlightJson(prettyBody)\n : escHtml(prettyBody);\n\n var resEl = document.getElementById(\"response\");\n resEl.innerHTML =\n '<div class=\"res-top\">' +\n '<span class=\"badge ' + cls + '\">' + status + \" \" + esc(statusText || \"\") + '</span>' +\n '<span class=\"timing\">' + (elapsed || 0).toFixed(1) + ' ms</span>' +\n '</div>' +\n redirectBanner +\n '<div class=\"res-tabs\">' +\n '<button class=\"res-tab active\" data-tab=\"pretty\">Pretty</button>' +\n '<button class=\"res-tab\" data-tab=\"raw\">Raw</button>' +\n '<button class=\"res-tab\" data-tab=\"headers\">Headers<span class=\"res-tab-count\">(' + headerCount + ')</span></button>' +\n '</div>' +\n '<div class=\"res-tab-content active\" data-tab=\"pretty\"><div class=\"res-body\"><pre>' + prettyContent + '</pre></div></div>' +\n '<div class=\"res-tab-content\" data-tab=\"raw\"><div class=\"res-body\"><pre>' + esc(rawBody) + '</pre></div></div>' +\n '<div class=\"res-tab-content\" data-tab=\"headers\"><table class=\"htable\">' + headerRows + '</table></div>';\n\n // Tab switching\n resEl.querySelectorAll(\".res-tab\").forEach(function(tab) {\n tab.onclick = function() {\n var target = tab.dataset.tab;\n resEl.querySelectorAll(\".res-tab\").forEach(function(t) { t.classList.toggle(\"active\", t.dataset.tab === target); });\n resEl.querySelectorAll(\".res-tab-content\").forEach(function(c) { c.classList.toggle(\"active\", c.dataset.tab === target); });\n };\n });\n}\n\nfunction renderError(msg) {\n document.getElementById(\"response\").innerHTML = '<div class=\"res-error\">' + esc(msg) + '</div>';\n}\n\nfunction esc(s) {\n var d = document.createElement(\"div\");\n d.textContent = s;\n return d.innerHTML;\n}\n\n// ── OAuth popup ──\n\nvar lastRedirectUrl = \"\";\nvar oauthPopup = null;\nfunction openAuthPopup(url) {\n if (oauthPopup && !oauthPopup.closed) oauthPopup.close();\n oauthPopup = window.open(url, \"stoma-oauth\", \"width=600,height=700\");\n}\n\nwindow.addEventListener(\"message\", function(event) {\n if (event.origin !== window.location.origin) return;\n if (!event.data || event.data.type !== \"stoma-oauth-callback\") return;\n var params = event.data.params;\n if (!params) return;\n\n var callbackPath = \"/auth/callback\";\n for (var i = 0; i < registry.routes.length; i++) {\n if (registry.routes[i].path.toLowerCase().indexOf(\"callback\") !== -1) {\n callbackPath = registry.routes[i].path;\n break;\n }\n }\n\n var qs = new URLSearchParams(params).toString();\n document.getElementById(\"method\").value = \"GET\";\n document.getElementById(\"path\").value = callbackPath + \"?\" + qs;\n\n var banner = document.getElementById(\"callback-banner\");\n banner.innerHTML = 'OAuth parameters received. Click <strong>Send</strong> to complete authorization.';\n banner.style.display = \"\";\n\n document.getElementById(\"response\").innerHTML =\n '<div class=\"res-empty\">OAuth callback parameters prefilled. Click Send to exchange for a token.</div>';\n});\n</script>\n</body>\n</html>`;\n}\n\nfunction esc(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n}\n","/**\n * Generates a self-contained HTML page that relays OAuth callback\n * query parameters to the playground opener window via postMessage.\n *\n * Served by `wrapWithPlayground` when a browser navigates to a\n * callback route (e.g., after the OAuth provider redirects the popup).\n */\nexport function oauthRelayHtml(url: URL): string {\n const paramsJson = JSON.stringify(\n Object.fromEntries(url.searchParams.entries())\n ).replace(/</g, \"\\\\u003c\");\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head><meta charset=\"utf-8\" /><title>Authorization Complete</title></head>\n<body style=\"font-family:system-ui;padding:2rem;text-align:center;background:#1e1e1e;color:#d4d4d4\">\n<h3>Authorization Complete</h3>\n<p id=\"msg\">Sending parameters to the playground\\u2026</p>\n<script>\n(function() {\n var params = ${paramsJson};\n if (window.opener) {\n window.opener.postMessage({ type: \"stoma-oauth-callback\", params: params }, location.origin);\n document.getElementById(\"msg\").textContent = \"Parameters sent. You can close this window.\";\n } else {\n document.getElementById(\"msg\").textContent = \"No opener window found. Please copy the URL and paste it into the playground manually.\";\n }\n})();\n</script>\n</body>\n</html>`;\n}\n","import type { GatewayRegistry } from \"../gateway/types.js\";\nimport { playgroundHtml } from \"./html.js\";\nimport { oauthRelayHtml } from \"./oauth-relay.js\";\n\n/**\n * Wrap a gateway's fetch function with playground routes.\n *\n * Intercepts playground paths before delegating to the original gateway fetch.\n *\n * - `/__playground` → serves the playground HTML UI\n * - `/__playground/registry` → returns the gateway registry as JSON\n * - `/__playground/send` → redirect fallback: re-executes a request server-side\n * to capture full 3xx response details that browser\n * fetch cannot access (opaque redirect limitation)\n */\nexport function wrapWithPlayground(\n gatewayFetch: (request: Request) => Response | Promise<Response>,\n registry: GatewayRegistry\n): (request: Request) => Response | Promise<Response> {\n const html = playgroundHtml(registry);\n\n // Pre-compute callback route paths for OAuth relay interception\n const callbackPaths = registry.routes\n .map((r) => r.path)\n .filter((p) => p.toLowerCase().includes(\"callback\"));\n\n return async (request: Request) => {\n const url = new URL(request.url);\n\n if (url.pathname === \"/__playground\") {\n return new Response(html, {\n headers: { \"content-type\": \"text/html; charset=utf-8\" },\n });\n }\n\n if (url.pathname === \"/__playground/registry\") {\n return Response.json(registry);\n }\n\n if (url.pathname === \"/__playground/send\" && request.method === \"POST\") {\n return handlePlaygroundSend(request, gatewayFetch, url.origin);\n }\n\n // OAuth relay: when the OAuth provider redirects the popup to a callback\n // route, serve a relay page that sends the params back to the playground\n // via postMessage instead of letting the gateway handle the browser request.\n const isNavigation = request.headers.get(\"accept\")?.includes(\"text/html\");\n if (isNavigation && url.search) {\n const isCallbackRoute = callbackPaths.some(\n (p) =>\n url.pathname === p || url.pathname.startsWith(p.replace(/\\*$/, \"\"))\n );\n if (isCallbackRoute) {\n return new Response(oauthRelayHtml(url), {\n headers: { \"content-type\": \"text/html; charset=utf-8\" },\n });\n }\n }\n\n return gatewayFetch(request);\n };\n}\n\n/**\n * Execute a request against the gateway in-process and return the full\n * response details as JSON. This avoids browser fetch limitations\n * (redirect following, CORS, opaque responses).\n */\nasync function handlePlaygroundSend(\n request: Request,\n gatewayFetch: (request: Request) => Response | Promise<Response>,\n origin: string\n): Promise<Response> {\n try {\n const payload = (await request.json()) as {\n method: string;\n path: string;\n headers?: Record<string, string>;\n body?: string;\n };\n\n // Build a Request as if the browser had made it directly\n const targetUrl = new URL(payload.path, origin).href;\n const init: RequestInit = {\n method: payload.method,\n headers: payload.headers ?? {},\n };\n if (payload.body && payload.method !== \"GET\" && payload.method !== \"HEAD\") {\n init.body = payload.body;\n }\n\n const gatewayRequest = new Request(targetUrl, init);\n const start = performance.now();\n const res = await gatewayFetch(gatewayRequest);\n const elapsed = performance.now() - start;\n\n const body = await res.text();\n const headers: Record<string, string> = {};\n res.headers.forEach((v, k) => {\n headers[k] = v;\n });\n\n return Response.json({\n status: res.status,\n statusText: res.statusText,\n headers,\n body,\n elapsed,\n });\n } catch (err) {\n return Response.json(\n {\n error: err instanceof Error ? err.message : String(err),\n },\n { status: 500 }\n );\n }\n}\n","import type { Server } from \"node:http\";\nimport { serve } from \"@hono/node-server\";\n\nexport interface StartServerOptions {\n fetch: (request: Request) => Response | Promise<Response>;\n port: number;\n hostname: string;\n}\n\nexport function startServer(options: StartServerOptions): Promise<Server> {\n const { fetch, port, hostname } = options;\n\n return new Promise<Server>((resolve, reject) => {\n const server = serve({ fetch, port, hostname }, () =>\n resolve(server as unknown as Server)\n );\n\n // Handle EADDRINUSE before the callback fires\n (server as unknown as Server).on?.(\n \"error\",\n (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n reject(\n new Error(\n `Port ${port} is already in use. Try a different port with --port <number>`\n )\n );\n } else {\n reject(err);\n }\n }\n );\n });\n}\n","export interface Logger {\n info: (message: string) => void;\n verbose: (message: string) => void;\n error: (message: string) => void;\n}\n\nexport function createLogger(verbose: boolean): Logger {\n return {\n info: (message: string) => console.log(message),\n verbose: verbose ? (message: string) => console.log(message) : () => {},\n error: (message: string) => console.error(message),\n };\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nlet cachedVersion: string | undefined;\n\nexport function getVersion(): string {\n if (cachedVersion) return cachedVersion;\n\n try {\n const dir = dirname(fileURLToPath(import.meta.url));\n // Try both: bundled (dist/bin.js → ../package.json) and source (src/utils/version.ts → ../../package.json)\n for (const rel of [\"..\", \"../..\"]) {\n const candidate = resolve(dir, rel, \"package.json\");\n if (existsSync(candidate)) {\n const pkg = JSON.parse(readFileSync(candidate, \"utf-8\"));\n if (pkg.name === \"@vivero/stoma-cli\") {\n cachedVersion = (pkg.version as string) ?? \"0.0.0\";\n return cachedVersion!;\n }\n }\n }\n cachedVersion = \"0.0.0\";\n } catch {\n cachedVersion = \"0.0.0\";\n }\n\n return cachedVersion!;\n}\n","import { runCli } from \"./cli.js\";\n\nrunCli(process.argv.slice(2));\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBA4LjB,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["path","require","fetch","resolve","path","fetch","resolve","existsSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands/run.ts","../src/gateway/resolve.ts","../src/playground/html.ts","../src/playground/oauth-relay.ts","../src/playground/wrap.ts","../src/server/serve.ts","../src/utils/logger.ts","../src/utils/version.ts","../src/bin.ts"],"sourcesContent":["import { Builtins, Cli } from \"clipanion\";\nimport { RunCommand } from \"./commands/index.js\";\nimport { getVersion } from \"./utils/version.js\";\n\nexport function createCli() {\n const cli = new Cli({\n binaryLabel: \"stoma\",\n binaryName: \"stoma\",\n binaryVersion: getVersion(),\n });\n\n cli.register(Builtins.HelpCommand);\n cli.register(Builtins.VersionCommand);\n cli.register(RunCommand);\n\n return cli;\n}\n\nexport async function runCli(argv: string[]) {\n const cli = createCli();\n await cli.runExit(argv);\n}\n","import path from \"node:path\";\nimport { Command, Option } from \"clipanion\";\nimport { resolveGateway } from \"../gateway/resolve.js\";\nimport type { GatewayInstance } from \"../gateway/types.js\";\nimport { wrapWithPlayground } from \"../playground/wrap.js\";\nimport { startServer } from \"../server/serve.js\";\nimport { createLogger } from \"../utils/logger.js\";\n\nexport class RunCommand extends Command {\n static override paths = [[\"run\"]];\n\n static override usage = Command.Usage({\n description: \"Start a local HTTP server for a Stoma gateway file\",\n details: `\n Loads a compiled gateway JS file (or TypeScript with tsx) and serves it\n on a local Node.js HTTP server.\n\n The file must export a gateway instance, a factory function, or a\n \\`createPlaygroundGateway\\` named export.\n `,\n examples: [\n [\"Run a compiled gateway file\", \"stoma run ./my-gateway.js\"],\n [\"Run on a custom port\", \"stoma run ./my-gateway.js --port 3000\"],\n [\"Run with debug logging\", \"stoma run ./my-gateway.js --debug\"],\n [\n \"Run a remote gateway file\",\n \"stoma run https://example.com/gateway.ts --trust-remote\",\n ],\n ],\n });\n\n file = Option.String({ required: true });\n\n port = Option.String(\"--port,-p\", \"8787\", {\n description: \"Port to listen on (default: 8787)\",\n });\n\n host = Option.String(\"--host,-H\", \"localhost\", {\n description: \"Hostname to bind to (default: localhost)\",\n });\n\n debug = Option.Boolean(\"--debug,-d\", false, {\n description: \"Enable gateway debug logging\",\n });\n\n verbose = Option.Boolean(\"--verbose,-v\", false, {\n description: \"Verbose CLI output\",\n });\n\n playground = Option.Boolean(\"--playground\", false, {\n description: \"Serve interactive playground UI at /__playground\",\n });\n\n trustRemote = Option.Boolean(\"--trust-remote\", false, {\n description:\n \"Allow loading gateway files from remote URLs (downloads and executes code — use only with trusted sources)\",\n });\n\n async execute() {\n const log = createLogger(this.verbose);\n const isRemote =\n this.file.startsWith(\"http://\") || this.file.startsWith(\"https://\");\n const filePath = isRemote ? this.file : path.resolve(this.file);\n const portNum = parseInt(this.port, 10);\n\n if (Number.isNaN(portNum) || portNum < 0 || portNum > 65535) {\n log.error(`Invalid port: ${this.port}`);\n return 1;\n }\n\n log.info(`Loading gateway from ${filePath}`);\n\n let gateway: GatewayInstance;\n try {\n gateway = await resolveGateway(filePath, {\n debug: this.debug,\n trustRemote: this.trustRemote,\n });\n } catch (err) {\n log.error(\n `Failed to load gateway: ${err instanceof Error ? err.message : String(err)}`\n );\n return 1;\n }\n\n log.info(\n `Gateway \"${gateway.name}\" loaded (${gateway.routeCount} route${gateway.routeCount === 1 ? \"\" : \"s\"})`\n );\n\n if (this.verbose && gateway._registry) {\n for (const route of gateway._registry.routes) {\n const methods = Array.isArray(route.methods)\n ? route.methods.join(\",\")\n : \"ALL\";\n log.verbose(` ${methods.padEnd(8)} ${route.path}`);\n }\n }\n\n try {\n const fetch =\n this.playground && gateway._registry\n ? wrapWithPlayground(gateway.app.fetch, gateway._registry)\n : gateway.app.fetch;\n\n const server = await startServer({\n fetch,\n port: portNum,\n hostname: this.host,\n });\n\n log.info(\n `Gateway \"${gateway.name}\" listening on http://${this.host}:${portNum}`\n );\n\n if (this.playground) {\n log.info(`Playground: http://${this.host}:${portNum}/__playground`);\n }\n\n if (gateway._registry) {\n for (const route of gateway._registry.routes) {\n const methods = Array.isArray(route.methods)\n ? route.methods.join(\",\")\n : \"ALL\";\n log.info(` ${methods.padEnd(8)} ${route.path}`);\n }\n }\n\n await new Promise<void>((resolve) => {\n let shuttingDown = false;\n\n const shutdown = () => {\n if (shuttingDown) {\n log.info(\"\\nForce exit\");\n process.exit(1);\n }\n shuttingDown = true;\n log.info(\"\\nShutting down...\");\n\n // Force exit after 3s if graceful shutdown stalls\n const forceExit = setTimeout(() => {\n log.error(\"Graceful shutdown timed out, forcing exit\");\n process.exit(1);\n }, 3_000);\n forceExit.unref();\n\n server.closeAllConnections();\n server.close(() => {\n clearTimeout(forceExit);\n resolve();\n });\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n });\n\n // Clipanion's runExit() only sets process.exitCode — it doesn't\n // call process.exit(). If @hono/node-server leaves lingering handles\n // the event loop never drains and the process hangs.\n return process.exit(0) as never;\n } catch (err) {\n log.error(\n `Server error: ${err instanceof Error ? err.message : String(err)}`\n );\n return 1;\n }\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { mkdtemp, rm, unlink, writeFile } from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { build } from \"esbuild\";\nimport type { GatewayInstance } from \"./types.js\";\n\nexport interface ResolveOptions {\n debug?: boolean;\n /** Allow fetching gateway files from remote URLs. */\n trustRemote?: boolean;\n}\n\nconst TS_EXTENSIONS = [\".ts\", \".tsx\", \".mts\"];\n\n/**\n * Dynamic-import a gateway file and resolve its export to a GatewayInstance.\n *\n * TypeScript files are automatically transpiled via esbuild before importing.\n * Remote URLs (http/https) are supported when `trustRemote` is set.\n */\nexport async function resolveGateway(\n filePathOrUrl: string,\n options: ResolveOptions = {}\n): Promise<GatewayInstance> {\n if (isRemoteUrl(filePathOrUrl)) {\n if (!options.trustRemote) {\n throw new Error(\n \"Remote URLs require the --trust-remote flag.\\n\" +\n \"This will download and execute code from the URL. Only use with trusted sources.\"\n );\n }\n return resolveRemoteGateway(filePathOrUrl, options);\n }\n\n if (!existsSync(filePathOrUrl)) {\n throw new Error(`File not found: ${filePathOrUrl}`);\n }\n\n return resolveLocalFile(filePathOrUrl, options);\n}\n\nasync function resolveLocalFile(\n filePath: string,\n options: ResolveOptions\n): Promise<GatewayInstance> {\n const isTypeScript = TS_EXTENSIONS.some((ext) => filePath.endsWith(ext));\n\n let mod: Record<string, unknown>;\n\n if (isTypeScript) {\n mod = await importTypeScript(filePath);\n } else {\n try {\n mod = await import(pathToFileURL(filePath).href);\n } catch (err) {\n throw new Error(\n `Failed to import: ${filePath}\\n${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n return resolveFromModule(mod, options);\n}\n\nfunction isRemoteUrl(input: string): boolean {\n return input.startsWith(\"http://\") || input.startsWith(\"https://\");\n}\n\n/**\n * Fetch a remote gateway file to a temp directory, resolve it, then clean up.\n */\nasync function resolveRemoteGateway(\n url: string,\n options: ResolveOptions\n): Promise<GatewayInstance> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(\n `Failed to fetch remote gateway: ${res.status} ${res.statusText}`\n );\n }\n\n const filename = filenameFromUrl(url, res.headers.get(\"content-type\"));\n const tmpDir = await mkdtemp(path.join(tmpdir(), \"stoma-remote-\"));\n const tmpPath = path.join(tmpDir, filename);\n\n try {\n await writeFile(tmpPath, await res.text(), \"utf-8\");\n return await resolveLocalFile(tmpPath, options);\n } finally {\n await unlink(tmpPath).catch(() => {});\n await rm(tmpDir, { recursive: true, force: true }).catch(() => {});\n }\n}\n\n/**\n * Derive a filename (with extension) from a URL path and optional content-type.\n * Falls back to `.ts` so esbuild can transpile unknown sources.\n */\nfunction filenameFromUrl(url: string, contentType: string | null): string {\n const pathname = new URL(url).pathname;\n const basename = path.basename(pathname);\n\n // If the URL has a recognisable extension, use it\n if (/\\.(ts|tsx|mts|js|mjs|cjs)$/.test(basename)) {\n return basename;\n }\n\n // Infer from content-type\n if (contentType?.includes(\"javascript\")) {\n return \"gateway.mjs\";\n }\n if (contentType?.includes(\"typescript\")) {\n return \"gateway.ts\";\n }\n\n // Default to TypeScript — esbuild can handle both TS and JS\n return basename ? `${basename}.ts` : \"gateway.ts\";\n}\n\n/**\n * Get the node_modules search paths from the CLI's own install location.\n * Uses Node's own resolution algorithm via createRequire, so it works\n * correctly regardless of monorepo hoisting, Yarn PnP, or flat installs.\n */\nfunction getCliNodePaths(): string[] {\n const require = createRequire(import.meta.url);\n const paths = (require.resolve.paths(\"@vivero/stoma\") ?? []).filter(\n existsSync\n );\n\n // Also include the node_modules of @vivero/stoma itself, so transitive\n // deps like @vivero/stoma-core are resolvable even if not hoisted.\n try {\n const stomaEntry = require.resolve(\"@vivero/stoma\");\n const stomaDir = path.dirname(stomaEntry);\n // Walk up to find the package root (contains package.json)\n let dir = stomaDir;\n while (dir !== path.dirname(dir)) {\n if (existsSync(path.join(dir, \"package.json\"))) {\n const nested = path.join(dir, \"node_modules\");\n if (existsSync(nested) && !paths.includes(nested)) {\n paths.unshift(nested);\n }\n break;\n }\n dir = path.dirname(dir);\n }\n } catch {\n // @vivero/stoma not resolvable — will error later during build\n }\n\n return paths;\n}\n\n/**\n * Transpile a TypeScript file with esbuild and import the result.\n *\n * Bundles all dependencies into a self-contained JS file so the output\n * runs without any npm install — matching how the docs editor compiles\n * gateway configs. The temp file is written next to the source and\n * cleaned up after import.\n *\n * Uses `nodePaths` from the CLI's own install so that `@vivero/stoma`\n * and `hono` are always available, even when the gateway file lives\n * outside any project (e.g. ~/Downloads/).\n */\nasync function importTypeScript(\n filePath: string\n): Promise<Record<string, unknown>> {\n const tmpFile = filePath.replace(/\\.tsx?$/, `.stoma-tmp-${Date.now()}.mjs`);\n\n try {\n const result = await build({\n entryPoints: [filePath],\n bundle: true,\n format: \"esm\",\n platform: \"node\",\n target: \"node20\",\n nodePaths: getCliNodePaths(),\n write: false,\n logLevel: \"silent\",\n });\n\n if (!result.outputFiles?.length) {\n throw new Error(\"TypeScript transpilation produced no output\");\n }\n\n await writeFile(tmpFile, result.outputFiles[0].text, \"utf-8\");\n return await import(pathToFileURL(tmpFile).href);\n } catch (err) {\n if (\n err instanceof Error &&\n err.message.includes(\"TypeScript transpilation\")\n ) {\n throw err;\n }\n throw new Error(\n `Failed to transpile TypeScript: ${filePath}\\n${err instanceof Error ? err.message : String(err)}`\n );\n } finally {\n await unlink(tmpFile).catch(() => {});\n }\n}\n\n/**\n * Resolve a GatewayInstance from a module's exports.\n *\n * Resolution order:\n * 1. `mod.createPlaygroundGateway` (function) — backward compat with editor snippets\n * 2. `mod.default` (function) — call as async factory\n * 3. `mod.default` (object with `.app` + `._registry`) — GatewayInstance directly\n * 4. `mod.default` (object with `.fetch`) — bare Hono app, wrap in minimal instance\n * 5. Throw descriptive error\n */\nexport async function resolveFromModule(\n mod: Record<string, unknown>,\n _options: ResolveOptions = {}\n): Promise<GatewayInstance> {\n // 1. Named export: createPlaygroundGateway()\n if (typeof mod.createPlaygroundGateway === \"function\") {\n const result = await mod.createPlaygroundGateway();\n return asGatewayInstance(result, \"createPlaygroundGateway()\");\n }\n\n // 2. Default export: factory function\n if (typeof mod.default === \"function\") {\n const result = await mod.default();\n return asGatewayInstance(result, \"default()\");\n }\n\n // 3. Default export: GatewayInstance directly\n if (isGatewayInstance(mod.default)) {\n return mod.default;\n }\n\n // 4. Default export: bare Hono app (has .fetch)\n if (isHonoApp(mod.default)) {\n return {\n app: mod.default,\n name: \"unnamed-gateway\",\n routeCount: 0,\n _registry: { routes: [], policies: [], gatewayName: \"unnamed-gateway\" },\n };\n }\n\n throw new Error(\n \"Could not resolve a gateway from the module exports.\\n\\n\" +\n \"The file must export a gateway in one of these forms:\\n\" +\n \" export default createGateway({ ... })\\n\" +\n \" export default async function() { return createGateway({ ... }) }\\n\" +\n \" export function createPlaygroundGateway() { return createGateway({ ... }) }\\n\" +\n \" export default app // a Hono app with a .fetch method\"\n );\n}\n\nfunction isGatewayInstance(value: unknown): value is GatewayInstance {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"app\" in value &&\n \"_registry\" in value\n );\n}\n\nfunction isHonoApp(\n value: unknown\n): value is { fetch: (req: Request) => Response | Promise<Response> } {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"fetch\" in value &&\n typeof (value as Record<string, unknown>).fetch === \"function\"\n );\n}\n\nfunction asGatewayInstance(value: unknown, source: string): GatewayInstance {\n if (isGatewayInstance(value)) {\n return value;\n }\n if (isHonoApp(value)) {\n return {\n app: value,\n name: \"unnamed-gateway\",\n routeCount: 0,\n _registry: { routes: [], policies: [], gatewayName: \"unnamed-gateway\" },\n };\n }\n throw new Error(\n `${source} did not return a valid gateway instance. ` +\n \"Expected an object with .app and ._registry properties, or a Hono app with a .fetch method.\"\n );\n}\n","import type { GatewayRegistry } from \"../gateway/types.js\";\n\nexport function playgroundHtml(registry: GatewayRegistry): string {\n const registryJson = JSON.stringify(registry).replace(/</g, \"\\\\u003c\");\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>${esc(registry.gatewayName)} — Stoma Playground</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n:root{\n --bg:#1e1e1e;--bg2:#252526;--bg3:#2d2d2d;\n --border:#3c3c3c;--text:#d4d4d4;--muted:#969696;--dim:#555;\n --teal:#4ec9b0;--blue:#0e639c;--blue2:#1177bb;\n --yellow:#dcdcaa;--red:#f48771;--blue-text:#569cd6;\n --mono:\"Cascadia Code\",\"Fira Code\",\"JetBrains Mono\",\"SF Mono\",monospace;\n --sans:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;\n}\nhtml,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--sans)}\nbody{display:flex;flex-direction:column;overflow:hidden}\n\n/* Header */\n.hdr{display:flex;align-items:center;justify-content:space-between;height:3rem;padding:0 1rem;background:var(--bg2);border-bottom:1px solid var(--border);flex-shrink:0}\n.hdr-left{display:flex;align-items:center;gap:.5rem}\n.logo{font-weight:700;font-size:.875rem;color:var(--teal);text-decoration:none}\n.hdr-div{color:var(--dim);font-size:.875rem}\n.hdr-title{font-size:.8125rem;color:var(--muted)}\n\n/* Routes bar */\n.routes-bar{padding:.5rem 1rem;display:flex;flex-wrap:wrap;gap:.25rem;flex-shrink:0;border-bottom:1px solid var(--border);background:var(--bg)}\n.chip{all:unset;display:inline-flex;align-items:center;gap:.375rem;height:1.5rem;padding:0 .5rem;border-radius:.25rem;border:1px solid var(--border);background:var(--bg3);font-family:var(--mono);font-size:.6875rem;cursor:pointer;user-select:none;transition:border-color .15s,background .15s}\n.chip:hover{border-color:var(--blue);background:#333}\n.method{font-size:.5625rem;font-weight:700;letter-spacing:.04em;padding:.0625rem .25rem;border-radius:.125rem}\n.m-get{background:var(--teal);color:var(--bg)}\n.m-post{background:var(--blue-text);color:#fff}\n.m-put,.m-patch{background:var(--yellow);color:var(--bg)}\n.m-delete{background:var(--red);color:var(--bg)}\n\n/* Workspace — two pane split */\n.workspace{flex:1;display:flex;gap:.75rem;padding:.75rem 1rem;overflow:hidden}\n.pane-left{flex:1;display:flex;flex-direction:column;gap:.625rem;overflow-y:auto;min-width:0}\n.pane-right{flex:1;display:flex;flex-direction:column;min-width:0;overflow:hidden}\n\n/* Token Store */\n.token-store{border:1px solid var(--border);border-radius:.25rem;background:var(--bg2);overflow:hidden;flex-shrink:0}\n.token-header{display:flex;align-items:center;justify-content:space-between;padding:.375rem .625rem;background:var(--bg3);border-bottom:1px solid var(--border)}\n.token-title{font-size:.625rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}\n.token-item{display:flex;align-items:center;gap:.375rem;padding:.25rem .625rem;border-bottom:1px solid rgba(60,60,60,.2)}\n.token-item:last-child{border-bottom:none}\n.token-name{font-size:.6875rem;font-family:var(--mono);color:var(--teal);min-width:6rem;flex-shrink:0}\n.token-val{font-size:.6875rem;font-family:var(--mono);color:var(--dim);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.token-actions{display:flex;gap:.25rem;flex-shrink:0}\n.token-btn{all:unset;display:inline-flex;align-items:center;height:1.25rem;padding:0 .375rem;border-radius:.1875rem;font-size:.5625rem;font-weight:600;cursor:pointer;transition:background .15s;user-select:none}\n.token-btn-use{background:var(--blue);color:#fff}\n.token-btn-use:hover{background:var(--blue2)}\n.token-btn-del{background:var(--bg3);color:var(--muted);border:1px solid var(--border)}\n.token-btn-del:hover{background:#3a3a3a}\n\n/* Form */\n.form-row{display:flex;gap:.375rem}\n.sel{width:5.5rem;height:1.75rem;padding:0 .375rem;border-radius:.25rem;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:.75rem;font-family:var(--mono);cursor:pointer;outline:none;flex-shrink:0}\n.sel:focus{border-color:var(--blue)}\n.inp{flex:1;height:1.75rem;padding:0 .5rem;border-radius:.25rem;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:.75rem;font-family:var(--mono);outline:none;min-width:0}\n.inp:focus{border-color:var(--blue)}\n.btn{all:unset;display:inline-flex;align-items:center;height:1.75rem;padding:0 .75rem;border-radius:.25rem;background:var(--blue);color:#fff;font-size:.75rem;font-weight:600;cursor:pointer;transition:background .15s;user-select:none;white-space:nowrap}\n.btn:hover{background:var(--blue2)}\n.btn:disabled{opacity:.4;cursor:not-allowed}\n\n.form-sections{display:flex;flex-direction:column;gap:.375rem;margin-top:.375rem}\n.field{display:flex;flex-direction:column;gap:.125rem}\n.lbl{font-size:.5625rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}\n.lbl-row{display:flex;align-items:center;justify-content:space-between}\n.sm-btn{all:unset;font-size:.5625rem;font-weight:600;color:var(--blue-text);cursor:pointer;user-select:none}\n.sm-btn:hover{text-decoration:underline}\n.ta{width:100%;padding:.375rem .5rem;border-radius:.25rem;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:.6875rem;font-family:var(--mono);line-height:1.4;resize:vertical;outline:none;flex:1;min-height:3.5rem}\n.ta:focus{border-color:var(--blue)}\n.ta::placeholder{color:var(--dim)}\n\n/* Header rows */\n.hdr-rows{display:flex;flex-direction:column;gap:.25rem}\n.hdr-row{display:flex;gap:.25rem;align-items:center}\n.hdr-input{height:1.5rem;padding:0 .375rem;border-radius:.25rem;border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:.6875rem;font-family:var(--mono);outline:none}\n.hdr-input:focus{border-color:var(--blue)}\n.hdr-name{width:10rem;flex-shrink:0}\n.hdr-val{flex:1;min-width:0}\n.hdr-del{all:unset;display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:.25rem;font-size:.75rem;color:var(--dim);cursor:pointer;flex-shrink:0}\n.hdr-del:hover{color:var(--red);background:rgba(244,135,113,.1)}\n\n/* Callback Banner */\n.callback-banner{padding:.625rem .75rem;background:#1a2332;border:1px solid var(--blue-text);border-radius:.25rem;font-size:.75rem;color:var(--blue-text);flex-shrink:0}\n.callback-banner strong{color:#fff}\n\n/* Response panel */\n.res{flex:1;display:flex;flex-direction:column;border:1px solid var(--border);border-radius:.25rem;background:var(--bg2);overflow:hidden}\n.res-empty{padding:2rem;text-align:center;color:var(--dim);font-size:.8125rem}\n.res-top{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:var(--bg2);border-bottom:1px solid var(--border);flex-shrink:0}\n.badge{display:inline-flex;align-items:center;height:1.25rem;padding:0 .375rem;border-radius:.1875rem;font-family:var(--mono);font-size:.6875rem;font-weight:700;line-height:1}\n.b-2xx{background:var(--teal);color:var(--bg)}\n.b-3xx{background:var(--blue-text);color:#fff}\n.b-4xx{background:var(--yellow);color:var(--bg)}\n.b-5xx{background:var(--red);color:var(--bg)}\n.timing{font-size:.75rem;font-family:var(--mono);color:var(--muted)}\n\n/* Response tabs */\n.res-tabs{display:flex;background:var(--bg3);border-bottom:1px solid var(--border);flex-shrink:0}\n.res-tab{all:unset;padding:.375rem .75rem;font-size:.6875rem;font-weight:600;color:var(--muted);cursor:pointer;border-bottom:2px solid transparent;transition:color .15s,border-color .15s;user-select:none}\n.res-tab:hover{color:var(--text)}\n.res-tab.active{color:var(--teal);border-bottom-color:var(--teal)}\n.res-tab-count{font-weight:400;color:var(--dim);margin-left:.25rem}\n\n/* Tab content */\n.res-tab-content{flex:1;overflow:auto;display:none}\n.res-tab-content.active{display:block}\n.res-body{padding:.5rem .75rem}\n.res-body pre{font-family:var(--mono);font-size:.6875rem;line-height:1.5;white-space:pre-wrap;word-break:break-word;margin:0}\n.htable{width:100%;border-collapse:collapse;font-size:.6875rem;font-family:var(--mono)}\n.htable td{padding:.25rem .75rem;border-bottom:1px solid rgba(60,60,60,.2);vertical-align:top}\n.htable td:first-child{color:var(--muted);white-space:nowrap;width:1%;padding-right:.375rem}\n.htable td:last-child{word-break:break-all}\n\n.res-error{padding:1rem;color:var(--red);font-family:var(--mono);font-size:.8125rem;text-align:center;word-break:break-word}\n\n/* Redirect banner (OAuth) */\n.res-redirect{padding:.75rem;background:#1a2332;border-bottom:1px solid var(--border);flex-shrink:0}\n.res-redirect-title{font-size:.75rem;font-weight:700;color:var(--blue-text);margin-bottom:.25rem}\n.res-redirect-url{font-size:.6875rem;font-family:var(--mono);color:var(--muted);word-break:break-all;margin-bottom:.5rem}\n.res-redirect-btn{all:unset;display:inline-flex;align-items:center;height:1.5rem;padding:0 .625rem;border-radius:.25rem;background:var(--blue-text);color:#fff;font-size:.6875rem;font-weight:600;cursor:pointer;transition:background .15s;user-select:none}\n.res-redirect-btn:hover{background:#3d8fd6}\n.res-redirect-notice{font-size:.5625rem;color:var(--dim);margin-top:.375rem}\n\n/* JSON syntax highlighting */\n.j-key{color:#9cdcfe}\n.j-str{color:#ce9178}\n.j-num{color:#b5cea8}\n.j-lit{color:#569cd6}\n</style>\n</head>\n<body>\n\n<header class=\"hdr\">\n <div class=\"hdr-left\">\n <span class=\"logo\">Stoma</span>\n <span class=\"hdr-div\">/</span>\n <span class=\"hdr-title\" id=\"gw-name\"></span>\n </div>\n</header>\n\n<div class=\"routes-bar\" id=\"routes\"></div>\n\n<div class=\"workspace\">\n <!-- Left pane: request -->\n <div class=\"pane-left\">\n <div class=\"token-store\" id=\"token-store\" style=\"display:none\">\n <div class=\"token-header\">\n <span class=\"token-title\">Saved Tokens</span>\n <button class=\"sm-btn\" id=\"clear-tokens\" type=\"button\">Clear All</button>\n </div>\n <div id=\"token-list\"></div>\n </div>\n\n <form id=\"form\" autocomplete=\"off\">\n <div class=\"form-row\">\n <select class=\"sel\" id=\"method\">\n <option>GET</option><option>POST</option><option>PUT</option><option>PATCH</option><option>DELETE</option>\n </select>\n <input class=\"inp\" id=\"path\" placeholder=\"/path\" value=\"/\" />\n <button class=\"btn\" type=\"submit\" id=\"send\">Send</button>\n </div>\n <div class=\"form-sections\">\n <div class=\"field\">\n <div class=\"lbl-row\">\n <label class=\"lbl\">Headers</label>\n <button class=\"sm-btn\" id=\"add-header\" type=\"button\">+ Add</button>\n </div>\n <div class=\"hdr-rows\" id=\"header-rows\"></div>\n </div>\n <div class=\"field\" style=\"flex:1;display:flex;flex-direction:column\">\n <label class=\"lbl\" for=\"body\">Body</label>\n <textarea class=\"ta\" id=\"body\" placeholder='{\"key\":\"value\"}'></textarea>\n </div>\n </div>\n </form>\n\n <div class=\"callback-banner\" id=\"callback-banner\" style=\"display:none\"></div>\n </div>\n\n <!-- Right pane: response -->\n <div class=\"pane-right\">\n <div class=\"res\" id=\"response\">\n <div class=\"res-empty\">Send a request to see the response</div>\n </div>\n </div>\n</div>\n\n<script>\nvar registry = ${registryJson};\ndocument.getElementById(\"gw-name\").textContent = registry.gatewayName || \"Playground\";\n\n// ── Route chips ──\n\nvar routesEl = document.getElementById(\"routes\");\nfor (var ri = 0; ri < registry.routes.length; ri++) {\n var route = registry.routes[ri];\n for (var mi = 0; mi < route.methods.length; mi++) {\n (function(m, path) {\n var chip = document.createElement(\"button\");\n chip.type = \"button\";\n chip.className = \"chip\";\n chip.innerHTML = '<span class=\"method m-' + m.toLowerCase() + '\">' + m + '</span><span>' + esc(path) + '</span>';\n chip.onclick = function() {\n document.getElementById(\"method\").value = m;\n document.getElementById(\"path\").value = path;\n document.getElementById(\"form\").requestSubmit();\n };\n routesEl.appendChild(chip);\n })(route.methods[mi], route.path.replace(/\\\\*$/, \"\"));\n }\n}\n\n// ── Header table management ──\n\nvar headerRowsEl = document.getElementById(\"header-rows\");\n\nfunction addHeaderRow(name, value) {\n var row = document.createElement(\"div\");\n row.className = \"hdr-row\";\n var nameInp = document.createElement(\"input\");\n nameInp.className = \"hdr-input hdr-name\";\n nameInp.placeholder = \"Header name\";\n nameInp.value = name || \"\";\n var valInp = document.createElement(\"input\");\n valInp.className = \"hdr-input hdr-val\";\n valInp.placeholder = \"Value\";\n valInp.value = value || \"\";\n var del = document.createElement(\"button\");\n del.type = \"button\";\n del.className = \"hdr-del\";\n del.textContent = \"\\\\u00d7\";\n del.onclick = function() { row.remove(); };\n row.appendChild(nameInp);\n row.appendChild(valInp);\n row.appendChild(del);\n headerRowsEl.appendChild(row);\n return row;\n}\n\nfunction getHeaders() {\n var headers = {};\n var rows = headerRowsEl.querySelectorAll(\".hdr-row\");\n for (var i = 0; i < rows.length; i++) {\n var inputs = rows[i].querySelectorAll(\"input\");\n var n = inputs[0].value.trim();\n var v = inputs[1].value.trim();\n if (n) headers[n] = v;\n }\n return headers;\n}\n\nfunction setHeader(name, value) {\n var rows = headerRowsEl.querySelectorAll(\".hdr-row\");\n var lowerName = name.toLowerCase();\n for (var i = 0; i < rows.length; i++) {\n var inputs = rows[i].querySelectorAll(\"input\");\n if (inputs[0].value.trim().toLowerCase() === lowerName) {\n inputs[1].value = value;\n return;\n }\n }\n addHeaderRow(name, value);\n}\n\ndocument.getElementById(\"add-header\").onclick = function() { addHeaderRow(); };\n\n// ── Token store (localStorage) ──\n\nvar TOKEN_KEY = \"stoma-playground-tokens\";\n\nfunction loadTokens() {\n try {\n var raw = localStorage.getItem(TOKEN_KEY);\n return raw ? JSON.parse(raw) : [];\n } catch (e) { return []; }\n}\n\nfunction persistTokens(tokens) {\n try { localStorage.setItem(TOKEN_KEY, JSON.stringify(tokens)); } catch (e) {}\n}\n\nfunction saveToken(label, value) {\n var tokens = loadTokens();\n for (var i = 0; i < tokens.length; i++) {\n if (tokens[i].label === label) {\n tokens[i].value = value;\n persistTokens(tokens);\n renderTokenStore();\n return;\n }\n }\n tokens.push({ label: label, value: value });\n persistTokens(tokens);\n renderTokenStore();\n}\n\nfunction removeToken(idx) {\n var tokens = loadTokens();\n tokens.splice(idx, 1);\n persistTokens(tokens);\n renderTokenStore();\n}\n\nfunction clearTokens() {\n persistTokens([]);\n renderTokenStore();\n}\n\nfunction applyToken(idx) {\n var tokens = loadTokens();\n if (tokens[idx]) setHeader(\"Authorization\", \"Bearer \" + tokens[idx].value);\n}\n\nfunction renderTokenStore() {\n var tokens = loadTokens();\n var storeEl = document.getElementById(\"token-store\");\n var listEl = document.getElementById(\"token-list\");\n if (!tokens.length) {\n storeEl.style.display = \"none\";\n listEl.innerHTML = \"\";\n return;\n }\n storeEl.style.display = \"\";\n var html = \"\";\n for (var i = 0; i < tokens.length; i++) {\n var shortVal = tokens[i].value.length > 40 ? tokens[i].value.slice(0, 40) + \"\\\\u2026\" : tokens[i].value;\n html +=\n '<div class=\"token-item\">' +\n '<span class=\"token-name\">' + esc(tokens[i].label) + '</span>' +\n '<span class=\"token-val\">' + esc(shortVal) + '</span>' +\n '<span class=\"token-actions\">' +\n '<button class=\"token-btn token-btn-use\" data-idx=\"' + i + '\">Use</button>' +\n '<button class=\"token-btn token-btn-del\" data-idx=\"' + i + '\">\\\\u00d7</button>' +\n '</span>' +\n '</div>';\n }\n listEl.innerHTML = html;\n listEl.querySelectorAll(\".token-btn-use\").forEach(function(btn) {\n btn.onclick = function() { applyToken(Number(btn.dataset.idx)); };\n });\n listEl.querySelectorAll(\".token-btn-del\").forEach(function(btn) {\n btn.onclick = function() { removeToken(Number(btn.dataset.idx)); };\n });\n}\n\ndocument.getElementById(\"clear-tokens\").onclick = clearTokens;\n\n// Restore tokens on load\nrenderTokenStore();\nvar initTokens = loadTokens();\nif (initTokens.length) applyToken(initTokens.length - 1);\n\n// ── Token auto-detection from responses ──\n\nfunction detectAndSaveTokens(data) {\n if (!data || !data.body) return;\n try {\n var parsed = JSON.parse(data.body);\n var token = parsed.access_token || parsed.token;\n if (typeof token === \"string\" && token.length > 0) {\n var label = parsed.token_type ? parsed.token_type + \"_token\" : \"access_token\";\n saveToken(label, token);\n setHeader(\"Authorization\", \"Bearer \" + token);\n }\n } catch (e) {}\n}\n\n// ── Send request ──\n//\n// Direct fetch to the gateway (same origin). For redirect responses the\n// browser returns an opaque response (redirect:\"manual\"), so we fall back\n// to the server-side proxy which can read the full 3xx details.\n\nfunction sendViaProxy(method, path, headers, bodyText) {\n return fetch(\"/__playground/send\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ method: method, path: path, headers: headers, body: bodyText || undefined }),\n }).then(function(proxyRes) {\n if (!proxyRes.ok) {\n return proxyRes.json().catch(function() { return null; }).then(function(err) {\n renderError(err && err.error ? err.error : \"Request failed: \" + proxyRes.status);\n });\n }\n return proxyRes.json().then(function(data) {\n if (data.error) renderError(data.error);\n else { renderResponse(data); detectAndSaveTokens(data); }\n });\n });\n}\n\ndocument.getElementById(\"form\").addEventListener(\"submit\", function(e) {\n e.preventDefault();\n var callbackBanner = document.getElementById(\"callback-banner\");\n callbackBanner.style.display = \"none\";\n\n var method = document.getElementById(\"method\").value;\n var path = document.getElementById(\"path\").value;\n var bodyText = document.getElementById(\"body\").value;\n var sendBtn = document.getElementById(\"send\");\n var headers = getHeaders();\n\n sendBtn.disabled = true;\n sendBtn.textContent = \"Sending\\\\u2026\";\n\n var start = performance.now();\n var init = { method: method, headers: headers, redirect: \"manual\" };\n if (bodyText && method !== \"GET\" && method !== \"HEAD\") init.body = bodyText;\n\n fetch(path, init).then(function(res) {\n // Opaque redirect — fall back to proxy for full 3xx details\n if (res.type === \"opaqueredirect\") return null;\n\n var status = res.status;\n var statusText = res.statusText;\n var resHeaders = {};\n res.headers.forEach(function(v, k) { resHeaders[k] = v; });\n\n return res.text().then(function(bodyStr) {\n return { status: status, statusText: statusText, headers: resHeaders, body: bodyStr, elapsed: performance.now() - start };\n });\n }).catch(function() {\n return null;\n }).then(function(data) {\n if (data) {\n renderResponse(data);\n detectAndSaveTokens(data);\n return;\n }\n return sendViaProxy(method, path, headers, bodyText);\n }).catch(function(err) {\n renderError(err.message || \"Request failed\");\n }).finally(function() {\n sendBtn.disabled = false;\n sendBtn.textContent = \"Send\";\n });\n});\n\n// ── Render helpers ──\n\nfunction escHtml(s) {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\n}\n\nfunction highlightJson(prettyStr) {\n var s = escHtml(prettyStr);\n // Keys\n s = s.replace(/\"([^\"\\\\\\\\]*(?:\\\\\\\\.[^\"\\\\\\\\]*)*)\"s*:/g, '<span class=\"j-key\">\"$1\"</span>:');\n // String values (after : or in arrays after , or [)\n s = s.replace(/:\\\\s*\"([^\"\\\\\\\\]*(?:\\\\\\\\.[^\"\\\\\\\\]*)*)\"/g, ': <span class=\"j-str\">\"$1\"</span>');\n // Numbers\n s = s.replace(/:\\\\s*(-?\\\\d+(?:\\\\.\\\\d+)?(?:[eE][+-]?\\\\d+)?)/g, ': <span class=\"j-num\">$1</span>');\n // Booleans and null\n s = s.replace(/:\\\\s*(true|false|null)\\\\b/g, ': <span class=\"j-lit\">$1</span>');\n return s;\n}\n\nfunction renderResponse(ref) {\n var status = ref.status, statusText = ref.statusText, headers = ref.headers, body = ref.body, elapsed = ref.elapsed;\n var cls = status < 300 ? \"b-2xx\" : status < 400 ? \"b-3xx\" : status < 500 ? \"b-4xx\" : \"b-5xx\";\n\n var rawBody = body || \"\";\n var prettyBody = rawBody;\n var isJson = false;\n try {\n prettyBody = JSON.stringify(JSON.parse(rawBody), null, 2);\n isJson = true;\n } catch (e) {}\n\n var headerCount = 0;\n var headerRows = \"\";\n for (var k in headers) {\n if (headers.hasOwnProperty(k)) {\n headerCount++;\n headerRows += \"<tr><td>\" + esc(k) + \"</td><td>\" + esc(String(headers[k])) + \"</td></tr>\";\n }\n }\n\n // Redirect banner (OAuth flows)\n var redirectBanner = \"\";\n var locationUrl = headers && headers.location;\n if (status >= 300 && status < 400 && locationUrl && !locationUrl.startsWith(\"/\")) {\n lastRedirectUrl = locationUrl;\n redirectBanner =\n '<div class=\"res-redirect\">' +\n '<div class=\"res-redirect-title\">Redirect Detected</div>' +\n '<div class=\"res-redirect-url\">' + esc(locationUrl) + '</div>' +\n '<button class=\"res-redirect-btn\" onclick=\"openAuthPopup(lastRedirectUrl)\">Open Authorization</button>' +\n '<div class=\"res-redirect-notice\">Opens in a popup. After authorizing, the callback parameters will be sent back here.</div>' +\n '</div>';\n }\n\n var prettyContent = isJson\n ? highlightJson(prettyBody)\n : escHtml(prettyBody);\n\n var resEl = document.getElementById(\"response\");\n resEl.innerHTML =\n '<div class=\"res-top\">' +\n '<span class=\"badge ' + cls + '\">' + status + \" \" + esc(statusText || \"\") + '</span>' +\n '<span class=\"timing\">' + (elapsed || 0).toFixed(1) + ' ms</span>' +\n '</div>' +\n redirectBanner +\n '<div class=\"res-tabs\">' +\n '<button class=\"res-tab active\" data-tab=\"pretty\">Pretty</button>' +\n '<button class=\"res-tab\" data-tab=\"raw\">Raw</button>' +\n '<button class=\"res-tab\" data-tab=\"headers\">Headers<span class=\"res-tab-count\">(' + headerCount + ')</span></button>' +\n '</div>' +\n '<div class=\"res-tab-content active\" data-tab=\"pretty\"><div class=\"res-body\"><pre>' + prettyContent + '</pre></div></div>' +\n '<div class=\"res-tab-content\" data-tab=\"raw\"><div class=\"res-body\"><pre>' + esc(rawBody) + '</pre></div></div>' +\n '<div class=\"res-tab-content\" data-tab=\"headers\"><table class=\"htable\">' + headerRows + '</table></div>';\n\n // Tab switching\n resEl.querySelectorAll(\".res-tab\").forEach(function(tab) {\n tab.onclick = function() {\n var target = tab.dataset.tab;\n resEl.querySelectorAll(\".res-tab\").forEach(function(t) { t.classList.toggle(\"active\", t.dataset.tab === target); });\n resEl.querySelectorAll(\".res-tab-content\").forEach(function(c) { c.classList.toggle(\"active\", c.dataset.tab === target); });\n };\n });\n}\n\nfunction renderError(msg) {\n document.getElementById(\"response\").innerHTML = '<div class=\"res-error\">' + esc(msg) + '</div>';\n}\n\nfunction esc(s) {\n var d = document.createElement(\"div\");\n d.textContent = s;\n return d.innerHTML;\n}\n\n// ── OAuth popup ──\n\nvar lastRedirectUrl = \"\";\nvar oauthPopup = null;\nfunction openAuthPopup(url) {\n if (oauthPopup && !oauthPopup.closed) oauthPopup.close();\n oauthPopup = window.open(url, \"stoma-oauth\", \"width=600,height=700\");\n}\n\nwindow.addEventListener(\"message\", function(event) {\n if (event.origin !== window.location.origin) return;\n if (!event.data || event.data.type !== \"stoma-oauth-callback\") return;\n var params = event.data.params;\n if (!params) return;\n\n var callbackPath = \"/auth/callback\";\n for (var i = 0; i < registry.routes.length; i++) {\n if (registry.routes[i].path.toLowerCase().indexOf(\"callback\") !== -1) {\n callbackPath = registry.routes[i].path;\n break;\n }\n }\n\n var qs = new URLSearchParams(params).toString();\n document.getElementById(\"method\").value = \"GET\";\n document.getElementById(\"path\").value = callbackPath + \"?\" + qs;\n\n var banner = document.getElementById(\"callback-banner\");\n banner.innerHTML = 'OAuth parameters received. Click <strong>Send</strong> to complete authorization.';\n banner.style.display = \"\";\n\n document.getElementById(\"response\").innerHTML =\n '<div class=\"res-empty\">OAuth callback parameters prefilled. Click Send to exchange for a token.</div>';\n});\n</script>\n</body>\n</html>`;\n}\n\nfunction esc(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n}\n","/**\n * Generates a self-contained HTML page that relays OAuth callback\n * query parameters to the playground opener window via postMessage.\n *\n * Served by `wrapWithPlayground` when a browser navigates to a\n * callback route (e.g., after the OAuth provider redirects the popup).\n */\nexport function oauthRelayHtml(url: URL): string {\n const paramsJson = JSON.stringify(\n Object.fromEntries(url.searchParams.entries())\n ).replace(/</g, \"\\\\u003c\");\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head><meta charset=\"utf-8\" /><title>Authorization Complete</title></head>\n<body style=\"font-family:system-ui;padding:2rem;text-align:center;background:#1e1e1e;color:#d4d4d4\">\n<h3>Authorization Complete</h3>\n<p id=\"msg\">Sending parameters to the playground\\u2026</p>\n<script>\n(function() {\n var params = ${paramsJson};\n if (window.opener) {\n window.opener.postMessage({ type: \"stoma-oauth-callback\", params: params }, location.origin);\n document.getElementById(\"msg\").textContent = \"Parameters sent. You can close this window.\";\n } else {\n document.getElementById(\"msg\").textContent = \"No opener window found. Please copy the URL and paste it into the playground manually.\";\n }\n})();\n</script>\n</body>\n</html>`;\n}\n","import type { GatewayRegistry } from \"../gateway/types.js\";\nimport { playgroundHtml } from \"./html.js\";\nimport { oauthRelayHtml } from \"./oauth-relay.js\";\n\n/**\n * Wrap a gateway's fetch function with playground routes.\n *\n * Intercepts playground paths before delegating to the original gateway fetch.\n *\n * - `/__playground` → serves the playground HTML UI\n * - `/__playground/registry` → returns the gateway registry as JSON\n * - `/__playground/send` → redirect fallback: re-executes a request server-side\n * to capture full 3xx response details that browser\n * fetch cannot access (opaque redirect limitation)\n */\nexport function wrapWithPlayground(\n gatewayFetch: (request: Request) => Response | Promise<Response>,\n registry: GatewayRegistry\n): (request: Request) => Response | Promise<Response> {\n const html = playgroundHtml(registry);\n\n // Pre-compute callback route paths for OAuth relay interception\n const callbackPaths = registry.routes\n .map((r) => r.path)\n .filter((p) => p.toLowerCase().includes(\"callback\"));\n\n return async (request: Request) => {\n const url = new URL(request.url);\n\n if (url.pathname === \"/__playground\") {\n return new Response(html, {\n headers: { \"content-type\": \"text/html; charset=utf-8\" },\n });\n }\n\n if (url.pathname === \"/__playground/registry\") {\n return Response.json(registry);\n }\n\n if (url.pathname === \"/__playground/send\" && request.method === \"POST\") {\n return handlePlaygroundSend(request, gatewayFetch, url.origin);\n }\n\n // OAuth relay: when the OAuth provider redirects the popup to a callback\n // route, serve a relay page that sends the params back to the playground\n // via postMessage instead of letting the gateway handle the browser request.\n const isNavigation = request.headers.get(\"accept\")?.includes(\"text/html\");\n if (isNavigation && url.search) {\n const isCallbackRoute = callbackPaths.some(\n (p) =>\n url.pathname === p || url.pathname.startsWith(p.replace(/\\*$/, \"\"))\n );\n if (isCallbackRoute) {\n return new Response(oauthRelayHtml(url), {\n headers: { \"content-type\": \"text/html; charset=utf-8\" },\n });\n }\n }\n\n return gatewayFetch(request);\n };\n}\n\n/**\n * Execute a request against the gateway in-process and return the full\n * response details as JSON. This avoids browser fetch limitations\n * (redirect following, CORS, opaque responses).\n */\nasync function handlePlaygroundSend(\n request: Request,\n gatewayFetch: (request: Request) => Response | Promise<Response>,\n origin: string\n): Promise<Response> {\n try {\n const payload = (await request.json()) as {\n method: string;\n path: string;\n headers?: Record<string, string>;\n body?: string;\n };\n\n // Build a Request as if the browser had made it directly\n const targetUrl = new URL(payload.path, origin).href;\n const init: RequestInit = {\n method: payload.method,\n headers: payload.headers ?? {},\n };\n if (payload.body && payload.method !== \"GET\" && payload.method !== \"HEAD\") {\n init.body = payload.body;\n }\n\n const gatewayRequest = new Request(targetUrl, init);\n const start = performance.now();\n const res = await gatewayFetch(gatewayRequest);\n const elapsed = performance.now() - start;\n\n const body = await res.text();\n const headers: Record<string, string> = {};\n res.headers.forEach((v, k) => {\n headers[k] = v;\n });\n\n return Response.json({\n status: res.status,\n statusText: res.statusText,\n headers,\n body,\n elapsed,\n });\n } catch (err) {\n return Response.json(\n {\n error: err instanceof Error ? err.message : String(err),\n },\n { status: 500 }\n );\n }\n}\n","import type { Server } from \"node:http\";\nimport { serve } from \"@hono/node-server\";\n\nexport interface StartServerOptions {\n fetch: (request: Request) => Response | Promise<Response>;\n port: number;\n hostname: string;\n}\n\nexport function startServer(options: StartServerOptions): Promise<Server> {\n const { fetch, port, hostname } = options;\n\n return new Promise<Server>((resolve, reject) => {\n const server = serve({ fetch, port, hostname }, () =>\n resolve(server as unknown as Server)\n );\n\n // Handle EADDRINUSE before the callback fires\n (server as unknown as Server).on?.(\n \"error\",\n (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n reject(\n new Error(\n `Port ${port} is already in use. Try a different port with --port <number>`\n )\n );\n } else {\n reject(err);\n }\n }\n );\n });\n}\n","export interface Logger {\n info: (message: string) => void;\n verbose: (message: string) => void;\n error: (message: string) => void;\n}\n\nexport function createLogger(verbose: boolean): Logger {\n return {\n info: (message: string) => console.log(message),\n verbose: verbose ? (message: string) => console.log(message) : () => {},\n error: (message: string) => console.error(message),\n };\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nlet cachedVersion: string | undefined;\n\nexport function getVersion(): string {\n if (cachedVersion) return cachedVersion;\n\n try {\n const dir = dirname(fileURLToPath(import.meta.url));\n // Try both: bundled (dist/bin.js → ../package.json) and source (src/utils/version.ts → ../../package.json)\n for (const rel of [\"..\", \"../..\"]) {\n const candidate = resolve(dir, rel, \"package.json\");\n if (existsSync(candidate)) {\n const pkg = JSON.parse(readFileSync(candidate, \"utf-8\"));\n if (pkg.name === \"@vivero/stoma-cli\") {\n cachedVersion = (pkg.version as string) ?? \"0.0.0\";\n return cachedVersion!;\n }\n }\n }\n cachedVersion = \"0.0.0\";\n } catch {\n cachedVersion = \"0.0.0\";\n }\n\n return cachedVersion!;\n}\n","import { runCli } from \"./cli.js\";\n\nrunCli(process.argv.slice(2));\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBA4LjB,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["path","require","fetch","resolve","path","fetch","resolve","existsSync"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vivero/stoma-cli",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.7",
|
|
4
4
|
"description": "CLI for running Stoma API gateways locally — load a gateway file and serve it on a Node.js HTTP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -56,8 +56,6 @@
|
|
|
56
56
|
}
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
|
-
"prepack": "node ../../scripts/resolve-workspace-deps.mjs",
|
|
60
|
-
"postpack": "node ../../scripts/resolve-workspace-deps.mjs --restore",
|
|
61
59
|
"dev": "tsx src/bin.ts",
|
|
62
60
|
"build": "tsup",
|
|
63
61
|
"typecheck": "tsc --noEmit",
|
|
@@ -68,7 +66,7 @@
|
|
|
68
66
|
},
|
|
69
67
|
"dependencies": {
|
|
70
68
|
"@hono/node-server": "^1.14.0",
|
|
71
|
-
"@vivero/stoma": "0.1.0-rc.
|
|
69
|
+
"@vivero/stoma": "0.1.0-rc.11",
|
|
72
70
|
"clipanion": "^4.0.0-rc.4",
|
|
73
71
|
"esbuild": "^0.25.0",
|
|
74
72
|
"hono": "^4.7.0"
|
|
@@ -81,4 +79,4 @@
|
|
|
81
79
|
"typescript": "^5.9.3",
|
|
82
80
|
"vitest": "3.2.4"
|
|
83
81
|
}
|
|
84
|
-
}
|
|
82
|
+
}
|