@tdk-landscape/tdk-cli-core 1.3.156 → 1.3.158
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/cli/dist/commands/doctor.js +15 -10
- package/cli/dist/commands/up.js +2 -15
- package/cli/dist/utils/doctor-crashloop.js +2 -1
- package/cli/dist/utils/doctor-fix.js +188 -9
- package/cli/dist/utils/down-cleanup.js +26 -0
- package/cli/dist/utils/tilt-port-owner.js +14 -0
- package/cli/dist-bundle/chunk-UQKHSV4F.js +377 -0
- package/cli/dist-bundle/cli.js +206 -206
- package/cli/dist-bundle/{cold-preflight-A2DOE5ZP.js → cold-preflight-VJ3F7P3N.js} +1 -1
- package/cli/package.json +1 -1
- package/engine/topologies/tilt/resources/orchestrator/apply.star +3 -4
- package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +12 -2
- package/package.json +1 -1
- package/cli/dist-bundle/chunk-I3NXJOZC.js +0 -377
|
@@ -12,13 +12,12 @@ import { projectNeedsBun } from "../utils/bun-requirement.js";
|
|
|
12
12
|
import { AUTOGENERATED_DIR, CONTAINER_RUNTIME_CHECK, MASTER_CONFIG_FILES, PACKAGE_JSON, REQUIRED_PACKAGE_SCRIPTS, } from "../utils/constants.js";
|
|
13
13
|
import { isPathDiscovered, readDiscoveryPaths } from "../utils/discovery-paths.js";
|
|
14
14
|
import { checkCrashLoopingContainers } from "../utils/doctor-crashloop.js";
|
|
15
|
-
import { applyDoctorFixes, checkDuplicateTdkInstalls, hasFailedActions, planDoctorFixes, } from "../utils/doctor-fix.js";
|
|
15
|
+
import { applyDoctorFixes, checkDuplicateTdkInstalls, hasFailedActions, planDoctorFixes, projectTiltPids, } from "../utils/doctor-fix.js";
|
|
16
16
|
import { checkDiskSpace, dockerDesktopStuck } from "../utils/doctor-host.js";
|
|
17
17
|
import { collectDoctorChecks, createDoctorReport, getDoctorExitCode, } from "../utils/doctor-report.js";
|
|
18
18
|
import { checkHealthRoutes, checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, projectConfigEnablesVerdaccio, summarizeAdvertisedProbes, summarizeServiceProbes, summarizeSmokeResults, } from "../utils/doctor-runtime.js";
|
|
19
19
|
import { checkStarlarkLoadExports } from "../utils/doctor-starlark.js";
|
|
20
20
|
import { checkDockerNetworkCapacity, checkDuplicateResourceNames, checkDuplicateResourcePorts, checkFrontendBackendUrls, checkMigrationsInApi, checkNatsBroker, checkPrismaConsistency, checkResourcePackageJson, checkServiceUrlPorts, checkSharedPlatformPostgres, checkSharedStackRoutes, checkTiltInstances, } from "../utils/doctor-wiring.js";
|
|
21
|
-
import { findProjectTiltUpPids } from "../utils/down-cleanup.js";
|
|
22
21
|
import { validateEnvFile } from "../utils/env-validator.js";
|
|
23
22
|
import { execAsync, isExecTimeout } from "../utils/exec-async.js";
|
|
24
23
|
import { formatCount } from "../utils/formatting.js";
|
|
@@ -1018,6 +1017,7 @@ export const doctorCommand = new Command("doctor")
|
|
|
1018
1017
|
.option("--strict", "Fail warnings such as a WSL project under /mnt/c")
|
|
1019
1018
|
.option("--fix", "Repair this project's unused Docker networks and leftover doctor probes", false)
|
|
1020
1019
|
.option("--dry-run", "With --fix: list the repairs without changing anything", false)
|
|
1020
|
+
.option("--scope <scope>", "With --fix: project (default) or machine, which also removes unused networks of other TDK projects", "project")
|
|
1021
1021
|
.option("-y, --yes", "With --fix: apply every repair without asking", false)
|
|
1022
1022
|
.option("--ping-timeout <ms>", "Per-service ping timeout in milliseconds", String(DEFAULT_PING_TIMEOUT_MS))
|
|
1023
1023
|
.action(async (options) => {
|
|
@@ -1033,8 +1033,14 @@ export const doctorCommand = new Command("doctor")
|
|
|
1033
1033
|
process.exit(2);
|
|
1034
1034
|
return;
|
|
1035
1035
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1036
|
+
const scopeInvalid = !["project", "machine"].includes(options.scope);
|
|
1037
|
+
const scopeWithoutFix = options.scope !== "project" && !options.fix;
|
|
1038
|
+
if (scopeInvalid || scopeWithoutFix || ((options.dryRun || options.yes) && !options.fix)) {
|
|
1039
|
+
const message = scopeInvalid
|
|
1040
|
+
? `--scope must be project or machine, got "${options.scope}"`
|
|
1041
|
+
: scopeWithoutFix
|
|
1042
|
+
? "--scope only applies with --fix"
|
|
1043
|
+
: `${options.dryRun ? "--dry-run" : "--yes"} only applies with --fix`;
|
|
1038
1044
|
if (options.json)
|
|
1039
1045
|
console.log(JSON.stringify(createDoctorReport([], false, [{ code: "USAGE", message }])));
|
|
1040
1046
|
console.error(message);
|
|
@@ -1100,12 +1106,11 @@ export const doctorCommand = new Command("doctor")
|
|
|
1100
1106
|
// An unreadable config is reported by the project checks below.
|
|
1101
1107
|
}
|
|
1102
1108
|
}
|
|
1103
|
-
const tiltPids = projectRoot
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
: undefined);
|
|
1109
|
+
const tiltPids = projectRoot ? projectTiltPids(projectRoot) : [];
|
|
1110
|
+
const planned = await planDoctorFixes(projectName, undefined, {
|
|
1111
|
+
tiltPids,
|
|
1112
|
+
machine: options.scope === "machine",
|
|
1113
|
+
});
|
|
1109
1114
|
fixActions = await applyDoctorFixes(planned, {
|
|
1110
1115
|
dryRun: options.dryRun,
|
|
1111
1116
|
confirm: options.yes || (!options.json && process.stdin.isTTY)
|
package/cli/dist/commands/up.js
CHANGED
|
@@ -29,7 +29,7 @@ import { buildSmokePlans, formatSmokeFailure, runSmokePlans } from "../utils/smo
|
|
|
29
29
|
import { buildStartupReport, formatStartupReport, isStartupStalled, } from "../utils/startup-report.js";
|
|
30
30
|
import { evaluateTdkVersionFloor } from "../utils/tdk-version.js";
|
|
31
31
|
import { buildTiltUpArgs, runTilt } from "../utils/tilt.js";
|
|
32
|
-
import {
|
|
32
|
+
import { classifyTiltPorts } from "../utils/tilt-port-owner.js";
|
|
33
33
|
import { stopTiltOnPort } from "../utils/tilt-process.js";
|
|
34
34
|
import { parseTiltPort, resolveTiltPort, secondUpAction, stopTiltForUp, } from "../utils/tilt-startup.js";
|
|
35
35
|
import { findUnknownServices, resolveOnlySelection } from "../utils/up-only.js";
|
|
@@ -369,20 +369,7 @@ export const upCommand = new Command("up")
|
|
|
369
369
|
const basePort = STANDARD_PORTS.tiltUi;
|
|
370
370
|
const watchedPort = configuredTiltPort ?? basePort;
|
|
371
371
|
const candidatePorts = options.only ? [...new Set([basePort, watchedPort])] : [watchedPort];
|
|
372
|
-
const running =
|
|
373
|
-
const foreign = [];
|
|
374
|
-
for (const candidate of candidatePorts) {
|
|
375
|
-
if ((await tiltGetUiResources(candidate)) === null)
|
|
376
|
-
continue;
|
|
377
|
-
// A Tilt on this port may belong to another project; that is not "this environment is running".
|
|
378
|
-
const owner = findTiltPortOwner(candidate);
|
|
379
|
-
if (owner && isForeignTilt(owner, projectRoot)) {
|
|
380
|
-
foreign.push({ port: candidate, root: owner.root });
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
running.push(candidate);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
372
|
+
const { running, foreign } = await classifyTiltPorts(candidatePorts, projectRoot, async (candidate) => (await tiltGetUiResources(candidate)) !== null);
|
|
386
373
|
if (foreign.length > 0 && configuredTiltPort !== undefined) {
|
|
387
374
|
const other = foreign[0];
|
|
388
375
|
const message = `TILT_PORT=${other?.port} is used by another project's Tilt (${other?.root}). Choose a free port, or run tdk down in that project.`;
|
|
@@ -55,7 +55,8 @@ export function crashLoopHint(logs, state) {
|
|
|
55
55
|
if (/ECONNREFUSED|connection refused|could not connect/i.test(logs)) {
|
|
56
56
|
return "a dependency is not reachable: check that the service it connects to is running and healthy";
|
|
57
57
|
}
|
|
58
|
-
if (
|
|
58
|
+
if (/\b(?:environment variable|env var)\b.*\b(?:missing|not set|required|undefined)\b/i.test(logs) ||
|
|
59
|
+
/\b[A-Z][A-Z0-9_]{2,}\b (?:is )?(?:not set|required|missing)\b/.test(logs)) {
|
|
59
60
|
return "a required environment variable is missing: check the project's .env";
|
|
60
61
|
}
|
|
61
62
|
if (state.exitCode === 137)
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// Copyright (c) 2026 TDK Landscape contributors
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
|
-
import { realpathSync } from "node:fs";
|
|
4
|
+
import { realpathSync, statfsSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
5
7
|
import { promisify } from "node:util";
|
|
6
|
-
import {
|
|
8
|
+
import { DISK_WARN_GIB } from "./doctor-host.js";
|
|
9
|
+
import { findProjectTiltUpPids, projectNetworkNames, tdkNetworkOwner, tdkNetworkPrefixes, } from "./down-cleanup.js";
|
|
10
|
+
import { tiltGetUiResources } from "./up-readiness.js";
|
|
7
11
|
import { findAllOnPath } from "./which.js";
|
|
8
12
|
const execFilePromise = promisify(execFile);
|
|
9
13
|
const DOCKER_TIMEOUT_MS = 15_000;
|
|
@@ -21,6 +25,51 @@ function firstLine(error) {
|
|
|
21
25
|
const stderr = String(error.stderr ?? error).trim();
|
|
22
26
|
return stderr.split(/\r?\n/)[0] ?? "";
|
|
23
27
|
}
|
|
28
|
+
const STALE_TILT_MIN_AGE_S = 120;
|
|
29
|
+
/** `ps -o etime=` ("[[dd-]hh:]mm:ss") in seconds. */
|
|
30
|
+
export function parseElapsed(etime) {
|
|
31
|
+
const match = /^\s*(?:(?:(\d+)-)?(\d+):)?(\d+):(\d+)\s*$/.exec(etime);
|
|
32
|
+
if (!match)
|
|
33
|
+
return null;
|
|
34
|
+
const [, days = "0", hours = "0", minutes = "0", seconds = "0"] = match;
|
|
35
|
+
return ((Number(days) * 24 + Number(hours)) * 60 + Number(minutes)) * 60 + Number(seconds);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Whether any of `ports` answers as a Tilt UI. A busy Tilt can miss one request, and a wrong "stale" ends in a kill,
|
|
39
|
+
* so a port gets a second try before it counts as silent.
|
|
40
|
+
*/
|
|
41
|
+
export async function anyTiltAnswers(ports, probe = tiltGetUiResources, attempts = 2, wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
42
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
43
|
+
if (attempt > 0)
|
|
44
|
+
await wait(1000);
|
|
45
|
+
for (const port of ports) {
|
|
46
|
+
if ((await probe(port)) !== null)
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
async function tiltProcessStatus(pid) {
|
|
53
|
+
if (process.platform === "win32")
|
|
54
|
+
return "unknown";
|
|
55
|
+
try {
|
|
56
|
+
const { stdout: etime } = await execFilePromise("ps", ["-p", String(pid), "-o", "etime="], {
|
|
57
|
+
encoding: "utf-8",
|
|
58
|
+
timeout: 5000,
|
|
59
|
+
});
|
|
60
|
+
const age = parseElapsed(etime);
|
|
61
|
+
// A Tilt that is still starting has not bound its port yet.
|
|
62
|
+
if (age === null || age < STALE_TILT_MIN_AGE_S)
|
|
63
|
+
return "unknown";
|
|
64
|
+
const { stdout } = await execFilePromise("lsof", ["-nP", "-a", "-p", String(pid), "-iTCP", "-sTCP:LISTEN", "-Fn"], { encoding: "utf-8", timeout: 5000 });
|
|
65
|
+
const ports = [...stdout.matchAll(/^n.*:(\d+)$/gm)].map((m) => Number(m[1]));
|
|
66
|
+
return (await anyTiltAnswers(ports)) ? "healthy" : "stale";
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// No lsof, or the process is gone: do not guess.
|
|
70
|
+
return "unknown";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
24
73
|
export function createDockerFixDeps(run = runDocker) {
|
|
25
74
|
return {
|
|
26
75
|
async listNetworks() {
|
|
@@ -57,6 +106,54 @@ export function createDockerFixDeps(run = runDocker) {
|
|
|
57
106
|
throw new Error(firstLine(error));
|
|
58
107
|
}
|
|
59
108
|
},
|
|
109
|
+
freeDiskGib() {
|
|
110
|
+
try {
|
|
111
|
+
const stats = statfsSync(homedir());
|
|
112
|
+
return (Number(stats.bavail) * Number(stats.bsize)) / 1024 ** 3;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
async reclaimable() {
|
|
119
|
+
let buildCache = null;
|
|
120
|
+
try {
|
|
121
|
+
const df = await run(["system", "df", "--format", "{{.Type}}\t{{.Reclaimable}}"]);
|
|
122
|
+
for (const line of df.split(/\r?\n/)) {
|
|
123
|
+
const [type, size] = line.split("\t");
|
|
124
|
+
// "1.531GB" or "1.5GB (39%)"; the percentage is of the whole, not useful here.
|
|
125
|
+
if (type === "Build Cache" && size)
|
|
126
|
+
buildCache = size.replace(/\s*\(.*\)/, "").trim();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// Leave unknown.
|
|
131
|
+
}
|
|
132
|
+
let danglingImages = 0;
|
|
133
|
+
try {
|
|
134
|
+
const ids = await run(["images", "-q", "-f", "dangling=true"]);
|
|
135
|
+
danglingImages = ids.split(/\r?\n/).filter(Boolean).length;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// Leave zero.
|
|
139
|
+
}
|
|
140
|
+
return { buildCache, danglingImages };
|
|
141
|
+
},
|
|
142
|
+
async prune(kind) {
|
|
143
|
+
try {
|
|
144
|
+
// Never `volume prune`, never `-a`: only the build cache and untagged images.
|
|
145
|
+
await run(kind === "builder" ? ["builder", "prune", "-f"] : ["image", "prune", "-f"]);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
throw new Error(firstLine(error));
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
async tiltStatus(pid) {
|
|
152
|
+
return tiltProcessStatus(pid);
|
|
153
|
+
},
|
|
154
|
+
stopProcess(pid) {
|
|
155
|
+
process.kill(pid, "SIGTERM");
|
|
156
|
+
},
|
|
60
157
|
isProcessAlive(pid) {
|
|
61
158
|
try {
|
|
62
159
|
process.kill(pid, 0);
|
|
@@ -71,17 +168,66 @@ export function createDockerFixDeps(run = runDocker) {
|
|
|
71
168
|
}
|
|
72
169
|
export const dockerFixDeps = createDockerFixDeps();
|
|
73
170
|
/**
|
|
74
|
-
* What `--fix` would do
|
|
75
|
-
* (the exact names the engine creates,
|
|
171
|
+
* What `--fix` would do: stop this project's stale Tilt; remove unused networks that are a leftover doctor probe or
|
|
172
|
+
* this project's own (the exact names the engine creates, skipped while its Tilt runs). Other TDK projects' unused
|
|
173
|
+
* networks are planned only with `context.machine`, each naming its owner.
|
|
76
174
|
*/
|
|
77
|
-
export async function planDoctorFixes(projectName, deps = dockerFixDeps,
|
|
78
|
-
const networks = await deps.listNetworks();
|
|
175
|
+
export async function planDoctorFixes(projectName, deps = dockerFixDeps, context = {}) {
|
|
79
176
|
const actions = [];
|
|
177
|
+
// A Tilt that has run a while without serving its UI is stale and may be stopped; any other Tilt of this project is
|
|
178
|
+
// running or starting, and its networks are not to be touched.
|
|
179
|
+
const liveTilt = [];
|
|
180
|
+
for (const pid of context.tiltPids ?? []) {
|
|
181
|
+
if ((await deps.tiltStatus(pid)) === "stale") {
|
|
182
|
+
actions.push({
|
|
183
|
+
id: `stop-tilt:${pid}`,
|
|
184
|
+
check: "Tilt processes",
|
|
185
|
+
scope: "project",
|
|
186
|
+
command: `kill -TERM ${pid}`,
|
|
187
|
+
status: "planned",
|
|
188
|
+
reason: "this project's Tilt is running but its UI does not answer",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
liveTilt.push(pid);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const projectBusy = liveTilt.length > 0
|
|
196
|
+
? `Tilt is running for this project (pid ${liveTilt.join(", ")}); run tdk down first`
|
|
197
|
+
: undefined;
|
|
198
|
+
const networks = await deps.listNetworks();
|
|
199
|
+
const freeGib = deps.freeDiskGib();
|
|
200
|
+
if (freeGib !== null && freeGib < DISK_WARN_GIB) {
|
|
201
|
+
const { buildCache, danglingImages } = await deps.reclaimable();
|
|
202
|
+
const low = `${freeGib.toFixed(1)} GB free`;
|
|
203
|
+
if (buildCache && !/^0(\.0+)?\s*B$/i.test(buildCache)) {
|
|
204
|
+
actions.push({
|
|
205
|
+
id: "prune:builder",
|
|
206
|
+
check: "Disk Space",
|
|
207
|
+
scope: "host",
|
|
208
|
+
command: "docker builder prune -f",
|
|
209
|
+
status: "planned",
|
|
210
|
+
reason: `${low}; ${buildCache} of build cache reclaimable`,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (danglingImages > 0) {
|
|
214
|
+
actions.push({
|
|
215
|
+
id: "prune:images",
|
|
216
|
+
check: "Disk Space",
|
|
217
|
+
scope: "host",
|
|
218
|
+
command: "docker image prune -f",
|
|
219
|
+
status: "planned",
|
|
220
|
+
reason: `${low}; ${danglingImages} untagged image${danglingImages === 1 ? "" : "s"}`,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
80
224
|
const own = new Set(projectName ? projectNetworkNames(projectName) : []);
|
|
225
|
+
const prefixes = tdkNetworkPrefixes(networks.map((network) => network.name));
|
|
81
226
|
for (const network of networks) {
|
|
82
227
|
const probe = PROBE_NETWORK.exec(network.name);
|
|
83
228
|
let scope;
|
|
84
229
|
let check;
|
|
230
|
+
let reason = "unused network";
|
|
85
231
|
if (probe) {
|
|
86
232
|
scope = "probe";
|
|
87
233
|
check = "Orphaned probe networks";
|
|
@@ -90,6 +236,11 @@ export async function planDoctorFixes(projectName, deps = dockerFixDeps, project
|
|
|
90
236
|
scope = "project";
|
|
91
237
|
check = "Docker networks";
|
|
92
238
|
}
|
|
239
|
+
else if (context.machine && tdkNetworkOwner(network.name, prefixes)) {
|
|
240
|
+
scope = "machine";
|
|
241
|
+
check = "Docker networks";
|
|
242
|
+
reason = `unused network of project "${tdkNetworkOwner(network.name, prefixes)}"`;
|
|
243
|
+
}
|
|
93
244
|
else {
|
|
94
245
|
continue;
|
|
95
246
|
}
|
|
@@ -99,7 +250,7 @@ export async function planDoctorFixes(projectName, deps = dockerFixDeps, project
|
|
|
99
250
|
scope,
|
|
100
251
|
command: `docker network rm ${network.name}`,
|
|
101
252
|
status: "planned",
|
|
102
|
-
reason
|
|
253
|
+
reason,
|
|
103
254
|
};
|
|
104
255
|
if (network.containers > 0) {
|
|
105
256
|
action.status = "skipped";
|
|
@@ -146,9 +297,18 @@ export async function applyDoctorFixes(actions, options, deps = dockerFixDeps) {
|
|
|
146
297
|
results.push({ ...action, status: "skipped", reason: "not confirmed" });
|
|
147
298
|
continue;
|
|
148
299
|
}
|
|
149
|
-
const name = action.id.slice("remove-network:".length);
|
|
150
300
|
try {
|
|
151
|
-
|
|
301
|
+
if (action.id.startsWith("prune:")) {
|
|
302
|
+
await deps.prune(action.id === "prune:builder" ? "builder" : "images");
|
|
303
|
+
results.push({ ...action, status: "applied", reason: "pruned" });
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (action.id.startsWith("stop-tilt:")) {
|
|
307
|
+
deps.stopProcess(Number(action.id.slice("stop-tilt:".length)));
|
|
308
|
+
results.push({ ...action, status: "applied", reason: "stopped" });
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
await deps.removeNetwork(action.id.slice("remove-network:".length));
|
|
152
312
|
results.push({ ...action, status: "applied", reason: "removed" });
|
|
153
313
|
}
|
|
154
314
|
catch (error) {
|
|
@@ -209,3 +369,22 @@ export async function checkDuplicateTdkInstalls(installs) {
|
|
|
209
369
|
fix: "Keep one: remove the others, or run tdk upgrade. The first on PATH is the one your shell runs; tools that launch /usr/local/bin/tdk directly may use another",
|
|
210
370
|
};
|
|
211
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* PIDs of `tilt up` processes for this project. The process list shows the Tiltfile path as it was typed at launch,
|
|
374
|
+
* which may or may not be the symlink-resolved path, so both spellings are looked up.
|
|
375
|
+
*/
|
|
376
|
+
export function projectTiltPids(projectRoot, find = findProjectTiltUpPids) {
|
|
377
|
+
const roots = new Set([projectRoot]);
|
|
378
|
+
try {
|
|
379
|
+
roots.add(realpathSync(projectRoot));
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// Keep the path as given.
|
|
383
|
+
}
|
|
384
|
+
const pids = new Set();
|
|
385
|
+
for (const root of roots) {
|
|
386
|
+
for (const pid of find(join(root, ".tdk", ".tdk-out", "Tiltfile")))
|
|
387
|
+
pids.add(pid);
|
|
388
|
+
}
|
|
389
|
+
return [...pids];
|
|
390
|
+
}
|
|
@@ -17,6 +17,32 @@ export function projectNetworkNames(projectName) {
|
|
|
17
17
|
const prefix = projectName.replace(/-/g, "_");
|
|
18
18
|
return PROJECT_NETWORK_SUFFIXES.map((suffix) => `${prefix}_${suffix}`);
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Prefixes of TDK projects among `networkNames`, from the networks the engine creates. Generic suffixes such as
|
|
22
|
+
* `backend` also occur in non-TDK compose projects, so a prefix counts only if it has a Traefik network, which
|
|
23
|
+
* only TDK creates.
|
|
24
|
+
*/
|
|
25
|
+
export function tdkNetworkPrefixes(networkNames) {
|
|
26
|
+
const prefixes = new Set();
|
|
27
|
+
for (const name of networkNames) {
|
|
28
|
+
for (const marker of ["traefik-public", "traefik-private"]) {
|
|
29
|
+
if (name.endsWith(`_${marker}`))
|
|
30
|
+
prefixes.add(name.slice(0, -(marker.length + 1)));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return prefixes;
|
|
34
|
+
}
|
|
35
|
+
/** The TDK project prefix a network belongs to (`umami` for `umami_backend`), or null. */
|
|
36
|
+
export function tdkNetworkOwner(name, prefixes) {
|
|
37
|
+
for (const suffix of PROJECT_NETWORK_SUFFIXES) {
|
|
38
|
+
if (name.endsWith(`_${suffix}`)) {
|
|
39
|
+
const prefix = name.slice(0, -(suffix.length + 1));
|
|
40
|
+
if (prefixes.has(prefix))
|
|
41
|
+
return prefix;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
20
46
|
/**
|
|
21
47
|
* Remove this project's own networks. Only the exact names the engine creates are touched, and `docker network rm`
|
|
22
48
|
* itself refuses a network that still has containers attached, so another project's networks and in-use networks
|
|
@@ -47,3 +47,17 @@ function canonical(path) {
|
|
|
47
47
|
export function isForeignTilt(owner, projectRoot) {
|
|
48
48
|
return owner !== null && canonical(owner.root) !== canonical(projectRoot);
|
|
49
49
|
}
|
|
50
|
+
/** Split the candidate Tilt UI ports that answer into this project's and other projects'. */
|
|
51
|
+
export async function classifyTiltPorts(candidatePorts, projectRoot, isServing, ownerOf = findTiltPortOwner) {
|
|
52
|
+
const result = { running: [], foreign: [] };
|
|
53
|
+
for (const port of candidatePorts) {
|
|
54
|
+
if (!(await isServing(port)))
|
|
55
|
+
continue;
|
|
56
|
+
const owner = ownerOf(port);
|
|
57
|
+
if (owner && isForeignTilt(owner, projectRoot))
|
|
58
|
+
result.foreign.push({ port, root: owner.root });
|
|
59
|
+
else
|
|
60
|
+
result.running.push(port);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|