@flisk-sh/flisk 1.3.199 → 1.3.201
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/dist/cli.js +39 -16
- package/cli/dist/commands/open.js +16 -3
- package/cli/dist/utils/one-command.js +34 -0
- package/cli/dist/utils/project-templates.js +14 -0
- package/cli/dist/utils/stack-progress.js +66 -0
- package/cli/dist/utils/up-readiness.js +9 -2
- package/cli/dist-bundle/chunk-5ZSZ4WCI.js +2 -0
- package/cli/dist-bundle/chunk-GCFNSFW7.js +52 -0
- package/cli/dist-bundle/chunk-GNNN2WCF.js +4 -0
- package/cli/dist-bundle/{chunk-3SPYAO2E.js → chunk-KXKBWLTE.js} +1 -1
- package/cli/dist-bundle/{chunk-GW7N23FV.js → chunk-LYOSHFYU.js} +1 -1
- package/cli/dist-bundle/chunk-M6S25PIS.js +366 -0
- package/cli/dist-bundle/chunk-NC6KOYDN.js +2 -0
- package/cli/dist-bundle/chunk-R67UHACQ.js +24 -0
- package/cli/dist-bundle/chunk-S5EXKK7H.js +34 -0
- package/cli/dist-bundle/cli.js +203 -203
- package/cli/dist-bundle/cold-preflight-6Z4RBFKV.js +2 -0
- package/cli/dist-bundle/one-command-YP6L6MMM.js +5 -0
- package/cli/dist-bundle/open-SMKISRHH.js +2 -0
- package/cli/dist-bundle/project-WEC2GSR7.js +2 -0
- package/cli/dist-bundle/stack-progress-UVREHNNB.js +2 -0
- package/cli/dist-bundle/tilt-port-owner-67UVQJTX.js +2 -0
- package/cli/package.json +1 -1
- package/package.json +1 -1
- package/cli/dist-bundle/chunk-6357RMGA.js +0 -4
- package/cli/dist-bundle/chunk-7IWOM6M2.js +0 -5
- package/cli/dist-bundle/chunk-HSNRMF4B.js +0 -435
- package/cli/dist-bundle/chunk-KGKKDD4K.js +0 -2
- package/cli/dist-bundle/chunk-WMK2PS63.js +0 -34
- package/cli/dist-bundle/cold-preflight-WR2M53Z6.js +0 -2
- package/cli/dist-bundle/one-command-7OW747ML.js +0 -4
- package/cli/dist-bundle/open-635MSZHV.js +0 -2
- package/cli/dist-bundle/project-R7P6QBF4.js +0 -2
- package/cli/dist-bundle/project-templates-BHURA3LT.js +0 -2
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
|
|
2
|
+
import{b as N}from"./chunk-5ZSZ4WCI.js";import{G as O,O as m,Ta as f,c as $,d as C,fa as T,g as U,h as E,k as F,l as P,s as l,x as M}from"./chunk-LYOSHFYU.js";import{execFileSync as de,execSync as ue}from"node:child_process";import{existsSync as R,readdirSync as V,readFileSync as y}from"node:fs";import{basename as le,dirname as me,join as u,relative as k}from"node:path";function Oe(t){return I(t,!1)}function Ne(t){return I(t,!0)}function I(t,e){return`import { Hono } from 'hono';
|
|
3
|
+
${e?`import { sql } from 'drizzle-orm';
|
|
4
|
+
import { db } from './db.js';
|
|
5
|
+
`:""}
|
|
6
|
+
const app = new Hono();
|
|
7
|
+
|
|
8
|
+
// Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
|
|
9
|
+
app.get('/health', (c) => {
|
|
10
|
+
return c.json({ status: 'ok', service: '${t}' });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
app.get('/health/live', (c) => {
|
|
14
|
+
return c.json({ status: 'alive', timestamp: Date.now() });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
app.get('/health/ready', async (c) => {
|
|
18
|
+
const dependencies: Record<string, string> = {};
|
|
19
|
+
let allReady = true;
|
|
20
|
+
|
|
21
|
+
// Add dependency checks here (database, cache, etc.)
|
|
22
|
+
// Mark allReady = false if any dependency is unhealthy
|
|
23
|
+
|
|
24
|
+
const status = allReady ? 'ready' : 'not_ready';
|
|
25
|
+
return c.json({ status, dependencies }, allReady ? 200 : 503);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
${e?`// Database check: runs a trivial query through the Drizzle client (src/db.ts).
|
|
29
|
+
app.get('/health/db', async (c) => {
|
|
30
|
+
try {
|
|
31
|
+
await db.execute(sql\`select 1\`);
|
|
32
|
+
return c.json({ status: 'ok', database: 'up' });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Database health check failed:', error);
|
|
35
|
+
return c.json({ status: 'error', database: 'down' }, 503);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
`:""}app.get('/', (c) => {
|
|
40
|
+
return c.json({
|
|
41
|
+
service: '${t}',
|
|
42
|
+
version: '1.0.0',
|
|
43
|
+
endpoints: ['/health', '/health/live', '/health/ready'${e?", '/health/db'":""}]
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const port = process.env.PORT || 3000;
|
|
48
|
+
console.log('\\n\u{1F680} ${t} running on http://localhost:' + port);
|
|
49
|
+
console.log('\u{1F4CA} Health check: http://localhost:' + port + '/health\\n');
|
|
50
|
+
|
|
51
|
+
export default {
|
|
52
|
+
port,
|
|
53
|
+
fetch: app.fetch,
|
|
54
|
+
};
|
|
55
|
+
`}var G={id:"bun",label:"Bun + Hono",installHint:"bun install"};var se="go run .",L={id:"go",label:"Go 1.23 (net/http)",devCommand:se,watch:["**/*.go","go.mod","go.sum"],installHint:"go mod tidy # optional: Docker builds the image for you",createFiles(t){return[{filename:"go.mod",content:`module ${t}
|
|
56
|
+
|
|
57
|
+
go 1.23
|
|
58
|
+
`,description:"Generating go.mod",emoji:"\u{1F4E6}"},{filename:"main.go",content:`package main
|
|
59
|
+
|
|
60
|
+
import (
|
|
61
|
+
"encoding/json"
|
|
62
|
+
"log"
|
|
63
|
+
"net/http"
|
|
64
|
+
"os"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
func writeJSON(w http.ResponseWriter, status int, body any) {
|
|
68
|
+
w.Header().Set("Content-Type", "application/json")
|
|
69
|
+
w.WriteHeader(status)
|
|
70
|
+
_ = json.NewEncoder(w).Encode(body)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func newMux() *http.ServeMux {
|
|
74
|
+
mux := http.NewServeMux()
|
|
75
|
+
|
|
76
|
+
// Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
|
|
77
|
+
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
|
|
78
|
+
writeJSON(w, http.StatusOK, map[string]string{"status": "ok", "service": "${t}"})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
|
|
82
|
+
writeJSON(w, http.StatusOK, map[string]any{
|
|
83
|
+
"service": "${t}",
|
|
84
|
+
"version": "1.0.0",
|
|
85
|
+
"endpoints": []string{"/health"},
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
return mux
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func main() {
|
|
93
|
+
port := os.Getenv("PORT")
|
|
94
|
+
if port == "" {
|
|
95
|
+
port = "3000"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ":" + port listens on every interface, which Traefik needs inside the container network.
|
|
99
|
+
log.Printf("${t} listening on :%s", port)
|
|
100
|
+
log.Fatal(http.ListenAndServe(":"+port, newMux()))
|
|
101
|
+
}
|
|
102
|
+
`,description:"Generating backend source",emoji:"\u{1F4BB}"},{filename:"main_test.go",content:`package main
|
|
103
|
+
|
|
104
|
+
import (
|
|
105
|
+
"encoding/json"
|
|
106
|
+
"net/http"
|
|
107
|
+
"net/http/httptest"
|
|
108
|
+
"testing"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
func TestHealth(t *testing.T) {
|
|
112
|
+
rec := httptest.NewRecorder()
|
|
113
|
+
newMux().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/health", nil))
|
|
114
|
+
|
|
115
|
+
if rec.Code != http.StatusOK {
|
|
116
|
+
t.Fatalf("GET /health = %d, want %d", rec.Code, http.StatusOK)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
func TestRoot(t *testing.T) {
|
|
121
|
+
rec := httptest.NewRecorder()
|
|
122
|
+
newMux().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
|
|
123
|
+
|
|
124
|
+
if rec.Code != http.StatusOK {
|
|
125
|
+
t.Fatalf("GET / = %d, want %d", rec.Code, http.StatusOK)
|
|
126
|
+
}
|
|
127
|
+
if got := rec.Header().Get("Content-Type"); got != "application/json" {
|
|
128
|
+
t.Fatalf("GET / Content-Type = %q, want %q", got, "application/json")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
var body map[string]any
|
|
132
|
+
if err := json.NewDecoder(rec.Body).Decode(&body); err != nil {
|
|
133
|
+
t.Fatalf("GET / body is not JSON: %v", err)
|
|
134
|
+
}
|
|
135
|
+
if body["service"] != "${t}" {
|
|
136
|
+
t.Errorf("service = %v, want %q", body["service"], "${t}")
|
|
137
|
+
}
|
|
138
|
+
if body["version"] != "1.0.0" {
|
|
139
|
+
t.Errorf("version = %v, want %q", body["version"], "1.0.0")
|
|
140
|
+
}
|
|
141
|
+
endpoints, ok := body["endpoints"].([]any)
|
|
142
|
+
if !ok || len(endpoints) != 1 || endpoints[0] != "/health" {
|
|
143
|
+
t.Errorf("endpoints = %v, want [/health]", body["endpoints"])
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
`,description:"Generating smoke test",emoji:"\u{1F9EA}"}]}};var ne="node --watch src/index.js",B={id:"node",label:"Node.js 22",devCommand:ne,watch:["src/**/*"],installHint:"npm install # optional: Docker builds the image for you",createFiles(t){return[{filename:"package.json",content:`{
|
|
147
|
+
"name": "${t}",
|
|
148
|
+
"version": "1.0.0",
|
|
149
|
+
"type": "module",
|
|
150
|
+
"scripts": {
|
|
151
|
+
"dev": "node --watch src/index.js",
|
|
152
|
+
"start": "node src/index.js",
|
|
153
|
+
"test": "node --test"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
`,description:"Generating package.json",emoji:"\u{1F4E6}"},{filename:"package-lock.json",content:`{
|
|
157
|
+
"name": "${t}",
|
|
158
|
+
"version": "1.0.0",
|
|
159
|
+
"lockfileVersion": 3,
|
|
160
|
+
"requires": true,
|
|
161
|
+
"packages": {
|
|
162
|
+
"": {
|
|
163
|
+
"name": "${t}",
|
|
164
|
+
"version": "1.0.0"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
`,description:"Generating package-lock.json",emoji:"\u{1F512}"},{filename:"src/index.js",content:`import http from "node:http";
|
|
169
|
+
|
|
170
|
+
const name = "${t}";
|
|
171
|
+
const port = Number(process.env.PORT) || 3000;
|
|
172
|
+
|
|
173
|
+
function sendJson(res, status, data) {
|
|
174
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
175
|
+
res.end(JSON.stringify(data));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export const server = http.createServer((req, res) => {
|
|
179
|
+
const url = new URL(req.url, \`http://\${req.headers.host || "localhost"}\`);
|
|
180
|
+
|
|
181
|
+
// Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
|
|
182
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
183
|
+
return sendJson(res, 200, { status: "ok", service: name });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (req.method === "GET" && url.pathname === "/") {
|
|
187
|
+
return sendJson(res, 200, {
|
|
188
|
+
service: name,
|
|
189
|
+
version: "1.0.0",
|
|
190
|
+
endpoints: ["/health"],
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return sendJson(res, 404, { error: "Not Found" });
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (process.env.NODE_ENV !== "test") {
|
|
198
|
+
// "0.0.0.0" listens on every interface, which Traefik needs inside the container network.
|
|
199
|
+
server.listen(port, "0.0.0.0", () => {
|
|
200
|
+
console.log(\`\\n\u{1F680} \${name} running on http://localhost:\${port}\`);
|
|
201
|
+
console.log(\`\u{1F4CA} Health check: http://localhost:\${port}/health\\n\`);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
`,description:"Generating backend source",emoji:"\u{1F4BB}"},{filename:"tests/health.test.js",content:`import assert from "node:assert/strict";
|
|
205
|
+
import { after, before, describe, it } from "node:test";
|
|
206
|
+
import { server } from "../src/index.js";
|
|
207
|
+
|
|
208
|
+
describe("health check", () => {
|
|
209
|
+
let baseUrl;
|
|
210
|
+
|
|
211
|
+
before((_, done) => {
|
|
212
|
+
server.listen(0, "127.0.0.1", () => {
|
|
213
|
+
const address = server.address();
|
|
214
|
+
baseUrl = \`http://127.0.0.1:\${address.port}\`;
|
|
215
|
+
done();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
after((_, done) => {
|
|
220
|
+
server.close(done);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("GET /health returns 200 with ok status", async () => {
|
|
224
|
+
const res = await fetch(\`\${baseUrl}/health\`);
|
|
225
|
+
assert.equal(res.status, 200);
|
|
226
|
+
const body = await res.json();
|
|
227
|
+
assert.deepEqual(body, { status: "ok", service: "${t}" });
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
`,description:"Generating smoke test",emoji:"\u{1F9EA}"}]}};var re="uvicorn main:app --app-dir src --host 0.0.0.0 --port ${PORT:-4000} --reload --reload-dir src",q={id:"python",label:"Python 3.12 + FastAPI",devCommand:re,watch:["src/**/*"],installHint:'pip install -e ".[dev]" # optional: Docker builds the image for you',createFiles(t){return[{filename:"pyproject.toml",content:`[project]
|
|
231
|
+
name = "${t}"
|
|
232
|
+
version = "0.0.1"
|
|
233
|
+
requires-python = ">=3.12"
|
|
234
|
+
dependencies = [
|
|
235
|
+
"fastapi==0.115.6",
|
|
236
|
+
"uvicorn[standard]==0.34.0",
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
[project.optional-dependencies]
|
|
240
|
+
dev = [
|
|
241
|
+
"pytest==9.0.3",
|
|
242
|
+
"httpx==0.28.1",
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
[build-system]
|
|
246
|
+
requires = ["setuptools>=68"]
|
|
247
|
+
build-backend = "setuptools.build_meta"
|
|
248
|
+
|
|
249
|
+
# Only the dependencies are installed into the image; the source is synced for live reload.
|
|
250
|
+
[tool.setuptools]
|
|
251
|
+
py-modules = []
|
|
252
|
+
|
|
253
|
+
[tool.pytest.ini_options]
|
|
254
|
+
pythonpath = ["src"]
|
|
255
|
+
testpaths = ["tests"]
|
|
256
|
+
`,description:"Generating pyproject.toml",emoji:"\u{1F4E6}"},{filename:"src/main.py",content:`import os
|
|
257
|
+
|
|
258
|
+
from fastapi import FastAPI
|
|
259
|
+
|
|
260
|
+
app = FastAPI(title="${t}")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@app.get("/health")
|
|
264
|
+
def health() -> dict[str, str]:
|
|
265
|
+
return {"status": "ok", "service": "${t}"}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@app.get("/")
|
|
269
|
+
def root() -> dict[str, object]:
|
|
270
|
+
return {"service": "${t}", "version": "1.0.0", "endpoints": ["/health"]}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
if __name__ == "__main__":
|
|
274
|
+
import uvicorn
|
|
275
|
+
|
|
276
|
+
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "3000")))
|
|
277
|
+
`,description:"Generating backend source",emoji:"\u{1F4BB}"},{filename:"tests/test_health.py",content:`from fastapi.testclient import TestClient
|
|
278
|
+
|
|
279
|
+
from main import app
|
|
280
|
+
|
|
281
|
+
client = TestClient(app)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def test_health() -> None:
|
|
285
|
+
response = client.get("/health")
|
|
286
|
+
assert response.status_code == 200
|
|
287
|
+
assert response.json() == {"status": "ok", "service": "${t}"}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def test_root() -> None:
|
|
291
|
+
response = client.get("/")
|
|
292
|
+
assert response.status_code == 200
|
|
293
|
+
assert response.json() == {"service": "${t}", "version": "1.0.0", "endpoints": ["/health"]}
|
|
294
|
+
`,description:"Generating test file",emoji:"\u{1F9EA}"}]}};var oe="cargo run",H={id:"rust",label:"Rust (axum)",devCommand:oe,watch:["src/**/*.rs","Cargo.toml","Cargo.lock"],installHint:"cargo build # optional: Docker builds the image for you",createFiles(t){return[{filename:"Cargo.toml",content:`[package]
|
|
295
|
+
name = "${t}"
|
|
296
|
+
version = "0.1.0"
|
|
297
|
+
edition = "2021"
|
|
298
|
+
|
|
299
|
+
# The image copies the release binary by this fixed name.
|
|
300
|
+
[[bin]]
|
|
301
|
+
name = "app"
|
|
302
|
+
path = "src/main.rs"
|
|
303
|
+
|
|
304
|
+
[dependencies]
|
|
305
|
+
axum = "0.8"
|
|
306
|
+
serde_json = "1"
|
|
307
|
+
tokio = { version = "1", features = ["macros", "rt-multi-thread", "net"] }
|
|
308
|
+
`,description:"Generating Cargo.toml",emoji:"\u{1F4E6}"},{filename:"src/main.rs",content:`use axum::{routing::get, Json, Router};
|
|
309
|
+
use serde_json::{json, Value};
|
|
310
|
+
|
|
311
|
+
fn app() -> Router {
|
|
312
|
+
Router::new()
|
|
313
|
+
.route("/", get(root))
|
|
314
|
+
// Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
|
|
315
|
+
.route("/health", get(health))
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async fn root() -> Json<Value> {
|
|
319
|
+
Json(json!({ "service": "${t}", "version": "1.0.0", "endpoints": ["/health"] }))
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async fn health() -> Json<Value> {
|
|
323
|
+
Json(json!({ "status": "ok", "service": "${t}" }))
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
#[tokio::main]
|
|
327
|
+
async fn main() {
|
|
328
|
+
let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string());
|
|
329
|
+
|
|
330
|
+
// 0.0.0.0 listens on every interface, which Traefik needs inside the container network.
|
|
331
|
+
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}"))
|
|
332
|
+
.await
|
|
333
|
+
.expect("failed to bind the port");
|
|
334
|
+
println!("${t} listening on 0.0.0.0:{port}");
|
|
335
|
+
axum::serve(listener, app()).await.expect("server error");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
#[cfg(test)]
|
|
339
|
+
mod tests {
|
|
340
|
+
use super::*;
|
|
341
|
+
|
|
342
|
+
#[tokio::test]
|
|
343
|
+
async fn health_reports_ok() {
|
|
344
|
+
let Json(body) = health().await;
|
|
345
|
+
assert_eq!(body["status"], "ok");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
`,description:"Generating backend source and smoke test",emoji:"\u{1F4BB}"}]}};var J="bun",h={bun:G,python:q,go:L,rust:H,node:B};function ae(t){let e=(t??J).trim().toLowerCase(),n=Object.hasOwn(h,e)?h[e]:void 0;if(!n){let s=Object.keys(h).join(", ");throw new O(`Unknown backend language "${t}". Supported languages: ${s}.`,[`Use one of: ${s}`,`Omit --language to use ${J}`])}return n}function qe(t,e){if(t!=="backend"){if(e!==void 0)throw new O("--language can only be used with --type backend.",["Add --type backend, or drop --language for this resource type"]);return}return ae(e)}import{exec as ie,execFile as ce}from"node:child_process";import{promisify as W}from"node:util";var pe=W(ie),z=async(t,e)=>{if(process.platform==="win32"){let s=t.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)??[],[o,...r]=s.map(c=>c.startsWith('"')||c.startsWith("'")?c.slice(1,-1):c);if(!o)throw new Error("Command is empty");let a=M(o)??o,{stdout:i}=await W(ce)(a,r,{encoding:"utf-8",timeout:e,windowsHide:!0,shell:!1});return i}let{stdout:n}=await pe(t,{encoding:"utf-8",timeout:e});return n};function Ke(t){let e=t;return e?.code==="ETIMEDOUT"||e?.killed===!0}var x=1e4,fe=/\.(?:[cm]?[jt]sx?|vue)$/,he=/(?:\.test\.|\.spec\.|\/tests?\/|\/__tests__\/)/,ge=7,ke=new Set(["node_modules",E,"dist","tests","__tests__"]);function Y(t){try{let e=JSON.parse(y(u(t,".tdk",F),"utf-8"));return typeof e?.project?.name=="string"?e.project.name:void 0}catch{return}}function ye(t){try{let e=JSON.parse(y(t,"utf-8")).params;return e&&typeof e=="object"?e:{}}catch{return{}}}function A(t){try{let e=JSON.parse(y(u(t,P),"utf-8"));return e&&typeof e=="object"?e:{}}catch{return{}}}function K(t){if(typeof t!="string")return;let e=t.match(/(?:^|[^0-9])(\d+)(?:\.|$)/);return e?Number(e[1]):void 0}function be(t){let e=S(t);if(e===void 0)return["missing prisma/schema.prisma"];let n=e.replace(/\/\*[\s\S]*?\*\//g,"").replace(/^\s*\/\/.*$/gm,""),s=[],o=n.match(/datasource\s+\w+\s*\{([^}]*)\}/)?.[1]??"";return/provider\s*=\s*["']postgresql["']/.test(o)||s.push("schema provider is not postgresql"),/\b(?:url|directUrl)\s*=/.test(n)&&s.push("schema still declares url/directUrl"),s}function we(t){let e=u(t,E,"prisma.config.autogenerated.ts"),s=[u(t,"prisma.config.ts"),e].find(a=>R(a));if(!s)return["missing prisma.config.ts or generated Prisma config"];let r=(S(s)??"").match(/datasource\s*:\s*\{([^}]*)\}/)?.[1]??"";return/url\s*:\s*(?:env\(["']DATABASE_URL["']\)|process\.env\.DATABASE_URL)\s*,?/.test(r)?[]:['Prisma config datasource.url is not env("DATABASE_URL")']}function it(t=l()??process.cwd(),e=f(t)){let n=e.filter(r=>r.config?.featuresEnabled?.includes("prisma")),s=[],o=e.filter(r=>r.config?.appType==="migrator");for(let r of e){let a=r.config?.features;Array.isArray(a)&&a.includes("prisma")&&!r.config?.featuresEnabled?.includes("prisma")&&s.push(`${r.name}: use featuresEnabled, not features, for Prisma`)}if(n.length===0)return s.length===0?{name:"Prisma 7 consistency",didPass:!0,isSkipped:!0,message:"No resources enable Prisma; Prisma consistency checks were skipped"}:{name:"Prisma 7 consistency",didPass:!1,message:`${m(s.length,"Prisma consistency error")}:
|
|
349
|
+
${s.join(`
|
|
350
|
+
`)}`,fix:"Use featuresEnabled to opt into the validated Prisma 7 shape"};for(let r of n){let a=A(r.path).dependencies,c={...A(r.path).devDependencies??{},...a??{}},d=K(c.prisma),p=K(c["@prisma/client"]);if(d===void 0||p===void 0?s.push(`${r.name}: prisma and @prisma/client are required`):(d!==p||d!==ge)&&s.push(`${r.name}: prisma packages must both be major 7`),s.push(...be(u(r.path,"prisma","schema.prisma")).map(g=>`${r.name}: ${g}`)),s.push(...we(r.path).map(g=>`${r.name}: ${g}`)),(r.config?.appType??r.type)==="migrator")r.config?.dependsOn?.some(w=>w==="postgres"||w===C)||s.push(`${r.name}: migrator must depend on postgres or database-management`);else{r.config?.dependsOn?.some(_=>o.some(v=>v.name===_))||s.push(`${r.name}: API must depend on its Prisma migrator, which depends on postgres or database-management`);let w=A(r.path).scripts;Object.entries(w??{}).some(([_,v])=>ee.has(_)&&typeof v=="string"&&/prisma\s+migrate/.test(v))&&s.push(`${r.name}: API start script must not run prisma migrate`);let te=u(r.path,r.config?.dockerfile??"Dockerfile");j(te,!0)&&s.push(`${r.name}: API image/startup must not run Prisma migrations`)}}return n.length>0&&!N(t,e).willStart&&s.push("shared platform Postgres will not start for the Prisma resources"),s.length===0?{name:"Prisma 7 consistency",didPass:!0,message:"Prisma resources use the validated Prisma 7 shape"}:{name:"Prisma 7 consistency",didPass:!1,message:`${m(s.length,"Prisma consistency error")}:
|
|
351
|
+
${s.join(`
|
|
352
|
+
`)}`,fix:"Use Prisma 7 packages and config, put migrations in a migrator, and make the API depend on that migrator"}}function X(t){let e=new Map;for(let n of t){let s=n.config?.port;T(n.config?.appType)&&typeof s=="number"&&e.set(n.name,s)}return e}function ve(t){let e=t.config?.language;return typeof e=="string"&&Object.hasOwn(h,e)&&h[e].createFiles!==void 0}function $e(t){return(t.config?.appType??t.type)===$}function ct(t=l()??process.cwd()){let e=f(t).filter(n=>!$e(n)&&!ve(n)&&!R(u(n.path,P))).map(n=>n.name);return e.length===0?{name:"Resource package.json",didPass:!0,message:"Every resource has a package.json"}:{name:"Resource package.json",didPass:!1,message:`${m(e.length,"resource")} without a package.json: ${e.join(", ")}. The image build stops at "COPY .../package.json: not found"`,fix:"Add a package.json next to each service.json (copy one from a working resource), or delete the folder's service.json if it is not a service"}}function pt(t=l()??process.cwd(),e=f(t)){let n=new Map;for(let r of e){let a=n.get(r.name)??[];a.push(k(t,r.configPath)||r.configPath),n.set(r.name,a)}let s=[...n].filter(([,r])=>r.length>1);if(s.length===0)return{name:"Duplicate resource names",didPass:!0,message:"Every resource has a unique appName"};let o=s.map(([r,a])=>`"${r}" in ${a.join(" and ")}`);return{name:"Duplicate resource names",didPass:!1,message:`${m(s.length,"appName")} used by more than one resource:
|
|
353
|
+
${o.join(`
|
|
354
|
+
`)}`,fix:'Give each resource its own "appName" in its service.json. The name becomes the Compose service and the Traefik router, so two resources with one name collide'}}function Pe(t){let e=t.config?.appType;return T(e)?!0:e===$&&t.config?.exposeViaProxy!==!1}function dt(t=l()??process.cwd()){let e=new Map;for(let o of f(t)){let r=o.config?.port;if(typeof r!="number"||!Pe(o))continue;let a=e.get(r)??[];a.push(o.name),e.set(r,a)}let n=[...e].filter(([,o])=>o.length>1);if(n.length===0)return{name:"Duplicate resource ports",didPass:!0,message:"No two routed resources use the same port"};let s=n.map(([o,r])=>`port ${o}: ${r.join(", ")}`);return{name:"Duplicate resource ports",didPass:!1,isWarning:!0,message:`${m(n.length,"port")} used by more than one routed resource:
|
|
355
|
+
${s.join(`
|
|
356
|
+
`)}`,fix:'Usually a copied service.json. Each container can listen on the same port, but pick distinct "port" values (flisk resource assigns free ones) so URLs like http://<resource>:<port> and localhost checks stay unambiguous'}}var Te=/https?:\/\/([a-z][a-z0-9-]*):(\d+)/g;function ut(t=l()??process.cwd()){let e=f(t),n=X(e),s=new Map([...n].map(([r,a])=>[a,r])),o=[];for(let r of e)for(let[a,i]of Object.entries(ye(r.configPath)))for(let[,c,d]of String(i).matchAll(Te)){let p=Number(d),b=n.get(c);b!==void 0&&b!==p?o.push(`${r.name} param ${a}: ${c}:${p}, but ${c} listens on ${b}`):(c==="localhost"||c==="127.0.0.1")&&s.has(p)&&s.get(p)!==r.name&&o.push(`${r.name} param ${a}: localhost:${p} is ${r.name} itself inside its container; ${s.get(p)} is reached as ${s.get(p)}:${p}`)}return o.length===0?{name:"Service URL ports",didPass:!0,message:"Service URLs in params point at the ports the services listen on"}:{name:"Service URL ports",didPass:!1,message:`${m(o.length,"service URL")} in params that cannot connect:
|
|
357
|
+
${o.join(`
|
|
358
|
+
`)}`,fix:'Flisk runs each backend on the "port" in its service.json, and containers reach each other by resource name (http://<resource>:<port>). Use that port in the URL'}}function D(t){let e=[],n;try{n=V(t,{withFileTypes:!0})}catch{return e}for(let s of n){let o=u(t,s.name);s.isDirectory()?ke.has(s.name)||e.push(...D(o)):fe.test(s.name)&&!he.test(o)&&e.push(o)}return e}var je=/(?:localhost|127\.0\.0\.1):(\d{2,5})\b/g,Q=/^\s*(?:\/\/|\/\*|\*|<!--|#)/;function lt(t=l()??process.cwd()){let e=new Map;for(let s of f(t)){let o=s.config;if(!(T(o?.appType)||o?.appType===$&&o.exposeViaProxy!==!1))continue;let a=me(s.path),i=o?.stack??le(a),c=`${a}\0${i}`,d=e.get(c)??{stack:i,resources:[]};d.resources.push(s),e.set(c,d)}let n=[];for(let{stack:s,resources:o}of e.values()){if(o.length<2)continue;let r=i=>!!(i.config?.traefik?.host||i.config?.traefik?.pathPrefix),a=o.filter(i=>!r(i)).map(i=>i.name);a.length>0&&n.push(`stack "${s}": ${a.sort().join(", ")} lose the shared /api/${s}-management route because ${o.length} backends share the stack; they are routed at /api/<name> only`);for(let i of["host","pathPrefix"]){let c=new Map;for(let d of o){let p=d.config?.traefik?.[i];p&&c.set(p,[...c.get(p)??[],d.name])}for(let[d,p]of c)p.length>1&&n.push(`stack "${s}": ${p.sort().join(" and ")} both set traefik.${i} "${d}", so Traefik cannot tell them apart`)}}return n.length===0?{name:"Stack API routes",didPass:!0,message:"No two backends in one service directory compete for one stack route"}:{name:"Stack API routes",didPass:!1,isWarning:!0,message:n.join(`
|
|
359
|
+
`),fix:'Call each backend at /api/<name>, move one backend to its own stack, or give each its own "traefik.pathPrefix" in service.json'}}function mt(t=l()??process.cwd()){let e=f(t),n=new Map([...X(e)].map(([r,a])=>[a,r])),s=[];for(let r of e.filter(a=>a.config?.appType==="frontend"))for(let a of D(u(r.path,"src")))y(a,"utf-8").split(`
|
|
360
|
+
`).forEach((i,c)=>{if(!Q.test(i))for(let[,d]of i.matchAll(je)){let p=n.get(Number(d));p&&s.push(`${k(t,a)}:${c+1} calls localhost:${d} (${p})`)}});if(s.length===0)return{name:"Frontend backend URLs",didPass:!0,message:"No frontend calls a backend through a localhost port"};let o=Y(t)??"<project>";return{name:"Frontend backend URLs",didPass:!1,message:`${m(s.length,"frontend call")} to a backend port that Flisk does not publish on localhost:
|
|
361
|
+
${s.join(`
|
|
362
|
+
`)}`,fix:`Backends are only reachable through Traefik: http://api.${o}.localhost/api/<name without -api>/... (for example catalog-api is /api/catalog). Use that URL, or the generated .autogenerated/env.ts`}}var Z=/\b(?:prisma\s+(?:migrate\s+(?:deploy|dev|resolve)|db\s+push)|drizzle-kit\s+(?:migrate|push)|knex\s+migrate:\w+|typeorm\s+migration:run|mikro-orm\s+migration:up|sequelize(?:-cli)?\s+db:migrate|dbmate\s+(?:up|migrate)|atlas\s+migrate\s+apply|alembic\s+upgrade)\b/,ee=new Set(["start","dev","serve","prestart","predev","preserve"]),xe=/\.(?:sh|bash)$/,Se=/^\s*(?:CMD|ENTRYPOINT)\b/i;function S(t){try{return y(t,"utf-8")}catch{return}}function _e(t){let e=S(u(t,P));if(e===void 0)return[];let n={};try{n=JSON.parse(e)?.scripts??{}}catch{return[]}return Object.entries(n).filter(([s,o])=>ee.has(s)&&typeof o=="string"&&Z.test(o)).map(([s])=>`package.json "${s}" script`)}function j(t,e){let n=S(t);return n===void 0?!1:n.split(`
|
|
363
|
+
`).some(s=>Z.test(s.replace(/["'`,[\]]+/g," "))&&!Q.test(s)&&(!e||Se.test(s)))}function ft(t=l()??process.cwd()){let e=[];for(let n of f(t)){let s=n.config?.appType??n.type;if(s!=="backend"&&s!=="worker")continue;let o=n.path;for(let i of _e(o))e.push(`${n.name}: ${i}`);let r=u(o,n.config?.dockerfile??"Dockerfile");j(r,!0)&&e.push(`${n.name}: ${k(t,r)} CMD or ENTRYPOINT`);let a=[];try{a=V(o,{withFileTypes:!0})}catch{}for(let i of a){let c=u(o,i.name);i.isFile()&&xe.test(i.name)&&j(c,!1)&&e.push(`${n.name}: ${k(t,c)}`)}for(let i of D(u(o,"src")))j(i,!1)&&e.push(`${n.name}: ${k(t,i)}`)}return e.length===0?{name:"Migrations outside the API",didPass:!0,message:"No backend or worker runs a migration command from its own start-up"}:{name:"Migrations outside the API",didPass:!1,isWarning:!0,message:`${m(e.length,"migration command")} run from a backend or worker's own start-up:
|
|
364
|
+
${e.join(`
|
|
365
|
+
`)}`,fix:"Move the migration into a `migrator` resource (appType migrator) that the service depends on, so it runs once before the service starts instead of on every start and restart"}}function ht(t=l()??process.cwd()){let e=f(t).filter(s=>s.config?.featuresEnabled?.includes("nats")).map(s=>s.name);if(e.length===0)return{name:"NATS broker",didPass:!0,message:"No resource enables the nats feature",isSkipped:!0};if(R(u(t,"services","platform","messaging",U)))return{name:"NATS broker",didPass:!0,message:`${m(e.length,"resource")} enable nats and services/platform/messaging/docker-compose.yml is present`};let n=(Y(t)??"project").replace(/-/g,"_");return{name:"NATS broker",didPass:!1,message:`${e.join(", ")} enable the nats feature, but no NATS server will start: Flisk only starts one when services/platform/messaging/docker-compose.yml exists, and it does not generate that file`,fix:`Create it with a "nats" service (image nats with -js, container_name ${n}_nats, network ${n}_backend, external) and a "redis" service, which Flisk registers alongside it`}}function gt(t=l()??process.cwd()){let e=N(t),n=" (project resource set; flisk up may select a subset and not start Postgres)",s=e.focusWouldEnableDatabaseManagement?" Warning: generated Tiltfile default focus expands CORE_INFRA to database-management while project.json has it off; default flisk up takes the feature path.":"";if(e.unknownDependsOnNames.length>0){let r=e.unknownDependsOnNames.map(({resource:i,name:c})=>`"${c}" (from ${i})`).join(", "),a=e.willStart?` Postgres will start because ${e.reason==="feature"?"database-management is enabled":`resource(s) ${e.dependsOnUsers.join(", ")} depend on postgres/database-management`}${n}`:" Postgres will not start for this project.json/generated Tiltfile state.";return{name:"Shared platform Postgres",didPass:!1,message:`${m(e.unknownDependsOnNames.length,"unknown dependsOn name")}: ${r}. These are not known services or stacks, and are not the shared platform database names (postgres, database-management).${a}${s}`,fix:'Use "postgres" or "database-management" in dependsOn for the shared platform database, or fix the name to match an existing service'}}if(!e.willStart)return{name:"Shared platform Postgres",didPass:!0,isWarning:!!s,isSkipped:!s,message:s?s.trim():"Shared platform Postgres will not start: database-management is off and no resource depends on postgres or database-management."};if(e.reason==="feature"&&e.dependsOnUsers.length===0){let r=e.featureOnFromTiltfile||e.featureOnFromSpecMaster?" (generated Tiltfile/spec.master)":"";return{name:"Shared platform Postgres",didPass:!0,isWarning:!!s,message:`Shared platform Postgres will start because the database-management feature is enabled${r} (project-wide; not a flisk up selection).${s}`}}let o=e.reason==="feature"?`the database-management feature is enabled and resource(s) ${e.dependsOnUsers.join(", ")} depend on postgres/database-management`:`resource(s) ${e.dependsOnUsers.join(", ")} depend on postgres/database-management`;return{name:"Shared platform Postgres",didPass:!0,isWarning:!!s,message:`Shared platform Postgres will start because ${o}${n}${s}`}}function Ee(t){let e=[];for(let n of t.split(`
|
|
366
|
+
`)){let s=n.match(/^\s*(\d+)\s+.*\btilt up\b.*?\s-f\s+(\S+)/i);s&&e.push({pid:Number(s[1]),root:s[2].split(/[\\/]\.tdk[\\/]/i)[0]})}return e}function kt(t=l()??process.cwd(),e=ue){let n;try{n=process.platform==="win32"?de("powershell.exe",["-NoProfile","-Command","Get-CimInstance Win32_Process | Where-Object { $_.Name -eq 'tilt.exe' } | ForEach-Object { '{0} {1}' -f $_.ProcessId, $_.CommandLine }"],{encoding:"utf-8",timeout:x,windowsHide:!0}):e("ps -Ao pid=,command=",{encoding:"utf-8",timeout:x})}catch{return{name:"Tilt processes",didPass:!0,isSkipped:!0,message:"Could not list processes"}}let s=Ee(n),o=s.filter(a=>a.root===t),r=s.filter(a=>a.root!==t);return o.length>1?{name:"Tilt processes",didPass:!1,message:`${o.length} Tilt processes are running for this project (pids ${o.map(a=>a.pid).join(", ")}). They fight over ports and containers`,fix:`Stop them all, then start one: flisk down && kill ${o.map(a=>a.pid).join(" ")} && flisk up`}:r.length>0?{name:"Tilt processes",didPass:!0,isSkipped:!0,message:`Another project's Tilt is running: ${r.map(i=>`${i.root} (pid ${i.pid})`).join(", ")}`,fix:"flisk up will use the next free Tilt UI port (10351, ...). If it is a leftover, run flisk down in that project"}:{name:"Tilt processes",didPass:!0,message:"No stray Tilt processes"}}async function yt(t=z){let e=`tdk_doctor_probe_${process.pid}`;try{await t(`docker network create ${e}`,x)}catch(n){let s=String(n.stderr??n);return/address pools/i.test(s)?{name:"Docker networks",didPass:!1,message:"Docker cannot create another network: all predefined address pools are in use. Each Flisk project needs several",fix:"run flisk down --prune-networks in projects you are not using, or docker network prune (removes all unused networks)"}:{name:"Docker networks",didPass:!0,isSkipped:!0,message:"Could not test network creation"}}try{await t(`docker network rm ${e}`,x)}catch{}return{name:"Docker networks",didPass:!0,message:"Docker can create networks"}}export{Oe as a,Ne as b,h as c,ae as d,qe as e,z as f,Ke as g,it as h,ct as i,pt as j,dt as k,ut as l,lt as m,mt as n,ft as o,ht as p,gt as q,Ee as r,kt as s,yt as t};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
|
|
2
|
+
import{r as c}from"./chunk-M6S25PIS.js";import{execFileSync as u}from"node:child_process";import{realpathSync as l}from"node:fs";import{resolve as p}from"node:path";var m=(o,t)=>u(o,t,{encoding:"utf-8",stdio:"pipe",timeout:5e3,windowsHide:!0});function a(o,t=process.platform,e=m){if(t==="win32")return null;try{let s=e("lsof",["-nP",`-iTCP:${o}`,"-sTCP:LISTEN","-t"]).split(/\r?\n/).map(r=>r.trim()).filter(Boolean);for(let r of s){let n=e("ps",["-p",r,"-o","command="]).trim(),[i]=c(`${r} ${n}`);if(i)return i}}catch{}return null}function f(o){try{return l(o)}catch{return p(o)}}function d(o,t){return o!==null&&f(o.root)!==f(t)}async function P(o,t,e,s=a){let r={running:[],foreign:[]};for(let n of o){if(!await e(n))continue;let i=s(n);i&&d(i,t)?r.foreign.push({port:n,root:i.root}):r.running.push(n)}return r}export{a,d as b,P as c};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
|
|
2
|
+
import{O as h,Ta as D,Ua as L,fa as _,h as I,k as w,l as A,q as j,qa as x,s as T,x as F}from"./chunk-LYOSHFYU.js";import{execFileSync as B}from"node:child_process";import{existsSync as de,mkdirSync as le,readFileSync as K,realpathSync as fe,writeFileSync as pe}from"node:fs";import{join as S,resolve as he,sep as me}from"node:path";import{createServer as ie}from"node:net";import{connect as oe}from"node:net";function U(e,t=500){return new Promise(r=>{let s=oe({port:e,host:"127.0.0.1"}),n=o=>{s.destroy(),r(o)};s.setTimeout(t,()=>n(!1)),s.once("connect",()=>n(!0)),s.once("error",()=>n(!1))})}var ae={ingressHttp:{start:8080,end:8180},ingressHttps:{start:8443,end:8543},postgres:{start:15432,end:15532}},v={ingressHttp:"TDK_HTTP_PORT",ingressHttps:"TDK_HTTPS_PORT",postgres:"TDK_POSTGRES_PORT"},H={ingressHttp:80,ingressHttps:443,postgres:5432},E={ingressHttp:H.ingressHttp,ingressHttps:H.ingressHttps};function ce(e,t){return e==="EACCES"&&t==="darwin"}async function O(e){return await U(e)?!1:new Promise(t=>{let r=ie();r.once("error",s=>{t(ce(s.code,process.platform))}),r.listen(e,"127.0.0.1",()=>r.close(()=>t(!0)))})}function ue(e,t){let r=Number(t);if(!Number.isInteger(r)||r<1||r>65535)throw new Error(`${e} must be an integer between 1 and 65535 (received ${JSON.stringify(t)})`);return r}async function R(e={}){let t=e.env??process.env,r=e.isAvailable??O,s={ingressHttp:!1,ingressHttps:!1,postgres:!1},n={ingressHttp:"",ingressHttps:"",postgres:""},o={explicit:s},a=new Set;for(let c of["ingressHttp","ingressHttps","postgres"]){let u=t[v[c]],i=u!==void 0&&u.trim()!=="",d=e.ranges?.[c]??ae[c],l=E[c],f=i?[ue(v[c],u)]:[...l===void 0?[]:[l],...Array.from({length:d.end-d.start+1},(g,k)=>d.start+k)],m;for(let g of f)if(!a.has(g)){if(await r(g)){m=g;break}if(i)throw new Error(`${v[c]}=${g} is already in use. Choose a free host port and retry.`)}if(m===void 0)throw new Error(`No available host port in ${d.start}-${d.end} for ${c}. Set ${v[c]} to a free port and retry.`);a.add(m),o[c]=m,s[c]=i,n[c]=i?"explicit override":m===l?"requested port is free":`selected from fallback range ${d.start}-${d.end}`}return{...o,requested:{...H},reason:n}}function Ve(e){return`Host ports: HTTP ${e.ingressHttp}, HTTPS ${e.ingressHttps}, Postgres ${e.postgres}`}var G=S(".tdk",".tdk-out","host-ports.json");function ge(e,t,r){return e.split(`
|
|
3
|
+
`).some(s=>{let[n="",o=""]=s.split("|"),a=n.toLowerCase(),c=t.toLowerCase();return(a.startsWith(`${c}_`)||a.startsWith(`${c}-`))&&o.includes(`:${r}->`)})}function V(e){let t=S(e,G);if(!de(t))return null;try{let r=JSON.parse(K(t,"utf-8"));if(!r||typeof r!="object")return null;let s=r;return!Number.isInteger(s.ingressHttp)||!Number.isInteger(s.ingressHttps)||!Number.isInteger(s.postgres)||!s.requested||!s.explicit||!s.reason?null:s}catch{return null}}function Ye(e,t){let r=S(e,G);le(S(e,".tdk",".tdk-out"),{recursive:!0}),pe(r,`${JSON.stringify(t,null,2)}
|
|
4
|
+
`,{encoding:"utf-8",mode:384})}function Qe(e){process.env.TDK_HTTP_PORT=String(e.ingressHttp),process.env.TDK_HTTPS_PORT=String(e.ingressHttps),process.env.TDK_POSTGRES_PORT=String(e.postgres)}var ke="com.docker.compose.project.working_dir";function Pe(e,t,r){let s=new Set([he(t)]);try{s.add(fe(t))}catch{}return e.split(`
|
|
5
|
+
`).some(n=>{let[,o="",a=""]=n.split("|");return!a||!o.includes(`:${r}->`)?!1:[...s].some(c=>a===c||a.startsWith(`${c}${me}`))})}function Xe(e,t,r=()=>B("docker",["ps","--format",`{{.Names}}|{{.Ports}}|{{.Label "${ke}"}}`],{encoding:"utf-8",timeout:5e3})){let s=V(e);if(!s||s.ingressHttp===t)return t;try{if(Pe(r(),e,s.ingressHttp))return s.ingressHttp}catch{}return t}async function et(e,t={}){let r=V(e),s=["TDK_HTTP_PORT","TDK_HTTPS_PORT","TDK_POSTGRES_PORT"].some(i=>process.env[i]);if(t.inspectDocker===!1&&r&&!s)return r;let n="";try{let i=JSON.parse(K(S(e,".tdk",w),"utf-8"));n=String(i?.project?.name??"tdk-project").replace(/-/g,"_").toLowerCase()}catch{n="tdk_project"}let o="";if(t.inspectDocker!==!1)try{o=B("docker",["ps","--format","{{.Names}}|{{.Ports}}"],{encoding:"utf-8",timeout:5e3})}catch{}let a=i=>ge(o,n,i),c=t.isPortFree??O,u=async i=>await c(i)||a(i);if(r&&!s){for(let i of[r.ingressHttp,r.ingressHttps,r.postgres])if(!await u(i))return R({isAvailable:u});for(let[i,d]of Object.entries(E)){let l=r[i];if(d!==void 0&&l!==d&&await u(d))return R({isAvailable:u})}return r}return R({isAvailable:u})}import{execFileSync as Q,execSync as $}from"node:child_process";import{existsSync as C,readdirSync as Te,readFileSync as N}from"node:fs";import{createServer as Se}from"node:net";import{join as P}from"node:path";function W(e,t){return e==="pending"||e==="in_progress"||e==="none"||t==="pending"||t==="none"}var J="container is running, but its health check is failing";function M(e){let t=e?.runtimeStatus??"";if(t!=="ok")return t;let r=e?.composeResourceInfo?.healthStatus;return r==="unhealthy"?"error":r==="starting"?"pending":t}var b=1e4,z=3e3,be="http://localhost:4873";function X(e){return`'${e.replace(/'/g,"'\\''")}'`}function y(e,t,r,s,n){return process.platform==="win32"?Q(F(t)??t,r,{...n,windowsHide:!0}):e(s,n)}var ye=[80,443];function $e(e){return e.replace(/-/g,"_")}function we(e,t){let r=new Set(t),s=[];for(let n of e.split(`
|
|
6
|
+
`)){let o=n.trim();if(!o)continue;let a=o.indexOf(" "),c=a>=0?o.slice(0,a):o,u=a>=0?o.slice(a+1):"",i=[];for(let d of r)u.split(/[\s,]+/).some(f=>f.startsWith(`${d}->`)||f.includes(`:${d}->`))&&i.push(d);i.length>0&&s.push({name:c,ports:u,publishedPorts:i})}return s}function ee(e,t){let r=e.toLowerCase(),s=t.toLowerCase();return r===`${s}_traefik`||r.startsWith(`${s}_traefik`)}function ve(e,t){return e.filter(r=>!ee(r.name,t))}function Re(e){try{let t=e??L();return new Set(t.filter(r=>r.config?.sablier?.enable&&r.config?.sablier?.deferStart).map(r=>r.name))}catch{return new Set}}function _e(e,t=new Set){let s=JSON.parse(e).items??[],n=[],o=0,a=0,c=0;for(let u of s){let i=u.metadata?.name??"unknown",d=u.status?.updateStatus??"",l=M(u.status),f=l==="error"&&u.status?.runtimeStatus==="ok",g=(u.status?.buildHistory?.[0]?.error??"").trim()||(f?J:"");if(d==="error"||l==="error"){n.push({name:i,updateStatus:d,runtimeStatus:l,error:g.replace(/\s+/g," ").slice(0,800)});continue}if(d==="ok"&&(l==="ok"||l==="not_applicable")){c+=1;continue}i!=="(Tiltfile)"&&W(d,l)&&(t.has(i)?a+=1:o+=1)}return{failures:n,pendingCount:o,deferredCount:a,okCount:c,total:s.length}}var xe=/failed to resolve source metadata for docker\.io\/library\/([A-Za-z0-9._-]+-l[1-4](?:-[a-z]+)*)(?::[^\s:]+)?:/i;function te(e){let t=e.replace(/\s+/g," ").trim(),r=t.match(/Bind for [^ ]+:(\d+) failed: port is already allocated/i);if(r)return`host port ${r[1]} is already allocated`;if(/port is already allocated/i.test(t))return"a required host port is already allocated";let s=[...t.matchAll(/ConnectionRefused downloading package manifest (@[A-Za-z0-9._/-]+)/gi)].map(c=>c[1]);if(s.length>0){let c=[...new Set(s)],u=c.slice(0,4).join(", "),i=c.length>4?` (+${c.length-4} more)`:"";return`private registry ConnectionRefused for ${u}${i} \u2014 Verdaccio (:4873) is down or unreachable from the build`}let n=t.match(xe);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,a=t.match(/\bup\s+-d\s+--no-build\s+([A-Za-z0-9._-]+)/i)?.[1];if(o||a||/docker compose .*\bup\s+-d\s+--no-build\b/i.test(t)){let c=o??`${a??"service"}:dev`,u=a?` for ${a}`:"";return`compose run failed because image is missing (${c})${u} \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 q(e){return/ConnectionRefused downloading package manifest|Verdaccio|private registry|dependency install failed during ImageBuild|install-deps\.sh|bun install/i.test(e)}function He(e=T()??process.cwd()){let t=P(e,".tdk",w);if(!C(t))return!1;try{let r=JSON.parse(N(t,"utf-8"));return r.optional_infra?.verdaccio===!0||Object.values(r.phases??{}).some(s=>(s.enabledStacks??[]).includes("verdaccio"))}catch{return!1}}function re(e){for(let t of[P(e,".npmrc"),P(e,A)])if(C(t))try{let r=N(t,"utf-8");if(/:4873\b/.test(r)||/verdaccio/i.test(r))return!0}catch{}return!1}function Ee(e=T()??process.cwd()){return He(e)||re(e)}function mt(e=$,t=T()??process.cwd(),r=process.env.VERDACCIO_URL??process.env.NPM_REGISTRY_URL??be,s=!0){if(!s&&!re(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(!Ee(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 c=y(e,"docker",["ps","--format","{{.Names}}"],"docker ps --format '{{.Names}}'",{stdio:"pipe",encoding:"utf-8",timeout:b});n=/verdaccio/i.test(c)}catch{}let o=!1,a="";try{let c=process.platform==="win32"?Q("curl.exe",["-fsS","-o","NUL","-w","%{http_code}","--max-time","2",r],{stdio:"pipe",encoding:"utf-8",timeout:z+1e3,windowsHide:!0}).trim():e(`curl -fsS -o /dev/null -w '%{http_code}' --max-time 2 ${X(r)}`,{stdio:"pipe",encoding:"utf-8",timeout:z+1e3}).trim();o=/^[23]\d\d$/.test(c),a=`HTTP ${c}`}catch(c){a=c instanceof Error?c.message.slice(0,120):"request failed"}return o?{name:"Private npm registry",didPass:!0,message:`Private npm registry reachable at ${r}${n?" (verdaccio container running)":""}`}:n?{name:"Private npm registry",didPass:!1,message:`Verdaccio container is running but ${r} is not responding (${a}). 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 ${r}. 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 gt(e=$,t=x(),r=ye){let s=$e(t),n="";try{n=y(e,"docker",["ps","--format","{{.Names}}\\t{{.Ports}}"],"docker ps --format '{{.Names}}\\t{{.Ports}}'",{stdio:"pipe",encoding:"utf-8",timeout:b})}catch{return{name:"Ingress Ports",didPass:!0,isSkipped:!0,message:"Docker not available - skipped ingress port check"}}let o=we(n,r),a=ve(o,s);if(a.length===0){let u=o.filter(i=>ee(i.name,s));return u.length>0?{name:"Ingress Ports",didPass:!0,message:`Ingress ports ${r.join("/")} held by this project's Traefik (${u.map(i=>i.name).join(", ")})`}:{name:"Ingress Ports",didPass:!0,message:`Ingress ports ${r.join("/")} are free for Traefik`}}return{name:"Ingress Ports",didPass:!1,message:`Traefik cannot bind ingress ports because another container already owns them:
|
|
7
|
+
${a.map(u=>{let i=u.publishedPorts.join(", ");return`${u.name} (host ports ${i})`}).join(`
|
|
8
|
+
`)}`,fix:`Stop the foreign container(s), e.g. \`docker stop ${a[0]?.name}\`, or shut down the other Flisk/Tilt project using that Traefik. Then re-run \`flisk up\`.`}}function Z(e,t){return new Promise(r=>{let s=Se();s.once("error",n=>r(n.code==="EADDRINUSE"?"in-use":"unknown")),s.once("listening",()=>s.close(()=>r("free"))),s.listen(e,t)})}async function kt(e){for(let t=0;t<2;t++){let r=await Z(e,"0.0.0.0");if(r==="unknown")return r;if(r==="free"){let s=await Z(e,"127.0.0.1");if(s==="free"||s==="unknown")return s}t===0&&await new Promise(s=>setTimeout(s,50))}return"in-use"}function Oe(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 Pt(e){let t=e.filter(o=>o.status!==void 0),r=e.filter(o=>!o.ok);if(t.length===0)return{name:"Service Health",didPass:!0,isSkipped:!0,message:`No services responded - skipped ping of ${h(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(r.length===0)return{name:"Service Health",didPass:!0,message:`All ${h(e.length,"service")} responding on /health`};let s=r.map(o=>`${o.name} (${Oe(o)})
|
|
9
|
+
${o.url}`).join(`
|
|
10
|
+
`),n=r.some(o=>o.status===404);return{name:"Service Health",didPass:!1,message:`${h(r.length,"service")} not healthy (${e.length-r.length}/${e.length} healthy):
|
|
11
|
+
${s}`,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 Tt(e){if(e.length===0)return{name:"Service Smoke",didPass:!0,isSkipped:!0,message:"No read-only smoke checks declared"};let t=e.filter(s=>!s.ok);if(t.length===0)return{name:"Service Smoke",didPass:!0,message:`All ${h(e.length,"service")} pass their smoke checks`};let r=t.map(s=>s.failure??`${s.name}: failed`).join(`
|
|
12
|
+
`);return{name:"Service Smoke",didPass:!1,message:`${h(t.length,"service")} failing a smoke check (${e.length-t.length}/${e.length} passing):
|
|
13
|
+
${r}`,fix:'Add the route the smoke step expects, or correct its "path" in the service.json "smoke" block. Then re-run flisk doctor.'}}function St(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 r=t.filter(n=>n.status===404);if(r.length===0)return{name:"Advertised Endpoints",didPass:!0,message:`${h(t.length,"advertised endpoint")} reachable`};let s=r.map(n=>`${n.name} (404)
|
|
14
|
+
${n.url}`).join(`
|
|
15
|
+
`);return{name:"Advertised Endpoints",didPass:!0,isWarning:!0,message:`${h(r.length,"advertised link")} ${r.length===1?"returns":"return"} 404 (the service may be healthy, but nothing answers at this exact path):
|
|
16
|
+
${s}`,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 bt(e=$){let t="";try{t=y(e,"tilt",["get","uiresources","-o","json"],"tilt get uiresources -o json",{stdio:"pipe",encoding:"utf-8",timeout:b})}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 r;try{r=_e(t,Re())}catch{return{name:"Tilt Resources",didPass:!0,isSkipped:!0,message:"Could not parse Tilt resource status - skipped"}}if(r.total===0)return{name:"Tilt Resources",didPass:!0,isSkipped:!0,message:"No Tilt UIResources found"};if(r.failures.length===0){let p=r.deferredCount>0?`, ${r.deferredCount} deferred`:"";return{name:"Tilt Resources",didPass:!0,message:`Tilt resources healthy (${r.okCount} ok, ${r.pendingCount} pending${p})`}}let s=Ne(r.failures),o=s.slice(0,20),a=s.length-o.length,c=o.map(p=>({failure:p,why:Ae(p,e)})),u=c.map(({failure:p,why:ne})=>`${p.name}: ${ne}`).join(`
|
|
17
|
+
`),i=a>0?`
|
|
18
|
+
\u2026and ${h(a,"more failed resource")}`:"",d=r.pendingCount>0?`
|
|
19
|
+
${h(r.pendingCount,"resource")} still pending/not started \u2014 often blocked by the failures above.`:"",l=r.deferredCount>0?`
|
|
20
|
+
${h(r.deferredCount,"resource")} intentionally deferred (sablier.deferStart) \u2014 not started until first request, unrelated to the failures above.`:"",f=s.some(p=>/port is already allocated/i.test(p.error)),m=s.some(p=>q(p.error)||q(te(p.error))),g=c.some(({why:p})=>/Kafka broker unreachable|kafka:9092/i.test(p)),k="Open the Tilt UI URL printed by flisk up (default port "+j.tiltUi+" when TILT_PORT is unset), inspect the red resources, fix the listed errors, then `tilt trigger <resource>` or re-run `flisk up`.";return f?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.":m?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.":g&&(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 ${h(r.failures.length,"failed resource")}:
|
|
21
|
+
${u}${i}${d}${l}`,fix:k}}var Ce=/^(traefik|postgres|nats|infisical|verdaccio|proxy)([.-]|$)/i;function Y(e){return Ce.test(e)}function Ne(e){return[...e].sort((t,r)=>{let s=Y(t.name)?0:1,n=Y(r.name)?0:1;return s!==n?s-n:t.name.localeCompare(r.name)})}function Ie(e,t=$,r=x()){try{let s=y(t,"docker",["ps","-a","--format","{{.Names}}"],"docker ps -a --format '{{.Names}}'",{stdio:"pipe",encoding:"utf-8",timeout:b}).split(`
|
|
22
|
+
`).map(i=>i.trim()).filter(Boolean),n=`-autogenerated-${e}-1`,o=s.find(i=>i===e)??s.find(i=>i.startsWith(`${r}-`)&&i.endsWith(n))??s.find(i=>i.endsWith(n))??s.find(i=>i.includes(e));if(!o)return null;let a=y(t,"docker",["logs","--tail","120",o],`docker logs --tail 120 ${X(o)} 2>&1`,{stdio:"pipe",encoding:"utf-8",timeout:b});if(/getaddrinfo ENOTFOUND/i.test(a)&&/kafka:9092/i.test(a))return"runtime crash: Kafka broker unreachable (getaddrinfo ENOTFOUND kafka:9092)";if(/ECONNREFUSED/i.test(a)&&/kafka/i.test(a))return"runtime crash: Kafka connection refused";let c=a.match(/ConnectionRefused downloading package manifest (@[A-Za-z0-9._/-]+)/i);if(c?.[1])return`runtime/build log: private registry ConnectionRefused for ${c[1]}`;if(/ZodError/i.test(a)){let i=[...a.matchAll(/"path"\s*:\s*\[\s*"([^"]+)"\s*\]/g)].map(l=>l[1]),d=[...new Set(i)].slice(0,6);return d.length>0?`runtime crash: ZodError missing required config/env: ${d.join(", ")}`:"runtime crash: ZodError during config/env validation"}let u=a.match(/error:\s+Failed to [^:\n]+[^\n]{0,120}/i)?.[0]??a.match(/Failed to (?:start|initialize) [^\n]{0,120}/i)?.[0]??a.match(/^\s*error:\s+[^\n]{0,160}/im)?.[0];if(u)return`runtime crash: ${u.replace(/\s+/g," ").trim().slice(0,180)}`}catch{return null}return null}function Ae(e,t=$){return e.error?te(e.error):e.runtimeStatus==="error"?Ie(e.name,t)??"runtime error (container crashed or unhealthy \u2014 check Tilt/docker logs)":e.updateStatus||e.runtimeStatus||"unknown error"}var je=/\.(ts|tsx|mts|js|mjs|cjs)$/,Fe=new Set(["node_modules","dist",I,"tests","__tests__"]);function se(e){let t=[];for(let r of Te(e,{withFileTypes:!0}))r.isDirectory()?Fe.has(r.name)||t.push(...se(P(e,r.name))):je.test(r.name)&&!/\.(test|spec)\./.test(r.name)&&t.push(P(e,r.name));return t}function De(e){let t=e?.healthCheckPath||"/health";return{container:t,traefik:e?.traefik?.healthCheck||t}}function Le(e){let t=[];for(let r of e){if(!_(r.config?.appType))continue;let s=P(r.path,"src");if(!C(s))continue;let n=se(s);if(n.length===0)continue;let o=n.map(i=>N(i,"utf-8")),{container:a,traefik:c}=De(r.config),u=a===c?[{path:a,usedBy:"container and Traefik healthchecks"}]:[{path:a,usedBy:"container healthcheck"},{path:c,usedBy:"Traefik healthcheck"}];for(let{path:i,usedBy:d}of u){let l=["'",'"',"`"].map(f=>`${f}${i}${f}`);o.some(f=>l.some(m=>f.includes(m)))||t.push({name:r.name,healthPath:i,usedBy:d})}}return t}function yt(e=T()??process.cwd()){let t=[];try{t=D(e)}catch{return{name:"Health Routes",didPass:!0,isSkipped:!0,message:"Could not read services - skipped health route check"}}let r=t.filter(o=>_(o.config?.appType));if(r.length===0)return{name:"Health Routes",didPass:!0,isSkipped:!0,message:"No backend services to check"};let s=Le(r);return s.length===0?{name:"Health Routes",didPass:!0,message:`All ${h(r.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):
|
|
23
|
+
${s.map(o=>`${o.name}: no "${o.healthPath}" route in src/ (probed by the ${o.usedBy})`).join(`
|
|
24
|
+
`)}`,fix:`Add the route, e.g. for Hono: app.get("${s[0]?.healthPath}", (c) => c.json({ status: "ok" })). To use a different path, set "healthCheckPath" (container healthcheck) or "traefik.healthCheck" (Traefik) in its service.json.`}}export{R as a,Ve as b,V as c,Ye as d,Qe as e,Xe as f,et as g,W as h,M as i,$e as j,Re as k,_e as l,te as m,He as n,mt as o,gt as p,kt as q,Pt as r,Tt as s,St as t,bt as u,yt as v};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import{createRequire as __tdkRequire}from'node:module';const require=__tdkRequire(import.meta.url);
|
|
2
|
+
import{b as N,c as pe,d as H,i as D,j as q,n as J,o as I,q as C}from"./chunk-KXKBWLTE.js";import{H as B,J as V,L as A,Na as ne,Ra as ae,Sa as se,Ua as ie,V as F,W as z,Wa as ce,Xa as le,Ya as de,aa as f,ba as v,ca as t,d as P,ja as re,k as U,ka as G,la as W,m as E,s as T}from"./chunk-LYOSHFYU.js";import{b as p}from"./chunk-CF46CD6R.js";import{execFileSync as ve}from"node:child_process";import{cpSync as ye,existsSync as y,readdirSync as Se,readFileSync as Y,writeFileSync as je}from"node:fs";import{basename as _e,dirname as we,join as _,resolve as M}from"node:path";import{cwd as ue}from"node:process";import{fileURLToPath as xe}from"node:url";import{Command as Pe}from"commander";var he="-y, --yes",ke="Skip prompts and confirmations; use defaults";function fe(e,o=ke){return e.option(he,o,!1)}async function be(e,o=!0){let c=await I({message:e,initial:o});return c||F(),c}function Ne(e,o=1){e.valid||A(e.error??"Validation failed",o)}function De(e,o,c,a){return e.dryRun?(a?a():(console.log(`Dry run - ${o}`),console.log(`Would run: ${c}`)),!0):!1}async function Je(e,o){return await be(e,!0)?!0:(o&&o(),!1)}var L=new Map;function me(e){e!==void 0?L.delete(e):L.clear()}function Ue(e=!1){let o=T(),c=o??"";if(!e){let n=L.get(c);if(n)return n}let{resources:a,problems:l}=o?ae(o):{resources:ie(),problems:[]},d=de(a),r=le(a);se(l);let g=a.filter(n=>!n.stack),m=new Map;for(let n of a)if(n.stack){let w=m.get(n.stack)||[];w.push(n),m.set(n.stack,w)}let k={resources:a,problems:l,stacks:d,stackNames:r,unassignedResources:g,resourcesByStack:m};return L.set(c,k),k}var O={proxy:{name:"proxy",description:"Traefik reverse proxy and API gateway for routing all services",category:"core",phase:"pre_alpha",enabled_by_default:!0,dependsOn:[]},verdaccio:{name:"verdaccio",description:"Verdaccio npm registry for package management and local publishing (Premium - requires TDK_LICENSE_KEY)",category:"premium",phase:"pre_alpha",enabled_by_default:!1,dependsOn:[]},[P]:{name:P,description:"PostgreSQL database for data persistence and SQL operations",category:"core",phase:"pre_alpha",enabled_by_default:!0,dependsOn:[]},monitoring:{name:"monitoring",description:"SigNoz or SkyWalking observability platform for monitoring and tracing",category:"optional",phase:"pre_alpha",enabled_by_default:!1,dependsOn:[P]},elk:{name:"elk",description:"Elasticsearch, Logstash, Kibana stack for log aggregation and analysis",category:"optional",phase:"pre_alpha",enabled_by_default:!1,dependsOn:[]},debezium:{name:"debezium",description:"Change Data Capture for real-time data streaming from databases",category:"optional",phase:"pre_alpha",enabled_by_default:!1,dependsOn:[P]},"golden-image":{name:"golden-image",description:"Pre-built Docker image layer for faster builds and smaller deployments",category:"optional",phase:"pre_alpha",enabled_by_default:!0,dependsOn:[]}};var j={demo:{repo:"https://github.com/flisk-sh/tdk-restaurant-example.git",description:"The demo stack: restaurant ops with reservations, kitchen and floor control"},restaurant:{repo:"https://github.com/flisk-sh/tdk-restaurant-example.git",description:"Restaurant ops: reservations, kitchen, floor control (Hono + Vite + Bun)"},saas:{repo:"https://github.com/flisk-sh/tdk-saas-starter.git",description:"SaaS starter: account dashboard and a working checkout button (Hono + Vite + Bun)"},erp:{repo:"https://github.com/flisk-sh/tdk-erp-system.git",description:"Enterprise ERP: 100 microservices across 7 business domains"},"user-management":{repo:"https://github.com/flisk-sh/tdk-user-management.git",description:"User/auth management services"},ecommerce:{repo:"https://github.com/flisk-sh/tdk-ecommerce-example.git",description:"Vue 3 storefront and Hono catalog API (needs Flisk 1.3.75+ for --framework vue)"},example:{repo:"https://github.com/flisk-sh/tdk-example.git",bundledPath:"examples/tdk-example",description:"Hono + Postgres + NATS worker + Vite example with a routed write path"},"agent-stack":{repo:"https://github.com/flisk-sh/tdk-agent-stack.git",description:"AI agent stack: Bun agent + MCP tool server + Postgres + NATS"}};function Ge(e){let o=e[2];if(!(!o||o.startsWith("-")))return o in j?o:void 0}function K(e,o){try{return ce(e,o)}catch{return[]}}function Ee(e){return new Set([...e.phases.pre_alpha.enabledStacks,...e.phases.alpha.enabledStacks,...e.phases.beta.enabledStacks,...e.phases.out_of_scope.enabledStacks])}function Q(e,o){let c=Ee(e),a=o.filter(l=>!c.has(l));return a.length===0?(e.phases.pre_alpha.enabledStacks=Array.from(new Set(e.phases.pre_alpha.enabledStacks)),[]):(e.phases.pre_alpha.enabledStacks=Array.from(new Set([...e.phases.pre_alpha.enabledStacks,...a])),a)}function ut(e,o){let c=_(e,".tdk",U);if(!y(c))return[];let a=D(e),l=Q(a,K(e,o));return l.length>0&&N(c,a),l}function Ae(){return Object.values(O).filter(e=>e.enabled_by_default&&e.category==="core").map(e=>e.name)}function Ce(e,o){return o||(j[e]?.repo.split("/").pop()??e).replace(/\.git$/,"")}async function Oe(e,o,c){let a=j[e];if(!a){console.log(p.red(`
|
|
3
|
+
\u274C Unknown template: "${e}"`)),console.log(p.yellow(`
|
|
4
|
+
Available templates:`));for(let[r,g]of Object.entries(j))console.log(` ${p.cyan(r)} ${p.gray(`- ${g.description}`)}`);console.log(p.gray("\nOr run `flisk project` with no template for a blank project.")),process.exit(1)}let l=Ce(e,o);ne(l)||B.invalidPath(l).exit();let d=M(ue(),l);y(d)&&Se(d).length>0&&B.directoryExists(d).exit(),z(`Cloning template: ${e}`),t(`${a.description}
|
|
5
|
+
`,0),t(`Source: ${a.bundledPath?"bundled Flisk example":a.repo}`,0),t(`Destination: ${d}
|
|
6
|
+
`,0);try{if(a.bundledPath){let r=M(we(xe(new URL("../dist/commands/project.js",import.meta.url).href)),"../../../");ye(M(r,a.bundledPath),d,{recursive:!0});let g=_e(d).replace(/-/g,"_");for(let m of["services/platform/messaging/docker-compose.yml","services/shop/orders-api/service.json","services/shop/orders-api/src/index.ts","services/shop/orders-worker/service.json","services/shop/orders-worker/src/index.ts"]){let k=_(d,m),n=Y(k,"utf-8");je(k,n.replaceAll("tdk_example",g))}}else ve("git",["clone",a.repo,d],{stdio:"inherit"})}catch{A(a.bundledPath?"Bundled example copy failed. Reinstall the Flisk CLI package and try again.":"git clone failed. Check you have git installed and network access.")}me(),console.log(p.green(`
|
|
7
|
+
\u2705 Cloned "${e}" into ${l}/`)),t(`
|
|
8
|
+
Next steps:`,0),t(`1. cd ${l}`),t("2. flisk doctor # check Docker/Tilt are ready"),t("3. flisk up # start the stack"),t(`
|
|
9
|
+
Runtime readiness:`,0);for(let r of c)t(r,0)}var R={version:"1.0",project:{name:"",version:"1.0.0"},phases:{pre_alpha:{name:"Pre-Alpha",description:"Core infrastructure and MVP services",enabledStacks:Ae()},alpha:{name:"Alpha",description:"Essential business services",enabledStacks:[]},beta:{name:"Beta",description:"Extended features",enabledStacks:[]},out_of_scope:{name:"Out of Scope",description:"Future releases",enabledStacks:[]}},optional_infra:{monitoring:!1,elk:!1,debezium:!1,golden_image:!0,verdaccio:!1},discovery:{paths:["services/*/*"]},overrides:{}},gt=fe(new Pe("project")).description("Initialize or validate project-level master configuration").argument("[template]",`Clone a starter example instead of a blank project (${Object.keys(j).join(", ")})`).option("--path <dir>","Directory to clone the template into (default: the template's repo name)").option("--check","Check if master configs exist and are in sync").option("--force","Overwrite existing configuration (dangerous)").option("--config-file <path>","Load project config from existing JSON file").action(async(e,o)=>{let{assertMachineReadyOrExit:c,detectRuntimePrerequisites:a,formatRuntimeNextSteps:l}=await import("./cold-preflight-6Z4RBFKV.js");await c({scope:"scaffold"});let d=l(a());if(e){await V(async()=>Oe(e,o.path,d));return}await V(async()=>{let r=T();r||(r=ue(),v(`\u{1F680} Initializing new Flisk project...
|
|
10
|
+
`),t(`Location: ${r}
|
|
11
|
+
`,0));let g=_(r,".tdk"),m=_(g,U);if(o.check){let i=E.every(u=>y(_(r,".tdk",".tdk-out",u))),h=y(m);if(i&&h){let u=D(r);f("Project configuration is valid"),t(`Project: ${u.project.name}`,3),t(`Phases: ${Object.keys(u.phases).join(", ")}`,3),process.exit(0)}else{if(console.log(p.yellow("\u26A0\uFE0F Project configuration incomplete:")),h||t(".tdk/project.json (not found)",3),!i)for(let u of E.filter(S=>!y(_(r,".tdk/.tdk-out",S))))t(`.tdk/.tdk-out/${u} (not found)`,3);t("Run `flisk project` to create them."),process.exit(1)}}z("Project Configuration"),t(`Project root: ${r}
|
|
12
|
+
`,0),y(g)||(pe(g),f("Created: .tdk/ directory"));let k=y(m);if(k&&!o.force){f(".tdk/project.json exists");let i=JSON.parse(Y(m,"utf-8")),h=D(r),u=K(r),S=Q(h,u);if((S.length>0||!i.phases)&&N(m,h),S.length>0&&t(`Auto-enabled discovered service stacks: ${S.join(", ")}`,0),v(`
|
|
13
|
+
\u{1F4CB} Regenerating master configuration files...
|
|
14
|
+
`),await q(r),W(r))f("Created: .env (the defaults work as-is)");else{let b=G(r);b.length>0&&f(`Updated: .env (added ${b.join(", ")}; existing values unchanged)`)}let $=H(r);$.length>0&&f(`Added to .gitignore: ${$.join(", ")}`),console.log(p.green(`
|
|
15
|
+
\u2705 Project configuration regenerated!`)),t(`
|
|
16
|
+
Generated in .tdk/.tdk-out/:`,0);for(let b of E)t(`- ${b}`);t(`
|
|
17
|
+
Runtime readiness:`,0);for(let b of d)t(b,0);return}if(k&&o.force&&!o.yes&&(console.log(p.red(`
|
|
18
|
+
\u26A0\uFE0F WARNING: --force will overwrite .tdk/project.json!`)),!await I({message:"This will reset your project configuration. Continue?",initial:!1}))){F();return}let n,w=K(r);if(o.configFile){let i=M(o.configFile);y(i)||A(`Config file not found: ${i}`);let h=Y(i,"utf-8");n=JSON.parse(h),f(`Loaded config from: ${i}`)}else if(o.yes)n=JSON.parse(JSON.stringify(R)),n.project.name=r.split("/").pop()||"my-project",console.log(p.gray("Using default configuration (non-interactive mode)"));else{v(`\u{1F4DD} Project Setup Wizard
|
|
19
|
+
`);let i=await J({message:"Project name:",initial:r.split("/").pop()||"my-project",validate:s=>s.trim()!==""||"Project name is required"}),h=await J({message:"Project version:",initial:"1.0.0-alpha"}),u=Object.values(O).filter(s=>s.category==="core"&&s.phase==="pre_alpha"),S=new Set(u.map(s=>s.name)),te=await C({message:"Select Pre-Alpha enabled stacks (core infrastructure):",choices:[...u.map(s=>({title:s.description,value:s.name,selected:s.enabled_by_default})),...w.filter(s=>!S.has(s)).map(s=>({title:`${s} (discovered service stack)`,value:s,selected:!0}))]}),$=await C({message:"Select Alpha enabled stacks (core business):",choices:[{title:"api (Backend API)",value:"api"},{title:"app (Frontend app)",value:"app"}]}),b=await C({message:"Select Beta enabled stacks (extended features):",choices:[{title:"worker (Background jobs)",value:"worker"},{title:"migrator (Database migrations)",value:"migrator"}]}),x=await C({message:"Enable optional infrastructure (high resource):",choices:Object.values(O).filter(s=>s.category==="optional").concat(Object.values(O).filter(s=>s.category==="premium")).map(s=>({title:s.description,value:s.name,selected:s.enabled_by_default}))});v(`
|
|
20
|
+
\u{1F4C2} Service discovery`),t("What: folder patterns Flisk scans for a service.json in each match."),t("Where: type one or more globs, comma-separated."),t("How: saved as discovery.paths in .tdk/project.json - editable later."),t(`Example: ${R.discovery.paths.join(", ")}
|
|
21
|
+
`);let ge=await J({message:"Folders to scan:",initial:R.discovery.paths.join(", ")});n=JSON.parse(JSON.stringify(R)),n.project.name=i,n.project.version=h,n.phases.pre_alpha.enabledStacks=Array.from(new Set(te)),n.phases.alpha.enabledStacks=Array.from(new Set($)),n.phases.beta.enabledStacks=Array.from(new Set(b)),n.optional_infra.monitoring=x.includes("monitoring"),n.optional_infra.elk=x.includes("elk"),n.optional_infra.debezium=x.includes("debezium"),n.optional_infra.golden_image=x.includes("golden_image"),n.optional_infra.verdaccio=x.includes("verdaccio");let oe=ge.split(",").map(s=>s.trim()).filter(s=>s.length>0);n.discovery.paths=oe.length>0?oe:R.discovery.paths}let X=Q(n,w);if(X.length>0&&t(`Auto-enabled discovered service stacks: ${X.join(", ")}`,0),v(`
|
|
22
|
+
\u{1F4CB} Creating project configuration...
|
|
23
|
+
`),N(m,n),f("Created: .tdk/project.json"),t(`\u2192 Project: ${n.project.name}`),v(`
|
|
24
|
+
\u{1F4CB} Generating master configuration files...
|
|
25
|
+
`),await q(r),v(`
|
|
26
|
+
\u{1F527} Setting up environment variables...
|
|
27
|
+
`),W(r))f("Created: .env (the defaults work as-is)");else{let i=G(r);i.length>0?f(`Updated: .env (added ${i.join(", ")}; existing values unchanged)`):f("Found: .env (environment already configured)")}let Z=re(r);if(Z.missing.length>0){console.log(p.yellow(`
|
|
28
|
+
\u26A0\uFE0F Missing required environment variables:`));for(let i of Z.missing)t(`- ${i}`,1);t("\u2192 Edit .env and fill in these values before running `flisk up`",1)}let ee=H(r);ee.length>0&&f(`Added to .gitignore: ${ee.join(", ")}`),console.log(p.green(`
|
|
29
|
+
\u2705 Project configuration complete!`)),t(`
|
|
30
|
+
Generated files in .tdk/.tdk-out/:`,0);for(let i of E)t(`- ${i}`);t(`
|
|
31
|
+
Environment configuration:`,0),t("- .env (contains all required variables)"),t(`
|
|
32
|
+
Source file:`,0),t("- .tdk/project.json (edit this to change project structure)"),t(`
|
|
33
|
+
Next steps:`,0),t("1. flisk resource api --type backend --stack app # scaffold a service"),t("2. flisk up app # start it"),t(`
|
|
34
|
+
Runtime readiness:`,0);for(let i of d)t(i,0);t("\nAfter editing .tdk/project.json, run `flisk config regenerate`.",0)})});export{me as a,Ue as b,fe as c,Ne as d,De as e,Je as f,Ge as g,ut as h,Ce as i,Oe as j,gt as k};
|