@mesh-tech/mesh-cli 0.36.0 → 0.37.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 (103) hide show
  1. package/assets/docs-preview.css +1599 -0
  2. package/dist/bin/mesh.auth-preflight-JFKL4Y4X.js +48 -0
  3. package/dist/bin/mesh.auth-preflight-JFKL4Y4X.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-5A5OFSYR.js +187 -0
  7. package/dist/bin/mesh.chunk-5A5OFSYR.js.map +6 -0
  8. package/dist/bin/mesh.chunk-7DMAFUBQ.js +7715 -0
  9. package/dist/bin/mesh.chunk-7DMAFUBQ.js.map +6 -0
  10. package/dist/bin/mesh.chunk-A4DGMGHF.js +303 -0
  11. package/dist/bin/mesh.chunk-A4DGMGHF.js.map +6 -0
  12. package/dist/bin/mesh.chunk-CQ2IC4OP.js +217 -0
  13. package/dist/bin/mesh.chunk-CQ2IC4OP.js.map +6 -0
  14. package/dist/bin/mesh.chunk-F4654EPX.js +383 -0
  15. package/dist/bin/mesh.chunk-F4654EPX.js.map +6 -0
  16. package/dist/bin/mesh.chunk-IQOBTL42.js +60 -0
  17. package/dist/bin/mesh.chunk-IQOBTL42.js.map +6 -0
  18. package/dist/bin/mesh.chunk-J6DVWKRW.js +647 -0
  19. package/dist/bin/mesh.chunk-J6DVWKRW.js.map +6 -0
  20. package/dist/bin/mesh.chunk-JB7FWYSP.js +1751 -0
  21. package/dist/bin/mesh.chunk-JB7FWYSP.js.map +6 -0
  22. package/dist/bin/mesh.chunk-MCBMHWXP.js +837 -0
  23. package/dist/bin/mesh.chunk-MCBMHWXP.js.map +6 -0
  24. package/dist/bin/mesh.chunk-OW5EMVG5.js +507 -0
  25. package/dist/bin/mesh.chunk-OW5EMVG5.js.map +6 -0
  26. package/dist/bin/mesh.chunk-R5VVTXG4.js +10092 -0
  27. package/dist/bin/mesh.chunk-R5VVTXG4.js.map +6 -0
  28. package/dist/bin/mesh.chunk-VKV6Z3UI.js +1146 -0
  29. package/dist/bin/mesh.chunk-VKV6Z3UI.js.map +6 -0
  30. package/dist/bin/mesh.chunk-WFFDF346.js +59 -0
  31. package/dist/bin/mesh.chunk-WFFDF346.js.map +6 -0
  32. package/dist/bin/mesh.chunk-WOJOTUFZ.js +835 -0
  33. package/dist/bin/mesh.chunk-WOJOTUFZ.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-KK6ZP256.js +274 -0
  37. package/dist/bin/mesh.convert-KK6ZP256.js.map +6 -0
  38. package/dist/bin/mesh.create-app-A5F45PWZ.js +39 -0
  39. package/dist/bin/mesh.create-app-A5F45PWZ.js.map +6 -0
  40. package/dist/bin/mesh.dev-token-server-GYDYPXYH.js +61 -0
  41. package/dist/bin/mesh.dev-token-server-GYDYPXYH.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-JNHPVMCT.js +91 -0
  45. package/dist/bin/mesh.login-JNHPVMCT.js.map +6 -0
  46. package/dist/bin/mesh.platform-setup-EB64EFRN.js +37 -0
  47. package/dist/bin/mesh.platform-setup-EB64EFRN.js.map +6 -0
  48. package/dist/bin/mesh.preview-3IP34TDO.js +2410 -0
  49. package/dist/bin/mesh.preview-3IP34TDO.js.map +6 -0
  50. package/dist/bin/mesh.program-DWOKHNMQ.js +23 -0
  51. package/dist/bin/mesh.program-DWOKHNMQ.js.map +6 -0
  52. package/dist/bin/mesh.registry-OXF57OQW.js +47 -0
  53. package/dist/bin/mesh.registry-OXF57OQW.js.map +6 -0
  54. package/dist/bin/mesh.registry-identity-2HSGQGJC.js +42 -0
  55. package/dist/bin/mesh.registry-identity-2HSGQGJC.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-ARIIY6CV.js +454 -0
  65. package/dist/bin/mesh.start-ARIIY6CV.js.map +6 -0
  66. package/dist/bin/mesh.wizard-ROAQAGSF.js +500 -0
  67. package/dist/bin/mesh.wizard-ROAQAGSF.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/dev.d.ts +6 -0
  72. package/dist/src/commands/dev.js +152 -3
  73. package/dist/src/commands/docs.js +33 -0
  74. package/dist/src/commands/local/act-as-local.d.ts +5 -0
  75. package/dist/src/commands/local/act-as-local.js +41 -0
  76. package/dist/src/commands/local/auth-provision.d.ts +1 -0
  77. package/dist/src/commands/local/auth-provision.js +1 -0
  78. package/dist/src/commands/local/index.js +4 -0
  79. package/dist/src/commands/local/local-api-key.d.ts +44 -0
  80. package/dist/src/commands/local/local-api-key.js +115 -0
  81. package/dist/src/commands/local/local-spicedb.d.ts +95 -0
  82. package/dist/src/commands/local/local-spicedb.js +304 -0
  83. package/dist/src/commands/local/publish-docs.d.ts +81 -0
  84. package/dist/src/commands/local/publish-docs.js +328 -0
  85. package/dist/src/commands/local/seed-zitadel.js +2 -0
  86. package/dist/src/commands/temporal.js +191 -61
  87. package/dist/src/commands/temporal.js.map +3 -3
  88. package/dist/src/docs/convert.d.ts +35 -0
  89. package/dist/src/docs/convert.js +276 -0
  90. package/dist/src/docs/preview-source.d.ts +13 -0
  91. package/dist/src/docs/preview-source.js +58 -0
  92. package/dist/src/docs/preview.d.ts +32 -0
  93. package/dist/src/docs/preview.js +279 -0
  94. package/fragments/agents/AGENTS.md.hbs +27 -27
  95. package/fragments/base/README.md.hbs +5 -2
  96. package/fragments/base/index.ts.hbs +17 -30
  97. package/fragments/base/package.json.hbs +4 -1
  98. package/fragments/service/api/openapi.json.hbs +1 -1
  99. package/fragments/service/docs/docs.yaml.hbs +10 -0
  100. package/fragments/service/docs/pages/getting-started.md.hbs +6 -9
  101. package/package.json +19 -3
  102. package/skills/mesh/SKILL.md +2 -2
  103. package/stack/docker-compose.hub.yml +6 -0
@@ -0,0 +1,328 @@
1
+ import { createHash } from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { execFileSync } from "node:child_process";
5
+ import { pathToFileURL } from "node:url";
6
+ import { API_REGISTRY_SCHEMA_VERSION, apiEntryPath, docsSourceKey, parseDocsConfig, sandboxModeSchema, } from "@mesh-tech/api-registry";
7
+ import { buildDocsSource } from "@mesh-tech/api-registry/docs-source-build";
8
+ import { ARTIFACTS_BUCKET, LOCAL_ENV } from "./seed.js";
9
+ import { localAppNamespace } from "./dev-local.js";
10
+ import { connectLocalSpicedb, localSpicedbIo } from "./local-spicedb.js";
11
+ export function readLocalPublishDeclaration(pkg, appRoot) {
12
+ const problems = [];
13
+ const mesh = pkg?.mesh;
14
+ const out = { problems };
15
+ const docs = mesh?.docs;
16
+ if (docs !== undefined) {
17
+ if (typeof docs?.config !== "string" || !docs.config)
18
+ problems.push("mesh.docs.config must name the docs.yaml");
19
+ const sandbox = docs?.sandbox === undefined ? undefined : sandboxModeSchema.safeParse(docs.sandbox);
20
+ if (sandbox && !sandbox.success)
21
+ problems.push("mesh.docs.sandbox must be live, mock or vendor-sandbox");
22
+ if (docs?.local !== undefined && (typeof docs.local !== "string" || !docs.local.includes("#"))) {
23
+ problems.push("mesh.docs.local must be module#export");
24
+ }
25
+ const integrationTryIt = Array.isArray(docs?.integrationTryIt)
26
+ ? [...new Set(docs.integrationTryIt.filter((n) => typeof n === "string" && n.length > 0))].sort()
27
+ : [];
28
+ if (docs?.integrationTryIt !== undefined && !Array.isArray(docs.integrationTryIt)) {
29
+ problems.push("mesh.docs.integrationTryIt must list integration names");
30
+ }
31
+ if (integrationTryIt.length > 0 && docs?.actAs !== true) {
32
+ problems.push("mesh.docs.integrationTryIt needs mesh.docs.actAs: true");
33
+ }
34
+ if (typeof docs?.config === "string" && docs.config) {
35
+ out.docs = {
36
+ config: path.resolve(appRoot, docs.config),
37
+ actAs: docs.actAs === true,
38
+ ...(sandbox?.success ? { sandbox: sandbox.data } : {}),
39
+ ...(typeof docs.local === "string" && docs.local.includes("#") ? { local: docs.local } : {}),
40
+ ...(typeof docs.service === "string" && docs.service ? { service: docs.service } : {}),
41
+ ...(integrationTryIt.length > 0 ? { integrationTryIt } : {}),
42
+ };
43
+ }
44
+ }
45
+ const metadata = mesh?.authz?.metadata;
46
+ if (metadata !== undefined) {
47
+ if (typeof metadata !== "string" || !metadata.includes("#"))
48
+ problems.push("mesh.authz.metadata must be module#export");
49
+ else
50
+ out.authzMetadata = metadata;
51
+ }
52
+ const spicedbSchema = mesh?.authz?.spicedbSchema;
53
+ if (spicedbSchema !== undefined) {
54
+ if (typeof spicedbSchema !== "string" || !spicedbSchema.includes("#"))
55
+ problems.push("mesh.authz.spicedbSchema must be module#export");
56
+ else if (!out.authzMetadata)
57
+ problems.push("mesh.authz.spicedbSchema needs mesh.authz.metadata");
58
+ else
59
+ out.authzSpicedbSchema = spicedbSchema;
60
+ }
61
+ const spicedbSeed = mesh?.authz?.spicedbSeed;
62
+ if (spicedbSeed !== undefined) {
63
+ if (typeof spicedbSeed !== "string" || !spicedbSeed.includes("#"))
64
+ problems.push("mesh.authz.spicedbSeed must be module#export");
65
+ else if (!out.authzSpicedbSchema)
66
+ problems.push("mesh.authz.spicedbSeed needs mesh.authz.spicedbSchema");
67
+ else
68
+ out.authzSpicedbSeed = spicedbSeed;
69
+ }
70
+ return out;
71
+ }
72
+ export async function callExport(appRoot, spec, arg) {
73
+ const [modulePath, exportName] = spec.split("#");
74
+ const file = path.resolve(appRoot, modulePath);
75
+ let mod;
76
+ if (/\.m?js$/.test(file)) {
77
+ mod = (await import(pathToFileURL(file).href));
78
+ }
79
+ else {
80
+ let tsImport;
81
+ try {
82
+ ({ tsImport } = await import("tsx/esm/api"));
83
+ }
84
+ catch (err) {
85
+ throw new Error(`${spec}: loading a TypeScript module needs tsx, which the mesh CLI ships as a dependency and could not load (${err.message}). ` +
86
+ "Reinstall @mesh-tech/mesh-cli, or point the declaration at a built .js/.mjs file.");
87
+ }
88
+ mod = (await tsImport(file, import.meta.url));
89
+ }
90
+ const value = mod[exportName];
91
+ if (typeof value !== "function")
92
+ throw new Error(`${spec} is not an exported function`);
93
+ return value(arg);
94
+ }
95
+ export function localDocsEntry(input) {
96
+ return {
97
+ schemaVersion: API_REGISTRY_SCHEMA_VERSION,
98
+ name: input.name,
99
+ kind: "app",
100
+ title: input.title,
101
+ ...(input.description ? { description: input.description } : {}),
102
+ surfaces: { http: { url: `http://localhost:${input.httpPort}` } },
103
+ docs: {
104
+ url: input.docs.url,
105
+ contentHash: input.docs.contentHash,
106
+ source: { url: input.docs.sourceUrl, contentHash: input.docs.sourceContentHash },
107
+ },
108
+ enabledOps: [],
109
+ ...(input.actAs
110
+ ? {
111
+ actAs: {
112
+ url: `http://${input.name}.${input.namespace}:${input.httpPort}`,
113
+ ...(input.integrations?.length ? { integrations: [...input.integrations] } : {}),
114
+ },
115
+ }
116
+ : {}),
117
+ ...(input.sandbox ? { sandbox: input.sandbox } : {}),
118
+ ...(input.eventsEntry ? { events: { entry: input.eventsEntry } } : {}),
119
+ };
120
+ }
121
+ export const LOCAL_PLATFORM_NETWORK = "mesh-local_mesh_network";
122
+ export function aliasContainerArgs(input) {
123
+ return [
124
+ "run",
125
+ "-d",
126
+ "--rm",
127
+ "--name",
128
+ `mesh-local-alias-${input.namespace}`,
129
+ "--network",
130
+ LOCAL_PLATFORM_NETWORK,
131
+ "--network-alias",
132
+ `${input.name}.${input.namespace}`,
133
+ "--add-host",
134
+ "host.docker.internal:host-gateway",
135
+ "alpine/socat",
136
+ `TCP-LISTEN:${input.port},fork,reuseaddr`,
137
+ `TCP:host.docker.internal:${input.port}`,
138
+ ];
139
+ }
140
+ export function aliasContainerName(namespace) {
141
+ return `mesh-local-alias-${namespace}`;
142
+ }
143
+ function docker(args) {
144
+ try {
145
+ return execFileSync("docker", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
146
+ }
147
+ catch (err) {
148
+ const stderr = err.stderr?.trim();
149
+ throw new Error(`docker ${args[0]} failed${stderr ? `: ${stderr}` : ""}`);
150
+ }
151
+ }
152
+ export function removeAllAliasContainers() {
153
+ let names = [];
154
+ try {
155
+ names = docker(["ps", "-aq", "--filter", "name=^mesh-local-alias-", "--format", "{{.Names}}"])
156
+ .split("\n")
157
+ .map((n) => n.trim())
158
+ .filter(Boolean);
159
+ }
160
+ catch {
161
+ return [];
162
+ }
163
+ for (const name of names) {
164
+ try {
165
+ docker(["rm", "-f", name]);
166
+ }
167
+ catch {
168
+ }
169
+ }
170
+ return names;
171
+ }
172
+ function ensureAliasContainer(input) {
173
+ try {
174
+ docker(["rm", "-f", aliasContainerName(input.namespace)]);
175
+ }
176
+ catch {
177
+ }
178
+ docker(aliasContainerArgs(input));
179
+ }
180
+ export function resolveLocalSandbox(local, declared) {
181
+ const notes = [];
182
+ const parsed = local.sandbox === undefined ? undefined : sandboxModeSchema.safeParse(local.sandbox);
183
+ if (parsed && !parsed.success)
184
+ notes.push("the local export returned a sandbox that is not live, mock or vendor-sandbox");
185
+ const pins = {};
186
+ for (const [key, value] of Object.entries(local.env ?? {}))
187
+ if (typeof value === "string")
188
+ pins[key] = value;
189
+ const wanted = parsed?.success ? parsed.data : declared;
190
+ if (!wanted || wanted === "live")
191
+ return { sandbox: "live", pins, notes };
192
+ if (!parsed?.success || Object.keys(pins).length === 0) {
193
+ notes.push(`sandbox '${wanted}' is published as 'live': nothing pins the service env that would make it true, so try-it runs reads only`);
194
+ return { sandbox: "live", pins, notes };
195
+ }
196
+ return { sandbox: wanted, pins, notes };
197
+ }
198
+ export async function publishLocalDocs(input) {
199
+ const { declaration, appRoot, tenant, app, awsConfig } = input;
200
+ const docs = declaration.docs;
201
+ const result = {
202
+ problems: [...declaration.problems],
203
+ notes: [],
204
+ pins: {},
205
+ authzPublished: false,
206
+ authzEnv: {},
207
+ docsPublished: false,
208
+ actAs: false,
209
+ };
210
+ const { SSMClient, PutParameterCommand, GetParameterCommand } = await import("@aws-sdk/client-ssm");
211
+ const ssm = new SSMClient(awsConfig);
212
+ const namespace = localAppNamespace(tenant, app);
213
+ if (declaration.authzMetadata && input.zitadel) {
214
+ const metadata = await callExport(appRoot, declaration.authzMetadata);
215
+ const pointerPath = localAuthzPointerPath(tenant, app);
216
+ const metadataPath = `${pointerPath}/ops-hub-metadata`;
217
+ await ssm.send(new PutParameterCommand({ Name: metadataPath, Type: "String", Tier: "Advanced", Overwrite: true, Value: JSON.stringify(metadata) }));
218
+ let current = {};
219
+ try {
220
+ const existing = await ssm.send(new GetParameterCommand({ Name: pointerPath }));
221
+ current = JSON.parse(existing.Parameter?.Value ?? "{}");
222
+ }
223
+ catch {
224
+ }
225
+ let instanceRefs = [];
226
+ const { authzSpicedbSchema: schemaExport, authzSpicedbSeed: seedExport } = declaration;
227
+ if (schemaExport) {
228
+ const connection = await connectLocalSpicedb({
229
+ app,
230
+ tenant,
231
+ schema: () => callExport(appRoot, schemaExport),
232
+ ...(seedExport ? { seed: () => callExport(appRoot, seedExport) } : {}),
233
+ io: localSpicedbIo(tenant, awsConfig),
234
+ });
235
+ instanceRefs = connection.instanceRefs;
236
+ result.authzEnv = connection.env;
237
+ result.notes.push(...connection.notes);
238
+ }
239
+ const pointer = {
240
+ ...current,
241
+ spicedb: { instanceRefs },
242
+ zitadel: input.zitadel,
243
+ opsHubMetadataRef: metadataPath,
244
+ mode: typeof current.mode === "string" ? current.mode : "policy-engine",
245
+ };
246
+ await ssm.send(new PutParameterCommand({ Name: pointerPath, Type: "String", Overwrite: true, Value: JSON.stringify(pointer) }));
247
+ result.authzPublished = true;
248
+ }
249
+ else if (declaration.authzMetadata) {
250
+ result.notes.push("the app has no local Zitadel project yet, so its API-key metadata was not published");
251
+ }
252
+ if (!docs)
253
+ return result;
254
+ const local = docs.local ? (await callExport(appRoot, docs.local, { ...input.serviceEnv })) : {};
255
+ const sandbox = resolveLocalSandbox(local, docs.sandbox);
256
+ result.pins = sandbox.pins;
257
+ result.notes.push(...sandbox.notes);
258
+ result.sandbox = sandbox.sandbox;
259
+ const built = buildDocsSource({
260
+ configPath: docs.config,
261
+ ...(local.availability ? { availability: local.availability } : {}),
262
+ ...(local.events ? { events: local.events } : {}),
263
+ });
264
+ result.problems.push(...built.problems);
265
+ const config = parseDocsConfig(fs.readFileSync(docs.config, "utf8"), docs.config);
266
+ const { S3Client, PutObjectCommand } = await import("@aws-sdk/client-s3");
267
+ const s3 = new S3Client({ ...awsConfig, forcePathStyle: true });
268
+ const specKey = `api-docs/${app}/openapi.json`;
269
+ const sourceKey = docsSourceKey(app);
270
+ const specText = fs.readFileSync(built.specPath, "utf8");
271
+ await s3.send(new PutObjectCommand({ Bucket: ARTIFACTS_BUCKET, Key: specKey, Body: specText, ContentType: "application/json" }));
272
+ await s3.send(new PutObjectCommand({ Bucket: ARTIFACTS_BUCKET, Key: sourceKey, Body: built.json, ContentType: "application/json" }));
273
+ if (docs.actAs) {
274
+ try {
275
+ ensureAliasContainer({ name: app, namespace, port: input.httpPort });
276
+ result.actAs = true;
277
+ }
278
+ catch (err) {
279
+ result.notes.push(`try-it is off: ${err instanceof Error ? err.message : String(err)}`);
280
+ }
281
+ }
282
+ const entry = localDocsEntry({
283
+ name: app,
284
+ title: config.title,
285
+ ...(config.description ? { description: config.description } : {}),
286
+ docs: {
287
+ url: `s3://${ARTIFACTS_BUCKET}/${specKey}`,
288
+ contentHash: createHash("sha256").update(specText).digest("hex"),
289
+ sourceUrl: `s3://${ARTIFACTS_BUCKET}/${sourceKey}`,
290
+ sourceContentHash: built.contentHash,
291
+ },
292
+ httpPort: input.httpPort,
293
+ namespace,
294
+ actAs: result.actAs,
295
+ ...(docs.integrationTryIt?.length ? { integrations: docs.integrationTryIt } : {}),
296
+ sandbox: sandbox.sandbox,
297
+ ...(local.events?.service ? { eventsEntry: local.events.service } : {}),
298
+ });
299
+ await ssm.send(new PutParameterCommand({
300
+ Name: apiEntryPath(tenant, LOCAL_ENV, app),
301
+ Type: "String",
302
+ Tier: "Advanced",
303
+ Overwrite: true,
304
+ Value: JSON.stringify(entry),
305
+ }));
306
+ result.docsPublished = true;
307
+ return result;
308
+ }
309
+ export function localAuthzPointerPath(tenant, app) {
310
+ return `/mesh-platform/${tenant}/${LOCAL_ENV}/apps/${app}/stacks/local/authz`;
311
+ }
312
+ export async function unpublishLocalDocs(tenant, app, awsConfig) {
313
+ const removed = [];
314
+ try {
315
+ docker(["rm", "-f", aliasContainerName(localAppNamespace(tenant, app))]);
316
+ removed.push("alias container");
317
+ }
318
+ catch {
319
+ }
320
+ try {
321
+ const { SSMClient, DeleteParameterCommand } = await import("@aws-sdk/client-ssm");
322
+ await new SSMClient(awsConfig).send(new DeleteParameterCommand({ Name: apiEntryPath(tenant, LOCAL_ENV, app) }));
323
+ removed.push("registry entry");
324
+ }
325
+ catch {
326
+ }
327
+ return removed;
328
+ }
@@ -1,4 +1,5 @@
1
1
  import * as fs from "fs";
2
+ import { publishLocalHubActAsKey } from "./act-as-local.js";
2
3
  import * as os from "os";
3
4
  import * as path from "path";
4
5
  import { hubOperatorRoleKeys, hubRoleKeyTenant, restrictedRoleBase } from "@mesh-tech/api-registry";
@@ -438,6 +439,7 @@ export async function seedZitadel(awsConfig) {
438
439
  await publishHubAuthzPointer(hubAuth.projectId, org.result[0].id, awsConfig);
439
440
  await writeRegistryParams(cliClientId, awsConfig);
440
441
  await ensureOpsHubAdmin(pat, awsConfig);
442
+ await publishLocalHubActAsKey(awsConfig).catch((err) => logWarn(`Hub act-as key not published: ${err instanceof Error ? err.message : err} — local try-it will be refused`));
441
443
  writeContextConfig(LOGIN_CONTEXT, { issuer: ZITADEL_ISSUER, clientId: cliClientId });
442
444
  logSuccess(`Login context '${LOGIN_CONTEXT}' configured → try: mesh login ${LOGIN_CONTEXT}`);
443
445
  return { projectId, cliClientId, hubAuth };
@@ -499,6 +499,90 @@ var init_mocks = __esm({
499
499
  }
500
500
  });
501
501
 
502
+ // packages/api-registry/src/docs-source.ts
503
+ import { parse as parseYaml2 } from "yaml";
504
+ import { z } from "zod";
505
+ function isReaderFacingServerUrl(value) {
506
+ let url;
507
+ try {
508
+ url = new URL(value);
509
+ } catch {
510
+ return false;
511
+ }
512
+ if (url.protocol !== "https:" && url.protocol !== "http:") return false;
513
+ const host = url.hostname.toLowerCase();
514
+ if (host.startsWith("[") || /^\d+(\.\d+){3}$/.test(host)) return false;
515
+ const labels = host.split(".");
516
+ if (labels.length < 2) return false;
517
+ const tld = labels[labels.length - 1];
518
+ if (["svc", "local", "internal", "localhost", "cluster"].includes(tld)) return false;
519
+ if (host.endsWith(".svc.cluster.local") || host.endsWith(".cluster.local")) return false;
520
+ return /^[a-z]{2,63}$/.test(tld) || /^xn--[a-z0-9-]{1,59}$/.test(tld);
521
+ }
522
+ var PAGE_ID_PATTERN, pageIdSchema, docsConfigSchema, OPERATION_AVAILABILITY_STATES, operationAvailabilitySchema, docsEventsCatalogSchema, SANDBOX_MODES, sandboxModeSchema, MAX_DOCS_IMAGE_BYTES, MAX_DOCS_IMAGES_TOTAL_BYTES, IMAGE_KEY_PATTERN, BASE64_PATTERN, docsSourceBundleSchema;
523
+ var init_docs_source = __esm({
524
+ "packages/api-registry/src/docs-source.ts"() {
525
+ "use strict";
526
+ PAGE_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*(?:\/[a-z0-9][a-z0-9_-]*)*$/;
527
+ pageIdSchema = z.string().regex(PAGE_ID_PATTERN, "a page id is a lower-case path under pages/, without .md");
528
+ docsConfigSchema = z.object({
529
+ title: z.string().min(1),
530
+ description: z.string().min(1).optional(),
531
+ spec: z.string().min(1).optional(),
532
+ pages: z.string().min(1).optional(),
533
+ landing: pageIdSchema.optional(),
534
+ serverUrl: z.string().url().refine(isReaderFacingServerUrl, "serverUrl must be a reader-facing http(s) URL, never an in-cluster or local address").optional(),
535
+ nav: z.array(z.object({ group: z.string().min(1), pages: z.array(pageIdSchema).min(1) }).strict()).optional(),
536
+ reference: z.union([z.object({ label: z.string().min(1).optional() }).strict(), z.literal(false)]).optional(),
537
+ events: z.string().min(1).optional()
538
+ }).strict();
539
+ OPERATION_AVAILABILITY_STATES = ["available", "not_enabled", "not_implemented", "not_supported"];
540
+ operationAvailabilitySchema = z.object({
541
+ state: z.enum(OPERATION_AVAILABILITY_STATES),
542
+ reason: z.string().min(1).optional()
543
+ });
544
+ docsEventsCatalogSchema = z.object({
545
+ service: z.string().min(1),
546
+ types: z.array(
547
+ z.object({
548
+ name: z.string().min(1),
549
+ description: z.string().optional(),
550
+ schemaVersion: z.number().int().positive().optional(),
551
+ payloadSchema: z.record(z.string(), z.unknown()).optional()
552
+ })
553
+ ),
554
+ delivery: z.object({
555
+ payload: z.enum(["thin", "full"]).optional(),
556
+ retry: z.object({ maxAttempts: z.number().int().positive().optional() }).optional(),
557
+ signing: z.array(z.string().min(1)).optional()
558
+ }).optional()
559
+ });
560
+ SANDBOX_MODES = ["live", "mock", "vendor-sandbox"];
561
+ sandboxModeSchema = z.enum(SANDBOX_MODES);
562
+ MAX_DOCS_IMAGE_BYTES = 1024 * 1024;
563
+ MAX_DOCS_IMAGES_TOTAL_BYTES = 4 * 1024 * 1024;
564
+ IMAGE_KEY_PATTERN = /^[A-Za-z0-9_-][A-Za-z0-9._-]*(?:\/[A-Za-z0-9_-][A-Za-z0-9._-]*)*$/;
565
+ BASE64_PATTERN = /^[A-Za-z0-9+/]*={0,2}$/;
566
+ docsSourceBundleSchema = z.object({
567
+ bundleVersion: z.number().int().positive(),
568
+ config: docsConfigSchema,
569
+ pages: z.record(
570
+ pageIdSchema,
571
+ z.object({ title: z.string().min(1), description: z.string().optional(), markdown: z.string() })
572
+ ),
573
+ availability: z.record(z.string().min(1), operationAvailabilitySchema).optional(),
574
+ events: docsEventsCatalogSchema.optional(),
575
+ images: z.record(
576
+ z.string().regex(IMAGE_KEY_PATTERN, "an image key is a path under pages/"),
577
+ z.object({
578
+ contentType: z.enum(["image/png", "image/jpeg", "image/gif", "image/webp"]),
579
+ data: z.string().regex(BASE64_PATTERN, "image data is base64")
580
+ }).strict()
581
+ ).optional()
582
+ });
583
+ }
584
+ });
585
+
502
586
  // packages/api-registry/src/hub-roles.ts
503
587
  function hubOperatorRoleKeys(tenants) {
504
588
  const cleaned = [...new Set(tenants.filter((t) => t !== ""))].sort();
@@ -542,93 +626,106 @@ var init_hub_roles = __esm({
542
626
  });
543
627
 
544
628
  // packages/api-registry/src/index.ts
545
- import { z } from "zod";
629
+ import { z as z2 } from "zod";
546
630
  var rateLimitSpecSchema, rateLimitDefaultsSchema, integrationHealthSchema, apiSurfaceKindSchema, apiDocsSiteAudienceSchema, apiSurfaceSchema, apiRegistryEntrySchema, integrationStatusSchema, hubActAsExportsSchema;
547
631
  var init_src = __esm({
548
632
  "packages/api-registry/src/index.ts"() {
549
633
  "use strict";
634
+ init_docs_source();
550
635
  init_hub_roles();
551
- rateLimitSpecSchema = z.object({
552
- rps: z.number().int().min(1),
553
- burst: z.number().int().min(1)
636
+ init_docs_source();
637
+ rateLimitSpecSchema = z2.object({
638
+ rps: z2.number().int().min(1),
639
+ burst: z2.number().int().min(1)
554
640
  });
555
- rateLimitDefaultsSchema = z.record(z.string().min(1), rateLimitSpecSchema);
556
- integrationHealthSchema = z.union([
557
- z.object({ op: z.string().trim().min(1) }),
558
- z.object({ unavailable: z.string().trim().min(1) })
641
+ rateLimitDefaultsSchema = z2.record(z2.string().min(1), rateLimitSpecSchema);
642
+ integrationHealthSchema = z2.union([
643
+ z2.object({ op: z2.string().trim().min(1) }),
644
+ z2.object({ unavailable: z2.string().trim().min(1) })
559
645
  ]);
560
- apiSurfaceKindSchema = z.enum(["app", "vendor", "events"]);
561
- apiDocsSiteAudienceSchema = z.discriminatedUnion("readers", [
562
- z.object({ readers: z.literal("partner"), roles: z.array(z.string().min(1)) }),
563
- z.object({ readers: z.literal("operator") })
646
+ apiSurfaceKindSchema = z2.enum(["app", "vendor", "events"]);
647
+ apiDocsSiteAudienceSchema = z2.discriminatedUnion("readers", [
648
+ z2.object({ readers: z2.literal("partner"), roles: z2.array(z2.string().min(1)) }),
649
+ z2.object({ readers: z2.literal("operator") })
564
650
  ]);
565
- apiSurfaceSchema = z.object({
566
- http: z.object({ url: z.string().min(1) }).optional(),
567
- nexus: z.object({ endpoint: z.string().min(1), taskQueue: z.string().min(1) }).optional()
651
+ apiSurfaceSchema = z2.object({
652
+ http: z2.object({ url: z2.string().min(1) }).optional(),
653
+ nexus: z2.object({ endpoint: z2.string().min(1), taskQueue: z2.string().min(1) }).optional()
568
654
  });
569
- apiRegistryEntrySchema = z.object({
570
- schemaVersion: z.number().int().positive().default(1),
571
- name: z.string().min(1),
655
+ apiRegistryEntrySchema = z2.object({
656
+ schemaVersion: z2.number().int().positive().default(1),
657
+ name: z2.string().min(1),
572
658
  kind: apiSurfaceKindSchema.optional(),
573
- provider: z.string().optional(),
574
- externalService: z.string().min(1).optional(),
575
- version: z.string().optional(),
576
- title: z.string().optional(),
577
- description: z.string().optional(),
659
+ provider: z2.string().optional(),
660
+ externalService: z2.string().min(1).optional(),
661
+ version: z2.string().optional(),
662
+ title: z2.string().optional(),
663
+ description: z2.string().optional(),
578
664
  surfaces: apiSurfaceSchema,
579
- credentials: z.object({ keyedBy: z.string().nullish() }).optional(),
580
- docs: z.object({
581
- url: z.string().min(1),
582
- contentHash: z.string().min(1),
583
- trimmed: z.object({ url: z.string().min(1), contentHash: z.string().min(1) }).optional(),
584
- siteUrl: z.string().min(1).optional(),
585
- audience: apiDocsSiteAudienceSchema.optional()
665
+ credentials: z2.object({ keyedBy: z2.string().nullish() }).optional(),
666
+ docs: z2.object({
667
+ url: z2.string().min(1),
668
+ contentHash: z2.string().min(1),
669
+ trimmed: z2.object({ url: z2.string().min(1), contentHash: z2.string().min(1) }).optional(),
670
+ siteUrl: z2.string().min(1).optional(),
671
+ audience: apiDocsSiteAudienceSchema.optional(),
672
+ source: z2.object({ url: z2.string().min(1), contentHash: z2.string().min(1) }).optional()
586
673
  }),
587
- enabledOps: z.array(z.string()),
674
+ enabledOps: z2.array(z2.string()),
588
675
  // The consumer schema strips unknown keys, so a producer-side field that is
589
676
  // not mirrored here never reaches a consumer — mirror every addition.
590
- status: z.object({ url: z.string().min(1) }).optional(),
591
- actAs: z.object({ url: z.string().min(1) }).optional(),
592
- appVersion: z.string().min(1).optional(),
677
+ status: z2.object({ url: z2.string().min(1) }).optional(),
678
+ actAs: z2.object({ url: z2.string().min(1), integrations: z2.array(z2.string().min(1)).optional() }).optional(),
679
+ appVersion: z2.string().min(1).optional(),
593
680
  rateLimits: rateLimitDefaultsSchema.optional(),
594
681
  health: integrationHealthSchema.optional(),
595
- producedRateClasses: z.array(z.string().min(1)).optional()
682
+ producedRateClasses: z2.array(z2.string().min(1)).optional(),
683
+ sandbox: sandboxModeSchema.optional(),
684
+ events: z2.object({ entry: z2.string().min(1) }).optional()
596
685
  });
597
- integrationStatusSchema = z.object({
598
- contract: z.literal("v1"),
599
- name: z.string().min(1),
600
- provider: z.string().optional(),
601
- type: z.string().optional(),
602
- title: z.string().optional(),
603
- definitionVersion: z.string().optional(),
604
- packageVersion: z.string().min(1).optional(),
605
- tenant: z.string().min(1),
606
- instanceKey: z.string().optional(),
607
- surfaces: z.array(z.enum(["http", "nexus"])),
608
- coreMode: z.enum(["mock", "live"]),
609
- coreModeSource: z.string().optional(),
610
- enabledOps: z.array(z.string()),
611
- credentialsWired: z.boolean(),
686
+ integrationStatusSchema = z2.object({
687
+ contract: z2.literal("v1"),
688
+ name: z2.string().min(1),
689
+ provider: z2.string().optional(),
690
+ type: z2.string().optional(),
691
+ title: z2.string().optional(),
692
+ definitionVersion: z2.string().optional(),
693
+ packageVersion: z2.string().min(1).optional(),
694
+ tenant: z2.string().min(1),
695
+ instanceKey: z2.string().optional(),
696
+ surfaces: z2.array(z2.enum(["http", "nexus"])),
697
+ coreMode: z2.enum(["mock", "live"]),
698
+ coreModeSource: z2.string().optional(),
699
+ enabledOps: z2.array(z2.string()),
700
+ credentialsWired: z2.boolean(),
612
701
  rateLimits: rateLimitDefaultsSchema.optional(),
613
702
  health: integrationHealthSchema.optional(),
614
- startedAt: z.string().min(1)
703
+ startedAt: z2.string().min(1)
615
704
  });
616
- hubActAsExportsSchema = z.object({
617
- issuer: z.string().min(1),
618
- keys: z.array(
619
- z.object({
620
- kid: z.string().min(1),
621
- kty: z.literal("RSA"),
622
- n: z.string().min(1),
623
- e: z.string().min(1),
624
- alg: z.string().optional(),
625
- use: z.string().optional()
705
+ hubActAsExportsSchema = z2.object({
706
+ issuer: z2.string().min(1),
707
+ keys: z2.array(
708
+ z2.object({
709
+ kid: z2.string().min(1),
710
+ kty: z2.literal("RSA"),
711
+ n: z2.string().min(1),
712
+ e: z2.string().min(1),
713
+ alg: z2.string().optional(),
714
+ use: z2.string().optional()
626
715
  })
627
716
  ).min(1)
628
717
  });
629
718
  }
630
719
  });
631
720
 
721
+ // packages/mesh-cli/src/commands/local/act-as-local.ts
722
+ var init_act_as_local = __esm({
723
+ "packages/mesh-cli/src/commands/local/act-as-local.ts"() {
724
+ "use strict";
725
+ init_seed();
726
+ }
727
+ });
728
+
632
729
  // packages/mesh-cli/src/commands/local/mock-idps.ts
633
730
  var init_mock_idps = __esm({
634
731
  "packages/mesh-cli/src/commands/local/mock-idps.ts"() {
@@ -643,6 +740,7 @@ var HUB_DEFAULT_REDIRECT_URI, HUB_ROLES, ZITADEL_SSM_PARAM, TEST_USERS_SSM_PREFI
643
740
  var init_seed_zitadel = __esm({
644
741
  "packages/mesh-cli/src/commands/local/seed-zitadel.ts"() {
645
742
  "use strict";
743
+ init_act_as_local();
646
744
  init_src();
647
745
  init_log();
648
746
  init_errors();
@@ -686,6 +784,34 @@ var init_dev_local = __esm({
686
784
  }
687
785
  });
688
786
 
787
+ // packages/mesh-cli/src/commands/local/local-spicedb.ts
788
+ var init_local_spicedb = __esm({
789
+ "packages/mesh-cli/src/commands/local/local-spicedb.ts"() {
790
+ "use strict";
791
+ init_seed();
792
+ init_aws_errors();
793
+ init_dev_local();
794
+ }
795
+ });
796
+
797
+ // packages/mesh-cli/src/commands/local/publish-docs.ts
798
+ var init_publish_docs = __esm({
799
+ "packages/mesh-cli/src/commands/local/publish-docs.ts"() {
800
+ "use strict";
801
+ init_seed();
802
+ init_dev_local();
803
+ init_local_spicedb();
804
+ }
805
+ });
806
+
807
+ // packages/mesh-cli/src/commands/local/local-api-key.ts
808
+ var init_local_api_key = __esm({
809
+ "packages/mesh-cli/src/commands/local/local-api-key.ts"() {
810
+ "use strict";
811
+ init_seed();
812
+ }
813
+ });
814
+
689
815
  // packages/mesh-cli/src/commands/local/docker-runner.ts
690
816
  var init_docker_runner = __esm({
691
817
  "packages/mesh-cli/src/commands/local/docker-runner.ts"() {
@@ -748,6 +874,10 @@ var init_dev = __esm({
748
874
  init_dev_local();
749
875
  init_auth_provision();
750
876
  init_seed();
877
+ init_publish_docs();
878
+ init_act_as_local();
879
+ init_local_api_key();
880
+ init_seed_zitadel();
751
881
  init_mocks();
752
882
  init_dev_local();
753
883
  init_stack();