@flisk-sh/flisk 1.3.201 → 1.3.205
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/CHANGELOG.md +26 -0
- package/cli/dist/cli.js +41 -9
- package/cli/dist/commands/doctor.js +22 -0
- package/cli/dist/commands/open.js +9 -1
- package/cli/dist/commands/project.js +4 -2
- package/cli/dist/commands/resource.js +3 -1
- package/cli/dist/utils/cold-preflight.js +14 -0
- package/cli/dist/utils/colima.js +163 -0
- package/cli/dist/utils/doctor-fix.js +105 -2
- package/cli/dist/utils/machine-output.js +27 -0
- package/cli/dist/utils/project-templates.js +1 -0
- package/cli/dist/utils/stack-progress.js +5 -4
- package/cli/dist-bundle/{chunk-M6S25PIS.js → chunk-4TMLNLYG.js} +1 -1
- package/cli/dist-bundle/{chunk-R67UHACQ.js → chunk-5G7E6CO2.js} +1 -1
- package/cli/dist-bundle/chunk-67M3DX6J.js +34 -0
- package/cli/dist-bundle/{chunk-KXKBWLTE.js → chunk-BHQDQQYQ.js} +1 -1
- package/cli/dist-bundle/{chunk-LYOSHFYU.js → chunk-CLQSJP6M.js} +1 -1
- package/cli/dist-bundle/chunk-GIMHTZNR.js +2 -0
- package/cli/dist-bundle/chunk-IQNUVQH5.js +52 -0
- package/cli/dist-bundle/{chunk-NC6KOYDN.js → chunk-P2MFLNY2.js} +1 -1
- package/cli/dist-bundle/chunk-WVSI4XL5.js +4 -0
- package/cli/dist-bundle/{chunk-5ZSZ4WCI.js → chunk-YNJTA77W.js} +1 -1
- package/cli/dist-bundle/chunk-YZRPX76N.js +2 -0
- package/cli/dist-bundle/cli.js +226 -226
- package/cli/dist-bundle/cold-preflight-R53YXYOC.js +2 -0
- package/cli/dist-bundle/colima-SVZX2UML.js +2 -0
- package/cli/dist-bundle/{open-SMKISRHH.js → open-PKNOQQXA.js} +1 -1
- package/cli/dist-bundle/project-I5QVEGNB.js +2 -0
- package/cli/dist-bundle/project-templates-JDRE7AVL.js +2 -0
- package/cli/dist-bundle/stack-progress-6GO2TCRH.js +2 -0
- package/cli/dist-bundle/{tilt-port-owner-67UVQJTX.js → tilt-port-owner-Y5LHXWPS.js} +1 -1
- package/cli/package.json +4 -4
- package/engine/assets/docker/wake-gateway/Dockerfile +1 -1
- package/engine/topologies/platform/docker/compose/compose.star +20 -18
- package/engine/topologies/platform/docker/compose/unsafe_value.star +23 -0
- package/engine/topologies/platform/docker/networking/traefik.star +19 -0
- package/package.json +2 -2
- package/cli/dist-bundle/chunk-GCFNSFW7.js +0 -52
- package/cli/dist-bundle/chunk-GNNN2WCF.js +0 -4
- package/cli/dist-bundle/chunk-S5EXKK7H.js +0 -34
- package/cli/dist-bundle/cold-preflight-6Z4RBFKV.js +0 -2
- package/cli/dist-bundle/project-WEC2GSR7.js +0 -2
- package/cli/dist-bundle/stack-progress-UVREHNNB.js +0 -2
- /package/cli/dist-bundle/{devtools-ZUHXW33W.js → devtools-MGITLOWZ.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes to the `flisk` CLI. Versions match [npm](https://www.npmjs.com/package/@flisk-sh/flisk?activeTab=versions) and the [binary releases](https://github.com/flisk-sh/releases/releases).
|
|
4
4
|
|
|
5
|
+
## 1.3.195 (2026-10-21)
|
|
6
|
+
|
|
7
|
+
- `flisk demo` and `flisk restaurant` now clone the demo template into a fixed directory (`~/.flisk/flisk-demo/`) instead of creating `tdk-restaurant-example-N` directories in the current workspace. Previous runs are cleaned up automatically. The `restaurant` template still creates a named directory in the current folder when used explicitly.
|
|
8
|
+
|
|
9
|
+
## 1.3.194 (2026-10-21)
|
|
10
|
+
|
|
11
|
+
- `flisk open` now opens the frontend of the project that was started, not the current working directory. The one-command flow (`npx @flisk-sh/flisk demo`) boots the cloned project and opens its frontend automatically.
|
|
12
|
+
|
|
13
|
+
## 1.3.193 (2026-10-21)
|
|
14
|
+
|
|
15
|
+
- New `flisk open` command: opens the first ready frontend in the browser. Works with `--json` output and integrates with the one-command flow.
|
|
16
|
+
|
|
17
|
+
## 1.3.192 (2026-10-21)
|
|
18
|
+
|
|
19
|
+
- Fixed the one-command flow (`npx @flisk-sh/flisk demo`): the cloned project now boots in place and the browser opens when the stack is up. The CLI copy was updated from "tdk" to "Flisk" throughout.
|
|
20
|
+
|
|
21
|
+
## 1.3.191 (2026-10-21)
|
|
22
|
+
|
|
23
|
+
- One command from scratch: `npx @flisk-sh/flisk demo` (or `restaurant`, `saas`, `erp`, etc.) clones a starter, installs the CLI globally, boots the stack, and opens the frontend.
|
|
24
|
+
- CLI rebrand: all user-facing copy changed from "tdk" / "TDK" to "Flisk" / "flisk" (package scope, homepage, repository, command names, error messages, docs).
|
|
25
|
+
- Internal: `cli/src/utils/one-command.ts` added to run the stack and detect the frontend URL.
|
|
26
|
+
|
|
27
|
+
## 1.3.190 (2026-10-21)
|
|
28
|
+
|
|
29
|
+
- Package metadata corrected: `repository`, `bugs`, and `homepage` in `package.json` now point at `flisk-sh/flisk` and `https://flisk.sh` (were still pointing at the decommissioned `tdk-landscape/tdk-cli-core` and `tdk-landscape.github.io`).
|
|
30
|
+
|
|
5
31
|
## 1.3.189 (2026-10-21)
|
|
6
32
|
|
|
7
33
|
- `flisk resource` now reports a malformed `service.json` with the file path and the reason it could not be parsed, instead of a raw JSON error (#1685).
|
package/cli/dist/cli.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Copyright (c) 2026 Flisk Landscape contributors
|
|
3
3
|
// SPDX-License-Identifier: MIT
|
|
4
|
-
import { existsSync, readdirSync } from "node:fs";
|
|
5
|
-
import {
|
|
4
|
+
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
6
7
|
import { cwd } from "node:process";
|
|
7
8
|
import chalk from "chalk";
|
|
8
9
|
import { showHelp } from "./commands/help.js";
|
|
@@ -34,14 +35,36 @@ if (templateArg) {
|
|
|
34
35
|
const runtimeNextSteps = formatRuntimeNextSteps(detectRuntimePrerequisites());
|
|
35
36
|
// A second demo in the same folder gets its own directory instead of failing.
|
|
36
37
|
const { nextAvailableProjectDir } = await import("./utils/one-command.js");
|
|
38
|
+
const { PROJECT_TEMPLATES } = await import("./utils/project-templates.js");
|
|
37
39
|
const baseName = templateTargetDir(templateArg, projectArg);
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
const fixedDir = projectArg ? undefined : PROJECT_TEMPLATES[templateArg]?.fixedDir;
|
|
41
|
+
let targetName;
|
|
42
|
+
if (fixedDir) {
|
|
43
|
+
// A template with a fixed directory always lands in the same place, so a demo never
|
|
44
|
+
// leaves a new folder behind in whatever folder the command was typed in.
|
|
45
|
+
targetName = join(homedir(), ".flisk", fixedDir);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
targetName = nextAvailableProjectDir(baseName, (candidate) => {
|
|
49
|
+
const path = resolve(cwd(), candidate);
|
|
50
|
+
return existsSync(path) && readdirSync(path).length > 0;
|
|
51
|
+
});
|
|
52
|
+
if (targetName !== baseName) {
|
|
53
|
+
console.log(chalk.gray(`The folder ${baseName} is taken, using ${targetName} instead.`));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const destination = isAbsolute(targetName) ? targetName : resolve(cwd(), targetName);
|
|
57
|
+
if (fixedDir && existsSync(destination)) {
|
|
58
|
+
// The demo owns this directory, so a second run starts from a fresh copy of it.
|
|
59
|
+
if (existsSync(join(destination, ".tdk", "project.json"))) {
|
|
60
|
+
console.log(chalk.gray(`Refreshing the demo in ${destination}...`));
|
|
61
|
+
rmSync(destination, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log(chalk.yellow(`${destination} already exists and is not a Flisk project.`));
|
|
65
|
+
console.log(chalk.gray(" Remove it, or use --project <dir> to put the demo somewhere else."));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
45
68
|
}
|
|
46
69
|
await cloneProjectTemplate(templateArg, targetName, runtimeNextSteps);
|
|
47
70
|
const install = ensureGlobalInstall();
|
|
@@ -53,6 +76,15 @@ if (templateArg) {
|
|
|
53
76
|
const { initializeProject, installServiceDependencies, runStackQuiet } = await import("./utils/one-command.js");
|
|
54
77
|
const { openFirstFrontend } = await import("./commands/open.js");
|
|
55
78
|
const { startSpinner } = await import("./utils/spinner.js");
|
|
79
|
+
// Docker on Colima is only usable while the VM is up, and a stopped VM looks like a
|
|
80
|
+
// broken Docker daemon. Say so before the long wait instead of after it.
|
|
81
|
+
const { checkColima } = await import("./utils/colima.js");
|
|
82
|
+
const colima = checkColima();
|
|
83
|
+
if (colima.isColima && !colima.running) {
|
|
84
|
+
console.log(chalk.yellow(colima.problem ?? "The Colima VM is not running."));
|
|
85
|
+
console.log(chalk.gray(` Fix: ${colima.fix ?? "colima start"}`));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
56
88
|
// A stack for this project may already be running (the same example in another folder
|
|
57
89
|
// shares its compose project). Then there is nothing to start: open what is up.
|
|
58
90
|
const { findReadyFrontend } = await import("./commands/open.js");
|
|
@@ -9,6 +9,7 @@ import { hasVerdaccioLicense } from "../generator/extension-fetch.js";
|
|
|
9
9
|
import { readProjectConfig } from "../generator/template-engine.js";
|
|
10
10
|
import { DEVCONTAINER_DOCKER_FIX, detectHost, isContainerHost, WEBCONTAINER_DOCS, WEBCONTAINER_UP_MESSAGE, } from "../utils/agent-host.js";
|
|
11
11
|
import { projectNeedsBun } from "../utils/bun-requirement.js";
|
|
12
|
+
import { checkColima as checkColimaState } from "../utils/colima.js";
|
|
12
13
|
import { AUTOGENERATED_DIR, CONTAINER_RUNTIME_CHECK, MASTER_CONFIG_FILES, PACKAGE_JSON, REQUIRED_PACKAGE_SCRIPTS, } from "../utils/constants.js";
|
|
13
14
|
import { isPathDiscovered, readDiscoveryPaths } from "../utils/discovery-paths.js";
|
|
14
15
|
import { checkCrashLoopingContainers } from "../utils/doctor-crashloop.js";
|
|
@@ -272,6 +273,26 @@ async function succeeds(command) {
|
|
|
272
273
|
return false;
|
|
273
274
|
}
|
|
274
275
|
}
|
|
276
|
+
/** The Colima VM behind Docker: a stopped VM or a colima CLI that cannot see it. */
|
|
277
|
+
export async function checkColima() {
|
|
278
|
+
const status = checkColimaState();
|
|
279
|
+
if (!status.isColima) {
|
|
280
|
+
return { name: "Colima", didPass: true, message: "Docker does not run on Colima" };
|
|
281
|
+
}
|
|
282
|
+
if (status.running && !status.problem) {
|
|
283
|
+
return {
|
|
284
|
+
name: "Colima",
|
|
285
|
+
didPass: true,
|
|
286
|
+
message: `Colima VM is running${status.arch ? ` (${status.arch})` : ""}`,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
name: "Colima",
|
|
291
|
+
didPass: false,
|
|
292
|
+
message: status.problem ?? "The Colima VM is not running",
|
|
293
|
+
fix: status.fix,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
275
296
|
export async function checkDockerRuntime() {
|
|
276
297
|
if (process.platform === "win32") {
|
|
277
298
|
try {
|
|
@@ -1323,6 +1344,7 @@ export const doctorCommand = new Command("doctor")
|
|
|
1323
1344
|
didPass: false,
|
|
1324
1345
|
message: "Ingress port plan is unavailable",
|
|
1325
1346
|
},
|
|
1347
|
+
checkColima,
|
|
1326
1348
|
checkDockerRuntime,
|
|
1327
1349
|
() => checkDiskSpace(),
|
|
1328
1350
|
requiresContainerRuntime("Docker OS", checkDockerOperatingSystem),
|
|
@@ -98,7 +98,15 @@ export async function openFirstFrontend(projectRootOverride) {
|
|
|
98
98
|
// publishing it to the host. Saying that is more useful than "not serving yet".
|
|
99
99
|
console.log(chalk.yellow(`The stack is running, but this machine cannot reach port ${ingress}.`));
|
|
100
100
|
console.log(chalk.gray(" Docker is not publishing the port to the host."));
|
|
101
|
-
|
|
101
|
+
const { checkColima } = await import("../utils/colima.js");
|
|
102
|
+
const colima = checkColima();
|
|
103
|
+
if (colima.isColima && !colima.running) {
|
|
104
|
+
console.log(chalk.gray(` ${colima.problem ?? "The Colima VM is not running"}`));
|
|
105
|
+
console.log(chalk.gray(` Fix: ${colima.fix ?? "colima start"}`));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.log(chalk.gray(" Restart the Docker runtime (Docker Desktop or Colima), then run `flisk open` again."));
|
|
109
|
+
}
|
|
102
110
|
}
|
|
103
111
|
else {
|
|
104
112
|
console.log(chalk.yellow("The stack is not serving the frontend yet."));
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
4
|
import { cpSync, existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
6
|
import { cwd } from "node:process";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import chalk from "chalk";
|
|
@@ -101,7 +101,9 @@ export async function cloneProjectTemplate(templateName, targetDir, runtimeNextS
|
|
|
101
101
|
if (!isPathSafe(dirName)) {
|
|
102
102
|
errorFactories.invalidPath(dirName).exit();
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
// If targetDir is an absolute path, use it directly as the destination
|
|
105
|
+
// Otherwise resolve it relative to cwd()
|
|
106
|
+
const destination = isAbsolute(dirName) ? dirName : resolve(cwd(), dirName);
|
|
105
107
|
if (existsSync(destination) && readdirSync(destination).length > 0) {
|
|
106
108
|
errorFactories.directoryExists(destination).exit();
|
|
107
109
|
}
|
|
@@ -214,7 +214,9 @@ export function createPackageJson(name, type, frameworkId, prismaEnabled = false
|
|
|
214
214
|
// image's `bun run build` fails with TS2688. ^20 matches the services that build.
|
|
215
215
|
"@types/node": "^20.0.0",
|
|
216
216
|
typescript: "^7.0.2",
|
|
217
|
-
|
|
217
|
+
// The same version the CLI tests itself with (cli/package.json devDependencies.vitest).
|
|
218
|
+
// Dependabot pins it exactly to keep CVE-2026-112950 fixed, and the scaffold follows.
|
|
219
|
+
vitest: "5.0.5",
|
|
218
220
|
"@biomejs/biome": "^2.5.13",
|
|
219
221
|
// vitest 5 peers on vite ^6.4.0 || ^7 || ^8; vite 5 makes `vitest run` fail at startup with
|
|
220
222
|
// ERR_PACKAGE_PATH_NOT_EXPORTED ('./module-runner'), in every resource once it is hoisted.
|
|
@@ -7,6 +7,7 @@ import { checkDockerCompose, checkDockerRuntime, checkDockerVersions, checkTilt,
|
|
|
7
7
|
import { BUN_FLOOR_LABEL, bunMeetsFloor } from "./bun-floor.js";
|
|
8
8
|
import { projectNeedsBun } from "./bun-requirement.js";
|
|
9
9
|
import { readyNextStep } from "./cli-command.js";
|
|
10
|
+
import { checkColima } from "./colima.js";
|
|
10
11
|
import { PROJECT_JSON } from "./constants.js";
|
|
11
12
|
import { checkNatsBroker } from "./doctor-wiring.js";
|
|
12
13
|
import { getHostPortPlan } from "./host-port-config.js";
|
|
@@ -88,6 +89,19 @@ export async function runColdPreflight(opts = {}) {
|
|
|
88
89
|
// The runtime executables are only probed when the command actually starts a
|
|
89
90
|
// stack. Scaffolding a project must not depend on a running Docker daemon.
|
|
90
91
|
if (scope === "runtime") {
|
|
92
|
+
// Docker on Colima is only useful when the VM is up: a stopped VM looks like a broken
|
|
93
|
+
// Docker daemon, and the port publishing silently stops working.
|
|
94
|
+
const colima = checkColima();
|
|
95
|
+
if (colima.isColima) {
|
|
96
|
+
items.push({
|
|
97
|
+
id: "colima",
|
|
98
|
+
ok: colima.running,
|
|
99
|
+
message: colima.running
|
|
100
|
+
? `Colima VM is running${colima.arch ? ` (${colima.arch})` : ""}`
|
|
101
|
+
: (colima.problem ?? "The Colima VM is not running"),
|
|
102
|
+
fix: colima.running ? undefined : colima.fix,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
91
105
|
items.push(await safeCheck("docker", checkDockerRuntime, "Docker daemon is not running"));
|
|
92
106
|
items.push(await safeCheck("compose", async () => {
|
|
93
107
|
const [compose, versions] = await Promise.all([
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Copyright (c) 2026 Flisk Landscape contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
const defaultDeps = () => ({
|
|
7
|
+
env: process.env,
|
|
8
|
+
dockerAnswers: () => {
|
|
9
|
+
try {
|
|
10
|
+
execFileSync("docker", ["info", "--format", "{{.ServerVersion}}"], {
|
|
11
|
+
encoding: "utf-8",
|
|
12
|
+
timeout: 10_000,
|
|
13
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
14
|
+
});
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
run: (command, args) => {
|
|
22
|
+
// colima logs to stderr, so both streams matter.
|
|
23
|
+
const result = spawnSync(command, args, {
|
|
24
|
+
encoding: "utf-8",
|
|
25
|
+
timeout: 20_000,
|
|
26
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
27
|
+
});
|
|
28
|
+
return `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
29
|
+
},
|
|
30
|
+
exists: existsSync,
|
|
31
|
+
home: process.env.HOME ?? "",
|
|
32
|
+
});
|
|
33
|
+
/** True when the Docker endpoint belongs to a Colima VM. */
|
|
34
|
+
export function isColimaDocker(deps = defaultDeps()) {
|
|
35
|
+
const host = deps.env.DOCKER_HOST ?? "";
|
|
36
|
+
if (host.includes(".colima"))
|
|
37
|
+
return true;
|
|
38
|
+
const socket = join(deps.home, ".colima", "default", "docker.sock");
|
|
39
|
+
return deps.exists(socket) && host.includes("docker.sock") === false && host !== "";
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The state of the Colima VM behind Docker.
|
|
43
|
+
*
|
|
44
|
+
* Two failures matter and both look like "Docker is not working": the VM is not running, and
|
|
45
|
+
* Lima itself is an x86_64 build on an Apple Silicon machine, which cannot start the VM.
|
|
46
|
+
*/
|
|
47
|
+
export function checkColima(deps = defaultDeps()) {
|
|
48
|
+
if (!isColimaDocker(deps)) {
|
|
49
|
+
return { isColima: false, running: true };
|
|
50
|
+
}
|
|
51
|
+
let status = "";
|
|
52
|
+
try {
|
|
53
|
+
status = deps.run("colima", ["status"]);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const message = String(error?.stderr ?? error);
|
|
57
|
+
if (/rosetta|native arch/i.test(message)) {
|
|
58
|
+
return {
|
|
59
|
+
isColima: true,
|
|
60
|
+
running: false,
|
|
61
|
+
problem: "Lima is installed as an x86_64 build, so it cannot start the Colima VM on this Mac",
|
|
62
|
+
fix: "Install a native Lima (brew reinstall lima, or use a native limactl on PATH) and run `colima start`",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (deps.dockerAnswers()) {
|
|
66
|
+
// The VM is up: the colima CLI on this PATH simply cannot see it, which is what a
|
|
67
|
+
// Rosetta (x86_64) limactl does on Apple Silicon.
|
|
68
|
+
return {
|
|
69
|
+
isColima: true,
|
|
70
|
+
running: true,
|
|
71
|
+
problem: "The Colima VM is running, but the colima CLI on this PATH cannot see it",
|
|
72
|
+
fix: "Use a native limactl (a Rosetta x86_64 limactl cannot talk to the VM): brew reinstall lima, or put a native limactl first on PATH",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (/not running/i.test(message)) {
|
|
76
|
+
return {
|
|
77
|
+
isColima: true,
|
|
78
|
+
running: false,
|
|
79
|
+
problem: "The Colima VM is not running",
|
|
80
|
+
fix: "Run `colima start`",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
isColima: true,
|
|
85
|
+
running: false,
|
|
86
|
+
problem: "The state of the Colima VM could not be read",
|
|
87
|
+
fix: "Run `colima status` to see what it says",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (/is running/i.test(status)) {
|
|
91
|
+
const arch = /arch:\s*(\S+)/.exec(status)?.[1];
|
|
92
|
+
return { isColima: true, running: true, arch };
|
|
93
|
+
}
|
|
94
|
+
if (deps.dockerAnswers()) {
|
|
95
|
+
return {
|
|
96
|
+
isColima: true,
|
|
97
|
+
running: true,
|
|
98
|
+
problem: "The Colima VM is running, but the colima CLI on this PATH cannot see it",
|
|
99
|
+
fix: "Use a native limactl (a Rosetta x86_64 limactl cannot talk to the VM): brew reinstall lima, or put a native limactl first on PATH",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
isColima: true,
|
|
104
|
+
running: false,
|
|
105
|
+
problem: "The Colima VM is not running",
|
|
106
|
+
fix: "Run `colima start`",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* What can be repaired about the Colima runtime: a VM that is not running is started, a VM
|
|
111
|
+
* whose Docker daemon does not answer is restarted, and a colima CLI that cannot see the VM
|
|
112
|
+
* (a Rosetta limactl) needs a native lima.
|
|
113
|
+
*/
|
|
114
|
+
export function planColimaRepair(status, dockerAnswers) {
|
|
115
|
+
if (!status.isColima)
|
|
116
|
+
return undefined;
|
|
117
|
+
// A Lima that is an x86_64 build on Apple Silicon, or a colima CLI that cannot see the VM because of it,
|
|
118
|
+
// needs a native limactl before anything else can start.
|
|
119
|
+
if (status.problem && /x86_64|rosetta|native arch|cannot see it/i.test(status.problem)) {
|
|
120
|
+
return "native-lima";
|
|
121
|
+
}
|
|
122
|
+
if (!status.running)
|
|
123
|
+
return "start";
|
|
124
|
+
if (!dockerAnswers)
|
|
125
|
+
return "restart";
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
/** Runs a colima command with a native limactl first on PATH, when one is installed. */
|
|
129
|
+
export function nativeLimaPath(env = process.env) {
|
|
130
|
+
const home = env.HOME ?? "";
|
|
131
|
+
// Prefer the Apple Silicon Homebrew location first; an x86_64 limactl must never be treated as native.
|
|
132
|
+
const candidates = [
|
|
133
|
+
"/opt/homebrew/bin",
|
|
134
|
+
home ? `${home}/.local/bin` : "",
|
|
135
|
+
home ? `${home}/bin` : "",
|
|
136
|
+
].filter(Boolean);
|
|
137
|
+
for (const dir of candidates) {
|
|
138
|
+
const candidate = join(dir, "limactl");
|
|
139
|
+
try {
|
|
140
|
+
if (!existsSync(candidate))
|
|
141
|
+
continue;
|
|
142
|
+
// Verify the binary is actually a native (arm64/aarch64) build, not a Rosetta x86_64 one.
|
|
143
|
+
let archOutput = "";
|
|
144
|
+
try {
|
|
145
|
+
archOutput = execFileSync("file", ["-bL", candidate], {
|
|
146
|
+
encoding: "utf-8",
|
|
147
|
+
timeout: 5_000,
|
|
148
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// `file` unavailable: fall back to accepting the candidate.
|
|
153
|
+
return dir;
|
|
154
|
+
}
|
|
155
|
+
if (/arm64|aarch64/i.test(archOutput))
|
|
156
|
+
return dir;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Not readable; try the next one.
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// Copyright (c) 2026 Flisk Landscape contributors
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
-
import { execFile } from "node:child_process";
|
|
4
|
-
import { realpathSync, statfsSync } from "node:fs";
|
|
3
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
4
|
+
import { existsSync, realpathSync, statfsSync } from "node:fs";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
|
+
import { checkColima, nativeLimaPath, planColimaRepair } from "./colima.js";
|
|
8
9
|
import { DISK_WARN_GIB } from "./doctor-host.js";
|
|
9
10
|
import { findProjectTiltUpPids, projectNetworkNames, tdkNetworkOwner, tdkNetworkPrefixes, } from "./down-cleanup.js";
|
|
10
11
|
import { tiltGetUiResources } from "./up-readiness.js";
|
|
@@ -21,6 +22,20 @@ const runDocker = async (args) => {
|
|
|
21
22
|
});
|
|
22
23
|
return stdout;
|
|
23
24
|
};
|
|
25
|
+
/** Whether the Docker daemon answers right now. */
|
|
26
|
+
function dockerAnswers() {
|
|
27
|
+
try {
|
|
28
|
+
execFileSync("docker", ["info", "--format", "{{.ServerVersion}}"], {
|
|
29
|
+
encoding: "utf-8",
|
|
30
|
+
timeout: 15_000,
|
|
31
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
32
|
+
});
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
24
39
|
function firstLine(error) {
|
|
25
40
|
const stderr = String(error.stderr ?? error).trim();
|
|
26
41
|
return stderr.split(/\r?\n/)[0] ?? "";
|
|
@@ -72,6 +87,9 @@ async function tiltProcessStatus(pid) {
|
|
|
72
87
|
}
|
|
73
88
|
export function createDockerFixDeps(run = runDocker) {
|
|
74
89
|
return {
|
|
90
|
+
colima: runColima,
|
|
91
|
+
colimaStatus: () => checkColima(),
|
|
92
|
+
dockerAnswers,
|
|
75
93
|
async listNetworks() {
|
|
76
94
|
const names = (await run(["network", "ls", "--format", "{{.Name}}"]))
|
|
77
95
|
.split(/\r?\n/)
|
|
@@ -167,6 +185,34 @@ export function createDockerFixDeps(run = runDocker) {
|
|
|
167
185
|
};
|
|
168
186
|
}
|
|
169
187
|
export const dockerFixDeps = createDockerFixDeps();
|
|
188
|
+
/** Starts or restarts Colima, with a native limactl first on PATH when one is installed. */
|
|
189
|
+
async function runColima(action) {
|
|
190
|
+
if (action === "native-lima") {
|
|
191
|
+
let nativeDir = nativeLimaPath();
|
|
192
|
+
if (!nativeDir) {
|
|
193
|
+
// No native limactl on PATH: reinstall Lima through the native Homebrew, which pulls a native build on Apple Silicon.
|
|
194
|
+
const brewBin = existsSync("/opt/homebrew/bin/brew") ? "/opt/homebrew/bin/brew" : "brew";
|
|
195
|
+
await execFilePromise(brewBin, ["reinstall", "lima"], {
|
|
196
|
+
encoding: "utf-8",
|
|
197
|
+
timeout: 300_000,
|
|
198
|
+
});
|
|
199
|
+
nativeDir = nativeLimaPath();
|
|
200
|
+
if (!nativeDir) {
|
|
201
|
+
throw new Error("brew reinstall lima did not provide a native limactl");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const env = nativeDir
|
|
205
|
+
? { ...process.env, PATH: `${nativeDir}:${process.env.PATH ?? ""}` }
|
|
206
|
+
: process.env;
|
|
207
|
+
await execFilePromise("colima", ["start"], { encoding: "utf-8", timeout: 300_000, env });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const nativeDir = nativeLimaPath();
|
|
211
|
+
const env = nativeDir
|
|
212
|
+
? { ...process.env, PATH: `${nativeDir}:${process.env.PATH ?? ""}` }
|
|
213
|
+
: process.env;
|
|
214
|
+
await execFilePromise("colima", [action], { encoding: "utf-8", timeout: 300_000, env });
|
|
215
|
+
}
|
|
170
216
|
/**
|
|
171
217
|
* What `--fix` would do: stop this project's stale Tilt; remove unused networks that are a leftover doctor probe or
|
|
172
218
|
* this project's own (the exact names the engine creates, skipped while its Tilt runs). Other Flisk projects' unused
|
|
@@ -195,6 +241,43 @@ export async function planDoctorFixes(projectName, deps = dockerFixDeps, context
|
|
|
195
241
|
const projectBusy = liveTilt.length > 0
|
|
196
242
|
? `Tilt is running for this project (pid ${liveTilt.join(", ")}); run flisk down first`
|
|
197
243
|
: undefined;
|
|
244
|
+
// The Docker runtime itself: a Colima VM that is not running, one whose daemon does not
|
|
245
|
+
// answer, or a colima CLI that cannot see the VM. All three look like "Docker is broken".
|
|
246
|
+
const colima = deps.colimaStatus();
|
|
247
|
+
const repair = planColimaRepair(colima, deps.dockerAnswers());
|
|
248
|
+
if (repair === "start") {
|
|
249
|
+
actions.push({
|
|
250
|
+
id: "colima:start",
|
|
251
|
+
check: "Colima",
|
|
252
|
+
scope: "host",
|
|
253
|
+
command: "colima start",
|
|
254
|
+
status: "planned",
|
|
255
|
+
reason: colima.problem ?? "the Colima VM is not running",
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
else if (repair === "restart") {
|
|
259
|
+
actions.push({
|
|
260
|
+
id: "colima:restart",
|
|
261
|
+
check: "Colima",
|
|
262
|
+
scope: "host",
|
|
263
|
+
command: "colima restart",
|
|
264
|
+
status: "planned",
|
|
265
|
+
reason: "the Colima VM is running but its Docker daemon does not answer",
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
else if (repair === "native-lima") {
|
|
269
|
+
const nativeDir = nativeLimaPath();
|
|
270
|
+
actions.push({
|
|
271
|
+
id: "colima:native-lima",
|
|
272
|
+
check: "Colima",
|
|
273
|
+
scope: "host",
|
|
274
|
+
command: nativeDir ? `PATH="${nativeDir}:$PATH" colima start` : "brew reinstall lima",
|
|
275
|
+
status: "planned",
|
|
276
|
+
reason: nativeDir
|
|
277
|
+
? `the colima CLI on this PATH cannot see the VM; a native limactl is installed in ${nativeDir}`
|
|
278
|
+
: "the colima CLI on this PATH cannot see the VM; lima is installed as an x86_64 build (brew reinstall lima)",
|
|
279
|
+
});
|
|
280
|
+
}
|
|
198
281
|
const networks = await deps.listNetworks();
|
|
199
282
|
const freeGib = deps.freeDiskGib();
|
|
200
283
|
if (freeGib !== null && freeGib < DISK_WARN_GIB) {
|
|
@@ -303,6 +386,26 @@ export async function applyDoctorFixes(actions, options, deps = dockerFixDeps) {
|
|
|
303
386
|
results.push({ ...action, status: "applied", reason: "pruned" });
|
|
304
387
|
continue;
|
|
305
388
|
}
|
|
389
|
+
if (action.id.startsWith("colima:")) {
|
|
390
|
+
const what = action.id.slice("colima:".length);
|
|
391
|
+
if (what === "start" || what === "restart") {
|
|
392
|
+
await deps.colima(what);
|
|
393
|
+
results.push({ ...action, status: "applied", reason: `colima ${what} ran` });
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
await deps.colima("native-lima");
|
|
398
|
+
results.push({
|
|
399
|
+
...action,
|
|
400
|
+
status: "applied",
|
|
401
|
+
reason: "native lima repaired and colima started",
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
results.push({ ...action, status: "failed", reason: firstLine(error) });
|
|
406
|
+
}
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
306
409
|
if (action.id.startsWith("stop-tilt:")) {
|
|
307
410
|
deps.stopProcess(Number(action.id.slice("stop-tilt:".length)));
|
|
308
411
|
results.push({ ...action, status: "applied", reason: "stopped" });
|
|
@@ -5,6 +5,33 @@ export const SCHEMA_VERSION = 1;
|
|
|
5
5
|
export function createMachineEnvelope(data, errors = []) {
|
|
6
6
|
return { schemaVersion: SCHEMA_VERSION, data, errors };
|
|
7
7
|
}
|
|
8
|
+
function isMachineError(value) {
|
|
9
|
+
if (typeof value !== "object" || value === null)
|
|
10
|
+
return false;
|
|
11
|
+
const error = value;
|
|
12
|
+
if (typeof error.code !== "string" || typeof error.message !== "string")
|
|
13
|
+
return false;
|
|
14
|
+
if (error.suggestions !== undefined) {
|
|
15
|
+
if (!Array.isArray(error.suggestions))
|
|
16
|
+
return false;
|
|
17
|
+
if (!error.suggestions.every((item) => typeof item === "string"))
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
/** Narrows unknown JSON to the machine-output envelope used by CLI `--json` commands. */
|
|
23
|
+
export function isMachineOutputEnvelope(value) {
|
|
24
|
+
if (typeof value !== "object" || value === null)
|
|
25
|
+
return false;
|
|
26
|
+
const envelope = value;
|
|
27
|
+
if (envelope.schemaVersion !== SCHEMA_VERSION)
|
|
28
|
+
return false;
|
|
29
|
+
if (!("data" in envelope))
|
|
30
|
+
return false;
|
|
31
|
+
if (!Array.isArray(envelope.errors))
|
|
32
|
+
return false;
|
|
33
|
+
return envelope.errors.every(isMachineError);
|
|
34
|
+
}
|
|
8
35
|
export function toMachineError(error) {
|
|
9
36
|
if (error instanceof TdkError) {
|
|
10
37
|
return {
|
|
@@ -3,6 +3,7 @@ export const PROJECT_TEMPLATES = {
|
|
|
3
3
|
demo: {
|
|
4
4
|
repo: "https://github.com/flisk-sh/tdk-restaurant-example.git",
|
|
5
5
|
description: "The demo stack: restaurant ops with reservations, kitchen and floor control",
|
|
6
|
+
fixedDir: "flisk-demo",
|
|
6
7
|
},
|
|
7
8
|
restaurant: {
|
|
8
9
|
repo: "https://github.com/flisk-sh/tdk-restaurant-example.git",
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// Copyright (c) 2026 Flisk Landscape contributors
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
|
+
const defaultTiltResourceReader = (port) => new Promise((resolve) => {
|
|
5
|
+
execFile("tilt", ["get", "uiresources", "-o", "json", "--port", String(port)], { timeout: 15_000, maxBuffer: 16 * 1024 * 1024 }, (error, stdout) => resolve(error ? null : stdout));
|
|
6
|
+
});
|
|
4
7
|
/** Reads the resource states from the Tilt UI API. */
|
|
5
|
-
export async function readStackProgress(port,
|
|
6
|
-
const json = await
|
|
7
|
-
execFile(tiltBin, ["get", "uiresources", "-o", "json", "--port", String(port)], { timeout: 15_000, maxBuffer: 16 * 1024 * 1024 }, (error, stdout) => resolve(error ? null : stdout));
|
|
8
|
-
});
|
|
8
|
+
export async function readStackProgress(port, read = defaultTiltResourceReader) {
|
|
9
|
+
const json = await read(port);
|
|
9
10
|
if (!json)
|
|
10
11
|
return undefined;
|
|
11
12
|
let items = [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
|
|
2
|
-
import{b as N}from"./chunk-
|
|
2
|
+
import{b as N}from"./chunk-YNJTA77W.js";import{G as O,O as m,Ta as f,c as $,d as C,fa as T,g as U,h as E,k as F,l as P,s as l,x as M}from"./chunk-CLQSJP6M.js";import{execFileSync as de,execSync as ue}from"node:child_process";import{existsSync as R,readdirSync as V,readFileSync as y}from"node:fs";import{basename as le,dirname as me,join as u,relative as k}from"node:path";function Oe(t){return I(t,!1)}function Ne(t){return I(t,!0)}function I(t,e){return`import { Hono } from 'hono';
|
|
3
3
|
${e?`import { sql } from 'drizzle-orm';
|
|
4
4
|
import { db } from './db.js';
|
|
5
5
|
`:""}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
|
|
2
|
-
import{O as h,Ta as D,Ua as L,fa as _,h as I,k as w,l as A,q as j,qa as x,s as T,x as F}from"./chunk-
|
|
2
|
+
import{O as h,Ta as D,Ua as L,fa as _,h as I,k as w,l as A,q as j,qa as x,s as T,x as F}from"./chunk-CLQSJP6M.js";import{execFileSync as B}from"node:child_process";import{existsSync as de,mkdirSync as le,readFileSync as K,realpathSync as fe,writeFileSync as pe}from"node:fs";import{join as S,resolve as he,sep as me}from"node:path";import{createServer as ie}from"node:net";import{connect as oe}from"node:net";function U(e,t=500){return new Promise(r=>{let s=oe({port:e,host:"127.0.0.1"}),n=o=>{s.destroy(),r(o)};s.setTimeout(t,()=>n(!1)),s.once("connect",()=>n(!0)),s.once("error",()=>n(!1))})}var ae={ingressHttp:{start:8080,end:8180},ingressHttps:{start:8443,end:8543},postgres:{start:15432,end:15532}},v={ingressHttp:"TDK_HTTP_PORT",ingressHttps:"TDK_HTTPS_PORT",postgres:"TDK_POSTGRES_PORT"},H={ingressHttp:80,ingressHttps:443,postgres:5432},E={ingressHttp:H.ingressHttp,ingressHttps:H.ingressHttps};function ce(e,t){return e==="EACCES"&&t==="darwin"}async function O(e){return await U(e)?!1:new Promise(t=>{let r=ie();r.once("error",s=>{t(ce(s.code,process.platform))}),r.listen(e,"127.0.0.1",()=>r.close(()=>t(!0)))})}function ue(e,t){let r=Number(t);if(!Number.isInteger(r)||r<1||r>65535)throw new Error(`${e} must be an integer between 1 and 65535 (received ${JSON.stringify(t)})`);return r}async function R(e={}){let t=e.env??process.env,r=e.isAvailable??O,s={ingressHttp:!1,ingressHttps:!1,postgres:!1},n={ingressHttp:"",ingressHttps:"",postgres:""},o={explicit:s},a=new Set;for(let c of["ingressHttp","ingressHttps","postgres"]){let u=t[v[c]],i=u!==void 0&&u.trim()!=="",d=e.ranges?.[c]??ae[c],l=E[c],f=i?[ue(v[c],u)]:[...l===void 0?[]:[l],...Array.from({length:d.end-d.start+1},(g,k)=>d.start+k)],m;for(let g of f)if(!a.has(g)){if(await r(g)){m=g;break}if(i)throw new Error(`${v[c]}=${g} is already in use. Choose a free host port and retry.`)}if(m===void 0)throw new Error(`No available host port in ${d.start}-${d.end} for ${c}. Set ${v[c]} to a free port and retry.`);a.add(m),o[c]=m,s[c]=i,n[c]=i?"explicit override":m===l?"requested port is free":`selected from fallback range ${d.start}-${d.end}`}return{...o,requested:{...H},reason:n}}function Ve(e){return`Host ports: HTTP ${e.ingressHttp}, HTTPS ${e.ingressHttps}, Postgres ${e.postgres}`}var G=S(".tdk",".tdk-out","host-ports.json");function ge(e,t,r){return e.split(`
|
|
3
3
|
`).some(s=>{let[n="",o=""]=s.split("|"),a=n.toLowerCase(),c=t.toLowerCase();return(a.startsWith(`${c}_`)||a.startsWith(`${c}-`))&&o.includes(`:${r}->`)})}function V(e){let t=S(e,G);if(!de(t))return null;try{let r=JSON.parse(K(t,"utf-8"));if(!r||typeof r!="object")return null;let s=r;return!Number.isInteger(s.ingressHttp)||!Number.isInteger(s.ingressHttps)||!Number.isInteger(s.postgres)||!s.requested||!s.explicit||!s.reason?null:s}catch{return null}}function Ye(e,t){let r=S(e,G);le(S(e,".tdk",".tdk-out"),{recursive:!0}),pe(r,`${JSON.stringify(t,null,2)}
|
|
4
4
|
`,{encoding:"utf-8",mode:384})}function Qe(e){process.env.TDK_HTTP_PORT=String(e.ingressHttp),process.env.TDK_HTTPS_PORT=String(e.ingressHttps),process.env.TDK_POSTGRES_PORT=String(e.postgres)}var ke="com.docker.compose.project.working_dir";function Pe(e,t,r){let s=new Set([he(t)]);try{s.add(fe(t))}catch{}return e.split(`
|
|
5
5
|
`).some(n=>{let[,o="",a=""]=n.split("|");return!a||!o.includes(`:${r}->`)?!1:[...s].some(c=>a===c||a.startsWith(`${c}${me}`))})}function Xe(e,t,r=()=>B("docker",["ps","--format",`{{.Names}}|{{.Ports}}|{{.Label "${ke}"}}`],{encoding:"utf-8",timeout:5e3})){let s=V(e);if(!s||s.ingressHttp===t)return t;try{if(Pe(r(),e,s.ingressHttp))return s.ingressHttp}catch{}return t}async function et(e,t={}){let r=V(e),s=["TDK_HTTP_PORT","TDK_HTTPS_PORT","TDK_POSTGRES_PORT"].some(i=>process.env[i]);if(t.inspectDocker===!1&&r&&!s)return r;let n="";try{let i=JSON.parse(K(S(e,".tdk",w),"utf-8"));n=String(i?.project?.name??"tdk-project").replace(/-/g,"_").toLowerCase()}catch{n="tdk_project"}let o="";if(t.inspectDocker!==!1)try{o=B("docker",["ps","--format","{{.Names}}|{{.Ports}}"],{encoding:"utf-8",timeout:5e3})}catch{}let a=i=>ge(o,n,i),c=t.isPortFree??O,u=async i=>await c(i)||a(i);if(r&&!s){for(let i of[r.ingressHttp,r.ingressHttps,r.postgres])if(!await u(i))return R({isAvailable:u});for(let[i,d]of Object.entries(E)){let l=r[i];if(d!==void 0&&l!==d&&await u(d))return R({isAvailable:u})}return r}return R({isAvailable:u})}import{execFileSync as Q,execSync as $}from"node:child_process";import{existsSync as C,readdirSync as Te,readFileSync as N}from"node:fs";import{createServer as Se}from"node:net";import{join as P}from"node:path";function W(e,t){return e==="pending"||e==="in_progress"||e==="none"||t==="pending"||t==="none"}var J="container is running, but its health check is failing";function M(e){let t=e?.runtimeStatus??"";if(t!=="ok")return t;let r=e?.composeResourceInfo?.healthStatus;return r==="unhealthy"?"error":r==="starting"?"pending":t}var b=1e4,z=3e3,be="http://localhost:4873";function X(e){return`'${e.replace(/'/g,"'\\''")}'`}function y(e,t,r,s,n){return process.platform==="win32"?Q(F(t)??t,r,{...n,windowsHide:!0}):e(s,n)}var ye=[80,443];function $e(e){return e.replace(/-/g,"_")}function we(e,t){let r=new Set(t),s=[];for(let n of e.split(`
|