@mesh-tech/mesh-cli 0.13.0 → 0.15.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 (56) hide show
  1. package/dist/bin/mesh.js +940 -277
  2. package/dist/bin/mesh.js.map +4 -4
  3. package/dist/build-info.json +2 -2
  4. package/dist/src/commands/create-app.d.ts +4 -0
  5. package/dist/src/commands/create-app.d.ts.map +1 -1
  6. package/dist/src/commands/create-app.js +13 -6
  7. package/dist/src/commands/create-app.js.map +1 -1
  8. package/dist/src/commands/dev.d.ts +11 -0
  9. package/dist/src/commands/dev.d.ts.map +1 -1
  10. package/dist/src/commands/dev.js +70 -1
  11. package/dist/src/commands/dev.js.map +1 -1
  12. package/dist/src/commands/hub/index.d.ts.map +1 -1
  13. package/dist/src/commands/hub/index.js +65 -4
  14. package/dist/src/commands/hub/index.js.map +1 -1
  15. package/dist/src/commands/local/auth-provision.d.ts +34 -0
  16. package/dist/src/commands/local/auth-provision.d.ts.map +1 -1
  17. package/dist/src/commands/local/auth-provision.js +78 -1
  18. package/dist/src/commands/local/auth-provision.js.map +1 -1
  19. package/dist/src/commands/local/hub-local.d.ts +38 -0
  20. package/dist/src/commands/local/hub-local.d.ts.map +1 -1
  21. package/dist/src/commands/local/hub-local.js +137 -0
  22. package/dist/src/commands/local/hub-local.js.map +1 -1
  23. package/dist/src/commands/local/index.d.ts.map +1 -1
  24. package/dist/src/commands/local/index.js +31 -3
  25. package/dist/src/commands/local/index.js.map +1 -1
  26. package/dist/src/commands/local/seed-zitadel.d.ts +10 -3
  27. package/dist/src/commands/local/seed-zitadel.d.ts.map +1 -1
  28. package/dist/src/commands/local/seed-zitadel.js +259 -78
  29. package/dist/src/commands/local/seed-zitadel.js.map +1 -1
  30. package/dist/src/commands/local/stack.d.ts +30 -0
  31. package/dist/src/commands/local/stack.d.ts.map +1 -1
  32. package/dist/src/commands/local/stack.js +48 -0
  33. package/dist/src/commands/local/stack.js.map +1 -1
  34. package/dist/src/commands/secrets/index.d.ts +1 -0
  35. package/dist/src/commands/secrets/index.d.ts.map +1 -1
  36. package/dist/src/commands/secrets/index.js +12 -0
  37. package/dist/src/commands/secrets/index.js.map +1 -1
  38. package/dist/src/commands/secrets/reindex.d.ts +48 -0
  39. package/dist/src/commands/secrets/reindex.d.ts.map +1 -0
  40. package/dist/src/commands/secrets/reindex.js +157 -0
  41. package/dist/src/commands/secrets/reindex.js.map +1 -0
  42. package/dist/src/commands/secrets/set.d.ts +63 -0
  43. package/dist/src/commands/secrets/set.d.ts.map +1 -1
  44. package/dist/src/commands/secrets/set.js +78 -3
  45. package/dist/src/commands/secrets/set.js.map +1 -1
  46. package/fragments/base/index.ts.hbs +9 -0
  47. package/fragments/base/package.json.hbs +1 -1
  48. package/fragments/service/api/package.json.hbs +4 -0
  49. package/fragments/service/api/src/index.ts.hbs +1 -4
  50. package/fragments/temporal/worker/package.json.hbs +1 -0
  51. package/fragments/temporal/worker/src/activities.ts.hbs +5 -1
  52. package/fragments/temporal/worker/src/workflows.ts.hbs +4 -1
  53. package/package.json +3 -2
  54. package/skills/core/SKILL.md +4 -2
  55. package/stack/docker-compose.hub.yml +5 -0
  56. package/stack/docker-compose.yml +35 -0
package/dist/bin/mesh.js CHANGED
@@ -580,11 +580,11 @@ function createVcsFolderReader(opts) {
580
580
  }
581
581
  return p;
582
582
  };
583
- const readPath = async (repo, path41) => {
583
+ const readPath = async (repo, path42) => {
584
584
  const dir = await cloneRepo(repo);
585
- const ref = await resolveFolderRef(dir, path41);
586
- if (!ref) throw new Error(`path "${path41}" not found on any ref of ${repo}`);
587
- const files = await readFolderAtRef(dir, ref, path41);
585
+ const ref = await resolveFolderRef(dir, path42);
586
+ if (!ref) throw new Error(`path "${path42}" not found on any ref of ${repo}`);
587
+ const files = await readFolderAtRef(dir, ref, path42);
588
588
  return { ref, files };
589
589
  };
590
590
  const cleanup = async () => {
@@ -1530,7 +1530,7 @@ function execTailscaleCmd(args) {
1530
1530
  }
1531
1531
  attempts.push({ binary, args });
1532
1532
  }
1533
- return new Promise((resolve15, reject) => {
1533
+ return new Promise((resolve16, reject) => {
1534
1534
  let index = 0;
1535
1535
  function tryNext() {
1536
1536
  if (index >= attempts.length) {
@@ -1542,7 +1542,7 @@ function execTailscaleCmd(args) {
1542
1542
  if (error) {
1543
1543
  tryNext();
1544
1544
  } else {
1545
- resolve15(stdout);
1545
+ resolve16(stdout);
1546
1546
  }
1547
1547
  });
1548
1548
  }
@@ -1859,8 +1859,8 @@ function resolveHubPlatformName(platform) {
1859
1859
  return platform?.name ?? "mesh";
1860
1860
  }
1861
1861
  async function ssmGetParameter(name) {
1862
- const { SSMClient: SSMClient4, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
1863
- const ssm = new SSMClient4({ region: process.env.AWS_REGION || "us-east-2" });
1862
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
1863
+ const ssm = new SSMClient5({ region: process.env.AWS_REGION || "us-east-2" });
1864
1864
  const resp = await ssm.send(new GetParameterCommand2({ Name: name, WithDecryption: true }));
1865
1865
  return resp.Parameter?.Value;
1866
1866
  }
@@ -1901,8 +1901,8 @@ var init_kubeconfig = __esm({
1901
1901
  // libs/mesh-cli/src/utils/temporal-auth.ts
1902
1902
  import { execFileSync as execFileSync3 } from "node:child_process";
1903
1903
  async function resolveTemporalAuth(tenant, env, platformName = tenant) {
1904
- const { SSMClient: SSMClient4, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
1905
- const ssm = new SSMClient4({ region: process.env.AWS_REGION || "us-east-2" });
1904
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
1905
+ const ssm = new SSMClient5({ region: process.env.AWS_REGION || "us-east-2" });
1906
1906
  const results = {};
1907
1907
  async function trySSM(name) {
1908
1908
  try {
@@ -2034,14 +2034,14 @@ var init_temporal_auth = __esm({
2034
2034
  // libs/mesh-cli/src/utils/reachability.ts
2035
2035
  import { createConnection } from "node:net";
2036
2036
  function probeTcpReachable(host, port, timeoutMs = 1500) {
2037
- return new Promise((resolve15) => {
2037
+ return new Promise((resolve16) => {
2038
2038
  let settled = false;
2039
2039
  const socket = createConnection({ host, port });
2040
2040
  const finish = (ok) => {
2041
2041
  if (settled) return;
2042
2042
  settled = true;
2043
2043
  socket.destroy();
2044
- resolve15(ok);
2044
+ resolve16(ok);
2045
2045
  };
2046
2046
  socket.setTimeout(timeoutMs);
2047
2047
  socket.once("connect", () => finish(true));
@@ -2050,7 +2050,7 @@ function probeTcpReachable(host, port, timeoutMs = 1500) {
2050
2050
  });
2051
2051
  }
2052
2052
  function probeConnectionHolds(host, port, timeoutMs = 800, holdMs = 25) {
2053
- return new Promise((resolve15) => {
2053
+ return new Promise((resolve16) => {
2054
2054
  let settled = false;
2055
2055
  let holdTimer;
2056
2056
  const socket = createConnection({ host, port });
@@ -2059,7 +2059,7 @@ function probeConnectionHolds(host, port, timeoutMs = 800, holdMs = 25) {
2059
2059
  settled = true;
2060
2060
  if (holdTimer) clearTimeout(holdTimer);
2061
2061
  socket.destroy();
2062
- resolve15(ok);
2062
+ resolve16(ok);
2063
2063
  };
2064
2064
  socket.setTimeout(timeoutMs);
2065
2065
  socket.once("connect", () => {
@@ -2191,16 +2191,16 @@ async function waitForPort(host, port, timeoutMs, intervalMs = 500) {
2191
2191
  const attemptTimeout = Math.max(250, Math.min(1e3, remaining));
2192
2192
  if (await tryConnect(host, port, attemptTimeout)) return true;
2193
2193
  if (Date.now() + intervalMs >= deadline) return false;
2194
- await new Promise((resolve15) => setTimeout(resolve15, intervalMs));
2194
+ await new Promise((resolve16) => setTimeout(resolve16, intervalMs));
2195
2195
  }
2196
2196
  }
2197
2197
  function tryConnect(host, port, timeoutMs) {
2198
- return new Promise((resolve15) => {
2198
+ return new Promise((resolve16) => {
2199
2199
  const socket = net.connect({ host, port });
2200
2200
  const done = (ok) => {
2201
2201
  socket.removeAllListeners();
2202
2202
  socket.destroy();
2203
- resolve15(ok);
2203
+ resolve16(ok);
2204
2204
  };
2205
2205
  socket.setTimeout(timeoutMs);
2206
2206
  socket.once("connect", () => done(true));
@@ -2277,7 +2277,7 @@ async function ensureTemporalNamespace(namespace, description) {
2277
2277
  });
2278
2278
  break;
2279
2279
  } catch {
2280
- await new Promise((resolve15) => setTimeout(resolve15, 1e3));
2280
+ await new Promise((resolve16) => setTimeout(resolve16, 1e3));
2281
2281
  }
2282
2282
  }
2283
2283
  logSuccess(`Temporal namespace '${namespace}' is active`);
@@ -2297,8 +2297,8 @@ function buildFabricCheckPayload(targetBytes = 8e3) {
2297
2297
  return JSON.stringify({ ...base, pad: "x".repeat(Math.max(0, targetBytes - overhead)) });
2298
2298
  }
2299
2299
  async function verifyFabric() {
2300
- const { SSMClient: SSMClient4, PutParameterCommand, GetParameterCommand: GetParameterCommand2, GetParametersByPathCommand: GetParametersByPathCommand3, DeleteParametersCommand } = await import("@aws-sdk/client-ssm");
2301
- const ssm = new SSMClient4(AWS_CONFIG);
2300
+ const { SSMClient: SSMClient5, PutParameterCommand, GetParameterCommand: GetParameterCommand2, GetParametersByPathCommand: GetParametersByPathCommand3, DeleteParametersCommand } = await import("@aws-sdk/client-ssm");
2301
+ const ssm = new SSMClient5(AWS_CONFIG);
2302
2302
  const payload = buildFabricCheckPayload();
2303
2303
  const mainParam = FABRIC_CHECK_PATH;
2304
2304
  const childParams = [`${FABRIC_CHECK_PATH}/vpc`, `${FABRIC_CHECK_PATH}/eks`];
@@ -2350,8 +2350,8 @@ async function verifyFabric() {
2350
2350
  }
2351
2351
  }
2352
2352
  async function registerTenantEnv(tenant, opts = {}) {
2353
- const { SSMClient: SSMClient4, PutParameterCommand } = await import("@aws-sdk/client-ssm");
2354
- const ssm = new SSMClient4(AWS_CONFIG);
2353
+ const { SSMClient: SSMClient5, PutParameterCommand } = await import("@aws-sdk/client-ssm");
2354
+ const ssm = new SSMClient5(AWS_CONFIG);
2355
2355
  await ssm.send(
2356
2356
  new PutParameterCommand({
2357
2357
  Name: `/mesh-platform/${tenant}`,
@@ -2379,8 +2379,8 @@ async function registerTenantEnv(tenant, opts = {}) {
2379
2379
  );
2380
2380
  }
2381
2381
  async function seedLocalPlatform() {
2382
- const { SSMClient: SSMClient4, PutParameterCommand } = await import("@aws-sdk/client-ssm");
2383
- const ssm = new SSMClient4(AWS_CONFIG);
2382
+ const { SSMClient: SSMClient5, PutParameterCommand } = await import("@aws-sdk/client-ssm");
2383
+ const ssm = new SSMClient5(AWS_CONFIG);
2384
2384
  await ssm.send(
2385
2385
  new PutParameterCommand({
2386
2386
  Name: APP_TENANTS_PARAM,
@@ -2434,14 +2434,14 @@ var init_seed = __esm({
2434
2434
  // libs/mesh-cli/src/commands/local/helpers.ts
2435
2435
  import * as net2 from "net";
2436
2436
  async function upsertLocalSecret(secretId, value, client) {
2437
- const { SecretsManagerClient: SecretsManagerClient8, CreateSecretCommand: CreateSecretCommand3, PutSecretValueCommand: PutSecretValueCommand3 } = await import("@aws-sdk/client-secrets-manager");
2438
- const sm = client ?? new SecretsManagerClient8(LOCAL_AWS_CONFIG);
2437
+ const { SecretsManagerClient: SecretsManagerClient9, CreateSecretCommand: CreateSecretCommand4, PutSecretValueCommand: PutSecretValueCommand4 } = await import("@aws-sdk/client-secrets-manager");
2438
+ const sm = client ?? new SecretsManagerClient9(LOCAL_AWS_CONFIG);
2439
2439
  const secretString = JSON.stringify(value);
2440
2440
  try {
2441
- await sm.send(new CreateSecretCommand3({ Name: secretId, SecretString: secretString }));
2441
+ await sm.send(new CreateSecretCommand4({ Name: secretId, SecretString: secretString }));
2442
2442
  } catch (err) {
2443
2443
  if (err?.name === "ResourceExistsException") {
2444
- await sm.send(new PutSecretValueCommand3({ SecretId: secretId, SecretString: secretString }));
2444
+ await sm.send(new PutSecretValueCommand4({ SecretId: secretId, SecretString: secretString }));
2445
2445
  } else {
2446
2446
  throw err;
2447
2447
  }
@@ -2449,16 +2449,16 @@ async function upsertLocalSecret(secretId, value, client) {
2449
2449
  }
2450
2450
  function probeTcp(port, opts = {}) {
2451
2451
  const { host = "127.0.0.1", timeoutMs = 2e3 } = opts;
2452
- return new Promise((resolve15) => {
2452
+ return new Promise((resolve16) => {
2453
2453
  const socket = net2.connect({ host, port, timeout: timeoutMs });
2454
2454
  socket.once("connect", () => {
2455
2455
  socket.destroy();
2456
- resolve15(true);
2456
+ resolve16(true);
2457
2457
  });
2458
- socket.once("error", () => resolve15(false));
2458
+ socket.once("error", () => resolve16(false));
2459
2459
  socket.once("timeout", () => {
2460
2460
  socket.destroy();
2461
- resolve15(false);
2461
+ resolve16(false);
2462
2462
  });
2463
2463
  });
2464
2464
  }
@@ -2496,7 +2496,8 @@ __export(stack_exports, {
2496
2496
  stackDir: () => stackDir,
2497
2497
  stackOwnedElsewhere: () => stackOwnedElsewhere,
2498
2498
  stackServices: () => stackServices,
2499
- summarizeComposeFailure: () => summarizeComposeFailure
2499
+ summarizeComposeFailure: () => summarizeComposeFailure,
2500
+ writeAppServiceProbes: () => writeAppServiceProbes
2500
2501
  });
2501
2502
  import { execFileSync as execFileSync4, spawn, spawnSync } from "child_process";
2502
2503
  import * as fs7 from "fs";
@@ -2514,6 +2515,26 @@ function localProbesDir() {
2514
2515
  fs7.mkdirSync(dir, { recursive: true });
2515
2516
  return dir;
2516
2517
  }
2518
+ function writeAppServiceProbes(args) {
2519
+ const entries = Object.entries(args.services);
2520
+ if (entries.length === 0) return void 0;
2521
+ const healthPath = args.healthPath ?? "/health";
2522
+ const targets = entries.map(([name, port]) => ({
2523
+ targets: [`http://host.docker.internal:${port}${healthPath}`],
2524
+ labels: {
2525
+ type: "service",
2526
+ tenant: args.tenant,
2527
+ env: args.env,
2528
+ app: args.app,
2529
+ service: name,
2530
+ target: name
2531
+ }
2532
+ }));
2533
+ const file = path8.join(localProbesDir(), `${args.tenant}-${args.app}-services.json`);
2534
+ fs7.writeFileSync(file, `${JSON.stringify(targets, null, 2)}
2535
+ `);
2536
+ return file;
2537
+ }
2517
2538
  function hubPort() {
2518
2539
  const raw = process.env.MESH_HUB_PORT?.trim();
2519
2540
  if (!raw) return DEFAULT_HUB_PORT;
@@ -2690,7 +2711,7 @@ async function composeStreamed(args, opts = {}) {
2690
2711
  let captured = "";
2691
2712
  let exitCode;
2692
2713
  try {
2693
- exitCode = await new Promise((resolve15, reject) => {
2714
+ exitCode = await new Promise((resolve16, reject) => {
2694
2715
  const child = spawn("docker", fullArgs, {
2695
2716
  cwd: dir,
2696
2717
  env,
@@ -2706,11 +2727,11 @@ async function composeStreamed(args, opts = {}) {
2706
2727
  child.stdout.on("data", consume);
2707
2728
  child.stderr.on("data", consume);
2708
2729
  child.on("error", reject);
2709
- child.on("close", (code) => resolve15(code ?? 1));
2730
+ child.on("close", (code) => resolve16(code ?? 1));
2710
2731
  });
2711
2732
  } finally {
2712
2733
  heartbeat.stop();
2713
- await new Promise((resolve15) => logStream.end(resolve15));
2734
+ await new Promise((resolve16) => logStream.end(resolve16));
2714
2735
  }
2715
2736
  if (exitCode !== 0) {
2716
2737
  throw new MeshCliError(
@@ -2798,6 +2819,13 @@ var init_stack = __esm({
2798
2819
  probe: { kind: "http", url: "http://localhost:8080/debug/healthz" },
2799
2820
  hint: "admin@local.mesh / LocalDev1!"
2800
2821
  },
2822
+ {
2823
+ service: "mailpit",
2824
+ label: "Mailbox (local mail)",
2825
+ url: "http://localhost:8025",
2826
+ probe: { kind: "http", url: "http://localhost:8025/readyz" },
2827
+ hint: "every activation + password-reset mail Zitadel sends locally lands here"
2828
+ },
2801
2829
  {
2802
2830
  service: "database",
2803
2831
  label: "Postgres",
@@ -3158,12 +3186,12 @@ async function fetchRemoteExternalCredentials(tenant, env, external, profile) {
3158
3186
  const region = process.env.MESH_PLATFORM_REGION ?? process.env.AWS_REGION ?? "us-east-2";
3159
3187
  const fallbackProfile = !process.env.AWS_ACCESS_KEY_ID && !process.env.AWS_PROFILE ? process.env.MESH_AWS_PROFILE ?? profile : void 0;
3160
3188
  try {
3161
- const { SecretsManagerClient: SecretsManagerClient8, GetSecretValueCommand: GetSecretValueCommand8 } = await import("@aws-sdk/client-secrets-manager");
3189
+ const { SecretsManagerClient: SecretsManagerClient9, GetSecretValueCommand: GetSecretValueCommand9 } = await import("@aws-sdk/client-secrets-manager");
3162
3190
  if (fallbackProfile) process.env.AWS_PROFILE = fallbackProfile;
3163
- const sm = new SecretsManagerClient8({ region });
3191
+ const sm = new SecretsManagerClient9({ region });
3164
3192
  let res;
3165
3193
  try {
3166
- res = await sm.send(new GetSecretValueCommand8({ SecretId: secretId }));
3194
+ res = await sm.send(new GetSecretValueCommand9({ SecretId: secretId }));
3167
3195
  } finally {
3168
3196
  if (fallbackProfile) delete process.env.AWS_PROFILE;
3169
3197
  }
@@ -3277,8 +3305,8 @@ async function seedLocalMock(args) {
3277
3305
  `${secretName}/.config`,
3278
3306
  Object.fromEntries(Object.entries(value).filter(([key]) => !SECRET_KEY_RE.test(key)))
3279
3307
  );
3280
- const { SSMClient: SSMClient4, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3281
- const ssm = new SSMClient4(LOCAL_AWS_CONFIG);
3308
+ const { SSMClient: SSMClient5, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3309
+ const ssm = new SSMClient5(LOCAL_AWS_CONFIG);
3282
3310
  const base = `/mesh-platform/${tenant}/${LOCAL_ENV}/apps/${app}/stacks/local/external-services/${decl.external}`;
3283
3311
  await ssm.send(
3284
3312
  new PutParameterCommand({
@@ -3382,16 +3410,48 @@ var init_mocks = __esm({
3382
3410
  });
3383
3411
 
3384
3412
  // libs/api-registry/src/hub-roles.ts
3385
- var HUB_STAFF_ROLES, HUB_RESTRICTED_ROLES, HUB_BASE_ROLES, RESTRICTED;
3413
+ function restrictedRoleBase(roleKey) {
3414
+ const sep5 = roleKey.indexOf(HUB_ROLE_KEY_SEPARATOR);
3415
+ const base = sep5 === -1 ? roleKey : roleKey.slice(0, sep5);
3416
+ return RESTRICTED.has(base) ? base : null;
3417
+ }
3418
+ function hubRoleKeyTenant(roleKey) {
3419
+ const sep5 = roleKey.indexOf(HUB_ROLE_KEY_SEPARATOR);
3420
+ if (sep5 === -1) return null;
3421
+ const scope = roleKey.slice(sep5 + 1);
3422
+ const appSep = scope.indexOf(HUB_ROLE_APP_SEPARATOR);
3423
+ return appSep === -1 ? scope : scope.slice(0, appSep);
3424
+ }
3425
+ function hubOperatorRoleKeys(tenants) {
3426
+ const cleaned = [...new Set(tenants.filter((t) => t !== ""))].sort();
3427
+ for (const tenant of cleaned) {
3428
+ for (const sep5 of [HUB_ROLE_KEY_SEPARATOR, HUB_ROLE_APP_SEPARATOR]) {
3429
+ if (tenant.includes(sep5)) {
3430
+ throw new Error(
3431
+ `hubOperatorRoleKeys: tenant name "${tenant}" contains the role-key separator "${sep5}" \u2014 it would parse as a different tenant's or app's grant`
3432
+ );
3433
+ }
3434
+ }
3435
+ }
3436
+ return [
3437
+ ...HUB_BASE_ROLES,
3438
+ ...cleaned.flatMap(
3439
+ (tenant) => HUB_RESTRICTED_ROLES.map((role) => `${role}${HUB_ROLE_KEY_SEPARATOR}${tenant}`)
3440
+ )
3441
+ ];
3442
+ }
3443
+ var HUB_STAFF_ROLES, HUB_RESTRICTED_ROLES, HUB_BASE_ROLES, HUB_ROLE_KEY_SEPARATOR, HUB_ROLE_APP_SEPARATOR, RESTRICTED;
3386
3444
  var init_hub_roles = __esm({
3387
3445
  "libs/api-registry/src/hub-roles.ts"() {
3388
3446
  "use strict";
3389
- HUB_STAFF_ROLES = ["ops", "admin", "developer"];
3390
- HUB_RESTRICTED_ROLES = ["auditor", "vendor"];
3447
+ HUB_STAFF_ROLES = ["ops", "admin"];
3448
+ HUB_RESTRICTED_ROLES = ["developer", "auditor", "vendor"];
3391
3449
  HUB_BASE_ROLES = [
3392
3450
  ...HUB_STAFF_ROLES,
3393
3451
  ...HUB_RESTRICTED_ROLES
3394
3452
  ];
3453
+ HUB_ROLE_KEY_SEPARATOR = ":";
3454
+ HUB_ROLE_APP_SEPARATOR = "/";
3395
3455
  RESTRICTED = new Set(HUB_RESTRICTED_ROLES);
3396
3456
  }
3397
3457
  });
@@ -3506,7 +3566,9 @@ async function api(pat, method, apiPath, body, orgId) {
3506
3566
  const text = await res.text();
3507
3567
  const data = text ? JSON.parse(text) : {};
3508
3568
  if (!res.ok) {
3509
- const err = new Error(`Zitadel ${method} ${apiPath} \u2192 ${res.status}: ${data?.message ?? text}`);
3569
+ const err = new Error(
3570
+ `Zitadel ${method} ${apiPath} \u2192 ${res.status}: ${data?.message ?? text}`
3571
+ );
3510
3572
  err.status = res.status;
3511
3573
  err.zitadelCode = data?.code;
3512
3574
  throw err;
@@ -3552,7 +3614,12 @@ async function ensureCliApp(pat, projectId) {
3552
3614
  logInfo(`Zitadel app '${CLI_APP_NAME}' already exists`);
3553
3615
  return existing;
3554
3616
  }
3555
- const created = await api(pat, "POST", `/management/v1/projects/${projectId}/apps/oidc`, buildCliAppPayload());
3617
+ const created = await api(
3618
+ pat,
3619
+ "POST",
3620
+ `/management/v1/projects/${projectId}/apps/oidc`,
3621
+ buildCliAppPayload()
3622
+ );
3556
3623
  logSuccess(`Created Zitadel application '${CLI_APP_NAME}' (PKCE + device code)`);
3557
3624
  return created.clientId;
3558
3625
  }
@@ -3594,12 +3661,16 @@ function readHubAuth() {
3594
3661
  return null;
3595
3662
  }
3596
3663
  }
3664
+ function hubRoleDisplayName(roleKey) {
3665
+ const tenant = hubRoleKeyTenant(roleKey);
3666
+ return tenant === null ? roleKey : `${restrictedRoleBase(roleKey) ?? roleKey} (${tenant})`;
3667
+ }
3597
3668
  async function ensureHubRoles(pat, projectId) {
3598
3669
  for (const role of HUB_ROLES) {
3599
3670
  try {
3600
3671
  await api(pat, "POST", `/management/v1/projects/${projectId}/roles`, {
3601
3672
  roleKey: role,
3602
- displayName: role
3673
+ displayName: hubRoleDisplayName(role)
3603
3674
  });
3604
3675
  logSuccess(`Created Hub role '${role}'`);
3605
3676
  } catch (err) {
@@ -3628,7 +3699,10 @@ async function reconcileHubRedirectUris(pat, projectId, app) {
3628
3699
  redirectUris,
3629
3700
  postLogoutRedirectUris,
3630
3701
  responseTypes: cfg.responseTypes ?? ["OIDC_RESPONSE_TYPE_CODE"],
3631
- grantTypes: cfg.grantTypes ?? ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN"],
3702
+ grantTypes: cfg.grantTypes ?? [
3703
+ "OIDC_GRANT_TYPE_AUTHORIZATION_CODE",
3704
+ "OIDC_GRANT_TYPE_REFRESH_TOKEN"
3705
+ ],
3632
3706
  appType: "OIDC_APP_TYPE_WEB",
3633
3707
  authMethodType: "OIDC_AUTH_METHOD_TYPE_BASIC",
3634
3708
  accessTokenType: cfg.accessTokenType ?? "OIDC_TOKEN_TYPE_JWT",
@@ -3637,7 +3711,9 @@ async function reconcileHubRedirectUris(pat, projectId, app) {
3637
3711
  idTokenUserinfoAssertion: cfg.idTokenUserinfoAssertion ?? true,
3638
3712
  devMode: cfg.devMode ?? true
3639
3713
  });
3640
- logSuccess(`Registered Hub redirect URI http://localhost:${hubPort()}/oauth2/callback (MESH_HUB_PORT)`);
3714
+ logSuccess(
3715
+ `Registered Hub redirect URI http://localhost:${hubPort()}/oauth2/callback (MESH_HUB_PORT)`
3716
+ );
3641
3717
  }
3642
3718
  async function ensureHubApp(pat, projectId) {
3643
3719
  const existing = await searchZitadelApp(pat, projectId, HUB_APP_NAME);
@@ -3713,15 +3789,42 @@ async function seedHubAuth(pat) {
3713
3789
  const persistedCookie = persisted?.cookieSecret && [16, 24, 32].includes(persisted.cookieSecret.length) ? persisted.cookieSecret : void 0;
3714
3790
  const config = {
3715
3791
  clientId: app.clientId,
3792
+ projectId,
3716
3793
  clientSecret: app.clientSecret ?? persisted?.clientSecret ?? "",
3717
3794
  cookieSecret: persistedCookie ?? (await import("crypto")).randomBytes(16).toString("hex")
3718
3795
  };
3719
3796
  fs9.writeFileSync(hubAuthPath(), JSON.stringify(config, null, 2), { mode: 384 });
3720
3797
  return config;
3721
3798
  }
3799
+ async function publishHubAuthzPointer(projectId, orgId, awsConfig) {
3800
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3801
+ const ssm = new SSMClient5(awsConfig);
3802
+ const name = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/apps/hub/stacks/local/authz`;
3803
+ let current = {};
3804
+ try {
3805
+ const existing = await ssm.send(new GetParameterCommand2({ Name: name }));
3806
+ current = JSON.parse(existing.Parameter?.Value ?? "{}");
3807
+ } catch {
3808
+ }
3809
+ const value = {
3810
+ spicedb: { instanceRefs: [] },
3811
+ ...current,
3812
+ zitadel: { projectId, orgId, issuer: ZITADEL_ISSUER }
3813
+ };
3814
+ await ssm.send(
3815
+ new PutParameterCommand({
3816
+ Name: name,
3817
+ Type: "String",
3818
+ Overwrite: true,
3819
+ Value: JSON.stringify(value),
3820
+ Description: "Hub authz pointer (local analog of the platform Pulumi program)"
3821
+ })
3822
+ );
3823
+ logSuccess(`Hub authz pointer published \u2192 project ${projectId} in org ${orgId}`);
3824
+ }
3722
3825
  async function writeRegistryParams(cliClientId, awsConfig) {
3723
- const { SSMClient: SSMClient4, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3724
- const ssm = new SSMClient4(awsConfig);
3826
+ const { SSMClient: SSMClient5, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3827
+ const ssm = new SSMClient5(awsConfig);
3725
3828
  await ssm.send(
3726
3829
  new PutParameterCommand({
3727
3830
  Name: ZITADEL_SSM_PARAM,
@@ -3743,6 +3846,77 @@ async function writeRegistryParams(cliClientId, awsConfig) {
3743
3846
  );
3744
3847
  }
3745
3848
  }
3849
+ async function ensureOpsHubAdmin(pat, awsConfig) {
3850
+ const {
3851
+ SecretsManagerClient: SecretsManagerClient9,
3852
+ GetSecretValueCommand: GetSecretValueCommand9,
3853
+ CreateSecretCommand: CreateSecretCommand4,
3854
+ PutSecretValueCommand: PutSecretValueCommand4
3855
+ } = await import("@aws-sdk/client-secrets-manager");
3856
+ const sm = new SecretsManagerClient9(awsConfig);
3857
+ const existing = await sm.send(new GetSecretValueCommand9({ SecretId: OPS_HUB_SECRET_ID })).catch(() => null);
3858
+ if (existing?.SecretString) {
3859
+ logSuccess("Hub admin key already provisioned (Zitadel writes enabled)");
3860
+ return;
3861
+ }
3862
+ const userName = "hub-opshub";
3863
+ const found = await api(pat, "POST", "/management/v1/users/_search", {
3864
+ queries: [{ userNameQuery: { userName, method: "TEXT_QUERY_METHOD_EQUALS" } }]
3865
+ });
3866
+ let userId = found?.result?.[0]?.id;
3867
+ if (!userId) {
3868
+ const created = await api(pat, "POST", "/management/v1/users/machine", {
3869
+ userName,
3870
+ name: "Hub Ops Hub admin",
3871
+ description: "Local Hub Zitadel admin plane (seeded by mesh start)"
3872
+ });
3873
+ userId = created?.userId;
3874
+ }
3875
+ if (!userId) throw new MeshCliError("could not create the Hub's Zitadel admin user");
3876
+ await api(pat, "POST", "/admin/v1/members", { userId, roles: ["IAM_OWNER"] }).catch(
3877
+ () => void 0
3878
+ );
3879
+ const key = await api(pat, "POST", `/management/v1/users/${userId}/keys`, {
3880
+ type: "KEY_TYPE_JSON"
3881
+ });
3882
+ if (!key?.keyDetails)
3883
+ throw new MeshCliError("Zitadel did not return a machine key for the Hub admin user");
3884
+ const raw = JSON.parse(Buffer.from(key.keyDetails, "base64").toString("utf8"));
3885
+ const secretString = JSON.stringify({ keyId: raw.keyId, key: raw.key, userId: raw.userId });
3886
+ await sm.send(new CreateSecretCommand4({ Name: OPS_HUB_SECRET_ID, SecretString: secretString })).catch(async () => {
3887
+ await sm.send(
3888
+ new PutSecretValueCommand4({ SecretId: OPS_HUB_SECRET_ID, SecretString: secretString })
3889
+ );
3890
+ });
3891
+ logSuccess(`Hub admin key provisioned \u2192 ${OPS_HUB_SECRET_ID} (Zitadel writes enabled)`);
3892
+ }
3893
+ async function ensureLocalSmtp(pat) {
3894
+ try {
3895
+ const existing = await api(pat, "POST", "/admin/v1/smtp/_search", {}).catch(() => null);
3896
+ const configs = existing?.result ?? [];
3897
+ const match = configs.find((c) => c.host === LOCAL_SMTP.host);
3898
+ if (!match) {
3899
+ const created = await api(pat, "POST", "/admin/v1/smtp", {
3900
+ senderAddress: LOCAL_SMTP.from,
3901
+ senderName: LOCAL_SMTP.fromName,
3902
+ tls: false,
3903
+ host: LOCAL_SMTP.host,
3904
+ user: "",
3905
+ password: ""
3906
+ });
3907
+ if (created?.id) await api(pat, "POST", `/admin/v1/smtp/${created.id}/_activate`, {});
3908
+ logSuccess(`Local mailbox wired to Zitadel \u2192 ${LOCAL_SMTP.host} (view at http://localhost:8025)`);
3909
+ return;
3910
+ }
3911
+ if (match.state !== "SMTP_CONFIG_ACTIVE" && match.id) {
3912
+ await api(pat, "POST", `/admin/v1/smtp/${match.id}/_activate`, {});
3913
+ }
3914
+ } catch (err) {
3915
+ logWarn(
3916
+ `Could not wire the local mailbox to Zitadel (${err.message}). Activation and password-reset mail will not be delivered locally.`
3917
+ );
3918
+ }
3919
+ }
3746
3920
  async function seedZitadel(awsConfig) {
3747
3921
  const pat = readSeederPat();
3748
3922
  const org = await api(pat, "POST", "/admin/v1/orgs/_search", {
@@ -3754,21 +3928,26 @@ async function seedZitadel(awsConfig) {
3754
3928
  { remediation: { command: "mesh stop --destroy && mesh start" } }
3755
3929
  );
3756
3930
  }
3757
- logSuccess(`Platform org '${PLATFORM_ORG}' ready (platform tenant root \u2014 holds platform-service auth config)`);
3931
+ logSuccess(
3932
+ `Platform org '${PLATFORM_ORG}' ready (platform tenant root \u2014 holds platform-service auth config)`
3933
+ );
3758
3934
  try {
3759
3935
  await api(pat, "PUT", "/v2/features/instance", { loginV2: { required: false } });
3760
3936
  } catch {
3761
3937
  }
3938
+ await ensureLocalSmtp(pat);
3762
3939
  const projectId = await ensureCliProject(pat);
3763
3940
  const cliClientId = await ensureCliApp(pat, projectId);
3764
3941
  await ensureTestUsers(pat);
3765
3942
  const hubAuth = await seedHubAuth(pat);
3943
+ await publishHubAuthzPointer(hubAuth.projectId, org.result[0].id, awsConfig);
3766
3944
  await writeRegistryParams(cliClientId, awsConfig);
3945
+ await ensureOpsHubAdmin(pat, awsConfig);
3767
3946
  writeContextConfig(LOGIN_CONTEXT, { issuer: ZITADEL_ISSUER, clientId: cliClientId });
3768
3947
  logSuccess(`Login context '${LOGIN_CONTEXT}' configured \u2192 try: mesh login ${LOGIN_CONTEXT}`);
3769
3948
  return { projectId, cliClientId, hubAuth };
3770
3949
  }
3771
- var ZITADEL_ISSUER, LOGIN_CONTEXT, PLATFORM_ORG, CLI_PROJECT_NAME, CLI_APP_NAME, CLI_REDIRECT_URI, HUB_PROJECT_NAME, HUB_APP_NAME, hubRedirectUri, HUB_DEFAULT_REDIRECT_URI, HUB_ROLES, ZITADEL_SSM_PARAM, TEST_USERS_SSM_PREFIX, TEST_USERS, isAlreadyExists, ensureCliProject, ensureHubProject;
3950
+ var ZITADEL_ISSUER, LOGIN_CONTEXT, PLATFORM_ORG, CLI_PROJECT_NAME, CLI_APP_NAME, CLI_REDIRECT_URI, HUB_PROJECT_NAME, HUB_APP_NAME, hubRedirectUri, HUB_DEFAULT_REDIRECT_URI, HUB_ROLES, ZITADEL_SSM_PARAM, TEST_USERS_SSM_PREFIX, TEST_USERS, isAlreadyExists, ensureCliProject, ensureHubProject, OPS_HUB_SECRET_ID, LOCAL_SMTP;
3772
3951
  var init_seed_zitadel = __esm({
3773
3952
  "libs/mesh-cli/src/commands/local/seed-zitadel.ts"() {
3774
3953
  "use strict";
@@ -3788,16 +3967,37 @@ var init_seed_zitadel = __esm({
3788
3967
  HUB_APP_NAME = "ui";
3789
3968
  hubRedirectUri = () => `http://localhost:${hubPort()}/oauth2/callback`;
3790
3969
  HUB_DEFAULT_REDIRECT_URI = `http://localhost:${DEFAULT_HUB_PORT}/oauth2/callback`;
3791
- HUB_ROLES = HUB_BASE_ROLES;
3970
+ HUB_ROLES = hubOperatorRoleKeys([LOCAL_TENANT]);
3792
3971
  ZITADEL_SSM_PARAM = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/platform/zitadel`;
3793
3972
  TEST_USERS_SSM_PREFIX = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/temporal/test-users`;
3794
3973
  TEST_USERS = [
3795
- { name: "dev", email: "dev@local.mesh", firstName: "Dev", lastName: "User", password: "LocalDev1!" },
3796
- { name: "ops", email: "ops@local.mesh", firstName: "Ops", lastName: "User", password: "LocalDev1!" }
3974
+ {
3975
+ name: "dev",
3976
+ email: "dev@local.mesh",
3977
+ firstName: "Dev",
3978
+ lastName: "User",
3979
+ password: "LocalDev1!"
3980
+ },
3981
+ {
3982
+ name: "ops",
3983
+ email: "ops@local.mesh",
3984
+ firstName: "Ops",
3985
+ lastName: "User",
3986
+ password: "LocalDev1!"
3987
+ }
3797
3988
  ];
3798
3989
  isAlreadyExists = (err) => err?.status === 409 || err?.zitadelCode === 6 || /already exists/i.test(err?.message ?? "");
3799
- ensureCliProject = (pat) => ensureZitadelProject(pat, CLI_PROJECT_NAME, { describe: "platform client feature", logExisting: true });
3990
+ ensureCliProject = (pat) => ensureZitadelProject(pat, CLI_PROJECT_NAME, {
3991
+ describe: "platform client feature",
3992
+ logExisting: true
3993
+ });
3800
3994
  ensureHubProject = (pat) => ensureZitadelProject(pat, HUB_PROJECT_NAME, { describe: "Hub platform app" });
3995
+ OPS_HUB_SECRET_ID = "mesh/local/dev/zitadel/ops-hub";
3996
+ LOCAL_SMTP = {
3997
+ host: "mailpit:1025",
3998
+ from: "no-reply@local.mesh",
3999
+ fromName: "Mesh (local)"
4000
+ };
3801
4001
  }
3802
4002
  });
3803
4003
 
@@ -3911,9 +4111,9 @@ async function ensureM2mCaller(pat, tenant, app, orgId, projectId, roles) {
3911
4111
  return false;
3912
4112
  }
3913
4113
  async function authSecretExists(tenant, app, service) {
3914
- const { SecretsManagerClient: SecretsManagerClient8, GetSecretValueCommand: GetSecretValueCommand8 } = await import("@aws-sdk/client-secrets-manager");
3915
- const sm = new SecretsManagerClient8(LOCAL_AWS_CONFIG);
3916
- return sm.send(new GetSecretValueCommand8({ SecretId: authSecretPath(tenant, app, service) })).then(
4114
+ const { SecretsManagerClient: SecretsManagerClient9, GetSecretValueCommand: GetSecretValueCommand9 } = await import("@aws-sdk/client-secrets-manager");
4115
+ const sm = new SecretsManagerClient9(LOCAL_AWS_CONFIG);
4116
+ return sm.send(new GetSecretValueCommand9({ SecretId: authSecretPath(tenant, app, service) })).then(
3917
4117
  () => true,
3918
4118
  () => false
3919
4119
  );
@@ -3924,8 +4124,8 @@ async function writeAuthSecret(tenant, app, service, value) {
3924
4124
  logSuccess(`Stored service credentials \u2192 ${name}`);
3925
4125
  }
3926
4126
  async function registerLocalApp(args) {
3927
- const { SSMClient: SSMClient4, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3928
- const ssm = new SSMClient4(LOCAL_AWS_CONFIG);
4127
+ const { SSMClient: SSMClient5, PutParameterCommand } = await import("@aws-sdk/client-ssm");
4128
+ const ssm = new SSMClient5(LOCAL_AWS_CONFIG);
3929
4129
  const put2 = (name, value, description) => ssm.send(
3930
4130
  new PutParameterCommand({ Name: name, Type: "String", Overwrite: true, Value: JSON.stringify(value), Description: description })
3931
4131
  );
@@ -3967,7 +4167,18 @@ async function registerLocalApp(args) {
3967
4167
  }
3968
4168
  await put2(
3969
4169
  `${base}/meta`,
3970
- { runtime: "mesh-dev-local", services: args.services, ports: args.ports ?? {} },
4170
+ {
4171
+ runtime: "mesh-dev-local",
4172
+ services: args.services,
4173
+ ports: args.ports ?? {},
4174
+ // The same two fields a deployed stack stamps, so the Hub's app list and
4175
+ // its activity timeline read a local run exactly as they read a deploy —
4176
+ // which version of this app is running here, and since when. Without them
4177
+ // a local app shows a blank version and never appears on the timeline,
4178
+ // and "deployments aren't tracked" is indistinguishable from "no deploys".
4179
+ ...args.version ? { version: args.version } : {},
4180
+ deployedAt: (/* @__PURE__ */ new Date()).toISOString()
4181
+ },
3971
4182
  "Local run metadata (mesh dev --local)"
3972
4183
  );
3973
4184
  logSuccess(`Registered app in the local registry \u2192 ${base}`);
@@ -3975,8 +4186,8 @@ async function registerLocalApp(args) {
3975
4186
  }
3976
4187
  async function reconcileRegistryFromZitadel() {
3977
4188
  const pat = readSeederPat();
3978
- const { SSMClient: SSMClient4, GetParameterCommand: GetParameterCommand2, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3979
- const ssm = new SSMClient4(LOCAL_AWS_CONFIG);
4189
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2, PutParameterCommand } = await import("@aws-sdk/client-ssm");
4190
+ const ssm = new SSMClient5(LOCAL_AWS_CONFIG);
3980
4191
  const orgs = await api(pat, "POST", "/admin/v1/orgs/_search", { query: { limit: 200 } });
3981
4192
  const tenants = [];
3982
4193
  let apps = 0;
@@ -4041,6 +4252,57 @@ async function reconcileRegistryFromZitadel() {
4041
4252
  }
4042
4253
  return { tenants, apps };
4043
4254
  }
4255
+ async function ensureSignInApp(args) {
4256
+ const pat = readSeederPat();
4257
+ const orgId = await ensureOrg(pat, args.tenant);
4258
+ const projectId = await ensureProject(pat, orgId, args.app);
4259
+ const name = `${args.service}-web`;
4260
+ const base = args.baseUrl.replace(/\/+$/, "");
4261
+ const redirectUris = [`${base}/oauth2/callback`];
4262
+ const postLogoutRedirectUris = [base, `${base}/`];
4263
+ const config = {
4264
+ redirectUris,
4265
+ postLogoutRedirectUris,
4266
+ responseTypes: ["OIDC_RESPONSE_TYPE_CODE"],
4267
+ grantTypes: ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN"],
4268
+ appType: "OIDC_APP_TYPE_WEB",
4269
+ authMethodType: "OIDC_AUTH_METHOD_TYPE_BASIC",
4270
+ // Roles must ride in the token: the app authorizes on them, and the Hub
4271
+ // shows which roles a person holds by reading the same grants.
4272
+ accessTokenType: "OIDC_TOKEN_TYPE_JWT",
4273
+ accessTokenRoleAssertion: true,
4274
+ idTokenRoleAssertion: true,
4275
+ idTokenUserinfoAssertion: true,
4276
+ // http:// callbacks are only permitted in dev mode.
4277
+ devMode: true
4278
+ };
4279
+ const existing = await searchZitadelApp(pat, projectId, name, orgId);
4280
+ if (existing) {
4281
+ await api(
4282
+ pat,
4283
+ "PUT",
4284
+ `/management/v1/projects/${projectId}/apps/${existing.id}/oidc_config`,
4285
+ config,
4286
+ orgId
4287
+ );
4288
+ return;
4289
+ }
4290
+ const created = await api(
4291
+ pat,
4292
+ "POST",
4293
+ `/management/v1/projects/${projectId}/apps/oidc`,
4294
+ { name, ...config },
4295
+ orgId
4296
+ );
4297
+ await writeAuthSecret(args.tenant, args.app, name, {
4298
+ clientId: created.clientId,
4299
+ clientSecret: created.clientSecret,
4300
+ issuer: "http://localhost:8080",
4301
+ orgId,
4302
+ projectId
4303
+ });
4304
+ logSuccess(`Created Zitadel application '${name}' (browser sign-in \u2192 ${base}/oauth2/callback)`);
4305
+ }
4044
4306
  async function ensureAppTenantAuth(args) {
4045
4307
  const pat = readSeederPat();
4046
4308
  const orgId = await ensureOrg(pat, args.tenant);
@@ -4652,14 +4914,14 @@ function isConfigStale(configMtimeMs, startedAtIso) {
4652
4914
  return configMtimeMs > Date.parse(startedAtIso);
4653
4915
  }
4654
4916
  function canConnect(host, port, timeoutMs = 800) {
4655
- return new Promise((resolve15) => {
4917
+ return new Promise((resolve16) => {
4656
4918
  const socket = new net3.Socket();
4657
4919
  let settled = false;
4658
4920
  const done = (ok) => {
4659
4921
  if (settled) return;
4660
4922
  settled = true;
4661
4923
  socket.destroy();
4662
- resolve15(ok);
4924
+ resolve16(ok);
4663
4925
  };
4664
4926
  socket.setTimeout(timeoutMs);
4665
4927
  socket.once("connect", () => done(true));
@@ -5118,7 +5380,7 @@ async function startTokenServer(port, context) {
5118
5380
  })();
5119
5381
  });
5120
5382
  server.on("error", (e) => logWarn(`token-server error: ${e.message}`));
5121
- await new Promise((resolve15) => server.listen(port, "127.0.0.1", resolve15));
5383
+ await new Promise((resolve16) => server.listen(port, "127.0.0.1", resolve16));
5122
5384
  logInfo(`dev-user token-server on http://127.0.0.1:${port} (context ${context})`);
5123
5385
  await new Promise(() => {
5124
5386
  });
@@ -5141,6 +5403,26 @@ import * as os6 from "os";
5141
5403
  import * as path15 from "path";
5142
5404
  import { Option } from "commander";
5143
5405
  import { SecretsManagerClient as SecretsManagerClient2, GetSecretValueCommand as GetSecretValueCommand2 } from "@aws-sdk/client-secrets-manager";
5406
+ function registerServiceProbes(devOutput, tenant, probeFiles) {
5407
+ const services = {};
5408
+ for (const [name, service] of Object.entries(devOutput.services)) {
5409
+ if (name.startsWith("mock-")) continue;
5410
+ services[name] = service.port;
5411
+ }
5412
+ try {
5413
+ const file = writeAppServiceProbes({
5414
+ tenant,
5415
+ env: devOutput.platform?.env ?? "dev",
5416
+ app: devOutput.app ?? "",
5417
+ services
5418
+ });
5419
+ if (file) probeFiles.push(file);
5420
+ } catch (err) {
5421
+ logWarn(
5422
+ `Could not register uptime probes for this session (${err instanceof Error ? err.message : err}) \u2014 the Hub will show no uptime for these services.`
5423
+ );
5424
+ }
5425
+ }
5144
5426
  function deriveSessionName(projectName, wt) {
5145
5427
  return wt.isPrimary ? `${projectName}-dev` : `${projectName}-${wt.slug}`;
5146
5428
  }
@@ -5228,10 +5510,10 @@ function removeSessionState(sessionName) {
5228
5510
  }
5229
5511
  }
5230
5512
  async function isPortFree(port) {
5231
- const bindSucceeds = (host) => new Promise((resolve15) => {
5513
+ const bindSucceeds = (host) => new Promise((resolve16) => {
5232
5514
  const server = net4.createServer();
5233
- server.once("error", () => resolve15(false));
5234
- const onListening = () => server.close(() => resolve15(true));
5515
+ server.once("error", () => resolve16(false));
5516
+ const onListening = () => server.close(() => resolve16(true));
5235
5517
  if (host === void 0) server.listen(port, onListening);
5236
5518
  else server.listen(port, host, onListening);
5237
5519
  });
@@ -5239,24 +5521,24 @@ async function isPortFree(port) {
5239
5521
  return bindSucceeds("127.0.0.1");
5240
5522
  }
5241
5523
  async function findFreePort() {
5242
- return new Promise((resolve15, reject) => {
5524
+ return new Promise((resolve16, reject) => {
5243
5525
  const server = net4.createServer();
5244
5526
  server.once("error", reject);
5245
5527
  server.listen(0, "127.0.0.1", () => {
5246
5528
  const { port } = server.address();
5247
- server.close(() => resolve15(port));
5529
+ server.close(() => resolve16(port));
5248
5530
  });
5249
5531
  });
5250
5532
  }
5251
5533
  function isPortListening(port) {
5252
- return new Promise((resolve15) => {
5534
+ return new Promise((resolve16) => {
5253
5535
  const s = new net4.Socket();
5254
5536
  let done = false;
5255
5537
  const fin = (ok) => {
5256
5538
  if (done) return;
5257
5539
  done = true;
5258
5540
  s.destroy();
5259
- resolve15(ok);
5541
+ resolve16(ok);
5260
5542
  };
5261
5543
  s.setTimeout(300);
5262
5544
  s.once("connect", () => fin(true));
@@ -6789,6 +7071,8 @@ function registerDevCommand(program2) {
6789
7071
  }
6790
7072
  return;
6791
7073
  }
7074
+ let signInServices = [];
7075
+ let appVersion;
6792
7076
  try {
6793
7077
  const tenant = localTenant;
6794
7078
  const app = rawDevOutput2.app ?? projectName;
@@ -6796,6 +7080,7 @@ function registerDevCommand(program2) {
6796
7080
  const services = Object.keys(rawDevOutput2.services);
6797
7081
  const authServices = services.filter((name) => !name.startsWith("mock-"));
6798
7082
  let authRoles = [];
7083
+ signInServices = [];
6799
7084
  try {
6800
7085
  const appPkg = JSON.parse(
6801
7086
  fs13.readFileSync(path15.join(appRoot, "package.json"), "utf-8")
@@ -6803,6 +7088,10 @@ function registerDevCommand(program2) {
6803
7088
  if (Array.isArray(appPkg?.mesh?.auth?.roles)) {
6804
7089
  authRoles = appPkg.mesh.auth.roles.filter((r) => typeof r === "string");
6805
7090
  }
7091
+ if (Array.isArray(appPkg?.mesh?.auth?.signIn)) {
7092
+ signInServices = appPkg.mesh.auth.signIn.filter((r) => typeof r === "string");
7093
+ }
7094
+ if (typeof appPkg?.version === "string") appVersion = appPkg.version;
6806
7095
  } catch {
6807
7096
  }
6808
7097
  await ensureAppTenantAuth({ tenant, app, services: authServices, roles: authRoles });
@@ -6813,10 +7102,30 @@ function registerDevCommand(program2) {
6813
7102
  );
6814
7103
  }
6815
7104
  const devOutput2 = await allocatePorts(rawDevOutput2, worktree);
7105
+ for (const service of signInServices) {
7106
+ const port = devOutput2.services[service]?.port;
7107
+ if (!port) {
7108
+ logWarn(`mesh.auth.signIn names '${service}', which this app does not run \u2014 no sign-in app registered.`);
7109
+ continue;
7110
+ }
7111
+ try {
7112
+ await ensureSignInApp({
7113
+ tenant: localTenant,
7114
+ app: devOutput2.app ?? projectName,
7115
+ service,
7116
+ baseUrl: `http://localhost:${port}`
7117
+ });
7118
+ } catch (err) {
7119
+ logWarn(
7120
+ `Could not register the browser sign-in for '${service}' (${err instanceof Error ? err.message : err}) \u2014 the Hub's Access \u2192 Sign-in tab will report this app has no login.`
7121
+ );
7122
+ }
7123
+ }
6816
7124
  try {
6817
7125
  await registerLocalApp({
6818
7126
  tenant: localTenant,
6819
7127
  app: devOutput2.app ?? projectName,
7128
+ version: appVersion,
6820
7129
  services: Object.keys(devOutput2.services),
6821
7130
  ports: Object.fromEntries(
6822
7131
  Object.entries(devOutput2.services).map(([name, svc]) => [name, svc.port])
@@ -6893,6 +7202,7 @@ function registerDevCommand(program2) {
6893
7202
  );
6894
7203
  logInfo(`Docker runner: ${composePath}`);
6895
7204
  dockerDevUp(sessionName);
7205
+ registerServiceProbes(devOutput2, localTenant, externalProbeFiles);
6896
7206
  saveSessionState(sessionName, {
6897
7207
  appRoot,
6898
7208
  stack: "local",
@@ -6924,6 +7234,7 @@ function registerDevCommand(program2) {
6924
7234
  { transport: "vpn-direct" }
6925
7235
  // local mode: no tunnels
6926
7236
  );
7237
+ registerServiceProbes(finalDevOutput2, localTenant, externalProbeFiles);
6927
7238
  saveSessionState(sessionName, {
6928
7239
  appRoot,
6929
7240
  stack: "local",
@@ -7297,8 +7608,8 @@ function registerDevCommand(program2) {
7297
7608
  });
7298
7609
  dev.command("test-user [name]").description("Get Temporal test user credentials (from Pulumi-managed test users)").option("--tenant <tenant>", "Tenant name", "mesh").option("--env <env>", "Environment", "dev").option("--region <region>", "AWS region", "us-east-2").action(
7299
7610
  async (name, opts) => {
7300
- const { SSMClient: SSMClient4, GetParameterCommand: GetParameterCommand2, GetParametersByPathCommand: GetParametersByPathCommand3 } = await import("@aws-sdk/client-ssm");
7301
- const ssm = new SSMClient4({ region: opts.region });
7611
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2, GetParametersByPathCommand: GetParametersByPathCommand3 } = await import("@aws-sdk/client-ssm");
7612
+ const ssm = new SSMClient5({ region: opts.region });
7302
7613
  const basePath = `/mesh-platform/${opts.tenant}/${opts.env}/temporal/test-users`;
7303
7614
  if (!name) {
7304
7615
  try {
@@ -7395,6 +7706,7 @@ var init_dev = __esm({
7395
7706
  init_seed();
7396
7707
  init_mocks();
7397
7708
  init_dev_local();
7709
+ init_stack();
7398
7710
  init_docker_runner();
7399
7711
  init_errors();
7400
7712
  init_stack_flag();
@@ -7626,7 +7938,7 @@ function discoverRunnerPids(tenant, exclude) {
7626
7938
  }
7627
7939
  }
7628
7940
  function startControlServer(payload) {
7629
- return new Promise((resolve15, reject) => {
7941
+ return new Promise((resolve16, reject) => {
7630
7942
  const server = net6.createServer((socket) => {
7631
7943
  socket.on("error", () => {
7632
7944
  });
@@ -7639,12 +7951,12 @@ function startControlServer(payload) {
7639
7951
  });
7640
7952
  server.on("error", reject);
7641
7953
  server.listen(0, "127.0.0.1", () => {
7642
- resolve15({ server, port: server.address().port });
7954
+ resolve16({ server, port: server.address().port });
7643
7955
  });
7644
7956
  });
7645
7957
  }
7646
7958
  function queryControl(port, timeoutMs = 700) {
7647
- return new Promise((resolve15) => {
7959
+ return new Promise((resolve16) => {
7648
7960
  const s = new net6.Socket();
7649
7961
  let buf = "";
7650
7962
  let done = false;
@@ -7652,7 +7964,7 @@ function queryControl(port, timeoutMs = 700) {
7652
7964
  if (done) return;
7653
7965
  done = true;
7654
7966
  s.destroy();
7655
- resolve15(v);
7967
+ resolve16(v);
7656
7968
  };
7657
7969
  s.setTimeout(timeoutMs);
7658
7970
  s.once("timeout", () => fin(null));
@@ -7877,14 +8189,14 @@ function findRunningDaemon(tenant) {
7877
8189
  return null;
7878
8190
  }
7879
8191
  function portAccepts(port, timeoutMs = 400) {
7880
- return new Promise((resolve15) => {
8192
+ return new Promise((resolve16) => {
7881
8193
  const s = new net7.Socket();
7882
8194
  let done = false;
7883
8195
  const fin = (v) => {
7884
8196
  if (done) return;
7885
8197
  done = true;
7886
8198
  s.destroy();
7887
- resolve15(v);
8199
+ resolve16(v);
7888
8200
  };
7889
8201
  s.setTimeout(timeoutMs);
7890
8202
  s.once("connect", () => fin(true));
@@ -8422,24 +8734,24 @@ function deriveLoginServer2(context) {
8422
8734
  return `https://${parts.join(".")}`;
8423
8735
  }
8424
8736
  function findFreePort2() {
8425
- return new Promise((resolve15, reject) => {
8737
+ return new Promise((resolve16, reject) => {
8426
8738
  const s = net8.createServer();
8427
8739
  s.on("error", reject);
8428
8740
  s.listen(0, "127.0.0.1", () => {
8429
8741
  const port = s.address().port;
8430
- s.close(() => resolve15(port));
8742
+ s.close(() => resolve16(port));
8431
8743
  });
8432
8744
  });
8433
8745
  }
8434
8746
  function canConnect2(port) {
8435
- return new Promise((resolve15) => {
8747
+ return new Promise((resolve16) => {
8436
8748
  const s = new net8.Socket();
8437
8749
  let settled = false;
8438
8750
  const done = (ok) => {
8439
8751
  if (settled) return;
8440
8752
  settled = true;
8441
8753
  s.destroy();
8442
- resolve15(ok);
8754
+ resolve16(ok);
8443
8755
  };
8444
8756
  s.setTimeout(500);
8445
8757
  s.once("connect", () => done(true));
@@ -8798,13 +9110,13 @@ function parseContextTenantEnv(context) {
8798
9110
  return { tenant: tenant || "mesh", env: env || "dev" };
8799
9111
  }
8800
9112
  function findFreePort3() {
8801
- return new Promise((resolve15, reject) => {
9113
+ return new Promise((resolve16, reject) => {
8802
9114
  const srv = net9.createServer();
8803
9115
  srv.on("error", reject);
8804
9116
  srv.listen(0, "127.0.0.1", () => {
8805
9117
  const addr = srv.address();
8806
9118
  const port = typeof addr === "object" && addr ? addr.port : 0;
8807
- srv.close(() => resolve15(port));
9119
+ srv.close(() => resolve16(port));
8808
9120
  });
8809
9121
  });
8810
9122
  }
@@ -9291,9 +9603,9 @@ function parseTenantEnv(context) {
9291
9603
  }
9292
9604
  async function getRegistrySsmExport(tenant, env) {
9293
9605
  try {
9294
- const { SSMClient: SSMClient4, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
9606
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
9295
9607
  const region = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? CA_REGION;
9296
- const ssm = new SSMClient4({ region });
9608
+ const ssm = new SSMClient5({ region });
9297
9609
  const ssmPath = `/mesh-platform/${tenant}/${env}/registry`;
9298
9610
  const response = await ssm.send(new GetParameterCommand2({ Name: ssmPath }));
9299
9611
  if (!response.Parameter?.Value) return null;
@@ -9883,9 +10195,9 @@ async function discoverConfigFromSsm(context) {
9883
10195
  const ssmPath = `/mesh-platform/${tenant}/${env}/platform/zitadel`;
9884
10196
  logInfo(`Attempting SSM discovery from ${ssmPath}...`);
9885
10197
  try {
9886
- const { SSMClient: SSMClient4, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
10198
+ const { SSMClient: SSMClient5, GetParameterCommand: GetParameterCommand2 } = await import("@aws-sdk/client-ssm");
9887
10199
  const region = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-2";
9888
- const ssm = new SSMClient4({ region });
10200
+ const ssm = new SSMClient5({ region });
9889
10201
  const resp = await ssm.send(new GetParameterCommand2({ Name: ssmPath }));
9890
10202
  const raw = resp.Parameter?.Value;
9891
10203
  if (!raw) {
@@ -9978,11 +10290,11 @@ function readAllCredentials() {
9978
10290
  function readCredentials(context) {
9979
10291
  return readAllCredentials()[context] ?? null;
9980
10292
  }
9981
- function atomicWriteFileSync(path41, data, mode) {
9982
- const tmpPath = `${path41}.${process.pid}.${atomicWriteCounter++}.tmp`;
10293
+ function atomicWriteFileSync(path42, data, mode) {
10294
+ const tmpPath = `${path42}.${process.pid}.${atomicWriteCounter++}.tmp`;
9983
10295
  try {
9984
10296
  fs19.writeFileSync(tmpPath, data, { mode });
9985
- fs19.renameSync(tmpPath, path41);
10297
+ fs19.renameSync(tmpPath, path42);
9986
10298
  } catch (err) {
9987
10299
  try {
9988
10300
  fs19.unlinkSync(tmpPath);
@@ -10055,7 +10367,7 @@ async function refreshTokens(issuer, clientId, refreshToken) {
10055
10367
  return resp.json();
10056
10368
  }
10057
10369
  function login(context, config) {
10058
- return new Promise((resolve15, reject) => {
10370
+ return new Promise((resolve16, reject) => {
10059
10371
  const codeVerifier = generateCodeVerifier();
10060
10372
  const codeChallenge = generateCodeChallenge(codeVerifier);
10061
10373
  const state = base64url(crypto3.randomBytes(16));
@@ -10139,7 +10451,7 @@ function login(context, config) {
10139
10451
  }
10140
10452
  hintVpnIfDisconnected(context);
10141
10453
  teardown();
10142
- resolve15();
10454
+ resolve16();
10143
10455
  } catch (err) {
10144
10456
  teardown();
10145
10457
  reject(err);
@@ -11734,6 +12046,7 @@ __export(create_app_exports, {
11734
12046
  ensureWorkspaceGlobs: () => ensureWorkspaceGlobs,
11735
12047
  isInsidePlatformMonorepo: () => isInsidePlatformMonorepo,
11736
12048
  registerCreateAppCommand: () => registerCreateAppCommand,
12049
+ resolveAppDir: () => resolveAppDir,
11737
12050
  shouldBootstrapAppsRepo: () => shouldBootstrapAppsRepo,
11738
12051
  workspaceGlobForApp: () => workspaceGlobForApp
11739
12052
  });
@@ -11918,7 +12231,7 @@ function parsePrimitives(input2) {
11918
12231
  }
11919
12232
  function resolveAppDir(cwd, tenant, name, test) {
11920
12233
  const baseDir = test ? "tests/tenants" : "tenants";
11921
- const possiblePaths = [
12234
+ const possiblePaths = test ? [path24.join(cwd, baseDir, tenant, "apps")] : [
11922
12235
  path24.join(cwd, baseDir, tenant, "apps"),
11923
12236
  path24.join(cwd, "..", tenant, "apps"),
11924
12237
  path24.join(cwd, "apps")
@@ -12431,26 +12744,26 @@ async function startTunnelBackground(instanceId, rdsHost, rdsPort, localPort) {
12431
12744
  throw new Error(`Tunnel failed to start after ${maxAttempts} seconds`);
12432
12745
  }
12433
12746
  function checkPort(port) {
12434
- return new Promise((resolve15) => {
12747
+ return new Promise((resolve16) => {
12435
12748
  const socket = new net10.Socket();
12436
12749
  socket.setTimeout(500);
12437
12750
  socket.on("connect", () => {
12438
12751
  socket.destroy();
12439
- resolve15(true);
12752
+ resolve16(true);
12440
12753
  });
12441
12754
  socket.on("timeout", () => {
12442
12755
  socket.destroy();
12443
- resolve15(false);
12756
+ resolve16(false);
12444
12757
  });
12445
12758
  socket.on("error", () => {
12446
12759
  socket.destroy();
12447
- resolve15(false);
12760
+ resolve16(false);
12448
12761
  });
12449
12762
  socket.connect(port, "localhost");
12450
12763
  });
12451
12764
  }
12452
12765
  function sleep2(ms) {
12453
- return new Promise((resolve15) => setTimeout(resolve15, ms));
12766
+ return new Promise((resolve16) => setTimeout(resolve16, ms));
12454
12767
  }
12455
12768
  async function psqlCommand(options) {
12456
12769
  let platformTenant;
@@ -12476,13 +12789,13 @@ async function psqlCommand(options) {
12476
12789
  logInfo(`App credentials: tenant=${appTenant}, stage=${appStage}, app=${appName}`);
12477
12790
  }
12478
12791
  const psqlCheck = spawn5("which", ["psql"]);
12479
- await new Promise((resolve15, reject) => {
12792
+ await new Promise((resolve16, reject) => {
12480
12793
  psqlCheck.on("exit", (code) => {
12481
12794
  if (code !== 0) {
12482
12795
  logError("psql not found. Install with: brew install postgresql");
12483
12796
  reject(new Error("psql not found"));
12484
12797
  } else {
12485
- resolve15();
12798
+ resolve16();
12486
12799
  }
12487
12800
  });
12488
12801
  });
@@ -12569,26 +12882,26 @@ function findSstOutputs() {
12569
12882
  return null;
12570
12883
  }
12571
12884
  function checkPort2(port) {
12572
- return new Promise((resolve15) => {
12885
+ return new Promise((resolve16) => {
12573
12886
  const socket = new net11.Socket();
12574
12887
  socket.setTimeout(500);
12575
12888
  socket.on("connect", () => {
12576
12889
  socket.destroy();
12577
- resolve15(true);
12890
+ resolve16(true);
12578
12891
  });
12579
12892
  socket.on("timeout", () => {
12580
12893
  socket.destroy();
12581
- resolve15(false);
12894
+ resolve16(false);
12582
12895
  });
12583
12896
  socket.on("error", () => {
12584
12897
  socket.destroy();
12585
- resolve15(false);
12898
+ resolve16(false);
12586
12899
  });
12587
12900
  socket.connect(port, "localhost");
12588
12901
  });
12589
12902
  }
12590
12903
  function sleep3(ms) {
12591
- return new Promise((resolve15) => setTimeout(resolve15, ms));
12904
+ return new Promise((resolve16) => setTimeout(resolve16, ms));
12592
12905
  }
12593
12906
  async function startTunnelBackground2(instanceId, rdsEndpoint, rdsPort, localPort) {
12594
12907
  logInfo("Starting database tunnel...");
@@ -13308,7 +13621,7 @@ var init_discover = __esm({
13308
13621
 
13309
13622
  // libs/mesh-cli/src/docs/assemble.ts
13310
13623
  import { execFileSync as execFileSync20 } from "node:child_process";
13311
- import { mkdirSync as mkdirSync15, readFileSync as readFileSync23, rmSync as rmSync5, writeFileSync as writeFileSync15 } from "node:fs";
13624
+ import { mkdirSync as mkdirSync15, readFileSync as readFileSync23, rmSync as rmSync5, writeFileSync as writeFileSync16 } from "node:fs";
13312
13625
  import path27 from "node:path";
13313
13626
  import { parse as parseYaml4 } from "yaml";
13314
13627
  function splitFrontMatter(markdown) {
@@ -13621,7 +13934,7 @@ function assemblePortal(options) {
13621
13934
  `;
13622
13935
  const outFile = path27.join(outDir, page.filePath);
13623
13936
  mkdirSync15(path27.dirname(outFile), { recursive: true });
13624
- writeFileSync15(
13937
+ writeFileSync16(
13625
13938
  outFile,
13626
13939
  joinFrontMatter(outData, rewritten.body.trimEnd() + sourceNote)
13627
13940
  );
@@ -13928,8 +14241,8 @@ function formatDefault(value, paths = currentMachinePaths()) {
13928
14241
  if (typeof value === "boolean" || typeof value === "number") return String(value);
13929
14242
  return normalizeMachinePaths(JSON.stringify(value), paths);
13930
14243
  }
13931
- function slugifyCommandPath(path41) {
13932
- return path41.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
14244
+ function slugifyCommandPath(path42) {
14245
+ return path42.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
13933
14246
  }
13934
14247
  function extractOptions(command) {
13935
14248
  const options = command.options ?? [];
@@ -13963,17 +14276,17 @@ function isUndocumented(command) {
13963
14276
  function extractCommand(command, parentPath, depth) {
13964
14277
  const internals = command;
13965
14278
  const name = internals._name ?? "";
13966
- const path41 = parentPath ? `${parentPath} ${name}` : name;
14279
+ const path42 = parentPath ? `${parentPath} ${name}` : name;
13967
14280
  return {
13968
- path: path41,
14281
+ path: path42,
13969
14282
  name,
13970
14283
  aliases: [...internals._aliases ?? []],
13971
14284
  description: internals._description ?? "",
13972
14285
  args: extractArgs(command),
13973
14286
  options: extractOptions(command),
13974
- slug: slugifyCommandPath(path41),
14287
+ slug: slugifyCommandPath(path42),
13975
14288
  depth,
13976
- subcommands: (internals.commands ?? []).filter((child) => !isUndocumented(child)).map((child) => extractCommand(child, path41, depth + 1))
14289
+ subcommands: (internals.commands ?? []).filter((child) => !isUndocumented(child)).map((child) => extractCommand(child, path42, depth + 1))
13977
14290
  };
13978
14291
  }
13979
14292
  function extractCliReference(program2) {
@@ -14069,7 +14382,7 @@ import {
14069
14382
  mkdirSync as mkdirSync16,
14070
14383
  mkdtempSync as mkdtempSync3,
14071
14384
  readFileSync as readFileSync24,
14072
- writeFileSync as writeFileSync16
14385
+ writeFileSync as writeFileSync17
14073
14386
  } from "node:fs";
14074
14387
  import { tmpdir as tmpdir6 } from "node:os";
14075
14388
  import path28 from "node:path";
@@ -14085,13 +14398,13 @@ function runAssemble(args) {
14085
14398
  const errors = [...discovery.errors];
14086
14399
  if (args.writeFiles !== false) {
14087
14400
  const manifestPath = args.outDir.includes(tmpdir6()) ? path28.join(args.outDir, PUBLISH_MANIFEST) : path28.join(args.outDir, "..", PUBLISH_MANIFEST);
14088
- writeFileSync16(
14401
+ writeFileSync17(
14089
14402
  manifestPath,
14090
14403
  `${JSON.stringify(assembled.publishManifest, null, 2)}
14091
14404
  `
14092
14405
  );
14093
14406
  if (args.configOut) {
14094
- writeFileSync16(
14407
+ writeFileSync17(
14095
14408
  args.configOut,
14096
14409
  renderZudokuConfig({
14097
14410
  navigation: assembled.navigation,
@@ -14106,7 +14419,7 @@ function runAssemble(args) {
14106
14419
  VERSION_JSON_DIR
14107
14420
  );
14108
14421
  mkdirSync16(versionDir, { recursive: true });
14109
- writeFileSync16(
14422
+ writeFileSync17(
14110
14423
  path28.join(versionDir, VERSION_JSON),
14111
14424
  renderVersionJson({
14112
14425
  baseline: currentBaseline(args.repoRoot),
@@ -14161,7 +14474,7 @@ async function checkPortal(args) {
14161
14474
  }
14162
14475
  if (args.manifestOut) {
14163
14476
  mkdirSync16(path28.dirname(args.manifestOut), { recursive: true });
14164
- writeFileSync16(
14477
+ writeFileSync17(
14165
14478
  args.manifestOut,
14166
14479
  `${JSON.stringify(result.assembled.publishManifest, null, 2)}
14167
14480
  `
@@ -14201,14 +14514,14 @@ ${summary}`);
14201
14514
  function runZudoku(args) {
14202
14515
  const cliArgs = args.mode === "dev" ? ["dev", "--port", String(args.port ?? 3e3)] : ["build"];
14203
14516
  logInfo(`Running \`zudoku ${cliArgs.join(" ")}\` in ${args.appDir}`);
14204
- return new Promise((resolve15, reject) => {
14517
+ return new Promise((resolve16, reject) => {
14205
14518
  const child = spawn7("pnpm", ["exec", "zudoku", ...cliArgs], {
14206
14519
  cwd: args.appDir,
14207
14520
  stdio: "inherit",
14208
14521
  env: process.env
14209
14522
  });
14210
14523
  child.on("error", reject);
14211
- child.on("close", (code) => resolve15(code ?? 1));
14524
+ child.on("close", (code) => resolve16(code ?? 1));
14212
14525
  });
14213
14526
  }
14214
14527
  async function runPortalCommand(opts, deps) {
@@ -14419,17 +14732,17 @@ async function serveDocsSite(args) {
14419
14732
  res.writeHead(500).end("internal error");
14420
14733
  });
14421
14734
  });
14422
- await new Promise((resolve15, reject) => {
14735
+ await new Promise((resolve16, reject) => {
14423
14736
  server.once("error", reject);
14424
- server.listen(args.port, "127.0.0.1", () => resolve15());
14737
+ server.listen(args.port, "127.0.0.1", () => resolve16());
14425
14738
  });
14426
14739
  const address = server.address();
14427
14740
  const port = typeof address === "object" && address ? address.port : args.port;
14428
14741
  return {
14429
14742
  url: `http://127.0.0.1:${port}`,
14430
14743
  port,
14431
- close: () => new Promise((resolve15, reject) => {
14432
- server.close((error) => error ? reject(error) : resolve15());
14744
+ close: () => new Promise((resolve16, reject) => {
14745
+ server.close((error) => error ? reject(error) : resolve16());
14433
14746
  })
14434
14747
  };
14435
14748
  }
@@ -14461,7 +14774,7 @@ var init_serve = __esm({
14461
14774
 
14462
14775
  // libs/mesh-cli/src/docs/registry-docs.ts
14463
14776
  import { execFileSync as execFileSync22 } from "node:child_process";
14464
- import { existsSync as existsSync23, mkdirSync as mkdirSync17, readFileSync as readFileSync26, renameSync as renameSync3, rmSync as rmSync6, writeFileSync as writeFileSync17 } from "node:fs";
14777
+ import { existsSync as existsSync23, mkdirSync as mkdirSync17, readFileSync as readFileSync26, renameSync as renameSync3, rmSync as rmSync6, writeFileSync as writeFileSync18 } from "node:fs";
14465
14778
  import { tmpdir as tmpdir7 } from "node:os";
14466
14779
  import path30 from "node:path";
14467
14780
  function readDocsRegistryAuth(npmrcPath2 = homeNpmrcPath()) {
@@ -14547,7 +14860,7 @@ async function fetchDocsArtifact(auth, version, cacheRoot = docsCacheRoot(), fet
14547
14860
  }
14548
14861
  mkdirSync17(cacheRoot, { recursive: true });
14549
14862
  const tgzPath = path30.join(cacheRoot, `.${version}.tgz`);
14550
- writeFileSync17(tgzPath, Buffer.from(await tgzResponse.arrayBuffer()));
14863
+ writeFileSync18(tgzPath, Buffer.from(await tgzResponse.arrayBuffer()));
14551
14864
  const staging = path30.join(cacheRoot, `.staging-${version}`);
14552
14865
  rmSync6(staging, { recursive: true, force: true });
14553
14866
  mkdirSync17(staging, { recursive: true });
@@ -14591,7 +14904,7 @@ __export(start_exports, {
14591
14904
  tmuxServeArgs: () => tmuxServeArgs
14592
14905
  });
14593
14906
  import { execFileSync as execFileSync23 } from "node:child_process";
14594
- import { appendFileSync as appendFileSync2, existsSync as existsSync24, writeFileSync as writeFileSync18 } from "node:fs";
14907
+ import { appendFileSync as appendFileSync2, existsSync as existsSync24, writeFileSync as writeFileSync19 } from "node:fs";
14595
14908
  import path31 from "node:path";
14596
14909
  function registryAuthOrThrow() {
14597
14910
  const auth = readDocsRegistryAuth();
@@ -14682,7 +14995,7 @@ Or run in the foreground instead: mesh docs start --foreground`
14682
14995
  } catch (error) {
14683
14996
  lastError = error instanceof Error ? error.message : String(error);
14684
14997
  }
14685
- await new Promise((resolve15) => setTimeout(resolve15, 500));
14998
+ await new Promise((resolve16) => setTimeout(resolve16, 500));
14686
14999
  }
14687
15000
  throw new Error(
14688
15001
  `the docs server did not answer at ${url} within 60s (${lastError || "no response"}).
@@ -14719,8 +15032,8 @@ async function runDocsServeStatic(args) {
14719
15032
  }
14720
15033
  const server = await serveDocsSite({ root: args.root, port: requestedPort });
14721
15034
  logSuccess(`Mesh docs serving at ${server.url} (from ${args.root})`);
14722
- await new Promise((resolve15) => {
14723
- const stop = () => void server.close().finally(() => resolve15());
15035
+ await new Promise((resolve16) => {
15036
+ const stop = () => void server.close().finally(() => resolve16());
14724
15037
  process.once("SIGINT", stop);
14725
15038
  process.once("SIGTERM", stop);
14726
15039
  });
@@ -14774,8 +15087,8 @@ async function runDocsStart(args) {
14774
15087
  stdio: "inherit",
14775
15088
  env: process.env
14776
15089
  });
14777
- await new Promise((resolve15) => {
14778
- child.on("close", () => resolve15());
15090
+ await new Promise((resolve16) => {
15091
+ child.on("close", () => resolve16());
14779
15092
  process.once("SIGINT", () => child.kill("SIGINT"));
14780
15093
  process.once("SIGTERM", () => child.kill("SIGTERM"));
14781
15094
  });
@@ -14790,8 +15103,8 @@ async function runDocsStart(args) {
14790
15103
  });
14791
15104
  reportAssembly(result);
14792
15105
  const { spawn: spawn10 } = await import("node:child_process");
14793
- writeFileSync18(buildLog, "");
14794
- const code = await new Promise((resolve15) => {
15106
+ writeFileSync19(buildLog, "");
15107
+ const code = await new Promise((resolve16) => {
14795
15108
  const child = spawn10("pnpm", ["exec", "zudoku", "build"], {
14796
15109
  cwd: appDir,
14797
15110
  stdio: ["ignore", "pipe", "pipe"],
@@ -14799,8 +15112,8 @@ async function runDocsStart(args) {
14799
15112
  });
14800
15113
  child.stdout?.on("data", (chunk) => appendFileSync2(buildLog, chunk));
14801
15114
  child.stderr?.on("data", (chunk) => appendFileSync2(buildLog, chunk));
14802
- child.on("error", () => resolve15(1));
14803
- child.on("close", (exitCode) => resolve15(exitCode ?? 1));
15115
+ child.on("error", () => resolve16(1));
15116
+ child.on("close", (exitCode) => resolve16(exitCode ?? 1));
14804
15117
  });
14805
15118
  if (code !== 0) throw new Error(`zudoku build exited with code ${code} \u2014 see ${buildLog}`);
14806
15119
  serveRoot = path31.join(appDir, "dist");
@@ -14820,9 +15133,9 @@ async function runDocsStart(args) {
14820
15133
  const server = await serveDocsSite({ root: serveRoot, port: requestedPort });
14821
15134
  logSuccess(`Mesh docs (${label}) serving at ${server.url}`);
14822
15135
  logInfo("Press Ctrl-C to stop");
14823
- await new Promise((resolve15) => {
15136
+ await new Promise((resolve16) => {
14824
15137
  const stop = () => {
14825
- void server.close().finally(() => resolve15());
15138
+ void server.close().finally(() => resolve16());
14826
15139
  };
14827
15140
  process.once("SIGINT", stop);
14828
15141
  process.once("SIGTERM", stop);
@@ -15174,10 +15487,10 @@ function assembleHubEnv(session, sessionEnv, opts) {
15174
15487
  { remediation: { command: "mesh dev --kill && mesh dev # relaunch to refresh session state" } }
15175
15488
  );
15176
15489
  }
15177
- if (platform.tenant === "local") {
15178
- throw new MeshCliError(
15179
- "This session runs on the full-local platform (`mesh start`) \u2014 use its containerized Hub instead of `mesh hub dev`.",
15180
- { remediation: { command: "mesh start --with-hub # MESH_HUB_PORT=<port> publishes it off 9000" } }
15490
+ const isLocalPlatform = platform.tenant === "local";
15491
+ if (isLocalPlatform) {
15492
+ notes.push(
15493
+ "Local platform session \u2014 the Hub runs from this checkout against `mesh start`. Its containerized Hub keeps :9000, so pass --port/--api-port to run both."
15181
15494
  );
15182
15495
  }
15183
15496
  const hubTenant = platform.name ?? "mesh";
@@ -15205,7 +15518,11 @@ function assembleHubEnv(session, sessionEnv, opts) {
15205
15518
  "Dev session predates the per-session token-server (MESH-2039) \u2014 starting a dedicated one so tokens stay fresh past ~1h."
15206
15519
  );
15207
15520
  }
15208
- const temporalAddress = sessionEnv.TEMPORAL_ADDRESS ?? (session.state.devOutput.tunnels["temporal"] ? tunnelClientAddress(session.state.devOutput.tunnels["temporal"]) : void 0);
15521
+ const temporalAddress = sessionEnv.TEMPORAL_ADDRESS ?? (session.state.devOutput.tunnels["temporal"] ? tunnelClientAddress(session.state.devOutput.tunnels["temporal"]) : (
15522
+ // The local platform runs Temporal as a published container, so there is
15523
+ // no tunnel to record and none to demand — the address is fixed.
15524
+ isLocalPlatform ? "localhost:7233" : void 0
15525
+ ));
15209
15526
  if (!temporalAddress) {
15210
15527
  throw new MeshCliError(
15211
15528
  `Dev session '${session.name}' exposes no Temporal tunnel \u2014 the Hub can't reach the stack's Temporal.`,
@@ -15218,7 +15535,8 @@ function assembleHubEnv(session, sessionEnv, opts) {
15218
15535
  TEMPORAL_ADDRESS: temporalAddress,
15219
15536
  HUB_TENANT: hubTenant,
15220
15537
  HUB_SCOPE_ENV: scopeEnv,
15221
- HUB_SCOPE_TENANTS: scopeTenants.join(",")
15538
+ HUB_SCOPE_TENANTS: scopeTenants.join(","),
15539
+ ...isLocalPlatform ? localPlatformEnv2(sessionEnv) : {}
15222
15540
  };
15223
15541
  const uiEnv = {
15224
15542
  ...forwarded,
@@ -15267,24 +15585,24 @@ function resolvePlatformDir(explicit, cwd, env = process.env) {
15267
15585
  );
15268
15586
  }
15269
15587
  async function findFreePort4() {
15270
- return new Promise((resolve15, reject) => {
15588
+ return new Promise((resolve16, reject) => {
15271
15589
  const server = net12.createServer();
15272
15590
  server.listen(0, "127.0.0.1", () => {
15273
15591
  const address = server.address();
15274
15592
  server.close(
15275
- () => typeof address === "object" && address ? resolve15(address.port) : reject(new Error("no port"))
15593
+ () => typeof address === "object" && address ? resolve16(address.port) : reject(new Error("no port"))
15276
15594
  );
15277
15595
  });
15278
15596
  server.on("error", reject);
15279
15597
  });
15280
15598
  }
15281
15599
  function isPortListening2(port) {
15282
- return new Promise((resolve15) => {
15600
+ return new Promise((resolve16) => {
15283
15601
  const socket = net12.connect({ host: "127.0.0.1", port, timeout: 400 });
15284
15602
  const done = (ok) => {
15285
15603
  socket.removeAllListeners();
15286
15604
  socket.destroy();
15287
- resolve15(ok);
15605
+ resolve16(ok);
15288
15606
  };
15289
15607
  socket.once("connect", () => done(true));
15290
15608
  socket.once("timeout", () => done(false));
@@ -15405,7 +15723,7 @@ async function hubDevAction(opts) {
15405
15723
  execFileSync24("tmux", ["send-keys", "-t", `${HUB_SESSION}:${window}`, buildLaunchCommand(envFile, dir, cmd), "Enter"]);
15406
15724
  };
15407
15725
  launch("api", apiDir, assembled.apiEnv, "pnpm dev", false);
15408
- launch("ui", uiDir, assembled.uiEnv, `pnpm dev -- --port ${uiPort} --strictPort`, true);
15726
+ launch("ui", uiDir, assembled.uiEnv, `pnpm dev --port ${uiPort} --strictPort`, true);
15409
15727
  logInfo("Waiting for the Hub to come up\u2026");
15410
15728
  const apiUp = await waitForPort("127.0.0.1", apiPort, 9e4);
15411
15729
  const uiUp = apiUp && await waitForPort("127.0.0.1", uiPort, 9e4);
@@ -15428,7 +15746,7 @@ function registerHubCommands(program2) {
15428
15746
  "Launch the current-code Hub (api + ui) against a running `mesh dev` session \u2014 env auto-assembled, zero hand-set vars"
15429
15747
  ).option("--session <name>", "dev session to observe (default: auto-detect from cwd)").option("--tenants <list>", "comma-separated HUB_SCOPE_TENANTS override (default: the session's tenant)").option("--port <port>", `Hub UI host port (default: $MESH_HUB_DEV_PORT or ${DEFAULT_HUB_UI_PORT})`).option("--api-port <port>", `Hub API port (default: ${DEFAULT_HUB_API_PORT})`).option("--platform-dir <dir>", "mesh-platform checkout to run the Hub from (default: $MESH_PLATFORM_DIR or walk up from cwd)").option("--print-env", "print the assembled env (secrets redacted) and exit without launching").option("--kill", "tear down the running Hub session").action(hubDevAction);
15430
15748
  }
15431
- var HUB_SESSION, DEFAULT_HUB_UI_PORT, DEFAULT_HUB_API_PORT, FORWARD_PREFIXES, SENSITIVE_RE;
15749
+ var HUB_SESSION, DEFAULT_HUB_UI_PORT, DEFAULT_HUB_API_PORT, FORWARD_PREFIXES, localPlatformEnv2, SENSITIVE_RE;
15432
15750
  var init_hub = __esm({
15433
15751
  "libs/mesh-cli/src/commands/hub/index.ts"() {
15434
15752
  "use strict";
@@ -15442,6 +15760,35 @@ var init_hub = __esm({
15442
15760
  DEFAULT_HUB_UI_PORT = Number(DEFAULT_HUB_PORT);
15443
15761
  DEFAULT_HUB_API_PORT = 3002;
15444
15762
  FORWARD_PREFIXES = ["AWS_", "DEV_USER_"];
15763
+ localPlatformEnv2 = (sessionEnv) => ({
15764
+ // The issuer the local `mesh login` tokens carry. Zitadel routes by Host
15765
+ // header, so the host must stay `localhost` on both sides of the call.
15766
+ ZITADEL_ISSUER: sessionEnv.ZITADEL_ISSUER ?? "http://localhost:8080",
15767
+ // Postgres publishes on 5433; the Hub's ops schema lives in the `hub` database
15768
+ // alongside the per-app ones.
15769
+ OPS_DB_HOST: "localhost",
15770
+ OPS_DB_PORT: "5433",
15771
+ OPS_DB_NAME: "hub",
15772
+ OPS_DB_USER: "postgres",
15773
+ DATABASE_PASSWORD: "postgres",
15774
+ PGSSLMODE: "disable",
15775
+ DATABASE_URL: "postgres://postgres:postgres@localhost:5433/hub?sslmode=disable",
15776
+ // The Hub's ADMIN plane. `mesh start` seeds the machine key this names, in the
15777
+ // platform org with IAM_OWNER, so the Hub can administer each app tenant's own
15778
+ // Zitadel org rather than only its own.
15779
+ ZITADEL_OPSHUB_SECRET_NAME: "mesh/local/dev/zitadel/ops-hub",
15780
+ SPICEDB_ENDPOINT: "localhost:50051",
15781
+ SPICEDB_HTTP_ENDPOINT: "http://localhost:8443",
15782
+ SPICEDB_HTTP_SCHEME: "http",
15783
+ SPICEDB_PRESHARED_KEY: "local-dev-key",
15784
+ LOKI_URL: "http://localhost:3100",
15785
+ TEMPO_URL: "http://localhost:3200",
15786
+ PROMETHEUS_URL: "http://localhost:9090",
15787
+ TEMPORAL_UI_URL: "http://localhost:8233",
15788
+ // The stack's namespace. Absent, the Hub's compliance schedules fail to
15789
+ // register with "Temporal namespace is required".
15790
+ TEMPORAL_NAMESPACE: "local-dev"
15791
+ });
15445
15792
  SENSITIVE_RE = /TOKEN|SECRET|KEY|PASSWORD/i;
15446
15793
  }
15447
15794
  });
@@ -16042,6 +16389,103 @@ async function ensureHubImages() {
16042
16389
  }
16043
16390
  return version;
16044
16391
  }
16392
+ function readWorkspaceCatalog(repoRoot2) {
16393
+ const text = fs29.readFileSync(path36.join(repoRoot2, "pnpm-workspace.yaml"), "utf-8");
16394
+ const marker = "\ncatalog:\n";
16395
+ const at = text.indexOf(marker);
16396
+ if (at === -1) return {};
16397
+ const catalog = {};
16398
+ for (const line of text.slice(at + marker.length).split("\n")) {
16399
+ if (line.trim() !== "" && !/^\s/.test(line)) break;
16400
+ const m = /^\s+"?([^":\s]+)"?:\s*"?([^"\s#]+)"?/.exec(line);
16401
+ if (m) catalog[m[1]] = m[2];
16402
+ }
16403
+ return catalog;
16404
+ }
16405
+ function normalizeHubManifests(contextDir, catalog) {
16406
+ const DEP_FIELDS = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
16407
+ const touched = [];
16408
+ for (const entry of fs29.readdirSync(contextDir, { withFileTypes: true })) {
16409
+ if (!entry.isDirectory()) continue;
16410
+ const manifest = path36.join(contextDir, entry.name, "package.json");
16411
+ if (!fs29.existsSync(manifest)) continue;
16412
+ const pkg = JSON.parse(fs29.readFileSync(manifest, "utf-8"));
16413
+ let changed = false;
16414
+ for (const field of DEP_FIELDS) {
16415
+ const deps = pkg[field];
16416
+ if (!deps) continue;
16417
+ for (const [name, spec] of Object.entries(deps)) {
16418
+ if (typeof spec !== "string") continue;
16419
+ if (spec.startsWith("workspace:")) {
16420
+ delete deps[name];
16421
+ changed = true;
16422
+ } else if (spec.startsWith("catalog:")) {
16423
+ const key = spec.slice("catalog:".length) || name;
16424
+ const range = catalog[name] ?? catalog[key];
16425
+ if (!range) {
16426
+ throw new MeshCliError(
16427
+ `${entry.name}/package.json depends on "${name}": "${spec}", which the workspace catalog does not define.`,
16428
+ { remediation: { command: "Add the dependency to the `catalog:` block in pnpm-workspace.yaml" } }
16429
+ );
16430
+ }
16431
+ deps[name] = range;
16432
+ changed = true;
16433
+ }
16434
+ }
16435
+ }
16436
+ if (changed) {
16437
+ fs29.writeFileSync(manifest, `${JSON.stringify(pkg, null, 2)}
16438
+ `);
16439
+ touched.push(`${entry.name}/package.json`);
16440
+ }
16441
+ }
16442
+ return touched;
16443
+ }
16444
+ async function buildHubImagesFromSource(repoRoot2) {
16445
+ const hubDir = path36.join(repoRoot2, "apps", "hub");
16446
+ if (!fs29.existsSync(path36.join(hubDir, "package.json"))) {
16447
+ throw new MeshCliError(
16448
+ `--hub-from-source needs a mesh-platform checkout; no apps/hub under ${repoRoot2}.`,
16449
+ { remediation: { command: "Run mesh start from a mesh-platform checkout, or drop --hub-from-source" } }
16450
+ );
16451
+ }
16452
+ const npmrc = npmrcPath();
16453
+ const version = `${JSON.parse(fs29.readFileSync(path36.join(hubDir, "package.json"), "utf-8")).version}-src`;
16454
+ const build = startHeartbeat("building apps/hub (api + ui) from source");
16455
+ try {
16456
+ for (const pkg of ["@mesh-tech/hub-api", "@mesh-tech/hub-ui"]) {
16457
+ await execFileAsync2("pnpm", ["--filter", pkg, "build"], { cwd: repoRoot2, maxBuffer: 64 * 1024 * 1024 });
16458
+ }
16459
+ } finally {
16460
+ build.stop();
16461
+ }
16462
+ const context = path36.join(cacheDir(), `context-${version}`);
16463
+ fs29.rmSync(context, { recursive: true, force: true });
16464
+ fs29.mkdirSync(context, { recursive: true });
16465
+ await execFileAsync2("pnpm", ["pack", "--pack-destination", context], { cwd: hubDir, maxBuffer: 64 * 1024 * 1024 });
16466
+ const tarball = fs29.readdirSync(context).find((f) => f.endsWith(".tgz"));
16467
+ if (!tarball) throw new MeshCliError("pnpm pack produced no tarball for apps/hub");
16468
+ execFileSync26("tar", ["-xzf", path36.join(context, tarball), "-C", context, "--strip-components", "1"], {
16469
+ stdio: ["ignore", "pipe", "pipe"]
16470
+ });
16471
+ const rewritten = normalizeHubManifests(context, readWorkspaceCatalog(repoRoot2));
16472
+ if (rewritten.length > 0) logInfo(`Normalized ${rewritten.length} manifest(s) for npm: ${rewritten.join(", ")}`);
16473
+ const hubStackDir = path36.join(findPackageRoot(), "stack", "hub");
16474
+ for (const [name, dockerfile] of [
16475
+ ["mesh-local-hub-api", "Dockerfile.api"],
16476
+ ["mesh-local-hub-ui", "Dockerfile.ui"]
16477
+ ]) {
16478
+ const tag = `${name}:${version}`;
16479
+ logInfo(`Building ${tag} from source\u2026`);
16480
+ execFileSync26(
16481
+ "docker",
16482
+ ["build", "-f", path36.join(hubStackDir, dockerfile), "-t", tag, "--secret", `id=npmrc,src=${npmrc}`, context],
16483
+ { stdio: ["ignore", "inherit", "inherit"], env: { ...process.env, DOCKER_BUILDKIT: "1" } }
16484
+ );
16485
+ logSuccess(`Built ${tag}`);
16486
+ }
16487
+ return version;
16488
+ }
16045
16489
  var execFileAsync2, HUB_PACKAGE, HUB_IMAGES, LOCAL_IMAGE_REV, HUB_AUTH_IMAGE;
16046
16490
  var init_hub_local = __esm({
16047
16491
  "libs/mesh-cli/src/commands/local/hub-local.ts"() {
@@ -16059,6 +16503,8 @@ var init_hub_local = __esm({
16059
16503
  });
16060
16504
 
16061
16505
  // libs/mesh-cli/src/commands/local/index.ts
16506
+ import * as fs30 from "fs";
16507
+ import * as path37 from "path";
16062
16508
  import chalk5 from "chalk";
16063
16509
  function crashError(crashed) {
16064
16510
  return new MeshCliError(
@@ -16101,7 +16547,7 @@ async function waitForStack(io = {
16101
16547
  );
16102
16548
  }
16103
16549
  logInfo(`Waiting for ${pending.map((e) => e.label).join(", ")} \u2026`);
16104
- await new Promise((resolve15) => setTimeout(resolve15, WAIT_POLL_MS));
16550
+ await new Promise((resolve16) => setTimeout(resolve16, WAIT_POLL_MS));
16105
16551
  }
16106
16552
  }
16107
16553
  function printEndpoints(hubRunning) {
@@ -16144,10 +16590,22 @@ function printEndpoints(hubRunning) {
16144
16590
  console.log("");
16145
16591
  logInfo("Next: `mesh dev` in an apps repo wires services to this stack automatically.");
16146
16592
  }
16593
+ function repoRootForHubSource() {
16594
+ for (let dir = process.cwd(); ; ) {
16595
+ if (fs30.existsSync(path37.join(dir, "apps", "hub", "package.json"))) return dir;
16596
+ const parent = path37.dirname(dir);
16597
+ if (parent === dir) break;
16598
+ dir = parent;
16599
+ }
16600
+ return path37.resolve(findPackageRoot(), "..", "..");
16601
+ }
16147
16602
  function registerLocalCommands(program2) {
16148
16603
  program2.command("start").description("Start the full-local Mesh platform (Docker only \u2014 no AWS, no VPN)").option("--no-seed", "skip first-boot seeding (tenant registry, artifacts bucket)").option("--no-hub", "start without the Hub (API + UI)").option(
16149
16604
  "--with-hub",
16150
16605
  "force-refresh the Hub to the latest published @mesh-tech/hub (the Hub is included by default when its images exist or registry auth is available)"
16606
+ ).option(
16607
+ "--hub-from-source",
16608
+ "build the Hub images from THIS checkout's apps/hub instead of the published tarball (the only way to exercise a Hub change locally)"
16151
16609
  ).option(
16152
16610
  "--takeover",
16153
16611
  "re-own a stack that another checkout started (may recreate shared containers with THIS checkout's config)",
@@ -16172,7 +16630,9 @@ Re-running start from here may recreate shared containers with this checkout's c
16172
16630
  }
16173
16631
  let hubVersion;
16174
16632
  if (opts.hub) {
16175
- if (opts.withHub) {
16633
+ if (opts.hubFromSource) {
16634
+ hubVersion = await buildHubImagesFromSource(repoRootForHubSource());
16635
+ } else if (opts.withHub) {
16176
16636
  const probe = await probeRegistryToken();
16177
16637
  const plan = planWithHubRefresh(probe.state, localHubVersion());
16178
16638
  if (plan.action === "fail") {
@@ -16260,7 +16720,7 @@ Re-running start from here may recreate shared containers with this checkout's c
16260
16720
  overlayStarted: true
16261
16721
  });
16262
16722
  if (crashed.length > 0) throw crashError(crashed);
16263
- await new Promise((resolve15) => setTimeout(resolve15, 2e3));
16723
+ await new Promise((resolve16) => setTimeout(resolve16, 2e3));
16264
16724
  up = await probeEndpoint(endpoint);
16265
16725
  }
16266
16726
  if (!up) {
@@ -16521,6 +16981,12 @@ import {
16521
16981
  } from "@aws-sdk/client-secrets-manager";
16522
16982
  import input from "@inquirer/input";
16523
16983
  import password from "@inquirer/password";
16984
+ import {
16985
+ buildInstanceIndexValue,
16986
+ instanceIndexSecretId,
16987
+ isInstanceKey,
16988
+ parseInstanceIndex
16989
+ } from "@mesh-tech/secrets";
16524
16990
  async function ensureAwsCredentialsForStack(stack) {
16525
16991
  if (process.env.AWS_ACCESS_KEY_ID || process.env.AWS_SESSION_TOKEN) return;
16526
16992
  if (!stack) return;
@@ -16705,6 +17171,12 @@ Usage: mesh secrets set external/<name>`);
16705
17171
  );
16706
17172
  process.exit(1);
16707
17173
  }
17174
+ if (opts.key && !isInstanceKey(opts.key)) {
17175
+ logError(
17176
+ `Invalid instance key "${opts.key}": keys must not contain "/", start with ".", or end with ".config" \u2014 those names are reserved for the index and config mirrors.`
17177
+ );
17178
+ process.exit(1);
17179
+ }
16708
17180
  let existing = {};
16709
17181
  try {
16710
17182
  const response = await smClient.send(
@@ -16729,6 +17201,9 @@ Usage: mesh secrets set external/<name>`);
16729
17201
  const merged2 = deepMerge(existing, values2);
16730
17202
  await writeSecret(smClient, secretId, merged2, svc.meta);
16731
17203
  await writeConfigMirror(smClient, secretId, schema, merged2, svc.meta);
17204
+ if (opts.key) {
17205
+ await addKeyToInstanceIndex(smClient, secretPrefix, opts.key, serviceName);
17206
+ }
16732
17207
  logSuccess(`Credentials written to ${secretId}`);
16733
17208
  return;
16734
17209
  }
@@ -16744,6 +17219,9 @@ Usage: mesh secrets set external/<name>`);
16744
17219
  const merged = deepMerge(existing, values);
16745
17220
  await writeSecret(smClient, secretId, merged, svc.meta);
16746
17221
  await writeConfigMirror(smClient, secretId, schema, merged, svc.meta);
17222
+ if (opts.key) {
17223
+ await addKeyToInstanceIndex(smClient, secretPrefix, opts.key, serviceName);
17224
+ }
16747
17225
  logSuccess(`Credentials written to ${secretId}`);
16748
17226
  console.error("\nStored fields:");
16749
17227
  displaySummary(schema.fields, merged);
@@ -16809,6 +17287,45 @@ function extractNonSecretFields(fields, values) {
16809
17287
  }
16810
17288
  return result;
16811
17289
  }
17290
+ async function addKeyToInstanceIndex(client, secretPrefix, key, serviceName) {
17291
+ const indexId = instanceIndexSecretId(secretPrefix);
17292
+ try {
17293
+ let keys = [];
17294
+ try {
17295
+ const res = await client.send(new GetSecretValueCommand5({ SecretId: indexId }));
17296
+ keys = parseInstanceIndex(res.SecretString ?? "");
17297
+ } catch (err) {
17298
+ if (!isResourceNotFound(err)) throw err;
17299
+ }
17300
+ if (keys.includes(key)) return;
17301
+ const secretString = buildInstanceIndexValue([...keys, key]);
17302
+ try {
17303
+ await client.send(
17304
+ new PutSecretValueCommand({ SecretId: indexId, SecretString: secretString })
17305
+ );
17306
+ } catch (err) {
17307
+ if (!isResourceNotFound(err)) throw err;
17308
+ await client.send(
17309
+ new CreateSecretCommand({
17310
+ Name: indexId,
17311
+ Description: `Instance-key index for ${serviceName} (read by listInstances(); maintained by mesh secrets set/reindex and the Hub)`,
17312
+ SecretString: secretString,
17313
+ Tags: [
17314
+ { Key: "mesh:type", Value: "external-service-index" },
17315
+ { Key: "mesh:service", Value: serviceName }
17316
+ ]
17317
+ })
17318
+ );
17319
+ logInfo(
17320
+ `Created ${indexId} OUT OF BAND \u2014 the declaring stack does not manage it. Deploy the declaring stack (it creates the index) before writing keys, or import this secret into the stack before its next deploy.`
17321
+ );
17322
+ }
17323
+ } catch (err) {
17324
+ logInfo(
17325
+ `Warning: failed to record instance "${key}" in ${indexId} \u2014 listInstances() will not see it until you run: mesh secrets reindex external/${serviceName} (${err})`
17326
+ );
17327
+ }
17328
+ }
16812
17329
  async function writeConfigMirror(client, secretId, schema, merged, meta) {
16813
17330
  const configId = `${secretId}/.config`;
16814
17331
  const configValues = extractNonSecretFields(schema.fields, merged);
@@ -16850,17 +17367,157 @@ var init_set = __esm({
16850
17367
  }
16851
17368
  });
16852
17369
 
17370
+ // libs/mesh-cli/src/commands/secrets/reindex.ts
17371
+ import { SSMClient as SSMClient3 } from "@aws-sdk/client-ssm";
17372
+ import {
17373
+ SecretsManagerClient as SecretsManagerClient6,
17374
+ ListSecretsCommand,
17375
+ GetSecretValueCommand as GetSecretValueCommand6,
17376
+ PutSecretValueCommand as PutSecretValueCommand2,
17377
+ CreateSecretCommand as CreateSecretCommand2
17378
+ } from "@aws-sdk/client-secrets-manager";
17379
+ import {
17380
+ buildInstanceIndexValue as buildInstanceIndexValue2,
17381
+ instanceIndexSecretId as instanceIndexSecretId2,
17382
+ instanceKeyFromSecretName,
17383
+ parseInstanceIndex as parseInstanceIndex2
17384
+ } from "@mesh-tech/secrets";
17385
+ async function scanInstanceKeys(client, secretPrefix) {
17386
+ const keys = [];
17387
+ let nextToken;
17388
+ do {
17389
+ const response = await client.send(
17390
+ new ListSecretsCommand({
17391
+ Filters: [{ Key: "name", Values: [`${secretPrefix}/`] }],
17392
+ NextToken: nextToken
17393
+ })
17394
+ );
17395
+ for (const secret of response.SecretList ?? []) {
17396
+ const key = secret.Name ? instanceKeyFromSecretName(secretPrefix, secret.Name) : null;
17397
+ if (key) keys.push(key);
17398
+ }
17399
+ nextToken = response.NextToken;
17400
+ } while (nextToken);
17401
+ return keys;
17402
+ }
17403
+ function indexAlreadyMatches(missing, stale, indexExists, malformed) {
17404
+ return missing.length === 0 && stale.length === 0 && indexExists && !malformed;
17405
+ }
17406
+ async function reindexCommand(servicePath, opts) {
17407
+ const stackOpt = resolveStackOption(opts);
17408
+ const context = detectContext(stackOpt);
17409
+ const region = opts.region ?? process.env.AWS_REGION ?? "us-east-2";
17410
+ await ensureAwsCredentialsForStack(stackOpt ?? context.stage);
17411
+ const ssmClient = new SSMClient3({ region });
17412
+ const smClient = new SecretsManagerClient6({ region });
17413
+ const services = await discoverExternalServices(
17414
+ ssmClient,
17415
+ context.tenant,
17416
+ context.platformEnv
17417
+ );
17418
+ const serviceName = servicePath?.replace(/^external\//, "");
17419
+ if (!serviceName || !services.has(serviceName)) {
17420
+ if (serviceName) {
17421
+ logError(`External service "${serviceName}" not found.`);
17422
+ }
17423
+ const keyed = [...services.entries()].filter(([, s]) => s.schema?.keyedBy);
17424
+ console.error("\nMulti-instance external services:");
17425
+ for (const [name, svc2] of keyed) {
17426
+ console.error(` ${name} (keyed by ${svc2.schema.keyedBy})`);
17427
+ }
17428
+ if (keyed.length === 0) console.error(" (none)");
17429
+ console.error(`
17430
+ Usage: mesh secrets reindex external/<name>`);
17431
+ process.exit(serviceName ? 1 : 0);
17432
+ }
17433
+ const svc = services.get(serviceName);
17434
+ if (!svc.schema?.keyedBy) {
17435
+ logError(
17436
+ `"${serviceName}" is single-instance (no keyedBy) \u2014 it has no instance index.`
17437
+ );
17438
+ process.exit(1);
17439
+ }
17440
+ const secretPrefix = svc.meta.secretPrefix;
17441
+ const indexId = instanceIndexSecretId2(secretPrefix);
17442
+ const actual = [...new Set(await scanInstanceKeys(smClient, secretPrefix))].sort();
17443
+ let listed = [];
17444
+ let indexExists = true;
17445
+ let malformed = false;
17446
+ try {
17447
+ const res = await smClient.send(new GetSecretValueCommand6({ SecretId: indexId }));
17448
+ try {
17449
+ listed = parseInstanceIndex2(res.SecretString ?? "");
17450
+ } catch {
17451
+ malformed = true;
17452
+ logInfo(`Existing index at ${indexId} is malformed \u2014 rebuilding from scratch.`);
17453
+ }
17454
+ } catch (err) {
17455
+ if (!isResourceNotFound(err)) throw err;
17456
+ indexExists = false;
17457
+ logInfo(`No index at ${indexId} yet \u2014 it will be created.`);
17458
+ }
17459
+ const missing = actual.filter((k) => !listed.includes(k));
17460
+ const stale = listed.filter((k) => !actual.includes(k));
17461
+ logInfo(`Instances in Secrets Manager: ${actual.length ? actual.join(", ") : "(none)"}`);
17462
+ if (missing.length > 0) logInfo(`Missing from index: ${missing.join(", ")}`);
17463
+ if (stale.length > 0) logInfo(`Stale in index (secret gone): ${stale.join(", ")}`);
17464
+ if (indexAlreadyMatches(missing, stale, indexExists, malformed)) {
17465
+ logSuccess(`Index at ${indexId} already matches \u2014 nothing to do.`);
17466
+ return;
17467
+ }
17468
+ if (opts.dryRun) {
17469
+ logInfo(`Dry run \u2014 would write ${indexId} with ${actual.length} instance(s).`);
17470
+ return;
17471
+ }
17472
+ await writeInstanceIndex(smClient, secretPrefix, serviceName, actual);
17473
+ logSuccess(`Index rebuilt: ${indexId} now lists ${actual.length} instance(s).`);
17474
+ }
17475
+ async function writeInstanceIndex(client, secretPrefix, serviceName, keys) {
17476
+ const indexId = instanceIndexSecretId2(secretPrefix);
17477
+ const secretString = buildInstanceIndexValue2(keys);
17478
+ try {
17479
+ await client.send(
17480
+ new PutSecretValueCommand2({ SecretId: indexId, SecretString: secretString })
17481
+ );
17482
+ } catch (err) {
17483
+ if (!isResourceNotFound(err)) throw err;
17484
+ await client.send(
17485
+ new CreateSecretCommand2({
17486
+ Name: indexId,
17487
+ Description: `Instance-key index for ${serviceName} (read by listInstances(); maintained by mesh secrets set/reindex and the Hub)`,
17488
+ SecretString: secretString,
17489
+ Tags: [
17490
+ { Key: "mesh:type", Value: "external-service-index" },
17491
+ { Key: "mesh:service", Value: serviceName }
17492
+ ]
17493
+ })
17494
+ );
17495
+ logInfo(
17496
+ `Created ${indexId} OUT OF BAND \u2014 the declaring stack does not manage it. Deploy the declaring stack BEFORE reindex next time, or import this secret into the stack before its next deploy.`
17497
+ );
17498
+ }
17499
+ }
17500
+ var init_reindex = __esm({
17501
+ "libs/mesh-cli/src/commands/secrets/reindex.ts"() {
17502
+ "use strict";
17503
+ init_context();
17504
+ init_log();
17505
+ init_stack_flag();
17506
+ init_set();
17507
+ }
17508
+ });
17509
+
16853
17510
  // libs/mesh-cli/src/commands/secrets/migrate-config.ts
16854
17511
  import {
16855
- SSMClient as SSMClient3,
17512
+ SSMClient as SSMClient4,
16856
17513
  GetParametersByPathCommand as GetParametersByPathCommand2
16857
17514
  } from "@aws-sdk/client-ssm";
16858
17515
  import {
16859
- SecretsManagerClient as SecretsManagerClient6,
16860
- GetSecretValueCommand as GetSecretValueCommand6,
16861
- PutSecretValueCommand as PutSecretValueCommand2,
16862
- CreateSecretCommand as CreateSecretCommand2,
16863
- ListSecretsCommand
17516
+ SecretsManagerClient as SecretsManagerClient7,
17517
+ GetSecretValueCommand as GetSecretValueCommand7,
17518
+ PutSecretValueCommand as PutSecretValueCommand3,
17519
+ CreateSecretCommand as CreateSecretCommand3,
17520
+ ListSecretsCommand as ListSecretsCommand2
16864
17521
  } from "@aws-sdk/client-secrets-manager";
16865
17522
  function normalizeEntry2(raw) {
16866
17523
  if (raw.type === "group" && raw.fields) {
@@ -16946,7 +17603,7 @@ function isResourceNotFound2(err) {
16946
17603
  }
16947
17604
  async function readSecret(client, secretId) {
16948
17605
  try {
16949
- const res = await client.send(new GetSecretValueCommand6({ SecretId: secretId }));
17606
+ const res = await client.send(new GetSecretValueCommand7({ SecretId: secretId }));
16950
17607
  return res.SecretString ? JSON.parse(res.SecretString) : null;
16951
17608
  } catch (err) {
16952
17609
  if (isResourceNotFound2(err)) return null;
@@ -16956,10 +17613,10 @@ async function readSecret(client, secretId) {
16956
17613
  async function writeSecret2(client, secretId, values, description) {
16957
17614
  const secretString = JSON.stringify(values);
16958
17615
  try {
16959
- await client.send(new PutSecretValueCommand2({ SecretId: secretId, SecretString: secretString }));
17616
+ await client.send(new PutSecretValueCommand3({ SecretId: secretId, SecretString: secretString }));
16960
17617
  } catch (err) {
16961
17618
  if (isResourceNotFound2(err)) {
16962
- await client.send(new CreateSecretCommand2({
17619
+ await client.send(new CreateSecretCommand3({
16963
17620
  Name: secretId,
16964
17621
  Description: description,
16965
17622
  SecretString: secretString,
@@ -16972,7 +17629,7 @@ async function writeSecret2(client, secretId, values, description) {
16972
17629
  }
16973
17630
  async function secretExists(client, secretId) {
16974
17631
  try {
16975
- await client.send(new GetSecretValueCommand6({ SecretId: secretId }));
17632
+ await client.send(new GetSecretValueCommand7({ SecretId: secretId }));
16976
17633
  return true;
16977
17634
  } catch (err) {
16978
17635
  if (isResourceNotFound2(err)) return false;
@@ -16985,7 +17642,7 @@ async function listInstanceKeys(client, secretPrefix) {
16985
17642
  let nextToken;
16986
17643
  do {
16987
17644
  const response = await client.send(
16988
- new ListSecretsCommand({
17645
+ new ListSecretsCommand2({
16989
17646
  Filters: [{ Key: "name", Values: [prefix2] }],
16990
17647
  NextToken: nextToken
16991
17648
  })
@@ -17051,8 +17708,8 @@ async function migrateService(smClient, meta, schema, opts) {
17051
17708
  async function migrateConfigCommand(opts) {
17052
17709
  const context = detectContext(resolveStackOption(opts));
17053
17710
  const region = opts.region ?? process.env.AWS_REGION ?? "us-east-2";
17054
- const ssmClient = new SSMClient3({ region });
17055
- const smClient = new SecretsManagerClient6({ region });
17711
+ const ssmClient = new SSMClient4({ region });
17712
+ const smClient = new SecretsManagerClient7({ region });
17056
17713
  logInfo(`Migrating .config mirrors for ${context.tenant}/${context.platformEnv}`);
17057
17714
  if (opts.dryRun) logInfo("DRY-RUN mode \u2014 no writes");
17058
17715
  if (opts.force) logInfo("FORCE mode \u2014 overwrite existing .config");
@@ -17105,6 +17762,11 @@ function registerSecretsCommands(program2) {
17105
17762
  secrets.command("set [service]").description("Set credentials for an external service (e.g., mesh secrets set external/symitar)").option("--key <key>", "Instance key for multi-instance services (e.g., FI ID)").option("--all", "Prompt for all fields including optional ones").option("--json <json>", "Non-interactive: provide values as JSON").option("--stack <stack>", "Pulumi stack name (override detection)").addOption(new Option2("--stage <stack>", "Deprecated alias for --stack").hideHelp()).option("--region <region>", "AWS region (default: us-east-2)").action(async (service, opts) => {
17106
17763
  await setCommand(service, opts);
17107
17764
  });
17765
+ secrets.command("reindex [service]").description(
17766
+ "Rebuild a multi-instance external service's instance index (the {prefix}/.index secret listInstances() reads) from the per-key secrets actually in Secrets Manager \u2014 the repair for an instance secret created or deleted out of band"
17767
+ ).option("--dry-run", "Show what would change without writing").option("--stack <stack>", "Pulumi stack name (override detection)").addOption(new Option2("--stage <stack>", "Deprecated alias for --stack").hideHelp()).option("--region <region>", "AWS region (default: us-east-2)").action(async (service, opts) => {
17768
+ await reindexCommand(service, opts);
17769
+ });
17108
17770
  secrets.command("migrate-config").description("Backfill .config mirror secrets for all external services").option("--force", "Overwrite existing .config mirrors").option("--dry-run", "Show what would be written without writing").option("--stack <stack>", "Pulumi stack name (override detection)").addOption(new Option2("--stage <stack>", "Deprecated alias for --stack").hideHelp()).option("--region <region>", "AWS region (default: us-east-2)").action(async (opts) => {
17109
17771
  await migrateConfigCommand(opts);
17110
17772
  });
@@ -17114,28 +17776,29 @@ var init_secrets = __esm({
17114
17776
  "use strict";
17115
17777
  init_exec2();
17116
17778
  init_set();
17779
+ init_reindex();
17117
17780
  init_migrate_config();
17118
17781
  }
17119
17782
  });
17120
17783
 
17121
17784
  // libs/mesh-cli/src/commands/stack.ts
17122
17785
  import { execFileSync as execFileSync27 } from "child_process";
17123
- import * as path37 from "path";
17124
- import * as fs30 from "fs";
17786
+ import * as path38 from "path";
17787
+ import * as fs31 from "fs";
17125
17788
  import { parse as parseYaml5 } from "yaml";
17126
17789
  function readTopLevelYamlKey(appRoot, stack, key) {
17127
- const configFile = path37.join(appRoot, `Pulumi.${stack}.yaml`);
17128
- if (!fs30.existsSync(configFile)) return null;
17129
- const content = fs30.readFileSync(configFile, "utf-8");
17790
+ const configFile = path38.join(appRoot, `Pulumi.${stack}.yaml`);
17791
+ if (!fs31.existsSync(configFile)) return null;
17792
+ const content = fs31.readFileSync(configFile, "utf-8");
17130
17793
  const pattern = new RegExp(`^${key}:\\s*(.+)$`, "m");
17131
17794
  const match = content.match(pattern);
17132
17795
  if (!match) return null;
17133
17796
  return match[1].trim().replace(/^["']|["']$/g, "");
17134
17797
  }
17135
17798
  function readConfigBlockKey(appRoot, stack, key) {
17136
- const configFile = path37.join(appRoot, `Pulumi.${stack}.yaml`);
17137
- if (!fs30.existsSync(configFile)) return null;
17138
- const content = fs30.readFileSync(configFile, "utf-8");
17799
+ const configFile = path38.join(appRoot, `Pulumi.${stack}.yaml`);
17800
+ if (!fs31.existsSync(configFile)) return null;
17801
+ const content = fs31.readFileSync(configFile, "utf-8");
17139
17802
  const pattern = new RegExp(`^\\s{2}${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:\\s*(.+)$`, "m");
17140
17803
  const match = content.match(pattern);
17141
17804
  if (!match) return null;
@@ -17195,11 +17858,11 @@ ${records}
17195
17858
  }
17196
17859
  }
17197
17860
  function readBaseConfigFromYaml(appRoot, stack) {
17198
- const file = path37.join(appRoot, `Pulumi.${stack}.yaml`);
17199
- if (!fs30.existsSync(file)) return {};
17861
+ const file = path38.join(appRoot, `Pulumi.${stack}.yaml`);
17862
+ if (!fs31.existsSync(file)) return {};
17200
17863
  let doc;
17201
17864
  try {
17202
- doc = parseYaml5(fs30.readFileSync(file, "utf-8"));
17865
+ doc = parseYaml5(fs31.readFileSync(file, "utf-8"));
17203
17866
  } catch {
17204
17867
  return {};
17205
17868
  }
@@ -17276,7 +17939,7 @@ Specify which to base on: mesh stack init --from <stack>`
17276
17939
  }
17277
17940
  }
17278
17941
  const baseConfigPath = `${appRoot}/Pulumi.${baseStack}.yaml`;
17279
- if (!fs30.existsSync(baseConfigPath)) {
17942
+ if (!fs31.existsSync(baseConfigPath)) {
17280
17943
  logError(`Stack config not found: Pulumi.${baseStack}.yaml`);
17281
17944
  process.exit(1);
17282
17945
  }
@@ -17298,8 +17961,8 @@ Specify which to base on: mesh stack init --from <stack>`
17298
17961
  );
17299
17962
  }
17300
17963
  }
17301
- const newConfigPath = path37.join(appRoot, `Pulumi.${newStack}.yaml`);
17302
- const configExists = fs30.existsSync(newConfigPath);
17964
+ const newConfigPath = path38.join(appRoot, `Pulumi.${newStack}.yaml`);
17965
+ const configExists = fs31.existsSync(newConfigPath);
17303
17966
  const secretsProvider = readTopLevelYamlKey(appRoot, baseStack, "secretsprovider");
17304
17967
  const credEnv = await resolvePulumiEnv({ appRoot, stack: baseStack });
17305
17968
  const pulumiEnv = { ...process.env, ...credEnv };
@@ -17946,9 +18609,9 @@ var init_capture_history = __esm({
17946
18609
 
17947
18610
  // libs/mesh-cli/src/commands/temporal.ts
17948
18611
  import { spawnSync as spawnSync4 } from "node:child_process";
17949
- import { writeFileSync as writeFileSync21, mkdirSync as mkdirSync22 } from "node:fs";
18612
+ import { writeFileSync as writeFileSync23, mkdirSync as mkdirSync22 } from "node:fs";
17950
18613
  import { homedir as homedir9 } from "node:os";
17951
- import { dirname as dirname26, join as join34, resolve as resolve13 } from "node:path";
18614
+ import { dirname as dirname27, join as join35, resolve as resolve14 } from "node:path";
17952
18615
  async function resolveConnection(options) {
17953
18616
  if (options.address && options.namespace) {
17954
18617
  return { address: options.address, namespace: options.namespace };
@@ -18318,7 +18981,7 @@ async function recoverConversation(workflowId, runId, options) {
18318
18981
  };
18319
18982
  const output2 = JSON.stringify(blob, null, 2) + "\n";
18320
18983
  if (options.out) {
18321
- writeFileSync21(options.out, output2);
18984
+ writeFileSync23(options.out, output2);
18322
18985
  logSuccess(
18323
18986
  `Wrote snapshot (${messages.length} messages) to ${options.out}`
18324
18987
  );
@@ -18341,7 +19004,7 @@ async function recoverConversation(workflowId, runId, options) {
18341
19004
  2
18342
19005
  ) + "\n" : renderTranscriptMarkdown(turns);
18343
19006
  if (options.out) {
18344
- writeFileSync21(options.out, output);
19007
+ writeFileSync23(options.out, output);
18345
19008
  logSuccess(`Wrote ${turns.length} turns to ${options.out}`);
18346
19009
  } else {
18347
19010
  process.stdout.write(output);
@@ -18376,10 +19039,10 @@ async function captureHistory(workflowId, runId, options) {
18376
19039
  }
18377
19040
  const { serializeHistoryToFixture: serializeHistoryToFixture2 } = await Promise.resolve().then(() => (init_capture_history(), capture_history_exports));
18378
19041
  const { fixture, eventCount, decryptedPayloads } = await serializeHistoryToFixture2(events, decrypter);
18379
- const outPath = options.out ?? join34(homedir9(), ".mesh", "replay-histories", `${sanitizeFileId(workflowId)}.json`);
18380
- mkdirSync22(dirname26(outPath), { recursive: true });
19042
+ const outPath = options.out ?? join35(homedir9(), ".mesh", "replay-histories", `${sanitizeFileId(workflowId)}.json`);
19043
+ mkdirSync22(dirname27(outPath), { recursive: true });
18381
19044
  if (options.out) warnIfNotGitIgnored(options.out);
18382
- writeFileSync21(outPath, JSON.stringify(fixture, null, 2) + "\n");
19045
+ writeFileSync23(outPath, JSON.stringify(fixture, null, 2) + "\n");
18383
19046
  logSuccess(
18384
19047
  `Wrote replay history (${eventCount} events, ${decryptedPayloads} payloads decrypted) to ${outPath}`
18385
19048
  );
@@ -18394,9 +19057,9 @@ function sanitizeFileId(id) {
18394
19057
  return id.replace(/[^A-Za-z0-9._-]/g, "_");
18395
19058
  }
18396
19059
  function warnIfNotGitIgnored(outPath) {
18397
- const abs = resolve13(outPath);
19060
+ const abs = resolve14(outPath);
18398
19061
  try {
18399
- const res = spawnSync4("git", ["-C", dirname26(abs), "check-ignore", "-q", abs], {
19062
+ const res = spawnSync4("git", ["-C", dirname27(abs), "check-ignore", "-q", abs], {
18400
19063
  stdio: "ignore"
18401
19064
  });
18402
19065
  if (res.status !== 1) return;
@@ -18564,8 +19227,8 @@ var init_temporal = __esm({
18564
19227
 
18565
19228
  // libs/mesh-cli/src/commands/tenant.ts
18566
19229
  import chalk6 from "chalk";
18567
- import * as fs31 from "fs";
18568
- import * as path38 from "path";
19230
+ import * as fs32 from "fs";
19231
+ import * as path39 from "path";
18569
19232
  import { parseDocument, YAMLMap, isMap } from "yaml";
18570
19233
  function validateTenantName(name) {
18571
19234
  return /^[a-z][a-z0-9-]*$/.test(name) && !name.endsWith("-");
@@ -18633,16 +19296,16 @@ function resolveStackConfig(explicitStack) {
18633
19296
  { remediation: { command: "mesh tenant add <name> --stack <stack>" } }
18634
19297
  );
18635
19298
  }
18636
- const file = path38.join(appRoot, `Pulumi.${stack}.yaml`);
18637
- if (!fs31.existsSync(file)) {
19299
+ const file = path39.join(appRoot, `Pulumi.${stack}.yaml`);
19300
+ if (!fs32.existsSync(file)) {
18638
19301
  throw new MeshCliError(`Stack config not found: ${file}`, {
18639
19302
  remediation: { command: "mesh tenant add <name> --stack <stack>" }
18640
19303
  });
18641
19304
  }
18642
- return { appRoot, stack, file, content: fs31.readFileSync(file, "utf-8") };
19305
+ return { appRoot, stack, file, content: fs32.readFileSync(file, "utf-8") };
18643
19306
  }
18644
19307
  function readProjectName(appRoot) {
18645
- const projectDoc = parseDocument(fs31.readFileSync(path38.join(appRoot, "Pulumi.yaml"), "utf-8"));
19308
+ const projectDoc = parseDocument(fs32.readFileSync(path39.join(appRoot, "Pulumi.yaml"), "utf-8"));
18646
19309
  return String(projectDoc.get("name") ?? "");
18647
19310
  }
18648
19311
  function looksLikePlatformLayer(projectName) {
@@ -18685,12 +19348,12 @@ function registerTenantCommands(program2) {
18685
19348
  if (!result.ok) {
18686
19349
  if (result.reason === "exists") {
18687
19350
  throw new MeshCliError(
18688
- `${result.detail} (${path38.basename(resolved.file)}). Edit the existing entry instead of re-adding it.`
19351
+ `${result.detail} (${path39.basename(resolved.file)}). Edit the existing entry instead of re-adding it.`
18689
19352
  );
18690
19353
  }
18691
19354
  throw new MeshCliError(`${resolved.file}: ${result.detail}`);
18692
19355
  }
18693
- atomicWriteFileSync(resolved.file, result.yaml, fs31.statSync(resolved.file).mode & 511);
19356
+ atomicWriteFileSync(resolved.file, result.yaml, fs32.statSync(resolved.file).mode & 511);
18694
19357
  if (opts.json) {
18695
19358
  emitJsonPayload({
18696
19359
  ok: true,
@@ -18703,7 +19366,7 @@ function registerTenantCommands(program2) {
18703
19366
  return;
18704
19367
  }
18705
19368
  logInfo(
18706
- `Tenant '${name}' registered in ${path38.basename(resolved.file)} (mesh:tenants, subdomain '${subdomain}')${result.createdTenantsBlock ? " \u2014 created the mesh:tenants block" : ""}`
19369
+ `Tenant '${name}' registered in ${path39.basename(resolved.file)} (mesh:tenants, subdomain '${subdomain}')${result.createdTenantsBlock ? " \u2014 created the mesh:tenants block" : ""}`
18707
19370
  );
18708
19371
  console.log("");
18709
19372
  console.log("Next steps:");
@@ -18729,7 +19392,7 @@ function registerTenantCommands(program2) {
18729
19392
  `Project '${projectName}' does not look like a platform layer \u2014 tenants are declared on the tenant platform repo's PLATFORM stack (e.g. mesh-sandbox/platform).`
18730
19393
  );
18731
19394
  }
18732
- logInfo(`No tenants declared in ${path38.basename(resolved.file)}.`);
19395
+ logInfo(`No tenants declared in ${path39.basename(resolved.file)}.`);
18733
19396
  return;
18734
19397
  }
18735
19398
  logInfo(`Tenants on stack '${resolved.stack}':`);
@@ -18751,12 +19414,12 @@ var init_tenant = __esm({
18751
19414
 
18752
19415
  // libs/mesh-cli/src/commands/tunnel/index.ts
18753
19416
  import { spawn as spawn9 } from "child_process";
18754
- import * as fs32 from "fs";
19417
+ import * as fs33 from "fs";
18755
19418
  import * as os13 from "os";
18756
- import * as path39 from "path";
19419
+ import * as path40 from "path";
18757
19420
  import {
18758
- SecretsManagerClient as SecretsManagerClient7,
18759
- GetSecretValueCommand as GetSecretValueCommand7
19421
+ SecretsManagerClient as SecretsManagerClient8,
19422
+ GetSecretValueCommand as GetSecretValueCommand8
18760
19423
  } from "@aws-sdk/client-secrets-manager";
18761
19424
  function resolveTenantEnv(options) {
18762
19425
  if (options.tenant && options.env) {
@@ -18773,9 +19436,9 @@ function readPulumiAwsRegion(stage) {
18773
19436
  const preferred = stage ? `Pulumi.${stage}.yaml` : void 0;
18774
19437
  const rank = (f) => f === preferred ? 0 : f === "Pulumi.yaml" ? 2 : 1;
18775
19438
  try {
18776
- const files = fs32.readdirSync(".").filter((f) => f.startsWith("Pulumi.") && f.endsWith(".yaml")).sort((a, b) => rank(a) - rank(b));
19439
+ const files = fs33.readdirSync(".").filter((f) => f.startsWith("Pulumi.") && f.endsWith(".yaml")).sort((a, b) => rank(a) - rank(b));
18777
19440
  for (const f of files) {
18778
- const m = fs32.readFileSync(f, "utf-8").match(/^\s*aws:region:\s*["']?([^"'\n]+)["']?/m);
19441
+ const m = fs33.readFileSync(f, "utf-8").match(/^\s*aws:region:\s*["']?([^"'\n]+)["']?/m);
18779
19442
  if (m?.[1]) return m[1].trim();
18780
19443
  }
18781
19444
  } catch {
@@ -18784,14 +19447,14 @@ function readPulumiAwsRegion(stage) {
18784
19447
  }
18785
19448
  function awsProfileSections(name) {
18786
19449
  return [
18787
- [path39.join(os13.homedir(), ".aws", "config"), `[profile ${name}]`],
18788
- [path39.join(os13.homedir(), ".aws", "credentials"), `[${name}]`]
19450
+ [path40.join(os13.homedir(), ".aws", "config"), `[profile ${name}]`],
19451
+ [path40.join(os13.homedir(), ".aws", "credentials"), `[${name}]`]
18789
19452
  ];
18790
19453
  }
18791
19454
  function awsProfileExists(name) {
18792
19455
  for (const [file, header] of awsProfileSections(name)) {
18793
19456
  try {
18794
- const lines = fs32.readFileSync(file, "utf-8").split("\n");
19457
+ const lines = fs33.readFileSync(file, "utf-8").split("\n");
18795
19458
  if (lines.some((l) => l.trim() === header)) return true;
18796
19459
  } catch {
18797
19460
  }
@@ -18817,7 +19480,7 @@ function awsProfileRegion(profile) {
18817
19480
  if (!profile) return void 0;
18818
19481
  for (const [file, header] of awsProfileSections(profile)) {
18819
19482
  try {
18820
- const region = parseProfileRegion(fs32.readFileSync(file, "utf-8"), header);
19483
+ const region = parseProfileRegion(fs33.readFileSync(file, "utf-8"), header);
18821
19484
  if (region) return region;
18822
19485
  } catch {
18823
19486
  }
@@ -18826,14 +19489,14 @@ function awsProfileRegion(profile) {
18826
19489
  }
18827
19490
  function awsDefaultProfileSections() {
18828
19491
  return [
18829
- [path39.join(os13.homedir(), ".aws", "config"), "[default]"],
18830
- [path39.join(os13.homedir(), ".aws", "credentials"), "[default]"]
19492
+ [path40.join(os13.homedir(), ".aws", "config"), "[default]"],
19493
+ [path40.join(os13.homedir(), ".aws", "credentials"), "[default]"]
18831
19494
  ];
18832
19495
  }
18833
19496
  function awsDefaultProfileRegion() {
18834
19497
  for (const [file, header] of awsDefaultProfileSections()) {
18835
19498
  try {
18836
- const region = parseProfileRegion(fs32.readFileSync(file, "utf-8"), header);
19499
+ const region = parseProfileRegion(fs33.readFileSync(file, "utf-8"), header);
18837
19500
  if (region) return region;
18838
19501
  } catch {
18839
19502
  }
@@ -18974,9 +19637,9 @@ async function tunnelServices(serviceNames, options) {
18974
19637
  process.on("SIGTERM", cleanup);
18975
19638
  await Promise.race(
18976
19639
  processes.map(
18977
- (proc) => new Promise((resolve15) => {
18978
- proc.on("exit", () => resolve15());
18979
- proc.on("error", () => resolve15());
19640
+ (proc) => new Promise((resolve16) => {
19641
+ proc.on("exit", () => resolve16());
19642
+ proc.on("error", () => resolve16());
18980
19643
  })
18981
19644
  )
18982
19645
  );
@@ -19019,10 +19682,10 @@ async function tunnelExternal(name, options) {
19019
19682
  const { appTenant, appStage } = resolveCredentialAxis(options);
19020
19683
  const secretId = externalSecretId(appTenant, appStage, name, key);
19021
19684
  const setHint = `mesh secrets set external/${name}${key ? ` --key=${key}` : ""}`;
19022
- const sm = new SecretsManagerClient7({});
19685
+ const sm = new SecretsManagerClient8({});
19023
19686
  let creds;
19024
19687
  try {
19025
- const out = await sm.send(new GetSecretValueCommand7({ SecretId: secretId }));
19688
+ const out = await sm.send(new GetSecretValueCommand8({ SecretId: secretId }));
19026
19689
  creds = JSON.parse(out.SecretString ?? "{}");
19027
19690
  } catch (err) {
19028
19691
  logError(
@@ -19073,9 +19736,9 @@ async function tunnelExternal(name, options) {
19073
19736
  };
19074
19737
  process.on("SIGINT", cleanup);
19075
19738
  process.on("SIGTERM", cleanup);
19076
- await new Promise((resolve15) => {
19077
- proc.on("exit", () => resolve15());
19078
- proc.on("error", () => resolve15());
19739
+ await new Promise((resolve16) => {
19740
+ proc.on("exit", () => resolve16());
19741
+ proc.on("error", () => resolve16());
19079
19742
  });
19080
19743
  cleanup();
19081
19744
  }
@@ -19202,8 +19865,8 @@ async function resolveToken(opts) {
19202
19865
  "no token: pass --token, set VCS_TOKEN, or pass --context <platform-context> (after mesh login)"
19203
19866
  );
19204
19867
  }
19205
- async function vcsApi(baseUrl, token, path41, method = "GET", body) {
19206
- const res = await fetch(`${baseUrl}${path41}`, {
19868
+ async function vcsApi(baseUrl, token, path42, method = "GET", body) {
19869
+ const res = await fetch(`${baseUrl}${path42}`, {
19207
19870
  method,
19208
19871
  headers: {
19209
19872
  authorization: `Bearer ${token}`,
@@ -19214,7 +19877,7 @@ async function vcsApi(baseUrl, token, path41, method = "GET", body) {
19214
19877
  const data = await res.json().catch(() => ({}));
19215
19878
  if (!res.ok) {
19216
19879
  throw new Error(
19217
- `${method} ${path41} failed (${res.status}): ${String(data.error ?? "unknown error")}`
19880
+ `${method} ${path42} failed (${res.status}): ${String(data.error ?? "unknown error")}`
19218
19881
  );
19219
19882
  }
19220
19883
  return data;
@@ -19257,8 +19920,8 @@ var init_clone = __esm({
19257
19920
 
19258
19921
  // libs/mesh-cli/src/commands/vcs/get.ts
19259
19922
  import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
19260
- import { join as join37, dirname as dirname27 } from "node:path";
19261
- async function getCommand(repo, path41, opts) {
19923
+ import { join as join38, dirname as dirname28 } from "node:path";
19924
+ async function getCommand(repo, path42, opts) {
19262
19925
  let vcsBaseUrl;
19263
19926
  let token;
19264
19927
  if (opts.target) {
@@ -19273,18 +19936,18 @@ async function getCommand(repo, path41, opts) {
19273
19936
  if (!token) throw new Error("no token: pass --target (after mesh login), or --token / --context");
19274
19937
  const reader = createVcsFolderReader({ vcsBaseUrl, token });
19275
19938
  try {
19276
- const { ref, files } = await reader.readPath(repo, path41);
19939
+ const { ref, files } = await reader.readPath(repo, path42);
19277
19940
  if (files.length === 0) {
19278
- logInfo(`no files at ${repo}:${path41}`);
19941
+ logInfo(`no files at ${repo}:${path42}`);
19279
19942
  return;
19280
19943
  }
19281
19944
  if (opts.output) {
19282
19945
  for (const f of files) {
19283
- const abs = join37(opts.output, f.path);
19284
- await mkdir2(dirname27(abs), { recursive: true });
19946
+ const abs = join38(opts.output, f.path);
19947
+ await mkdir2(dirname28(abs), { recursive: true });
19285
19948
  await writeFile2(abs, f.contents);
19286
19949
  }
19287
- logInfo(`wrote ${files.length} file(s) from ${repo}:${path41} (${ref}) \u2192 ${opts.output}`);
19950
+ logInfo(`wrote ${files.length} file(s) from ${repo}:${path42} (${ref}) \u2192 ${opts.output}`);
19288
19951
  } else {
19289
19952
  for (const f of files) {
19290
19953
  if (files.length > 1) process.stdout.write(`
@@ -19309,10 +19972,10 @@ var init_get = __esm({
19309
19972
  });
19310
19973
 
19311
19974
  // libs/mesh-cli/src/commands/vcs/drafts.ts
19312
- async function call(opts, repo, path41, method = "GET", body) {
19975
+ async function call(opts, repo, path42, method = "GET", body) {
19313
19976
  const target = await resolveTarget2({ ...opts, repo });
19314
19977
  const token = await resolveToken(opts);
19315
- return vcsApi(target.baseUrl, token, `/v1/repos/${repo}${path41}`, method, body);
19978
+ return vcsApi(target.baseUrl, token, `/v1/repos/${repo}${path42}`, method, body);
19316
19979
  }
19317
19980
  async function draftsListCommand(repo, opts) {
19318
19981
  const data = await call(opts, repo, `/drafts${opts.all ? "?all=true" : ""}`);
@@ -19367,7 +20030,7 @@ var init_drafts = __esm({
19367
20030
 
19368
20031
  // libs/mesh-cli/src/commands/vcs/propose.ts
19369
20032
  import { readFile as readFile2 } from "node:fs/promises";
19370
- import { join as join38 } from "node:path";
20033
+ import { join as join39 } from "node:path";
19371
20034
  function parseStatus(out) {
19372
20035
  const tokens = out.split("\0");
19373
20036
  const changes = [];
@@ -19422,7 +20085,7 @@ async function proposeCommand(opts) {
19422
20085
  async (c) => c.status.startsWith("D") ? { op: "delete", path: c.path } : {
19423
20086
  op: "write",
19424
20087
  path: c.path,
19425
- content: await readFile2(join38(cwd, c.path), "utf8")
20088
+ content: await readFile2(join39(cwd, c.path), "utf8")
19426
20089
  }
19427
20090
  )
19428
20091
  );
@@ -19446,8 +20109,8 @@ async function proposeCommand(opts) {
19446
20109
  operations,
19447
20110
  ...opts.mergeParent ? { mergeParent: opts.mergeParent } : {}
19448
20111
  };
19449
- const path41 = opts.revise ? `/v1/repos/${target.repo}/proposals/${opts.revise}/revisions` : `/v1/repos/${target.repo}/proposals`;
19450
- const data = await vcsApi(target.baseUrl, token, path41, "POST", body);
20112
+ const path42 = opts.revise ? `/v1/repos/${target.repo}/proposals/${opts.revise}/revisions` : `/v1/repos/${target.repo}/proposals`;
20113
+ const data = await vcsApi(target.baseUrl, token, path42, "POST", body);
19451
20114
  console.log(
19452
20115
  `proposal ${data.proposalId} @ ${data.sha.slice(0, 8)} \u2014 review: mesh vcs show ${target.repo} ${data.proposalId} --url ${target.baseUrl}`
19453
20116
  );
@@ -19460,10 +20123,10 @@ var init_propose = __esm({
19460
20123
  });
19461
20124
 
19462
20125
  // libs/mesh-cli/src/commands/vcs/review.ts
19463
- async function call2(opts, repo, path41, method = "GET", body) {
20126
+ async function call2(opts, repo, path42, method = "GET", body) {
19464
20127
  const target = await resolveTarget2({ ...opts, repo });
19465
20128
  const token = await resolveToken(opts);
19466
- return vcsApi(target.baseUrl, token, `/v1/repos/${repo}${path41}`, method, body);
20129
+ return vcsApi(target.baseUrl, token, `/v1/repos/${repo}${path42}`, method, body);
19467
20130
  }
19468
20131
  async function proposalsCommand(repo, opts) {
19469
20132
  console.log(JSON.stringify(await call2(opts, repo, "/proposals"), null, 2));
@@ -19604,8 +20267,8 @@ function computeHappyPath(nodes, edges) {
19604
20267
  const queue = [{ id: startNode.id, path: [startNode.id] }];
19605
20268
  const visited = /* @__PURE__ */ new Set([startNode.id]);
19606
20269
  while (queue.length > 0) {
19607
- const { id, path: path41 } = queue.shift();
19608
- if (targetIds.has(id)) return path41;
20270
+ const { id, path: path42 } = queue.shift();
20271
+ if (targetIds.has(id)) return path42;
19609
20272
  const neighbors = adjacency.get(id) ?? [];
19610
20273
  const hasMainEdge = neighbors.some((e) => e.isMainPath);
19611
20274
  const candidates = hasMainEdge ? neighbors.filter((e) => e.isMainPath) : neighbors;
@@ -19624,7 +20287,7 @@ function computeHappyPath(nodes, edges) {
19624
20287
  if (visited.has(neighbor.to)) continue;
19625
20288
  if (!allowExceptional && (neighbor.isExceptional || neighbor.isTimeout || failureEndIds.has(neighbor.to))) continue;
19626
20289
  visited.add(neighbor.to);
19627
- queue.push({ id: neighbor.to, path: [...path41, neighbor.to] });
20290
+ queue.push({ id: neighbor.to, path: [...path42, neighbor.to] });
19628
20291
  }
19629
20292
  }
19630
20293
  return null;
@@ -19872,11 +20535,11 @@ function wrapIntoGroup(workflow, nodeIds, groupId, label, groupType) {
19872
20535
  }
19873
20536
  }
19874
20537
  if (found) {
19875
- const path41 = [];
19876
- for (let c = found; c !== void 0; c = prev.get(c)) path41.unshift(c);
19877
- for (let i = 0; i < path41.length - 1; i++) mainEdgeKeys.add(edgeKey(path41[i], path41[i + 1]));
20538
+ const path42 = [];
20539
+ for (let c = found; c !== void 0; c = prev.get(c)) path42.unshift(c);
20540
+ for (let i = 0; i < path42.length - 1; i++) mainEdgeKeys.add(edgeKey(path42[i], path42[i + 1]));
19878
20541
  mainEdgeKeys.add(edgeKey(found, successExit.id));
19879
- mainBody = path41;
20542
+ mainBody = path42;
19880
20543
  mainTail = [successExit.id];
19881
20544
  }
19882
20545
  }
@@ -20278,11 +20941,11 @@ var init_apply_preview_patch = __esm({
20278
20941
 
20279
20942
  // libs/workflow-model/src/process-artifact.ts
20280
20943
  import { z as z4 } from "zod";
20281
- function formatPath(path41) {
20282
- return path41.length > 0 ? z4.core.toDotPath(path41) : "(root)";
20944
+ function formatPath(path42) {
20945
+ return path42.length > 0 ? z4.core.toDotPath(path42) : "(root)";
20283
20946
  }
20284
20947
  function issueToDiagnostic(issue) {
20285
- const path41 = formatPath(issue.path);
20948
+ const path42 = formatPath(issue.path);
20286
20949
  let code = "SCHEMA_INVALID";
20287
20950
  if (issue.code === "custom") {
20288
20951
  const paramCode = issue.params?.code;
@@ -20293,8 +20956,8 @@ function issueToDiagnostic(issue) {
20293
20956
  return {
20294
20957
  severity: "error",
20295
20958
  code,
20296
- message: `${path41}: ${issue.message}`,
20297
- path: path41
20959
+ message: `${path42}: ${issue.message}`,
20960
+ path: path42
20298
20961
  };
20299
20962
  }
20300
20963
  function parseProcessArtifact(json) {
@@ -20374,20 +21037,20 @@ var init_process_artifact = __esm({
20374
21037
  outcome: "DUPLICATE_OUTCOME_ID"
20375
21038
  };
20376
21039
  const idFirstSeenAt = /* @__PURE__ */ new Map();
20377
- const checkId = (id, kind, path41) => {
21040
+ const checkId = (id, kind, path42) => {
20378
21041
  const first = idFirstSeenAt.get(id);
20379
21042
  if (first) {
20380
21043
  const kindLabel = kind === first.kind ? kind : "process";
20381
21044
  ctx.addIssue({
20382
21045
  code: "custom",
20383
- path: path41,
20384
- message: `Duplicate ${kindLabel} id "${id}": ${kind} at ${z4.core.toDotPath(path41)} collides with ${first.kind} at ${z4.core.toDotPath(first.path)}.`,
21046
+ path: path42,
21047
+ message: `Duplicate ${kindLabel} id "${id}": ${kind} at ${z4.core.toDotPath(path42)} collides with ${first.kind} at ${z4.core.toDotPath(first.path)}.`,
20385
21048
  params: {
20386
21049
  code: kind === first.kind ? SAME_KIND_CODE[kind] : "DUPLICATE_PROCESS_ID"
20387
21050
  }
20388
21051
  });
20389
21052
  } else {
20390
- idFirstSeenAt.set(id, { kind, path: path41 });
21053
+ idFirstSeenAt.set(id, { kind, path: path42 });
20391
21054
  }
20392
21055
  };
20393
21056
  process2.stages.forEach((stage, stageIndex) => {
@@ -20425,13 +21088,13 @@ function lintProcess(artifact, inventory, predicateNames) {
20425
21088
  const internalCommands = process2.internalCommands ?? [];
20426
21089
  const internalSet = new Set(internalCommands);
20427
21090
  const boundCommandPaths = /* @__PURE__ */ new Map();
20428
- const checkPredicate = (predicate, path41) => {
21091
+ const checkPredicate = (predicate, path42) => {
20429
21092
  if (!predicateSet.has(predicate)) {
20430
21093
  diagnostics.push({
20431
21094
  severity: "error",
20432
21095
  code: "UNKNOWN_PREDICATE",
20433
- message: `Predicate "${predicate}" referenced at ${path41} is not in the predicate registry.`,
20434
- path: path41
21096
+ message: `Predicate "${predicate}" referenced at ${path42} is not in the predicate registry.`,
21097
+ path: path42
20435
21098
  });
20436
21099
  }
20437
21100
  };
@@ -20572,8 +21235,8 @@ var init_src3 = __esm({
20572
21235
  });
20573
21236
 
20574
21237
  // libs/mesh-cli/src/commands/workflow.ts
20575
- import * as fs33 from "fs";
20576
- import * as path40 from "path";
21238
+ import * as fs34 from "fs";
21239
+ import * as path41 from "path";
20577
21240
  import { createRequire as createRequire2 } from "module";
20578
21241
  import { execFileSync as execFileSync29 } from "child_process";
20579
21242
  function resolveExtractorPath() {
@@ -20589,7 +21252,7 @@ function runExtraction(targetPath, extractorPath, explicitProcessPath) {
20589
21252
  let explicitProcessArtifact;
20590
21253
  if (explicitProcessPath) {
20591
21254
  try {
20592
- explicitProcessArtifact = JSON.parse(fs33.readFileSync(explicitProcessPath, "utf-8"));
21255
+ explicitProcessArtifact = JSON.parse(fs34.readFileSync(explicitProcessPath, "utf-8"));
20593
21256
  } catch (err) {
20594
21257
  logError(
20595
21258
  `Could not read/parse --process ${explicitProcessPath}: ${err instanceof Error ? err.message : String(err)}`
@@ -20689,7 +21352,7 @@ function runLintExtraction(targetPath, extractorPath, explicitProcessPath) {
20689
21352
  let explicitProcessArtifact;
20690
21353
  if (explicitProcessPath) {
20691
21354
  try {
20692
- explicitProcessArtifact = JSON.parse(fs33.readFileSync(explicitProcessPath, "utf-8"));
21355
+ explicitProcessArtifact = JSON.parse(fs34.readFileSync(explicitProcessPath, "utf-8"));
20693
21356
  } catch (err) {
20694
21357
  logError(
20695
21358
  `Could not read/parse --process ${explicitProcessPath}: ${err instanceof Error ? err.message : String(err)}`
@@ -20794,16 +21457,16 @@ function registerWorkflowCommands(program2) {
20794
21457
  ).action(
20795
21458
  async (targetPath, opts) => {
20796
21459
  try {
20797
- const resolvedPath = path40.resolve(targetPath);
20798
- if (!fs33.existsSync(resolvedPath)) {
21460
+ const resolvedPath = path41.resolve(targetPath);
21461
+ if (!fs34.existsSync(resolvedPath)) {
20799
21462
  logError(`Path does not exist: ${resolvedPath}`);
20800
21463
  process.exitCode = 1;
20801
21464
  return;
20802
21465
  }
20803
21466
  let resolvedProcessPath;
20804
21467
  if (opts.process) {
20805
- resolvedProcessPath = path40.resolve(opts.process);
20806
- if (!fs33.existsSync(resolvedProcessPath)) {
21468
+ resolvedProcessPath = path41.resolve(opts.process);
21469
+ if (!fs34.existsSync(resolvedProcessPath)) {
20807
21470
  logError(`--process path does not exist: ${resolvedProcessPath}`);
20808
21471
  process.exitCode = 1;
20809
21472
  return;
@@ -20852,8 +21515,8 @@ function registerWorkflowCommands(program2) {
20852
21515
  "Extract the as-built command/query/activity inventory from Temporal workflow source\n\nThe inventory ({ workflowType, commands, queries, activities }) is the process-\nconformance lint's code-side input \u2014 it is never hand-edited. Supports single\nfiles or directories."
20853
21516
  ).action(async (targetPath) => {
20854
21517
  try {
20855
- const resolvedPath = path40.resolve(targetPath);
20856
- if (!fs33.existsSync(resolvedPath)) {
21518
+ const resolvedPath = path41.resolve(targetPath);
21519
+ if (!fs34.existsSync(resolvedPath)) {
20857
21520
  logError(`Path does not exist: ${resolvedPath}`);
20858
21521
  process.exitCode = 1;
20859
21522
  return;
@@ -20896,16 +21559,16 @@ function registerWorkflowCommands(program2) {
20896
21559
  "Comma-separated named-predicate registry (enables UNKNOWN_PREDICATE checks)"
20897
21560
  ).action(async (targetPath, opts) => {
20898
21561
  try {
20899
- const resolvedPath = path40.resolve(targetPath);
20900
- if (!fs33.existsSync(resolvedPath)) {
21562
+ const resolvedPath = path41.resolve(targetPath);
21563
+ if (!fs34.existsSync(resolvedPath)) {
20901
21564
  logError(`Path does not exist: ${resolvedPath}`);
20902
21565
  process.exitCode = 1;
20903
21566
  return;
20904
21567
  }
20905
21568
  let resolvedProcessPath;
20906
21569
  if (opts.process) {
20907
- resolvedProcessPath = path40.resolve(opts.process);
20908
- if (!fs33.existsSync(resolvedProcessPath)) {
21570
+ resolvedProcessPath = path41.resolve(opts.process);
21571
+ if (!fs34.existsSync(resolvedProcessPath)) {
20909
21572
  logError(`--process path does not exist: ${resolvedProcessPath}`);
20910
21573
  process.exitCode = 1;
20911
21574
  return;