@mesh-tech/mesh-cli 0.35.2 → 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 (107) 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 -26414
  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/create-app.d.ts +1 -0
  72. package/dist/src/commands/create-app.js +4 -0
  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 +1 -0
  97. package/dist/src/utils/scaffold-versions.js +1 -0
  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 +22 -33
  101. package/fragments/base/package.json.hbs +5 -1
  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,837 @@
1
+ import {
2
+ logInfo,
3
+ logSuccess,
4
+ startHeartbeat
5
+ } from "./mesh.chunk-WFFDF346.js";
6
+ import {
7
+ MeshCliError
8
+ } from "./mesh.chunk-IQOBTL42.js";
9
+
10
+ // packages/mesh-cli/src/commands/local/stack.ts
11
+ import { execFileSync as execFileSync2, spawn, spawnSync } from "child_process";
12
+ import * as fs2 from "fs";
13
+
14
+ // packages/mesh-cli/src/commands/local/helpers.ts
15
+ import * as net from "net";
16
+ import { execFileSync } from "child_process";
17
+
18
+ // packages/mesh-cli/src/commands/local/seed.ts
19
+ var LOCAL_TENANT = "local";
20
+ var LOCAL_ENV = "dev";
21
+ var LOCAL_AWS_ENDPOINT = "http://localhost:4566";
22
+ var LOCAL_AWS_REGION = "us-east-2";
23
+ var ARTIFACTS_BUCKET = "mesh-local-artifacts";
24
+ var DATA_BUCKET = "mesh-local-data";
25
+ var APP_TENANTS_PARAM = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/app-tenants`;
26
+ var DATA_BUCKET_SEEDS = [
27
+ {
28
+ key: "samples/pipes/fragment-0001.json",
29
+ body: JSON.stringify(
30
+ {
31
+ id: "sample-0001",
32
+ kind: "pipes-fragment",
33
+ account: "0001",
34
+ asOf: "2026-01-01",
35
+ lines: [{ amount: "125.00", currency: "USD", description: "Sample credit" }]
36
+ },
37
+ null,
38
+ 2
39
+ )
40
+ }
41
+ ];
42
+ var LOCAL_AWS_CONFIG = {
43
+ endpoint: LOCAL_AWS_ENDPOINT,
44
+ region: LOCAL_AWS_REGION,
45
+ credentials: { accessKeyId: "test", secretAccessKey: "test" }
46
+ };
47
+ var AWS_CONFIG = LOCAL_AWS_CONFIG;
48
+ function localAwsEnv() {
49
+ return {
50
+ AWS_ENDPOINT_URL: LOCAL_AWS_ENDPOINT,
51
+ AWS_REGION: LOCAL_AWS_REGION,
52
+ AWS_ACCESS_KEY_ID: "test",
53
+ AWS_SECRET_ACCESS_KEY: "test"
54
+ };
55
+ }
56
+ function buildAppTenantsSeed() {
57
+ const tenantExport = {
58
+ albGroupName: LOCAL_TENANT,
59
+ certificateArn: "",
60
+ baseDomain: "localhost",
61
+ deployerRoleArn: "arn:aws:iam::000000000000:role/local-dev-deployer",
62
+ artifactsBucket: ARTIFACTS_BUCKET,
63
+ spicedb: {
64
+ endpoint: "localhost:50051",
65
+ presharedKeySecret: "local-dev-key"
66
+ },
67
+ zitadel: {
68
+ orgId: "local",
69
+ domain: "localhost:8080",
70
+ issuer: "http://localhost:8080"
71
+ }
72
+ };
73
+ return {
74
+ hubTenant: LOCAL_TENANT,
75
+ primaryTenant: tenantExport,
76
+ sharedTenants: {}
77
+ };
78
+ }
79
+ var FABRIC_CHECK_PATH = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/.fabric-check`;
80
+ var TEMPORAL_NAMESPACE = `${LOCAL_TENANT}-${LOCAL_ENV}`;
81
+ var TEMPORAL_ADDRESS = "localhost:7233";
82
+ async function seedTemporalNamespace() {
83
+ await ensureTemporalNamespace(
84
+ TEMPORAL_NAMESPACE,
85
+ `Mesh local tenant '${LOCAL_TENANT}' env '${LOCAL_ENV}' (seeded by mesh start)`
86
+ );
87
+ }
88
+ async function ensureTemporalNamespace(namespace, description) {
89
+ const { Connection } = await import("@temporalio/client");
90
+ const connection = await Connection.connect({ address: TEMPORAL_ADDRESS });
91
+ try {
92
+ await connection.workflowService.registerNamespace({
93
+ namespace,
94
+ description: description ?? `Mesh local namespace '${namespace}'`,
95
+ // protobufjs accepts plain numbers for int64 at runtime; the generated
96
+ // typing insists on Long (whose package isn't a direct dep), hence any.
97
+ workflowExecutionRetentionPeriod: { seconds: 3 * 24 * 60 * 60 }
98
+ });
99
+ logInfo(`Registered Temporal namespace '${namespace}' \u2014 waiting for it to become active\u2026`);
100
+ const deadline = Date.now() + 2e4;
101
+ while (Date.now() < deadline) {
102
+ try {
103
+ await connection.workflowService.describeTaskQueue({
104
+ namespace,
105
+ taskQueue: { name: "namespace-propagation-probe" },
106
+ taskQueueType: 1
107
+ });
108
+ break;
109
+ } catch {
110
+ await new Promise((resolve2) => setTimeout(resolve2, 1e3));
111
+ }
112
+ }
113
+ logSuccess(`Temporal namespace '${namespace}' is active`);
114
+ } catch (err) {
115
+ if (err?.name === "NamespaceAlreadyExistsError" || /already exists/i.test(err?.message ?? "")) {
116
+ logInfo(`Temporal namespace '${namespace}' already exists`);
117
+ } else {
118
+ throw err;
119
+ }
120
+ } finally {
121
+ await connection.close();
122
+ }
123
+ }
124
+ function buildFabricCheckPayload(targetBytes = 8e3) {
125
+ const base = { probe: "advanced-tier-round-trip", tenant: LOCAL_TENANT, env: LOCAL_ENV, pad: "" };
126
+ const overhead = JSON.stringify(base).length;
127
+ return JSON.stringify({ ...base, pad: "x".repeat(Math.max(0, targetBytes - overhead)) });
128
+ }
129
+ async function verifyFabric() {
130
+ const { SSMClient, PutParameterCommand, GetParameterCommand, GetParametersByPathCommand, DeleteParametersCommand } = await import("@aws-sdk/client-ssm");
131
+ const ssm = new SSMClient(AWS_CONFIG);
132
+ const payload = buildFabricCheckPayload();
133
+ const mainParam = FABRIC_CHECK_PATH;
134
+ const childParams = [`${FABRIC_CHECK_PATH}/vpc`, `${FABRIC_CHECK_PATH}/eks`];
135
+ try {
136
+ await ssm.send(
137
+ new PutParameterCommand({
138
+ Name: mainParam,
139
+ Type: "String",
140
+ Tier: "Advanced",
141
+ Overwrite: true,
142
+ Value: payload
143
+ })
144
+ );
145
+ for (const name of childParams) {
146
+ await ssm.send(
147
+ new PutParameterCommand({ Name: name, Type: "String", Overwrite: true, Value: `{"probe":"${name}"}` })
148
+ );
149
+ }
150
+ const roundTrip = await ssm.send(new GetParameterCommand({ Name: mainParam }));
151
+ if (roundTrip.Parameter?.Value !== payload) {
152
+ throw new MeshCliError(
153
+ `Local AWS fabric failed the ${payload.length}-byte Advanced-tier SSM round-trip (got ${roundTrip.Parameter?.Value?.length ?? 0} bytes back).`,
154
+ {
155
+ remediation: {
156
+ docs: "packages/mesh-cli/stack/docker-compose.yml \u2014 swap the ministack image for motoserver/moto (design \xA72.5 fallback)"
157
+ }
158
+ }
159
+ );
160
+ }
161
+ const byPath = await ssm.send(
162
+ new GetParametersByPathCommand({ Path: FABRIC_CHECK_PATH, Recursive: true })
163
+ );
164
+ const returned = new Set((byPath.Parameters ?? []).map((p) => p.Name));
165
+ const missing = childParams.filter((name) => !returned.has(name));
166
+ if (missing.length > 0) {
167
+ throw new MeshCliError(
168
+ `Local AWS fabric's GetParametersByPath missed child parameter(s): ${missing.join(", ")} (splitKeys exports would not resolve).`,
169
+ {
170
+ remediation: {
171
+ docs: "packages/mesh-cli/stack/docker-compose.yml \u2014 swap the ministack image for motoserver/moto (design \xA72.5 fallback)"
172
+ }
173
+ }
174
+ );
175
+ }
176
+ logSuccess(`Fabric check passed: ${payload.length}-byte Advanced-tier round-trip + GetParametersByPath`);
177
+ } finally {
178
+ await ssm.send(new DeleteParametersCommand({ Names: [mainParam, ...childParams] })).catch(() => {
179
+ });
180
+ }
181
+ }
182
+ async function registerTenantEnv(tenant, opts = {}) {
183
+ const { SSMClient, PutParameterCommand } = await import("@aws-sdk/client-ssm");
184
+ const ssm = new SSMClient(AWS_CONFIG);
185
+ await ssm.send(
186
+ new PutParameterCommand({
187
+ Name: `/mesh-platform/${tenant}`,
188
+ Type: "String",
189
+ Overwrite: true,
190
+ Value: JSON.stringify({ name: tenant, tier: opts.tier ?? "shared", subdomain: tenant }),
191
+ Description: `Tenant registration (seeded by mesh CLI, local platform)`
192
+ })
193
+ );
194
+ await ssm.send(
195
+ new PutParameterCommand({
196
+ Name: `/mesh-platform/${tenant}/${LOCAL_ENV}`,
197
+ Type: "String",
198
+ Overwrite: true,
199
+ Value: JSON.stringify({
200
+ env: LOCAL_ENV,
201
+ region: LOCAL_AWS_REGION,
202
+ baseDomain: "localhost",
203
+ // monitoring is true: the local stack runs the same Loki/Tempo/
204
+ // Prometheus backends the hosted platform provides.
205
+ services: { temporal: true, zitadel: true, monitoring: true }
206
+ }),
207
+ Description: `Environment registration (seeded by mesh CLI, local platform)`
208
+ })
209
+ );
210
+ }
211
+ async function seedLocalPlatform() {
212
+ const { SSMClient, PutParameterCommand } = await import("@aws-sdk/client-ssm");
213
+ const ssm = new SSMClient(AWS_CONFIG);
214
+ await ssm.send(
215
+ new PutParameterCommand({
216
+ Name: APP_TENANTS_PARAM,
217
+ Type: "String",
218
+ Overwrite: true,
219
+ Value: JSON.stringify(buildAppTenantsSeed()),
220
+ Description: `TenantEnvironments exports for ${LOCAL_TENANT}-${LOCAL_ENV} (seeded by mesh start)`
221
+ })
222
+ );
223
+ await registerTenantEnv(LOCAL_TENANT, { tier: "hub" });
224
+ logSuccess(`Registered tenant '${LOCAL_TENANT}' env '${LOCAL_ENV}' \u2192 ${APP_TENANTS_PARAM}`);
225
+ const { S3Client, CreateBucketCommand, PutObjectCommand } = await import("@aws-sdk/client-s3");
226
+ const s3 = new S3Client({ ...AWS_CONFIG, forcePathStyle: true });
227
+ for (const bucket of [ARTIFACTS_BUCKET, DATA_BUCKET]) {
228
+ try {
229
+ await s3.send(new CreateBucketCommand({ Bucket: bucket }));
230
+ logSuccess(`Created bucket s3://${bucket}`);
231
+ } catch (err) {
232
+ if (err?.name === "BucketAlreadyOwnedByYou" || err?.name === "BucketAlreadyExists") {
233
+ logInfo(`Bucket s3://${bucket} already exists`);
234
+ } else {
235
+ throw err;
236
+ }
237
+ }
238
+ }
239
+ for (const seed of DATA_BUCKET_SEEDS) {
240
+ await s3.send(
241
+ new PutObjectCommand({
242
+ Bucket: DATA_BUCKET,
243
+ Key: seed.key,
244
+ Body: seed.body,
245
+ ContentType: "application/json"
246
+ })
247
+ );
248
+ }
249
+ logSuccess(`Seeded s3://${DATA_BUCKET} with ${DATA_BUCKET_SEEDS.length} sample object(s)`);
250
+ await seedTemporalNamespace();
251
+ return { parameter: APP_TENANTS_PARAM, bucket: ARTIFACTS_BUCKET };
252
+ }
253
+
254
+ // packages/mesh-cli/src/utils/aws-errors.ts
255
+ function isAwsErrorCode(err, code) {
256
+ const e = err;
257
+ return e?.name === code || e?.__type === code || e?.Code === code || e?.code === code || typeof e?.message === "string" && e.message.includes(code);
258
+ }
259
+
260
+ // packages/mesh-cli/src/commands/local/helpers.ts
261
+ async function upsertLocalSecret(secretId, value, client) {
262
+ const { SecretsManagerClient, CreateSecretCommand, PutSecretValueCommand } = await import("@aws-sdk/client-secrets-manager");
263
+ const sm = client ?? new SecretsManagerClient(LOCAL_AWS_CONFIG);
264
+ const secretString = JSON.stringify(value);
265
+ try {
266
+ await sm.send(new CreateSecretCommand({ Name: secretId, SecretString: secretString }));
267
+ } catch (err) {
268
+ if (isAwsErrorCode(err, "ResourceExistsException")) {
269
+ await sm.send(new PutSecretValueCommand({ SecretId: secretId, SecretString: secretString }));
270
+ } else {
271
+ throw err;
272
+ }
273
+ }
274
+ }
275
+ function probeTcp(port, opts = {}) {
276
+ const { host = "127.0.0.1", timeoutMs = 2e3 } = opts;
277
+ return new Promise((resolve2) => {
278
+ const socket = net.connect({ host, port, timeout: timeoutMs });
279
+ socket.once("connect", () => {
280
+ socket.destroy();
281
+ resolve2(true);
282
+ });
283
+ socket.once("error", () => resolve2(false));
284
+ socket.once("timeout", () => {
285
+ socket.destroy();
286
+ resolve2(false);
287
+ });
288
+ });
289
+ }
290
+ function hostPortsOf(endpoints) {
291
+ const ports = /* @__PURE__ */ new Set();
292
+ const fromUrl = (raw) => {
293
+ try {
294
+ const u = new URL(raw.includes("://") ? raw : `tcp://${raw}`);
295
+ const port = Number(u.port || (u.protocol === "https:" ? 443 : u.protocol === "http:" ? 80 : NaN));
296
+ if (Number.isInteger(port) && port > 0) ports.add(port);
297
+ } catch {
298
+ }
299
+ };
300
+ for (const e of endpoints) {
301
+ if (e.probe.kind === "tcp") ports.add(e.probe.port);
302
+ else if (e.probe.kind === "http") fromUrl(e.probe.url);
303
+ fromUrl(e.url);
304
+ }
305
+ return [...ports].sort((a, b) => a - b);
306
+ }
307
+ function parseDockerPsPorts(output, ignorePrefix) {
308
+ const held = /* @__PURE__ */ new Map();
309
+ for (const line of output.split("\n")) {
310
+ const tab = line.indexOf(" ");
311
+ if (tab < 0) continue;
312
+ const name = line.slice(0, tab).trim();
313
+ if (!name || name.startsWith(ignorePrefix)) continue;
314
+ for (const m of line.slice(tab + 1).matchAll(/:(\d+)->\d+\/(?:tcp|udp)/g)) {
315
+ const port = Number(m[1]);
316
+ if (!held.has(port)) held.set(port, name);
317
+ }
318
+ }
319
+ return held;
320
+ }
321
+ var defaultPortConflictIo = {
322
+ dockerPs: () => {
323
+ try {
324
+ return execFileSync("docker", ["ps", "--format", "{{.Names}} {{.Ports}}"], {
325
+ encoding: "utf-8",
326
+ stdio: ["ignore", "pipe", "ignore"]
327
+ });
328
+ } catch {
329
+ return "";
330
+ }
331
+ },
332
+ processOn: (port) => {
333
+ try {
334
+ const out = execFileSync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-Fc"], {
335
+ encoding: "utf-8",
336
+ stdio: ["ignore", "pipe", "ignore"]
337
+ });
338
+ const cmd = out.split("\n").find((l) => l.startsWith("c"));
339
+ return cmd ? cmd.slice(1) : null;
340
+ } catch {
341
+ return null;
342
+ }
343
+ },
344
+ listening: (port) => probeTcp(port, { timeoutMs: 300 })
345
+ };
346
+ async function findPortConflicts(ports, ignorePrefix, io = defaultPortConflictIo) {
347
+ const unique = [...new Set(ports)].sort((a, b) => a - b);
348
+ const bound = (await Promise.all(unique.map(async (port) => await io.listening(port) ? port : null))).filter(
349
+ (p) => p !== null
350
+ );
351
+ if (bound.length === 0) return [];
352
+ const containers = parseDockerPsPorts(io.dockerPs(), ignorePrefix);
353
+ return bound.map((port) => {
354
+ const container = containers.get(port);
355
+ if (container) return { port, holder: { kind: "container", name: container } };
356
+ const process2 = io.processOn(port);
357
+ return { port, holder: process2 ? { kind: "process", name: process2 } : null };
358
+ });
359
+ }
360
+ function describePortConflicts(conflicts, moveHints = /* @__PURE__ */ new Map()) {
361
+ const lines = conflicts.flatMap(({ port, holder }) => {
362
+ const remedy = holder?.kind === "container" ? `docker stop ${holder.name}` : `lsof -nP -iTCP:${port} -sTCP:LISTEN, then stop what it names`;
363
+ const what = holder ? `held by ${holder.kind} ${holder.name}` : "held by something lsof would not name";
364
+ const move = moveHints.get(port);
365
+ return move ? [` ${String(port).padEnd(5)} ${what.padEnd(38)} (${remedy})`, ` ${"".padEnd(5)} or: ${move}`] : [` ${String(port).padEnd(5)} ${what.padEnd(38)} (${remedy})`];
366
+ });
367
+ return `Cannot start the local platform \u2014 ${conflicts.length === 1 ? "a host port it needs is" : "host ports it needs are"} already in use:
368
+ ` + lines.join("\n") + '\nStop what holds the port (or move it), then run mesh start again. A port lost this way does not fail as "port in use": the container never joins the network and a later service reports an unrelated error.\nIf the port is held by something you cannot stop and the stack can live without it: mesh start --skip-port-check';
369
+ }
370
+
371
+ // packages/mesh-cli/src/commands/local/stack.ts
372
+ import * as path2 from "path";
373
+ import { fileURLToPath } from "url";
374
+ import { parse as parseYaml } from "yaml";
375
+
376
+ // packages/mesh-cli/src/utils/cache-home.ts
377
+ import fs from "node:fs";
378
+ import os from "node:os";
379
+ import path from "node:path";
380
+ function meshCacheHome() {
381
+ return process.env.MESH_CACHE_HOME || path.join(os.homedir(), ".cache", "mesh");
382
+ }
383
+ function meshCacheDir(...segments) {
384
+ const dir = path.join(meshCacheHome(), ...segments);
385
+ fs.mkdirSync(dir, { recursive: true });
386
+ return dir;
387
+ }
388
+
389
+ // packages/mesh-cli/src/commands/local/stack.ts
390
+ var COMPOSE_PROJECT = "mesh-local";
391
+ function localLogsDir() {
392
+ return meshCacheDir("mesh-local", "logs");
393
+ }
394
+ function localProbesDir() {
395
+ return meshCacheDir("mesh-local", "probes");
396
+ }
397
+ function writeAppServiceProbes(args) {
398
+ const entries = Object.entries(args.services);
399
+ if (entries.length === 0) return void 0;
400
+ const healthPath = args.healthPath ?? "/health";
401
+ const targets = entries.map(([name, port]) => ({
402
+ targets: [`http://host.docker.internal:${port}${healthPath}`],
403
+ labels: {
404
+ type: "service",
405
+ tenant: args.tenant,
406
+ env: args.env,
407
+ app: args.app,
408
+ service: name,
409
+ target: name
410
+ }
411
+ }));
412
+ const file = path2.join(localProbesDir(), `${args.tenant}-${args.app}-services.json`);
413
+ fs2.writeFileSync(file, `${JSON.stringify(targets, null, 2)}
414
+ `);
415
+ return file;
416
+ }
417
+ var STACK_ENDPOINTS = [
418
+ {
419
+ service: "temporal",
420
+ label: "Temporal gRPC",
421
+ url: "localhost:7233",
422
+ probe: { kind: "tcp", port: 7233 }
423
+ },
424
+ {
425
+ service: "temporal-ui",
426
+ label: "Temporal UI",
427
+ url: "http://localhost:8233/namespaces/local-dev/workflows",
428
+ probe: { kind: "http", url: "http://localhost:8233" },
429
+ hint: "seeded namespace: local-dev"
430
+ },
431
+ {
432
+ service: "zitadel",
433
+ label: "Zitadel console",
434
+ url: "http://localhost:8080",
435
+ probe: { kind: "http", url: "http://localhost:8080/debug/healthz" },
436
+ hint: "admin@local.mesh / LocalDev1!"
437
+ },
438
+ {
439
+ service: "mailpit",
440
+ label: "Mailbox (local mail)",
441
+ url: "http://localhost:8025",
442
+ probe: { kind: "http", url: "http://localhost:8025/readyz" },
443
+ hint: "every activation + password-reset mail Zitadel sends locally lands here"
444
+ },
445
+ {
446
+ service: "database",
447
+ label: "Postgres",
448
+ url: "postgres://postgres:postgres@localhost:5433",
449
+ probe: { kind: "tcp", port: 5433 },
450
+ hint: "databases: temporal, app, spicedb, hub"
451
+ },
452
+ {
453
+ service: "spicedb",
454
+ label: "SpiceDB gRPC",
455
+ url: "localhost:50051",
456
+ probe: { kind: "tcp", port: 50051 },
457
+ hint: "preshared key: local-dev-key"
458
+ },
459
+ {
460
+ service: "ministack",
461
+ label: "ministack (local AWS endpoint)",
462
+ url: "http://localhost:4566",
463
+ probe: { kind: "http", url: "http://localhost:4566/_ministack/health" },
464
+ hint: "SSM registry under /mesh-platform/local/dev"
465
+ },
466
+ {
467
+ service: "stackport",
468
+ label: "StackPort (local AWS console)",
469
+ url: "http://localhost:4567",
470
+ probe: { kind: "http", url: "http://localhost:4567" },
471
+ hint: "browse the registry (SSM), secrets, S3 artifacts"
472
+ },
473
+ {
474
+ service: "memcached",
475
+ label: "Memcached",
476
+ url: "localhost:11211",
477
+ probe: { kind: "tcp", port: 11211 }
478
+ },
479
+ {
480
+ service: "loki",
481
+ label: "Loki (logs)",
482
+ url: "http://localhost:3100",
483
+ probe: { kind: "http", url: "http://localhost:3100/ready" },
484
+ hint: "mesh dev --local service logs, hosted label scheme"
485
+ },
486
+ {
487
+ service: "tempo",
488
+ label: "Tempo (traces)",
489
+ url: "http://localhost:3200",
490
+ probe: { kind: "http", url: "http://localhost:3200/ready" }
491
+ },
492
+ {
493
+ service: "prometheus",
494
+ label: "Prometheus (metrics)",
495
+ url: "http://localhost:9090",
496
+ probe: { kind: "http", url: "http://localhost:9090/-/ready" }
497
+ },
498
+ {
499
+ service: "otel-collector",
500
+ label: "OTel collector (OTLP in)",
501
+ url: "http://localhost:4318",
502
+ probe: { kind: "http", url: "http://localhost:13133" },
503
+ hint: "apps: OTEL_EXPORTER_OTLP_ENDPOINT (injected by mesh dev --local)"
504
+ },
505
+ {
506
+ service: "elasticsearch",
507
+ label: "OpenSearch (Temporal visibility)",
508
+ url: "http://localhost:9200",
509
+ probe: { kind: "http", url: "http://localhost:9200" }
510
+ }
511
+ ];
512
+ var DEFAULT_HUB_PORT = "9000";
513
+ function hubPort() {
514
+ const raw = process.env.MESH_HUB_PORT?.trim();
515
+ if (!raw) return DEFAULT_HUB_PORT;
516
+ if (!/^\d+$/.test(raw) || Number(raw) < 1 || Number(raw) > 65535) {
517
+ throw new MeshCliError(`MESH_HUB_PORT must be a TCP port number 1\u201365535 (got '${raw}').`, {
518
+ remediation: { command: "unset MESH_HUB_PORT # or export a valid port, e.g. 9100" }
519
+ });
520
+ }
521
+ return raw;
522
+ }
523
+ function hubEndpoints() {
524
+ return [
525
+ {
526
+ service: "hub-api",
527
+ label: "Hub API",
528
+ url: "http://localhost:4568",
529
+ probe: { kind: "http", url: "http://localhost:4568/health" }
530
+ },
531
+ {
532
+ service: "hub-ui",
533
+ label: "Hub UI",
534
+ url: `http://localhost:${hubPort()}`,
535
+ probe: { kind: "http", url: `http://localhost:${hubPort()}/ping` },
536
+ hint: "sign in: admin@local.mesh or dev@local.mesh / LocalDev1! (oauth2-proxy, same as deployed)"
537
+ }
538
+ ];
539
+ }
540
+ function findPackageRoot(startDir) {
541
+ let dir = startDir ?? path2.dirname(fileURLToPath(import.meta.url));
542
+ for (let i = 0; i < 8; i++) {
543
+ const pkgPath = path2.join(dir, "package.json");
544
+ if (fs2.existsSync(pkgPath)) {
545
+ try {
546
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
547
+ if (pkg.name === "@mesh-tech/mesh-cli") return dir;
548
+ } catch {
549
+ }
550
+ }
551
+ const parent = path2.dirname(dir);
552
+ if (parent === dir) break;
553
+ dir = parent;
554
+ }
555
+ throw new MeshCliError("Could not locate the mesh-cli package root (stack assets missing).", {
556
+ remediation: { command: "npm install -g @mesh-tech/mesh-cli" }
557
+ });
558
+ }
559
+ function stackDir() {
560
+ const dir = path2.join(findPackageRoot(), "stack");
561
+ if (!fs2.existsSync(path2.join(dir, "docker-compose.yml"))) {
562
+ throw new MeshCliError(`Local stack assets not found at ${dir}.`, {
563
+ remediation: { command: "npm install -g @mesh-tech/mesh-cli" }
564
+ });
565
+ }
566
+ return dir;
567
+ }
568
+ var BASE_COMPOSE_FILE = "docker-compose.yml";
569
+ var HUB_OVERLAY_FILE = "docker-compose.hub.yml";
570
+ var IDPS_OVERLAY_FILE = "docker-compose.idps.yml";
571
+ function parseComposeServiceNames(yamlText) {
572
+ const doc = parseYaml(yamlText);
573
+ return Object.keys(doc?.services ?? {});
574
+ }
575
+ var overlayServicesCache = /* @__PURE__ */ new Map();
576
+ function overlayServicesOf(overlayFile) {
577
+ const cached = overlayServicesCache.get(overlayFile);
578
+ if (cached) return cached;
579
+ const file = path2.join(stackDir(), overlayFile);
580
+ if (!fs2.existsSync(file)) {
581
+ throw new MeshCliError(`Local stack assets are incomplete: ${file} is missing.`, {
582
+ remediation: { command: "npm install -g @mesh-tech/mesh-cli" }
583
+ });
584
+ }
585
+ const base = new Set(
586
+ parseComposeServiceNames(fs2.readFileSync(path2.join(stackDir(), BASE_COMPOSE_FILE), "utf-8"))
587
+ );
588
+ const names = new Set(
589
+ parseComposeServiceNames(fs2.readFileSync(file, "utf-8")).filter((n) => !base.has(n))
590
+ );
591
+ overlayServicesCache.set(overlayFile, names);
592
+ return names;
593
+ }
594
+ function hubOverlayServices() {
595
+ return overlayServicesOf(HUB_OVERLAY_FILE);
596
+ }
597
+ function idpsOverlayServices() {
598
+ return overlayServicesOf(IDPS_OVERLAY_FILE);
599
+ }
600
+ function stackOwnedElsewhere() {
601
+ try {
602
+ const first = compose(["ps", "-q"]).trim().split("\n").filter(Boolean)[0];
603
+ if (!first) return void 0;
604
+ const label = execFileSync2(
605
+ "docker",
606
+ ["inspect", first, "--format", '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}'],
607
+ { encoding: "utf-8" }
608
+ ).trim();
609
+ if (!label) return void 0;
610
+ const real = (p) => {
611
+ try {
612
+ return fs2.realpathSync(p);
613
+ } catch {
614
+ return path2.resolve(p);
615
+ }
616
+ };
617
+ return real(label) === real(stackDir()) ? void 0 : label;
618
+ } catch {
619
+ return void 0;
620
+ }
621
+ }
622
+ function ensureDockerAvailable() {
623
+ const probe = spawnSync("docker", ["info", "--format", "{{.ServerVersion}}"], {
624
+ stdio: ["ignore", "pipe", "pipe"]
625
+ });
626
+ if (probe.error || probe.status !== 0) {
627
+ throw new MeshCliError("Docker is not available (is Docker Desktop / the docker daemon running?).", {
628
+ remediation: { docs: "https://docs.docker.com/get-docker/" }
629
+ });
630
+ }
631
+ }
632
+ function compose(args, opts = {}) {
633
+ const dir = stackDir();
634
+ const files = ["-f", path2.join(dir, "docker-compose.yml")];
635
+ if (opts.hub) files.push("-f", path2.join(dir, "docker-compose.hub.yml"));
636
+ if (opts.idps) files.push("-f", path2.join(dir, IDPS_OVERLAY_FILE));
637
+ const fullArgs = ["compose", "-p", COMPOSE_PROJECT, ...files, ...args];
638
+ return execFileSync2("docker", fullArgs, {
639
+ cwd: dir,
640
+ encoding: "utf-8",
641
+ // MESH_LOCAL_LOGS on every call: the compose file mounts it into the
642
+ // OTel collector, and an unset variable would both warn and resolve to
643
+ // a different mount (recreate churn between CLI and raw compose runs).
644
+ // IGNORE_ORPHANS: base-only `up` calls would otherwise flag the hub
645
+ // overlay's running containers as orphans.
646
+ env: {
647
+ ...process.env,
648
+ MESH_LOCAL_LOGS: localLogsDir(),
649
+ MESH_LOCAL_PROBES: localProbesDir(),
650
+ COMPOSE_IGNORE_ORPHANS: "1",
651
+ ...opts.env
652
+ },
653
+ stdio: opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"]
654
+ });
655
+ }
656
+ function summarizeComposeFailure(output) {
657
+ const lines = output.split("\n").map((l) => l.trim()).filter(Boolean);
658
+ const errorish = lines.filter(
659
+ (l) => /\b(error|failed|failure|fatal|denied|unauthorized|cannot|no such)\b/i.test(l)
660
+ );
661
+ const line = errorish.at(-1) ?? lines.at(-1) ?? "no output captured";
662
+ return line.length > 300 ? `${line.slice(0, 297)}\u2026` : line;
663
+ }
664
+ function composeLogPath(op) {
665
+ const dir = meshCacheDir("mesh-local");
666
+ return path2.join(dir, `compose-${op.replace(/[^a-z0-9-]/gi, "_")}.log`);
667
+ }
668
+ async function composeStreamed(args, opts = {}) {
669
+ const dir = stackDir();
670
+ const files = ["-f", path2.join(dir, "docker-compose.yml")];
671
+ if (opts.hub) files.push("-f", path2.join(dir, "docker-compose.hub.yml"));
672
+ if (opts.idps) files.push("-f", path2.join(dir, IDPS_OVERLAY_FILE));
673
+ const fullArgs = ["compose", "-p", COMPOSE_PROJECT, ...files, ...args];
674
+ const env = {
675
+ ...process.env,
676
+ MESH_LOCAL_LOGS: localLogsDir(),
677
+ MESH_LOCAL_PROBES: localProbesDir(),
678
+ COMPOSE_IGNORE_ORPHANS: "1",
679
+ ...opts.env
680
+ };
681
+ const op = args[0] ?? "cmd";
682
+ if (process.stderr.isTTY) {
683
+ const res = spawnSync("docker", fullArgs, {
684
+ cwd: dir,
685
+ env,
686
+ stdio: ["ignore", "inherit", "inherit"]
687
+ });
688
+ if (res.status !== 0) {
689
+ throw new MeshCliError(
690
+ `docker compose ${op} failed (exit ${res.status ?? "?"}) \u2014 the compose output above has the details.`,
691
+ {
692
+ remediation: {
693
+ command: `docker compose -p ${COMPOSE_PROJECT} ps && docker compose -p ${COMPOSE_PROJECT} logs --tail 50`
694
+ }
695
+ }
696
+ );
697
+ }
698
+ return;
699
+ }
700
+ const logPath = composeLogPath(op);
701
+ const logStream = fs2.createWriteStream(logPath);
702
+ const heartbeat = startHeartbeat(`docker compose ${args.join(" ")}`);
703
+ let captured = "";
704
+ let exitCode;
705
+ try {
706
+ exitCode = await new Promise((resolve2, reject) => {
707
+ const child = spawn("docker", fullArgs, {
708
+ cwd: dir,
709
+ env,
710
+ stdio: ["ignore", "pipe", "pipe"]
711
+ });
712
+ const consume = (chunk) => {
713
+ heartbeat.touch();
714
+ const text = chunk.toString();
715
+ captured += text;
716
+ logStream.write(text);
717
+ process.stderr.write(text);
718
+ };
719
+ child.stdout.on("data", consume);
720
+ child.stderr.on("data", consume);
721
+ child.on("error", reject);
722
+ child.on("close", (code) => resolve2(code ?? 1));
723
+ });
724
+ } finally {
725
+ heartbeat.stop();
726
+ await new Promise((resolve2) => logStream.end(resolve2));
727
+ }
728
+ if (exitCode !== 0) {
729
+ throw new MeshCliError(
730
+ `docker compose ${op} failed (exit ${exitCode}): ${summarizeComposeFailure(captured)}`,
731
+ {
732
+ remediation: {
733
+ command: `docker compose -p ${COMPOSE_PROJECT} logs --tail 50`,
734
+ docs: logPath
735
+ }
736
+ }
737
+ );
738
+ }
739
+ }
740
+ function parseComposePs(output) {
741
+ const services = [];
742
+ for (const line of output.split("\n")) {
743
+ const trimmed = line.trim();
744
+ if (!trimmed) continue;
745
+ try {
746
+ const entry = JSON.parse(trimmed);
747
+ services.push({
748
+ name: entry.Service ?? entry.Name ?? "unknown",
749
+ state: entry.State ?? "unknown",
750
+ health: entry.Health || void 0
751
+ });
752
+ } catch {
753
+ }
754
+ }
755
+ return services;
756
+ }
757
+ function stackServices() {
758
+ return parseComposePs(compose(["ps", "-a", "--format", "json"], { hub: true }));
759
+ }
760
+ var ONE_SHOT_SERVICES = /* @__PURE__ */ new Set(["spicedb-migrate", "zitadel-machinekey-init"]);
761
+ function crashedServices(services, opts) {
762
+ const dormant = new Set(
763
+ opts.overlays.filter((o) => !o.started).flatMap((o) => [...o.services])
764
+ );
765
+ return services.filter(
766
+ (s) => s.state === "exited" && !ONE_SHOT_SERVICES.has(s.name) && !dormant.has(s.name)
767
+ );
768
+ }
769
+ function overlayRunning(services, overlayServices) {
770
+ return services.some((s) => overlayServices.has(s.name) && s.state === "running");
771
+ }
772
+ function hubApiRunning(services) {
773
+ return services.some((s) => s.name === "hub-api" && s.state === "running");
774
+ }
775
+ async function probeEndpoint(endpoint) {
776
+ if (endpoint.probe.kind === "http") {
777
+ try {
778
+ const res = await fetch(endpoint.probe.url, { signal: AbortSignal.timeout(3e3) });
779
+ return res.status < 500;
780
+ } catch {
781
+ return false;
782
+ }
783
+ }
784
+ if (endpoint.probe.kind === "tcp") {
785
+ return probeTcp(endpoint.probe.port, { timeoutMs: 3e3 });
786
+ }
787
+ return true;
788
+ }
789
+
790
+ export {
791
+ LOCAL_TENANT,
792
+ LOCAL_ENV,
793
+ ARTIFACTS_BUCKET,
794
+ LOCAL_AWS_CONFIG,
795
+ localAwsEnv,
796
+ TEMPORAL_ADDRESS,
797
+ ensureTemporalNamespace,
798
+ verifyFabric,
799
+ registerTenantEnv,
800
+ seedLocalPlatform,
801
+ isAwsErrorCode,
802
+ upsertLocalSecret,
803
+ probeTcp,
804
+ hostPortsOf,
805
+ findPortConflicts,
806
+ describePortConflicts,
807
+ meshCacheDir,
808
+ COMPOSE_PROJECT,
809
+ localLogsDir,
810
+ localProbesDir,
811
+ writeAppServiceProbes,
812
+ STACK_ENDPOINTS,
813
+ DEFAULT_HUB_PORT,
814
+ hubPort,
815
+ hubEndpoints,
816
+ findPackageRoot,
817
+ stackDir,
818
+ BASE_COMPOSE_FILE,
819
+ HUB_OVERLAY_FILE,
820
+ IDPS_OVERLAY_FILE,
821
+ parseComposeServiceNames,
822
+ hubOverlayServices,
823
+ idpsOverlayServices,
824
+ stackOwnedElsewhere,
825
+ ensureDockerAvailable,
826
+ compose,
827
+ summarizeComposeFailure,
828
+ composeLogPath,
829
+ composeStreamed,
830
+ parseComposePs,
831
+ stackServices,
832
+ ONE_SHOT_SERVICES,
833
+ crashedServices,
834
+ overlayRunning,
835
+ hubApiRunning,
836
+ probeEndpoint
837
+ };