@mesh-tech/mesh-cli 0.14.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 (44) hide show
  1. package/dist/bin/mesh.js +686 -226
  2. package/dist/bin/mesh.js.map +3 -3
  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/fragments/base/index.ts.hbs +9 -0
  35. package/fragments/base/package.json.hbs +1 -1
  36. package/fragments/service/api/package.json.hbs +4 -0
  37. package/fragments/service/api/src/index.ts.hbs +1 -4
  38. package/fragments/temporal/worker/package.json.hbs +1 -0
  39. package/fragments/temporal/worker/src/activities.ts.hbs +5 -1
  40. package/fragments/temporal/worker/src/workflows.ts.hbs +4 -1
  41. package/package.json +2 -2
  42. package/skills/core/SKILL.md +1 -1
  43. package/stack/docker-compose.hub.yml +5 -0
  44. 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
  }
@@ -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`);
@@ -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",
@@ -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,12 +3789,39 @@ 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
3826
  const { SSMClient: SSMClient5, PutParameterCommand } = await import("@aws-sdk/client-ssm");
3724
3827
  const ssm = new SSMClient5(awsConfig);
@@ -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
 
@@ -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}`);
@@ -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",
@@ -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
  }
@@ -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) {
@@ -17323,22 +17783,22 @@ var init_secrets = __esm({
17323
17783
 
17324
17784
  // libs/mesh-cli/src/commands/stack.ts
17325
17785
  import { execFileSync as execFileSync27 } from "child_process";
17326
- import * as path37 from "path";
17327
- import * as fs30 from "fs";
17786
+ import * as path38 from "path";
17787
+ import * as fs31 from "fs";
17328
17788
  import { parse as parseYaml5 } from "yaml";
17329
17789
  function readTopLevelYamlKey(appRoot, stack, key) {
17330
- const configFile = path37.join(appRoot, `Pulumi.${stack}.yaml`);
17331
- if (!fs30.existsSync(configFile)) return null;
17332
- 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");
17333
17793
  const pattern = new RegExp(`^${key}:\\s*(.+)$`, "m");
17334
17794
  const match = content.match(pattern);
17335
17795
  if (!match) return null;
17336
17796
  return match[1].trim().replace(/^["']|["']$/g, "");
17337
17797
  }
17338
17798
  function readConfigBlockKey(appRoot, stack, key) {
17339
- const configFile = path37.join(appRoot, `Pulumi.${stack}.yaml`);
17340
- if (!fs30.existsSync(configFile)) return null;
17341
- 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");
17342
17802
  const pattern = new RegExp(`^\\s{2}${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:\\s*(.+)$`, "m");
17343
17803
  const match = content.match(pattern);
17344
17804
  if (!match) return null;
@@ -17398,11 +17858,11 @@ ${records}
17398
17858
  }
17399
17859
  }
17400
17860
  function readBaseConfigFromYaml(appRoot, stack) {
17401
- const file = path37.join(appRoot, `Pulumi.${stack}.yaml`);
17402
- if (!fs30.existsSync(file)) return {};
17861
+ const file = path38.join(appRoot, `Pulumi.${stack}.yaml`);
17862
+ if (!fs31.existsSync(file)) return {};
17403
17863
  let doc;
17404
17864
  try {
17405
- doc = parseYaml5(fs30.readFileSync(file, "utf-8"));
17865
+ doc = parseYaml5(fs31.readFileSync(file, "utf-8"));
17406
17866
  } catch {
17407
17867
  return {};
17408
17868
  }
@@ -17479,7 +17939,7 @@ Specify which to base on: mesh stack init --from <stack>`
17479
17939
  }
17480
17940
  }
17481
17941
  const baseConfigPath = `${appRoot}/Pulumi.${baseStack}.yaml`;
17482
- if (!fs30.existsSync(baseConfigPath)) {
17942
+ if (!fs31.existsSync(baseConfigPath)) {
17483
17943
  logError(`Stack config not found: Pulumi.${baseStack}.yaml`);
17484
17944
  process.exit(1);
17485
17945
  }
@@ -17501,8 +17961,8 @@ Specify which to base on: mesh stack init --from <stack>`
17501
17961
  );
17502
17962
  }
17503
17963
  }
17504
- const newConfigPath = path37.join(appRoot, `Pulumi.${newStack}.yaml`);
17505
- const configExists = fs30.existsSync(newConfigPath);
17964
+ const newConfigPath = path38.join(appRoot, `Pulumi.${newStack}.yaml`);
17965
+ const configExists = fs31.existsSync(newConfigPath);
17506
17966
  const secretsProvider = readTopLevelYamlKey(appRoot, baseStack, "secretsprovider");
17507
17967
  const credEnv = await resolvePulumiEnv({ appRoot, stack: baseStack });
17508
17968
  const pulumiEnv = { ...process.env, ...credEnv };
@@ -18149,9 +18609,9 @@ var init_capture_history = __esm({
18149
18609
 
18150
18610
  // libs/mesh-cli/src/commands/temporal.ts
18151
18611
  import { spawnSync as spawnSync4 } from "node:child_process";
18152
- import { writeFileSync as writeFileSync21, mkdirSync as mkdirSync22 } from "node:fs";
18612
+ import { writeFileSync as writeFileSync23, mkdirSync as mkdirSync22 } from "node:fs";
18153
18613
  import { homedir as homedir9 } from "node:os";
18154
- 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";
18155
18615
  async function resolveConnection(options) {
18156
18616
  if (options.address && options.namespace) {
18157
18617
  return { address: options.address, namespace: options.namespace };
@@ -18521,7 +18981,7 @@ async function recoverConversation(workflowId, runId, options) {
18521
18981
  };
18522
18982
  const output2 = JSON.stringify(blob, null, 2) + "\n";
18523
18983
  if (options.out) {
18524
- writeFileSync21(options.out, output2);
18984
+ writeFileSync23(options.out, output2);
18525
18985
  logSuccess(
18526
18986
  `Wrote snapshot (${messages.length} messages) to ${options.out}`
18527
18987
  );
@@ -18544,7 +19004,7 @@ async function recoverConversation(workflowId, runId, options) {
18544
19004
  2
18545
19005
  ) + "\n" : renderTranscriptMarkdown(turns);
18546
19006
  if (options.out) {
18547
- writeFileSync21(options.out, output);
19007
+ writeFileSync23(options.out, output);
18548
19008
  logSuccess(`Wrote ${turns.length} turns to ${options.out}`);
18549
19009
  } else {
18550
19010
  process.stdout.write(output);
@@ -18579,10 +19039,10 @@ async function captureHistory(workflowId, runId, options) {
18579
19039
  }
18580
19040
  const { serializeHistoryToFixture: serializeHistoryToFixture2 } = await Promise.resolve().then(() => (init_capture_history(), capture_history_exports));
18581
19041
  const { fixture, eventCount, decryptedPayloads } = await serializeHistoryToFixture2(events, decrypter);
18582
- const outPath = options.out ?? join34(homedir9(), ".mesh", "replay-histories", `${sanitizeFileId(workflowId)}.json`);
18583
- mkdirSync22(dirname26(outPath), { recursive: true });
19042
+ const outPath = options.out ?? join35(homedir9(), ".mesh", "replay-histories", `${sanitizeFileId(workflowId)}.json`);
19043
+ mkdirSync22(dirname27(outPath), { recursive: true });
18584
19044
  if (options.out) warnIfNotGitIgnored(options.out);
18585
- writeFileSync21(outPath, JSON.stringify(fixture, null, 2) + "\n");
19045
+ writeFileSync23(outPath, JSON.stringify(fixture, null, 2) + "\n");
18586
19046
  logSuccess(
18587
19047
  `Wrote replay history (${eventCount} events, ${decryptedPayloads} payloads decrypted) to ${outPath}`
18588
19048
  );
@@ -18597,9 +19057,9 @@ function sanitizeFileId(id) {
18597
19057
  return id.replace(/[^A-Za-z0-9._-]/g, "_");
18598
19058
  }
18599
19059
  function warnIfNotGitIgnored(outPath) {
18600
- const abs = resolve13(outPath);
19060
+ const abs = resolve14(outPath);
18601
19061
  try {
18602
- const res = spawnSync4("git", ["-C", dirname26(abs), "check-ignore", "-q", abs], {
19062
+ const res = spawnSync4("git", ["-C", dirname27(abs), "check-ignore", "-q", abs], {
18603
19063
  stdio: "ignore"
18604
19064
  });
18605
19065
  if (res.status !== 1) return;
@@ -18767,8 +19227,8 @@ var init_temporal = __esm({
18767
19227
 
18768
19228
  // libs/mesh-cli/src/commands/tenant.ts
18769
19229
  import chalk6 from "chalk";
18770
- import * as fs31 from "fs";
18771
- import * as path38 from "path";
19230
+ import * as fs32 from "fs";
19231
+ import * as path39 from "path";
18772
19232
  import { parseDocument, YAMLMap, isMap } from "yaml";
18773
19233
  function validateTenantName(name) {
18774
19234
  return /^[a-z][a-z0-9-]*$/.test(name) && !name.endsWith("-");
@@ -18836,16 +19296,16 @@ function resolveStackConfig(explicitStack) {
18836
19296
  { remediation: { command: "mesh tenant add <name> --stack <stack>" } }
18837
19297
  );
18838
19298
  }
18839
- const file = path38.join(appRoot, `Pulumi.${stack}.yaml`);
18840
- if (!fs31.existsSync(file)) {
19299
+ const file = path39.join(appRoot, `Pulumi.${stack}.yaml`);
19300
+ if (!fs32.existsSync(file)) {
18841
19301
  throw new MeshCliError(`Stack config not found: ${file}`, {
18842
19302
  remediation: { command: "mesh tenant add <name> --stack <stack>" }
18843
19303
  });
18844
19304
  }
18845
- return { appRoot, stack, file, content: fs31.readFileSync(file, "utf-8") };
19305
+ return { appRoot, stack, file, content: fs32.readFileSync(file, "utf-8") };
18846
19306
  }
18847
19307
  function readProjectName(appRoot) {
18848
- 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"));
18849
19309
  return String(projectDoc.get("name") ?? "");
18850
19310
  }
18851
19311
  function looksLikePlatformLayer(projectName) {
@@ -18888,12 +19348,12 @@ function registerTenantCommands(program2) {
18888
19348
  if (!result.ok) {
18889
19349
  if (result.reason === "exists") {
18890
19350
  throw new MeshCliError(
18891
- `${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.`
18892
19352
  );
18893
19353
  }
18894
19354
  throw new MeshCliError(`${resolved.file}: ${result.detail}`);
18895
19355
  }
18896
- atomicWriteFileSync(resolved.file, result.yaml, fs31.statSync(resolved.file).mode & 511);
19356
+ atomicWriteFileSync(resolved.file, result.yaml, fs32.statSync(resolved.file).mode & 511);
18897
19357
  if (opts.json) {
18898
19358
  emitJsonPayload({
18899
19359
  ok: true,
@@ -18906,7 +19366,7 @@ function registerTenantCommands(program2) {
18906
19366
  return;
18907
19367
  }
18908
19368
  logInfo(
18909
- `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" : ""}`
18910
19370
  );
18911
19371
  console.log("");
18912
19372
  console.log("Next steps:");
@@ -18932,7 +19392,7 @@ function registerTenantCommands(program2) {
18932
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).`
18933
19393
  );
18934
19394
  }
18935
- logInfo(`No tenants declared in ${path38.basename(resolved.file)}.`);
19395
+ logInfo(`No tenants declared in ${path39.basename(resolved.file)}.`);
18936
19396
  return;
18937
19397
  }
18938
19398
  logInfo(`Tenants on stack '${resolved.stack}':`);
@@ -18954,9 +19414,9 @@ var init_tenant = __esm({
18954
19414
 
18955
19415
  // libs/mesh-cli/src/commands/tunnel/index.ts
18956
19416
  import { spawn as spawn9 } from "child_process";
18957
- import * as fs32 from "fs";
19417
+ import * as fs33 from "fs";
18958
19418
  import * as os13 from "os";
18959
- import * as path39 from "path";
19419
+ import * as path40 from "path";
18960
19420
  import {
18961
19421
  SecretsManagerClient as SecretsManagerClient8,
18962
19422
  GetSecretValueCommand as GetSecretValueCommand8
@@ -18976,9 +19436,9 @@ function readPulumiAwsRegion(stage) {
18976
19436
  const preferred = stage ? `Pulumi.${stage}.yaml` : void 0;
18977
19437
  const rank = (f) => f === preferred ? 0 : f === "Pulumi.yaml" ? 2 : 1;
18978
19438
  try {
18979
- 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));
18980
19440
  for (const f of files) {
18981
- 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);
18982
19442
  if (m?.[1]) return m[1].trim();
18983
19443
  }
18984
19444
  } catch {
@@ -18987,14 +19447,14 @@ function readPulumiAwsRegion(stage) {
18987
19447
  }
18988
19448
  function awsProfileSections(name) {
18989
19449
  return [
18990
- [path39.join(os13.homedir(), ".aws", "config"), `[profile ${name}]`],
18991
- [path39.join(os13.homedir(), ".aws", "credentials"), `[${name}]`]
19450
+ [path40.join(os13.homedir(), ".aws", "config"), `[profile ${name}]`],
19451
+ [path40.join(os13.homedir(), ".aws", "credentials"), `[${name}]`]
18992
19452
  ];
18993
19453
  }
18994
19454
  function awsProfileExists(name) {
18995
19455
  for (const [file, header] of awsProfileSections(name)) {
18996
19456
  try {
18997
- const lines = fs32.readFileSync(file, "utf-8").split("\n");
19457
+ const lines = fs33.readFileSync(file, "utf-8").split("\n");
18998
19458
  if (lines.some((l) => l.trim() === header)) return true;
18999
19459
  } catch {
19000
19460
  }
@@ -19020,7 +19480,7 @@ function awsProfileRegion(profile) {
19020
19480
  if (!profile) return void 0;
19021
19481
  for (const [file, header] of awsProfileSections(profile)) {
19022
19482
  try {
19023
- const region = parseProfileRegion(fs32.readFileSync(file, "utf-8"), header);
19483
+ const region = parseProfileRegion(fs33.readFileSync(file, "utf-8"), header);
19024
19484
  if (region) return region;
19025
19485
  } catch {
19026
19486
  }
@@ -19029,14 +19489,14 @@ function awsProfileRegion(profile) {
19029
19489
  }
19030
19490
  function awsDefaultProfileSections() {
19031
19491
  return [
19032
- [path39.join(os13.homedir(), ".aws", "config"), "[default]"],
19033
- [path39.join(os13.homedir(), ".aws", "credentials"), "[default]"]
19492
+ [path40.join(os13.homedir(), ".aws", "config"), "[default]"],
19493
+ [path40.join(os13.homedir(), ".aws", "credentials"), "[default]"]
19034
19494
  ];
19035
19495
  }
19036
19496
  function awsDefaultProfileRegion() {
19037
19497
  for (const [file, header] of awsDefaultProfileSections()) {
19038
19498
  try {
19039
- const region = parseProfileRegion(fs32.readFileSync(file, "utf-8"), header);
19499
+ const region = parseProfileRegion(fs33.readFileSync(file, "utf-8"), header);
19040
19500
  if (region) return region;
19041
19501
  } catch {
19042
19502
  }
@@ -19177,9 +19637,9 @@ async function tunnelServices(serviceNames, options) {
19177
19637
  process.on("SIGTERM", cleanup);
19178
19638
  await Promise.race(
19179
19639
  processes.map(
19180
- (proc) => new Promise((resolve15) => {
19181
- proc.on("exit", () => resolve15());
19182
- proc.on("error", () => resolve15());
19640
+ (proc) => new Promise((resolve16) => {
19641
+ proc.on("exit", () => resolve16());
19642
+ proc.on("error", () => resolve16());
19183
19643
  })
19184
19644
  )
19185
19645
  );
@@ -19276,9 +19736,9 @@ async function tunnelExternal(name, options) {
19276
19736
  };
19277
19737
  process.on("SIGINT", cleanup);
19278
19738
  process.on("SIGTERM", cleanup);
19279
- await new Promise((resolve15) => {
19280
- proc.on("exit", () => resolve15());
19281
- proc.on("error", () => resolve15());
19739
+ await new Promise((resolve16) => {
19740
+ proc.on("exit", () => resolve16());
19741
+ proc.on("error", () => resolve16());
19282
19742
  });
19283
19743
  cleanup();
19284
19744
  }
@@ -19405,8 +19865,8 @@ async function resolveToken(opts) {
19405
19865
  "no token: pass --token, set VCS_TOKEN, or pass --context <platform-context> (after mesh login)"
19406
19866
  );
19407
19867
  }
19408
- async function vcsApi(baseUrl, token, path41, method = "GET", body) {
19409
- const res = await fetch(`${baseUrl}${path41}`, {
19868
+ async function vcsApi(baseUrl, token, path42, method = "GET", body) {
19869
+ const res = await fetch(`${baseUrl}${path42}`, {
19410
19870
  method,
19411
19871
  headers: {
19412
19872
  authorization: `Bearer ${token}`,
@@ -19417,7 +19877,7 @@ async function vcsApi(baseUrl, token, path41, method = "GET", body) {
19417
19877
  const data = await res.json().catch(() => ({}));
19418
19878
  if (!res.ok) {
19419
19879
  throw new Error(
19420
- `${method} ${path41} failed (${res.status}): ${String(data.error ?? "unknown error")}`
19880
+ `${method} ${path42} failed (${res.status}): ${String(data.error ?? "unknown error")}`
19421
19881
  );
19422
19882
  }
19423
19883
  return data;
@@ -19460,8 +19920,8 @@ var init_clone = __esm({
19460
19920
 
19461
19921
  // libs/mesh-cli/src/commands/vcs/get.ts
19462
19922
  import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
19463
- import { join as join37, dirname as dirname27 } from "node:path";
19464
- async function getCommand(repo, path41, opts) {
19923
+ import { join as join38, dirname as dirname28 } from "node:path";
19924
+ async function getCommand(repo, path42, opts) {
19465
19925
  let vcsBaseUrl;
19466
19926
  let token;
19467
19927
  if (opts.target) {
@@ -19476,18 +19936,18 @@ async function getCommand(repo, path41, opts) {
19476
19936
  if (!token) throw new Error("no token: pass --target (after mesh login), or --token / --context");
19477
19937
  const reader = createVcsFolderReader({ vcsBaseUrl, token });
19478
19938
  try {
19479
- const { ref, files } = await reader.readPath(repo, path41);
19939
+ const { ref, files } = await reader.readPath(repo, path42);
19480
19940
  if (files.length === 0) {
19481
- logInfo(`no files at ${repo}:${path41}`);
19941
+ logInfo(`no files at ${repo}:${path42}`);
19482
19942
  return;
19483
19943
  }
19484
19944
  if (opts.output) {
19485
19945
  for (const f of files) {
19486
- const abs = join37(opts.output, f.path);
19487
- await mkdir2(dirname27(abs), { recursive: true });
19946
+ const abs = join38(opts.output, f.path);
19947
+ await mkdir2(dirname28(abs), { recursive: true });
19488
19948
  await writeFile2(abs, f.contents);
19489
19949
  }
19490
- 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}`);
19491
19951
  } else {
19492
19952
  for (const f of files) {
19493
19953
  if (files.length > 1) process.stdout.write(`
@@ -19512,10 +19972,10 @@ var init_get = __esm({
19512
19972
  });
19513
19973
 
19514
19974
  // libs/mesh-cli/src/commands/vcs/drafts.ts
19515
- async function call(opts, repo, path41, method = "GET", body) {
19975
+ async function call(opts, repo, path42, method = "GET", body) {
19516
19976
  const target = await resolveTarget2({ ...opts, repo });
19517
19977
  const token = await resolveToken(opts);
19518
- return vcsApi(target.baseUrl, token, `/v1/repos/${repo}${path41}`, method, body);
19978
+ return vcsApi(target.baseUrl, token, `/v1/repos/${repo}${path42}`, method, body);
19519
19979
  }
19520
19980
  async function draftsListCommand(repo, opts) {
19521
19981
  const data = await call(opts, repo, `/drafts${opts.all ? "?all=true" : ""}`);
@@ -19570,7 +20030,7 @@ var init_drafts = __esm({
19570
20030
 
19571
20031
  // libs/mesh-cli/src/commands/vcs/propose.ts
19572
20032
  import { readFile as readFile2 } from "node:fs/promises";
19573
- import { join as join38 } from "node:path";
20033
+ import { join as join39 } from "node:path";
19574
20034
  function parseStatus(out) {
19575
20035
  const tokens = out.split("\0");
19576
20036
  const changes = [];
@@ -19625,7 +20085,7 @@ async function proposeCommand(opts) {
19625
20085
  async (c) => c.status.startsWith("D") ? { op: "delete", path: c.path } : {
19626
20086
  op: "write",
19627
20087
  path: c.path,
19628
- content: await readFile2(join38(cwd, c.path), "utf8")
20088
+ content: await readFile2(join39(cwd, c.path), "utf8")
19629
20089
  }
19630
20090
  )
19631
20091
  );
@@ -19649,8 +20109,8 @@ async function proposeCommand(opts) {
19649
20109
  operations,
19650
20110
  ...opts.mergeParent ? { mergeParent: opts.mergeParent } : {}
19651
20111
  };
19652
- const path41 = opts.revise ? `/v1/repos/${target.repo}/proposals/${opts.revise}/revisions` : `/v1/repos/${target.repo}/proposals`;
19653
- 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);
19654
20114
  console.log(
19655
20115
  `proposal ${data.proposalId} @ ${data.sha.slice(0, 8)} \u2014 review: mesh vcs show ${target.repo} ${data.proposalId} --url ${target.baseUrl}`
19656
20116
  );
@@ -19663,10 +20123,10 @@ var init_propose = __esm({
19663
20123
  });
19664
20124
 
19665
20125
  // libs/mesh-cli/src/commands/vcs/review.ts
19666
- async function call2(opts, repo, path41, method = "GET", body) {
20126
+ async function call2(opts, repo, path42, method = "GET", body) {
19667
20127
  const target = await resolveTarget2({ ...opts, repo });
19668
20128
  const token = await resolveToken(opts);
19669
- return vcsApi(target.baseUrl, token, `/v1/repos/${repo}${path41}`, method, body);
20129
+ return vcsApi(target.baseUrl, token, `/v1/repos/${repo}${path42}`, method, body);
19670
20130
  }
19671
20131
  async function proposalsCommand(repo, opts) {
19672
20132
  console.log(JSON.stringify(await call2(opts, repo, "/proposals"), null, 2));
@@ -19807,8 +20267,8 @@ function computeHappyPath(nodes, edges) {
19807
20267
  const queue = [{ id: startNode.id, path: [startNode.id] }];
19808
20268
  const visited = /* @__PURE__ */ new Set([startNode.id]);
19809
20269
  while (queue.length > 0) {
19810
- const { id, path: path41 } = queue.shift();
19811
- if (targetIds.has(id)) return path41;
20270
+ const { id, path: path42 } = queue.shift();
20271
+ if (targetIds.has(id)) return path42;
19812
20272
  const neighbors = adjacency.get(id) ?? [];
19813
20273
  const hasMainEdge = neighbors.some((e) => e.isMainPath);
19814
20274
  const candidates = hasMainEdge ? neighbors.filter((e) => e.isMainPath) : neighbors;
@@ -19827,7 +20287,7 @@ function computeHappyPath(nodes, edges) {
19827
20287
  if (visited.has(neighbor.to)) continue;
19828
20288
  if (!allowExceptional && (neighbor.isExceptional || neighbor.isTimeout || failureEndIds.has(neighbor.to))) continue;
19829
20289
  visited.add(neighbor.to);
19830
- queue.push({ id: neighbor.to, path: [...path41, neighbor.to] });
20290
+ queue.push({ id: neighbor.to, path: [...path42, neighbor.to] });
19831
20291
  }
19832
20292
  }
19833
20293
  return null;
@@ -20075,11 +20535,11 @@ function wrapIntoGroup(workflow, nodeIds, groupId, label, groupType) {
20075
20535
  }
20076
20536
  }
20077
20537
  if (found) {
20078
- const path41 = [];
20079
- for (let c = found; c !== void 0; c = prev.get(c)) path41.unshift(c);
20080
- 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]));
20081
20541
  mainEdgeKeys.add(edgeKey(found, successExit.id));
20082
- mainBody = path41;
20542
+ mainBody = path42;
20083
20543
  mainTail = [successExit.id];
20084
20544
  }
20085
20545
  }
@@ -20481,11 +20941,11 @@ var init_apply_preview_patch = __esm({
20481
20941
 
20482
20942
  // libs/workflow-model/src/process-artifact.ts
20483
20943
  import { z as z4 } from "zod";
20484
- function formatPath(path41) {
20485
- return path41.length > 0 ? z4.core.toDotPath(path41) : "(root)";
20944
+ function formatPath(path42) {
20945
+ return path42.length > 0 ? z4.core.toDotPath(path42) : "(root)";
20486
20946
  }
20487
20947
  function issueToDiagnostic(issue) {
20488
- const path41 = formatPath(issue.path);
20948
+ const path42 = formatPath(issue.path);
20489
20949
  let code = "SCHEMA_INVALID";
20490
20950
  if (issue.code === "custom") {
20491
20951
  const paramCode = issue.params?.code;
@@ -20496,8 +20956,8 @@ function issueToDiagnostic(issue) {
20496
20956
  return {
20497
20957
  severity: "error",
20498
20958
  code,
20499
- message: `${path41}: ${issue.message}`,
20500
- path: path41
20959
+ message: `${path42}: ${issue.message}`,
20960
+ path: path42
20501
20961
  };
20502
20962
  }
20503
20963
  function parseProcessArtifact(json) {
@@ -20577,20 +21037,20 @@ var init_process_artifact = __esm({
20577
21037
  outcome: "DUPLICATE_OUTCOME_ID"
20578
21038
  };
20579
21039
  const idFirstSeenAt = /* @__PURE__ */ new Map();
20580
- const checkId = (id, kind, path41) => {
21040
+ const checkId = (id, kind, path42) => {
20581
21041
  const first = idFirstSeenAt.get(id);
20582
21042
  if (first) {
20583
21043
  const kindLabel = kind === first.kind ? kind : "process";
20584
21044
  ctx.addIssue({
20585
21045
  code: "custom",
20586
- path: path41,
20587
- 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)}.`,
20588
21048
  params: {
20589
21049
  code: kind === first.kind ? SAME_KIND_CODE[kind] : "DUPLICATE_PROCESS_ID"
20590
21050
  }
20591
21051
  });
20592
21052
  } else {
20593
- idFirstSeenAt.set(id, { kind, path: path41 });
21053
+ idFirstSeenAt.set(id, { kind, path: path42 });
20594
21054
  }
20595
21055
  };
20596
21056
  process2.stages.forEach((stage, stageIndex) => {
@@ -20628,13 +21088,13 @@ function lintProcess(artifact, inventory, predicateNames) {
20628
21088
  const internalCommands = process2.internalCommands ?? [];
20629
21089
  const internalSet = new Set(internalCommands);
20630
21090
  const boundCommandPaths = /* @__PURE__ */ new Map();
20631
- const checkPredicate = (predicate, path41) => {
21091
+ const checkPredicate = (predicate, path42) => {
20632
21092
  if (!predicateSet.has(predicate)) {
20633
21093
  diagnostics.push({
20634
21094
  severity: "error",
20635
21095
  code: "UNKNOWN_PREDICATE",
20636
- message: `Predicate "${predicate}" referenced at ${path41} is not in the predicate registry.`,
20637
- path: path41
21096
+ message: `Predicate "${predicate}" referenced at ${path42} is not in the predicate registry.`,
21097
+ path: path42
20638
21098
  });
20639
21099
  }
20640
21100
  };
@@ -20775,8 +21235,8 @@ var init_src3 = __esm({
20775
21235
  });
20776
21236
 
20777
21237
  // libs/mesh-cli/src/commands/workflow.ts
20778
- import * as fs33 from "fs";
20779
- import * as path40 from "path";
21238
+ import * as fs34 from "fs";
21239
+ import * as path41 from "path";
20780
21240
  import { createRequire as createRequire2 } from "module";
20781
21241
  import { execFileSync as execFileSync29 } from "child_process";
20782
21242
  function resolveExtractorPath() {
@@ -20792,7 +21252,7 @@ function runExtraction(targetPath, extractorPath, explicitProcessPath) {
20792
21252
  let explicitProcessArtifact;
20793
21253
  if (explicitProcessPath) {
20794
21254
  try {
20795
- explicitProcessArtifact = JSON.parse(fs33.readFileSync(explicitProcessPath, "utf-8"));
21255
+ explicitProcessArtifact = JSON.parse(fs34.readFileSync(explicitProcessPath, "utf-8"));
20796
21256
  } catch (err) {
20797
21257
  logError(
20798
21258
  `Could not read/parse --process ${explicitProcessPath}: ${err instanceof Error ? err.message : String(err)}`
@@ -20892,7 +21352,7 @@ function runLintExtraction(targetPath, extractorPath, explicitProcessPath) {
20892
21352
  let explicitProcessArtifact;
20893
21353
  if (explicitProcessPath) {
20894
21354
  try {
20895
- explicitProcessArtifact = JSON.parse(fs33.readFileSync(explicitProcessPath, "utf-8"));
21355
+ explicitProcessArtifact = JSON.parse(fs34.readFileSync(explicitProcessPath, "utf-8"));
20896
21356
  } catch (err) {
20897
21357
  logError(
20898
21358
  `Could not read/parse --process ${explicitProcessPath}: ${err instanceof Error ? err.message : String(err)}`
@@ -20997,16 +21457,16 @@ function registerWorkflowCommands(program2) {
20997
21457
  ).action(
20998
21458
  async (targetPath, opts) => {
20999
21459
  try {
21000
- const resolvedPath = path40.resolve(targetPath);
21001
- if (!fs33.existsSync(resolvedPath)) {
21460
+ const resolvedPath = path41.resolve(targetPath);
21461
+ if (!fs34.existsSync(resolvedPath)) {
21002
21462
  logError(`Path does not exist: ${resolvedPath}`);
21003
21463
  process.exitCode = 1;
21004
21464
  return;
21005
21465
  }
21006
21466
  let resolvedProcessPath;
21007
21467
  if (opts.process) {
21008
- resolvedProcessPath = path40.resolve(opts.process);
21009
- if (!fs33.existsSync(resolvedProcessPath)) {
21468
+ resolvedProcessPath = path41.resolve(opts.process);
21469
+ if (!fs34.existsSync(resolvedProcessPath)) {
21010
21470
  logError(`--process path does not exist: ${resolvedProcessPath}`);
21011
21471
  process.exitCode = 1;
21012
21472
  return;
@@ -21055,8 +21515,8 @@ function registerWorkflowCommands(program2) {
21055
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."
21056
21516
  ).action(async (targetPath) => {
21057
21517
  try {
21058
- const resolvedPath = path40.resolve(targetPath);
21059
- if (!fs33.existsSync(resolvedPath)) {
21518
+ const resolvedPath = path41.resolve(targetPath);
21519
+ if (!fs34.existsSync(resolvedPath)) {
21060
21520
  logError(`Path does not exist: ${resolvedPath}`);
21061
21521
  process.exitCode = 1;
21062
21522
  return;
@@ -21099,16 +21559,16 @@ function registerWorkflowCommands(program2) {
21099
21559
  "Comma-separated named-predicate registry (enables UNKNOWN_PREDICATE checks)"
21100
21560
  ).action(async (targetPath, opts) => {
21101
21561
  try {
21102
- const resolvedPath = path40.resolve(targetPath);
21103
- if (!fs33.existsSync(resolvedPath)) {
21562
+ const resolvedPath = path41.resolve(targetPath);
21563
+ if (!fs34.existsSync(resolvedPath)) {
21104
21564
  logError(`Path does not exist: ${resolvedPath}`);
21105
21565
  process.exitCode = 1;
21106
21566
  return;
21107
21567
  }
21108
21568
  let resolvedProcessPath;
21109
21569
  if (opts.process) {
21110
- resolvedProcessPath = path40.resolve(opts.process);
21111
- if (!fs33.existsSync(resolvedProcessPath)) {
21570
+ resolvedProcessPath = path41.resolve(opts.process);
21571
+ if (!fs34.existsSync(resolvedProcessPath)) {
21112
21572
  logError(`--process path does not exist: ${resolvedProcessPath}`);
21113
21573
  process.exitCode = 1;
21114
21574
  return;