@mesh-tech/mesh-cli 0.36.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/assets/docs-preview.css +1599 -0
  2. package/dist/bin/mesh.auth-preflight-AQJ5TQES.js +48 -0
  3. package/dist/bin/mesh.auth-preflight-AQJ5TQES.js.map +6 -0
  4. package/dist/bin/mesh.capture-history-A4IAVVIR.js +38 -0
  5. package/dist/bin/mesh.capture-history-A4IAVVIR.js.map +6 -0
  6. package/dist/bin/mesh.chunk-2QIVBQX5.js +647 -0
  7. package/dist/bin/mesh.chunk-2QIVBQX5.js.map +6 -0
  8. package/dist/bin/mesh.chunk-2YRWJH5X.js +1747 -0
  9. package/dist/bin/mesh.chunk-2YRWJH5X.js.map +6 -0
  10. package/dist/bin/mesh.chunk-BF2SH6XT.js +303 -0
  11. package/dist/bin/mesh.chunk-BF2SH6XT.js.map +6 -0
  12. package/dist/bin/mesh.chunk-F4654EPX.js +383 -0
  13. package/dist/bin/mesh.chunk-F4654EPX.js.map +6 -0
  14. package/dist/bin/mesh.chunk-HPYZWPRM.js +1146 -0
  15. package/dist/bin/mesh.chunk-HPYZWPRM.js.map +6 -0
  16. package/dist/bin/mesh.chunk-I6NXLXGC.js +7715 -0
  17. package/dist/bin/mesh.chunk-I6NXLXGC.js.map +6 -0
  18. package/dist/bin/mesh.chunk-IQOBTL42.js +60 -0
  19. package/dist/bin/mesh.chunk-IQOBTL42.js.map +6 -0
  20. package/dist/bin/mesh.chunk-IU2IWH6O.js +835 -0
  21. package/dist/bin/mesh.chunk-IU2IWH6O.js.map +6 -0
  22. package/dist/bin/mesh.chunk-KN4BZOYH.js +187 -0
  23. package/dist/bin/mesh.chunk-KN4BZOYH.js.map +6 -0
  24. package/dist/bin/mesh.chunk-LMYPTJ2F.js +10092 -0
  25. package/dist/bin/mesh.chunk-LMYPTJ2F.js.map +6 -0
  26. package/dist/bin/mesh.chunk-MCBMHWXP.js +837 -0
  27. package/dist/bin/mesh.chunk-MCBMHWXP.js.map +6 -0
  28. package/dist/bin/mesh.chunk-OW5EMVG5.js +507 -0
  29. package/dist/bin/mesh.chunk-OW5EMVG5.js.map +6 -0
  30. package/dist/bin/mesh.chunk-TZAGJTKU.js +217 -0
  31. package/dist/bin/mesh.chunk-TZAGJTKU.js.map +6 -0
  32. package/dist/bin/mesh.chunk-WFFDF346.js +59 -0
  33. package/dist/bin/mesh.chunk-WFFDF346.js.map +6 -0
  34. package/dist/bin/mesh.cli-reference-H3TOPIET.js +158 -0
  35. package/dist/bin/mesh.cli-reference-H3TOPIET.js.map +6 -0
  36. package/dist/bin/mesh.convert-7N2H2SVK.js +274 -0
  37. package/dist/bin/mesh.convert-7N2H2SVK.js.map +6 -0
  38. package/dist/bin/mesh.create-app-NGXACFAU.js +39 -0
  39. package/dist/bin/mesh.create-app-NGXACFAU.js.map +6 -0
  40. package/dist/bin/mesh.dev-token-server-YP2VBP2S.js +61 -0
  41. package/dist/bin/mesh.dev-token-server-YP2VBP2S.js.map +6 -0
  42. package/dist/bin/mesh.js +20 -26418
  43. package/dist/bin/mesh.js.map +3 -3
  44. package/dist/bin/mesh.login-7SIAKFXJ.js +91 -0
  45. package/dist/bin/mesh.login-7SIAKFXJ.js.map +6 -0
  46. package/dist/bin/mesh.platform-setup-CJXE3BCP.js +37 -0
  47. package/dist/bin/mesh.platform-setup-CJXE3BCP.js.map +6 -0
  48. package/dist/bin/mesh.preview-RJGTCTY3.js +2410 -0
  49. package/dist/bin/mesh.preview-RJGTCTY3.js.map +6 -0
  50. package/dist/bin/mesh.program-BIDQBSSI.js +23 -0
  51. package/dist/bin/mesh.program-BIDQBSSI.js.map +6 -0
  52. package/dist/bin/mesh.registry-7P4MGXEK.js +47 -0
  53. package/dist/bin/mesh.registry-7P4MGXEK.js.map +6 -0
  54. package/dist/bin/mesh.registry-identity-JW4KO7N7.js +42 -0
  55. package/dist/bin/mesh.registry-identity-JW4KO7N7.js.map +6 -0
  56. package/dist/bin/mesh.skills-MNDSMBIE.js +23 -0
  57. package/dist/bin/mesh.skills-MNDSMBIE.js.map +6 -0
  58. package/dist/bin/mesh.src-5D5DFSLX.js +1302 -0
  59. package/dist/bin/mesh.src-5D5DFSLX.js.map +6 -0
  60. package/dist/bin/mesh.src-G7TJBLNJ.js +40 -0
  61. package/dist/bin/mesh.src-G7TJBLNJ.js.map +6 -0
  62. package/dist/bin/mesh.stack-EFTFAOCV.js +64 -0
  63. package/dist/bin/mesh.stack-EFTFAOCV.js.map +6 -0
  64. package/dist/bin/mesh.start-MPQFIEMH.js +454 -0
  65. package/dist/bin/mesh.start-MPQFIEMH.js.map +6 -0
  66. package/dist/bin/mesh.wizard-ZJE4S6PC.js +500 -0
  67. package/dist/bin/mesh.wizard-ZJE4S6PC.js.map +6 -0
  68. package/dist/build-info.json +2 -2
  69. package/dist/src/commands/app-check.d.ts +2 -1
  70. package/dist/src/commands/app-check.js +32 -0
  71. package/dist/src/commands/create-app.d.ts +0 -1
  72. package/dist/src/commands/create-app.js +0 -4
  73. package/dist/src/commands/dev.d.ts +6 -0
  74. package/dist/src/commands/dev.js +152 -3
  75. package/dist/src/commands/docs.js +33 -0
  76. package/dist/src/commands/local/act-as-local.d.ts +5 -0
  77. package/dist/src/commands/local/act-as-local.js +41 -0
  78. package/dist/src/commands/local/auth-provision.d.ts +1 -0
  79. package/dist/src/commands/local/auth-provision.js +1 -0
  80. package/dist/src/commands/local/index.js +4 -0
  81. package/dist/src/commands/local/local-api-key.d.ts +44 -0
  82. package/dist/src/commands/local/local-api-key.js +115 -0
  83. package/dist/src/commands/local/local-spicedb.d.ts +95 -0
  84. package/dist/src/commands/local/local-spicedb.js +304 -0
  85. package/dist/src/commands/local/publish-docs.d.ts +81 -0
  86. package/dist/src/commands/local/publish-docs.js +328 -0
  87. package/dist/src/commands/local/seed-zitadel.js +2 -0
  88. package/dist/src/commands/temporal.js +191 -61
  89. package/dist/src/commands/temporal.js.map +3 -3
  90. package/dist/src/docs/convert.d.ts +35 -0
  91. package/dist/src/docs/convert.js +276 -0
  92. package/dist/src/docs/preview-source.d.ts +13 -0
  93. package/dist/src/docs/preview-source.js +58 -0
  94. package/dist/src/docs/preview.d.ts +32 -0
  95. package/dist/src/docs/preview.js +279 -0
  96. package/dist/src/utils/scaffold-versions.d.ts +0 -1
  97. package/dist/src/utils/scaffold-versions.js +0 -1
  98. package/fragments/agents/AGENTS.md.hbs +27 -27
  99. package/fragments/base/README.md.hbs +5 -2
  100. package/fragments/base/index.ts.hbs +19 -33
  101. package/fragments/base/package.json.hbs +4 -2
  102. package/fragments/service/api/openapi.json.hbs +1 -1
  103. package/fragments/service/docs/docs.yaml.hbs +10 -0
  104. package/fragments/service/docs/pages/getting-started.md.hbs +6 -9
  105. package/package.json +19 -3
  106. package/skills/mesh/SKILL.md +2 -2
  107. package/stack/docker-compose.hub.yml +6 -0
@@ -0,0 +1,58 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { basename, join, resolve } from "node:path";
3
+ import { buildDocsSource } from "@mesh-tech/api-registry/docs-source-build";
4
+ export function resolveDocsConfigPath(target) {
5
+ const abs = resolve(target);
6
+ let stat;
7
+ try {
8
+ stat = statSync(abs);
9
+ }
10
+ catch {
11
+ throw new Error(`No docs source at ${abs} — pass the directory that holds docs.yaml, or docs.yaml itself`);
12
+ }
13
+ if (stat.isFile()) {
14
+ if (basename(abs) !== "docs.yaml")
15
+ throw new Error(`${abs} is not a docs.yaml`);
16
+ return abs;
17
+ }
18
+ const configPath = join(abs, "docs.yaml");
19
+ try {
20
+ if (statSync(configPath).isFile())
21
+ return configPath;
22
+ }
23
+ catch {
24
+ }
25
+ throw new Error(`No docs.yaml in ${abs}`);
26
+ }
27
+ function missingEventsCatalogProblem(service) {
28
+ return `docs.yaml names events "${service}", but no events catalog was supplied`;
29
+ }
30
+ export function readPreviewSource(configPath, opts = {}) {
31
+ const built = buildDocsSource({ configPath, ...(opts.specPath ? { specPath: resolve(opts.specPath) } : {}) });
32
+ const { config } = built.bundle;
33
+ const notes = [];
34
+ let problems = built.problems;
35
+ if (config.events) {
36
+ const expected = missingEventsCatalogProblem(config.events);
37
+ if (problems.includes(expected)) {
38
+ problems = problems.filter((p) => p !== expected);
39
+ notes.push(`events: the catalog of ${config.events} is built from the app's event definitions at deploy, so the preview has no Events section`);
40
+ }
41
+ }
42
+ notes.push("availability: each operation's availability is published by the deploy, so the preview shows none");
43
+ let specText;
44
+ try {
45
+ specText = readFileSync(built.specPath, "utf8");
46
+ }
47
+ catch {
48
+ specText = undefined;
49
+ }
50
+ return {
51
+ configPath: resolve(configPath),
52
+ specPath: built.specPath,
53
+ bundle: built.bundle,
54
+ ...(specText !== undefined ? { specText } : {}),
55
+ problems,
56
+ notes,
57
+ };
58
+ }
@@ -0,0 +1,32 @@
1
+ import { type DocsEngine } from "@mesh-tech/api-docs-engine";
2
+ import { type PreviewSource } from "./preview-source.js";
3
+ export declare const PREVIEW_CSS_PATH = "/docs-preview.css";
4
+ export declare function previewCssFile(): string;
5
+ export interface PreviewPage {
6
+ status: number;
7
+ html: string;
8
+ }
9
+ export declare function renderPreviewPage(input: {
10
+ source: PreviewSource;
11
+ engine: DocsEngine | undefined;
12
+ search: URLSearchParams;
13
+ }): PreviewPage;
14
+ export interface PreviewServer {
15
+ url: string;
16
+ port: number;
17
+ close(): Promise<void>;
18
+ }
19
+ export declare function startPreviewServer(args: {
20
+ configPath: string;
21
+ specPath?: string;
22
+ port: number;
23
+ cssFile?: string;
24
+ }): Promise<PreviewServer>;
25
+ export interface DocsPreviewOptions {
26
+ target: string;
27
+ port: string;
28
+ out?: string;
29
+ check: boolean;
30
+ spec?: string;
31
+ }
32
+ export declare function runDocsPreview(opts: DocsPreviewOptions): Promise<boolean>;
@@ -0,0 +1,279 @@
1
+ import { createServer } from "node:http";
2
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
4
+ import * as React from "react";
5
+ import { renderToString } from "react-dom/server";
6
+ import { buildEngine } from "@mesh-tech/api-docs-engine";
7
+ import { docsPage } from "@mesh-tech/api-registry";
8
+ import { DocsSourceView, locationFromParams, locationToParams } from "@mesh-tech/canvas-api-docs/docs";
9
+ import { logError, logInfo, logSuccess, logWarn } from "../utils/log.js";
10
+ import { findPackageRoot } from "../commands/local/stack.js";
11
+ import { readPreviewSource, resolveDocsConfigPath } from "./preview-source.js";
12
+ globalThis.React ??= React;
13
+ export const PREVIEW_CSS_PATH = "/docs-preview.css";
14
+ export function previewCssFile() {
15
+ return join(findPackageRoot(), "assets", "docs-preview.css");
16
+ }
17
+ function displayPath(file) {
18
+ const rel = relative(process.cwd(), file);
19
+ return rel && !rel.startsWith("..") && !isAbsolute(rel) ? rel : file;
20
+ }
21
+ function escapeHtml(text) {
22
+ return text.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
23
+ }
24
+ function engineCache() {
25
+ let last;
26
+ return (specText) => {
27
+ if (specText === undefined)
28
+ return undefined;
29
+ if (last?.text === specText)
30
+ return last.engine;
31
+ let engine;
32
+ try {
33
+ engine = buildEngine(JSON.parse(specText));
34
+ }
35
+ catch {
36
+ engine = undefined;
37
+ }
38
+ last = { text: specText, engine };
39
+ return engine;
40
+ };
41
+ }
42
+ export function renderPreviewPage(input) {
43
+ const { source, engine, search } = input;
44
+ const { bundle } = source;
45
+ const title = bundle.config.title;
46
+ const notices = [];
47
+ let status = 200;
48
+ let location = locationFromParams(search, bundle);
49
+ const askedPage = search.get("page");
50
+ if (askedPage && !(location.kind === "page" && location.page === askedPage)) {
51
+ status = 404;
52
+ notices.push(`This source has no page "${askedPage}".`);
53
+ }
54
+ if (search.get("section") === "events" && !bundle.events) {
55
+ notices.push(bundle.config.events
56
+ ? `The events of ${bundle.config.events} are documented from the catalog the deploy builds; the preview has none.`
57
+ : "This source documents no events.");
58
+ }
59
+ let op;
60
+ if (location.kind === "reference" && location.op) {
61
+ op = engine?.getOp(location.op) ?? undefined;
62
+ if (!op) {
63
+ status = 404;
64
+ notices.push(`The spec has no operation "${location.op}".`);
65
+ location = { kind: "reference" };
66
+ }
67
+ }
68
+ const view = React.createElement(DocsSourceView, {
69
+ bundle,
70
+ title,
71
+ index: engine?.index() ?? [],
72
+ tags: engine?.tags() ?? [],
73
+ location,
74
+ onNavigate: () => { },
75
+ hrefFor: (loc) => `?${locationToParams(loc).toString()}`,
76
+ host: {
77
+ loadOp: (id) => {
78
+ const slice = engine?.getOp(id);
79
+ return slice ? Promise.resolve(slice) : Promise.reject(new Error(`no operation ${id}`));
80
+ },
81
+ },
82
+ ...(op ? { preloadedOp: op } : {}),
83
+ });
84
+ const heading = location.kind === "page"
85
+ ? (docsPage(bundle, location.page)?.title ?? title)
86
+ : location.kind === "events"
87
+ ? "Events"
88
+ : op
89
+ ? (op.summary ?? op.name)
90
+ : ((bundle.config.reference || undefined)?.label ?? "API Reference");
91
+ return {
92
+ status,
93
+ html: previewDocument({
94
+ title: heading === title ? title : `${heading} · ${title}`,
95
+ source,
96
+ notices,
97
+ body: renderToString(view),
98
+ }),
99
+ };
100
+ }
101
+ function previewChrome(source) {
102
+ const where = displayPath(source.configPath);
103
+ return `<header class="border-b bg-muted/40">
104
+ <div class="mx-auto flex max-w-7xl flex-wrap items-center gap-x-4 gap-y-1 px-6 py-3 text-sm">
105
+ <span class="font-semibold">Docs preview</span>
106
+ <code class="font-mono text-xs text-muted-foreground">${escapeHtml(where)}</code>
107
+ <span class="ml-auto text-xs text-muted-foreground" data-preview-try-it>Try-it runs in the Hub — this preview renders the docs only.</span>
108
+ </div>
109
+ </header>`;
110
+ }
111
+ function list(items) {
112
+ return `<ul class="mt-2 list-disc space-y-1 pl-5">${items.map((i) => `<li>${escapeHtml(i)}</li>`).join("")}</ul>`;
113
+ }
114
+ function previewDocument(input) {
115
+ const { source, notices } = input;
116
+ const problems = source.problems.length
117
+ ? `<section role="alert" class="mx-auto max-w-7xl px-6 pt-4" data-preview-problems="${source.problems.length}">
118
+ <div class="rounded-md border border-destructive/50 bg-destructive/5 p-4 text-sm">
119
+ <p class="font-semibold text-destructive">${source.problems.length} problem${source.problems.length === 1 ? "" : "s"} — the deploy refuses this source until ${source.problems.length === 1 ? "it is" : "they are"} fixed</p>
120
+ ${list(source.problems)}
121
+ </div>
122
+ </section>`
123
+ : "";
124
+ const noticeBlock = notices.length
125
+ ? `<section class="mx-auto max-w-7xl px-6 pt-4" data-preview-notices>
126
+ <div class="rounded-md border bg-muted/40 p-4 text-sm">${list(notices)}</div>
127
+ </section>`
128
+ : "";
129
+ const notes = source.notes.length
130
+ ? `<details class="mx-auto max-w-7xl px-6 pt-4 text-xs text-muted-foreground">
131
+ <summary class="cursor-pointer select-none">What the preview does not show</summary>
132
+ ${list(source.notes)}
133
+ </details>`
134
+ : "";
135
+ return `<!doctype html>
136
+ <html lang="en">
137
+ <head>
138
+ <meta charset="utf-8">
139
+ <meta name="viewport" content="width=device-width, initial-scale=1">
140
+ <title>${escapeHtml(input.title)} — docs preview</title>
141
+ <link rel="stylesheet" href="${PREVIEW_CSS_PATH}">
142
+ </head>
143
+ <body class="bg-background text-foreground antialiased">
144
+ ${previewChrome(source)}
145
+ ${problems}
146
+ ${noticeBlock}
147
+ ${notes}
148
+ <main class="mx-auto max-w-7xl px-6 py-6">${input.body}</main>
149
+ </body>
150
+ </html>
151
+ `;
152
+ }
153
+ function errorDocument(configPath, heading, message) {
154
+ return `<!doctype html>
155
+ <html lang="en">
156
+ <head>
157
+ <meta charset="utf-8">
158
+ <meta name="viewport" content="width=device-width, initial-scale=1">
159
+ <title>${escapeHtml(heading)} — docs preview</title>
160
+ <link rel="stylesheet" href="${PREVIEW_CSS_PATH}">
161
+ </head>
162
+ <body class="bg-background text-foreground antialiased">
163
+ ${previewChrome({ configPath })}
164
+ <section role="alert" class="mx-auto max-w-7xl px-6 pt-4">
165
+ <div class="rounded-md border border-destructive/50 bg-destructive/5 p-4 text-sm">
166
+ <p class="font-semibold text-destructive">${escapeHtml(heading)}</p>
167
+ <pre class="mt-2 whitespace-pre-wrap font-mono text-xs">${escapeHtml(message)}</pre>
168
+ </div>
169
+ </section>
170
+ </body>
171
+ </html>
172
+ `;
173
+ }
174
+ export async function startPreviewServer(args) {
175
+ const engineFor = engineCache();
176
+ const cssFile = args.cssFile ?? previewCssFile();
177
+ const message = (error) => (error instanceof Error ? error.message : String(error));
178
+ const pageFor = (search) => {
179
+ let source;
180
+ try {
181
+ source = readPreviewSource(args.configPath, { specPath: args.specPath });
182
+ }
183
+ catch (error) {
184
+ return { status: 500, html: errorDocument(args.configPath, "The source cannot be read", message(error)) };
185
+ }
186
+ try {
187
+ return renderPreviewPage({ source, engine: engineFor(source.specText), search });
188
+ }
189
+ catch (error) {
190
+ return { status: 500, html: errorDocument(args.configPath, "The page cannot be rendered", message(error)) };
191
+ }
192
+ };
193
+ const handle = (req, res) => {
194
+ const url = new URL(req.url ?? "/", "http://localhost");
195
+ if (req.method !== "GET" && req.method !== "HEAD") {
196
+ res.writeHead(405, { allow: "GET, HEAD", "content-type": "text/plain; charset=utf-8" }).end("Method not allowed\n");
197
+ return;
198
+ }
199
+ if (url.pathname === PREVIEW_CSS_PATH) {
200
+ let css;
201
+ try {
202
+ css = readFileSync(cssFile, "utf8");
203
+ }
204
+ catch {
205
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" }).end("Stylesheet not found\n");
206
+ return;
207
+ }
208
+ res.writeHead(200, { "content-type": "text/css; charset=utf-8", "cache-control": "no-cache" }).end(css);
209
+ return;
210
+ }
211
+ if (url.pathname !== "/") {
212
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" }).end("Not found — the preview is served at /\n");
213
+ return;
214
+ }
215
+ const page = pageFor(url.searchParams);
216
+ res.writeHead(page.status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
217
+ res.end(req.method === "HEAD" ? undefined : page.html);
218
+ };
219
+ const server = createServer(handle);
220
+ await new Promise((resolvePromise, reject) => {
221
+ server.once("error", (error) => {
222
+ reject(error.code === "EADDRINUSE"
223
+ ? new Error(`Port ${args.port} is already in use — pass --port <n> for another one (0 picks a free port)`)
224
+ : error);
225
+ });
226
+ server.listen(args.port, "127.0.0.1", () => resolvePromise());
227
+ });
228
+ const address = server.address();
229
+ const port = typeof address === "object" && address ? address.port : args.port;
230
+ return {
231
+ url: `http://localhost:${port}`,
232
+ port,
233
+ close: () => new Promise((resolvePromise) => server.close(() => resolvePromise())),
234
+ };
235
+ }
236
+ function reportProblems(source) {
237
+ if (source.problems.length === 0) {
238
+ logSuccess(`${source.bundle.config.title}: no problems`);
239
+ }
240
+ else {
241
+ logWarn(`${source.bundle.config.title}: ${source.problems.length} problem${source.problems.length === 1 ? "" : "s"} — the deploy refuses this source until ${source.problems.length === 1 ? "it is" : "they are"} fixed:`);
242
+ for (const problem of source.problems)
243
+ console.error(` - ${problem}`);
244
+ }
245
+ for (const note of source.notes)
246
+ logInfo(`note — ${note}`);
247
+ }
248
+ export async function runDocsPreview(opts) {
249
+ const configPath = resolveDocsConfigPath(opts.target);
250
+ if (opts.check || opts.out) {
251
+ const source = readPreviewSource(configPath, { specPath: opts.spec });
252
+ reportProblems(source);
253
+ if (opts.out) {
254
+ const outPath = resolve(opts.out);
255
+ mkdirSync(dirname(outPath), { recursive: true });
256
+ writeFileSync(outPath, `${JSON.stringify(source.bundle, null, 2)}\n`);
257
+ logSuccess(`Docs source bundle → ${displayPath(outPath)}`);
258
+ }
259
+ return !opts.check || source.problems.length === 0;
260
+ }
261
+ const port = Number(opts.port);
262
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
263
+ throw new Error(`--port must be a port number (0 picks a free one), not "${opts.port}"`);
264
+ }
265
+ try {
266
+ reportProblems(readPreviewSource(configPath, { specPath: opts.spec }));
267
+ }
268
+ catch (error) {
269
+ logError(error instanceof Error ? error.message : String(error));
270
+ }
271
+ const server = await startPreviewServer({ configPath, ...(opts.spec ? { specPath: opts.spec } : {}), port });
272
+ logSuccess(`Docs preview → ${server.url}/ (re-reads ${displayPath(configPath)} on every request; Ctrl-C to stop)`);
273
+ await new Promise((resolvePromise) => {
274
+ const stop = () => void server.close().then(resolvePromise);
275
+ process.once("SIGINT", stop);
276
+ process.once("SIGTERM", stop);
277
+ });
278
+ return true;
279
+ }
@@ -2,7 +2,6 @@ export declare const SCAFFOLD_PACKAGE_LINES: {
2
2
  readonly "@mesh-tech/app-kit": "^1.45.0";
3
3
  readonly "@mesh-tech/esm-bundle": "^0.1.1";
4
4
  readonly "@mesh-tech/events-service": "^0.1.0";
5
- readonly "@mesh-tech/events-ui": "^0.1.0";
6
5
  readonly "@mesh-tech/authz": "^1.19.0";
7
6
  };
8
7
  export type ScaffoldPackage = keyof typeof SCAFFOLD_PACKAGE_LINES;
@@ -3,7 +3,6 @@ export const SCAFFOLD_PACKAGE_LINES = {
3
3
  "@mesh-tech/app-kit": "^1.45.0",
4
4
  "@mesh-tech/esm-bundle": "^0.1.1",
5
5
  "@mesh-tech/events-service": "^0.1.0",
6
- "@mesh-tech/events-ui": "^0.1.0",
7
6
  "@mesh-tech/authz": "^1.19.0",
8
7
  };
9
8
  export const SCAFFOLD_TOOLCHAIN = {
@@ -18,33 +18,33 @@ Every mesh app follows 5 steps in `index.ts`:
18
18
  {{#if service}}
19
19
  ## API docs
20
20
 
21
- `api/openapi.json` is the API's OpenAPI document and `docs/pages/` holds its
22
- guides; `index.ts` declares a `mesh.apps.ApiDocs` site from both, reachable
23
- under `mesh dev`. **The Hub docs door is the path a docs site is published
24
- on**: declare the API as a `mesh.apps.AppApiSurface` whose `docs` block builds
25
- the site, and the Hub serves it through that door to the readers the app's
26
- `docs` role admits. Giving the site a hostname of its own instead — `ingress`,
27
- with the `auth` proxy and the in-page `authentication` sign-in — is the
28
- deprecated public shape: it still deploys, as the compatibility path for the
29
- sites that are public today, and warns at every `pulumi preview`. Nothing here
30
- writes one, and a new app has no reason to. A site declared on a
31
- `mesh.apps.VendorApiSurface` (a third-party core's raw reference) is read
32
- through the same door by holders of the Hub's `vendor-docs` add-on only —
33
- never by a partner — and declaring `audience: { roles: [] }` on one fails
34
- the deploy. Add `actAs: true` on an app surface to let Hub operators execute
35
- requests against the deployed API as one of the app's API keys from the Docs
36
- tab (the Hub's act-as playground) — the surface then verifies the Hub's
37
- signed assertion and runs the request as the key. `mesh app check` flags a
38
- docs site put on an ingress (`DOCS_SITE_PUBLIC`). A vendor surface's site is
39
- built from the enabled-ops cut of the core's spec, not the whole document, and
40
- a spec over 1 MiB is split into per-entity variants at build time
41
- (`docs.split: { above }` moves the threshold, `false` turns it off) — an app's
42
- own spec is already the shape it serves and gets neither. Keep the spec in
43
- step with the routes in
44
- `api/src/index.ts` — the site renders what the file says. Keep pages `.md`,
45
- not `.mdx`: a site an API surface declares is served by the Hub under its own
46
- origin, which carries data and never code, so an `.mdx` page fails that
47
- deploy (the bare `ApiDocs` here accepts it, which is exactly the trap).
21
+ `api/openapi.json` is the API's OpenAPI document, `docs/pages/` holds its
22
+ guides, and `docs/docs.yaml` arranges them — a **native docs source** (title,
23
+ landing page, nav). **The Hub renders it on the app's Docs tab**, with its own
24
+ components, once the API is declared as a `mesh.apps.AppApiSurface` whose
25
+ `docs` block names it: `docs: { spec: "./api/openapi.json", config:
26
+ "./docs/docs.yaml" }`. No docs site, container or door is built, and no app
27
+ code reaches the Hub. Run `mesh docs preview docs` to see it as the Hub shows
28
+ it, with every problem the deploy would refuse (`--check` for CI). A page's
29
+ front-matter `title` is its heading, so a page never opens with its own `#`
30
+ heading, and pages are `.md` — Markdown data, never code. An image is a PNG,
31
+ JPEG, GIF or WebP file under `docs/pages/`, referenced relatively
32
+ (`![Flow](img/flow.png)`): it is bundled with the source and rendered from
33
+ those bytes, while an external image is never loaded. Under
34
+ `mesh dev --local`, `package.json`'s `mesh.docs` publishes the same source to
35
+ the local Hub's Docs tab.
36
+
37
+ A `docs` block that builds a Zudoku site instead (`pages`, `landingPage`,
38
+ `playground`, `audience`, `site`) is **deprecated**: it deploys, with a preview
39
+ warning, for one more minor release and is then removed with the Hub docs
40
+ door. `mesh docs convert --spec … --pages … --landing … --fix-pages` writes the
41
+ `docs.yaml` from such a block and prints the edit; `mesh app check` names it
42
+ (`DOCS_SITE_DEPRECATED`), and a site on a hostname of its own
43
+ (`DOCS_SITE_PUBLIC`). Add `actAs: true` on an app surface to let Hub operators
44
+ execute requests against the deployed API as one of the app's API keys from
45
+ the Docs tab (the Hub's act-as playground) — the surface then verifies the
46
+ Hub's signed assertion and runs the request as the key. Keep the spec in step
47
+ with the routes in `api/src/index.ts` — the docs render what the file says.
48
48
  {{/if}}
49
49
 
50
50
  {{#if temporal}}
@@ -4,8 +4,11 @@ A Mesh app for tenant `{{tenant}}`. The Pulumi program is `index.ts`; run it
4
4
  locally with `mesh dev`, deploy it with `mesh deploy up`.
5
5
  {{#if service}}
6
6
 
7
- The API's reference site is built by the platform from `api/openapi.json` and
8
- the guides in `docs/pages/` — this README is its landing page.
7
+ The API's docs are `api/openapi.json` and the guides in `docs/pages/`, arranged
8
+ by `docs/docs.yaml` — a native docs source, which the Hub renders on the app's
9
+ Docs tab once an API surface names it. `mesh docs preview docs` shows it
10
+ locally, and `mesh dev --local` publishes it to the local Hub's Docs tab
11
+ (`mesh.docs` in `package.json`).
9
12
  {{/if}}
10
13
 
11
14
  ## The version this app reports
@@ -114,9 +114,8 @@ const config = new mesh.apps.AppConfig("config", {
114
114
  // the delivery task queue, the worker registration that lists the app's
115
115
  // Temporal namespace on the Hub's Workflows page (with the service's own
116
116
  // housekeeping workflows marked internal, so only staff see them there), the
117
- // registry surface, and the operator UI (`@mesh-tech/events-ui`, installed in
118
- // package.json) as its own signed-in Service at `{app}-events-ui` — all
119
- // children of this one declaration.
117
+ // registry surface, and the Hub's act-as wiring that its Events tab reaches the
118
+ // service through — all children of this one declaration.
120
119
  //
121
120
  // Its database's schema migration gates the deploy: `pulumi up` waits for the
122
121
  // migration Job and fails when it fails, and the service's pods are held until
@@ -199,23 +198,23 @@ const worker = new mesh.apps.TemporalWorker("worker", {
199
198
  {{#if service}}
200
199
 
201
200
  // =============================================================================
202
- // Docs — the API's reference site, built by the platform from the spec and
203
- // the guides in docs/pages/ (this README is its landing page — `landingPage`
204
- // names another file, or `false` — and every guide is listed after it, in
205
- // path order). The Hub docs door is the path a docs site is published on:
206
- // here the site is reachable under `mesh dev`, and to publish it you declare
207
- // the API as a `mesh.apps.AppApiSurface` whose `docs` block builds this same
208
- // site (`docs: { spec, pages }`) — the Hub then serves it through that door.
209
- // A site given a hostname of its own instead (`ingress`, with the `auth`
210
- // proxy and the in-page `authentication` sign-in) is the deprecated public
211
- // shape: it still deploys, for the sites that are public today, and warns at
212
- // every preview. This declaration names none of it, and a new app needs
213
- // none. The site's readers hold this app's `docs` project role: declare the
214
- // role on the ZitadelAppIdentity and grant it from Access → Users; that same
215
- // role is what the Hub reads when a partner holding the Hub's `docs:<tenant>`
216
- // role opens this app's Docs tab, and the app then appears in their Apps list.
217
- // `mesh app check` flags a docs site put on an ingress instead
218
- // (`DOCS_SITE_PUBLIC`). Declaring `actAs: true`
201
+ // Docs — the spec (api/openapi.json) and the guides in docs/pages/, arranged
202
+ // by docs/docs.yaml: a native docs source. To publish it, declare the API as
203
+ // a `mesh.apps.AppApiSurface` whose docs block names it —
204
+ // `docs: { spec: "./api/openapi.json", config: "./docs/docs.yaml" }` — and
205
+ // the Hub renders it on the app's Docs tab itself: no docs site, container
206
+ // or door. `mesh docs preview docs` shows it locally, with every problem the
207
+ // deploy would refuse, and under `mesh dev --local` the app's package.json
208
+ // (`mesh.docs`) publishes it to the local Hub's Docs tab. A docs block that builds a
209
+ // site instead (`pages`, `landingPage`, `playground`, `audience`) is
210
+ // deprecated — `mesh docs convert` moves one, and `mesh app check` names it
211
+ // (`DOCS_SITE_DEPRECATED`); a site given a hostname of its own (`ingress`,
212
+ // with the `auth` proxy and the in-page `authentication` sign-in) is the
213
+ // older public shape (`DOCS_SITE_PUBLIC`). The readers hold this app's
214
+ // `docs` project role: declare the role on the ZitadelAppIdentity and grant
215
+ // it from Access → Users; that same role is what the Hub reads when a
216
+ // partner holding the Hub's `docs:<tenant>` role opens this app's Docs tab,
217
+ // and the app then appears in their Apps list. Declaring `actAs: true`
219
218
  // on that same surface opens the Hub's act-as playground: an operator picks
220
219
  // one of this app's API keys on the Docs tab and the Hub executes the request
221
220
  // against the deployed API as that key, over the cluster network, with a
@@ -230,19 +229,6 @@ const worker = new mesh.apps.TemporalWorker("worker", {
230
229
  // `externalService`; its registry entry publishes the name, and the Hub reads
231
230
  // the app's Core connection from the vendor's shared health measurement.
232
231
  // =============================================================================
233
-
234
- new mesh.apps.ApiDocs("docs", {
235
- env,
236
- sources: { openapi: "./api/openapi.json" },
237
- pages: "./docs/pages",
238
- serverUrl: api.url,
239
- zudoku: {
240
- metadata: {
241
- title: "{{titleCase name}} API",
242
- description: "HTTP API for {{titleCase name}}",
243
- },
244
- },
245
- });
246
232
  {{/if}}
247
233
 
248
234
  // =============================================================================
@@ -12,7 +12,6 @@
12
12
  {{#if events}}
13
13
  "@mesh-tech/authz": "{{#if workspaceDeps}}workspace:*{{else}}{{authzRange}}{{/if}}",
14
14
  "@mesh-tech/events-service": "{{#if workspaceDeps}}workspace:*{{else}}{{eventsServiceRange}}{{/if}}",
15
- "@mesh-tech/events-ui": "{{#if workspaceDeps}}workspace:*{{else}}{{eventsUiRange}}{{/if}}",
16
15
  {{/if}}
17
16
  "@mesh-tech/infra-components": "{{#if workspaceDeps}}workspace:*{{else}}{{meshRange}}{{/if}}",
18
17
  {{#if events}}
@@ -29,5 +28,8 @@
29
28
  {{/if}}
30
29
  "tsx": "{{#if workspaceDeps}}catalog:{{else}}{{tsxRange}}{{/if}}",
31
30
  "typescript": "{{#if workspaceDeps}}catalog:{{else}}{{typescriptRange}}{{/if}}"
32
- }
31
+ }{{#if service}},
32
+ "mesh": {
33
+ "docs": { "config": "docs/docs.yaml", "service": "api" }
34
+ }{{/if}}
33
35
  }
@@ -3,7 +3,7 @@
3
3
  "info": {
4
4
  "title": "{{titleCase name}} API",
5
5
  "version": "0.0.1",
6
- "description": "HTTP API for {{titleCase name}}. This document is what the docs site renders — keep it in step with api/src/index.ts."
6
+ "description": "HTTP API for {{titleCase name}}. This document is the API reference of the app's docs source (docs/docs.yaml) — keep it in step with api/src/index.ts."
7
7
  },
8
8
  "paths": {
9
9
  "/": {
@@ -0,0 +1,10 @@
1
+ # The {{titleCase name}} API's docs source: the guides under pages/ and the API
2
+ # reference from the spec, as data the Hub renders on the app's Docs tab once
3
+ # an API surface names this file (docs: { spec, config }). Check it with
4
+ # `mesh docs preview docs`.
5
+ title: {{titleCase name}} API
6
+ spec: ../api/openapi.json
7
+ pages: pages
8
+ landing: getting-started
9
+ reference:
10
+ label: API Reference
@@ -2,15 +2,12 @@
2
2
  title: Getting started
3
3
  ---
4
4
 
5
- # Getting started with the {{titleCase name}} API
6
-
7
- Guides for this API live in `docs/pages/` as Markdown and are published next to
8
- the reference generated from `api/openapi.json`. Add a page per topic — each
9
- is listed in the sidebar after the README landing page, in path order, under
10
- the `title` in its front matter; the platform builds the site on every deploy.
11
- Keep pages as `.md`, not `.mdx`: a
12
- site an API surface declares is also read inside the Hub (the docs door),
13
- which serves data, never code — an `.mdx` page fails that deploy.
5
+ Guides for the {{titleCase name}} API live in `docs/pages/` as Markdown and are
6
+ published next to the reference generated from `api/openapi.json`; add a page
7
+ per topic and list it in `docs/docs.yaml`'s `nav`. A page's `title` in its
8
+ front matter is also its heading, so a page does not open with its own `#`
9
+ heading. Keep pages as `.md`, not `.mdx`: the Hub renders them as Markdown
10
+ data and never runs code. `mesh docs preview docs` shows the result.
14
11
 
15
12
  ## Calling the API
16
13