@mesh-tech/mesh-cli 0.44.0 → 0.45.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 (60) hide show
  1. package/dist/bin/{mesh.auth-preflight-JTUNS42O.js → mesh.auth-preflight-3ASNYA3I.js} +1 -1
  2. package/dist/bin/{mesh.chunk-QE5LUIHN.js → mesh.chunk-252LTIHZ.js} +2 -2
  3. package/dist/bin/{mesh.chunk-TYM27CO3.js → mesh.chunk-4Y3JGAYZ.js} +1 -1
  4. package/dist/bin/{mesh.chunk-OQA2UBOD.js → mesh.chunk-6OGUV2DU.js} +1 -1
  5. package/dist/bin/{mesh.chunk-XOCERV6F.js → mesh.chunk-G4TCVQLL.js} +556 -110
  6. package/dist/bin/mesh.chunk-G4TCVQLL.js.map +6 -0
  7. package/dist/bin/{mesh.chunk-ZNM3DDAM.js → mesh.chunk-HPLMVIBO.js} +1 -1
  8. package/dist/bin/{mesh.chunk-QUKWWFZI.js → mesh.chunk-JSY6THRF.js} +1 -1
  9. package/dist/bin/{mesh.chunk-PANTMVC3.js → mesh.chunk-NN3LTNUM.js} +3 -3
  10. package/dist/bin/{mesh.chunk-HB2IZKLP.js → mesh.chunk-SFIJZF3S.js} +786 -276
  11. package/dist/bin/{mesh.chunk-HB2IZKLP.js.map → mesh.chunk-SFIJZF3S.js.map} +3 -3
  12. package/dist/bin/{mesh.chunk-HC7V75J3.js → mesh.chunk-UJAPUO2W.js} +1 -1
  13. package/dist/bin/{mesh.create-app-DVMZDEQP.js → mesh.create-app-BHB3BGH7.js} +3 -3
  14. package/dist/bin/{mesh.dev-token-server-4N6HGQAF.js → mesh.dev-token-server-6TNWX5TY.js} +2 -2
  15. package/dist/bin/mesh.js +9 -9
  16. package/dist/bin/{mesh.login-Q6R5GMVM.js → mesh.login-4O5PMS25.js} +2 -2
  17. package/dist/bin/{mesh.platform-setup-YK6KSOP7.js → mesh.platform-setup-MTY6ICAO.js} +1 -1
  18. package/dist/bin/{mesh.program-NOBVA3JS.js → mesh.program-CMOLQWZI.js} +9 -9
  19. package/dist/bin/{mesh.registry-KA3CNNPZ.js → mesh.registry-BDBH6DRP.js} +4 -4
  20. package/dist/bin/{mesh.registry-identity-DSTAHXUS.js → mesh.registry-identity-QOGDEMZO.js} +3 -3
  21. package/dist/bin/{mesh.review-command-3ZXPS6Y3.js → mesh.review-command-E7V2L5AM.js} +3 -3
  22. package/dist/bin/{mesh.start-JDSYIRZI.js → mesh.start-3MUKXN2U.js} +2 -2
  23. package/dist/bin/{mesh.wizard-QGWPV4XN.js → mesh.wizard-EH3SQEHW.js} +7 -7
  24. package/dist/build-info.json +2 -2
  25. package/dist/src/commands/dev.d.ts +1 -0
  26. package/dist/src/commands/dev.js +57 -1
  27. package/dist/src/commands/temporal.js +146 -42
  28. package/dist/src/commands/temporal.js.map +3 -3
  29. package/dist/src/commands/workstation.d.ts +3 -0
  30. package/dist/src/commands/workstation.js +395 -0
  31. package/dist/src/program.js +2 -0
  32. package/dist/src/utils/temporal-inspect.d.ts +17 -0
  33. package/dist/src/utils/temporal-inspect.js +71 -0
  34. package/dist/src/utils/workstation-machine.d.ts +54 -0
  35. package/dist/src/utils/workstation-machine.js +282 -0
  36. package/dist/src/utils/workstation-routes.d.ts +77 -0
  37. package/dist/src/utils/workstation-routes.js +180 -0
  38. package/fragments/base/README.md.hbs +3 -0
  39. package/package.json +2 -2
  40. package/skills/mesh/SKILL.md +3 -2
  41. package/skills/mesh/references/worktrees.md +4 -1
  42. package/dist/bin/mesh.chunk-XOCERV6F.js.map +0 -6
  43. /package/dist/bin/{mesh.auth-preflight-JTUNS42O.js.map → mesh.auth-preflight-3ASNYA3I.js.map} +0 -0
  44. /package/dist/bin/{mesh.chunk-QE5LUIHN.js.map → mesh.chunk-252LTIHZ.js.map} +0 -0
  45. /package/dist/bin/{mesh.chunk-TYM27CO3.js.map → mesh.chunk-4Y3JGAYZ.js.map} +0 -0
  46. /package/dist/bin/{mesh.chunk-OQA2UBOD.js.map → mesh.chunk-6OGUV2DU.js.map} +0 -0
  47. /package/dist/bin/{mesh.chunk-ZNM3DDAM.js.map → mesh.chunk-HPLMVIBO.js.map} +0 -0
  48. /package/dist/bin/{mesh.chunk-QUKWWFZI.js.map → mesh.chunk-JSY6THRF.js.map} +0 -0
  49. /package/dist/bin/{mesh.chunk-PANTMVC3.js.map → mesh.chunk-NN3LTNUM.js.map} +0 -0
  50. /package/dist/bin/{mesh.chunk-HC7V75J3.js.map → mesh.chunk-UJAPUO2W.js.map} +0 -0
  51. /package/dist/bin/{mesh.create-app-DVMZDEQP.js.map → mesh.create-app-BHB3BGH7.js.map} +0 -0
  52. /package/dist/bin/{mesh.dev-token-server-4N6HGQAF.js.map → mesh.dev-token-server-6TNWX5TY.js.map} +0 -0
  53. /package/dist/bin/{mesh.login-Q6R5GMVM.js.map → mesh.login-4O5PMS25.js.map} +0 -0
  54. /package/dist/bin/{mesh.platform-setup-YK6KSOP7.js.map → mesh.platform-setup-MTY6ICAO.js.map} +0 -0
  55. /package/dist/bin/{mesh.program-NOBVA3JS.js.map → mesh.program-CMOLQWZI.js.map} +0 -0
  56. /package/dist/bin/{mesh.registry-KA3CNNPZ.js.map → mesh.registry-BDBH6DRP.js.map} +0 -0
  57. /package/dist/bin/{mesh.registry-identity-DSTAHXUS.js.map → mesh.registry-identity-QOGDEMZO.js.map} +0 -0
  58. /package/dist/bin/{mesh.review-command-3ZXPS6Y3.js.map → mesh.review-command-E7V2L5AM.js.map} +0 -0
  59. /package/dist/bin/{mesh.start-JDSYIRZI.js.map → mesh.start-3MUKXN2U.js.map} +0 -0
  60. /package/dist/bin/{mesh.wizard-QGWPV4XN.js.map → mesh.wizard-EH3SQEHW.js.map} +0 -0
@@ -49,14 +49,14 @@ import {
49
49
  isExpiredAwsTokenMessage,
50
50
  probeRegistryToken,
51
51
  registryLoginFix
52
- } from "./mesh.chunk-HC7V75J3.js";
52
+ } from "./mesh.chunk-UJAPUO2W.js";
53
53
 
54
54
  // packages/mesh-cli/src/commands/login.ts
55
55
  import * as http from "http";
56
56
  import * as crypto3 from "crypto";
57
- import * as fs21 from "fs";
58
- import * as path22 from "path";
59
- import { execFileSync as execFileSync16 } from "child_process";
57
+ import * as fs23 from "fs";
58
+ import * as path24 from "path";
59
+ import { execFileSync as execFileSync17 } from "child_process";
60
60
 
61
61
  // packages/mesh-cli/src/utils/aws-auth.ts
62
62
  import * as fs2 from "fs";
@@ -263,8 +263,8 @@ ${body}${MANAGED_END}
263
263
  return existing.slice(0, startIdx) + section + existing.slice(tail);
264
264
  }
265
265
  if (existing.length === 0) return section;
266
- const sep3 = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
267
- return existing + sep3 + section;
266
+ const sep4 = existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
267
+ return existing + sep4 + section;
268
268
  }
269
269
  function stripBareProfile(existing, profileName) {
270
270
  const header = `[profile ${profileName}]`;
@@ -566,7 +566,7 @@ async function resolveAwsCredentials(roleArn, appRoot, stack) {
566
566
  }
567
567
 
568
568
  // packages/mesh-cli/src/commands/vpn/index.ts
569
- import { execFileSync as execFileSync15 } from "child_process";
569
+ import { execFileSync as execFileSync16 } from "child_process";
570
570
  import * as net8 from "node:net";
571
571
 
572
572
  // packages/mesh-cli/src/utils/context.ts
@@ -1093,7 +1093,7 @@ function execTailscaleCmd(args) {
1093
1093
  }
1094
1094
  attempts.push({ binary, args });
1095
1095
  }
1096
- return new Promise((resolve9, reject) => {
1096
+ return new Promise((resolve10, reject) => {
1097
1097
  let index = 0;
1098
1098
  function tryNext() {
1099
1099
  if (index >= attempts.length) {
@@ -1105,7 +1105,7 @@ function execTailscaleCmd(args) {
1105
1105
  if (error) {
1106
1106
  tryNext();
1107
1107
  } else {
1108
- resolve9(stdout);
1108
+ resolve10(stdout);
1109
1109
  }
1110
1110
  });
1111
1111
  }
@@ -1198,11 +1198,11 @@ function unregisterDnsRecords(cfg, suffix) {
1198
1198
  import * as net7 from "node:net";
1199
1199
 
1200
1200
  // packages/mesh-cli/src/utils/tailscale.ts
1201
- import * as fs20 from "node:fs";
1201
+ import * as fs22 from "node:fs";
1202
1202
  import * as net6 from "node:net";
1203
- import * as os3 from "node:os";
1204
- import * as path21 from "node:path";
1205
- import { spawn as spawn2, execFileSync as execFileSync14 } from "node:child_process";
1203
+ import * as os5 from "node:os";
1204
+ import * as path23 from "node:path";
1205
+ import { spawn as spawn2, execFileSync as execFileSync15 } from "node:child_process";
1206
1206
 
1207
1207
  // packages/mesh-cli/src/utils/pid.ts
1208
1208
  var MIN_SIGNALABLE_PID = 2;
@@ -1240,16 +1240,16 @@ function assertSignalablePid(pid, source) {
1240
1240
  }
1241
1241
 
1242
1242
  // packages/mesh-cli/src/utils/tailscale-targets.ts
1243
- import { execFileSync as execFileSync12 } from "node:child_process";
1244
- import * as fs18 from "node:fs";
1245
- import * as path19 from "node:path";
1243
+ import { execFileSync as execFileSync13 } from "node:child_process";
1244
+ import * as fs20 from "node:fs";
1245
+ import * as path21 from "node:path";
1246
1246
 
1247
1247
  // packages/mesh-cli/src/commands/dev.ts
1248
- import { execFileSync as execFileSync11, spawn, spawnSync as spawnSync2 } from "child_process";
1248
+ import { execFileSync as execFileSync12, spawn, spawnSync as spawnSync2 } from "child_process";
1249
1249
  import { createHash as createHash5 } from "crypto";
1250
- import * as fs17 from "fs";
1250
+ import * as fs19 from "fs";
1251
1251
  import * as net3 from "net";
1252
- import * as path18 from "path";
1252
+ import * as path20 from "path";
1253
1253
  import { Option } from "commander";
1254
1254
  import { SecretsManagerClient as SecretsManagerClient2, GetSecretValueCommand as GetSecretValueCommand2 } from "@aws-sdk/client-secrets-manager";
1255
1255
 
@@ -1615,14 +1615,14 @@ async function acquireTemporalBearer(vars = {}) {
1615
1615
  // packages/mesh-cli/src/utils/reachability.ts
1616
1616
  import { createConnection } from "node:net";
1617
1617
  function probeTcpReachable(host, port, timeoutMs = 1500) {
1618
- return new Promise((resolve9) => {
1618
+ return new Promise((resolve10) => {
1619
1619
  let settled = false;
1620
1620
  const socket = createConnection({ host, port });
1621
1621
  const finish = (ok) => {
1622
1622
  if (settled) return;
1623
1623
  settled = true;
1624
1624
  socket.destroy();
1625
- resolve9(ok);
1625
+ resolve10(ok);
1626
1626
  };
1627
1627
  socket.setTimeout(timeoutMs);
1628
1628
  socket.once("connect", () => finish(true));
@@ -1631,7 +1631,7 @@ function probeTcpReachable(host, port, timeoutMs = 1500) {
1631
1631
  });
1632
1632
  }
1633
1633
  function probeConnectionHolds(host, port, timeoutMs = 800, holdMs = 25) {
1634
- return new Promise((resolve9) => {
1634
+ return new Promise((resolve10) => {
1635
1635
  let settled = false;
1636
1636
  let holdTimer;
1637
1637
  const socket = createConnection({ host, port });
@@ -1640,7 +1640,7 @@ function probeConnectionHolds(host, port, timeoutMs = 800, holdMs = 25) {
1640
1640
  settled = true;
1641
1641
  if (holdTimer) clearTimeout(holdTimer);
1642
1642
  socket.destroy();
1643
- resolve9(ok);
1643
+ resolve10(ok);
1644
1644
  };
1645
1645
  socket.setTimeout(timeoutMs);
1646
1646
  socket.once("connect", () => {
@@ -1915,16 +1915,16 @@ async function waitForPort(host, port, timeoutMs, intervalMs = 500) {
1915
1915
  const attemptTimeout = Math.max(250, Math.min(1e3, remaining));
1916
1916
  if (await tryConnect(host, port, attemptTimeout)) return true;
1917
1917
  if (Date.now() + intervalMs >= deadline) return false;
1918
- await new Promise((resolve9) => setTimeout(resolve9, intervalMs));
1918
+ await new Promise((resolve10) => setTimeout(resolve10, intervalMs));
1919
1919
  }
1920
1920
  }
1921
1921
  function tryConnect(host, port, timeoutMs) {
1922
- return new Promise((resolve9) => {
1922
+ return new Promise((resolve10) => {
1923
1923
  const socket = net.connect({ host, port });
1924
1924
  const done = (ok) => {
1925
1925
  socket.removeAllListeners();
1926
1926
  socket.destroy();
1927
- resolve9(ok);
1927
+ resolve10(ok);
1928
1928
  };
1929
1929
  socket.setTimeout(timeoutMs);
1930
1930
  socket.once("connect", () => done(true));
@@ -3146,7 +3146,7 @@ async function fetchMockSamlMetadata() {
3146
3146
  if (cause instanceof MeshCliError) throw cause;
3147
3147
  lastError = String(cause);
3148
3148
  }
3149
- await new Promise((resolve9) => setTimeout(resolve9, 2e3));
3149
+ await new Promise((resolve10) => setTimeout(resolve10, 2e3));
3150
3150
  }
3151
3151
  throw new MeshCliError(`Could not read mock SAML metadata from ${url} (${lastError}).`, {
3152
3152
  remediation: { command: "mesh status # is mock-saml running?" }
@@ -3834,7 +3834,7 @@ function readLocalAuthzConfig(appRoot) {
3834
3834
  return null;
3835
3835
  }
3836
3836
  }
3837
- var slug = (name, sep3) => name.toLowerCase().replace(/[^a-z0-9]+/g, sep3).replace(new RegExp(`^\\${sep3}+|\\${sep3}+$`, "g"), "");
3837
+ var slug = (name, sep4) => name.toLowerCase().replace(/[^a-z0-9]+/g, sep4).replace(new RegExp(`^\\${sep4}+|\\${sep4}+$`, "g"), "");
3838
3838
  function appSpiceDBSlot(app) {
3839
3839
  return createHash2("sha256").update(app).digest().readUInt32BE(0) % PORT_SLOTS;
3840
3840
  }
@@ -4288,14 +4288,14 @@ function zedOf(value) {
4288
4288
  if (typeof zed !== "string" || !zed.trim()) throw new Error("the spicedbSchema export returned no zed schema");
4289
4289
  return zed;
4290
4290
  }
4291
- async function spicedbHttp(path23, body) {
4292
- const res = await fetch(`${HOST_HTTP}${path23}`, {
4291
+ async function spicedbHttp(path25, body) {
4292
+ const res = await fetch(`${HOST_HTTP}${path25}`, {
4293
4293
  method: "POST",
4294
4294
  headers: { Authorization: `Bearer ${LOCAL_SPICEDB_KEY}`, "Content-Type": "application/json" },
4295
4295
  body: JSON.stringify(body)
4296
4296
  });
4297
4297
  const parsed = await res.json().catch(() => ({}));
4298
- if (!res.ok) throw Object.assign(new Error(`SpiceDB ${path23}: ${res.status} ${parsed.message ?? ""}`.trim()), { status: res.status });
4298
+ if (!res.ok) throw Object.assign(new Error(`SpiceDB ${path25}: ${res.status} ${parsed.message ?? ""}`.trim()), { status: res.status });
4299
4299
  return parsed;
4300
4300
  }
4301
4301
  async function readLocalSpicedbSchema() {
@@ -5622,14 +5622,14 @@ var worktreeCheck = {
5622
5622
  }
5623
5623
  };
5624
5624
  function canConnect(host, port, timeoutMs = 800) {
5625
- return new Promise((resolve9) => {
5625
+ return new Promise((resolve10) => {
5626
5626
  const socket = new net2.Socket();
5627
5627
  let settled = false;
5628
5628
  const done = (ok) => {
5629
5629
  if (settled) return;
5630
5630
  settled = true;
5631
5631
  socket.destroy();
5632
- resolve9(ok);
5632
+ resolve10(ok);
5633
5633
  };
5634
5634
  socket.setTimeout(timeoutMs);
5635
5635
  socket.once("connect", () => done(true));
@@ -5896,6 +5896,438 @@ function resolveWorktreeIdentity(cwd = process.cwd(), gitRunner = defaultGitRunn
5896
5896
  };
5897
5897
  }
5898
5898
 
5899
+ // packages/mesh-cli/src/utils/workstation-machine.ts
5900
+ import { execFileSync as execFileSync11 } from "node:child_process";
5901
+ import { X509Certificate } from "node:crypto";
5902
+ import * as fs18 from "node:fs";
5903
+ import * as os4 from "node:os";
5904
+ import * as path19 from "node:path";
5905
+
5906
+ // packages/mesh-cli/src/utils/workstation-routes.ts
5907
+ import * as fs17 from "node:fs";
5908
+ import * as os3 from "node:os";
5909
+ import * as path18 from "node:path";
5910
+ var LABEL_MAX = 63;
5911
+ var SESSION_MARKER = "# mesh dev session: ";
5912
+ function workstationDir() {
5913
+ return process.env.MESH_WORKSTATION_DIR || path18.join(os3.homedir(), ".config", "mesh", "workstation");
5914
+ }
5915
+ function readWorkstationIdentity(dir = workstationDir()) {
5916
+ const file = path18.join(dir, "workstation.json");
5917
+ let raw;
5918
+ try {
5919
+ raw = fs17.readFileSync(file, "utf8");
5920
+ } catch {
5921
+ return void 0;
5922
+ }
5923
+ const identity = JSON.parse(raw);
5924
+ if (typeof identity.name !== "string" || typeof identity.domain !== "string" || !identity.domain.startsWith(`${identity.name}.`)) {
5925
+ throw new Error(`${file}: needs a name and a domain under it, e.g. {"name":"mbp","domain":"mbp.example.com"}`);
5926
+ }
5927
+ return identity;
5928
+ }
5929
+ function toLabel(raw) {
5930
+ return raw.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
5931
+ }
5932
+ function serviceRouteLabel(args) {
5933
+ if (args.route === false) return void 0;
5934
+ const named = args.service === args.app || args.service.startsWith(`${args.app}-`);
5935
+ return toLabel(args.route ?? (named ? args.service : `${args.app}-${args.service}`)) || void 0;
5936
+ }
5937
+ function worktreeRouteSuffix(wt, explicit) {
5938
+ if (explicit !== void 0) return toLabel(explicit);
5939
+ if (wt.isPrimary) return "";
5940
+ const short = wt.slug.replace(/^(feature|feat|fix|chore|bugfix|hotfix|refactor|lab|spike)-/, "").replace(/(-[0-9a-f]{8,})+$/, "");
5941
+ return toLabel(short) || toLabel(wt.slug);
5942
+ }
5943
+ function sessionRouteLabel(label, suffix) {
5944
+ if (!suffix) return label.slice(0, LABEL_MAX).replace(/-+$/, "");
5945
+ const room = LABEL_MAX - label.length - 1;
5946
+ if (room < 1) return label.slice(0, LABEL_MAX).replace(/-+$/, "");
5947
+ return `${label}-${suffix.slice(0, room).replace(/-+$/, "")}`;
5948
+ }
5949
+ function planSessionRoutes(args) {
5950
+ const routes = [];
5951
+ for (const [service, s] of Object.entries(args.services)) {
5952
+ if (!s.port || !s.command?.length) continue;
5953
+ const label = serviceRouteLabel({ app: args.app, service, route: s.route });
5954
+ if (!label) continue;
5955
+ routes.push({ service, hostname: `${sessionRouteLabel(label, args.suffix)}.${args.identity.domain}`, port: s.port });
5956
+ }
5957
+ return routes;
5958
+ }
5959
+ function renderRouteFile(route) {
5960
+ const matcher = `route-${route.hostname.replaceAll(".", "_")}`;
5961
+ return [
5962
+ route.session ? `${SESSION_MARKER}${route.session}` : "# Written by WorkstationRoute (Pulumi). Removed on destroy; edits are overwritten on up.",
5963
+ `@${matcher} host ${route.hostname}`,
5964
+ `handle @${matcher} {`,
5965
+ ` reverse_proxy 127.0.0.1:${route.port}`,
5966
+ "}",
5967
+ ""
5968
+ ].join("\n");
5969
+ }
5970
+ function parseRouteFile(text) {
5971
+ const hostname2 = /^@\S+ host (\S+)$/m.exec(text)?.[1];
5972
+ const port = Number(/reverse_proxy 127\.0\.0\.1:(\d+)/.exec(text)?.[1]);
5973
+ if (!hostname2 || !port) return void 0;
5974
+ const session = text.split("\n").find((l) => l.startsWith(SESSION_MARKER))?.slice(SESSION_MARKER.length).trim();
5975
+ return { hostname: hostname2, port, ...session ? { session } : {} };
5976
+ }
5977
+ function routesDir(dir) {
5978
+ return path18.join(dir, "routes");
5979
+ }
5980
+ function listRoutes(dir = workstationDir()) {
5981
+ let names;
5982
+ try {
5983
+ names = fs17.readdirSync(routesDir(dir)).filter((n) => n.endsWith(".caddy"));
5984
+ } catch {
5985
+ return [];
5986
+ }
5987
+ return names.flatMap((name) => {
5988
+ const file = path18.join(routesDir(dir), name);
5989
+ const route = parseRouteFile(fs17.readFileSync(file, "utf8"));
5990
+ return route ? [{ ...route, file }] : [];
5991
+ });
5992
+ }
5993
+ function removeSessionRoutes(session, dir = workstationDir()) {
5994
+ const removed = [];
5995
+ for (const route of listRoutes(dir)) {
5996
+ if (route.session !== session) continue;
5997
+ fs17.rmSync(route.file, { force: true });
5998
+ removed.push(route.hostname);
5999
+ }
6000
+ return removed;
6001
+ }
6002
+ function writeSessionRoutes(session, planned, dir = workstationDir()) {
6003
+ removeSessionRoutes(session, dir);
6004
+ const held = new Map(listRoutes(dir).map((r) => [r.hostname, r]));
6005
+ const result = { urls: {}, written: [], conflicts: [] };
6006
+ fs17.mkdirSync(routesDir(dir), { recursive: true });
6007
+ for (const route of planned) {
6008
+ const other = held.get(route.hostname);
6009
+ if (other) {
6010
+ if (other.port === route.port) result.urls[route.service] = `https://${route.hostname}`;
6011
+ else
6012
+ result.conflicts.push({
6013
+ ...route,
6014
+ heldBy: other.session ? `session ${other.session}` : "the stack",
6015
+ heldPort: other.port
6016
+ });
6017
+ continue;
6018
+ }
6019
+ const file = path18.join(routesDir(dir), `${route.hostname}.caddy`);
6020
+ const tmp = `${file}.${process.pid}.tmp`;
6021
+ fs17.writeFileSync(tmp, renderRouteFile({ hostname: route.hostname, port: route.port, session }));
6022
+ fs17.renameSync(tmp, file);
6023
+ result.written.push(route);
6024
+ result.urls[route.service] = `https://${route.hostname}`;
6025
+ }
6026
+ return result;
6027
+ }
6028
+ function renderWorkstationCaddyfile(identity, dir) {
6029
+ return [
6030
+ "# Written by `mesh workstation init`. Routes live in routes/; this file rarely changes.",
6031
+ "{",
6032
+ " admin 127.0.0.1:2119",
6033
+ ` storage file_system ${path18.join(dir, "caddy")}`,
6034
+ " skip_install_trust",
6035
+ " auto_https disable_redirects",
6036
+ " servers {",
6037
+ " protocols h1 h2",
6038
+ " }",
6039
+ "}",
6040
+ "",
6041
+ // macOS lets a user bind 443 only on the wildcard address, and SNI and Host are the
6042
+ // client's choice, so the site refuses any client outside the tailnet (100.64.0.0/10,
6043
+ // fd7a:115c:a1e0::/48) and loopback before any route.
6044
+ `*.${identity.domain}:443 {`,
6045
+ " tls {",
6046
+ " dns route53 {",
6047
+ ` hosted_zone_id ${identity.proxy.zoneId}`,
6048
+ ` region ${identity.proxy.region}`,
6049
+ " wait_for_route53_sync true",
6050
+ " }",
6051
+ // Route 53 takes up to a minute to reach all its name servers; ACME checks from several places.
6052
+ " propagation_delay 60s",
6053
+ " propagation_timeout 5m",
6054
+ " }",
6055
+ " @offnet not remote_ip 100.64.0.0/10 fd7a:115c:a1e0::/48 127.0.0.0/8 ::1",
6056
+ " handle @offnet {",
6057
+ " abort",
6058
+ " }",
6059
+ ` import ${path18.join(routesDir(dir), "*.caddy")}`,
6060
+ " handle {",
6061
+ ' respond "No service is routed at {host}" 404',
6062
+ " }",
6063
+ "}",
6064
+ ""
6065
+ ].join("\n");
6066
+ }
6067
+ function usableRouteUrls(planned, dir = workstationDir()) {
6068
+ const byHost = new Map(listRoutes(dir).map((r) => [r.hostname, r]));
6069
+ return Object.fromEntries(
6070
+ planned.filter((p) => byHost.get(p.hostname)?.port === p.port).map((p) => [p.service, `https://${p.hostname}`])
6071
+ );
6072
+ }
6073
+
6074
+ // packages/mesh-cli/src/utils/workstation-machine.ts
6075
+ function readCredentialProcess(configText, profile) {
6076
+ let inSection = false;
6077
+ for (const line of configText.split("\n")) {
6078
+ const header = /^\s*\[(.+)\]\s*$/.exec(line);
6079
+ if (header) {
6080
+ inSection = header[1].trim() === `profile ${profile}` || header[1].trim() === profile;
6081
+ continue;
6082
+ }
6083
+ const kv = /^\s*credential_process\s*=\s*(.+?)\s*$/.exec(line);
6084
+ if (inSection && kv) return kv[1];
6085
+ }
6086
+ return void 0;
6087
+ }
6088
+ function credentialProgram(command) {
6089
+ return command.split(/\s+/)[0];
6090
+ }
6091
+ function credentialProcessProblem(command, deps) {
6092
+ if (!command) return "the profile has no credential_process (run mesh login <platform> --export --role <roleArn>)";
6093
+ const program = credentialProgram(command);
6094
+ if (!path19.isAbsolute(program)) return `its program "${program}" is not an absolute path`;
6095
+ if (!deps.exists(program)) return `its program ${program} does not exist`;
6096
+ if (deps.isInsideGitTree(program)) {
6097
+ return `its program ${program} is inside a git checkout, and stops working when that checkout is removed`;
6098
+ }
6099
+ return void 0;
6100
+ }
6101
+ function isInsideGitTree(p) {
6102
+ let dir = path19.dirname(path19.resolve(p));
6103
+ for (; ; ) {
6104
+ if (fs18.existsSync(path19.join(dir, ".git"))) return true;
6105
+ const parent = path19.dirname(dir);
6106
+ if (parent === dir) return false;
6107
+ dir = parent;
6108
+ }
6109
+ }
6110
+ function rewriteCredentialProgram(configText, profile, cli) {
6111
+ let inSection = false;
6112
+ let rewritten = false;
6113
+ const lines = configText.split("\n").map((line) => {
6114
+ const header = /^\s*\[(.+)\]\s*$/.exec(line);
6115
+ if (header) {
6116
+ inSection = header[1].trim() === `profile ${profile}` || header[1].trim() === profile;
6117
+ return line;
6118
+ }
6119
+ const kv = /^(\s*credential_process\s*=\s*)(\S+)(.*)$/.exec(line);
6120
+ if (inSection && kv) {
6121
+ rewritten = true;
6122
+ return `${kv[1]}${cli}${kv[3]}`;
6123
+ }
6124
+ return line;
6125
+ });
6126
+ if (!rewritten) throw new Error(`no credential_process in [profile ${profile}]`);
6127
+ return lines.join("\n");
6128
+ }
6129
+ var LAUNCH_AGENT_LABEL = "tech.mesh.workstation-proxy";
6130
+ var LAUNCH_AGENTS_MACOS_ONLY = "LaunchAgents are macOS-only; run mesh workstation proxy in the foreground (or under your own service manager)";
6131
+ function launchAgentsSupported(platform = process.platform) {
6132
+ return platform === "darwin";
6133
+ }
6134
+ function launchAgentSearchPath(installerPath, execPath) {
6135
+ const entries = [
6136
+ ...(installerPath ?? "").split(":"),
6137
+ path19.dirname(execPath),
6138
+ "/usr/local/bin",
6139
+ "/usr/bin",
6140
+ "/bin",
6141
+ "/usr/sbin",
6142
+ "/sbin"
6143
+ ].filter((p) => path19.isAbsolute(p) && !p.split(path19.sep).includes("node_modules"));
6144
+ return [...new Set(entries)].join(":");
6145
+ }
6146
+ function xml(s) {
6147
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
6148
+ }
6149
+ function renderLaunchAgent(args) {
6150
+ const program = [args.caddy, "run", "--watch", "--config", args.caddyfile, "--adapter", "caddyfile"];
6151
+ const env = Object.entries(args.env).map(([k, v]) => ` <key>${xml(k)}</key>
6152
+ <string>${xml(v)}</string>`).join("\n");
6153
+ return [
6154
+ '<?xml version="1.0" encoding="UTF-8"?>',
6155
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
6156
+ '<plist version="1.0">',
6157
+ "<dict>",
6158
+ ` <key>Label</key>
6159
+ <string>${LAUNCH_AGENT_LABEL}</string>`,
6160
+ ` <key>ProgramArguments</key>
6161
+ <array>
6162
+ ${program.map((a) => ` <string>${xml(a)}</string>`).join("\n")}
6163
+ </array>`,
6164
+ ` <key>WorkingDirectory</key>
6165
+ <string>${xml(args.dir)}</string>`,
6166
+ ` <key>EnvironmentVariables</key>
6167
+ <dict>
6168
+ ${env}
6169
+ </dict>`,
6170
+ " <key>RunAtLoad</key>\n <true/>",
6171
+ " <key>KeepAlive</key>\n <true/>",
6172
+ ` <key>StandardOutPath</key>
6173
+ <string>${xml(path19.join(args.dir, "proxy.log"))}</string>`,
6174
+ ` <key>StandardErrorPath</key>
6175
+ <string>${xml(path19.join(args.dir, "proxy.log"))}</string>`,
6176
+ "</dict>",
6177
+ "</plist>",
6178
+ ""
6179
+ ].join("\n");
6180
+ }
6181
+ function certificateExpiry(dir, domain) {
6182
+ const certs = path19.join(dir, "caddy", "certificates");
6183
+ let best;
6184
+ const walk = (d) => {
6185
+ let entries;
6186
+ try {
6187
+ entries = fs18.readdirSync(d, { withFileTypes: true });
6188
+ } catch {
6189
+ return;
6190
+ }
6191
+ for (const e of entries) {
6192
+ const p = path19.join(d, e.name);
6193
+ if (e.isDirectory()) walk(p);
6194
+ else if (e.name === `wildcard_.${domain}.crt`) {
6195
+ const expiry = new Date(new X509Certificate(fs18.readFileSync(p)).validTo);
6196
+ if (!best || expiry > best) best = expiry;
6197
+ }
6198
+ }
6199
+ };
6200
+ walk(certs);
6201
+ return best;
6202
+ }
6203
+ function workstationWarnings(args) {
6204
+ const warnings = [];
6205
+ if (args.certExpiry) {
6206
+ const days = Math.floor((args.certExpiry.getTime() - args.now.getTime()) / 864e5);
6207
+ if (days < 14) warnings.push(`The certificate expires in ${days} day(s) and has not renewed: check proxy.log.`);
6208
+ }
6209
+ if (args.credentialProblem) warnings.push(`The proxy's AWS profile will not renew the certificate: ${args.credentialProblem}.`);
6210
+ else if (!args.credentialOk) {
6211
+ warnings.push("The proxy's AWS profile yields no credentials (mesh login expired?): renewals fail until you log in again.");
6212
+ }
6213
+ return warnings;
6214
+ }
6215
+ function discoveryRecordName(name, platform) {
6216
+ return `_mesh-workstation.${name}.${platform.replace(/\.$/, "")}`.toLowerCase();
6217
+ }
6218
+ function parseDiscoveryRecord(text) {
6219
+ const fields = /* @__PURE__ */ new Map();
6220
+ for (const part of text.trim().split(/\s+/)) {
6221
+ const eq = part.indexOf("=");
6222
+ if (eq > 0) fields.set(part.slice(0, eq), part.slice(eq + 1));
6223
+ }
6224
+ if (fields.get("v") !== "1") return `unknown discovery record version (${fields.get("v") ?? "none"})`;
6225
+ const roleArn = fields.get("role") ?? "";
6226
+ const zoneId = fields.get("zone") ?? "";
6227
+ const region = fields.get("region") ?? "";
6228
+ if (!/^arn:aws:iam::\d{12}:role\/[\w+=,.@/-]+$/.test(roleArn)) return `"${roleArn}" is not an IAM role ARN`;
6229
+ if (!/^Z[A-Z0-9]{1,32}$/.test(zoneId)) return `"${zoneId}" is not a Route 53 zone id`;
6230
+ if (!/^[a-z]{2}(-[a-z]+)+-\d$/.test(region)) return `"${region}" is not an AWS region`;
6231
+ return { roleArn, zoneId, region };
6232
+ }
6233
+ function caddyDownloadUrl(platform, arch) {
6234
+ const goArch = arch === "x64" ? "amd64" : arch;
6235
+ return `https://caddyserver.com/api/download?os=${platform}&arch=${goArch}&p=${encodeURIComponent("github.com/caddy-dns/route53")}`;
6236
+ }
6237
+ function caddyHasRoute53(listModulesOutput) {
6238
+ return listModulesOutput.split("\n").some((l) => l.trim() === "dns.providers.route53");
6239
+ }
6240
+ function shimFallsBackToGlobal(shimText) {
6241
+ return shimText.includes("canonical launcher shim") && shimText.includes("Not in a workspace: exec the next");
6242
+ }
6243
+ function zoneCandidates(platform) {
6244
+ const labels = platform.replace(/\.$/, "").toLowerCase().split(".");
6245
+ return labels.slice(0, -1).map((_, i) => labels.slice(i).join("."));
6246
+ }
6247
+ var RENEWAL_OVERDUE_DAYS = 28;
6248
+ function healthCheckDue(stamp, now) {
6249
+ if (!stamp) return true;
6250
+ const at = Date.parse(stamp.checkedAt);
6251
+ return !Number.isFinite(at) || now.getTime() - at >= 864e5;
6252
+ }
6253
+ function loginContextOf(command) {
6254
+ return command ? /\slogin\s+("?)([^\s"]+)\1/.exec(command)?.[2] : void 0;
6255
+ }
6256
+ function renewalWarnings(args) {
6257
+ const days = args.certExpiry ? Math.floor((args.certExpiry.getTime() - args.now.getTime()) / 864e5) : void 0;
6258
+ const expires = days === void 0 ? "" : ` (certificate expires in ${days} day${days === 1 ? "" : "s"})`;
6259
+ const login2 = args.loginContext ? `mesh login ${args.loginContext}` : "the profile's mesh login";
6260
+ if (!args.credentialOk) return [`Workstation certificate renewal is failing: run ${login2}${expires}`];
6261
+ if (days !== void 0 && days < RENEWAL_OVERDUE_DAYS) {
6262
+ return [`Workstation certificate has not renewed${expires}: check ~/.config/mesh/workstation/proxy.log`];
6263
+ }
6264
+ return [];
6265
+ }
6266
+ var STATIC_AWS_VARS = ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"];
6267
+ function proxyEnv(base, proxy) {
6268
+ const env = { ...base };
6269
+ for (const key of STATIC_AWS_VARS) delete env[key];
6270
+ env.AWS_REGION = proxy.region;
6271
+ if (proxy.awsProfile) env.AWS_PROFILE = proxy.awsProfile;
6272
+ if (proxy.awsConfigFile) env.AWS_CONFIG_FILE = proxy.awsConfigFile;
6273
+ return env;
6274
+ }
6275
+ function awsConfigFileOf(proxy) {
6276
+ return proxy.awsConfigFile ?? path19.join(os4.homedir(), ".aws", "config");
6277
+ }
6278
+ function profileYieldsCredentials(proxy, timeoutMs = 6e4) {
6279
+ if (!proxy.awsProfile) return true;
6280
+ const command = readCredentialProcess(fs18.readFileSync(awsConfigFileOf(proxy), "utf8"), proxy.awsProfile);
6281
+ if (!command) return false;
6282
+ try {
6283
+ const out = execFileSync11("/bin/sh", ["-c", command], {
6284
+ env: proxyEnv(process.env, proxy),
6285
+ cwd: os4.homedir(),
6286
+ timeout: timeoutMs,
6287
+ stdio: ["ignore", "pipe", "ignore"]
6288
+ });
6289
+ return typeof JSON.parse(out.toString()).AccessKeyId === "string";
6290
+ } catch {
6291
+ return false;
6292
+ }
6293
+ }
6294
+ function workstationRenewalWarnings(now = /* @__PURE__ */ new Date()) {
6295
+ try {
6296
+ const dir = workstationDir();
6297
+ const identity = readWorkstationIdentity(dir);
6298
+ if (!identity?.proxy) return [];
6299
+ const proxy = identity.proxy;
6300
+ const stampFile = path19.join(dir, "health.json");
6301
+ let stamp;
6302
+ try {
6303
+ stamp = JSON.parse(fs18.readFileSync(stampFile, "utf8"));
6304
+ } catch {
6305
+ stamp = void 0;
6306
+ }
6307
+ if (healthCheckDue(stamp, now)) {
6308
+ logInfo("Checking the workstation's certificate credentials\u2026");
6309
+ stamp = { checkedAt: now.toISOString(), credentialOk: profileYieldsCredentials(proxy, 2e4) };
6310
+ fs18.writeFileSync(stampFile, JSON.stringify(stamp) + "\n");
6311
+ }
6312
+ let command;
6313
+ if (proxy.awsProfile) {
6314
+ try {
6315
+ command = readCredentialProcess(fs18.readFileSync(awsConfigFileOf(proxy), "utf8"), proxy.awsProfile);
6316
+ } catch {
6317
+ command = void 0;
6318
+ }
6319
+ }
6320
+ return renewalWarnings({
6321
+ now,
6322
+ certExpiry: certificateExpiry(dir, identity.domain),
6323
+ credentialOk: stamp.credentialOk,
6324
+ loginContext: loginContextOf(command)
6325
+ });
6326
+ } catch {
6327
+ return [];
6328
+ }
6329
+ }
6330
+
5899
6331
  // packages/mesh-cli/src/commands/dev.ts
5900
6332
  function registerServiceProbes(devOutput, tenant, probeFiles) {
5901
6333
  const services = {};
@@ -5945,9 +6377,9 @@ mesh dev plan \u2014 ${kind}
5945
6377
  console.log(` (none \u2014 all services are deployed/K8s)`);
5946
6378
  } else {
5947
6379
  const monorepoRoot = findMonorepoRoot();
5948
- const wtWithSep = wt.worktreeRoot.endsWith(path18.sep) ? wt.worktreeRoot : wt.worktreeRoot + path18.sep;
6380
+ const wtWithSep = wt.worktreeRoot.endsWith(path20.sep) ? wt.worktreeRoot : wt.worktreeRoot + path20.sep;
5949
6381
  for (const [name, s] of locals) {
5950
- const dir = path18.resolve(appRoot, rebaseServiceSrc(s.src, monorepoRoot));
6382
+ const dir = path20.resolve(appRoot, rebaseServiceSrc(s.src, monorepoRoot));
5951
6383
  const outside = dir !== wt.worktreeRoot && !dir.startsWith(wtWithSep);
5952
6384
  const port = s.port > 0 ? `:${s.port}` : "(no port)";
5953
6385
  console.log(` ${name.padEnd(18)} ${port.padEnd(7)} ${dir}${outside ? " \u26A0 OUTSIDE worktree" : ""}`);
@@ -5957,16 +6389,16 @@ mesh dev plan \u2014 ${kind}
5957
6389
  }
5958
6390
  function getSessionStatePath(sessionName) {
5959
6391
  const dir = devSessionsRoot();
5960
- return path18.join(dir, `${sessionName}.json`);
6392
+ return path20.join(dir, `${sessionName}.json`);
5961
6393
  }
5962
6394
  function saveSessionState(sessionName, state) {
5963
- fs17.writeFileSync(getSessionStatePath(sessionName), JSON.stringify(state, null, 2));
6395
+ fs19.writeFileSync(getSessionStatePath(sessionName), JSON.stringify(state, null, 2));
5964
6396
  }
5965
6397
  function loadSessionState(sessionName) {
5966
6398
  const filePath = getSessionStatePath(sessionName);
5967
- if (!fs17.existsSync(filePath)) return null;
6399
+ if (!fs19.existsSync(filePath)) return null;
5968
6400
  try {
5969
- return JSON.parse(fs17.readFileSync(filePath, "utf-8"));
6401
+ return JSON.parse(fs19.readFileSync(filePath, "utf-8"));
5970
6402
  } catch {
5971
6403
  return null;
5972
6404
  }
@@ -5990,24 +6422,29 @@ function workflowChangeWouldStrand(priorState, appRoot, force) {
5990
6422
  return true;
5991
6423
  }
5992
6424
  function getServiceEnvFilePath(sessionName, serviceName) {
5993
- return path18.join(getSessionEnvDir(sessionName), envFileName(serviceName));
6425
+ return path20.join(getSessionEnvDir(sessionName), envFileName(serviceName));
5994
6426
  }
5995
6427
  function removeSessionState(sessionName) {
5996
6428
  try {
5997
- fs17.unlinkSync(getSessionStatePath(sessionName));
6429
+ fs19.unlinkSync(getSessionStatePath(sessionName));
6430
+ } catch {
6431
+ }
6432
+ try {
6433
+ const removed = removeSessionRoutes(sessionName);
6434
+ if (removed.length > 0) logInfo(`Removed workstation routes: ${removed.join(", ")}`);
5998
6435
  } catch {
5999
6436
  }
6000
6437
  try {
6001
- fs17.rmSync(getSessionEnvDir(sessionName), { recursive: true, force: true });
6438
+ fs19.rmSync(getSessionEnvDir(sessionName), { recursive: true, force: true });
6002
6439
  } catch {
6003
6440
  }
6004
6441
  }
6005
6442
  function getSessionPortMemoryPath(sessionName) {
6006
- return path18.join(path18.dirname(getSessionStatePath(sessionName)), `${sessionName}.ports.json`);
6443
+ return path20.join(path20.dirname(getSessionStatePath(sessionName)), `${sessionName}.ports.json`);
6007
6444
  }
6008
6445
  function loadSessionPortMemory(sessionName) {
6009
6446
  try {
6010
- const raw = JSON.parse(fs17.readFileSync(getSessionPortMemoryPath(sessionName), "utf-8"));
6447
+ const raw = JSON.parse(fs19.readFileSync(getSessionPortMemoryPath(sessionName), "utf-8"));
6011
6448
  return rememberedPortsOf(raw);
6012
6449
  } catch {
6013
6450
  return {};
@@ -6038,15 +6475,15 @@ function saveSessionPortMemory(sessionName, devOutput) {
6038
6475
  }
6039
6476
  const memory = { services, savedAt: (/* @__PURE__ */ new Date()).toISOString() };
6040
6477
  try {
6041
- fs17.writeFileSync(getSessionPortMemoryPath(sessionName), JSON.stringify(memory, null, 2));
6478
+ fs19.writeFileSync(getSessionPortMemoryPath(sessionName), JSON.stringify(memory, null, 2));
6042
6479
  } catch {
6043
6480
  }
6044
6481
  }
6045
6482
  async function isPortFree(port) {
6046
- const bindSucceeds = (host) => new Promise((resolve9) => {
6483
+ const bindSucceeds = (host) => new Promise((resolve10) => {
6047
6484
  const server = net3.createServer();
6048
- server.once("error", () => resolve9(false));
6049
- const onListening = () => server.close(() => resolve9(true));
6485
+ server.once("error", () => resolve10(false));
6486
+ const onListening = () => server.close(() => resolve10(true));
6050
6487
  if (host === void 0) server.listen(port, onListening);
6051
6488
  else server.listen(port, host, onListening);
6052
6489
  });
@@ -6054,24 +6491,24 @@ async function isPortFree(port) {
6054
6491
  return bindSucceeds("127.0.0.1");
6055
6492
  }
6056
6493
  async function findFreePort() {
6057
- return new Promise((resolve9, reject) => {
6494
+ return new Promise((resolve10, reject) => {
6058
6495
  const server = net3.createServer();
6059
6496
  server.once("error", reject);
6060
6497
  server.listen(0, "127.0.0.1", () => {
6061
6498
  const { port } = server.address();
6062
- server.close(() => resolve9(port));
6499
+ server.close(() => resolve10(port));
6063
6500
  });
6064
6501
  });
6065
6502
  }
6066
6503
  function isPortListening(port) {
6067
- return new Promise((resolve9) => {
6504
+ return new Promise((resolve10) => {
6068
6505
  const s = new net3.Socket();
6069
6506
  let done = false;
6070
6507
  const fin = (ok) => {
6071
6508
  if (done) return;
6072
6509
  done = true;
6073
6510
  s.destroy();
6074
- resolve9(ok);
6511
+ resolve10(ok);
6075
6512
  };
6076
6513
  s.setTimeout(300);
6077
6514
  s.once("connect", () => fin(true));
@@ -6179,16 +6616,16 @@ function mergePinnedPorts(fresh, pinned) {
6179
6616
  }
6180
6617
  function findMonorepoRoot() {
6181
6618
  let dir = process.cwd();
6182
- const root = path18.parse(dir).root;
6619
+ const root = path20.parse(dir).root;
6183
6620
  while (dir !== root) {
6184
- if (fs17.existsSync(path18.join(dir, "pnpm-workspace.yaml")) || fs17.existsSync(path18.join(dir, "turbo.json"))) {
6621
+ if (fs19.existsSync(path20.join(dir, "pnpm-workspace.yaml")) || fs19.existsSync(path20.join(dir, "turbo.json"))) {
6185
6622
  return dir;
6186
6623
  }
6187
- const gitPath = path18.join(dir, ".git");
6188
- if (fs17.existsSync(gitPath)) {
6624
+ const gitPath = path20.join(dir, ".git");
6625
+ if (fs19.existsSync(gitPath)) {
6189
6626
  return dir;
6190
6627
  }
6191
- dir = path18.dirname(dir);
6628
+ dir = path20.dirname(dir);
6192
6629
  }
6193
6630
  return null;
6194
6631
  }
@@ -6196,40 +6633,40 @@ function discoverApps(monorepoRoot) {
6196
6633
  const apps = [];
6197
6634
  function addApp(appDir, tenant) {
6198
6635
  const projectName = getProjectName(appDir);
6199
- const stacks = fs17.readdirSync(appDir).filter((f) => f.startsWith("Pulumi.") && f.endsWith(".yaml") && f !== "Pulumi.yaml").map((f) => f.replace(/^Pulumi\./, "").replace(/\.yaml$/, ""));
6636
+ const stacks = fs19.readdirSync(appDir).filter((f) => f.startsWith("Pulumi.") && f.endsWith(".yaml") && f !== "Pulumi.yaml").map((f) => f.replace(/^Pulumi\./, "").replace(/\.yaml$/, ""));
6200
6637
  apps.push({
6201
6638
  name: projectName,
6202
6639
  tenant,
6203
6640
  appPath: appDir,
6204
- relativePath: path18.relative(monorepoRoot, appDir),
6641
+ relativePath: path20.relative(monorepoRoot, appDir),
6205
6642
  stacks
6206
6643
  });
6207
6644
  }
6208
6645
  const searchDirs = ["tenants", "tests/tenants"];
6209
6646
  for (const searchDir of searchDirs) {
6210
- const base = path18.join(monorepoRoot, searchDir);
6211
- if (!fs17.existsSync(base)) continue;
6212
- for (const tenant of fs17.readdirSync(base)) {
6213
- const appsDir = path18.join(base, tenant, "apps");
6214
- if (!fs17.existsSync(appsDir) || !fs17.statSync(appsDir).isDirectory()) continue;
6215
- for (const app of fs17.readdirSync(appsDir)) {
6216
- const appDir = path18.join(appsDir, app);
6217
- if (!fs17.existsSync(path18.join(appDir, "Pulumi.yaml"))) continue;
6647
+ const base = path20.join(monorepoRoot, searchDir);
6648
+ if (!fs19.existsSync(base)) continue;
6649
+ for (const tenant of fs19.readdirSync(base)) {
6650
+ const appsDir = path20.join(base, tenant, "apps");
6651
+ if (!fs19.existsSync(appsDir) || !fs19.statSync(appsDir).isDirectory()) continue;
6652
+ for (const app of fs19.readdirSync(appsDir)) {
6653
+ const appDir = path20.join(appsDir, app);
6654
+ if (!fs19.existsSync(path20.join(appDir, "Pulumi.yaml"))) continue;
6218
6655
  addApp(appDir, tenant);
6219
6656
  }
6220
6657
  }
6221
6658
  }
6222
- const flatAppsDir = path18.join(monorepoRoot, "apps");
6223
- if (fs17.existsSync(flatAppsDir) && fs17.statSync(flatAppsDir).isDirectory()) {
6224
- for (const app of fs17.readdirSync(flatAppsDir)) {
6225
- const appDir = path18.join(flatAppsDir, app);
6226
- if (!fs17.statSync(appDir).isDirectory()) continue;
6227
- if (!fs17.existsSync(path18.join(appDir, "Pulumi.yaml"))) continue;
6659
+ const flatAppsDir = path20.join(monorepoRoot, "apps");
6660
+ if (fs19.existsSync(flatAppsDir) && fs19.statSync(flatAppsDir).isDirectory()) {
6661
+ for (const app of fs19.readdirSync(flatAppsDir)) {
6662
+ const appDir = path20.join(flatAppsDir, app);
6663
+ if (!fs19.statSync(appDir).isDirectory()) continue;
6664
+ if (!fs19.existsSync(path20.join(appDir, "Pulumi.yaml"))) continue;
6228
6665
  let tenant = "unknown";
6229
- const stackConfigs = fs17.readdirSync(appDir).filter((f) => f.startsWith("Pulumi.") && f.endsWith(".yaml") && f !== "Pulumi.yaml");
6666
+ const stackConfigs = fs19.readdirSync(appDir).filter((f) => f.startsWith("Pulumi.") && f.endsWith(".yaml") && f !== "Pulumi.yaml");
6230
6667
  if (stackConfigs.length > 0) {
6231
6668
  try {
6232
- const content = fs17.readFileSync(path18.join(appDir, stackConfigs[0]), "utf-8");
6669
+ const content = fs19.readFileSync(path20.join(appDir, stackConfigs[0]), "utf-8");
6233
6670
  const tenantMatch = content.match(/mesh:tenant:\s*(\S+)/);
6234
6671
  if (tenantMatch) tenant = tenantMatch[1];
6235
6672
  } catch {
@@ -6242,38 +6679,38 @@ function discoverApps(monorepoRoot) {
6242
6679
  }
6243
6680
  function findAppRoot2(appPath) {
6244
6681
  if (appPath) {
6245
- const resolved = path18.resolve(appPath);
6246
- if (fs17.existsSync(path18.join(resolved, "Pulumi.yaml"))) return resolved;
6682
+ const resolved = path20.resolve(appPath);
6683
+ if (fs19.existsSync(path20.join(resolved, "Pulumi.yaml"))) return resolved;
6247
6684
  const mono = findMonorepoRoot();
6248
6685
  if (mono) {
6249
- const fromMono = path18.resolve(mono, appPath);
6250
- if (fs17.existsSync(path18.join(fromMono, "Pulumi.yaml"))) return fromMono;
6686
+ const fromMono = path20.resolve(mono, appPath);
6687
+ if (fs19.existsSync(path20.join(fromMono, "Pulumi.yaml"))) return fromMono;
6251
6688
  }
6252
6689
  logError(`No Pulumi.yaml found at: ${appPath}`);
6253
6690
  process.exit(1);
6254
6691
  }
6255
6692
  let dir = process.cwd();
6256
- const root = path18.parse(dir).root;
6693
+ const root = path20.parse(dir).root;
6257
6694
  while (dir !== root) {
6258
- if (fs17.existsSync(path18.join(dir, "Pulumi.yaml"))) {
6695
+ if (fs19.existsSync(path20.join(dir, "Pulumi.yaml"))) {
6259
6696
  return dir;
6260
6697
  }
6261
- dir = path18.dirname(dir);
6698
+ dir = path20.dirname(dir);
6262
6699
  }
6263
6700
  return process.cwd();
6264
6701
  }
6265
6702
  function getProjectName(appRoot) {
6266
- const yamlPath = path18.join(appRoot, "Pulumi.yaml");
6267
- if (!fs17.existsSync(yamlPath)) return path18.basename(appRoot);
6268
- const content = fs17.readFileSync(yamlPath, "utf-8");
6703
+ const yamlPath = path20.join(appRoot, "Pulumi.yaml");
6704
+ if (!fs19.existsSync(yamlPath)) return path20.basename(appRoot);
6705
+ const content = fs19.readFileSync(yamlPath, "utf-8");
6269
6706
  const match = content.match(/^name:\s*(.+)$/m);
6270
- return match?.[1]?.trim() ?? path18.basename(appRoot);
6707
+ return match?.[1]?.trim() ?? path20.basename(appRoot);
6271
6708
  }
6272
6709
  function detectStack(appRoot, stageArg) {
6273
6710
  if (stageArg) return stageArg;
6274
6711
  if (process.env.MESH_STAGE) return process.env.MESH_STAGE;
6275
6712
  try {
6276
- const result = execFileSync11("pulumi", ["stack", "--show-name"], {
6713
+ const result = execFileSync12("pulumi", ["stack", "--show-name"], {
6277
6714
  cwd: appRoot,
6278
6715
  encoding: "utf-8",
6279
6716
  stdio: ["ignore", "pipe", "ignore"]
@@ -6282,7 +6719,7 @@ function detectStack(appRoot, stageArg) {
6282
6719
  } catch {
6283
6720
  }
6284
6721
  try {
6285
- const files = fs17.readdirSync(appRoot).filter((f) => f.startsWith("Pulumi.") && f.endsWith(".yaml") && f !== "Pulumi.yaml");
6722
+ const files = fs19.readdirSync(appRoot).filter((f) => f.startsWith("Pulumi.") && f.endsWith(".yaml") && f !== "Pulumi.yaml");
6286
6723
  if (files.length === 1) {
6287
6724
  const match = files[0].match(/^Pulumi\.(.+)\.yaml$/);
6288
6725
  if (match?.[1]) {
@@ -6296,7 +6733,7 @@ function detectStack(appRoot, stageArg) {
6296
6733
  }
6297
6734
  function stackArgs(appRoot, stack) {
6298
6735
  try {
6299
- const selected = execFileSync11("pulumi", ["stack", "--show-name"], {
6736
+ const selected = execFileSync12("pulumi", ["stack", "--show-name"], {
6300
6737
  cwd: appRoot,
6301
6738
  encoding: "utf-8",
6302
6739
  stdio: ["ignore", "pipe", "ignore"]
@@ -6305,7 +6742,7 @@ function stackArgs(appRoot, stack) {
6305
6742
  } catch {
6306
6743
  }
6307
6744
  try {
6308
- execFileSync11("pulumi", ["stack", "select", stack], {
6745
+ execFileSync12("pulumi", ["stack", "select", stack], {
6309
6746
  cwd: appRoot,
6310
6747
  encoding: "utf-8",
6311
6748
  stdio: ["ignore", "pipe", "ignore"]
@@ -6340,7 +6777,7 @@ function getDevOutput(appRoot, stack, awsEnv) {
6340
6777
  }
6341
6778
  }
6342
6779
  function gitCommitOfDir(dir) {
6343
- const run = (...args) => execFileSync11("git", ["-C", dir, ...args], {
6780
+ const run = (...args) => execFileSync12("git", ["-C", dir, ...args], {
6344
6781
  encoding: "utf-8",
6345
6782
  stdio: ["ignore", "pipe", "ignore"],
6346
6783
  timeout: 2e3
@@ -6355,7 +6792,7 @@ function gitCommitOfDir(dir) {
6355
6792
  }
6356
6793
  }
6357
6794
  function worktreeDigestOfDir(dir) {
6358
- const run = (cwd, ...args) => execFileSync11("git", ["-C", cwd, ...args], {
6795
+ const run = (cwd, ...args) => execFileSync12("git", ["-C", cwd, ...args], {
6359
6796
  stdio: ["ignore", "pipe", "ignore"],
6360
6797
  timeout: 1e4,
6361
6798
  maxBuffer: 256 * 1024 * 1024
@@ -6370,7 +6807,7 @@ function worktreeDigestOfDir(dir) {
6370
6807
  for (const file of untracked) {
6371
6808
  hash.update("\0").update(file).update("\0");
6372
6809
  try {
6373
- hash.update(fs17.readFileSync(path18.join(top, file)));
6810
+ hash.update(fs19.readFileSync(path20.join(top, file)));
6374
6811
  } catch {
6375
6812
  }
6376
6813
  }
@@ -6382,7 +6819,7 @@ function worktreeDigestOfDir(dir) {
6382
6819
  function currentProgramProvenance(appRoot, stack) {
6383
6820
  let configSha256 = null;
6384
6821
  try {
6385
- configSha256 = createHash5("sha256").update(fs17.readFileSync(path18.join(appRoot, `Pulumi.${stack}.yaml`))).digest("hex");
6822
+ configSha256 = createHash5("sha256").update(fs19.readFileSync(path20.join(appRoot, `Pulumi.${stack}.yaml`))).digest("hex");
6386
6823
  } catch {
6387
6824
  }
6388
6825
  const gitCommit = gitCommitOfDir(appRoot);
@@ -6417,7 +6854,7 @@ function warnIfDevOutputStale(devOutput, appRoot, stack) {
6417
6854
  }
6418
6855
  function hasTmux() {
6419
6856
  try {
6420
- execFileSync11("which", ["tmux"], { stdio: "ignore" });
6857
+ execFileSync12("which", ["tmux"], { stdio: "ignore" });
6421
6858
  return true;
6422
6859
  } catch {
6423
6860
  return false;
@@ -6425,7 +6862,7 @@ function hasTmux() {
6425
6862
  }
6426
6863
  function sessionExists(name) {
6427
6864
  try {
6428
- execFileSync11("tmux", ["has-session", "-t", name], { stdio: "ignore" });
6865
+ execFileSync12("tmux", ["has-session", "-t", name], { stdio: "ignore" });
6429
6866
  return true;
6430
6867
  } catch {
6431
6868
  return false;
@@ -6433,7 +6870,7 @@ function sessionExists(name) {
6433
6870
  }
6434
6871
  function killSession(name) {
6435
6872
  try {
6436
- execFileSync11("tmux", ["kill-session", "-t", name], { stdio: "ignore" });
6873
+ execFileSync12("tmux", ["kill-session", "-t", name], { stdio: "ignore" });
6437
6874
  return true;
6438
6875
  } catch {
6439
6876
  return false;
@@ -6479,9 +6916,9 @@ function buildChildAwsEnv(awsEnv, method, profile, opts) {
6479
6916
  if (method === "zitadel" && opts) {
6480
6917
  const sanitizedContext = opts.context.replace(/[^A-Za-z0-9_-]/g, "-");
6481
6918
  const profileName = `mesh-dev-${sanitizedContext}`;
6482
- const configPath = path18.join(opts.sessionScratchDir, "aws-config");
6919
+ const configPath = path20.join(opts.sessionScratchDir, "aws-config");
6483
6920
  const region = awsEnv.AWS_REGION ?? awsEnv.AWS_DEFAULT_REGION ?? "";
6484
- fs17.mkdirSync(opts.sessionScratchDir, { recursive: true });
6921
+ fs19.mkdirSync(opts.sessionScratchDir, { recursive: true });
6485
6922
  const profileBlock = renderCredentialProcessProfile({
6486
6923
  profileName,
6487
6924
  context: opts.context,
@@ -6489,7 +6926,7 @@ function buildChildAwsEnv(awsEnv, method, profile, opts) {
6489
6926
  region,
6490
6927
  meshBin: opts.meshBin
6491
6928
  });
6492
- const existing = fs17.existsSync(configPath) ? fs17.readFileSync(configPath, "utf8") : "";
6929
+ const existing = fs19.existsSync(configPath) ? fs19.readFileSync(configPath, "utf8") : "";
6493
6930
  const next = upsertManagedAwsConfigSection(
6494
6931
  stripBareProfile(existing, profileName),
6495
6932
  `${opts.context} (mesh dev)`,
@@ -6514,7 +6951,7 @@ function buildChildAwsEnv(awsEnv, method, profile, opts) {
6514
6951
  function readDotEnvFile(file) {
6515
6952
  let text;
6516
6953
  try {
6517
- text = fs17.readFileSync(file, "utf-8");
6954
+ text = fs19.readFileSync(file, "utf-8");
6518
6955
  } catch {
6519
6956
  return {};
6520
6957
  }
@@ -6547,30 +6984,30 @@ function rebaseServiceSrc(src, monorepoRoot) {
6547
6984
  if (src === "." || src === "./") return src;
6548
6985
  const rel = monorepoRelativeSrc(src);
6549
6986
  if (rel === null) return src;
6550
- const rebased = path18.join(monorepoRoot, rel);
6551
- return fs17.existsSync(rebased) ? rebased : src;
6987
+ const rebased = path20.join(monorepoRoot, rel);
6988
+ return fs19.existsSync(rebased) ? rebased : src;
6552
6989
  }
6553
6990
  function isLinkedDependencyDir(serviceDir, appRoot) {
6554
6991
  const pkg = enclosingPackage(serviceDir);
6555
6992
  return Boolean(pkg && appResolvedPackageDir(pkg.name, appRoot) === pkg.root);
6556
6993
  }
6557
6994
  function enclosingPackage(dir) {
6558
- for (let at = dir; ; at = path18.dirname(at)) {
6995
+ for (let at = dir; ; at = path20.dirname(at)) {
6559
6996
  try {
6560
- const manifest = JSON.parse(fs17.readFileSync(path18.join(at, "package.json"), "utf8"));
6997
+ const manifest = JSON.parse(fs19.readFileSync(path20.join(at, "package.json"), "utf8"));
6561
6998
  if (typeof manifest?.name === "string" && manifest.name) return { root: at, name: manifest.name };
6562
6999
  } catch {
6563
7000
  }
6564
- if (path18.dirname(at) === at) return null;
7001
+ if (path20.dirname(at) === at) return null;
6565
7002
  }
6566
7003
  }
6567
7004
  function appResolvedPackageDir(name, appRoot) {
6568
- for (let dir = appRoot; ; dir = path18.dirname(dir)) {
7005
+ for (let dir = appRoot; ; dir = path20.dirname(dir)) {
6569
7006
  try {
6570
- return fs17.realpathSync(path18.join(dir, "node_modules", name));
7007
+ return fs19.realpathSync(path20.join(dir, "node_modules", name));
6571
7008
  } catch {
6572
7009
  }
6573
- if (path18.dirname(dir) === dir) return null;
7010
+ if (path20.dirname(dir) === dir) return null;
6574
7011
  }
6575
7012
  }
6576
7013
  function relinkedServiceDir(serviceDir, appRoot) {
@@ -6578,11 +7015,11 @@ function relinkedServiceDir(serviceDir, appRoot) {
6578
7015
  if (!pkg) return null;
6579
7016
  const own = appResolvedPackageDir(pkg.name, appRoot);
6580
7017
  if (!own || own === pkg.root) return null;
6581
- const candidate = path18.join(own, path18.relative(pkg.root, serviceDir));
6582
- return fs17.existsSync(candidate) ? candidate : null;
7018
+ const candidate = path20.join(own, path20.relative(pkg.root, serviceDir));
7019
+ return fs19.existsSync(candidate) ? candidate : null;
6583
7020
  }
6584
7021
  function monorepoRelativeSrc(src) {
6585
- if (!path18.isAbsolute(src)) return src.replace(/^\.\//, "") || ".";
7022
+ if (!path20.isAbsolute(src)) return src.replace(/^\.\//, "") || ".";
6586
7023
  const m = src.match(/^.*\/mesh-platform(?:\/(.*))?$/);
6587
7024
  if (!m) return null;
6588
7025
  const rest = (m[1] ?? "").replace(/^\.worktrees\/[^/]+(?:\/|$)/, "");
@@ -6624,14 +7061,14 @@ function pointAtTunnel(key, value, tunnel) {
6624
7061
  }
6625
7062
  function setTmuxEnv(sessionName, vars) {
6626
7063
  for (const [key, value] of Object.entries(vars)) {
6627
- execFileSync11("tmux", ["set-environment", "-t", sessionName, key, value], { stdio: "ignore" });
7064
+ execFileSync12("tmux", ["set-environment", "-t", sessionName, key, value], { stdio: "ignore" });
6628
7065
  }
6629
7066
  }
6630
7067
  function readTmuxSessionEnv(sessionName, keys) {
6631
7068
  const out = {};
6632
7069
  for (const key of keys) {
6633
7070
  try {
6634
- const line = execFileSync11("tmux", ["show-environment", "-t", sessionName, key], {
7071
+ const line = execFileSync12("tmux", ["show-environment", "-t", sessionName, key], {
6635
7072
  encoding: "utf-8",
6636
7073
  stdio: ["ignore", "pipe", "ignore"]
6637
7074
  }).trim();
@@ -6710,7 +7147,7 @@ function stackNamespace(devOutput) {
6710
7147
  function resolveTemporalEncodingKey(namespace) {
6711
7148
  const secretName = `${namespace}-temporal-encoding-key`;
6712
7149
  try {
6713
- const b64 = execFileSync11(
7150
+ const b64 = execFileSync12(
6714
7151
  "kubectl",
6715
7152
  [
6716
7153
  "get",
@@ -6746,7 +7183,7 @@ async function resolveTemporalAuthVars(sessionName, devOutput) {
6746
7183
  setTmuxEnv(sessionName, { KUBECONFIG: kubeconfigPath });
6747
7184
  } else if (!process.env.KUBECONFIG) {
6748
7185
  const existing = sessionKubeconfigPath(sessionName);
6749
- if (fs17.existsSync(existing)) {
7186
+ if (fs19.existsSync(existing)) {
6750
7187
  process.env.KUBECONFIG = existing;
6751
7188
  }
6752
7189
  }
@@ -6919,7 +7356,7 @@ async function allocateSsmLocalPort(remotePort, reservedPorts) {
6919
7356
  }
6920
7357
  function hasSessionManagerPlugin() {
6921
7358
  try {
6922
- execFileSync11("which", ["session-manager-plugin"], { stdio: "ignore" });
7359
+ execFileSync12("which", ["session-manager-plugin"], { stdio: "ignore" });
6923
7360
  return true;
6924
7361
  } catch {
6925
7362
  return false;
@@ -7029,15 +7466,15 @@ async function startSsmTunnels(sessionName, devOutput, appRoot, awsEnv) {
7029
7466
  localPortNumber: [String(localPort)]
7030
7467
  })}'`
7031
7468
  ].join(" ");
7032
- execFileSync11("tmux", ["new-window", "-t", sessionName, "-n", windowName]);
7033
- execFileSync11(
7469
+ execFileSync12("tmux", ["new-window", "-t", sessionName, "-n", windowName]);
7470
+ execFileSync12(
7034
7471
  "tmux",
7035
7472
  ["set-option", "-t", `${sessionName}:${windowName}`, "remain-on-exit", "on"],
7036
7473
  { stdio: "ignore" }
7037
7474
  );
7038
7475
  const envFilePath = getServiceEnvFilePath(sessionName, windowName);
7039
7476
  writeEnvFile(envFilePath, { ...hostEnv, ...awsEnv });
7040
- execFileSync11("tmux", [
7477
+ execFileSync12("tmux", [
7041
7478
  "send-keys",
7042
7479
  "-t",
7043
7480
  `${sessionName}:${windowName}`,
@@ -7055,7 +7492,7 @@ async function startSsmTunnels(sessionName, devOutput, appRoot, awsEnv) {
7055
7492
  return rewritten;
7056
7493
  }
7057
7494
  async function updateDevboxProxy(devOutput, appRoot) {
7058
- const appName = devOutput.app ?? path18.basename(appRoot);
7495
+ const appName = devOutput.app ?? path20.basename(appRoot);
7059
7496
  const tenant = devOutput.platform?.tenant ?? "mesh";
7060
7497
  const env = devOutput.platform?.env ?? "dev";
7061
7498
  const stack = devOutput.stack ?? "dev";
@@ -7087,7 +7524,7 @@ async function updateDevboxProxy(devOutput, appRoot) {
7087
7524
  lines.push("");
7088
7525
  }
7089
7526
  const caddyfile = lines.join("\n");
7090
- execFileSync11(
7527
+ execFileSync12(
7091
7528
  "docker",
7092
7529
  [
7093
7530
  "run",
@@ -7103,7 +7540,7 @@ CADDYEOF`
7103
7540
  ],
7104
7541
  { stdio: "pipe" }
7105
7542
  );
7106
- execFileSync11(
7543
+ execFileSync12(
7107
7544
  "docker",
7108
7545
  [
7109
7546
  "run",
@@ -7130,10 +7567,45 @@ CADDYEOF`
7130
7567
  logInfo(` http://${hostname2} \u2192 localhost:${port}`);
7131
7568
  }
7132
7569
  }
7570
+ function planWorkstationRoutes(appRoot, devOutput) {
7571
+ const identity = readWorkstationIdentity();
7572
+ if (!identity) return [];
7573
+ return planSessionRoutes({
7574
+ identity,
7575
+ app: devOutput.app ?? path20.basename(appRoot),
7576
+ services: devOutput.services,
7577
+ suffix: worktreeRouteSuffix(resolveWorktreeIdentity(appRoot), process.env.MESH_DEV_ROUTE_SUFFIX)
7578
+ });
7579
+ }
7580
+ function workstationUrlVar(appRoot, devOutput, serviceName) {
7581
+ try {
7582
+ const url = usableRouteUrls(planWorkstationRoutes(appRoot, devOutput))[serviceName];
7583
+ return url ? { MESH_DEV_PUBLIC_URL: url } : {};
7584
+ } catch {
7585
+ return {};
7586
+ }
7587
+ }
7588
+ function applyWorkstationRoutes(sessionName, appRoot, devOutput) {
7589
+ try {
7590
+ const planned = planWorkstationRoutes(appRoot, devOutput);
7591
+ if (planned.length === 0) return {};
7592
+ const result = writeSessionRoutes(sessionName, planned);
7593
+ for (const warning of workstationRenewalWarnings()) logWarn(warning);
7594
+ for (const c of result.conflicts) {
7595
+ logWarn(
7596
+ `Workstation route ${c.hostname} is held by ${c.heldBy} (port ${c.heldPort}); ${c.service} (port ${c.port}) is not routed. Pick another name with --route-suffix <name>.`
7597
+ );
7598
+ }
7599
+ return result.urls;
7600
+ } catch (e) {
7601
+ logWarn(`Workstation routes skipped: ${e.message}`);
7602
+ return {};
7603
+ }
7604
+ }
7133
7605
  async function setupSubdomainRouting(devOutput, appRoot) {
7134
7606
  const tsInfo = await getTailscaleInfo();
7135
7607
  if (!tsInfo) return null;
7136
- const appName = devOutput.app ?? path18.basename(appRoot);
7608
+ const appName = devOutput.app ?? path20.basename(appRoot);
7137
7609
  const stack = devOutput.stack ?? "dev";
7138
7610
  const baseDomain = `${appName}.${stack}.${tsInfo.hostname}.vpn.internal`;
7139
7611
  const platform = devOutput.platform ?? { tenant: "mesh", env: "dev" };
@@ -7152,8 +7624,8 @@ async function setupSubdomainRouting(devOutput, appRoot) {
7152
7624
  } catch {
7153
7625
  }
7154
7626
  const caddyDir = "/tmp/mesh-dev-caddy";
7155
- const caddyfile = path18.join(caddyDir, "Caddyfile");
7156
- if (!fs17.existsSync(caddyDir)) fs17.mkdirSync(caddyDir, { recursive: true });
7627
+ const caddyfile = path20.join(caddyDir, "Caddyfile");
7628
+ if (!fs19.existsSync(caddyDir)) fs19.mkdirSync(caddyDir, { recursive: true });
7157
7629
  const caddyLines = [`# mesh-dev: ${appName}/${stack} (${tsInfo.hostname})`, ""];
7158
7630
  for (const [name, service] of Object.entries(devOutput.services)) {
7159
7631
  if (service.port) {
@@ -7171,16 +7643,16 @@ async function setupSubdomainRouting(devOutput, appRoot) {
7171
7643
  caddyLines.push("}");
7172
7644
  caddyLines.push("");
7173
7645
  }
7174
- fs17.writeFileSync(caddyfile, caddyLines.join("\n"));
7646
+ fs19.writeFileSync(caddyfile, caddyLines.join("\n"));
7175
7647
  let caddyRunning = false;
7176
7648
  try {
7177
- execFileSync11("pgrep", ["-f", "caddy run.*mesh-dev-caddy"], { stdio: "pipe" });
7649
+ execFileSync12("pgrep", ["-f", "caddy run.*mesh-dev-caddy"], { stdio: "pipe" });
7178
7650
  caddyRunning = true;
7179
7651
  } catch {
7180
7652
  }
7181
7653
  if (caddyRunning) {
7182
7654
  try {
7183
- execFileSync11("caddy", ["reload", "--config", caddyfile, "--adapter", "caddyfile"], {
7655
+ execFileSync12("caddy", ["reload", "--config", caddyfile, "--adapter", "caddyfile"], {
7184
7656
  stdio: "pipe"
7185
7657
  });
7186
7658
  } catch {
@@ -7189,7 +7661,7 @@ async function setupSubdomainRouting(devOutput, appRoot) {
7189
7661
  }
7190
7662
  } else {
7191
7663
  try {
7192
- execFileSync11("which", ["caddy"], { stdio: "pipe" });
7664
+ execFileSync12("which", ["caddy"], { stdio: "pipe" });
7193
7665
  } catch {
7194
7666
  logWarn(
7195
7667
  "Caddy not found. Install for subdomain routing: curl -fsSL https://caddyserver.com/api/download?os=linux&arch=arm64 -o /usr/local/bin/caddy && chmod +x /usr/local/bin/caddy"
@@ -7231,7 +7703,7 @@ async function startServices(sessionName, appRoot, devOutput, headless, awsEnv,
7231
7703
  return devOutput;
7232
7704
  }
7233
7705
  logInfo(`Creating tmux session: ${sessionName}`);
7234
- execFileSync11("tmux", ["new-session", "-d", "-s", sessionName, "-n", "status", "-c", appRoot]);
7706
+ execFileSync12("tmux", ["new-session", "-d", "-s", sessionName, "-n", "status", "-c", appRoot]);
7235
7707
  const sessionEnv = {};
7236
7708
  const shareOnSession = (vars) => {
7237
7709
  Object.assign(sessionEnv, vars);
@@ -7350,15 +7822,15 @@ async function startServices(sessionName, appRoot, devOutput, headless, awsEnv,
7350
7822
  const tokenPort = await findFreePort();
7351
7823
  const tokenUrl = `http://127.0.0.1:${tokenPort}`;
7352
7824
  shareOnSession({ DEV_USER_TOKEN_URL: tokenUrl });
7353
- execFileSync11("tmux", ["new-window", "-t", sessionName, "-n", "token-server"]);
7354
- execFileSync11(
7825
+ execFileSync12("tmux", ["new-window", "-t", sessionName, "-n", "token-server"]);
7826
+ execFileSync12(
7355
7827
  "tmux",
7356
7828
  ["set-option", "-t", `${sessionName}:token-server`, "remain-on-exit", "on"],
7357
7829
  { stdio: "ignore" }
7358
7830
  );
7359
7831
  const tokenServerEnvFile = getServiceEnvFilePath(sessionName, "token-server");
7360
7832
  writeEnvFile(tokenServerEnvFile, hostEnv);
7361
- execFileSync11("tmux", [
7833
+ execFileSync12("tmux", [
7362
7834
  "send-keys",
7363
7835
  "-t",
7364
7836
  `${sessionName}:token-server`,
@@ -7381,6 +7853,7 @@ async function startServices(sessionName, appRoot, devOutput, headless, awsEnv,
7381
7853
  logInfo(" Hub UI will show unauthenticated state without valid credentials.");
7382
7854
  }
7383
7855
  const monorepoRoot = findMonorepoRoot();
7856
+ const routeUrls = applyWorkstationRoutes(sessionName, appRoot, devOutput);
7384
7857
  const notLaunched = [];
7385
7858
  for (const [name, service] of Object.entries(devOutput.services)) {
7386
7859
  const cmd = service.command.join(" ");
@@ -7388,9 +7861,9 @@ async function startServices(sessionName, appRoot, devOutput, headless, awsEnv,
7388
7861
  logInfo(`Skipped: ${name} (no dev command, deployed to K8s)`);
7389
7862
  continue;
7390
7863
  }
7391
- let serviceDir = path18.resolve(appRoot, rebaseServiceSrc(service.src, monorepoRoot));
7392
- if (worktreeRoot && fs17.existsSync(serviceDir)) {
7393
- const wtRootWithSep = worktreeRoot.endsWith(path18.sep) ? worktreeRoot : worktreeRoot + path18.sep;
7864
+ let serviceDir = path20.resolve(appRoot, rebaseServiceSrc(service.src, monorepoRoot));
7865
+ if (worktreeRoot && fs19.existsSync(serviceDir)) {
7866
+ const wtRootWithSep = worktreeRoot.endsWith(path20.sep) ? worktreeRoot : worktreeRoot + path20.sep;
7394
7867
  const outside = serviceDir !== worktreeRoot && !serviceDir.startsWith(wtRootWithSep);
7395
7868
  if (outside && isLinkedDependencyDir(serviceDir, appRoot)) {
7396
7869
  logInfo(`${name}: source is a dev-linked package (${serviceDir})`);
@@ -7410,7 +7883,7 @@ async function startServices(sessionName, appRoot, devOutput, headless, awsEnv,
7410
7883
  }
7411
7884
  }
7412
7885
  }
7413
- if (!fs17.existsSync(serviceDir)) {
7886
+ if (!fs19.existsSync(serviceDir)) {
7414
7887
  logWarn(
7415
7888
  `Skipped: ${name} (source directory not found: ${serviceDir}${serviceDir !== service.src ? ` \u2014 rebased from ${service.src}` : ""})`
7416
7889
  );
@@ -7442,6 +7915,9 @@ async function startServices(sessionName, appRoot, devOutput, headless, awsEnv,
7442
7915
  // worktrees don't steal each other's workflow tasks. Empty on the primary
7443
7916
  // checkout → omitted → unchanged historical behavior.
7444
7917
  ...taskQueueSuffix ? { MESH_TASK_QUEUE_SUFFIX: taskQueueSuffix } : {},
7918
+ // The service's workstation route, when it has one: what a dev server needs to
7919
+ // accept its public host (Vite allowedHosts, a framework's action origins).
7920
+ ...routeUrls[name] ? { MESH_DEV_PUBLIC_URL: routeUrls[name] } : {},
7445
7921
  // AWS, for the same reason as the Temporal and dev-user overlays above: the
7446
7922
  // sourced env file WINS over the tmux session env, so a service that reads this
7447
7923
  // file gets whatever the file says and nothing else.
@@ -7472,11 +7948,11 @@ async function startServices(sessionName, appRoot, devOutput, headless, awsEnv,
7472
7948
  service.env?.OTEL_RESOURCE_ATTRIBUTES ? logShipperPath() : void 0,
7473
7949
  hostEnv
7474
7950
  );
7475
- execFileSync11("tmux", ["new-window", "-t", sessionName, "-n", name, "-c", serviceDir]);
7476
- execFileSync11("tmux", ["set-option", "-t", `${sessionName}:${name}`, "remain-on-exit", "on"], {
7951
+ execFileSync12("tmux", ["new-window", "-t", sessionName, "-n", name, "-c", serviceDir]);
7952
+ execFileSync12("tmux", ["set-option", "-t", `${sessionName}:${name}`, "remain-on-exit", "on"], {
7477
7953
  stdio: "ignore"
7478
7954
  });
7479
- execFileSync11("tmux", ["send-keys", "-t", `${sessionName}:${name}`, launchCmd, "Enter"]);
7955
+ execFileSync12("tmux", ["send-keys", "-t", `${sessionName}:${name}`, launchCmd, "Enter"]);
7480
7956
  logSuccess(`Started: ${name} (${serviceDir}, port ${service.port})`);
7481
7957
  }
7482
7958
  if (process.env.DEVCONTAINER === "1") {
@@ -7487,7 +7963,7 @@ async function startServices(sessionName, appRoot, devOutput, headless, awsEnv,
7487
7963
  }
7488
7964
  }
7489
7965
  const statusCmd = `watch -n2 -t npx mesh dev --status --session '${sessionName}'`;
7490
- execFileSync11("tmux", ["send-keys", "-t", `${sessionName}:status`, statusCmd, "Enter"]);
7966
+ execFileSync12("tmux", ["send-keys", "-t", `${sessionName}:status`, statusCmd, "Enter"]);
7491
7967
  console.log("");
7492
7968
  logSuccess(`Dev session started: ${sessionName}`);
7493
7969
  if (notLaunched.length > 0)
@@ -7512,7 +7988,9 @@ async function startServices(sessionName, appRoot, devOutput, headless, awsEnv,
7512
7988
  const s = devOutput.services[name];
7513
7989
  const hasCmd = s.command && s.command.length > 0;
7514
7990
  if (hasCmd) {
7515
- if (subdomainCtx && s.port) {
7991
+ if (routeUrls[name]) {
7992
+ console.log(` ${name.padEnd(20)} ${routeUrls[name]} (localhost:${s.port})`);
7993
+ } else if (subdomainCtx && s.port) {
7516
7994
  console.log(` ${name.padEnd(20)} http://${name}.${subdomainCtx.baseDomain}`);
7517
7995
  } else {
7518
7996
  const addr = s.port ? `http://localhost:${s.port}` : "(no port)";
@@ -7621,7 +8099,7 @@ async function restartService(sessionName, serviceName, appRoot, devOutput, awsE
7621
8099
  process.exit(1);
7622
8100
  }
7623
8101
  const waitMs = opts?.waitMs ?? RESTART_READY_TIMEOUT_MS;
7624
- const neverLaunched = !fs17.existsSync(getServiceEnvFilePath(sessionName, serviceName));
8102
+ const neverLaunched = !fs19.existsSync(getServiceEnvFilePath(sessionName, serviceName));
7625
8103
  if (neverLaunched && !opts?.refreshEnv && opts?.stack)
7626
8104
  logInfo(`'${serviceName}' has no launch env file (never started) \u2014 rendering it from the stack.`);
7627
8105
  const refreshEnv = opts?.refreshEnv || neverLaunched && Boolean(opts?.stack);
@@ -7665,6 +8143,8 @@ async function restartService(sessionName, serviceName, appRoot, devOutput, awsE
7665
8143
  // token from `mesh login` (the "restart silently 401s the Hub" trap).
7666
8144
  ...resolveDevUserVars(merged),
7667
8145
  ...taskQueueSuffix ? { MESH_TASK_QUEUE_SUFFIX: taskQueueSuffix } : {},
8146
+ // Same as launch: the workstation route the session holds for this service.
8147
+ ...workstationUrlVar(appRoot, merged, serviceName),
7668
8148
  // Same overlay as launch — a refresh that dropped these would take a working
7669
8149
  // service's credentials away on restart, which is the hardest kind of change to
7670
8150
  // attribute afterwards.
@@ -7678,7 +8158,7 @@ async function restartService(sessionName, serviceName, appRoot, devOutput, awsE
7678
8158
  const envFilePathToRefresh = getServiceEnvFilePath(sessionName, serviceName);
7679
8159
  let previousVars = null;
7680
8160
  try {
7681
- previousVars = parseEnvFile(fs17.readFileSync(envFilePathToRefresh, "utf-8"));
8161
+ previousVars = parseEnvFile(fs19.readFileSync(envFilePathToRefresh, "utf-8"));
7682
8162
  } catch {
7683
8163
  }
7684
8164
  if (previousVars) {
@@ -7699,10 +8179,10 @@ async function restartService(sessionName, serviceName, appRoot, devOutput, awsE
7699
8179
  }
7700
8180
  const target = `${sessionName}:${serviceName}`;
7701
8181
  try {
7702
- execFileSync11("tmux", ["respawn-pane", "-k", "-t", target], { stdio: "ignore" });
8182
+ execFileSync12("tmux", ["respawn-pane", "-k", "-t", target], { stdio: "ignore" });
7703
8183
  } catch {
7704
8184
  try {
7705
- execFileSync11("tmux", ["new-window", "-d", "-t", sessionName, "-n", serviceName], { stdio: "ignore" });
8185
+ execFileSync12("tmux", ["new-window", "-d", "-t", sessionName, "-n", serviceName], { stdio: "ignore" });
7706
8186
  logInfo(`Window '${serviceName}' did not exist \u2014 created it.`);
7707
8187
  } catch {
7708
8188
  logError(`Window '${serviceName}' not found in session, and could not be created.`);
@@ -7710,13 +8190,13 @@ async function restartService(sessionName, serviceName, appRoot, devOutput, awsE
7710
8190
  }
7711
8191
  }
7712
8192
  try {
7713
- execFileSync11("tmux", ["clear-history", "-t", target], { stdio: "ignore" });
8193
+ execFileSync12("tmux", ["clear-history", "-t", target], { stdio: "ignore" });
7714
8194
  } catch {
7715
8195
  }
7716
8196
  const portHeldBeforeStart = waitMs > 0 && await portHeldByAnother(service.port, {
7717
8197
  portAccepts: isPortListening,
7718
8198
  now: () => Date.now(),
7719
- sleep: (ms) => new Promise((resolve9) => setTimeout(resolve9, ms))
8199
+ sleep: (ms) => new Promise((resolve10) => setTimeout(resolve10, ms))
7720
8200
  });
7721
8201
  if (portHeldBeforeStart) {
7722
8202
  logWarn(
@@ -7728,8 +8208,8 @@ async function restartService(sessionName, serviceName, appRoot, devOutput, awsE
7728
8208
  const cmd = service.command.join(" ");
7729
8209
  const envFilePath = getServiceEnvFilePath(sessionName, serviceName);
7730
8210
  let restartCmd;
7731
- if (fs17.existsSync(envFilePath)) {
7732
- let serviceDir = path18.resolve(appRoot, rebaseServiceSrc(service.src, findMonorepoRoot()));
8211
+ if (fs19.existsSync(envFilePath)) {
8212
+ let serviceDir = path20.resolve(appRoot, rebaseServiceSrc(service.src, findMonorepoRoot()));
7733
8213
  if (!isLinkedDependencyDir(serviceDir, appRoot)) serviceDir = relinkedServiceDir(serviceDir, appRoot) ?? serviceDir;
7734
8214
  restartCmd = buildLaunchCommand(
7735
8215
  envFilePath,
@@ -7746,7 +8226,7 @@ async function restartService(sessionName, serviceName, appRoot, devOutput, awsE
7746
8226
  const serviceVars = getServiceEnvVars(service, devOutput.tunnels);
7747
8227
  restartCmd = `${envPrefix(serviceVars)}${cmd}`;
7748
8228
  }
7749
- execFileSync11("tmux", ["send-keys", "-t", target, restartCmd, "Enter"]);
8229
+ execFileSync12("tmux", ["send-keys", "-t", target, restartCmd, "Enter"]);
7750
8230
  if (waitMs <= 0) {
7751
8231
  logSuccess(`Restarted: ${serviceName} (not waiting for readiness \u2014 check \`mesh dev --status\`)`);
7752
8232
  return null;
@@ -7763,7 +8243,7 @@ async function restartService(sessionName, serviceName, appRoot, devOutput, awsE
7763
8243
  portAccepts: isPortListening,
7764
8244
  paneOutput: () => capturePane(target),
7765
8245
  now: () => Date.now(),
7766
- sleep: (ms) => new Promise((resolve9) => setTimeout(resolve9, ms))
8246
+ sleep: (ms) => new Promise((resolve10) => setTimeout(resolve10, ms))
7767
8247
  }
7768
8248
  );
7769
8249
  reportRestartReadiness(serviceName, service.port, readiness);
@@ -7771,7 +8251,7 @@ async function restartService(sessionName, serviceName, appRoot, devOutput, awsE
7771
8251
  }
7772
8252
  function capturePane(target, lines = 2e3) {
7773
8253
  try {
7774
- return execFileSync11("tmux", ["capture-pane", "-p", "-t", target, "-S", `-${lines}`], {
8254
+ return execFileSync12("tmux", ["capture-pane", "-p", "-t", target, "-S", `-${lines}`], {
7775
8255
  encoding: "utf-8",
7776
8256
  stdio: ["ignore", "pipe", "ignore"]
7777
8257
  });
@@ -7828,7 +8308,7 @@ function showLogs(sessionName, serviceName, tail) {
7828
8308
  }
7829
8309
  const target = `${sessionName}:${serviceName}`;
7830
8310
  try {
7831
- const result = execFileSync11("tmux", ["capture-pane", "-t", target, "-p", "-S", `-${tail}`], {
8311
+ const result = execFileSync12("tmux", ["capture-pane", "-t", target, "-p", "-S", `-${tail}`], {
7832
8312
  encoding: "utf-8"
7833
8313
  });
7834
8314
  process.stdout.write(result);
@@ -7865,7 +8345,10 @@ function buildDoctorContext(appRoot, stack, sessionName) {
7865
8345
  function registerDevCommand(program) {
7866
8346
  const dev = program.command("dev").description(
7867
8347
  "Start local dev environment (reads Pulumi stack outputs). The env is rendered from the stack's STORED outputs \u2014 when they are older than the program (git HEAD) or the stack config, mesh dev warns to run `mesh deploy up --stack <stack>` first"
7868
- ).option("--app <path>", "Path to a Pulumi app (relative to monorepo root or cwd)").option("--stack <stack>", "Pulumi stack name (default: auto-detect)").addOption(new Option("--stage <stack>", "Deprecated alias for --stack").hideHelp()).option("--headless", "Start without attaching to tmux").option("--kill", "Kill existing dev session").option("--status", "Show service status").option("--json", "Output status as JSON (with --status)").option(
8348
+ ).option("--app <path>", "Path to a Pulumi app (relative to monorepo root or cwd)").option("--stack <stack>", "Pulumi stack name (default: auto-detect)").addOption(new Option("--stage <stack>", "Deprecated alias for --stack").hideHelp()).option("--headless", "Start without attaching to tmux").option(
8349
+ "--route-suffix <name>",
8350
+ "On a workstation: the suffix of this session's HTTPS names (default: the worktree's name; '' for none)"
8351
+ ).option("--kill", "Kill existing dev session").option("--status", "Show service status").option("--json", "Output status as JSON (with --status)").option(
7869
8352
  "--session <name>",
7870
8353
  "Override the tmux session name (auto-derived per git worktree by default \u2014 omit to keep concurrent worktrees isolated)"
7871
8354
  ).option("--dry-run", "Print the resolved worktree/session/port plan and exit without launching").option("--force", "Relaunch even if workflow code changed since launch (may strand in-flight conversations)").option("--profile <name>", "AWS SSO profile to use (e.g., mesh-dev)").addOption(
@@ -7885,10 +8368,11 @@ function registerDevCommand(program) {
7885
8368
  "tmux"
7886
8369
  );
7887
8370
  dev.command("__token-server <port> <context>", { hidden: true }).action(async (port, context) => {
7888
- const { startTokenServer } = await import("./mesh.dev-token-server-4N6HGQAF.js");
8371
+ const { startTokenServer } = await import("./mesh.dev-token-server-6TNWX5TY.js");
7889
8372
  await startTokenServer(Number(port), context);
7890
8373
  });
7891
8374
  dev.action(async (options) => {
8375
+ if (options.routeSuffix !== void 0) process.env.MESH_DEV_ROUTE_SUFFIX = options.routeSuffix;
7892
8376
  if (options.profile) {
7893
8377
  applyAwsProfileOverride(options.profile);
7894
8378
  }
@@ -8069,7 +8553,7 @@ function registerDevCommand(program) {
8069
8553
  signInServices = [];
8070
8554
  try {
8071
8555
  const appPkg = JSON.parse(
8072
- fs17.readFileSync(path18.join(appRoot, "package.json"), "utf-8")
8556
+ fs19.readFileSync(path20.join(appRoot, "package.json"), "utf-8")
8073
8557
  );
8074
8558
  if (Array.isArray(appPkg?.mesh?.auth?.roles)) {
8075
8559
  authRoles = appPkg.mesh.auth.roles.filter((r) => typeof r === "string");
@@ -8166,7 +8650,7 @@ function registerDevCommand(program) {
8166
8650
  }
8167
8651
  let localDocs;
8168
8652
  try {
8169
- const appPkg = JSON.parse(fs17.readFileSync(path18.join(appRoot, "package.json"), "utf-8"));
8653
+ const appPkg = JSON.parse(fs19.readFileSync(path20.join(appRoot, "package.json"), "utf-8"));
8170
8654
  const declaration = readLocalPublishDeclaration(appPkg, appRoot);
8171
8655
  if (declaration.docs || declaration.authzMetadata || declaration.problems.length > 0) {
8172
8656
  const app = devOutput2.app ?? projectName;
@@ -8176,10 +8660,10 @@ function registerDevCommand(program) {
8176
8660
  if (!httpService || !service?.port) {
8177
8661
  logWarn(`mesh.docs names service '${httpService ?? ""}', which this app does not run \u2014 docs not published.`);
8178
8662
  } else {
8179
- const serviceDir = path18.resolve(appRoot, service.src);
8663
+ const serviceDir = path20.resolve(appRoot, service.src);
8180
8664
  const env = service.env ?? (service.env = {});
8181
8665
  const serviceEnv = {
8182
- ...readDotEnvFile(path18.join(serviceDir, ".env")),
8666
+ ...readDotEnvFile(path20.join(serviceDir, ".env")),
8183
8667
  ...Object.fromEntries(Object.entries(process.env).filter((e) => typeof e[1] === "string")),
8184
8668
  ...Object.fromEntries(Object.entries(env).map(([k, v]) => [k, v.value]))
8185
8669
  };
@@ -8371,9 +8855,9 @@ function registerDevCommand(program) {
8371
8855
  logInfo(`Project: ${projectName}, Stack: ${stack}`);
8372
8856
  const monorepoRoot = findMonorepoRoot();
8373
8857
  const looksLikeStandaloneApp = ["api", "worker", "web"].some(
8374
- (dir) => fs17.existsSync(path18.join(appRoot, dir, "package.json"))
8858
+ (dir) => fs19.existsSync(path20.join(appRoot, dir, "package.json"))
8375
8859
  );
8376
- if (monorepoRoot && path18.resolve(appRoot) === path18.resolve(monorepoRoot) && !looksLikeStandaloneApp) {
8860
+ if (monorepoRoot && path20.resolve(appRoot) === path20.resolve(monorepoRoot) && !looksLikeStandaloneApp) {
8377
8861
  logError(`mesh dev should be run from an app directory, not the monorepo root.
8378
8862
  `);
8379
8863
  logInfo("Try:");
@@ -8808,17 +9292,17 @@ function registerDevCommand(program) {
8808
9292
  // packages/mesh-cli/src/utils/tailscale-targets.ts
8809
9293
  var PORT_BLOCK = 3e3;
8810
9294
  function tenantPortIndex(tenant, registryPath) {
8811
- const p = registryPath ?? path19.join(CONFIG_DIR, "tailscale", "port-registry.json");
9295
+ const p = registryPath ?? path21.join(CONFIG_DIR, "tailscale", "port-registry.json");
8812
9296
  let reg;
8813
9297
  try {
8814
- reg = JSON.parse(fs18.readFileSync(p, "utf8"));
9298
+ reg = JSON.parse(fs20.readFileSync(p, "utf8"));
8815
9299
  } catch {
8816
9300
  reg = { mesh: 0 };
8817
9301
  }
8818
9302
  if (tenant in reg) return reg[tenant];
8819
9303
  const idx = Math.max(-1, ...Object.values(reg)) + 1;
8820
9304
  reg[tenant] = idx;
8821
- fs18.mkdirSync(path19.dirname(p), { recursive: true });
9305
+ fs20.mkdirSync(path21.dirname(p), { recursive: true });
8822
9306
  atomicWriteFileSync(p, JSON.stringify(reg, null, 2), 384);
8823
9307
  return idx;
8824
9308
  }
@@ -8846,7 +9330,7 @@ function resolveTunnelTargets(services, resolveIp, portOffset = 0) {
8846
9330
  }
8847
9331
  function resolveElbIp(host) {
8848
9332
  try {
8849
- const out = execFileSync12("dig", ["+short", host, "@1.1.1.1"], {
9333
+ const out = execFileSync13("dig", ["+short", host, "@1.1.1.1"], {
8850
9334
  encoding: "utf8",
8851
9335
  stdio: ["ignore", "pipe", "ignore"]
8852
9336
  });
@@ -8930,35 +9414,35 @@ function createForwarder(opts) {
8930
9414
 
8931
9415
  // packages/mesh-cli/src/utils/tunnel-ownership.ts
8932
9416
  import * as crypto2 from "node:crypto";
8933
- import * as fs19 from "node:fs";
9417
+ import * as fs21 from "node:fs";
8934
9418
  import * as net5 from "node:net";
8935
- import * as path20 from "node:path";
8936
- import { execFileSync as execFileSync13 } from "node:child_process";
9419
+ import * as path22 from "node:path";
9420
+ import { execFileSync as execFileSync14 } from "node:child_process";
8937
9421
  function mintRunnerToken() {
8938
9422
  return crypto2.randomBytes(16).toString("hex");
8939
9423
  }
8940
9424
  function readRunnerManifest(file) {
8941
- if (!fs19.existsSync(file)) return null;
9425
+ if (!fs21.existsSync(file)) return null;
8942
9426
  try {
8943
- return JSON.parse(fs19.readFileSync(file, "utf8"));
9427
+ return JSON.parse(fs21.readFileSync(file, "utf8"));
8944
9428
  } catch {
8945
9429
  return null;
8946
9430
  }
8947
9431
  }
8948
9432
  function writeRunnerManifest(file, manifest) {
8949
- fs19.mkdirSync(path20.dirname(file), { recursive: true });
8950
- fs19.writeFileSync(file, JSON.stringify(manifest, null, 2), { mode: 384 });
9433
+ fs21.mkdirSync(path22.dirname(file), { recursive: true });
9434
+ fs21.writeFileSync(file, JSON.stringify(manifest, null, 2), { mode: 384 });
8951
9435
  }
8952
9436
  function clearRunnerManifest(file, token) {
8953
9437
  if (token !== void 0 && readRunnerManifest(file)?.token !== token) return;
8954
9438
  try {
8955
- fs19.unlinkSync(file);
9439
+ fs21.unlinkSync(file);
8956
9440
  } catch {
8957
9441
  }
8958
9442
  }
8959
9443
  function getProcessGroupId(pid) {
8960
9444
  try {
8961
- const out = execFileSync13("ps", ["-o", "pgid=", "-p", String(pid)], {
9445
+ const out = execFileSync14("ps", ["-o", "pgid=", "-p", String(pid)], {
8962
9446
  encoding: "utf8",
8963
9447
  stdio: ["ignore", "pipe", "ignore"]
8964
9448
  }).trim();
@@ -8984,7 +9468,7 @@ function parseRunnerPidsFromPs(psOutput, tenant, exclude) {
8984
9468
  }
8985
9469
  function discoverRunnerPids(tenant, exclude) {
8986
9470
  try {
8987
- const out = execFileSync13("ps", ["ax", "-o", "pid=,command="], {
9471
+ const out = execFileSync14("ps", ["ax", "-o", "pid=,command="], {
8988
9472
  encoding: "utf8",
8989
9473
  stdio: ["ignore", "pipe", "ignore"]
8990
9474
  });
@@ -8994,7 +9478,7 @@ function discoverRunnerPids(tenant, exclude) {
8994
9478
  }
8995
9479
  }
8996
9480
  function startControlServer(payload) {
8997
- return new Promise((resolve9, reject) => {
9481
+ return new Promise((resolve10, reject) => {
8998
9482
  const server = net5.createServer((socket) => {
8999
9483
  socket.on("error", () => {
9000
9484
  });
@@ -9007,12 +9491,12 @@ function startControlServer(payload) {
9007
9491
  });
9008
9492
  server.on("error", reject);
9009
9493
  server.listen(0, "127.0.0.1", () => {
9010
- resolve9({ server, port: server.address().port });
9494
+ resolve10({ server, port: server.address().port });
9011
9495
  });
9012
9496
  });
9013
9497
  }
9014
9498
  function queryControl(port, timeoutMs = 700) {
9015
- return new Promise((resolve9) => {
9499
+ return new Promise((resolve10) => {
9016
9500
  const s = new net5.Socket();
9017
9501
  let buf = "";
9018
9502
  let done = false;
@@ -9020,7 +9504,7 @@ function queryControl(port, timeoutMs = 700) {
9020
9504
  if (done) return;
9021
9505
  done = true;
9022
9506
  s.destroy();
9023
- resolve9(v);
9507
+ resolve10(v);
9024
9508
  };
9025
9509
  s.setTimeout(timeoutMs);
9026
9510
  s.once("timeout", () => fin(null));
@@ -9076,7 +9560,7 @@ async function pollOwnership(check, deadlineMs) {
9076
9560
  }
9077
9561
  function describePortOwner(port) {
9078
9562
  try {
9079
- const out = execFileSync13("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-Fcp"], {
9563
+ const out = execFileSync14("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-Fcp"], {
9080
9564
  encoding: "utf8",
9081
9565
  stdio: ["ignore", "pipe", "ignore"]
9082
9566
  });
@@ -9118,58 +9602,58 @@ function buildSquatterError(tenant, squatted, detail) {
9118
9602
  // packages/mesh-cli/src/utils/tailscale.ts
9119
9603
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
9120
9604
  function tenantStateDir(tenant) {
9121
- return path21.join(CONFIG_DIR, "tailscale", tenant);
9605
+ return path23.join(CONFIG_DIR, "tailscale", tenant);
9122
9606
  }
9123
9607
  function socketPath(tenant) {
9124
- return path21.join(tenantStateDir(tenant), "tailscaled.sock");
9608
+ return path23.join(tenantStateDir(tenant), "tailscaled.sock");
9125
9609
  }
9126
9610
  function logPath(tenant) {
9127
- return path21.join(tenantStateDir(tenant), "tailscaled.log");
9611
+ return path23.join(tenantStateDir(tenant), "tailscaled.log");
9128
9612
  }
9129
9613
  function statePath(tenant) {
9130
- return path21.join(tenantStateDir(tenant), "tunnel-state.json");
9614
+ return path23.join(tenantStateDir(tenant), "tunnel-state.json");
9131
9615
  }
9132
9616
  function daemonMetaPath(tenant) {
9133
- return path21.join(tenantStateDir(tenant), "daemon.json");
9617
+ return path23.join(tenantStateDir(tenant), "daemon.json");
9134
9618
  }
9135
9619
  function runnerManifestPath(tenant) {
9136
- return path21.join(tenantStateDir(tenant), "runner.json");
9620
+ return path23.join(tenantStateDir(tenant), "runner.json");
9137
9621
  }
9138
9622
  function readTunnelState(tenant) {
9139
9623
  const p = statePath(tenant);
9140
- if (!fs20.existsSync(p)) return null;
9624
+ if (!fs22.existsSync(p)) return null;
9141
9625
  try {
9142
- return JSON.parse(fs20.readFileSync(p, "utf8"));
9626
+ return JSON.parse(fs22.readFileSync(p, "utf8"));
9143
9627
  } catch {
9144
9628
  return null;
9145
9629
  }
9146
9630
  }
9147
9631
  function writeTunnelState(tenant, state) {
9148
- fs20.mkdirSync(tenantStateDir(tenant), { recursive: true });
9632
+ fs22.mkdirSync(tenantStateDir(tenant), { recursive: true });
9149
9633
  atomicWriteFileSync(statePath(tenant), JSON.stringify(state, null, 2), 384);
9150
9634
  }
9151
9635
  function clearTunnelState(tenant) {
9152
9636
  try {
9153
- fs20.unlinkSync(statePath(tenant));
9637
+ fs22.unlinkSync(statePath(tenant));
9154
9638
  } catch {
9155
9639
  }
9156
9640
  }
9157
9641
  function readDaemonMeta(tenant) {
9158
9642
  const p = daemonMetaPath(tenant);
9159
- if (!fs20.existsSync(p)) return null;
9643
+ if (!fs22.existsSync(p)) return null;
9160
9644
  try {
9161
- return JSON.parse(fs20.readFileSync(p, "utf8"));
9645
+ return JSON.parse(fs22.readFileSync(p, "utf8"));
9162
9646
  } catch {
9163
9647
  return null;
9164
9648
  }
9165
9649
  }
9166
9650
  function writeDaemonMeta(tenant, meta) {
9167
- fs20.mkdirSync(tenantStateDir(tenant), { recursive: true });
9651
+ fs22.mkdirSync(tenantStateDir(tenant), { recursive: true });
9168
9652
  atomicWriteFileSync(daemonMetaPath(tenant), JSON.stringify(meta, null, 2), 384);
9169
9653
  }
9170
9654
  function clearDaemonMeta(tenant) {
9171
9655
  try {
9172
- fs20.unlinkSync(daemonMetaPath(tenant));
9656
+ fs22.unlinkSync(daemonMetaPath(tenant));
9173
9657
  } catch {
9174
9658
  }
9175
9659
  }
@@ -9189,18 +9673,18 @@ function tailscaleBinPath() {
9189
9673
  }
9190
9674
  function resolveBrewBin(name) {
9191
9675
  const brew = `/opt/homebrew/bin/${name}`;
9192
- if (fs20.existsSync(brew)) return brew;
9676
+ if (fs22.existsSync(brew)) return brew;
9193
9677
  try {
9194
- const p = execFileSync14("which", [name], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
9678
+ const p = execFileSync15("which", [name], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
9195
9679
  if (p) return p;
9196
9680
  } catch {
9197
9681
  }
9198
9682
  throw new Error(`${name} not found \u2014 install the standalone binary: brew install tailscale`);
9199
9683
  }
9200
9684
  function daemonState(tenant) {
9201
- if (!fs20.existsSync(socketPath(tenant))) return { backendState: "Down" };
9685
+ if (!fs22.existsSync(socketPath(tenant))) return { backendState: "Down" };
9202
9686
  try {
9203
- const out = execFileSync14(
9687
+ const out = execFileSync15(
9204
9688
  tailscaleBinPath(),
9205
9689
  ["--socket", socketPath(tenant), "status", "--json"],
9206
9690
  { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
@@ -9220,7 +9704,7 @@ function findRunningDaemon(tenant) {
9220
9704
  const sock = socketPath(tenant);
9221
9705
  let out;
9222
9706
  try {
9223
- out = execFileSync14("ps", ["ax", "-o", "pid=,command="], {
9707
+ out = execFileSync15("ps", ["ax", "-o", "pid=,command="], {
9224
9708
  encoding: "utf8",
9225
9709
  stdio: ["ignore", "pipe", "ignore"]
9226
9710
  });
@@ -9236,14 +9720,14 @@ function findRunningDaemon(tenant) {
9236
9720
  return null;
9237
9721
  }
9238
9722
  function portAccepts(port, timeoutMs = 400) {
9239
- return new Promise((resolve9) => {
9723
+ return new Promise((resolve10) => {
9240
9724
  const s = new net6.Socket();
9241
9725
  let done = false;
9242
9726
  const fin = (v) => {
9243
9727
  if (done) return;
9244
9728
  done = true;
9245
9729
  s.destroy();
9246
- resolve9(v);
9730
+ resolve10(v);
9247
9731
  };
9248
9732
  s.setTimeout(timeoutMs);
9249
9733
  s.once("connect", () => fin(true));
@@ -9272,8 +9756,8 @@ async function ensureDaemon(tenant, opts) {
9272
9756
  }
9273
9757
  }
9274
9758
  const dir = tenantStateDir(tenant);
9275
- fs20.mkdirSync(dir, { recursive: true });
9276
- const log = fs20.openSync(logPath(tenant), "a");
9759
+ fs22.mkdirSync(dir, { recursive: true });
9760
+ const log = fs22.openSync(logPath(tenant), "a");
9277
9761
  try {
9278
9762
  const child = spawn2(
9279
9763
  tailscaledBinPath(),
@@ -9293,7 +9777,7 @@ async function ensureDaemon(tenant, opts) {
9293
9777
  child.unref();
9294
9778
  if (child.pid) writeDaemonMeta(tenant, { socksPort: opts.socksPort, pid: child.pid });
9295
9779
  } finally {
9296
- fs20.closeSync(log);
9780
+ fs22.closeSync(log);
9297
9781
  }
9298
9782
  for (let i = 0; i < 40; i++) {
9299
9783
  await sleep(250);
@@ -9319,7 +9803,7 @@ function stopDaemon(tenant) {
9319
9803
  }
9320
9804
  for (const p of [socketPath(tenant)]) {
9321
9805
  try {
9322
- fs20.unlinkSync(p);
9806
+ fs22.unlinkSync(p);
9323
9807
  } catch {
9324
9808
  }
9325
9809
  }
@@ -9341,7 +9825,7 @@ function buildTailscaleUpArgs(opts) {
9341
9825
  }
9342
9826
  async function joinHeadscale(tenant, loginServer, opts = {}) {
9343
9827
  if (daemonState(tenant).backendState === "Running") return;
9344
- const hostname2 = `mesh-${tenant}-${os3.hostname().split(".")[0]}`;
9828
+ const hostname2 = `mesh-${tenant}-${os5.hostname().split(".")[0]}`;
9345
9829
  const child = spawn2(
9346
9830
  tailscaleBinPath(),
9347
9831
  buildTailscaleUpArgs({ socket: socketPath(tenant), loginServer, hostname: hostname2, preAuthKey: opts.preAuthKey }),
@@ -9384,7 +9868,7 @@ async function joinHeadscale(tenant, loginServer, opts = {}) {
9384
9868
  }
9385
9869
  function logout(tenant) {
9386
9870
  try {
9387
- execFileSync14(tailscaleBinPath(), ["--socket", socketPath(tenant), "logout"], { stdio: "ignore" });
9871
+ execFileSync15(tailscaleBinPath(), ["--socket", socketPath(tenant), "logout"], { stdio: "ignore" });
9388
9872
  } catch {
9389
9873
  }
9390
9874
  }
@@ -9464,7 +9948,7 @@ async function assertTunnelPortsFree(tenant, ports) {
9464
9948
  async function ensureForwarders(state) {
9465
9949
  const prior = readTunnelState(state.tenant);
9466
9950
  const expected = { socksPort: state.socksPort, ports: state.tunnels.map((t) => t.localPort) };
9467
- const runnerAlive = prior?.managed === "launchd" ? fs20.existsSync(launchAgentPath(state.tenant)) : pidAlive(prior?.supervisorPid);
9951
+ const runnerAlive = prior?.managed === "launchd" ? fs22.existsSync(launchAgentPath(state.tenant)) : pidAlive(prior?.supervisorPid);
9468
9952
  if (prior && runnerAlive && tunnelTargetsEqual(prior.tunnels, state.tunnels) && // A prior supervisor spawned for a DIFFERENT SOCKS port still binds the same
9469
9953
  // local ports but dials a dead upstream — reusing it silently strands every
9470
9954
  // tunnel. The port must match, and the runner must PROVE it is
@@ -9489,7 +9973,7 @@ async function ensureForwarders(state) {
9489
9973
  if (launchdAvailable()) {
9490
9974
  state.managed = "launchd";
9491
9975
  writeTunnelState(state.tenant, state);
9492
- if (path21.isAbsolute(meshBin) && installLaunchAgent(state.tenant, meshBin)) {
9976
+ if (path23.isAbsolute(meshBin) && installLaunchAgent(state.tenant, meshBin)) {
9493
9977
  if ((await awaitOwnership(state.tenant, expected, 8e3)).ok) return 0;
9494
9978
  uninstallLaunchAgent(state.tenant);
9495
9979
  await assertTunnelPortsFree(state.tenant, expected.ports);
@@ -9621,7 +10105,7 @@ function forwardersNeedingRebind(targets, listening) {
9621
10105
  return targets.filter((t) => !listening.has(t.localPort));
9622
10106
  }
9623
10107
  function launchAgentPath(tenant) {
9624
- return path21.join(os3.homedir(), "Library", "LaunchAgents", `${launchAgentLabel(tenant)}.plist`);
10108
+ return path23.join(os5.homedir(), "Library", "LaunchAgents", `${launchAgentLabel(tenant)}.plist`);
9625
10109
  }
9626
10110
  function guiDomain() {
9627
10111
  return `gui/${process.getuid?.() ?? 0}`;
@@ -9630,8 +10114,8 @@ var _launchdAvailable;
9630
10114
  function launchdAvailable() {
9631
10115
  if (_launchdAvailable !== void 0) return _launchdAvailable;
9632
10116
  try {
9633
- execFileSync14("which", ["launchctl"], { stdio: "ignore" });
9634
- execFileSync14("launchctl", ["print", guiDomain()], { stdio: "ignore" });
10117
+ execFileSync15("which", ["launchctl"], { stdio: "ignore" });
10118
+ execFileSync15("launchctl", ["print", guiDomain()], { stdio: "ignore" });
9635
10119
  _launchdAvailable = true;
9636
10120
  } catch {
9637
10121
  _launchdAvailable = false;
@@ -9643,30 +10127,30 @@ function installLaunchAgent(tenant, meshBin) {
9643
10127
  try {
9644
10128
  let realMeshBin = meshBin;
9645
10129
  try {
9646
- realMeshBin = fs20.realpathSync(meshBin);
10130
+ realMeshBin = fs22.realpathSync(meshBin);
9647
10131
  } catch {
9648
10132
  }
9649
10133
  let pathEnv = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
9650
10134
  try {
9651
- pathEnv = `${path21.dirname(tailscaledBinPath())}:${pathEnv}`;
10135
+ pathEnv = `${path23.dirname(tailscaledBinPath())}:${pathEnv}`;
9652
10136
  } catch {
9653
10137
  }
9654
- fs20.mkdirSync(path21.dirname(plistPath), { recursive: true });
9655
- fs20.writeFileSync(
10138
+ fs22.mkdirSync(path23.dirname(plistPath), { recursive: true });
10139
+ fs22.writeFileSync(
9656
10140
  plistPath,
9657
10141
  renderLaunchAgentPlist({
9658
10142
  tenant,
9659
10143
  meshBin: realMeshBin,
9660
10144
  nodeBin: process.execPath,
9661
- logPath: path21.join(tenantStateDir(tenant), "runner.log"),
10145
+ logPath: path23.join(tenantStateDir(tenant), "runner.log"),
9662
10146
  pathEnv
9663
10147
  })
9664
10148
  );
9665
10149
  try {
9666
- execFileSync14("launchctl", ["bootout", guiDomain(), plistPath], { stdio: "ignore" });
10150
+ execFileSync15("launchctl", ["bootout", guiDomain(), plistPath], { stdio: "ignore" });
9667
10151
  } catch {
9668
10152
  }
9669
- execFileSync14("launchctl", ["bootstrap", guiDomain(), plistPath], { stdio: "ignore" });
10153
+ execFileSync15("launchctl", ["bootstrap", guiDomain(), plistPath], { stdio: "ignore" });
9670
10154
  return true;
9671
10155
  } catch {
9672
10156
  return false;
@@ -9675,11 +10159,11 @@ function installLaunchAgent(tenant, meshBin) {
9675
10159
  function uninstallLaunchAgent(tenant) {
9676
10160
  const plistPath = launchAgentPath(tenant);
9677
10161
  try {
9678
- execFileSync14("launchctl", ["bootout", guiDomain(), plistPath], { stdio: "ignore" });
10162
+ execFileSync15("launchctl", ["bootout", guiDomain(), plistPath], { stdio: "ignore" });
9679
10163
  } catch {
9680
10164
  }
9681
10165
  try {
9682
- fs20.unlinkSync(plistPath);
10166
+ fs22.unlinkSync(plistPath);
9683
10167
  } catch {
9684
10168
  }
9685
10169
  }
@@ -9769,24 +10253,24 @@ function deriveLoginServer2(context) {
9769
10253
  return `https://${parts.join(".")}`;
9770
10254
  }
9771
10255
  function findFreePort2() {
9772
- return new Promise((resolve9, reject) => {
10256
+ return new Promise((resolve10, reject) => {
9773
10257
  const s = net7.createServer();
9774
10258
  s.on("error", reject);
9775
10259
  s.listen(0, "127.0.0.1", () => {
9776
10260
  const port = s.address().port;
9777
- s.close(() => resolve9(port));
10261
+ s.close(() => resolve10(port));
9778
10262
  });
9779
10263
  });
9780
10264
  }
9781
10265
  function canConnect2(port) {
9782
- return new Promise((resolve9) => {
10266
+ return new Promise((resolve10) => {
9783
10267
  const s = new net7.Socket();
9784
10268
  let settled = false;
9785
10269
  const done = (ok) => {
9786
10270
  if (settled) return;
9787
10271
  settled = true;
9788
10272
  s.destroy();
9789
- resolve9(ok);
10273
+ resolve10(ok);
9790
10274
  };
9791
10275
  s.setTimeout(500);
9792
10276
  s.once("connect", () => done(true));
@@ -9908,7 +10392,7 @@ function headscaleExec(namespace, args, opts) {
9908
10392
  stdio: ["pipe", "pipe", "pipe"]
9909
10393
  };
9910
10394
  try {
9911
- const result = execFileSync15(
10395
+ const result = execFileSync16(
9912
10396
  "kubectl",
9913
10397
  [
9914
10398
  "exec",
@@ -9937,7 +10421,7 @@ function clusterName(tenant, env) {
9937
10421
  }
9938
10422
  function assertPodReady(namespace, options) {
9939
10423
  try {
9940
- const output = execFileSync15(
10424
+ const output = execFileSync16(
9941
10425
  "kubectl",
9942
10426
  [
9943
10427
  "get",
@@ -9972,7 +10456,7 @@ async function vpnStatus(options) {
9972
10456
  const ns = resolveNamespace(options);
9973
10457
  logInfo(`Checking Headscale in namespace ${ns}...`);
9974
10458
  assertPodReady(ns, options);
9975
- const podJson = execFileSync15(
10459
+ const podJson = execFileSync16(
9976
10460
  "kubectl",
9977
10461
  [
9978
10462
  "get",
@@ -9994,7 +10478,7 @@ async function vpnStatus(options) {
9994
10478
  const restarts = container?.restartCount ?? 0;
9995
10479
  let endpoint = "unknown";
9996
10480
  try {
9997
- endpoint = execFileSync15(
10481
+ endpoint = execFileSync16(
9998
10482
  "kubectl",
9999
10483
  [
10000
10484
  "get",
@@ -10137,19 +10621,19 @@ function parseContextTenantEnv(context) {
10137
10621
  return { tenant: tenant || "mesh", env: env || "dev" };
10138
10622
  }
10139
10623
  function findFreePort3() {
10140
- return new Promise((resolve9, reject) => {
10624
+ return new Promise((resolve10, reject) => {
10141
10625
  const srv = net8.createServer();
10142
10626
  srv.on("error", reject);
10143
10627
  srv.listen(0, "127.0.0.1", () => {
10144
10628
  const addr = srv.address();
10145
10629
  const port = typeof addr === "object" && addr ? addr.port : 0;
10146
- srv.close(() => resolve9(port));
10630
+ srv.close(() => resolve10(port));
10147
10631
  });
10148
10632
  });
10149
10633
  }
10150
10634
  function findTailscale() {
10151
10635
  try {
10152
- return execFileSync15("which", ["tailscale"], {
10636
+ return execFileSync16("which", ["tailscale"], {
10153
10637
  encoding: "utf-8",
10154
10638
  stdio: ["pipe", "pipe", "pipe"]
10155
10639
  }).trim() || null;
@@ -10159,7 +10643,7 @@ function findTailscale() {
10159
10643
  }
10160
10644
  function tailscaleStatus() {
10161
10645
  try {
10162
- const json = execFileSync15("tailscale", ["status", "--json"], {
10646
+ const json = execFileSync16("tailscale", ["status", "--json"], {
10163
10647
  encoding: "utf-8",
10164
10648
  stdio: ["pipe", "pipe", "pipe"]
10165
10649
  });
@@ -10240,7 +10724,7 @@ async function vpnConnectSystem(endpoint) {
10240
10724
  "--accept-routes"
10241
10725
  ];
10242
10726
  try {
10243
- execFileSync15("tailscale", upArgs, { stdio: "inherit" });
10727
+ execFileSync16("tailscale", upArgs, { stdio: "inherit" });
10244
10728
  console.log("");
10245
10729
  logSuccess("VPN connected.");
10246
10730
  } catch (error) {
@@ -10267,7 +10751,7 @@ async function vpnDisconnect(opts) {
10267
10751
  }
10268
10752
  logInfo("Disconnecting system VPN\u2026");
10269
10753
  try {
10270
- execFileSync15("tailscale", ["down"], { stdio: "inherit" });
10754
+ execFileSync16("tailscale", ["down"], { stdio: "inherit" });
10271
10755
  logSuccess("VPN disconnected.");
10272
10756
  } catch (error) {
10273
10757
  const code = error.status;
@@ -10319,12 +10803,12 @@ function registerVpnCommands(program) {
10319
10803
  }
10320
10804
 
10321
10805
  // packages/mesh-cli/src/commands/login.ts
10322
- var CONFIG_DIR = path22.join(
10323
- process.env.XDG_CONFIG_HOME ?? path22.join(process.env.HOME ?? "~", ".config"),
10806
+ var CONFIG_DIR = path24.join(
10807
+ process.env.XDG_CONFIG_HOME ?? path24.join(process.env.HOME ?? "~", ".config"),
10324
10808
  "mesh"
10325
10809
  );
10326
- var CONFIG_FILE = path22.join(CONFIG_DIR, "config.json");
10327
- var CREDENTIALS_FILE = path22.join(CONFIG_DIR, "credentials.json");
10810
+ var CONFIG_FILE = path24.join(CONFIG_DIR, "config.json");
10811
+ var CREDENTIALS_FILE = path24.join(CONFIG_DIR, "credentials.json");
10328
10812
  var REDIRECT_PORT = 9876;
10329
10813
  var REDIRECT_URI = `http://localhost:${REDIRECT_PORT}/callback`;
10330
10814
  var BASE_SCOPES = "openid email profile offline_access urn:zitadel:iam:org:project:id:zitadel:aud";
@@ -10340,9 +10824,9 @@ function withDiscoveredFields(config, data) {
10340
10824
  return out;
10341
10825
  }
10342
10826
  function readConfig() {
10343
- if (!fs21.existsSync(CONFIG_FILE)) return {};
10827
+ if (!fs23.existsSync(CONFIG_FILE)) return {};
10344
10828
  try {
10345
- return JSON.parse(fs21.readFileSync(CONFIG_FILE, "utf-8"));
10829
+ return JSON.parse(fs23.readFileSync(CONFIG_FILE, "utf-8"));
10346
10830
  } catch {
10347
10831
  return {};
10348
10832
  }
@@ -10350,15 +10834,15 @@ function readConfig() {
10350
10834
  function writeContextConfig(context, config) {
10351
10835
  const existing = readConfig();
10352
10836
  existing[context] = config;
10353
- fs21.mkdirSync(CONFIG_DIR, { recursive: true });
10354
- fs21.writeFileSync(CONFIG_FILE, JSON.stringify(existing, null, 2));
10837
+ fs23.mkdirSync(CONFIG_DIR, { recursive: true });
10838
+ fs23.writeFileSync(CONFIG_FILE, JSON.stringify(existing, null, 2));
10355
10839
  }
10356
10840
  function clearContextConfig(context) {
10357
10841
  const existing = readConfig();
10358
10842
  if (!(context in existing)) return;
10359
10843
  delete existing[context];
10360
- fs21.mkdirSync(CONFIG_DIR, { recursive: true });
10361
- fs21.writeFileSync(CONFIG_FILE, JSON.stringify(existing, null, 2));
10844
+ fs23.mkdirSync(CONFIG_DIR, { recursive: true });
10845
+ fs23.writeFileSync(CONFIG_FILE, JSON.stringify(existing, null, 2));
10362
10846
  }
10363
10847
  function getContextConfig(context) {
10364
10848
  const config = readConfig();
@@ -10465,9 +10949,9 @@ function discoverConfigFromWellKnown(domain, contextKey = domain, opts = {}) {
10465
10949
  return discoverConfig(domain, contextKey, opts);
10466
10950
  }
10467
10951
  function readAllCredentials() {
10468
- if (!fs21.existsSync(CREDENTIALS_FILE)) return {};
10952
+ if (!fs23.existsSync(CREDENTIALS_FILE)) return {};
10469
10953
  try {
10470
- return JSON.parse(fs21.readFileSync(CREDENTIALS_FILE, "utf-8"));
10954
+ return JSON.parse(fs23.readFileSync(CREDENTIALS_FILE, "utf-8"));
10471
10955
  } catch {
10472
10956
  return {};
10473
10957
  }
@@ -10476,21 +10960,21 @@ function readCredentials(context) {
10476
10960
  return readAllCredentials()[context] ?? null;
10477
10961
  }
10478
10962
  var atomicWriteCounter = 0;
10479
- function atomicWriteFileSync(path23, data, mode) {
10480
- const tmpPath = `${path23}.${process.pid}.${atomicWriteCounter++}.tmp`;
10963
+ function atomicWriteFileSync(path25, data, mode) {
10964
+ const tmpPath = `${path25}.${process.pid}.${atomicWriteCounter++}.tmp`;
10481
10965
  try {
10482
- fs21.writeFileSync(tmpPath, data, { mode });
10483
- fs21.renameSync(tmpPath, path23);
10966
+ fs23.writeFileSync(tmpPath, data, { mode });
10967
+ fs23.renameSync(tmpPath, path25);
10484
10968
  } catch (err) {
10485
10969
  try {
10486
- fs21.unlinkSync(tmpPath);
10970
+ fs23.unlinkSync(tmpPath);
10487
10971
  } catch {
10488
10972
  }
10489
10973
  throw err;
10490
10974
  }
10491
10975
  }
10492
10976
  function writeCredentials(context, creds) {
10493
- fs21.mkdirSync(CONFIG_DIR, { recursive: true });
10977
+ fs23.mkdirSync(CONFIG_DIR, { recursive: true });
10494
10978
  const all = readAllCredentials();
10495
10979
  all[context] = creds;
10496
10980
  atomicWriteFileSync(CREDENTIALS_FILE, JSON.stringify(all, null, 2), 384);
@@ -10498,8 +10982,8 @@ function writeCredentials(context, creds) {
10498
10982
  function clearCredentials(context) {
10499
10983
  const all = readAllCredentials();
10500
10984
  delete all[context];
10501
- fs21.mkdirSync(CONFIG_DIR, { recursive: true });
10502
- fs21.writeFileSync(CREDENTIALS_FILE, JSON.stringify(all, null, 2), {
10985
+ fs23.mkdirSync(CONFIG_DIR, { recursive: true });
10986
+ fs23.writeFileSync(CREDENTIALS_FILE, JSON.stringify(all, null, 2), {
10503
10987
  mode: 384
10504
10988
  });
10505
10989
  }
@@ -10553,7 +11037,7 @@ async function refreshTokens(issuer, clientId, refreshToken) {
10553
11037
  return resp.json();
10554
11038
  }
10555
11039
  function login(context, config) {
10556
- return new Promise((resolve9, reject) => {
11040
+ return new Promise((resolve10, reject) => {
10557
11041
  const codeVerifier = generateCodeVerifier();
10558
11042
  const codeChallenge = generateCodeChallenge(codeVerifier);
10559
11043
  const state = base64url(crypto3.randomBytes(16));
@@ -10632,7 +11116,7 @@ function login(context, config) {
10632
11116
  }
10633
11117
  hintVpnIfDisconnected(context);
10634
11118
  teardown();
10635
- resolve9();
11119
+ resolve10();
10636
11120
  } catch (err) {
10637
11121
  teardown();
10638
11122
  reject(err);
@@ -10648,9 +11132,9 @@ function login(context, config) {
10648
11132
  const url = authUrl.toString();
10649
11133
  try {
10650
11134
  if (process.platform === "darwin") {
10651
- execFileSync16("open", [url], { stdio: "ignore" });
11135
+ execFileSync17("open", [url], { stdio: "ignore" });
10652
11136
  } else if (process.platform === "linux") {
10653
- execFileSync16("xdg-open", [url], { stdio: "ignore" });
11137
+ execFileSync17("xdg-open", [url], { stdio: "ignore" });
10654
11138
  } else {
10655
11139
  logInfo(`Open this URL in your browser:
10656
11140
  ${url}`);
@@ -10704,9 +11188,9 @@ async function attemptDeviceCode(context, config) {
10704
11188
  logInfo("Waiting for authorization...");
10705
11189
  try {
10706
11190
  if (process.platform === "darwin") {
10707
- execFileSync16("open", [openUrl], { stdio: "ignore" });
11191
+ execFileSync17("open", [openUrl], { stdio: "ignore" });
10708
11192
  } else if (process.platform === "linux") {
10709
- execFileSync16("xdg-open", [openUrl], { stdio: "ignore" });
11193
+ execFileSync17("xdg-open", [openUrl], { stdio: "ignore" });
10710
11194
  }
10711
11195
  } catch {
10712
11196
  }
@@ -10769,7 +11253,7 @@ function loginTimeoutMessage(context) {
10769
11253
  }
10770
11254
  function isRemoteEnvironment() {
10771
11255
  if (process.env.REMOTE_CONTAINERS || process.env.CODESPACES) return true;
10772
- if (fs21.existsSync("/.dockerenv")) return true;
11256
+ if (fs23.existsSync("/.dockerenv")) return true;
10773
11257
  if (process.env.SSH_CLIENT || process.env.SSH_TTY) return true;
10774
11258
  if (!process.stdout.isTTY || !process.stdin.isTTY) return true;
10775
11259
  return false;
@@ -11023,9 +11507,9 @@ function refuseReservedRegistryContext(context, verb) {
11023
11507
  }
11024
11508
  async function hintRegistryIfMissing() {
11025
11509
  try {
11026
- const { readRegistrySession } = await import("./mesh.registry-identity-DSTAHXUS.js");
11510
+ const { readRegistrySession } = await import("./mesh.registry-identity-QOGDEMZO.js");
11027
11511
  if (readRegistrySession()) return;
11028
- const { probeRegistryToken: probeRegistryToken2 } = await import("./mesh.auth-preflight-JTUNS42O.js");
11512
+ const { probeRegistryToken: probeRegistryToken2 } = await import("./mesh.auth-preflight-3ASNYA3I.js");
11029
11513
  const probe = await probeRegistryToken2();
11030
11514
  if (probe.state === "missing" || probe.state === "expired") {
11031
11515
  logInfo(REGISTRY_SEPARATE_HINT);
@@ -11157,12 +11641,12 @@ export AWS_REGION=${shellSingleQuote(args.region)}
11157
11641
  `;
11158
11642
  }
11159
11643
  function resolveUserAwsConfigPath(env = process.env) {
11160
- return env.AWS_CONFIG_FILE ?? path22.join(env.HOME ?? "~", ".aws", "config");
11644
+ return env.AWS_CONFIG_FILE ?? path24.join(env.HOME ?? "~", ".aws", "config");
11161
11645
  }
11162
11646
  function canWriteDir(dir) {
11163
11647
  try {
11164
- fs21.mkdirSync(dir, { recursive: true });
11165
- fs21.accessSync(dir, fs21.constants.W_OK);
11648
+ fs23.mkdirSync(dir, { recursive: true });
11649
+ fs23.accessSync(dir, fs23.constants.W_OK);
11166
11650
  return true;
11167
11651
  } catch {
11168
11652
  return false;
@@ -11170,9 +11654,9 @@ function canWriteDir(dir) {
11170
11654
  }
11171
11655
  function resolveAwsConfigTarget(env = process.env) {
11172
11656
  if (env.AWS_CONFIG_FILE) return { configPath: env.AWS_CONFIG_FILE, redirected: false };
11173
- const real = path22.join(env.HOME ?? "~", ".aws", "config");
11174
- if (canWriteDir(path22.dirname(real))) return { configPath: real, redirected: false };
11175
- return { configPath: path22.join(CONFIG_DIR, "aws-config"), redirected: true };
11657
+ const real = path24.join(env.HOME ?? "~", ".aws", "config");
11658
+ if (canWriteDir(path24.dirname(real))) return { configPath: real, redirected: false };
11659
+ return { configPath: path24.join(CONFIG_DIR, "aws-config"), redirected: true };
11176
11660
  }
11177
11661
  function resolveRoleOrExplain(opts, config) {
11178
11662
  const role = opts.role ?? process.env.MESH_AWS_ROLE ?? config.defaultRole;
@@ -11304,10 +11788,10 @@ async function exportAwsCredentials(context, config, opts) {
11304
11788
  const sanitized = context.replace(/[^A-Za-z0-9_-]/g, "-");
11305
11789
  const profileName = `mesh-${sanitized}`;
11306
11790
  const { configPath, redirected } = resolveAwsConfigTarget();
11307
- fs21.mkdirSync(path22.dirname(configPath), { recursive: true });
11308
- const existedBefore = fs21.existsSync(configPath);
11309
- const existing = existedBefore ? fs21.readFileSync(configPath, "utf-8") : "";
11310
- const mode = existedBefore ? fs21.statSync(configPath).mode & 511 : 384;
11791
+ fs23.mkdirSync(path24.dirname(configPath), { recursive: true });
11792
+ const existedBefore = fs23.existsSync(configPath);
11793
+ const existing = existedBefore ? fs23.readFileSync(configPath, "utf-8") : "";
11794
+ const mode = existedBefore ? fs23.statSync(configPath).mode & 511 : 384;
11311
11795
  atomicWriteFileSync(
11312
11796
  configPath,
11313
11797
  upsertManagedAwsConfigSection(
@@ -11426,6 +11910,8 @@ export {
11426
11910
  derivePlatformRef,
11427
11911
  readStackPlatform,
11428
11912
  derivePlatformContext,
11913
+ renderCredentialProcessProfile,
11914
+ upsertManagedAwsConfigSection,
11429
11915
  projectRoleKeys,
11430
11916
  resolveAwsCredentials,
11431
11917
  credProbeToPreflightError,
@@ -11458,6 +11944,30 @@ export {
11458
11944
  worktreeStackName,
11459
11945
  stackNeedsWorktreeIsolation,
11460
11946
  resolveWorktreeIdentity,
11947
+ workstationDir,
11948
+ readWorkstationIdentity,
11949
+ listRoutes,
11950
+ renderWorkstationCaddyfile,
11951
+ readCredentialProcess,
11952
+ credentialProcessProblem,
11953
+ isInsideGitTree,
11954
+ rewriteCredentialProgram,
11955
+ LAUNCH_AGENT_LABEL,
11956
+ LAUNCH_AGENTS_MACOS_ONLY,
11957
+ launchAgentsSupported,
11958
+ launchAgentSearchPath,
11959
+ renderLaunchAgent,
11960
+ certificateExpiry,
11961
+ workstationWarnings,
11962
+ discoveryRecordName,
11963
+ parseDiscoveryRecord,
11964
+ caddyDownloadUrl,
11965
+ caddyHasRoute53,
11966
+ shimFallsBackToGlobal,
11967
+ zoneCandidates,
11968
+ proxyEnv,
11969
+ awsConfigFileOf,
11970
+ profileYieldsCredentials,
11461
11971
  tunnelClientAddress,
11462
11972
  registerDevCommand,
11463
11973
  aggregateStatus,