@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,647 @@
1
+ // packages/api-registry/src/docs-source.ts
2
+ import { parse as parseYaml } from "yaml";
3
+ import { z } from "zod";
4
+ var PAGE_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*(?:\/[a-z0-9][a-z0-9_-]*)*$/;
5
+ function isReaderFacingServerUrl(value) {
6
+ let url;
7
+ try {
8
+ url = new URL(value);
9
+ } catch {
10
+ return false;
11
+ }
12
+ if (url.protocol !== "https:" && url.protocol !== "http:") return false;
13
+ const host = url.hostname.toLowerCase();
14
+ if (host.startsWith("[") || /^\d+(\.\d+){3}$/.test(host)) return false;
15
+ const labels = host.split(".");
16
+ if (labels.length < 2) return false;
17
+ const tld = labels[labels.length - 1];
18
+ if (["svc", "local", "internal", "localhost", "cluster"].includes(tld)) return false;
19
+ if (host.endsWith(".svc.cluster.local") || host.endsWith(".cluster.local")) return false;
20
+ return /^[a-z]{2,63}$/.test(tld) || /^xn--[a-z0-9-]{1,59}$/.test(tld);
21
+ }
22
+ function docsPage(bundle, id) {
23
+ return bundle && Object.hasOwn(bundle.pages, id) ? bundle.pages[id] : void 0;
24
+ }
25
+ var pageIdSchema = z.string().regex(PAGE_ID_PATTERN, "a page id is a lower-case path under pages/, without .md");
26
+ var docsConfigSchema = z.object({
27
+ title: z.string().min(1),
28
+ description: z.string().min(1).optional(),
29
+ spec: z.string().min(1).optional(),
30
+ pages: z.string().min(1).optional(),
31
+ landing: pageIdSchema.optional(),
32
+ serverUrl: z.string().url().refine(isReaderFacingServerUrl, "serverUrl must be a reader-facing http(s) URL, never an in-cluster or local address").optional(),
33
+ nav: z.array(z.object({ group: z.string().min(1), pages: z.array(pageIdSchema).min(1) }).strict()).optional(),
34
+ reference: z.union([z.object({ label: z.string().min(1).optional() }).strict(), z.literal(false)]).optional(),
35
+ events: z.string().min(1).optional()
36
+ }).strict();
37
+ var OPERATION_AVAILABILITY_STATES = ["available", "not_enabled", "not_implemented", "not_supported"];
38
+ var operationAvailabilitySchema = z.object({
39
+ state: z.enum(OPERATION_AVAILABILITY_STATES),
40
+ reason: z.string().min(1).optional()
41
+ });
42
+ var docsEventsCatalogSchema = z.object({
43
+ service: z.string().min(1),
44
+ types: z.array(
45
+ z.object({
46
+ name: z.string().min(1),
47
+ description: z.string().optional(),
48
+ schemaVersion: z.number().int().positive().optional(),
49
+ payloadSchema: z.record(z.string(), z.unknown()).optional()
50
+ })
51
+ ),
52
+ delivery: z.object({
53
+ payload: z.enum(["thin", "full"]).optional(),
54
+ retry: z.object({ maxAttempts: z.number().int().positive().optional() }).optional(),
55
+ signing: z.array(z.string().min(1)).optional()
56
+ }).optional()
57
+ });
58
+ var SANDBOX_MODES = ["live", "mock", "vendor-sandbox"];
59
+ var sandboxModeSchema = z.enum(SANDBOX_MODES);
60
+ var DEFAULT_SANDBOX_MODE = "live";
61
+ function tryItAllows(method, mode = DEFAULT_SANDBOX_MODE) {
62
+ if (["GET", "HEAD", "OPTIONS"].includes(method.toUpperCase())) return true;
63
+ return mode === "mock" || mode === "vendor-sandbox";
64
+ }
65
+ var DOCS_IMAGE_CONTENT_TYPES = {
66
+ png: "image/png",
67
+ jpg: "image/jpeg",
68
+ jpeg: "image/jpeg",
69
+ gif: "image/gif",
70
+ webp: "image/webp"
71
+ };
72
+ var MAX_DOCS_IMAGE_BYTES = 1024 * 1024;
73
+ var MAX_DOCS_IMAGES_TOTAL_BYTES = 4 * 1024 * 1024;
74
+ var IMAGE_KEY_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:\/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$/;
75
+ var BASE64_PATTERN = /^[A-Za-z0-9+/]*={0,2}$/;
76
+ function docsImageContentType(path) {
77
+ const dot = path.lastIndexOf(".");
78
+ const ext = dot === -1 ? "" : path.slice(dot + 1).toLowerCase();
79
+ return Object.hasOwn(DOCS_IMAGE_CONTENT_TYPES, ext) ? DOCS_IMAGE_CONTENT_TYPES[ext] : void 0;
80
+ }
81
+ function docsImageKey(pageId, src) {
82
+ if (!src || src.startsWith("/") || /[:?#\\]/.test(src)) return null;
83
+ let path;
84
+ try {
85
+ path = decodeURIComponent(src);
86
+ } catch {
87
+ return null;
88
+ }
89
+ if (/[:?#\\]/.test(path)) return null;
90
+ const parts = pageId.split("/").slice(0, -1);
91
+ for (const segment of path.split("/")) {
92
+ if (segment === "" || segment === ".") continue;
93
+ if (segment === "..") {
94
+ if (parts.length === 0) return null;
95
+ parts.pop();
96
+ continue;
97
+ }
98
+ parts.push(segment);
99
+ }
100
+ const key = parts.join("/");
101
+ return IMAGE_KEY_PATTERN.test(key) && docsImageContentType(key) ? key : null;
102
+ }
103
+ function docsImageUrl(bundle, pageId, src) {
104
+ const key = docsImageKey(pageId, src);
105
+ if (!key || !bundle?.images || !Object.hasOwn(bundle.images, key)) return null;
106
+ const image = bundle.images[key];
107
+ return `data:${image.contentType};base64,${image.data}`;
108
+ }
109
+ function docsImageRefs(markdown) {
110
+ const text = withoutCode(markdown);
111
+ const definitions = /* @__PURE__ */ new Map();
112
+ for (const m of text.matchAll(/^ {0,3}\[([^\]\n]+)\]:[ \t]*(?:<([^>\n]*)>|(\S+))/gm)) {
113
+ const label = normalizeLabel(m[1]);
114
+ if (!definitions.has(label)) definitions.set(label, m[2] ?? m[3]);
115
+ }
116
+ const refs = [];
117
+ for (const m of text.matchAll(/!\[([^\]\n]*)\]/g)) {
118
+ const after = m.index + m[0].length;
119
+ if (text[after] === "(") {
120
+ const inline = /\(\s*(?:<([^>\n]*)>|([^\s)]+))/y;
121
+ inline.lastIndex = after;
122
+ const src2 = inline.exec(text);
123
+ if (src2) refs.push(src2[1] ?? src2[2]);
124
+ continue;
125
+ }
126
+ let label = m[1];
127
+ if (text[after] === "[") {
128
+ const full = /\[([^\]\n]*)\]/y;
129
+ full.lastIndex = after;
130
+ const ref = full.exec(text);
131
+ if (ref && ref[1]) label = ref[1];
132
+ }
133
+ const src = definitions.get(normalizeLabel(label));
134
+ if (src !== void 0) refs.push(src);
135
+ }
136
+ return refs;
137
+ }
138
+ function normalizeLabel(label) {
139
+ return label.trim().toLowerCase().split(/\s+/).join(" ");
140
+ }
141
+ function withoutCode(markdown) {
142
+ const out = [];
143
+ let fence = null;
144
+ for (const line of markdown.split("\n")) {
145
+ const opener = /^ {0,3}(`{3,}|~{3,})/.exec(line);
146
+ if (fence) {
147
+ if (opener && opener[1][0] === fence.char && opener[1].length >= fence.length) fence = null;
148
+ out.push("");
149
+ continue;
150
+ }
151
+ if (opener) {
152
+ fence = { char: opener[1][0], length: opener[1].length };
153
+ out.push("");
154
+ continue;
155
+ }
156
+ out.push(line.replace(/`+[^`]*`+/g, ""));
157
+ }
158
+ return out.join("\n");
159
+ }
160
+ var DOCS_SOURCE_BUNDLE_VERSION = 1;
161
+ var docsSourceBundleSchema = z.object({
162
+ bundleVersion: z.number().int().positive(),
163
+ config: docsConfigSchema,
164
+ pages: z.record(
165
+ pageIdSchema,
166
+ z.object({ title: z.string().min(1), description: z.string().optional(), markdown: z.string() })
167
+ ),
168
+ availability: z.record(z.string().min(1), operationAvailabilitySchema).optional(),
169
+ events: docsEventsCatalogSchema.optional(),
170
+ images: z.record(
171
+ z.string().regex(IMAGE_KEY_PATTERN, "an image key is a path under pages/"),
172
+ z.object({
173
+ contentType: z.enum(["image/png", "image/jpeg", "image/gif", "image/webp"]),
174
+ data: z.string().regex(BASE64_PATTERN, "image data is base64")
175
+ }).strict()
176
+ ).optional()
177
+ });
178
+ function docsSourceProblems(bundle, specOperationIds2) {
179
+ const problems = [];
180
+ const { config, pages } = bundle;
181
+ if (config.landing && !Object.hasOwn(pages, config.landing)) {
182
+ problems.push(`landing "${config.landing}" is not a page of this source`);
183
+ }
184
+ const listed = /* @__PURE__ */ new Set();
185
+ for (const group of config.nav ?? []) {
186
+ for (const id of group.pages) {
187
+ if (!Object.hasOwn(pages, id)) problems.push(`nav group "${group.group}" names "${id}", which is not a page of this source`);
188
+ if (listed.has(id)) problems.push(`"${id}" is listed in the nav more than once`);
189
+ listed.add(id);
190
+ }
191
+ }
192
+ if (bundle.availability) {
193
+ const spec = new Set(specOperationIds2);
194
+ for (const id of Object.keys(bundle.availability)) {
195
+ if (!spec.has(id)) problems.push(`availability names ${id}, which the spec has no operation for`);
196
+ }
197
+ for (const id of specOperationIds2) {
198
+ if (!Object.hasOwn(bundle.availability, id)) problems.push(`availability leaves out ${id}`);
199
+ }
200
+ }
201
+ if (bundle.events && config.events && bundle.events.service !== config.events) {
202
+ problems.push(`docs.yaml names events "${config.events}", but the catalog is ${bundle.events.service}'s`);
203
+ }
204
+ return problems;
205
+ }
206
+ function docsSourceKey(name) {
207
+ return `api-docs/${name}/docs-source.json`;
208
+ }
209
+ function splitFrontMatter(text) {
210
+ const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
211
+ if (!m) return { frontMatter: {}, body: text };
212
+ const parsed = parseYaml(m[1]);
213
+ const frontMatter = parsed && typeof parsed === "object" ? parsed : {};
214
+ return { frontMatter, body: text.slice(m[0].length).replace(/^\s*\n/, "") };
215
+ }
216
+ var SPEC_METHODS = ["get", "put", "post", "delete", "patch", "head", "options", "trace"];
217
+ function eachSpecOperation(spec, visit) {
218
+ const paths = spec?.paths;
219
+ if (!paths || typeof paths !== "object") return;
220
+ for (const [route, item] of Object.entries(paths)) {
221
+ if (!item || typeof item !== "object") continue;
222
+ for (const [method, raw] of Object.entries(item)) {
223
+ if (!SPEC_METHODS.includes(method) || !raw || typeof raw !== "object") continue;
224
+ const op = raw;
225
+ const native = op["x-native-operation"];
226
+ const oid = op.operationId;
227
+ visit(
228
+ typeof native === "string" && native ? native : typeof oid === "string" && oid ? oid : `${method.toUpperCase()} ${route}`,
229
+ op
230
+ );
231
+ }
232
+ }
233
+ }
234
+ function specOperationIds(spec) {
235
+ const ids = [];
236
+ eachSpecOperation(spec, (id) => ids.push(id));
237
+ return ids;
238
+ }
239
+ function referencedDocsImages(pages) {
240
+ const keys = /* @__PURE__ */ new Set();
241
+ for (const { id, text } of pages) {
242
+ for (const ref of docsImageRefs(splitFrontMatter(text).body)) {
243
+ const key = docsImageKey(id, ref);
244
+ if (key) keys.add(key);
245
+ }
246
+ }
247
+ return [...keys].sort();
248
+ }
249
+ function isExternalImageRef(ref) {
250
+ return ref.startsWith("//") || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(ref);
251
+ }
252
+ function base64Bytes(data) {
253
+ const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
254
+ return Math.floor(data.length * 3 / 4) - padding;
255
+ }
256
+ function parseDocsConfig(text, label) {
257
+ const parsed = docsConfigSchema.safeParse(parseYaml(text) ?? {});
258
+ if (!parsed.success) {
259
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`).join("; ");
260
+ throw new Error(`${label}: invalid docs.yaml \u2014 ${detail}`);
261
+ }
262
+ return parsed.data;
263
+ }
264
+ function assembleDocsSource(files) {
265
+ const config = parseDocsConfig(files.configText, files.configLabel);
266
+ const problems = [];
267
+ if (files.pagesUnreadable && (config.landing || (config.nav ?? []).length > 0)) {
268
+ problems.push(`pages directory ${files.pagesUnreadable} cannot be read`);
269
+ }
270
+ for (const bad of files.invalidPages ?? []) problems.push(`page ${bad} has a path that is not a valid page id`);
271
+ const pages = {};
272
+ for (const { id, text } of [...files.pages].sort((a, b) => a.id.localeCompare(b.id))) {
273
+ if (!PAGE_ID_PATTERN.test(id)) {
274
+ problems.push(`page ${id} has a path that is not a valid page id`);
275
+ continue;
276
+ }
277
+ const { frontMatter, body } = splitFrontMatter(text);
278
+ const title = typeof frontMatter.title === "string" ? frontMatter.title.trim() : "";
279
+ if (!title) {
280
+ problems.push(`page ${id} has no front-matter title`);
281
+ continue;
282
+ }
283
+ if (/^#\s/.test(body.split("\n").find((l) => l.trim() !== "") ?? "")) {
284
+ problems.push(`page ${id} opens with a # heading; its front-matter title is already the heading`);
285
+ }
286
+ pages[id] = {
287
+ title,
288
+ ...typeof frontMatter.description === "string" ? { description: frontMatter.description } : {},
289
+ markdown: body
290
+ };
291
+ }
292
+ const referenced = /* @__PURE__ */ new Set();
293
+ for (const [id, page] of Object.entries(pages)) {
294
+ for (const ref of docsImageRefs(page.markdown)) {
295
+ if (isExternalImageRef(ref)) continue;
296
+ const key = docsImageKey(id, ref);
297
+ if (key) referenced.add(key);
298
+ else {
299
+ problems.push(
300
+ `page ${id} references image "${ref}", which is not a PNG, JPEG, GIF or WebP file inside the pages directory`
301
+ );
302
+ }
303
+ }
304
+ }
305
+ problems.push(...files.imageProblems ?? []);
306
+ const images = {};
307
+ let imageBytes = 0;
308
+ for (const key of [...referenced].sort()) {
309
+ if (!files.images || !Object.hasOwn(files.images, key)) continue;
310
+ images[key] = files.images[key];
311
+ imageBytes += base64Bytes(images[key].data);
312
+ }
313
+ if (imageBytes > MAX_DOCS_IMAGES_TOTAL_BYTES) {
314
+ problems.push(
315
+ `the pages' images total ${imageBytes} bytes, over the ${MAX_DOCS_IMAGES_TOTAL_BYTES}-byte limit for one source`
316
+ );
317
+ }
318
+ const guidesOnly = config.reference === false;
319
+ if (guidesOnly) {
320
+ if (!config.landing) problems.push("a guides-only source (reference: false) needs a landing page");
321
+ if (config.spec) problems.push("docs.yaml names a spec, but reference: false publishes no API reference");
322
+ if (files.availability) problems.push("a guides-only source (reference: false) publishes no availability");
323
+ }
324
+ let specIds = [];
325
+ if (guidesOnly) {
326
+ } else if (files.specText === void 0) problems.push(`spec ${files.specLabel} cannot be read`);
327
+ else {
328
+ try {
329
+ specIds = specOperationIds(JSON.parse(files.specText));
330
+ } catch {
331
+ problems.push(`spec ${files.specLabel} cannot be read as JSON`);
332
+ }
333
+ }
334
+ const bundle = {
335
+ bundleVersion: DOCS_SOURCE_BUNDLE_VERSION,
336
+ config,
337
+ pages,
338
+ ...files.availability ? { availability: files.availability } : {},
339
+ ...files.events ? { events: files.events } : {},
340
+ ...Object.keys(images).length > 0 ? { images } : {}
341
+ };
342
+ problems.push(...docsSourceProblems(bundle, specIds));
343
+ if (config.events && !files.events) {
344
+ problems.push(`docs.yaml names events "${config.events}", but no events catalog was supplied`);
345
+ }
346
+ return { bundle, problems };
347
+ }
348
+
349
+ // packages/api-registry/src/index.ts
350
+ import { z as z2 } from "zod";
351
+
352
+ // packages/api-registry/src/hub-roles.ts
353
+ var HUB_STAFF_ROLES = ["ops", "admin"];
354
+ var HUB_RESTRICTED_ROLES = ["developer", "auditor", "vendor", "docs"];
355
+ var HUB_VENDOR_DOCS_ROLE = "vendor-docs";
356
+ var HUB_ADDON_ROLES = [HUB_VENDOR_DOCS_ROLE];
357
+ var HUB_TIER_ROLES = [
358
+ ...HUB_STAFF_ROLES,
359
+ ...HUB_RESTRICTED_ROLES
360
+ ];
361
+ var HUB_BASE_ROLES = [
362
+ ...HUB_TIER_ROLES,
363
+ ...HUB_ADDON_ROLES
364
+ ];
365
+ var HUB_ROLE_KEY_SEPARATOR = ":";
366
+ var HUB_ROLE_APP_SEPARATOR = "/";
367
+ var RESTRICTED = new Set(HUB_RESTRICTED_ROLES);
368
+ var ADDONS = new Set(HUB_ADDON_ROLES);
369
+ function restrictedRoleBase(roleKey) {
370
+ const sep2 = roleKey.indexOf(HUB_ROLE_KEY_SEPARATOR);
371
+ const base = sep2 === -1 ? roleKey : roleKey.slice(0, sep2);
372
+ return RESTRICTED.has(base) ? base : null;
373
+ }
374
+ function hubRoleKeyTenant(roleKey) {
375
+ const sep2 = roleKey.indexOf(HUB_ROLE_KEY_SEPARATOR);
376
+ if (sep2 === -1) return null;
377
+ const scope = roleKey.slice(sep2 + 1);
378
+ const appSep = scope.indexOf(HUB_ROLE_APP_SEPARATOR);
379
+ return appSep === -1 ? scope : scope.slice(0, appSep);
380
+ }
381
+ function hubOperatorRoleKeys(tenants) {
382
+ const cleaned = [...new Set(tenants.filter((t) => t !== ""))].sort();
383
+ for (const tenant of cleaned) {
384
+ for (const sep2 of [HUB_ROLE_KEY_SEPARATOR, HUB_ROLE_APP_SEPARATOR]) {
385
+ if (tenant.includes(sep2)) {
386
+ throw new Error(
387
+ `hubOperatorRoleKeys: tenant name "${tenant}" contains the role-key separator "${sep2}" \u2014 it would parse as a different tenant's or app's grant`
388
+ );
389
+ }
390
+ }
391
+ }
392
+ return [
393
+ ...HUB_BASE_ROLES,
394
+ ...cleaned.flatMap(
395
+ (tenant) => HUB_RESTRICTED_ROLES.map((role) => `${role}${HUB_ROLE_KEY_SEPARATOR}${tenant}`)
396
+ )
397
+ ];
398
+ }
399
+
400
+ // packages/api-registry/src/index.ts
401
+ var rateLimitSpecSchema = z2.object({
402
+ rps: z2.number().int().min(1),
403
+ burst: z2.number().int().min(1)
404
+ });
405
+ var rateLimitDefaultsSchema = z2.record(z2.string().min(1), rateLimitSpecSchema);
406
+ var integrationHealthSchema = z2.union([
407
+ z2.object({ op: z2.string().trim().min(1) }),
408
+ z2.object({ unavailable: z2.string().trim().min(1) })
409
+ ]);
410
+ var apiSurfaceKindSchema = z2.enum(["app", "vendor", "events"]);
411
+ var apiDocsSiteAudienceSchema = z2.discriminatedUnion("readers", [
412
+ z2.object({ readers: z2.literal("partner"), roles: z2.array(z2.string().min(1)) }),
413
+ z2.object({ readers: z2.literal("operator") })
414
+ ]);
415
+ var API_REGISTRY_SCHEMA_VERSION = 8;
416
+ var apiSurfaceSchema = z2.object({
417
+ http: z2.object({ url: z2.string().min(1) }).optional(),
418
+ nexus: z2.object({ endpoint: z2.string().min(1), taskQueue: z2.string().min(1) }).optional()
419
+ });
420
+ var apiRegistryEntrySchema = z2.object({
421
+ schemaVersion: z2.number().int().positive().default(1),
422
+ name: z2.string().min(1),
423
+ kind: apiSurfaceKindSchema.optional(),
424
+ provider: z2.string().optional(),
425
+ externalService: z2.string().min(1).optional(),
426
+ version: z2.string().optional(),
427
+ title: z2.string().optional(),
428
+ description: z2.string().optional(),
429
+ surfaces: apiSurfaceSchema,
430
+ credentials: z2.object({ keyedBy: z2.string().nullish() }).optional(),
431
+ docs: z2.object({
432
+ url: z2.string().min(1),
433
+ contentHash: z2.string().min(1),
434
+ trimmed: z2.object({ url: z2.string().min(1), contentHash: z2.string().min(1) }).optional(),
435
+ siteUrl: z2.string().min(1).optional(),
436
+ audience: apiDocsSiteAudienceSchema.optional(),
437
+ source: z2.object({ url: z2.string().min(1), contentHash: z2.string().min(1) }).optional()
438
+ }),
439
+ enabledOps: z2.array(z2.string()),
440
+ // The consumer schema strips unknown keys, so a producer-side field that is
441
+ // not mirrored here never reaches a consumer — mirror every addition.
442
+ status: z2.object({ url: z2.string().min(1) }).optional(),
443
+ actAs: z2.object({ url: z2.string().min(1), integrations: z2.array(z2.string().min(1)).optional() }).optional(),
444
+ appVersion: z2.string().min(1).optional(),
445
+ rateLimits: rateLimitDefaultsSchema.optional(),
446
+ health: integrationHealthSchema.optional(),
447
+ producedRateClasses: z2.array(z2.string().min(1)).optional(),
448
+ sandbox: sandboxModeSchema.optional(),
449
+ events: z2.object({ entry: z2.string().min(1) }).optional()
450
+ });
451
+ var integrationStatusSchema = z2.object({
452
+ contract: z2.literal("v1"),
453
+ name: z2.string().min(1),
454
+ provider: z2.string().optional(),
455
+ type: z2.string().optional(),
456
+ title: z2.string().optional(),
457
+ definitionVersion: z2.string().optional(),
458
+ packageVersion: z2.string().min(1).optional(),
459
+ tenant: z2.string().min(1),
460
+ instanceKey: z2.string().optional(),
461
+ surfaces: z2.array(z2.enum(["http", "nexus"])),
462
+ coreMode: z2.enum(["mock", "live"]),
463
+ coreModeSource: z2.string().optional(),
464
+ enabledOps: z2.array(z2.string()),
465
+ credentialsWired: z2.boolean(),
466
+ rateLimits: rateLimitDefaultsSchema.optional(),
467
+ health: integrationHealthSchema.optional(),
468
+ startedAt: z2.string().min(1)
469
+ });
470
+ function assertPathSegment(value, label) {
471
+ if (!value || value.includes("/")) {
472
+ throw new Error(
473
+ `api-registry: invalid ${label} ${JSON.stringify(value)} \u2014 SSM path segments must be non-empty and must not contain "/"`
474
+ );
475
+ }
476
+ return value;
477
+ }
478
+ function apisRegistryRoot(tenant, env) {
479
+ return `/mesh-platform/${assertPathSegment(tenant, "tenant")}/${assertPathSegment(env, "env")}/apis`;
480
+ }
481
+ function apiEntryPath(tenant, env, name) {
482
+ return `${apisRegistryRoot(tenant, env)}/${assertPathSegment(name, "name")}`;
483
+ }
484
+ function actAsAudience(tenant, env, name) {
485
+ return `${assertPathSegment(tenant, "tenant")}/${assertPathSegment(env, "env")}/${assertPathSegment(name, "name")}`;
486
+ }
487
+ var hubActAsExportsSchema = z2.object({
488
+ issuer: z2.string().min(1),
489
+ keys: z2.array(
490
+ z2.object({
491
+ kid: z2.string().min(1),
492
+ kty: z2.literal("RSA"),
493
+ n: z2.string().min(1),
494
+ e: z2.string().min(1),
495
+ alg: z2.string().optional(),
496
+ use: z2.string().optional()
497
+ })
498
+ ).min(1)
499
+ });
500
+ function parseHubActAsExports(value) {
501
+ const result = hubActAsExportsSchema.safeParse(value);
502
+ if (!result.success) {
503
+ const detail = result.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`).join("; ");
504
+ throw new Error(`api-registry: invalid HubActAsExports \u2014 ${detail}`);
505
+ }
506
+ return result.data;
507
+ }
508
+ function hubActAsPath(tenant, env) {
509
+ return `/mesh-platform/${assertPathSegment(tenant, "tenant")}/${assertPathSegment(env, "env")}/hub/act-as`;
510
+ }
511
+
512
+ // packages/api-registry/src/docs-source-build.ts
513
+ import { createHash } from "node:crypto";
514
+ import { lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
515
+ import { dirname, join, relative, resolve, sep } from "node:path";
516
+ function readPagesDir(dir) {
517
+ const pages = [];
518
+ const invalid = [];
519
+ const walk = (current) => {
520
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
521
+ const full = join(current, entry.name);
522
+ if (lstatSync(full).isSymbolicLink()) continue;
523
+ if (entry.isDirectory()) walk(full);
524
+ else if (entry.isFile() && entry.name.endsWith(".md")) {
525
+ const id = relative(dir, full).slice(0, -".md".length).split(sep).join("/");
526
+ if (PAGE_ID_PATTERN.test(id)) pages.push({ id, text: readFileSync(full, "utf8") });
527
+ else invalid.push(relative(dir, full));
528
+ }
529
+ }
530
+ };
531
+ walk(dir);
532
+ return { pages, invalid };
533
+ }
534
+ function looksLike(bytes, contentType) {
535
+ const ascii = (from, to) => bytes.subarray(from, to).toString("latin1");
536
+ switch (contentType) {
537
+ case "image/png":
538
+ return bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
539
+ case "image/jpeg":
540
+ return bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
541
+ case "image/gif":
542
+ return ascii(0, 6) === "GIF87a" || ascii(0, 6) === "GIF89a";
543
+ case "image/webp":
544
+ return ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP";
545
+ }
546
+ }
547
+ function readDocsImages(pagesDir, keys) {
548
+ const images = {};
549
+ const problems = [];
550
+ let root;
551
+ try {
552
+ root = realpathSync(pagesDir);
553
+ } catch {
554
+ return { images, problems: keys.map((key) => `image ${key} cannot be read: the pages directory is missing`) };
555
+ }
556
+ for (const key of keys) {
557
+ const contentType = docsImageContentType(key);
558
+ if (!contentType) continue;
559
+ const full = join(root, ...key.split("/"));
560
+ let size;
561
+ try {
562
+ if (lstatSync(full).isSymbolicLink()) {
563
+ problems.push(`image ${key} is a symbolic link; bundle the file itself`);
564
+ continue;
565
+ }
566
+ const real = realpathSync(full);
567
+ if (!real.startsWith(root + sep)) {
568
+ problems.push(`image ${key} resolves outside the pages directory`);
569
+ continue;
570
+ }
571
+ const stat = lstatSync(real);
572
+ if (!stat.isFile()) throw new Error("not a file");
573
+ size = stat.size;
574
+ } catch {
575
+ problems.push(`image ${key} is referenced by a page but is not in the pages directory`);
576
+ continue;
577
+ }
578
+ if (size > MAX_DOCS_IMAGE_BYTES) {
579
+ problems.push(`image ${key} is ${size} bytes, over the ${MAX_DOCS_IMAGE_BYTES}-byte limit for one image`);
580
+ continue;
581
+ }
582
+ const bytes = readFileSync(full);
583
+ if (!looksLike(bytes, contentType)) {
584
+ problems.push(`image ${key} is not a ${contentType} file, as its extension says`);
585
+ continue;
586
+ }
587
+ images[key] = { contentType, data: bytes.toString("base64") };
588
+ }
589
+ return { images, problems };
590
+ }
591
+ function buildDocsSource(input) {
592
+ const configPath = resolve(input.configPath);
593
+ const baseDir = dirname(configPath);
594
+ const configText = readFileSync(configPath, "utf8");
595
+ const config = parseDocsConfig(configText, configPath);
596
+ const pagesDir = resolve(baseDir, config.pages ?? "pages");
597
+ let read;
598
+ try {
599
+ read = readPagesDir(pagesDir);
600
+ } catch {
601
+ read = void 0;
602
+ }
603
+ const specPath = resolve(input.specPath ?? resolve(baseDir, config.spec ?? "openapi.json"));
604
+ let specText;
605
+ if (config.reference !== false) {
606
+ try {
607
+ specText = readFileSync(specPath, "utf8");
608
+ } catch {
609
+ specText = void 0;
610
+ }
611
+ }
612
+ const images = read ? readDocsImages(pagesDir, referencedDocsImages(read.pages)) : void 0;
613
+ const { bundle, problems } = assembleDocsSource({
614
+ configLabel: configPath,
615
+ configText,
616
+ pages: read?.pages ?? [],
617
+ ...read ? { invalidPages: read.invalid } : { pagesUnreadable: pagesDir },
618
+ specLabel: specPath,
619
+ ...specText !== void 0 ? { specText } : {},
620
+ ...input.availability ? { availability: input.availability } : {},
621
+ ...input.events ? { events: input.events } : {},
622
+ ...images ? { images: images.images, imageProblems: images.problems } : {}
623
+ });
624
+ const json = JSON.stringify(bundle);
625
+ return { bundle, json, contentHash: createHash("sha256").update(json).digest("hex"), specPath, problems };
626
+ }
627
+
628
+ export {
629
+ PAGE_ID_PATTERN,
630
+ docsPage,
631
+ sandboxModeSchema,
632
+ DEFAULT_SANDBOX_MODE,
633
+ tryItAllows,
634
+ docsImageUrl,
635
+ docsSourceKey,
636
+ splitFrontMatter,
637
+ parseDocsConfig,
638
+ restrictedRoleBase,
639
+ hubRoleKeyTenant,
640
+ hubOperatorRoleKeys,
641
+ API_REGISTRY_SCHEMA_VERSION,
642
+ apiEntryPath,
643
+ actAsAudience,
644
+ parseHubActAsExports,
645
+ hubActAsPath,
646
+ buildDocsSource
647
+ };