@flisk-sh/flisk 1.3.198 → 1.3.200

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/cli/dist/cli.js +63 -15
  2. package/cli/dist/commands/open.js +8 -0
  3. package/cli/dist/utils/one-command.js +80 -0
  4. package/cli/dist/utils/project-templates.js +14 -0
  5. package/cli/dist/utils/stack-progress.js +64 -0
  6. package/cli/dist-bundle/{chunk-KBGQ4NJE.js → chunk-DS47LHYF.js} +1 -1
  7. package/cli/dist-bundle/chunk-EICMGZXL.js +5 -0
  8. package/cli/dist-bundle/chunk-GEXOZCEQ.js +2 -0
  9. package/cli/dist-bundle/chunk-H446NBVS.js +71 -0
  10. package/cli/dist-bundle/chunk-IYZENTVI.js +34 -0
  11. package/cli/dist-bundle/{chunk-US4D5GJJ.js → chunk-LMHKXU2Q.js} +1 -1
  12. package/cli/dist-bundle/chunk-RMZQU74X.js +366 -0
  13. package/cli/dist-bundle/chunk-RWNIFXXG.js +4 -0
  14. package/cli/dist-bundle/chunk-WUO7C6WO.js +2 -0
  15. package/cli/dist-bundle/cli.js +266 -266
  16. package/cli/dist-bundle/cold-preflight-LPH7WF3B.js +2 -0
  17. package/cli/dist-bundle/one-command-YP6L6MMM.js +5 -0
  18. package/cli/dist-bundle/open-PF6BGKCR.js +2 -0
  19. package/cli/dist-bundle/project-2TVWT57J.js +2 -0
  20. package/cli/dist-bundle/stack-progress-UVREHNNB.js +2 -0
  21. package/cli/dist-bundle/tilt-port-owner-7O5FOW5C.js +2 -0
  22. package/cli/package.json +1 -1
  23. package/package.json +1 -1
  24. package/cli/dist-bundle/chunk-DWHA5YKT.js +0 -435
  25. package/cli/dist-bundle/chunk-KGKKDD4K.js +0 -2
  26. package/cli/dist-bundle/chunk-SB5WAMSU.js +0 -5
  27. package/cli/dist-bundle/chunk-WPQOTHB5.js +0 -4
  28. package/cli/dist-bundle/chunk-YUARXGF4.js +0 -34
  29. package/cli/dist-bundle/cold-preflight-5TVSNLIV.js +0 -2
  30. package/cli/dist-bundle/one-command-TKXMWXCK.js +0 -4
  31. package/cli/dist-bundle/open-5SUD6LKB.js +0 -2
  32. package/cli/dist-bundle/project-5GTJYFAE.js +0 -2
  33. package/cli/dist-bundle/project-templates-BHURA3LT.js +0 -2
package/cli/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ import { createProgram } from "./program.js";
10
10
  import { ensureGlobalInstall } from "./utils/cli-command.js";
11
11
  import { formatColdPreflight, runColdPreflight } from "./utils/cold-preflight.js";
12
12
  import { unknownHelpTarget } from "./utils/help-command.js";
13
+ import { bareTemplateArg } from "./utils/project-templates.js";
13
14
  const program = createProgram();
14
15
  // Default: show help if no command provided
15
16
  if (process.argv.length === 2) {
@@ -24,12 +25,11 @@ if (process.argv.length === 2) {
24
25
  }
25
26
  // One command from scratch: flisk <template> | flisk --template <name> --project <dir>
26
27
  // It clones a starter, installs the CLI globally, boots the stack and opens the frontend.
27
- const templateArg = readOptionValue(process.argv, "--template") ?? (await bareTemplateArg(process.argv));
28
+ const templateArg = readOptionValue(process.argv, "--template") ?? bareTemplateArg(process.argv);
28
29
  const projectArg = readOptionValue(process.argv, "--project");
29
30
  if (templateArg) {
30
31
  const { cloneProjectTemplate, templateTargetDir } = await import("./commands/project.js");
31
32
  const { assertMachineReadyOrExit, detectRuntimePrerequisites, formatRuntimeNextSteps } = await import("./utils/cold-preflight.js");
32
- const { PROJECT_TEMPLATES } = await import("./utils/project-templates.js");
33
33
  await assertMachineReadyOrExit({ scope: "scaffold" });
34
34
  const runtimeNextSteps = formatRuntimeNextSteps(detectRuntimePrerequisites());
35
35
  // A second demo in the same folder gets its own directory instead of failing.
@@ -50,11 +50,68 @@ if (templateArg) {
50
50
  }
51
51
  const binPath = process.argv[1];
52
52
  if (binPath && existsSync(destination)) {
53
- const { runStackQuiet } = await import("./utils/one-command.js");
53
+ const { initializeProject, installServiceDependencies, runStackQuiet } = await import("./utils/one-command.js");
54
54
  const { openFirstFrontend } = await import("./commands/open.js");
55
55
  const { startSpinner } = await import("./utils/spinner.js");
56
- const spinner = startSpinner("Starting the stack (the first build takes a minute)...");
56
+ // A stack for this project may already be running (the same example in another folder
57
+ // shares its compose project). Then there is nothing to start: open what is up.
58
+ const { findReadyFrontend } = await import("./commands/open.js");
59
+ const running = await findReadyFrontend(destination, 0);
60
+ if (running) {
61
+ console.log(chalk.gray(`A stack for this project is already running (${running.name}).`));
62
+ const { openFirstFrontend } = await import("./commands/open.js");
63
+ await openFirstFrontend(destination);
64
+ process.exit(0);
65
+ }
66
+ // A cloned starter has no project configuration yet, and `flisk up` refuses to run
67
+ // without one, so the project is set up before the stack starts.
68
+ const setupSpinner = startSpinner("Setting up the project...");
69
+ const setup = await initializeProject(binPath, destination);
70
+ if (!setup.ok) {
71
+ setupSpinner.fail("The project setup failed.");
72
+ console.log(chalk.yellow(setup.error ?? "flisk project --yes did not finish."));
73
+ process.exit(1);
74
+ }
75
+ setupSpinner.succeed("The project is ready.");
76
+ // A fresh clone has no host dependencies, and the stack typechecks the services on the
77
+ // host, so install them before the stack starts.
78
+ const installSpinner = startSpinner("Installing the service dependencies...");
79
+ const installs = await installServiceDependencies(destination);
80
+ const failed = installs.filter((entry) => !entry.ok);
81
+ if (failed.length > 0) {
82
+ installSpinner.fail("Some services could not install their dependencies.");
83
+ for (const entry of failed) {
84
+ console.log(chalk.yellow(` - ${entry.dir}: run "bun install" there`));
85
+ }
86
+ }
87
+ else {
88
+ installSpinner.succeed("The service dependencies are installed.");
89
+ }
90
+ const spinner = startSpinner("Starting the stack (the first build takes a few minutes)...");
91
+ const started = Date.now();
92
+ // Show what the stack is doing instead of a silent spinner: the resource states, the
93
+ // elapsed time and where to watch the live logs. The Tilt port is not fixed (another
94
+ // project may hold the default), so the ports that answer are looked at.
95
+ const { readStackProgress, formatProgress } = await import("./utils/stack-progress.js");
96
+ const { classifyTiltPorts } = await import("./utils/tilt-port-owner.js");
97
+ const candidates = Array.from({ length: 10 }, (_, index) => 10350 + index);
98
+ let watching = true;
99
+ void (async () => {
100
+ while (watching) {
101
+ const { running } = await classifyTiltPorts(candidates, destination, async (port) => (await readStackProgress(port)) !== undefined);
102
+ for (const port of running) {
103
+ const progress = await readStackProgress(port);
104
+ if (progress) {
105
+ spinner.text = formatProgress({ ...progress, elapsed: (Date.now() - started) / 1000 });
106
+ break;
107
+ }
108
+ }
109
+ await new Promise((resolve) => setTimeout(resolve, 2000));
110
+ }
111
+ })();
57
112
  const outcome = await runStackQuiet(binPath, destination);
113
+ watching = false;
114
+ const tiltUiUrl = outcome.data?.tiltUrl ?? "http://localhost:10350";
58
115
  if (!outcome.ok) {
59
116
  spinner.fail("The stack did not come up.");
60
117
  console.log(chalk.yellow(outcome.error ?? "The stack stopped before it was ready."));
@@ -62,6 +119,8 @@ if (templateArg) {
62
119
  process.exit(1);
63
120
  }
64
121
  spinner.succeed("The stack is up.");
122
+ console.log(chalk.gray(` Tilt UI (live build logs): ${tiltUiUrl}`));
123
+ console.log(chalk.gray(` Container logs: docker compose logs -f (in ${destination})`));
65
124
  // The browser only opens when a frontend actually answers, the same way `flisk open` does.
66
125
  const opened = await openFirstFrontend(destination).catch((error) => {
67
126
  console.log(chalk.yellow(`Could not open a browser: ${error instanceof Error ? error.message : String(error)}`));
@@ -99,14 +158,3 @@ function readOptionValue(args, name) {
99
158
  }
100
159
  return undefined;
101
160
  }
102
- /** A bare template name (`flisk restaurant`) is the short form of `--template restaurant`. */
103
- async function bareTemplateArg(args) {
104
- const { PROJECT_TEMPLATES } = await import("./utils/project-templates.js");
105
- for (const arg of args.slice(2)) {
106
- if (arg.startsWith("-"))
107
- continue;
108
- if (arg in PROJECT_TEMPLATES)
109
- return arg;
110
- }
111
- return undefined;
112
- }
@@ -53,6 +53,14 @@ export async function pickReadyFrontend(targets, waitMs = 30000, onWait) {
53
53
  await new Promise((resolve) => setTimeout(resolve, 2000));
54
54
  }
55
55
  }
56
+ /**
57
+ * The first frontend of the project that answers, or undefined when none does. With a wait of
58
+ * zero this is a cheap "is a stack for this project already running?" check.
59
+ */
60
+ export async function findReadyFrontend(projectRoot, waitMs = 0, onWait) {
61
+ const targets = await listFrontendTargets(projectRoot);
62
+ return pickReadyFrontend(targets, waitMs, onWait);
63
+ }
56
64
  /**
57
65
  * Opens the first ready frontend of the project in the browser. Returns the URL it opened, or
58
66
  * null when the project has no frontend.
@@ -1,6 +1,8 @@
1
1
  // Copyright (c) 2026 Flisk Landscape contributors
2
2
  // SPDX-License-Identifier: MIT
3
3
  import { spawn } from "node:child_process";
4
+ import { readdirSync } from "node:fs";
5
+ import { join } from "node:path";
4
6
  /** The marker the stack prints before the frontend URLs. */
5
7
  const FRONTEND_MARKER = "Frontend URLs";
6
8
  /** Picks the first frontend URL out of the `flisk up` output, if it printed one. */
@@ -18,6 +20,39 @@ export function findFrontendUrl(output) {
18
20
  }
19
21
  return undefined;
20
22
  }
23
+ /**
24
+ * Initializes a cloned project.
25
+ *
26
+ * A starter repository holds services and a Tiltfile, but no project configuration, and
27
+ * `flisk up` refuses to run without one. So the one-command flow initializes the folder it
28
+ * just cloned before it starts the stack.
29
+ */
30
+ export function initializeProject(binPath, cwd, env = process.env, spawnFn = spawn) {
31
+ return new Promise((resolve) => {
32
+ const child = spawnFn(process.execPath, [binPath, "project", "--yes"], {
33
+ cwd,
34
+ env,
35
+ stdio: ["inherit", "inherit", "pipe"],
36
+ });
37
+ let errors = "";
38
+ child.stderr?.on("data", (chunk) => {
39
+ errors += String(chunk);
40
+ });
41
+ child.on("error", (error) => resolve({ ok: false, error: error.message }));
42
+ child.on("close", (code) => {
43
+ if (code === 0) {
44
+ resolve({ ok: true });
45
+ return;
46
+ }
47
+ const last = errors
48
+ .split("\n")
49
+ .map((line) => line.trim())
50
+ .filter(Boolean)
51
+ .pop();
52
+ resolve({ ok: false, error: last ?? `the project setup failed (code ${code})` });
53
+ });
54
+ });
55
+ }
21
56
  /** Reads the `{schemaVersion, data, errors}` envelope `flisk up --json` prints when it is done. */
22
57
  export function parseStackEnvelope(output) {
23
58
  for (const line of output.split("\n")) {
@@ -30,6 +65,7 @@ export function parseStackEnvelope(output) {
30
65
  const ok = data.ok === true;
31
66
  return {
32
67
  ok,
68
+ data: (parsed.data ?? {}),
33
69
  error: ok ? undefined : (parsed.errors?.[0]?.message ?? "the stack did not come up"),
34
70
  raw: output,
35
71
  };
@@ -95,3 +131,47 @@ export function nextAvailableProjectDir(base, isTaken) {
95
131
  }
96
132
  return base;
97
133
  }
134
+ /** The service folders of a project that have their own package.json. */
135
+ export function findServiceDirs(projectRoot, maxDepth = 4) {
136
+ const dirs = [];
137
+ const walk = (dir, depth) => {
138
+ if (depth > maxDepth)
139
+ return;
140
+ let entries;
141
+ try {
142
+ entries = readdirSync(dir, { withFileTypes: true }).map((entry) => entry.name);
143
+ }
144
+ catch {
145
+ return;
146
+ }
147
+ if (entries.includes("package.json"))
148
+ dirs.push(dir);
149
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
150
+ if (!entry.isDirectory() || entry.name === "node_modules" || entry.name.startsWith("."))
151
+ continue;
152
+ walk(join(dir, entry.name), depth + 1);
153
+ }
154
+ };
155
+ walk(projectRoot, 0);
156
+ return dirs;
157
+ }
158
+ /**
159
+ * Installs the host dependencies of the services. The stack typechecks the services on the
160
+ * host, so a fresh clone needs its dependencies before it can come up.
161
+ */
162
+ export async function installServiceDependencies(projectRoot, run = defaultRun) {
163
+ const results = [];
164
+ for (const dir of findServiceDirs(projectRoot)) {
165
+ const code = await run("bun", ["install"], dir);
166
+ results.push({ dir, ok: code === 0 });
167
+ }
168
+ return results;
169
+ }
170
+ /** Runs a command and resolves with its exit code. */
171
+ function defaultRun(command, args, cwd) {
172
+ return new Promise((resolve) => {
173
+ const child = spawn(command, args, { cwd, stdio: "ignore" });
174
+ child.on("error", () => resolve(1));
175
+ child.on("close", (code) => resolve(code ?? 1));
176
+ });
177
+ }
@@ -34,3 +34,17 @@ export const PROJECT_TEMPLATES = {
34
34
  description: "AI agent stack: Bun agent + MCP tool server + Postgres + NATS",
35
35
  },
36
36
  };
37
+ /**
38
+ * The template of the one-command flow (`flisk demo`), read from the raw arguments.
39
+ *
40
+ * Only the first argument can name a template. A command that takes a template of its
41
+ * own — `flisk project demo` clones a starter and configures it, `flisk up demo` is not a
42
+ * command at all — is that command, not the one-command flow. Looking further down the
43
+ * arguments hijacked every command that mentioned a template name anywhere.
44
+ */
45
+ export function bareTemplateArg(args) {
46
+ const first = args[2];
47
+ if (!first || first.startsWith("-"))
48
+ return undefined;
49
+ return first in PROJECT_TEMPLATES ? first : undefined;
50
+ }
@@ -0,0 +1,64 @@
1
+ // Copyright (c) 2026 Flisk Landscape contributors
2
+ // SPDX-License-Identifier: MIT
3
+ import { execFile } from "node:child_process";
4
+ /** Reads the resource states from the Tilt UI API. */
5
+ export async function readStackProgress(port, tiltBin = "tilt") {
6
+ const json = await new Promise((resolve) => {
7
+ execFile(tiltBin, ["get", "uiresources", "-o", "json", "--port", String(port)], { timeout: 15_000, maxBuffer: 16 * 1024 * 1024 }, (error, stdout) => resolve(error ? null : stdout));
8
+ });
9
+ if (!json)
10
+ return undefined;
11
+ let items = [];
12
+ try {
13
+ const parsed = JSON.parse(json);
14
+ items = parsed.items ?? [];
15
+ }
16
+ catch {
17
+ return undefined;
18
+ }
19
+ const working = [];
20
+ const broken = [];
21
+ let ready = 0;
22
+ let building = 0;
23
+ let pending = 0;
24
+ let failed = 0;
25
+ for (const item of items) {
26
+ const name = item.metadata?.name ?? "unknown";
27
+ const update = item.status?.updateStatus ?? "";
28
+ const runtime = item.status?.runtimeStatus ?? "";
29
+ if (update === "error" || runtime === "error") {
30
+ failed += 1;
31
+ broken.push({
32
+ name,
33
+ message: (item.status?.buildHistory?.[0]?.error ?? "the resource failed").replace(/\s+/g, " ").slice(0, 160),
34
+ });
35
+ continue;
36
+ }
37
+ if (update === "ok" && (runtime === "ok" || runtime === "not_applicable" || runtime === "")) {
38
+ ready += 1;
39
+ continue;
40
+ }
41
+ if (update === "pending" || update === "") {
42
+ pending += 1;
43
+ working.push({ name, state: runtime || "pending" });
44
+ continue;
45
+ }
46
+ building += 1;
47
+ working.push({ name, state: update });
48
+ }
49
+ return { ready, building, pending, failed, total: items.length, working, broken };
50
+ }
51
+ /** A one-line summary of the progress, for the progress line. */
52
+ export function formatProgress(progress) {
53
+ const minutes = Math.floor(progress.elapsed / 60);
54
+ const seconds = Math.round(progress.elapsed % 60);
55
+ const elapsed = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
56
+ const parts = [`${progress.ready}/${progress.total} resources ready`, elapsed];
57
+ if (progress.building > 0)
58
+ parts.push(`${progress.building} building`);
59
+ if (progress.pending > 0)
60
+ parts.push(`${progress.pending} queued`);
61
+ if (progress.failed > 0)
62
+ parts.push(`${progress.failed} failed`);
63
+ return parts.join(" · ");
64
+ }
@@ -1,5 +1,5 @@
1
1
  import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
2
- import{c as z,e as Q}from"./chunk-42IVF3YT.js";import{Fa as J,Ga as B,Qa as O,Ta as Y,cb as X,d as v,e as k,g as q,ga as L,h as x,i as P,j as F,k as W,l as A,p as d,q as N}from"./chunk-US4D5GJJ.js";import{b as V}from"./chunk-CF46CD6R.js";import{a as $}from"./chunk-6EA4DGCY.js";import{appendFileSync as le,existsSync as C,mkdirSync as fe,readFileSync as de,writeFileSync as Z}from"node:fs";import{dirname as pe,resolve as w}from"node:path";function ft(t,e,n){for(let[r,s]of e.entries())console.log(V.blue(`${s.emoji} ${s.description}...`)),s.type==="json"?me(t,s.filename,s.content):he(t,s.filename,String(s.content)),n&&n(s,r,e.length)}function ue(t,e,n=2){let r=`${JSON.stringify(e,null,n)}
2
+ import{c as z,e as Q}from"./chunk-42IVF3YT.js";import{Fa as J,Ga as B,Qa as O,Ta as Y,cb as X,d as v,e as k,g as q,ga as L,h as x,i as P,j as F,k as W,l as A,p as d,q as N}from"./chunk-LMHKXU2Q.js";import{b as V}from"./chunk-CF46CD6R.js";import{a as $}from"./chunk-6EA4DGCY.js";import{appendFileSync as le,existsSync as C,mkdirSync as fe,readFileSync as de,writeFileSync as Z}from"node:fs";import{dirname as pe,resolve as w}from"node:path";function ft(t,e,n){for(let[r,s]of e.entries())console.log(V.blue(`${s.emoji} ${s.description}...`)),s.type==="json"?me(t,s.filename,s.content):he(t,s.filename,String(s.content)),n&&n(s,r,e.length)}function ue(t,e,n=2){let r=`${JSON.stringify(e,null,n)}
3
3
  `;Z(t,r,"utf-8")}function me(t,e,n,r=2){let s=w(t,e);ue(s,n,r)}function U(t,e){Z(t,e,"utf-8")}function he(t,e,n){let r=w(t,e);U(r,n)}function dt(t){C(t)||fe(t,{recursive:!0})}var ge=[".env",".tdk/.tdk-out/",".tdk/.project-id",".tdk/smoke/","node_modules/"];function Se(t){let e=w(t,".gitignore"),n=C(e)?de(e,"utf-8"):"",r=new Set(n.split(`
4
4
  `).map(f=>f.trim().replace(/\/$/,""))),s=ge.filter(f=>!r.has(f.replace(/\/$/,"")));if(s.length===0)return[];let c=n&&!n.endsWith(`
5
5
  `)?`
@@ -0,0 +1,5 @@
1
+ import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
2
+ import{k as _}from"./chunk-LMHKXU2Q.js";import{execFileSync as v}from"node:child_process";import{existsSync as N,mkdirSync as A,readFileSync as y,realpathSync as F,writeFileSync as K}from"node:fs";import{join as g,resolve as L,sep as C}from"node:path";import{createServer as k}from"node:net";import{connect as R}from"node:net";function O(t,s=500){return new Promise(e=>{let r=R({port:t,host:"127.0.0.1"}),i=c=>{r.destroy(),e(c)};r.setTimeout(s,()=>i(!1)),r.once("connect",()=>i(!0)),r.once("error",()=>i(!1))})}var $={ingressHttp:{start:8080,end:8180},ingressHttps:{start:8443,end:8543},postgres:{start:15432,end:15532}},P={ingressHttp:"TDK_HTTP_PORT",ingressHttps:"TDK_HTTPS_PORT",postgres:"TDK_POSTGRES_PORT"},T={ingressHttp:80,ingressHttps:443,postgres:5432},m={ingressHttp:T.ingressHttp,ingressHttps:T.ingressHttps};function b(t,s){return t==="EACCES"&&s==="darwin"}async function S(t){return await O(t)?!1:new Promise(s=>{let e=k();e.once("error",r=>{s(b(r.code,process.platform))}),e.listen(t,"127.0.0.1",()=>e.close(()=>s(!0)))})}function D(t,s){let e=Number(s);if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} must be an integer between 1 and 65535 (received ${JSON.stringify(s)})`);return e}async function H(t={}){let s=t.env??process.env,e=t.isAvailable??S,r={ingressHttp:!1,ingressHttps:!1,postgres:!1},i={ingressHttp:"",ingressHttps:"",postgres:""},c={explicit:r},a=new Set;for(let n of["ingressHttp","ingressHttps","postgres"]){let f=s[P[n]],o=f!==void 0&&f.trim()!=="",p=t.ranges?.[n]??$[n],u=m[n],x=o?[D(P[n],f)]:[...u===void 0?[]:[u],...Array.from({length:p.end-p.start+1},(d,E)=>p.start+E)],l;for(let d of x)if(!a.has(d)){if(await e(d)){l=d;break}if(o)throw new Error(`${P[n]}=${d} is already in use. Choose a free host port and retry.`)}if(l===void 0)throw new Error(`No available host port in ${p.start}-${p.end} for ${n}. Set ${P[n]} to a free port and retry.`);a.add(l),c[n]=l,r[n]=o,i[n]=o?"explicit override":l===u?"requested port is free":`selected from fallback range ${p.start}-${p.end}`}return{...c,requested:{...T},reason:i}}function U(t){return`Host ports: HTTP ${t.ingressHttp}, HTTPS ${t.ingressHttps}, Postgres ${t.postgres}`}var h=g(".tdk",".tdk-out","host-ports.json");function j(t,s,e){return t.split(`
3
+ `).some(r=>{let[i="",c=""]=r.split("|"),a=i.toLowerCase(),n=s.toLowerCase();return(a.startsWith(`${n}_`)||a.startsWith(`${n}-`))&&c.includes(`:${e}->`)})}function w(t){let s=g(t,h);if(!N(s))return null;try{let e=JSON.parse(y(s,"utf-8"));if(!e||typeof e!="object")return null;let r=e;return!Number.isInteger(r.ingressHttp)||!Number.isInteger(r.ingressHttps)||!Number.isInteger(r.postgres)||!r.requested||!r.explicit||!r.reason?null:r}catch{return null}}function Z(t,s){let e=g(t,h);A(g(t,".tdk",".tdk-out"),{recursive:!0}),K(e,`${JSON.stringify(s,null,2)}
4
+ `,{encoding:"utf-8",mode:384})}function tt(t){process.env.TDK_HTTP_PORT=String(t.ingressHttp),process.env.TDK_HTTPS_PORT=String(t.ingressHttps),process.env.TDK_POSTGRES_PORT=String(t.postgres)}var I="com.docker.compose.project.working_dir";function G(t,s,e){let r=new Set([L(s)]);try{r.add(F(s))}catch{}return t.split(`
5
+ `).some(i=>{let[,c="",a=""]=i.split("|");return!a||!c.includes(`:${e}->`)?!1:[...r].some(n=>a===n||a.startsWith(`${n}${C}`))})}function et(t,s,e=()=>v("docker",["ps","--format",`{{.Names}}|{{.Ports}}|{{.Label "${I}"}}`],{encoding:"utf-8",timeout:5e3})){let r=w(t);if(!r||r.ingressHttp===s)return s;try{if(G(e(),t,r.ingressHttp))return r.ingressHttp}catch{}return s}async function rt(t,s={}){let e=w(t),r=["TDK_HTTP_PORT","TDK_HTTPS_PORT","TDK_POSTGRES_PORT"].some(o=>process.env[o]);if(s.inspectDocker===!1&&e&&!r)return e;let i="";try{let o=JSON.parse(y(g(t,".tdk",_),"utf-8"));i=String(o?.project?.name??"tdk-project").replace(/-/g,"_").toLowerCase()}catch{i="tdk_project"}let c="";if(s.inspectDocker!==!1)try{c=v("docker",["ps","--format","{{.Names}}|{{.Ports}}"],{encoding:"utf-8",timeout:5e3})}catch{}let a=o=>j(c,i,o),n=s.isPortFree??S,f=async o=>await n(o)||a(o);if(e&&!r){for(let o of[e.ingressHttp,e.ingressHttps,e.postgres])if(!await f(o))return H({isAvailable:f});for(let[o,p]of Object.entries(m)){let u=e[o];if(p!==void 0&&u!==p&&await f(p))return H({isAvailable:f})}return e}return H({isAvailable:f})}export{H as a,U as b,w as c,Z as d,tt as e,et as f,rt as g};
@@ -0,0 +1,2 @@
1
+ import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
2
+ import{Ta as _,cb as A,d as u,k as S}from"./chunk-LMHKXU2Q.js";import{existsSync as w,readFileSync as h}from"node:fs";import{join as i}from"node:path";var O=["postgres",u],y=["pre_alpha","alpha","beta","out_of_scope"];function D(e){return O.includes(e)}function b(e){try{return JSON.parse(h(i(e,".tdk",S),"utf-8"))}catch{return}}function d(e){try{return w(e)?h(e,"utf-8"):void 0}catch{return}}function k(e,s){let n=r=>{let t=r;for(;t<e.length&&/\s/.test(e[t]);)t+=1;return t},a=0;for(;;){let r=e.indexOf(s,a);if(r===-1)return;let t=n(r+s.length);if(e[t]==="="&&(t=n(t+1),e[t]==="[")){let o=e.indexOf("]",t+1);return o===-1?void 0:[...e.slice(t+1,o).matchAll(/"([^"]+)"|'([^']+)'/g)].map(f=>f[1]??f[2]??"")}a=r+s.length}}function N(e){let s=i(e,".tdk",".tdk-out","Tiltfile"),n=d(s);if(n)return k(n,"ALWAYS_ENABLED_INFRA")}function M(e){let s=i(e,".tdk",".tdk-out","spec.master"),n=d(s);if(n!==void 0)return/"database-management"\s*:\s*True/.test(n)}function x(e){let s=i(e,".tdk",".tdk-out"),n=d(i(s,"Tiltfile")),a=d(i(s,"spec.master")),r=d(i(s,"tdk-cli-ext","discovery","config.star")),t=d(i(s,"tdk-cli-ext","engine","topologies","tilt","config","profiles.star"));if(!n||!a||!r||!t)return!1;let o=a.match(/PRE_ALPHA_RESOURCES\s*=\s*\{([^}]*)\}/)?.[1],f=k(r,"CORE_INFRA");return/FOCUS_MODE\s*,\s*FOCUS_ENABLED_ALL\s*,\s*FOCUS_ENABLED_RESOURCES\s*=\s*Config\.apply_focus\(cfg\)/.test(n)&&/if FOCUS_MODE and FOCUS_ENABLED_ALL\s*:/.test(n)&&/"[^"]+"\s*:\s*True|'[^']+'\s*:\s*True/.test(o??"")&&(f?.includes("postgres")??!1)&&/["']postgres["']\s*:\s*["']database-management["']/.test(r)&&/for infra in CORE_INFRA_EXPORT\s*:/.test(t)&&/needed\[INFRA_STACK_MAP_EXPORT\[infra\]\]\s*=\s*True/.test(t)}function C(e){let s=b(e);if(!s)return!1;let n=s.phases??{};for(let r of y){let t=n[r]?.enabledStacks;if(Array.isArray(t)&&t.includes(u))return!0}let a=s.always_enabled_infra??A;return Array.isArray(a)&&a.includes(u)}function L(e,s){let n=new Set;for(let t of O)n.add(t);for(let t of s)n.add(t.name),t.stack&&n.add(t.stack);let a=b(e);if(a){let t=a.phases??{};for(let f of y){let l=t[f]?.enabledStacks;if(Array.isArray(l))for(let c of l)n.add(c)}let o=a.always_enabled_infra??A;if(Array.isArray(o))for(let f of o)n.add(f)}let r=N(e);if(Array.isArray(r))for(let t of r)n.add(t);return n}function $(e,s=_(e)){let n=C(e),a=N(e),r=Array.isArray(a)&&a.includes(u),t=M(e)===!0,o=n||r||t,f=!n&&x(e),l=L(e,s),c=[],E=[];for(let p of s){let P=p.config?.dependsOn??[],g=!1;for(let m of P){if(D(m)){g=!0;continue}l.has(m)||E.push({resource:p.name,name:m})}g&&c.push(p.name)}let T=o||c.length>0,F=o?"feature":c.length>0?"dependsOn":"none";return{featureOn:o,featureOnFromProjectJson:n,featureOnFromTiltfile:r,featureOnFromSpecMaster:t,focusWouldEnableDatabaseManagement:f,dependsOnUsers:c,willStart:T,reason:F,unknownDependsOnNames:E}}export{O as a,$ as b};
@@ -0,0 +1,71 @@
1
+ import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
2
+ import{a as xt,f as wt,g as te}from"./chunk-EICMGZXL.js";import{i as vt,o as Tt}from"./chunk-DS47LHYF.js";import{c as bt}from"./chunk-42IVF3YT.js";import{f as R,g as $t,h as Dt,i as Et,j as se,k as Nt,l as Ct,m as Rt,n as _t,o as Ot,p as re,q as jt,s as At,t as It}from"./chunk-RMZQU74X.js";import{Aa as St,Ba as yt,Ca as Pe,G as ut,O as y,Ta as x,Ua as Pt,b as v,fa as V,h as O,ja as pt,k as j,l as F,m as ge,ma as ft,na as Se,o as ke,oa as Q,pa as mt,q as ct,qa as G,s as h,t as lt,ua as ye,va as ht,wa as ee,x as D,xa as gt,y as dt,ya as kt}from"./chunk-LMHKXU2Q.js";import{b as g}from"./chunk-CF46CD6R.js";import{execFileSync as Us}from"node:child_process";import{readFileSync as ao}from"node:fs";import{join as co}from"node:path";import{execFileSync as kn,execSync as $s}from"node:child_process";import{existsSync as T,readdirSync as Sn,readFileSync as B}from"node:fs";import{join as w,normalize as Ye,relative as Ze}from"node:path";import{Command as yn}from"commander";import{existsSync as qs}from"node:fs";var ne="docs/agent-hosts.md",xe=`WebContainers cannot run Flisk: there is no Docker daemon or Tilt. Run \`flisk up\` on a machine with Docker. See ${ne}.`,Ft=`Docker is not reachable from this Dev Container. Mount the host Docker socket (-v /var/run/docker.sock:/var/run/docker.sock) or add the docker-outside-of-docker feature to devcontainer.json, then retry. Docker-in-docker is not supported. See ${ne}.`;function zs(){return{env:process.env,versions:process.versions,platform:process.platform,fileExists:qs}}function Ht(e=zs()){let{env:t,versions:s,platform:r,fileExists:n}=e;return s.webcontainer?{kind:"webcontainer",canUp:!1}:t.CODESPACES==="true"?{kind:"codespaces",canUp:!0}:t.REMOTE_CONTAINERS||n("/.dockerenv")&&t.DEVCONTAINER?{kind:"devcontainer",canUp:!0}:r==="win32"?{kind:"native-windows",canUp:t.TDK_ALLOW_NATIVE_WINDOWS==="1"}:r==="linux"&&t.WSL_DISTRO_NAME?{kind:"wsl2",canUp:!0}:{kind:"local",canUp:!0}}function Mt(e){return e==="devcontainer"||e==="codespaces"}function oe(e){return e.some(t=>V(t.config?.appType)||t.config?.appType==="frontend"||t.config?.appType==="worker")}import{existsSync as Xs,readFileSync as Ys}from"node:fs";import{join as Zs,relative as Lt,resolve as Bt,sep as Qs}from"node:path";var er=2,Ut=["services/*/*"];function Wt(e){return e.split(Qs).filter(Boolean)}function tr(e,t){let s=0,r=0,n=-1,o=0;for(;r<t.length;)if(s<e.length&&e[s]!=="*"&&(e[s]==="?"||e[s]===t[r]))s++,r++;else if(s<e.length&&e[s]==="*")n=s++,o=r;else if(n!==-1)s=n+1,r=++o;else return!1;for(;s<e.length&&e[s]==="*";)s++;return s===e.length}function sr(e,t){return e.length===t.length&&e.every((s,r)=>tr(s,t[r]))}function we(e){let t=Zs(e,".tdk",j);if(!Xs(t))return Ut;try{let s=JSON.parse(Ys(t,"utf-8"))?.discovery?.paths;if(Array.isArray(s)&&s.every(r=>typeof r=="string"))return s}catch{}return Ut}function ie(e,t,s){let r=Wt(Lt(e,Bt(e,t)));return s.some(n=>{let o=Wt(Lt(e,Bt(e,n)));for(let i=0;i<=er;i++){let a=r.length-i;if(a>0&&sr(o,r.slice(0,a)))return!0}return!1})}function bo(e,t,s,r){let n=we(e);if(ie(e,t,n))return{path:t};let o=`services/${s}/${r}`;return ie(e,o,n)?{path:o,adjustedFrom:t}:{path:t}}import{execFile as yr}from"node:child_process";import{promisify as Pr}from"node:util";import{execFileSync as Xt,execSync as q}from"node:child_process";import{existsSync as be,readdirSync as rr,readFileSync as ve}from"node:fs";import{join as L}from"node:path";function Vt(e,t){return e==="pending"||e==="in_progress"||e==="none"||t==="pending"||t==="none"}var Gt="container is running, but its health check is failing";function Kt(e){let t=e?.runtimeStatus??"";if(t!=="ok")return t;let s=e?.composeResourceInfo?.healthStatus;return s==="unhealthy"?"error":s==="starting"?"pending":t}var K=1e4,Jt=3e3,nr="http://localhost:4873";function Yt(e){return`'${e.replace(/'/g,"'\\''")}'`}function J(e,t,s,r,n){return process.platform==="win32"?Xt(D(t)??t,s,{...n,windowsHide:!0}):e(r,n)}var or=[80,443];function Te(e){return e.replace(/-/g,"_")}function ir(e,t){let s=new Set(t),r=[];for(let n of e.split(`
3
+ `)){let o=n.trim();if(!o)continue;let i=o.indexOf(" "),a=i>=0?o.slice(0,i):o,l=i>=0?o.slice(i+1):"",c=[];for(let d of s)l.split(/[\s,]+/).some(m=>m.startsWith(`${d}->`)||m.includes(`:${d}->`))&&c.push(d);c.length>0&&r.push({name:a,ports:l,publishedPorts:c})}return r}function Zt(e,t){let s=e.toLowerCase(),r=t.toLowerCase();return s===`${r}_traefik`||s.startsWith(`${r}_traefik`)}function ar(e,t){return e.filter(s=>!Zt(s.name,t))}function $e(e){try{let t=e??Pt();return new Set(t.filter(s=>s.config?.sablier?.enable&&s.config?.sablier?.deferStart).map(s=>s.name))}catch{return new Set}}function De(e,t=new Set){let r=JSON.parse(e).items??[],n=[],o=0,i=0,a=0;for(let l of r){let c=l.metadata?.name??"unknown",d=l.status?.updateStatus??"",u=Kt(l.status),m=u==="error"&&l.status?.runtimeStatus==="ok",S=(l.status?.buildHistory?.[0]?.error??"").trim()||(m?Gt:"");if(d==="error"||u==="error"){n.push({name:c,updateStatus:d,runtimeStatus:u,error:S.replace(/\s+/g," ").slice(0,800)});continue}if(d==="ok"&&(u==="ok"||u==="not_applicable")){a+=1;continue}c!=="(Tiltfile)"&&Vt(d,u)&&(t.has(c)?i+=1:o+=1)}return{failures:n,pendingCount:o,deferredCount:i,okCount:a,total:r.length}}var cr=/failed to resolve source metadata for docker\.io\/library\/([A-Za-z0-9._-]+-l[1-4](?:-[a-z]+)*)(?::[^\s:]+)?:/i;function Ee(e){let t=e.replace(/\s+/g," ").trim(),s=t.match(/Bind for [^ ]+:(\d+) failed: port is already allocated/i);if(s)return`host port ${s[1]} is already allocated`;if(/port is already allocated/i.test(t))return"a required host port is already allocated";let r=[...t.matchAll(/ConnectionRefused downloading package manifest (@[A-Za-z0-9._/-]+)/gi)].map(a=>a[1]);if(r.length>0){let a=[...new Set(r)],l=a.slice(0,4).join(", "),c=a.length>4?` (+${a.length-4} more)`:"";return`private registry ConnectionRefused for ${l}${c} \u2014 Verdaccio (:4873) is down or unreachable from the build`}let n=t.match(cr);if(n)return`base image ${n[1]} was missing when this build started (the golden layers weren't built yet, or golden-layers-build failed) - Tilt does not retry a failed build by itself`;if(/install-deps\.sh/i.test(t)||/bun install(?: --production)?/i.test(t)||/npm (?:ci|install)/i.test(t))return"dependency install failed during ImageBuild \u2014 usually Verdaccio (:4873) unreachable or missing @scoped packages (ConnectionRefused downloading package manifest)";let o=t.match(/No such image:\s*([^\s]+)/i)?.[1]??t.match(/pull access denied for ([^,:\s]+)/i)?.[1]??void 0,i=t.match(/\bup\s+-d\s+--no-build\s+([A-Za-z0-9._-]+)/i)?.[1];if(o||i||/docker compose .*\bup\s+-d\s+--no-build\b/i.test(t)){let a=o??`${i??"service"}:dev`,l=i?` for ${i}`:"";return`compose run failed because image is missing (${a})${l} \u2014 wait for the ImageBuild resource to finish, then re-trigger the *-run-only resource`}return/bun run build/i.test(t)?"ImageBuild failed during `bun run build` (compile/typecheck) \u2014 open the resource logs in Tilt for the TypeScript/build error":(/ImageBuild:/i.test(t)||/^Command "/i.test(t),t.slice(0,220))}function qt(e){return/ConnectionRefused downloading package manifest|Verdaccio|private registry|dependency install failed during ImageBuild|install-deps\.sh|bun install/i.test(e)}function Ne(e=h()??process.cwd()){let t=L(e,".tdk",j);if(!be(t))return!1;try{let s=JSON.parse(ve(t,"utf-8"));return s.optional_infra?.verdaccio===!0||Object.values(s.phases??{}).some(r=>(r.enabledStacks??[]).includes("verdaccio"))}catch{return!1}}function Qt(e){for(let t of[L(e,".npmrc"),L(e,F)])if(be(t))try{let s=ve(t,"utf-8");if(/:4873\b/.test(s)||/verdaccio/i.test(s))return!0}catch{}return!1}function lr(e=h()??process.cwd()){return Ne(e)||Qt(e)}function Ce(e=q,t=h()??process.cwd(),s=process.env.VERDACCIO_URL??process.env.NPM_REGISTRY_URL??nr,r=!0){if(!r&&!Qt(t))return{name:"Private npm registry",didPass:!0,isSkipped:!0,message:"Verdaccio is enabled in the project config but needs a Premium license, so Flisk will not start it - skipped"};if(!lr(t))return{name:"Private npm registry",didPass:!0,isSkipped:!0,message:"Private npm registry (Verdaccio) not enabled for this project - skipped"};let n=!1;try{let a=J(e,"docker",["ps","--format","{{.Names}}"],"docker ps --format '{{.Names}}'",{stdio:"pipe",encoding:"utf-8",timeout:K});n=/verdaccio/i.test(a)}catch{}let o=!1,i="";try{let a=process.platform==="win32"?Xt("curl.exe",["-fsS","-o","NUL","-w","%{http_code}","--max-time","2",s],{stdio:"pipe",encoding:"utf-8",timeout:Jt+1e3,windowsHide:!0}).trim():e(`curl -fsS -o /dev/null -w '%{http_code}' --max-time 2 ${Yt(s)}`,{stdio:"pipe",encoding:"utf-8",timeout:Jt+1e3}).trim();o=/^[23]\d\d$/.test(a),i=`HTTP ${a}`}catch(a){i=a instanceof Error?a.message.slice(0,120):"request failed"}return o?{name:"Private npm registry",didPass:!0,message:`Private npm registry reachable at ${s}${n?" (verdaccio container running)":""}`}:n?{name:"Private npm registry",didPass:!1,message:`Verdaccio container is running but ${s} is not responding (${i}). ImageBuilds that install @scoped packages will fail with ConnectionRefused downloading package manifest.`,fix:"Check `docker logs` for the verdaccio container, confirm port 4873 is published, then re-run `flisk doctor`."}:{name:"Private npm registry",didPass:!1,message:`Private npm registry (Verdaccio) is not reachable at ${s}. Docker ImageBuilds will fail with ConnectionRefused downloading package manifests for @flisk-sh/* and project-scoped packages.`,fix:"Start the registry with `bun run verdaccio:start` (or `docker compose -f docker-compose.verdaccio.yml up -d`), publish packages with `bun run publish:all` / `scripts/build/publish-to-verdaccio.sh`, then `tilt trigger` failed resources or re-run `flisk up`."}}function Re(e=q,t=G(),s=or){let r=Te(t),n="";try{n=J(e,"docker",["ps","--format","{{.Names}}\\t{{.Ports}}"],"docker ps --format '{{.Names}}\\t{{.Ports}}'",{stdio:"pipe",encoding:"utf-8",timeout:K})}catch{return{name:"Ingress Ports",didPass:!0,isSkipped:!0,message:"Docker not available - skipped ingress port check"}}let o=ir(n,s),i=ar(o,r);if(i.length===0){let l=o.filter(c=>Zt(c.name,r));return l.length>0?{name:"Ingress Ports",didPass:!0,message:`Ingress ports ${s.join("/")} held by this project's Traefik (${l.map(c=>c.name).join(", ")})`}:{name:"Ingress Ports",didPass:!0,message:`Ingress ports ${s.join("/")} are free for Traefik`}}return{name:"Ingress Ports",didPass:!1,message:`Traefik cannot bind ingress ports because another container already owns them:
4
+ ${i.map(l=>{let c=l.publishedPorts.join(", ");return`${l.name} (host ports ${c})`}).join(`
5
+ `)}`,fix:`Stop the foreign container(s), e.g. \`docker stop ${i[0]?.name}\`, or shut down the other Flisk/Tilt project using that Traefik. Then re-run \`flisk up\`.`}}function dr(e){let{status:t}=e;return t===void 0?e.error??"no response":t===404?"HTTP 404: Traefik is answering but has no route for this URL (container not started yet, or not running)":t===502||t===503||t===504?`HTTP ${t}: Traefik found the route but the container isn't answering (still starting, or unhealthy)`:`HTTP ${t}`}function _e(e){let t=e.filter(o=>o.status!==void 0),s=e.filter(o=>!o.ok);if(t.length===0)return{name:"Service Health",didPass:!0,isSkipped:!0,message:`No services responded - skipped ping of ${y(e.length,"service")} (stack may be down, or Traefik never bound :80)`,fix:"If `flisk up` is already running, check Traefik/port 80 in the Tilt UI. Otherwise start with: flisk up"};if(s.length===0)return{name:"Service Health",didPass:!0,message:`All ${y(e.length,"service")} responding on /health`};let r=s.map(o=>`${o.name} (${dr(o)})
6
+ ${o.url}`).join(`
7
+ `),n=s.some(o=>o.status===404);return{name:"Service Health",didPass:!1,message:`${y(s.length,"service")} not healthy (${e.length-s.length}/${e.length} healthy):
8
+ ${r}`,fix:n?"If `flisk up` just started, images may still be building: re-run `flisk doctor` in a minute. Otherwise check `docker ps` and the resource in the Tilt UI, and compare `flisk networks` with the URL above.":"Check container state and routing: docker ps, then flisk networks to compare the advertised URLs against Traefik's routers"}}function Oe(e){if(e.length===0)return{name:"Service Smoke",didPass:!0,isSkipped:!0,message:"No read-only smoke checks declared"};let t=e.filter(r=>!r.ok);if(t.length===0)return{name:"Service Smoke",didPass:!0,message:`All ${y(e.length,"service")} pass their smoke checks`};let s=t.map(r=>r.failure??`${r.name}: failed`).join(`
9
+ `);return{name:"Service Smoke",didPass:!1,message:`${y(t.length,"service")} failing a smoke check (${e.length-t.length}/${e.length} passing):
10
+ ${s}`,fix:'Add the route the smoke step expects, or correct its "path" in the service.json "smoke" block. Then re-run flisk doctor.'}}function es(e){let t=e.filter(n=>n.status!==void 0);if(t.length===0)return{name:"Advertised Endpoints",didPass:!0,isSkipped:!0,message:"No services responded - skipped advertised endpoint check"};let s=t.filter(n=>n.status===404);if(s.length===0)return{name:"Advertised Endpoints",didPass:!0,message:`${y(t.length,"advertised endpoint")} reachable`};let r=s.map(n=>`${n.name} (404)
11
+ ${n.url}`).join(`
12
+ `);return{name:"Advertised Endpoints",didPass:!0,isWarning:!0,message:`${y(s.length,"advertised link")} ${s.length===1?"returns":"return"} 404 (the service may be healthy, but nothing answers at this exact path):
13
+ ${r}`,fix:'Nothing handles this exact path, so opening the link shows a 404 even when /health is fine. If this API has no root route, you can leave it. Otherwise add a GET route for it (for example one that returns the service name), or declare a read-only "smoke" step on a real route in service.json. The URL flisk networks and the Flisk App print stays the same until the CLI changes how it reports it.'}}function je(e=q){let t="";try{t=J(e,"tilt",["get","uiresources","-o","json"],"tilt get uiresources -o json",{stdio:"pipe",encoding:"utf-8",timeout:K})}catch{return{name:"Tilt Resources",didPass:!0,isSkipped:!0,message:"Tilt is not running - skipped resource status check",fix:"Start them with: flisk up"}}let s;try{s=De(t,$e())}catch{return{name:"Tilt Resources",didPass:!0,isSkipped:!0,message:"Could not parse Tilt resource status - skipped"}}if(s.total===0)return{name:"Tilt Resources",didPass:!0,isSkipped:!0,message:"No Tilt UIResources found"};if(s.failures.length===0){let f=s.deferredCount>0?`, ${s.deferredCount} deferred`:"";return{name:"Tilt Resources",didPass:!0,message:`Tilt resources healthy (${s.okCount} ok, ${s.pendingCount} pending${f})`}}let r=pr(s.failures),o=r.slice(0,20),i=r.length-o.length,a=o.map(f=>({failure:f,why:mr(f,e)})),l=a.map(({failure:f,why:I})=>`${f.name}: ${I}`).join(`
14
+ `),c=i>0?`
15
+ \u2026and ${y(i,"more failed resource")}`:"",d=s.pendingCount>0?`
16
+ ${y(s.pendingCount,"resource")} still pending/not started \u2014 often blocked by the failures above.`:"",u=s.deferredCount>0?`
17
+ ${y(s.deferredCount,"resource")} intentionally deferred (sablier.deferStart) \u2014 not started until first request, unrelated to the failures above.`:"",m=r.some(f=>/port is already allocated/i.test(f.error)),P=r.some(f=>qt(f.error)||qt(Ee(f.error))),S=a.some(({why:f})=>/Kafka broker unreachable|kafka:9092/i.test(f)),k="Open the Tilt UI URL printed by flisk up (default port "+ct.tiltUi+" when TILT_PORT is unset), inspect the red resources, fix the listed errors, then `tilt trigger <resource>` or re-run `flisk up`.";return m?k="Free the conflicting host port (usually 80/443): `docker ps --filter publish=80` then `docker stop <other-traefik>`. Re-trigger Traefik in the Tilt UI or run `flisk up` again.":P?k="Start Verdaccio (`bun run verdaccio:start`), ensure packages are published (`bun run publish:all` or `bash scripts/build/publish-to-verdaccio.sh`), confirm `curl http://localhost:4873` works, then `tilt trigger` the failed resources.":S&&(k="Kafka is not running (optional infra). Start it, point the bridge at a real broker, or disable Kafka-dependent resources until Kafka is enabled."),{name:"Tilt Resources",didPass:!1,message:`Tilt reports ${y(s.failures.length,"failed resource")}:
18
+ ${l}${c}${d}${u}`,fix:k}}var ur=/^(traefik|postgres|nats|infisical|verdaccio|proxy)([.-]|$)/i;function zt(e){return ur.test(e)}function pr(e){return[...e].sort((t,s)=>{let r=zt(t.name)?0:1,n=zt(s.name)?0:1;return r!==n?r-n:t.name.localeCompare(s.name)})}function fr(e,t=q,s=G()){try{let r=J(t,"docker",["ps","-a","--format","{{.Names}}"],"docker ps -a --format '{{.Names}}'",{stdio:"pipe",encoding:"utf-8",timeout:K}).split(`
19
+ `).map(c=>c.trim()).filter(Boolean),n=`-autogenerated-${e}-1`,o=r.find(c=>c===e)??r.find(c=>c.startsWith(`${s}-`)&&c.endsWith(n))??r.find(c=>c.endsWith(n))??r.find(c=>c.includes(e));if(!o)return null;let i=J(t,"docker",["logs","--tail","120",o],`docker logs --tail 120 ${Yt(o)} 2>&1`,{stdio:"pipe",encoding:"utf-8",timeout:K});if(/getaddrinfo ENOTFOUND/i.test(i)&&/kafka:9092/i.test(i))return"runtime crash: Kafka broker unreachable (getaddrinfo ENOTFOUND kafka:9092)";if(/ECONNREFUSED/i.test(i)&&/kafka/i.test(i))return"runtime crash: Kafka connection refused";let a=i.match(/ConnectionRefused downloading package manifest (@[A-Za-z0-9._/-]+)/i);if(a?.[1])return`runtime/build log: private registry ConnectionRefused for ${a[1]}`;if(/ZodError/i.test(i)){let c=[...i.matchAll(/"path"\s*:\s*\[\s*"([^"]+)"\s*\]/g)].map(u=>u[1]),d=[...new Set(c)].slice(0,6);return d.length>0?`runtime crash: ZodError missing required config/env: ${d.join(", ")}`:"runtime crash: ZodError during config/env validation"}let l=i.match(/error:\s+Failed to [^:\n]+[^\n]{0,120}/i)?.[0]??i.match(/Failed to (?:start|initialize) [^\n]{0,120}/i)?.[0]??i.match(/^\s*error:\s+[^\n]{0,160}/im)?.[0];if(l)return`runtime crash: ${l.replace(/\s+/g," ").trim().slice(0,180)}`}catch{return null}return null}function mr(e,t=q){return e.error?Ee(e.error):e.runtimeStatus==="error"?fr(e.name,t)??"runtime error (container crashed or unhealthy \u2014 check Tilt/docker logs)":e.updateStatus||e.runtimeStatus||"unknown error"}var hr=/\.(ts|tsx|mts|js|mjs|cjs)$/,gr=new Set(["node_modules","dist",O,"tests","__tests__"]);function ts(e){let t=[];for(let s of rr(e,{withFileTypes:!0}))s.isDirectory()?gr.has(s.name)||t.push(...ts(L(e,s.name))):hr.test(s.name)&&!/\.(test|spec)\./.test(s.name)&&t.push(L(e,s.name));return t}function kr(e){let t=e?.healthCheckPath||"/health";return{container:t,traefik:e?.traefik?.healthCheck||t}}function Sr(e){let t=[];for(let s of e){if(!V(s.config?.appType))continue;let r=L(s.path,"src");if(!be(r))continue;let n=ts(r);if(n.length===0)continue;let o=n.map(c=>ve(c,"utf-8")),{container:i,traefik:a}=kr(s.config),l=i===a?[{path:i,usedBy:"container and Traefik healthchecks"}]:[{path:i,usedBy:"container healthcheck"},{path:a,usedBy:"Traefik healthcheck"}];for(let{path:c,usedBy:d}of l){let u=["'",'"',"`"].map(m=>`${m}${c}${m}`);o.some(m=>u.some(P=>m.includes(P)))||t.push({name:s.name,healthPath:c,usedBy:d})}}return t}function ss(e=h()??process.cwd()){let t=[];try{t=x(e)}catch{return{name:"Health Routes",didPass:!0,isSkipped:!0,message:"Could not read services - skipped health route check"}}let s=t.filter(o=>V(o.config?.appType));if(s.length===0)return{name:"Health Routes",didPass:!0,isSkipped:!0,message:"No backend services to check"};let r=Sr(s);return r.length===0?{name:"Health Routes",didPass:!0,message:`All ${y(s.length,"backend")} define their health route`}:{name:"Health Routes",didPass:!1,message:`Backends without a health route (the container never turns healthy, so Traefik returns 404):
20
+ ${r.map(o=>`${o.name}: no "${o.healthPath}" route in src/ (probed by the ${o.usedBy})`).join(`
21
+ `)}`,fix:`Add the route, e.g. for Hono: app.get("${r[0]?.healthPath}", (c) => c.json({ status: "ok" })). To use a different path, set "healthCheckPath" (container healthcheck) or "traefik.healthCheck" (Traefik) in its service.json.`}}var xr=Pr(yr),wr=1e4,br=5,rs=10*6e4,vr=3,Tr=200,$r=async e=>{let{stdout:t,stderr:s}=await xr("docker",e,{encoding:"utf-8",timeout:wr,windowsHide:!0});return`${t}${s}`};function Dr(e,t){let s=Te(t).toLowerCase(),r=e.replace(/-/g,"_").toLowerCase();return r===s||r.startsWith(`${s}_`)}function Er(e,t=Date.now()){if(e.status==="restarting")return!0;if(e.restartCount<br)return!1;if(e.status==="running"){let r=Date.parse(e.startedAt);return Number.isNaN(r)||t-r<rs}let s=Date.parse(e.finishedAt??"");return e.exitCode!==0&&!Number.isNaN(s)&&t-s<rs}function Nr(e,t){let s=/database "([^"]+)" does not exist/i.exec(e);return s?`database "${s[1]}" does not exist: re-run the project's database provisioning (flisk up) or create it`:/Cannot find module|MODULE_NOT_FOUND|module not found/i.test(e)?"a module is missing from the image: rebuild it (flisk up --force) after checking the resource's dependencies":/EADDRINUSE|address already in use/i.test(e)?"the port inside the container is already in use: check for two processes started by the same command":/ECONNREFUSED|connection refused|could not connect/i.test(e)?"a dependency is not reachable: check that the service it connects to is running and healthy":/\b(?:environment variable|env var)\b.*\b(?:missing|not set|required|undefined)\b/i.test(e)||/\b[A-Z][A-Z0-9_]{2,}\b (?:is )?(?:not set|required|missing)\b/.test(e)?"a required environment variable is missing: check the project's .env":t.exitCode===137?"killed (exit 137), usually out of memory: raise the container's memory limit":null}function Cr(e){let t=[];for(let s of e.split(/\r?\n/)){let[r,n,o,i,a,l]=s.split(" ");!r||n===void 0||t.push({name:r.replace(/^\//,""),restartCount:Number(n)||0,status:o??"",startedAt:i??"",exitCode:Number(a)||0,finishedAt:l??""})}return t}async function ns(e,t=[],s=$r,r=Date.now()){let n="Crash-looping containers",o;try{o=(await s(["ps","-a","--format",'{{.ID}} {{.Label "com.docker.compose.project"}}'])).split(/\r?\n/).map(u=>u.split(" ")).filter(([u,m])=>u&&m&&Dr(m,e)).map(([u])=>u)}catch{return{name:n,didPass:!0,isSkipped:!0,message:"Docker not available - skipped crash-loop check"}}if(o.length===0)return{name:n,didPass:!0,isSkipped:!0,message:"No project containers to check"};let i;try{i=Cr(await s(["inspect","--format","{{.Name}} {{.RestartCount}} {{.State.Status}} {{.State.StartedAt}} {{.State.ExitCode}} {{.State.FinishedAt}}",...o]))}catch{return{name:n,didPass:!0,isSkipped:!0,message:"Containers changed while checking - skipped crash-loop check"}}let a=i.filter(d=>Er(d,r));if(a.length===0)return{name:n,didPass:!0,message:"No project container is crash-looping"};let l=[],c=new Set;for(let d of a){let u="";try{u=await s(["logs","--tail",String(vr),d.name])}catch{}let m=Q(u,t).split(/\r?\n/).map(S=>S.trim().slice(0,Tr)).filter(Boolean),P=Nr(u,d);P&&c.add(P),l.push([`${d.name}: ${d.status}, restarted ${d.restartCount} times`,...m.map(S=>` | ${S}`)].join(`
22
+ `))}return{name:n,didPass:!1,message:`${a.length} container${a.length===1?" is":"s are"} crash-looping:
23
+ ${l.join(`
24
+ `)}`,fix:[...c,"Read the full log: docker logs <container>"].join("; ")}}import{execFile as Ur}from"node:child_process";import{realpathSync as ds,statfsSync as Wr}from"node:fs";import{homedir as Vr}from"node:os";import{join as Gr}from"node:path";import{promisify as Kr}from"node:util";import{statfsSync as Rr}from"node:fs";import{homedir as _r}from"node:os";var ae="Disk Space",Or=1024**3,jr=2,Ae=10,os=["Free disk space, then retry: flisk doctor"," docker system prune -af # unused images and build cache (needs Docker running)"," npm cache clean --force"," bun pm cache rm"," brew cleanup -s"].join(`
25
+ `);function is(e=Rr,t=_r()){let s;try{let n=e(t);s=Number(n.bavail)*Number(n.bsize)/Or}catch{return{name:ae,didPass:!0,isSkipped:!0,message:"Could not read free disk space"}}let r=`${s.toFixed(1)} GB free`;return s<jr?{name:ae,didPass:!1,message:`${r}: Docker cannot build or start containers`,fix:os}:s<Ae?{name:ae,didPass:!1,isWarning:!0,message:`${r}: image builds may fail`,fix:os}:{name:ae,didPass:!0,message:r}}var Ar="Quit Docker Desktop completely (whale icon > Quit Docker Desktop), reopen it, and wait until it shows Running. If it keeps getting stuck, check free disk space. Then retry: flisk doctor";function as(e,t=process.platform){return t!=="darwin"||!e?null:{name:"Container Runtime",didPass:!1,message:"Docker Desktop is open, but its engine is not responding",fix:Ar}}import{execFileSync as Ir}from"node:child_process";var Ie=(e,t)=>Ir(e,t,{encoding:"utf-8",stdio:"pipe",windowsHide:!0}),cs=["traefik-public","traefik-private","backend","database","infisical-network","verdaccio-network","proxy"];function Fe(e){let t=e.replace(/-/g,"_");return cs.map(s=>`${t}_${s}`)}function ls(e){let t=new Set;for(let s of e)for(let r of["traefik-public","traefik-private"])s.endsWith(`_${r}`)&&t.add(s.slice(0,-(r.length+1)));return t}function He(e,t){for(let s of cs)if(e.endsWith(`_${s}`)){let r=e.slice(0,-(s.length+1));if(t.has(r))return r}return null}function Jo(e,t=Ie){let s=new Set(t("docker",["network","ls","--format","{{.Name}}"]).split(/\r?\n/).map(n=>n.trim()).filter(Boolean)),r={removed:[],kept:[]};for(let n of Fe(e))if(s.has(n))try{t("docker",["network","rm",n]),r.removed.push(n)}catch(o){let i=String(o.stderr??o).trim();r.kept.push({name:n,reason:i.split(/\r?\n/)[0]??""})}return r}function ce(e,t=process.platform,s=Ie){if(t==="win32")return[];let r;try{r=s("ps",["-axo","pid=,command="])}catch{return[]}let n=[];for(let o of r.split(/\r?\n/)){let i=/^\s*(\d+)\s+(.*)$/.exec(o);if(!i)continue;let a=i[2]??"",l=(a.split(/\s+/)[0]??"").split("/").at(-1),c=` -f ${e}`,d=a.indexOf(c),u=a[d+c.length];l==="tilt"&&/^\S+\s+up\s/.test(a)&&d>0&&(u===void 0||u===" ")&&n.push(Number(i[1]))}return n}function qo(e,t=process.platform,s=Ie){let r=ce(e,t,s);for(let n of r)try{s("kill",["-TERM",String(n)])}catch{}return r}async function zo(e,t=8e3,s=ce){let r=Date.now()+t;for(;s(e).length>0;){if(Date.now()>=r)return!1;await new Promise(n=>setTimeout(n,250))}return!0}import{execFile as Fr}from"node:child_process";function Me(e){return new Promise(t=>{Fr(D("tilt")??"tilt",["get","uiresources","-o","json","--port",String(e)],{timeout:15e3,maxBuffer:16*1024*1024},(s,r)=>t(s?null:r))})}function Hr(e){let s=(JSON.parse(e).items??[]).filter(r=>r.status?.disableStatus?.state!=="Disabled").map(r=>r.status?.updateStatus==="not_applicable"?{...r,status:{...r.status,updateStatus:"ok"}}:r);return JSON.stringify({items:s})}function Mr(e){return(JSON.parse(e).items??[]).filter(t=>t.status?.disableStatus?.state!=="Disabled").flatMap(t=>t.metadata?.name?[t.metadata.name]:[])}function Lr(e,t){let s=JSON.parse(e).items??[],r=new Map(s.map(n=>[n.metadata?.name,n]));return{missing:t.filter(n=>!r.has(n)),disabled:t.filter(n=>r.get(n)?.status?.disableStatus?.state==="Disabled")}}function Br(e,t,s){let r=De(Hr(e),t),n={ready:r.failures.length===0&&r.pendingCount===0&&r.okCount>0,failures:r.failures.map(o=>({name:o.name,message:o.error||`${o.updateStatus}/${o.runtimeStatus}`})),pending:r.pendingCount,timedOut:!1,enabled:Mr(e)};if(s&&s.length>0){let{missing:o,disabled:i}=Lr(e,s);if(o.length>0&&(n={...n,ready:!1,pending:n.pending+o.length}),i.length>0)return{settled:!0,result:{...n,ready:!1,failures:[...n.failures,...i.map(a=>({name:a,message:"disabled in Tilt"}))]}}}return{result:n,settled:n.ready||n.failures.length>0&&n.pending===0}}async function ei(e,t={}){let s=t.timeoutMs??Number(process.env.TDK_UP_READY_TIMEOUT_MS??9e5),r=t.intervalMs??2e3,n=t.fetchJson??Me,o=t.deferred??$e(),i=Date.now()+s,a={ready:!1,failures:[],pending:0,timedOut:!1,enabled:[]};for(;;){let l=await n(e);if(l)try{let c=Br(l,o,t.expected);if(a=c.result,c.settled||a.failures.length>0&&t.isStalled?.(l))return a}catch{}if(Date.now()>=i)return{...a,ready:!1,timedOut:!0};await new Promise(c=>setTimeout(c,r))}}var le=Kr(Ur),Jr=15e3,qr=/^tdk_doctor_probe_(\d+)$/,zr=async e=>{let{stdout:t}=await le("docker",e,{encoding:"utf-8",timeout:Jr,windowsHide:!0});return t};function Le(e){return String(e.stderr??e).trim().split(/\r?\n/)[0]??""}var Xr=120;function Yr(e){let t=/^\s*(?:(?:(\d+)-)?(\d+):)?(\d+):(\d+)\s*$/.exec(e);if(!t)return null;let[,s="0",r="0",n="0",o="0"]=t;return((Number(s)*24+Number(r))*60+Number(n))*60+Number(o)}async function Zr(e,t=Me,s=2,r=n=>new Promise(o=>setTimeout(o,n))){for(let n=0;n<s;n++){n>0&&await r(1e3);for(let o of e)if(await t(o)!==null)return!0}return!1}async function Qr(e){if(process.platform==="win32")return"unknown";try{let{stdout:t}=await le("ps",["-p",String(e),"-o","etime="],{encoding:"utf-8",timeout:5e3}),s=Yr(t);if(s===null||s<Xr)return"unknown";let{stdout:r}=await le("lsof",["-nP","-a","-p",String(e),"-iTCP","-sTCP:LISTEN","-Fn"],{encoding:"utf-8",timeout:5e3}),n=[...r.matchAll(/^n.*:(\d+)$/gm)].map(o=>Number(o[1]));return await Zr(n)?"healthy":"stale"}catch{return"unknown"}}function en(e=zr){return{async listNetworks(){let t=(await e(["network","ls","--format","{{.Name}}"])).split(/\r?\n/).map(r=>r.trim()).filter(Boolean);return(await Promise.all(t.map(async r=>{try{let n=await e(["network","inspect","--format","{{len .Containers}}",r]);return{name:r,containers:Number(n.trim())||0}}catch(n){if(/no such network|not found/i.test(String(n)))return null;throw n}}))).filter(r=>r!==null)},async removeNetwork(t){try{await e(["network","rm",t])}catch(s){throw new Error(Le(s))}},freeDiskGib(){try{let t=Wr(Vr());return Number(t.bavail)*Number(t.bsize)/1024**3}catch{return null}},async reclaimable(){let t=null;try{let r=await e(["system","df","--format","{{.Type}} {{.Reclaimable}}"]);for(let n of r.split(/\r?\n/)){let[o,i]=n.split(" ");o==="Build Cache"&&i&&(t=i.replace(/\s*\(.*\)/,"").trim())}}catch{}let s=0;try{s=(await e(["images","-q","-f","dangling=true"])).split(/\r?\n/).filter(Boolean).length}catch{}return{buildCache:t,danglingImages:s}},async prune(t){try{await e(t==="builder"?["builder","prune","-f"]:["image","prune","-f"])}catch(s){throw new Error(Le(s))}},async tiltStatus(t){return Qr(t)},stopProcess(t){process.kill(t,"SIGTERM")},isProcessAlive(t){try{return process.kill(t,0),!0}catch(s){return s.code==="EPERM"}}}}var us=en();async function ps(e,t=us,s={}){let r=[],n=[];for(let d of s.tiltPids??[])await t.tiltStatus(d)==="stale"?r.push({id:`stop-tilt:${d}`,check:"Tilt processes",scope:"project",command:`kill -TERM ${d}`,status:"planned",reason:"this project's Tilt is running but its UI does not answer"}):n.push(d);let o=n.length>0?`Tilt is running for this project (pid ${n.join(", ")}); run flisk down first`:void 0,i=await t.listNetworks(),a=t.freeDiskGib();if(a!==null&&a<Ae){let{buildCache:d,danglingImages:u}=await t.reclaimable(),m=`${a.toFixed(1)} GB free`;d&&!/^0(\.0+)?\s*B$/i.test(d)&&r.push({id:"prune:builder",check:"Disk Space",scope:"host",command:"docker builder prune -f",status:"planned",reason:`${m}; ${d} of build cache reclaimable`}),u>0&&r.push({id:"prune:images",check:"Disk Space",scope:"host",command:"docker image prune -f",status:"planned",reason:`${m}; ${u} untagged image${u===1?"":"s"}`})}let l=new Set(e?Fe(e):[]),c=ls(i.map(d=>d.name));for(let d of i){let u=qr.exec(d.name),m,P,S="unused network";if(u)m="probe",P="Orphaned probe networks";else if(l.has(d.name))m="project",P="Docker networks";else if(s.machine&&He(d.name,c))m="machine",P="Docker networks",S=`unused network of project "${He(d.name,c)}"`;else continue;let k={id:`remove-network:${d.name}`,check:P,scope:m,command:`docker network rm ${d.name}`,status:"planned",reason:S};d.containers>0?(k.status="skipped",k.reason=`${d.containers} container(s) still attached`):m==="project"&&o?(k.status="skipped",k.reason=o):u&&t.isProcessAlive(Number(u[1]))&&(k.status="skipped",k.reason=`probe of a running process (pid ${u[1]})`),r.push(k)}return r}async function fs(e,t,s=us){let r=[];for(let n of e){if(n.status!=="planned"||t.dryRun){r.push(n);continue}if(!t.confirm){r.push({...n,status:"skipped",reason:"confirmation required; run in a terminal or pass --yes"});continue}let o=!1;try{o=await t.confirm(n)}catch{}if(!o){r.push({...n,status:"skipped",reason:"not confirmed"});continue}try{if(n.id.startsWith("prune:")){await s.prune(n.id==="prune:builder"?"builder":"images"),r.push({...n,status:"applied",reason:"pruned"});continue}if(n.id.startsWith("stop-tilt:")){s.stopProcess(Number(n.id.slice(10))),r.push({...n,status:"applied",reason:"stopped"});continue}await s.removeNetwork(n.id.slice(15)),r.push({...n,status:"applied",reason:"removed"})}catch(i){r.push({...n,status:"failed",reason:Le(i)})}}return r}function ms(e){return e.some(t=>t.status==="failed")}async function tn(e){try{let{stdout:t}=await le(e,["--version"],{encoding:"utf-8",timeout:5e3,windowsHide:!0});return t.trim().split(/\r?\n/)[0]||null}catch{return null}}async function sn(e=dt("flisk"),t=tn){let s=new Set,r=[];for(let n of e){let o=n;try{o=ds(n)}catch{}s.has(o)||(s.add(o),r.push({path:n,version:await t(n)}))}return r}async function hs(e){let t=e??await sn();if(t.length<=1)return{name:"Flisk installs",didPass:!0,message:"One flisk install on PATH"};let s=t.map(n=>`${n.path} (${n.version??"version unknown"})`).join(", "),r=new Set(t.map(n=>n.version));return{name:"Flisk installs",didPass:!1,isWarning:!0,message:`${t.length} flisk installs on PATH${r.size>1?" with different versions":""}: ${s}`,fix:"Keep one: remove the others, or run flisk upgrade. The first on PATH is the one your shell runs; tools that launch /usr/local/bin/flisk directly may use another"}}function gs(e,t=ce){let s=new Set([e]);try{s.add(ds(e))}catch{}let r=new Set;for(let n of s)for(let o of t(Gr(n,".tdk",".tdk-out","Tiltfile")))r.add(o);return[...r]}var Be=1;function rn(e,t=[]){return{schemaVersion:Be,data:e,errors:t}}function nn(e){return e instanceof ut?{error:{code:"COMMAND_FAILED",message:e.message,...e.suggestions.length>0?{suggestions:e.suggestions}:{}},exitCode:e.exitCode}:{error:{code:"INTERNAL",message:e instanceof Error?e.message:String(e)},exitCode:2}}function fi(e){let t=nn(e);console.log(JSON.stringify(rn(null,[t.error]))),console.error(t.error.message),process.exit(t.exitCode)}async function Ss(e,t){let s=[],r=[],n=o=>{o.status==="fulfilled"?s.push(o.value):r.push({code:"INTERNAL",message:o.reason instanceof Error?o.reason.message:String(o.reason)})};for(let o of await Promise.allSettled(e.map(i=>Promise.resolve().then(i))))n(o);for(let o of t)for(let i of await Promise.allSettled([Promise.resolve().then(o)]))n(i);return{checks:s,errors:r}}function ks(e){let t=e.find(s=>s.name===v);return t?t.didPass:null}function A(e,t,s=[],r,n){return{schemaVersion:Be,data:{ready:s.length===0&&e.every(o=>o.didPass||o.isSkipped||o.isWarning),inProject:t,checks:e,...n?{host:{...n,containerRuntimeReachable:ks(e),canUp:n.canUp&&ks(e)!==!1}}:{},...r!==void 0?{ports:{http:{requested:r?.requested.ingressHttp??80,chosen:r?.ingressHttp??null,explicit:r?.explicit.ingressHttp??!!process.env.TDK_HTTP_PORT,reason:r?.reason.ingressHttp??"port planning failed"},https:{requested:r?.requested.ingressHttps??443,chosen:r?.ingressHttps??null,explicit:r?.explicit.ingressHttps??!!process.env.TDK_HTTPS_PORT,reason:r?.reason.ingressHttps??"port planning failed"},postgres:{requested:r?.requested.postgres??5432,chosen:r?.postgres??null,explicit:r?.explicit.postgres??!!process.env.TDK_POSTGRES_PORT,reason:r?.reason.postgres??"port planning failed"}}}:{}},errors:s}}function ys(e){return e.errors.length>0?2:e.data.ready?0:1}import{existsSync as We,readFileSync as Ps}from"node:fs";import{dirname as on,join as Ue,normalize as an}from"node:path";function cn(e){let t=new Set,s=/^load\s*\(([\s\S]*?)\)/gm;for(let r of e.matchAll(s)){let n=r[1]??"",o=/["']([^"']+)["']/g,i=Array.from(n.matchAll(o),a=>a[1]);for(let a of i.slice(1))a.startsWith("_")&&t.add(a)}return Array.from(t).sort()}function xs(e,t,s){return t==="ext://tdk-cli"?Ue(s,".tdk",".tdk-out","tdk-cli-ext","Tiltfile"):t.startsWith(".")||!t.includes("://")&&!t.startsWith("@")?an(Ue(on(e),t)):null}function ln(e,t,s){let r=[],n=/^load\s*\(([\s\S]*?)\)/gm;for(let o of t.matchAll(n)){let l=(o[1]??"").match(/["']([^"']+)["']/)?.[1];if(!l)continue;let c=xs(e,l,s);c&&(We(c)||r.push(`${e}: ${l} -> ${c}`))}return r}function dn(e,t,s){let r=[],n=/^load\s*\(([\s\S]*?)\)/gm;for(let o of t.matchAll(n)){let l=(o[1]??"").match(/["']([^"']+)["']/)?.[1];if(l){let c=xs(e,l,s);c&&r.push(c)}}return r}function un(e,t){let s=new Set,r=[e];for(;r.length>0;){let n=r.pop();if(!n||s.has(n)||!We(n))continue;s.add(n);let o=Ps(n,"utf-8");for(let i of dn(n,o,t))s.has(i)||r.push(i)}return Array.from(s).sort()}function ws(){let e=h()??process.cwd(),t=Ue(e,".tdk",".tdk-out","Tiltfile");if(!We(t))return{name:"Starlark Load Exports",didPass:!0,isSkipped:!0,message:"No generated Tiltfile to check"};let s=[],r=[],n=un(t,e);for(let i of n){let a=Ps(i,"utf-8"),l=cn(a);l.length>0&&s.push(`${i}: ${l.join(", ")}`);for(let c of ln(i,a,e))r.push(c)}if(s.length===0&&r.length===0)return{name:"Starlark Load Exports",didPass:!0,message:`${y(n.length,"reachable Starlark file")} ${n.length===1?"has":"have"} valid load exports and paths`};let o=[];return s.length>0&&o.push(`Private exported symbols:
26
+ ${s.join(`
27
+ `)}`),r.length>0&&o.push(`Missing relative load targets:
28
+ ${r.join(`
29
+ `)}`),{name:"Starlark Load Exports",didPass:!1,message:`Starlark load() issues will stop Tilt before services start:
30
+ ${o.join(`
31
+
32
+ `)}`,fix:"Regenerate with a current Flisk (`flisk config regenerate`). If the issue remains, rename loaded symbols without leading underscores and update stale load paths."}}import{relative as pn}from"node:path";function de(e,t){return pn(e,t.configPath)||t.configPath}function bs(e){let t=e.config?.dependsOn;return t===void 0?[]:Array.isArray(t)&&t.every(s=>typeof s=="string")?t:null}function fn(e){let t=new Set(e.map(a=>a.name)),s=new Map(e.map(a=>[a.name,(bs(a)??[]).filter(l=>t.has(l))])),r=new Map,n=new Map,o=[],i=a=>{n.set(a,"visiting"),o.push(a);for(let l of s.get(a)??[])if(n.get(l)==="visiting"){let c=o.slice(o.indexOf(l)),d=c.indexOf([...c].sort()[0]??l),u=[...c.slice(d),...c.slice(0,d)];r.set(u.join(">"),[...u,u[0]??l])}else n.has(l)||i(l);o.pop(),n.set(a,"done")};for(let a of[...s.keys()].sort())n.has(a)||i(a);return[...r.values()]}function Ve(e=h()??process.cwd(),t=x(e)){let s=t.flatMap(r=>bs(r)===null?[`${de(e,r)} dependsOn ${JSON.stringify(r.config?.dependsOn)}: expected an array of service names`]:[]);return s.length===0?{name:"dependsOn",didPass:!0,message:"Every dependsOn is a list of service names"}:{name:"dependsOn",didPass:!1,message:`${y(s.length,"invalid dependsOn","invalid dependsOn")}:
33
+ ${s.join(`
34
+ `)}`,fix:'Write "dependsOn" as an array of service names, for example ["postgres", "api"]'}}function Ge(e=h()??process.cwd(),t=x(e)){let s=new Map(t.map(o=>[o.name,o])),r=fn(t);if(r.length===0)return{name:"Circular dependencies",didPass:!0,message:"No service depends on itself through dependsOn"};let n=r.map(o=>{let i=s.get(o[0]??""),a=o[1]??"";return`${i?de(e,i):o[0]} dependsOn "${a}": circular dependency ${o.join(" -> ")}`});return{name:"Circular dependencies",didPass:!1,message:`${r.length} ${r.length===1?"circular dependency":"circular dependencies"}:
35
+ ${n.join(`
36
+ `)}`,fix:'Remove one "dependsOn" entry in each cycle so the services no longer wait on each other, or move what they share into a third service'}}function Ke(e=h()??process.cwd(),t=x(e)){let s=t.flatMap(r=>{let n=r.config?.port;return n===void 0?[]:typeof n=="number"&&Number.isInteger(n)&&n>=1&&n<=65535?[]:[`${de(e,r)} port ${JSON.stringify(n)}: not an integer from 1 to 65535`]});return s.length===0?{name:"Service ports",didPass:!0,message:"Every declared port is valid"}:{name:"Service ports",didPass:!1,message:`${y(s.length,"invalid port")}:
37
+ ${s.join(`
38
+ `)}`,fix:'Set "port" to the port the service listens on, an integer from 1 to 65535'}}function Je(e=h()??process.cwd(),t=!1,s=x(e)){let r=[],n=[];for(let i of s){let a=i.config?.schemaVersion;if(a===Pe)continue;let l=de(e,i);a===void 0?r.push(`${l} schemaVersion: missing`):n.push(`${l} schemaVersion ${JSON.stringify(a)}: this flisk supports ${Pe}`)}if(r.length===0&&n.length===0)return{name:"schemaVersion",didPass:!0,message:"Every service.json has a supported schemaVersion"};let o=[...r.length>0?["Run `flisk config migrate` to add the missing schemaVersion"]:[],...n.length>0?["A version this flisk does not support cannot be migrated: upgrade flisk"]:[]];return{name:"schemaVersion",didPass:!1,isWarning:!t,message:`${y(r.length+n.length,"service.json file")} without a supported schemaVersion:
39
+ ${[...r,...n].join(`
40
+ `)}`,fix:o.join(". ")}}function Ci(e,t){let s=Je(e,!1,t);s.didPass||(console.warn(g.yellow(`\u26A0 ${s.message}`)),s.fix&&console.warn(g.gray(` ${s.fix}`)))}function Ri(e,t={},s=process.exit){let r=t.resources??x(e),n=[se(e,r),Ve(e,r),Ge(e,r),Ke(e,r)].filter(i=>!i.didPass);if(n.length===0)return;let o=n.map(i=>`${i.message}${i.fix?`
41
+ Fix: ${i.fix}`:""}`).join(`
42
+ `);t.onInvalid?.(o);for(let i of n)console.error(g.red(`\u2717 ${i.message}`)),i.fix&&console.error(g.gray(` Fix: ${i.fix}`));s(1)}import{readFileSync as mn}from"node:fs";import{join as hn}from"node:path";function vs(e){let t=e.trim().match(/(\d+)\.(\d+)(?:\.(\d+))?/);return t?[Number(t[1]),Number(t[2]),Number(t[3]??0)]:null}function gn(e){try{return JSON.parse(mn(hn(e,".tdk",j),"utf-8"))?.minTdkVersion}catch{return}}function Ts(e,t){let s=gn(e);if(s===void 0)return{status:"none"};let r=typeof s=="string"?vs(s):null;if(!r||!/^\d+\.\d+\.\d+$/.test(String(s).trim()))return{status:"malformed",message:`.tdk/project.json minTdkVersion ${JSON.stringify(s)} is not a version like "1.3.80"`,fix:'Set minTdkVersion to a MAJOR.MINOR.PATCH string, for example "1.3.80".'};let n=vs(t),o=n!==null&&(()=>{for(let[a,l]of r.entries()){let c=n[a]??0;if(c!==l)return c>l}return!0})(),i=r.join(".");return o?{status:"ok",required:i}:{status:"too-old",required:i,message:`flisk ${t} is older than the ${i} this project requires (minTdkVersion)`,fix:"Run: flisk upgrade"}}var C=1e4,N={dockerMissing:"See https://docs.docker.com/get-docker/",dockerDaemonDown:"Start Docker Desktop, OrbStack, or Colima, then retry: flisk doctor",tiltMissing:"curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash",bunMissing:"curl -fsSL https://bun.sh/install | bash",port80:"Stop the process bound to port 80, or stop local nginx/caddy. Then: flisk doctor",port5432:"Stop local Postgres or change the host port. Then: flisk doctor",notProject:"flisk project --yes"},Ds="WSL2 detected. Use Docker Desktop WSL integration. Guide: docs/wsl2.md",Es="Native Windows landscape boot is unsupported. Use WSL2 Ubuntu with Docker Desktop integration. Guide: docs/wsl2.md",qe=[0,25,0],H=[1,2,0];function ue(e,t){let s=et(e);return s?tt(s,t):!1}function Pn(e,t,s=process.platform==="linux"&&!!process.env.WSL_DISTRO_NAME){if(!s)return{name:"WSL project location",didPass:!0,isSkipped:!0,message:"Not running in WSL2 - skipped project filesystem check"};let r=e.replace(/\\/g,"/").replace(/\/+$/,"").toLowerCase();if(r!=="/mnt/c"&&!r.startsWith("/mnt/c/"))return{name:"WSL project location",didPass:!0,message:`${Ds} Project is on the WSL filesystem.`};let n=`${Ds} Project is under /mnt/c (${e}); hot reload may be broken. Move it into the WSL filesystem, such as ~/projects.`;return{name:"WSL project location",didPass:!t,isWarning:!t,message:n,fix:"Move the repository under your WSL home directory (for example, ~/projects) and retry flisk doctor."}}function xn(e){let t=e.replace(/^\[(.*)\]$/,"$1");return t==="0.0.0.0"||/^[0:]*:0\.0\.0\.0$/.test(t)?!0:t.includes(":")&&/^[0:]+$/.test(t)}function wn(e=process.env,t=!1){let s=(e.TDK_BIND_ADDRESS??"").trim();return xn(s)?{name:"Dev Port Bind Address",didPass:!t,isWarning:!t,message:`TDK_BIND_ADDRESS is ${s}: Traefik and Postgres are reachable from other machines on the network.`,fix:"Unset TDK_BIND_ADDRESS to keep dev ports on 127.0.0.1. Keep it only when you test from another device on purpose."}:{name:"Dev Port Bind Address",didPass:!0,message:s?`Dev ports bind to ${s}`:"Dev ports bind to 127.0.0.1 (this machine only)"}}function Rs(e){let t=e.filter(r=>!r.didPass&&!r.isSkipped&&!r.isWarning),s=e.filter(r=>r.didPass||r.isSkipped||r.isWarning);return t.sort((r,n)=>{let o=i=>i.name==="Host Ports"&&/\b5432\b/.test(i.message)?0:i.name==="Host Ports"?1:i.name==="Ingress Ports"?2:3;return o(r)-o(n)}),[...t,...s]}function bn(e,t){return t?e?"Doctor passed. Next: flisk up":"Doctor passed. Next: flisk project example":"Doctor failed. Fix the items above, then run: flisk doctor"}var Qe={checkDockerOperatingSystem:"Docker OS",checkDockerCompose:"Docker Compose",checkDockerVersions:"Docker Versions",checkDockerNetworkCapacity:"Docker Network Pool",checkContainerResourceHealth:"Container Resource Health",checkCrashLoopingContainers:"Crash-looping containers",checkTiltResourceHealth:"Tilt Resources",checkServiceHealth:"Service Health",checkServiceSmoke:"Service Smoke",checkAdvertisedEndpoints:"Advertised Endpoints"},Ns=new Set(["Docker OS","Docker Compose","Docker Versions","Docker Network Pool","Container Resource Health","Crash-looping containers","Tilt Resources","Service Health","Service Smoke","Advertised Endpoints",...Object.keys(Qe)]);function vn(e){return!!(e.requiresContainerRuntime||e.checkName&&Ns.has(e.checkName)||e.name&&Ns.has(e.name))}function E(e,t){let s=()=>t();return s.requiresContainerRuntime=!0,s.checkName=e,s}async function Tn(e,t={}){let s=[],r=!!t.containerRuntimeFailed;for(let n of e){if(r&&vn(n)){let i=n.checkName??(n.name&&Qe[n.name]?Qe[n.name]:n.name&&n.name!=="check"&&n.name!=="fn"?n.name:"Container check");s.push({name:i,didPass:!0,isSkipped:!0,message:`Docker not running - skipped ${i} check`});continue}let o=await n();if(s.push(o),o.name===v&&!o.didPass&&(r=!0),!t.all&&!o.didPass&&!o.isSkipped&&!o.isWarning)break}return s}function $n(e){let t=0,s=0,r=0,n=0;for(let o of e)o.isSkipped?r++:o.isWarning?n++:o.didPass?t++:s++;return n>0?`${t} passed, ${n} warned, ${s} failed, ${r} skipped`:`${t} passed, ${s} failed, ${r} skipped`}function rt(e){return $t(e)}function Dn(e,t,s,r,n){return async(o=R)=>{try{return await o(t,C),{name:e,didPass:!0,message:s}}catch(i){return rt(i)?{name:e,didPass:!1,message:`${e} did not respond within ${C/1e3}s`,fix:n}:{name:e,didPass:!1,message:r,fix:n}}}}async function ze(e){try{return await R(e,C),!0}catch{return!1}}async function nt(){if(process.platform==="win32")try{return await R("docker ps",C),{name:v,didPass:!0,message:"Docker Desktop Linux container engine is running"}}catch{return{name:v,didPass:!1,message:"Docker is not running",fix:N.dockerDaemonDown}}let e=!!D("docker");try{return await R("docker ps",C),{name:v,didPass:!0,message:"Docker daemon is running"}}catch(s){if(rt(s))return{name:v,didPass:!1,message:"Docker daemon is not responding",fix:N.dockerDaemonDown}}let t=as(await ze("pgrep -f com.docker.backend"));return t||(await ze("colima status")?{name:v,didPass:!0,message:"Colima (Docker runtime) is running"}:D("colima")?{name:v,didPass:!1,message:"Colima is installed but not running",fix:N.dockerDaemonDown}:await ze("podman ps")?{name:v,didPass:!0,message:"Podman is running"}:e?{name:v,didPass:!1,message:"Docker daemon is not running",fix:N.dockerDaemonDown}:{name:v,didPass:!1,message:"Docker is not running",fix:N.dockerMissing})}var z=Dn("Docker Compose","docker compose version","Docker Compose plugin available","Docker Compose plugin not found","Install Docker Compose: https://docs.docker.com/compose/install/");z.requiresContainerRuntime=!0;z.checkName="Docker Compose";var En=[25,0,0],Nn=[2,20,2];function et(e){let t=e.trim().match(/(\d+)\.(\d+)(?:\.(\d+))?/);return t?[Number(t[1]),Number(t[2]),Number(t[3]??0)]:null}function tt(e,t){for(let[s,r]of t.entries()){let n=e[s]??0;if(n!==r)return n>r}return!0}async function X(e=R){let t=async a=>String(await e(a,C)).trim(),s,r;try{[s,r]=await Promise.all([t("docker version --format '{{.Server.Version}}'"),t("docker compose version --short")])}catch{return{name:"Docker Versions",didPass:!0,isSkipped:!0,message:"Could not read Docker Engine/Compose versions - skipped version check"}}let n=et(s),o=et(r);if(!n||!o)return{name:"Docker Versions",didPass:!0,isSkipped:!0,message:`Unrecognized Docker version output (engine "${s}", compose "${r}") - skipped version check`};let i=[];return tt(n,En)||i.push(`Docker Engine ${s} (need 25.0+)`),tt(o,Nn)||i.push(`Docker Compose ${r} (need 2.20.2+)`),i.length===0?{name:"Docker Versions",didPass:!0,message:`Docker Engine ${s} and Compose ${r} support generated healthchecks`}:{name:"Docker Versions",didPass:!1,message:`Docker is too old for generated healthchecks (they use start_interval):
43
+ ${i.join(`
44
+ `)}`,fix:"Update Docker Desktop, or on Linux update docker-ce and the docker-compose-plugin package"}}X.requiresContainerRuntime=!0;X.checkName="Docker Versions";async function ot(e=R){let t;try{t=String(await e("tilt version",C)).trim()}catch{return{name:"Tilt CLI",didPass:!1,message:"Tilt CLI not found",fix:N.tiltMissing}}return ue(t,qe)?{name:"Tilt CLI",didPass:!0,message:`Tilt ${t} is available`}:{name:"Tilt CLI",didPass:!1,message:`Tilt ${t||"version unknown"} is below the required v${qe.join(".")} floor`,fix:`Install Tilt v${qe.join(".")} or newer: https://docs.tilt.dev/install.html`}}async function it(e=R){let t;try{t=String(await e("docker info --format '{{.OSType}}'",C)).trim().toLowerCase()}catch{return{name:"Docker OS",didPass:!0,isSkipped:!0,message:"Docker OS unavailable - daemon failure reported separately"}}return t!=="linux"?{name:"Docker OS",didPass:!1,message:`Docker is using the ${t||"unknown"} engine; Flisk requires Linux containers`,fix:"Switch Docker Desktop to Linux containers, enable WSL2 integration, then retry flisk doctor."}:{name:"Docker OS",didPass:!0,message:"Docker is using Linux containers"}}it.requiresContainerRuntime=!0;it.checkName="Docker OS";async function Cn(e=R){let t=h();if(!t)return{name:"Bun",didPass:!0,isSkipped:!0,message:"Not in a project - skipping Bun check"};if(!oe(x(t)))return{name:"Bun",didPass:!0,isSkipped:!0,message:"No generated JS services - skipping Bun check"};try{let s=String(await e("bun --version",C)).trim();return ue(s,H)?{name:"Bun",didPass:!0,message:`Bun ${s} is available`}:{name:"Bun",didPass:!1,message:`Bun ${s||"version unknown"} is below the required ${H.join(".")} floor`,fix:`Install Bun ${H.join(".")} or newer: https://bun.sh/install`}}catch{return{name:"Bun",didPass:!1,message:"Bun is not available",fix:N.bunMissing}}}function Rn(){let e=h()??process.cwd(),t=pt(e);if(t.missing.length>0)return{name:"Environment Variables",didPass:!1,message:`Missing required env variables: ${t.missing.join(", ")}`,fix:T(w(e,".env"))?`Edit .env and set these values:
45
+ ${t.missing.map(r=>`${r}=<value>`).join(`
46
+ `)}`:"No .env file yet. Run `flisk project` to create one with working defaults."};if(t.invalid.length>0)return{name:"Environment Variables",didPass:!1,message:`Invalid env variables: ${t.invalid.join(", ")}`,fix:"Edit .env and provide values for empty variables"};let s=t.warnings.filter(r=>!r.startsWith(".env file not found"));return{name:"Environment Variables",didPass:!0,message:s.length>0?`All required environment variables are set (warning: ${s.join("; ")})`:"All required environment variables are set"}}function _n(){let e=h()??process.cwd(),t=w(e,".tdk",".tdk-out"),s=ge.filter(r=>!T(w(t,r)));return s.length===0?{name:"Master Configs",didPass:!0,message:`${ge.join(", ")} found`}:{name:"Master Configs",didPass:!1,message:`Master configs missing: ${s.join(", ")}`,fix:"Run: flisk project"}}function On(e=lt()){let t=h();if(!t)return{name:"Flisk version",didPass:!0,isSkipped:!0,message:"Not in a project - skipping Flisk version check"};let s=Ts(t,e);return s.status==="none"?{name:"Flisk version",didPass:!0,isSkipped:!0,message:`flisk ${e} (no minTdkVersion in .tdk/project.json)`}:s.status==="malformed"||s.status==="too-old"?{name:"Flisk version",didPass:!1,message:s.message,fix:s.fix}:{name:"Flisk version",didPass:!0,message:`flisk ${e} meets minTdkVersion ${s.required}`}}function jn(){let e=h()??process.cwd(),t=we(e),s=x(e).filter(n=>!ie(e,n.path,t));if(s.length===0)return{name:"Resource Discovery",didPass:!0,message:`All resources are covered by discovery.paths ${JSON.stringify(t)}`};let r=s.map(n=>Ye(Ze(e,n.path))).join(", ");return{name:"Resource Discovery",didPass:!1,message:`${y(s.length,"resource")} outside discovery.paths ${JSON.stringify(t)}: ${r}. \`flisk up\` will not start them`,fix:"Move each under services/<stack>/<name>, or add a matching pattern to discovery.paths in .tdk/project.json and run: flisk config regenerate"}}var An=["install-deps.sh","bun-hoisted-symlink-fix.sh","prisma-bun-client-link-fix.sh","prisma-normalize-client.sh","infisical-entrypoint.sh"],In=/shared-platform-engineering\/docker-templates\/([\w.-]+)/g;function Fn(e){let t=new Set;for(let s of x(e)){let r=w(s.path,O);if(T(r)){for(let n of Sn(r))if(!(!n.startsWith("Dockerfile")||n.endsWith(".dockerignore")))for(let o of B(w(r,n),"utf-8").matchAll(In))t.add(o[1])}}return[...t]}function Hn(){let e=h()??process.cwd(),t=[];T(w(e,F))||t.push(`${F} (workspace root manifest)`);let s=w(e,"shared-platform-engineering","docker-templates"),r=[];try{r=Fn(e)}catch{}for(let n of new Set([...An,...r]))T(w(s,n))||t.push(`shared-platform-engineering/docker-templates/${n}`);return t.length===0?{name:"Generated Runtime Assets",didPass:!0,message:"Generated Docker runtime assets are present"}:{name:"Generated Runtime Assets",didPass:!1,message:`Generated Dockerfiles reference missing project runtime assets:
47
+ ${t.join(`
48
+ `)}`,fix:"Run `flisk project --yes` (or `flisk config regenerate`) with an updated Flisk. `flisk up` also refreshes these assets before starting Tilt."}}function Mn(){let e=h()??process.cwd(),t=x(e),s=[];for(let r of t){let n=w(r.path,F);if(!T(n))continue;let o=r.config?.appType??"backend",i=ke[o]??ke.backend,a;try{a=JSON.parse(B(n,"utf-8")).scripts??{}}catch{s.push(`${r.name} (package.json is invalid JSON)`);continue}let l=i.filter(c=>!a[c]);l.length>0&&s.push(`${r.name}: missing "${l.join('", "')}"`)}return s.length===0?{name:"Startup Scripts",didPass:!0,message:"All services have required package.json scripts"}:{name:"Startup Scripts",didPass:!1,message:`Services missing required package.json scripts:
49
+ ${s.join(`
50
+ `)}`,fix:`Add the missing scripts to each service's package.json, e.g.:
51
+ "dev": "bun run src/index.ts",
52
+ "build": "tsc",
53
+ "start": "bun run dist/index.js"`}}function _s(e){if(e.startsWith("@"))return[e.split("/").slice(0,2).join("/")];let t=e.split("/")[0]??e;return[`@types/${t}`,t]}function Os(e,t,s=0){let r=e[t];if(typeof r!="string"||s>3)return!1;if(/(^|[\s;&|(])tsc(\s|$)/.test(r))return!0;for(let n of r.matchAll(/\b(?:bun|npm|pnpm|yarn)\s+run\s+([\w:.-]+)/g))if(Os(e,n[1],s+1))return!0;return!1}function Cs(e){let t=_s(e);return e.includes("/")||e.startsWith("@")?t[t.length-1]:t[0]}var Ln={"@types/bun":"^1.4.2","@types/node":"^20.0.0"};function Bn(e,t){return(t.includes("bun")||e.has("@types/bun"))&&!e.has("@types/node")}function Un(){let e=h()??process.cwd(),t=x(e),s=[],r=new Set;for(let o of t){let i=w(o.path,F),a=w(o.path,O,"tsconfig.docker.autogenerated.json");if(!T(i)||!T(a))continue;let d=st(a)?.compilerOptions?.types;if(!Array.isArray(d))continue;let u=st(i),m=u?.scripts??{};if(!Os(m,"build"))continue;let P=new Set([...Object.keys(u?.devDependencies??{}),...Object.keys(u?.dependencies??{})]),S=d.filter(k=>typeof k=="string").filter(k=>!_s(k).some(f=>P.has(f)));for(let k of S)r.add(Cs(k));if(S.length>0){let k=S.map(f=>`"${f}" (needs ${Cs(f)})`).join(", ");s.push(`${o.name}: "types" lists ${k}`)}Bn(P,d)&&(r.add("@types/node"),s.push(`${o.name}: uses @types/bun, which references node types internally, but @types/node is not a direct dependency`))}if(s.length===0)return{name:"TypeScript Type Dependencies",didPass:!0,message:`Every service's Docker tsconfig "types" entry has its @types package installed`};let n=[...r].map(o=>{let i=Ln[o];return i?`"${o}": "${i}"`:`${o} (bun add -d ${o})`});return{name:"TypeScript Type Dependencies",didPass:!1,message:`\`bun run build\` will fail with TS2688 (Cannot find type definition file) because a @types package it needs is not a direct dependency:
54
+ ${s.join(`
55
+ `)}`,fix:`Add to each service's package.json devDependencies: ${n.join(", ")}`}}function st(e){try{return JSON.parse(B(e,"utf-8"))}catch{return null}}function Wn(e){let s=st(e)?.compilerOptions;return s?.allowSyntheticDefaultImports===!0||s?.esModuleInterop===!0}function Vn(){let e=h()??process.cwd(),t=x(e).filter(r=>r.config?.appType==="frontend");if(t.length===0)return{name:"Frontend Docker Preflight",didPass:!0,isSkipped:!0,message:"No frontend services to check"};let s=[];for(let r of t){T(w(r.path,"public"))||s.push(`${r.name}: no public/ folder, but the Docker build copies it. Create it: mkdir -p ${Ye(Ze(e,w(r.path,"public")))} && touch ${Ye(Ze(e,w(r.path,"public",".gitkeep")))}`);let n=w(r.path,O,"Dockerfile.app.autogenerated"),o=w(r.path,O,"tsconfig.docker.autogenerated.json");if(T(n)){let i=B(n,"utf-8"),a=/\bEXPOSE\s+80\b/.test(i),l=r.config?.stack,c=l?w(e,"services",l,O,"docker-compose.app.autogenerated.yml"):"";if(a&&c&&T(c)){let d=B(c,"utf-8"),u=r.name.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),m=new RegExp(`traefik\\.http\\.services\\.${u}\\.loadbalancer\\.server\\.port=(\\d+)`),S=d.match(m)?.[1];S&&S!=="80"&&s.push(`${r.name}: nginx runtime exposes 80, but generated Traefik routes to ${S}`)}}if(T(o)&&!Wn(o)){let i=w(r.path,"src","main.tsx");if(T(i)){let a=B(i,"utf-8"),l=[/import\s+\w+\s+from\s+["']react["']/,/import\s+\w+\s+from\s+["']react-dom\/client["']/].filter(c=>c.test(a)).map(c=>c.source.includes("react-dom")?"react-dom/client":"react");l.length>0&&s.push(`${r.name}: Docker tsconfig does not allow synthetic default imports, but src/main.tsx default-imports ${l.join(" and ")}`)}}}return s.length===0?{name:"Frontend Docker Preflight",didPass:!0,message:`${y(t.length,"frontend")} ${t.length===1?"has":"have"} compatible Docker build/routing metadata`}:{name:"Frontend Docker Preflight",didPass:!1,message:`Frontend Docker metadata has issues that will make \`flisk up\` fail or route 404:
56
+ ${s.join(`
57
+ `)}`,fix:'Regenerate with a current Flisk (`flisk config regenerate`) and ensure frontend nginx runtimes route to port 80. For React entrypoints, prefer named imports: `import { StrictMode } from "react"` and `import { createRoot } from "react-dom/client"`.'}}var Gn=90,Kn=80;function Jn(e){return e.split(`
58
+ `).map(t=>t.trim()).filter(Boolean).map(t=>{let[s,r,n]=t.split(",");return{name:s??"unknown",memPct:Number.parseFloat((r??"").replace(/%/g,"")),cpuPct:Number.parseFloat((n??"").replace(/%/g,""))}}).filter(t=>Number.isFinite(t.memPct)&&Number.isFinite(t.cpuPct))}var qn=Object.assign(function(){let t;try{t=kn(D("docker")??"docker",["stats","--no-stream","--format","{{.Name}},{{.MemPerc}},{{.CPUPerc}}"],{stdio:"pipe",encoding:"utf-8",timeout:C,windowsHide:process.platform==="win32"})}catch(o){return{name:"Container Resource Health",didPass:!0,isSkipped:!0,message:rt(o)?"Docker not responding - skipped container resource check":"Docker not running - skipped container resource check"}}let s=Jn(t);if(s.length===0)return{name:"Container Resource Health",didPass:!0,isSkipped:!0,message:"No running containers to check"};let r=s.filter(o=>o.memPct>=Gn&&o.cpuPct>=Kn);if(r.length===0)return{name:"Container Resource Health",didPass:!0,message:`${y(s.length,"container")} within normal resource usage`};let n=r.map(o=>`${o.name} (mem ${o.memPct.toFixed(0)}%, cpu ${o.cpuPct.toFixed(0)}%)`).join(`
59
+ `);return{name:"Container Resource Health",didPass:!1,message:`${y(r.length,"container")} pinned near its memory limit while burning CPU -- likely stuck in a boot-time retry/fetch loop, not a normal crash:
60
+ ${n}`,fix:"Check the container's boot logs for a network fetch loop (e.g. `bunx <pkg>` re-downloading a CLI on every start instead of using one already installed): docker logs <container>. If a Prisma auto-migration is the cause, set AUTO_MIGRATE=false in the project's .env as a temporary unblock."}},{requiresContainerRuntime:!0,checkName:"Container Resource Health"}),zn=5e3;async function Xn(e,t){let s=h()??process.cwd(),r=ye(x(s),t);return r.length===0?{name:"Service Health",didPass:!0,isSkipped:!0,message:"No routable services to ping"}:_e(await ee(r,e))}async function Yn(e,t){let s=h()??process.cwd(),r=x(s),n=kt(gt(r,t),{timeoutSeconds:Math.ceil(e/1e3)});return n.length===0?Oe([]):(await ee(ye(r,t),e)).some(i=>i.status!==void 0)?Oe(await yt(n,St(s))):{name:"Service Smoke",didPass:!0,isSkipped:!0,message:"No services responded - skipped smoke checks"}}async function Zn(e,t){let s=h()??process.cwd(),r=x(s),n=ht(r,t);return n.length===0?{name:"Advertised Endpoints",didPass:!0,isSkipped:!0,message:"No routable services to check"}:es(await ee(n,e))}function Qn(e,t){if(e.length===0){console.log(`${g.green("\u2713")} Nothing to repair
61
+ `);return}for(let s of e){let r=s.status==="planned"?`${g.cyan("would run")}`:s.status==="applied"?g.green("ran"):s.status==="failed"?g.red("failed"):g.gray("skipped"),n=s.status==="planned"?"":` (${s.reason})`;console.log(`${r}: ${s.command}${n}`)}t&&console.log(g.gray(`
62
+ Dry run: nothing was changed.`)),console.log("")}function Xe(e){for(let t of Rs(e))!t.didPass&&!t.isSkipped&&!t.isWarning?(console.log(`${g.red("\u2717")} ${t.message}`),t.fix&&console.log(`${g.blue("\u2139")} Fix: ${t.fix}`)):t.isWarning?(console.log(`${g.yellow("\u26A0")} ${g.yellow(t.message)}`),t.fix&&console.log(`${g.blue("\u2139")} Fix: ${t.fix}`)):t.isSkipped?(console.log(`${g.gray("\u25CB")} ${g.gray(t.message)}`),t.fix&&console.log(`${g.blue("\u2139")} ${t.fix}`)):console.log(`${g.green("\u2713")} ${t.message}`)}function eo(e,t,s,r={},n){let o=n??[...e,...t];r.all?(console.log(g.bold("Machine")),Xe(e),console.log(`
63
+ ${g.bold("Project")}`),s?t.length>0&&Xe(t):console.log(`${g.gray("\u25CB")} ${g.gray("Not in a Flisk project, so project checks were skipped")}`),console.log(""),console.log($n(o))):(Xe(o),console.log(""),o.every(a=>a.didPass||a.isSkipped||a.isWarning)&&!s&&(console.log(`${g.gray("\u25CB")} ${g.gray("Not in a Flisk project, so project checks were skipped")}`),console.log("")))}var to=new yn("doctor").description("Check environment readiness for Flisk").option("--all","Run every check and show Machine and Project sections with a summary",!1).option("--json","Output a versioned JSON readiness report",!1).exitOverride(e=>{e.exitCode===0&&process.exit(0),to.opts().json&&console.log(JSON.stringify(A([],!1,[{code:"USAGE",message:e.message}]))),process.exit(2)}).option("--no-ping","Skip pinging running services' /health endpoints").option("--strict","Fail warnings such as a WSL project under /mnt/c").option("--fix","Repair this project's unused Docker networks and leftover doctor probes",!1).option("--dry-run","With --fix: list the repairs without changing anything",!1).option("--scope <scope>","With --fix: project (default) or machine, which also removes unused networks of other Flisk projects","project").option("-y, --yes","With --fix: apply every repair without asking",!1).option("--ping-timeout <ms>","Per-service ping timeout in milliseconds",String(zn)).action(async e=>{let t=Number(e.pingTimeout);if(!/^\d+$/.test(e.pingTimeout)||!Number.isSafeInteger(t)||t<=0){let p="--ping-timeout must be a positive integer number of milliseconds";e.json&&console.log(JSON.stringify(A([],!1,[{code:"USAGE",message:p}]))),console.error(p),process.exit(2);return}let s=!["project","machine"].includes(e.scope),r=e.scope!=="project"&&!e.fix;if(s||r||(e.dryRun||e.yes)&&!e.fix){let p=s?`--scope must be project or machine, got "${e.scope}"`:r?"--scope only applies with --fix":`${e.dryRun?"--dry-run":"--yes"} only applies with --fix`;e.json&&console.log(JSON.stringify(A([],!1,[{code:"USAGE",message:p}]))),console.error(p),process.exit(2);return}e.json||console.log("Flisk doctor checks your local Docker + Tilt development environment; it does not check cluster deployments.");let n=Ht();if(process.platform==="win32"){if(e.json){let p=null;try{p=await xt()}catch{}console.log(JSON.stringify(A([{name:"Native Windows support",didPass:!1,message:Es,fix:"Use WSL2 Ubuntu with Docker Desktop integration. Guide: docs/wsl2.md"}],!!h(),[],p,{...n,canUp:!1})))}console.error(Es),process.exit(1);return}if(n.kind==="webcontainer"){let p=A([{name:"Host",didPass:!1,message:xe,fix:`Use a machine with Docker. Guide: ${ne}`}],!!h(),[],void 0,n);e.json&&console.log(JSON.stringify(p)),console.error(xe),process.exit(1);return}e.json||(console.log(`
64
+ ${g.bold("\u{1F50D} Flisk Doctor")}
65
+ `),console.log(`Checking environment...
66
+ `));let o=h(),i;if(e.fix){try{let p;if(o)try{p=vt(o).project.name}catch{}let b=o?gs(o):[],fe=await ps(p,void 0,{tiltPids:b,machine:e.scope==="machine"});i=await fs(fe,{dryRun:e.dryRun,confirm:e.yes||!e.json&&process.stdin.isTTY?async $=>e.yes||await Tt({message:`Run \`${$.command}\` (${$.check}, ${$.reason})?`}):void 0})}catch(p){i=[{id:"plan-fixes",check:"Docker networks",scope:"project",command:"docker network ls",status:"failed",reason:`could not list Docker networks: ${(p instanceof Error?p.message:String(p)).split(/\r?\n/)[0]}`}]}e.json||Qn(i,e.dryRun)}let a,l;try{a=await te(o??process.cwd())}catch(p){a=null,l=p instanceof Error?p.message:String(p)}let c=[()=>a?{name:"Host Ports",didPass:!0,message:`HTTP ${a.ingressHttp}, HTTPS ${a.ingressHttps}, Postgres ${a.postgres}`}:{name:"Host Ports",didPass:!1,message:l??"Could not select host ports",fix:"Set TDK_HTTP_PORT, TDK_HTTPS_PORT, or TDK_POSTGRES_PORT to available host ports."},()=>a?Re($s,void 0,[a.ingressHttp,a.ingressHttps]):{name:"Ingress Ports",didPass:!1,message:"Ingress port plan is unavailable"},nt,()=>is(),E("Docker OS",it),ot,E("Docker Compose",z),E("Docker Versions",X),Cn,()=>Pn(h()??process.cwd(),e.strict),()=>wn(process.env,e.strict),E("Docker Network Pool",()=>It()),()=>hs()],d=[()=>On(),_n,Hn,ws,Mn,()=>ss(),Un,Vn,jn,()=>Et(),()=>se(),()=>Ve(),()=>Ge(),()=>Ke(),()=>Je(void 0,e.strict),()=>Nt(),()=>Ct(),()=>_t(),()=>Rt(),()=>re(),()=>Dt(),()=>jt(),()=>Ot(),()=>At(),Rn,async()=>{let p=h()??process.cwd(),b=Ne(p)?await bt(p):!0;return Ce($s,p,void 0,b)},E("Container Resource Health",qn),E("Crash-looping containers",()=>{let p=h()??process.cwd(),b=[];try{b=Se(p)}catch{return{name:"Crash-looping containers",didPass:!0,isSkipped:!0,message:"Could not read .env - skipped crash-loop check"}}return ns(G(),b)}),E("Tilt Resources",je)];if(e.ping){let p=o?wt(o,a?.ingressHttp):a?.ingressHttp;d.push(E("Service Health",()=>Xn(t,p))),d.push(E("Service Smoke",()=>Yn(t,p))),d.push(E("Advertised Endpoints",()=>Zn(t,p)))}let u=!!o,m=[],P=[],S,k=[];if(e.json||!e.all){let p=await Ss(c,u?d:[]);S=p.checks,k=p.errors}else{let p=async($,Ks=!1)=>{let Js=$.map(Z=>{let me=async()=>{try{return await Z()}catch(he){return k.push({code:"INTERNAL",message:he instanceof Error?he.message:String(he)}),{name:Z.checkName??"Doctor check",didPass:!1,isSkipped:!1,message:"Check failed with an internal error"}}};return me.requiresContainerRuntime=Z.requiresContainerRuntime,me.checkName=Z.checkName,me});return Tn(Js,{all:!!e.all,containerRuntimeFailed:Ks})};m=await p(c);let b=m.some($=>!$.didPass&&!$.isSkipped&&!$.isWarning),fe=m.some($=>$.name===v&&!$.didPass);u&&(e.all||!b)&&(P=await p(d,fe)),S=[...m,...P]}if(Mt(n.kind))for(let p of S)p.name===v&&!p.didPass&&(p.fix=Ft);let f=A(Rs(S),u,k,a,n);i&&(f.data.actions=i),i&&ms(i)&&(f.data.ready=!1);let M=ys(f),W=f.data.ready;if(e.json){let p;try{p=Se(h()??process.cwd())}catch(b){if(!(b instanceof ft))throw b;console.log(JSON.stringify(A([],u,[...k,{code:"ENV_UNREADABLE",message:b.message}]))),console.error(b.message),process.exit(1)}console.log(JSON.stringify(mt(f,p)));for(let b of k)console.error(Q(b.message,p));M!==0&&process.exit(M);return}for(let p of k)console.error(`${g.red("\u2717")} Doctor check failed: ${p.message}`);eo(m,P,u,e,S);let at=bn(u,W);console.log(`${W?g.green(g.bold("\u2713")):g.red(g.bold("\u2717"))} ${at}`),M!==0&&process.exit(M)});function js(e){return ue(e,H)}var Y=`${H[0]}.${H[1]}+`;import{execFileSync as As}from"node:child_process";import{accessSync as Is,constants as Fs,readdirSync as so}from"node:fs";import{delimiter as ro,join as U}from"node:path";var _="flisk",Hs="@flisk-sh/flisk",no="https://github.com/flisk-sh/flisk",Ms="@tdk-landscape/tdk-cli-core";function Ls(e=_,t=process.env){let s=t.PATH??t.Path??"";for(let r of s.split(ro)){if(!r)continue;let n=[U(r,e),U(r,`${e}.cmd`),U(r,`${e}.exe`)];for(let o of n)try{return Is(o,Fs.X_OK),!0}catch{}}return!1}function oo(e=process.env){let t=e.HOME??e.USERPROFILE??"";if(!t)return"flisk-workspace";let s=U(t,"Documents");try{return Is(s,Fs.F_OK),U(s,"flisk")}catch{return U(t,"flisk")}}function io(e){try{return so(e).every(t=>t.startsWith("."))}catch{return!0}}function Bs(e=process.env,t=process.cwd()){if(!Ls(_,e))return`The \`${_}\` command is not on PATH (this run is not a global install). Install it with \`npm install -g ${Hs}\`, then run \`${_} project --yes\`. If npm reports EEXIST for \`flisk\`, the old package still owns that command: run \`npm uninstall -g ${Ms}\` first. The current release is published from ${no}.`;if(io(t))return`Next: ${_} project --yes`;let s=oo(e);return`Next: mkdir -p ${s} && cd ${s} && ${_} project --yes
67
+ (${_} project writes .tdk/ and the master configs into the current folder, so it starts clean in a folder of its own. Or clone a starter: ${_} project example)`}function wa(e=process.env){if(Ls(_,e))return"present";let t=e.npm_execpath?.includes("bun")?"bun":"npm",s=t==="bun"?"bun remove -g":"npm uninstall -g";try{As(s,[Ms],{stdio:"ignore",timeout:12e4})}catch{}try{return As(t,["install","-g",Hs],{stdio:"inherit",timeout:3e5}),"installed"}catch{return"failed"}}var lo=["docker","compose","tilt"],Vs=["node","bun","ports"],Gs=["node",...lo,"bun","ports"];function uo(e,t){return{id:e,ok:t.didPass||!!t.isSkipped,message:t.message,fix:t.fix}}function Ws(e){let[t=0,s=0]=String(e).split(".").map(r=>Number.parseInt(r,10));return!Number.isFinite(t)||!Number.isFinite(s)||t<22||t===22&&s<12}async function pe(e,t,s){try{return uo(e,await t())}catch{return{id:e,ok:!1,message:s}}}function po(e){return e.isSkipped?{...e,didPass:!1,isSkipped:!1,message:`Could not verify Docker Engine/Compose minimum versions: ${e.message}`,fix:e.fix??"Start Docker and ensure Docker Engine 25+ and Compose 2.20.2+ are installed."}:e}function fo(e){try{return x(e).some(t=>t.config?.featuresEnabled?.includes("prisma")??!1)}catch{return!1}}async function mo(e={}){let t=e.cwd??process.cwd(),s=e.scope??"runtime",r=h(t),n=!!r,o=[],i=process.versions.node;o.push({id:"node",ok:!Ws(i),message:i,fix:Ws(i)?`Install Node.js 22.12+ (current: ${i}). https://nodejs.org`:void 0}),s==="runtime"&&(o.push(await pe("docker",nt,"Docker daemon is not running")),o.push(await pe("compose",async()=>{let[f,I]=await Promise.all([z(),X()]);return f.didPass?po(I):f},"Docker Engine or Compose version check failed")),o.push(await pe("tilt",ot,"Tilt CLI not found")));let a=r?oe(x(r)):!0,l=!1,c=a?D("bun"):void 0;if(c)try{let f=Us(c,["--version"],{encoding:"utf-8",timeout:3e3}).trim();l=js(f)}catch{}o.push(a?{id:"bun",ok:l,message:l?`Bun ${Y} is available on PATH`:`Bun ${Y} not found on PATH`,fix:l?void 0:"curl -fsSL https://bun.sh/install | bash"}:{id:"bun",ok:!0,message:"Bun not needed: no generated JS services"});try{let f=await te(r??t,s==="scaffold"?{inspectDocker:!1}:{});o.push({id:"ports",ok:!0,message:`HTTP ${f.ingressHttp}, HTTPS ${f.ingressHttps}, Postgres ${f.postgres}`})}catch(f){o.push({id:"ports",ok:!1,message:f instanceof Error?f.message:String(f),fix:"Set TDK_HTTP_PORT, TDK_HTTPS_PORT, or TDK_POSTGRES_PORT to available host ports."})}if(r&&s==="runtime"){let f=await pe("nats",()=>re(r),"NATS broker check failed");if(f.ok||o.push(f),fo(r)){let I=!1;try{let M=JSON.parse(ao(co(r,".tdk",j),"utf-8")),W=String(M?.project?.name??"").replace(/-/g,"_");W&&(I=Us(D("docker")??"docker",["ps","--filter",`label=com.docker.compose.project=${W}`,"--filter","label=com.docker.compose.service=postgres","--filter","health=healthy","--format","{{.Names}}"],{encoding:"utf-8",timeout:3e3}).trim().length>0)}catch{}I||o.push({id:"prisma",ok:!1,message:"A Prisma resource is configured, but the project Postgres service is not healthy",fix:"Run `flisk up` to start the bundled Postgres, then check `flisk doctor`."})}}let d=o.filter(f=>!f.ok).slice(0,8),u=s==="scaffold"?Vs:Gs,m=d.some(f=>u.includes(f.id)),P=d.some(f=>f.id==="bun"),S=d.length?m?s==="scaffold"?P?`Project setup blocked. Bun ${Y} must be installed on PATH.
68
+ Fix the machine checks below.`:"Project setup blocked. Fix the machine checks below.":P?`Cold start blocked. Bun ${Y} must be installed on PATH.
69
+ Prisma/NATS configuration is project-specific and generated after \`flisk project\`. Fix the machine checks below.`:"Cold start blocked. Bun/Prisma/NATS are not the first failure.\nThey are generated after `flisk project`. Fix the machine checks below.":"Machine is ready. Project wiring is not.":"",k=d.length?"":n?"Environment ready for Flisk":`This machine is ready for Flisk.
70
+ ${Bs()}`;return{ok:d.length===0,inProject:n,items:d,header:S,footer:k}}function ho(e,t={}){let s=[e.header];for(let r of e.items)s.push(`FAIL ${r.id.padEnd(7)} ${r.message}`),r.fix&&s.push(` ${r.fix}`);return e.footer&&(t.includeSuccessFooter??!0)&&s.push(e.footer),s.filter(Boolean).join(`
71
+ `)}async function Fa(e={}){let t=e.scope??"runtime",s=await mo({scope:t}),r=t==="scaffold"?Vs:Gs;s.items.some(o=>r.includes(o.id))&&(console.error(ho(s)),process.exit(1))}function Ha(e=D){return{hasDockerCli:!!e("docker"),hasTiltCli:!!e("tilt")}}function Ma(e){let t=[];return e.hasDockerCli||t.push(`Docker is not installed. ${N.dockerMissing}`),e.hasTiltCli||t.push(`Tilt is not installed. ${N.tiltMissing}`),t.push("Docker and Tilt are only needed to start the stack: start them, then run `flisk doctor` (readiness) and `flisk up`."),t}export{rn as a,nn as b,fi as c,xe as d,Ft as e,Ht as f,Mt as g,we as h,ie as i,bo as j,Vt as k,Kt as l,$e as m,De as n,Ee as o,Jo as p,qo as q,zo as r,Me as s,Hr as t,Br as u,ei as v,Ci as w,Ri as x,Ts as y,to as z,Ms as A,wa as B,lo as C,Vs as D,Gs as E,Ws as F,po as G,mo as H,ho as I,Fa as J,Ha as K,Ma as L};