@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.
- package/dist/bin/{mesh.auth-preflight-JTUNS42O.js → mesh.auth-preflight-3ASNYA3I.js} +1 -1
- package/dist/bin/{mesh.chunk-QE5LUIHN.js → mesh.chunk-252LTIHZ.js} +2 -2
- package/dist/bin/{mesh.chunk-TYM27CO3.js → mesh.chunk-4Y3JGAYZ.js} +1 -1
- package/dist/bin/{mesh.chunk-OQA2UBOD.js → mesh.chunk-6OGUV2DU.js} +1 -1
- package/dist/bin/{mesh.chunk-XOCERV6F.js → mesh.chunk-G4TCVQLL.js} +556 -110
- package/dist/bin/mesh.chunk-G4TCVQLL.js.map +6 -0
- package/dist/bin/{mesh.chunk-ZNM3DDAM.js → mesh.chunk-HPLMVIBO.js} +1 -1
- package/dist/bin/{mesh.chunk-QUKWWFZI.js → mesh.chunk-JSY6THRF.js} +1 -1
- package/dist/bin/{mesh.chunk-PANTMVC3.js → mesh.chunk-NN3LTNUM.js} +3 -3
- package/dist/bin/{mesh.chunk-HB2IZKLP.js → mesh.chunk-SFIJZF3S.js} +786 -276
- package/dist/bin/{mesh.chunk-HB2IZKLP.js.map → mesh.chunk-SFIJZF3S.js.map} +3 -3
- package/dist/bin/{mesh.chunk-HC7V75J3.js → mesh.chunk-UJAPUO2W.js} +1 -1
- package/dist/bin/{mesh.create-app-DVMZDEQP.js → mesh.create-app-BHB3BGH7.js} +3 -3
- package/dist/bin/{mesh.dev-token-server-4N6HGQAF.js → mesh.dev-token-server-6TNWX5TY.js} +2 -2
- package/dist/bin/mesh.js +9 -9
- package/dist/bin/{mesh.login-Q6R5GMVM.js → mesh.login-4O5PMS25.js} +2 -2
- package/dist/bin/{mesh.platform-setup-YK6KSOP7.js → mesh.platform-setup-MTY6ICAO.js} +1 -1
- package/dist/bin/{mesh.program-NOBVA3JS.js → mesh.program-CMOLQWZI.js} +9 -9
- package/dist/bin/{mesh.registry-KA3CNNPZ.js → mesh.registry-BDBH6DRP.js} +4 -4
- package/dist/bin/{mesh.registry-identity-DSTAHXUS.js → mesh.registry-identity-QOGDEMZO.js} +3 -3
- package/dist/bin/{mesh.review-command-3ZXPS6Y3.js → mesh.review-command-E7V2L5AM.js} +3 -3
- package/dist/bin/{mesh.start-JDSYIRZI.js → mesh.start-3MUKXN2U.js} +2 -2
- package/dist/bin/{mesh.wizard-QGWPV4XN.js → mesh.wizard-EH3SQEHW.js} +7 -7
- package/dist/build-info.json +2 -2
- package/dist/src/commands/dev.d.ts +1 -0
- package/dist/src/commands/dev.js +57 -1
- package/dist/src/commands/temporal.js +146 -42
- package/dist/src/commands/temporal.js.map +3 -3
- package/dist/src/commands/workstation.d.ts +3 -0
- package/dist/src/commands/workstation.js +395 -0
- package/dist/src/program.js +2 -0
- package/dist/src/utils/temporal-inspect.d.ts +17 -0
- package/dist/src/utils/temporal-inspect.js +71 -0
- package/dist/src/utils/workstation-machine.d.ts +54 -0
- package/dist/src/utils/workstation-machine.js +282 -0
- package/dist/src/utils/workstation-routes.d.ts +77 -0
- package/dist/src/utils/workstation-routes.js +180 -0
- package/fragments/base/README.md.hbs +3 -0
- package/package.json +2 -2
- package/skills/mesh/SKILL.md +3 -2
- package/skills/mesh/references/worktrees.md +4 -1
- package/dist/bin/mesh.chunk-XOCERV6F.js.map +0 -6
- /package/dist/bin/{mesh.auth-preflight-JTUNS42O.js.map → mesh.auth-preflight-3ASNYA3I.js.map} +0 -0
- /package/dist/bin/{mesh.chunk-QE5LUIHN.js.map → mesh.chunk-252LTIHZ.js.map} +0 -0
- /package/dist/bin/{mesh.chunk-TYM27CO3.js.map → mesh.chunk-4Y3JGAYZ.js.map} +0 -0
- /package/dist/bin/{mesh.chunk-OQA2UBOD.js.map → mesh.chunk-6OGUV2DU.js.map} +0 -0
- /package/dist/bin/{mesh.chunk-ZNM3DDAM.js.map → mesh.chunk-HPLMVIBO.js.map} +0 -0
- /package/dist/bin/{mesh.chunk-QUKWWFZI.js.map → mesh.chunk-JSY6THRF.js.map} +0 -0
- /package/dist/bin/{mesh.chunk-PANTMVC3.js.map → mesh.chunk-NN3LTNUM.js.map} +0 -0
- /package/dist/bin/{mesh.chunk-HC7V75J3.js.map → mesh.chunk-UJAPUO2W.js.map} +0 -0
- /package/dist/bin/{mesh.create-app-DVMZDEQP.js.map → mesh.create-app-BHB3BGH7.js.map} +0 -0
- /package/dist/bin/{mesh.dev-token-server-4N6HGQAF.js.map → mesh.dev-token-server-6TNWX5TY.js.map} +0 -0
- /package/dist/bin/{mesh.login-Q6R5GMVM.js.map → mesh.login-4O5PMS25.js.map} +0 -0
- /package/dist/bin/{mesh.platform-setup-YK6KSOP7.js.map → mesh.platform-setup-MTY6ICAO.js.map} +0 -0
- /package/dist/bin/{mesh.program-NOBVA3JS.js.map → mesh.program-CMOLQWZI.js.map} +0 -0
- /package/dist/bin/{mesh.registry-KA3CNNPZ.js.map → mesh.registry-BDBH6DRP.js.map} +0 -0
- /package/dist/bin/{mesh.registry-identity-DSTAHXUS.js.map → mesh.registry-identity-QOGDEMZO.js.map} +0 -0
- /package/dist/bin/{mesh.review-command-3ZXPS6Y3.js.map → mesh.review-command-E7V2L5AM.js.map} +0 -0
- /package/dist/bin/{mesh.start-JDSYIRZI.js.map → mesh.start-3MUKXN2U.js.map} +0 -0
- /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-
|
|
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
|
|
58
|
-
import * as
|
|
59
|
-
import { execFileSync as
|
|
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
|
|
267
|
-
return existing +
|
|
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
|
|
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((
|
|
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
|
-
|
|
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
|
|
1201
|
+
import * as fs22 from "node:fs";
|
|
1202
1202
|
import * as net6 from "node:net";
|
|
1203
|
-
import * as
|
|
1204
|
-
import * as
|
|
1205
|
-
import { spawn as spawn2, execFileSync as
|
|
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
|
|
1244
|
-
import * as
|
|
1245
|
-
import * as
|
|
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
|
|
1248
|
+
import { execFileSync as execFileSync12, spawn, spawnSync as spawnSync2 } from "child_process";
|
|
1249
1249
|
import { createHash as createHash5 } from "crypto";
|
|
1250
|
-
import * as
|
|
1250
|
+
import * as fs19 from "fs";
|
|
1251
1251
|
import * as net3 from "net";
|
|
1252
|
-
import * as
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
1918
|
+
await new Promise((resolve10) => setTimeout(resolve10, intervalMs));
|
|
1919
1919
|
}
|
|
1920
1920
|
}
|
|
1921
1921
|
function tryConnect(host, port, timeoutMs) {
|
|
1922
|
-
return new Promise((
|
|
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
|
-
|
|
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((
|
|
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,
|
|
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(
|
|
4292
|
-
const res = await fetch(`${HOST_HTTP}${
|
|
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 ${
|
|
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((
|
|
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
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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(
|
|
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 =
|
|
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
|
|
6392
|
+
return path20.join(dir, `${sessionName}.json`);
|
|
5961
6393
|
}
|
|
5962
6394
|
function saveSessionState(sessionName, state) {
|
|
5963
|
-
|
|
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 (!
|
|
6399
|
+
if (!fs19.existsSync(filePath)) return null;
|
|
5968
6400
|
try {
|
|
5969
|
-
return JSON.parse(
|
|
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
|
|
6425
|
+
return path20.join(getSessionEnvDir(sessionName), envFileName(serviceName));
|
|
5994
6426
|
}
|
|
5995
6427
|
function removeSessionState(sessionName) {
|
|
5996
6428
|
try {
|
|
5997
|
-
|
|
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
|
-
|
|
6438
|
+
fs19.rmSync(getSessionEnvDir(sessionName), { recursive: true, force: true });
|
|
6002
6439
|
} catch {
|
|
6003
6440
|
}
|
|
6004
6441
|
}
|
|
6005
6442
|
function getSessionPortMemoryPath(sessionName) {
|
|
6006
|
-
return
|
|
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(
|
|
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
|
-
|
|
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((
|
|
6483
|
+
const bindSucceeds = (host) => new Promise((resolve10) => {
|
|
6047
6484
|
const server = net3.createServer();
|
|
6048
|
-
server.once("error", () =>
|
|
6049
|
-
const onListening = () => server.close(() =>
|
|
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((
|
|
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(() =>
|
|
6499
|
+
server.close(() => resolve10(port));
|
|
6063
6500
|
});
|
|
6064
6501
|
});
|
|
6065
6502
|
}
|
|
6066
6503
|
function isPortListening(port) {
|
|
6067
|
-
return new Promise((
|
|
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
|
-
|
|
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 =
|
|
6619
|
+
const root = path20.parse(dir).root;
|
|
6183
6620
|
while (dir !== root) {
|
|
6184
|
-
if (
|
|
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 =
|
|
6188
|
-
if (
|
|
6624
|
+
const gitPath = path20.join(dir, ".git");
|
|
6625
|
+
if (fs19.existsSync(gitPath)) {
|
|
6189
6626
|
return dir;
|
|
6190
6627
|
}
|
|
6191
|
-
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 =
|
|
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:
|
|
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 =
|
|
6211
|
-
if (!
|
|
6212
|
-
for (const tenant of
|
|
6213
|
-
const appsDir =
|
|
6214
|
-
if (!
|
|
6215
|
-
for (const app of
|
|
6216
|
-
const appDir =
|
|
6217
|
-
if (!
|
|
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 =
|
|
6223
|
-
if (
|
|
6224
|
-
for (const app of
|
|
6225
|
-
const appDir =
|
|
6226
|
-
if (!
|
|
6227
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
6246
|
-
if (
|
|
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 =
|
|
6250
|
-
if (
|
|
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 =
|
|
6693
|
+
const root = path20.parse(dir).root;
|
|
6257
6694
|
while (dir !== root) {
|
|
6258
|
-
if (
|
|
6695
|
+
if (fs19.existsSync(path20.join(dir, "Pulumi.yaml"))) {
|
|
6259
6696
|
return dir;
|
|
6260
6697
|
}
|
|
6261
|
-
dir =
|
|
6698
|
+
dir = path20.dirname(dir);
|
|
6262
6699
|
}
|
|
6263
6700
|
return process.cwd();
|
|
6264
6701
|
}
|
|
6265
6702
|
function getProjectName(appRoot) {
|
|
6266
|
-
const yamlPath =
|
|
6267
|
-
if (!
|
|
6268
|
-
const content =
|
|
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() ??
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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) =>
|
|
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) =>
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
6919
|
+
const configPath = path20.join(opts.sessionScratchDir, "aws-config");
|
|
6483
6920
|
const region = awsEnv.AWS_REGION ?? awsEnv.AWS_DEFAULT_REGION ?? "";
|
|
6484
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
6551
|
-
return
|
|
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 =
|
|
6995
|
+
for (let at = dir; ; at = path20.dirname(at)) {
|
|
6559
6996
|
try {
|
|
6560
|
-
const manifest = JSON.parse(
|
|
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 (
|
|
7001
|
+
if (path20.dirname(at) === at) return null;
|
|
6565
7002
|
}
|
|
6566
7003
|
}
|
|
6567
7004
|
function appResolvedPackageDir(name, appRoot) {
|
|
6568
|
-
for (let dir = appRoot; ; dir =
|
|
7005
|
+
for (let dir = appRoot; ; dir = path20.dirname(dir)) {
|
|
6569
7006
|
try {
|
|
6570
|
-
return
|
|
7007
|
+
return fs19.realpathSync(path20.join(dir, "node_modules", name));
|
|
6571
7008
|
} catch {
|
|
6572
7009
|
}
|
|
6573
|
-
if (
|
|
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 =
|
|
6582
|
-
return
|
|
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 (!
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
7033
|
-
|
|
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
|
-
|
|
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 ??
|
|
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
|
-
|
|
7527
|
+
execFileSync12(
|
|
7091
7528
|
"docker",
|
|
7092
7529
|
[
|
|
7093
7530
|
"run",
|
|
@@ -7103,7 +7540,7 @@ CADDYEOF`
|
|
|
7103
7540
|
],
|
|
7104
7541
|
{ stdio: "pipe" }
|
|
7105
7542
|
);
|
|
7106
|
-
|
|
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 ??
|
|
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 =
|
|
7156
|
-
if (!
|
|
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
|
-
|
|
7646
|
+
fs19.writeFileSync(caddyfile, caddyLines.join("\n"));
|
|
7175
7647
|
let caddyRunning = false;
|
|
7176
7648
|
try {
|
|
7177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7354
|
-
|
|
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
|
-
|
|
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 =
|
|
7392
|
-
if (worktreeRoot &&
|
|
7393
|
-
const wtRootWithSep = worktreeRoot.endsWith(
|
|
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 (!
|
|
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
|
-
|
|
7476
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 = !
|
|
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(
|
|
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
|
-
|
|
8182
|
+
execFileSync12("tmux", ["respawn-pane", "-k", "-t", target], { stdio: "ignore" });
|
|
7703
8183
|
} catch {
|
|
7704
8184
|
try {
|
|
7705
|
-
|
|
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
|
-
|
|
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((
|
|
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 (
|
|
7732
|
-
let serviceDir =
|
|
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
|
-
|
|
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((
|
|
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
|
|
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 =
|
|
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(
|
|
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-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
8663
|
+
const serviceDir = path20.resolve(appRoot, service.src);
|
|
8180
8664
|
const env = service.env ?? (service.env = {});
|
|
8181
8665
|
const serviceEnv = {
|
|
8182
|
-
...readDotEnvFile(
|
|
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) =>
|
|
8858
|
+
(dir) => fs19.existsSync(path20.join(appRoot, dir, "package.json"))
|
|
8375
8859
|
);
|
|
8376
|
-
if (monorepoRoot &&
|
|
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 ??
|
|
9295
|
+
const p = registryPath ?? path21.join(CONFIG_DIR, "tailscale", "port-registry.json");
|
|
8812
9296
|
let reg;
|
|
8813
9297
|
try {
|
|
8814
|
-
reg = JSON.parse(
|
|
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
|
-
|
|
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 =
|
|
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
|
|
9417
|
+
import * as fs21 from "node:fs";
|
|
8934
9418
|
import * as net5 from "node:net";
|
|
8935
|
-
import * as
|
|
8936
|
-
import { execFileSync as
|
|
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 (!
|
|
9425
|
+
if (!fs21.existsSync(file)) return null;
|
|
8942
9426
|
try {
|
|
8943
|
-
return JSON.parse(
|
|
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
|
-
|
|
8950
|
-
|
|
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
|
-
|
|
9439
|
+
fs21.unlinkSync(file);
|
|
8956
9440
|
} catch {
|
|
8957
9441
|
}
|
|
8958
9442
|
}
|
|
8959
9443
|
function getProcessGroupId(pid) {
|
|
8960
9444
|
try {
|
|
8961
|
-
const out =
|
|
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 =
|
|
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((
|
|
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
|
-
|
|
9494
|
+
resolve10({ server, port: server.address().port });
|
|
9011
9495
|
});
|
|
9012
9496
|
});
|
|
9013
9497
|
}
|
|
9014
9498
|
function queryControl(port, timeoutMs = 700) {
|
|
9015
|
-
return new Promise((
|
|
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
|
-
|
|
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 =
|
|
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
|
|
9605
|
+
return path23.join(CONFIG_DIR, "tailscale", tenant);
|
|
9122
9606
|
}
|
|
9123
9607
|
function socketPath(tenant) {
|
|
9124
|
-
return
|
|
9608
|
+
return path23.join(tenantStateDir(tenant), "tailscaled.sock");
|
|
9125
9609
|
}
|
|
9126
9610
|
function logPath(tenant) {
|
|
9127
|
-
return
|
|
9611
|
+
return path23.join(tenantStateDir(tenant), "tailscaled.log");
|
|
9128
9612
|
}
|
|
9129
9613
|
function statePath(tenant) {
|
|
9130
|
-
return
|
|
9614
|
+
return path23.join(tenantStateDir(tenant), "tunnel-state.json");
|
|
9131
9615
|
}
|
|
9132
9616
|
function daemonMetaPath(tenant) {
|
|
9133
|
-
return
|
|
9617
|
+
return path23.join(tenantStateDir(tenant), "daemon.json");
|
|
9134
9618
|
}
|
|
9135
9619
|
function runnerManifestPath(tenant) {
|
|
9136
|
-
return
|
|
9620
|
+
return path23.join(tenantStateDir(tenant), "runner.json");
|
|
9137
9621
|
}
|
|
9138
9622
|
function readTunnelState(tenant) {
|
|
9139
9623
|
const p = statePath(tenant);
|
|
9140
|
-
if (!
|
|
9624
|
+
if (!fs22.existsSync(p)) return null;
|
|
9141
9625
|
try {
|
|
9142
|
-
return JSON.parse(
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
9643
|
+
if (!fs22.existsSync(p)) return null;
|
|
9160
9644
|
try {
|
|
9161
|
-
return JSON.parse(
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
9676
|
+
if (fs22.existsSync(brew)) return brew;
|
|
9193
9677
|
try {
|
|
9194
|
-
const p =
|
|
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 (!
|
|
9685
|
+
if (!fs22.existsSync(socketPath(tenant))) return { backendState: "Down" };
|
|
9202
9686
|
try {
|
|
9203
|
-
const out =
|
|
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 =
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
9276
|
-
const log =
|
|
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
|
-
|
|
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
|
-
|
|
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}-${
|
|
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
|
-
|
|
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" ?
|
|
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 (
|
|
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
|
|
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
|
-
|
|
9634
|
-
|
|
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 =
|
|
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 = `${
|
|
10135
|
+
pathEnv = `${path23.dirname(tailscaledBinPath())}:${pathEnv}`;
|
|
9652
10136
|
} catch {
|
|
9653
10137
|
}
|
|
9654
|
-
|
|
9655
|
-
|
|
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:
|
|
10145
|
+
logPath: path23.join(tenantStateDir(tenant), "runner.log"),
|
|
9662
10146
|
pathEnv
|
|
9663
10147
|
})
|
|
9664
10148
|
);
|
|
9665
10149
|
try {
|
|
9666
|
-
|
|
10150
|
+
execFileSync15("launchctl", ["bootout", guiDomain(), plistPath], { stdio: "ignore" });
|
|
9667
10151
|
} catch {
|
|
9668
10152
|
}
|
|
9669
|
-
|
|
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
|
-
|
|
10162
|
+
execFileSync15("launchctl", ["bootout", guiDomain(), plistPath], { stdio: "ignore" });
|
|
9679
10163
|
} catch {
|
|
9680
10164
|
}
|
|
9681
10165
|
try {
|
|
9682
|
-
|
|
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((
|
|
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(() =>
|
|
10261
|
+
s.close(() => resolve10(port));
|
|
9778
10262
|
});
|
|
9779
10263
|
});
|
|
9780
10264
|
}
|
|
9781
10265
|
function canConnect2(port) {
|
|
9782
|
-
return new Promise((
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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((
|
|
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(() =>
|
|
10630
|
+
srv.close(() => resolve10(port));
|
|
10147
10631
|
});
|
|
10148
10632
|
});
|
|
10149
10633
|
}
|
|
10150
10634
|
function findTailscale() {
|
|
10151
10635
|
try {
|
|
10152
|
-
return
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
10323
|
-
process.env.XDG_CONFIG_HOME ??
|
|
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 =
|
|
10327
|
-
var CREDENTIALS_FILE =
|
|
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 (!
|
|
10827
|
+
if (!fs23.existsSync(CONFIG_FILE)) return {};
|
|
10344
10828
|
try {
|
|
10345
|
-
return JSON.parse(
|
|
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
|
-
|
|
10354
|
-
|
|
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
|
-
|
|
10361
|
-
|
|
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 (!
|
|
10952
|
+
if (!fs23.existsSync(CREDENTIALS_FILE)) return {};
|
|
10469
10953
|
try {
|
|
10470
|
-
return JSON.parse(
|
|
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(
|
|
10480
|
-
const tmpPath = `${
|
|
10963
|
+
function atomicWriteFileSync(path25, data, mode) {
|
|
10964
|
+
const tmpPath = `${path25}.${process.pid}.${atomicWriteCounter++}.tmp`;
|
|
10481
10965
|
try {
|
|
10482
|
-
|
|
10483
|
-
|
|
10966
|
+
fs23.writeFileSync(tmpPath, data, { mode });
|
|
10967
|
+
fs23.renameSync(tmpPath, path25);
|
|
10484
10968
|
} catch (err) {
|
|
10485
10969
|
try {
|
|
10486
|
-
|
|
10970
|
+
fs23.unlinkSync(tmpPath);
|
|
10487
10971
|
} catch {
|
|
10488
10972
|
}
|
|
10489
10973
|
throw err;
|
|
10490
10974
|
}
|
|
10491
10975
|
}
|
|
10492
10976
|
function writeCredentials(context, creds) {
|
|
10493
|
-
|
|
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
|
-
|
|
10502
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
11135
|
+
execFileSync17("open", [url], { stdio: "ignore" });
|
|
10652
11136
|
} else if (process.platform === "linux") {
|
|
10653
|
-
|
|
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
|
-
|
|
11191
|
+
execFileSync17("open", [openUrl], { stdio: "ignore" });
|
|
10708
11192
|
} else if (process.platform === "linux") {
|
|
10709
|
-
|
|
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 (
|
|
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-
|
|
11510
|
+
const { readRegistrySession } = await import("./mesh.registry-identity-QOGDEMZO.js");
|
|
11027
11511
|
if (readRegistrySession()) return;
|
|
11028
|
-
const { probeRegistryToken: probeRegistryToken2 } = await import("./mesh.auth-preflight-
|
|
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 ??
|
|
11644
|
+
return env.AWS_CONFIG_FILE ?? path24.join(env.HOME ?? "~", ".aws", "config");
|
|
11161
11645
|
}
|
|
11162
11646
|
function canWriteDir(dir) {
|
|
11163
11647
|
try {
|
|
11164
|
-
|
|
11165
|
-
|
|
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 =
|
|
11174
|
-
if (canWriteDir(
|
|
11175
|
-
return { configPath:
|
|
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
|
-
|
|
11308
|
-
const existedBefore =
|
|
11309
|
-
const existing = existedBefore ?
|
|
11310
|
-
const mode = existedBefore ?
|
|
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,
|