@flisk-sh/flisk 1.4.2 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +119 -43
- package/cli/dist/backend-languages/bun.js +5 -0
- package/cli/dist/backend-languages/go.js +3 -1
- package/cli/dist/cli.js +9 -0
- package/cli/dist/commands/completion.js +8 -1
- package/cli/dist/commands/config.js +130 -55
- package/cli/dist/commands/doctor.js +16 -15
- package/cli/dist/commands/logs.js +1 -0
- package/cli/dist/commands/project.js +1 -0
- package/cli/dist/commands/resource.js +39 -11
- package/cli/dist/commands/status.js +18 -9
- package/cli/dist/commands/up.js +102 -17
- package/cli/dist/frontend-frameworks/next.js +197 -0
- package/cli/dist/frontend-frameworks/react.js +81 -12
- package/cli/dist/frontend-frameworks/registry.js +5 -4
- package/cli/dist/frontend-frameworks/vue.js +82 -12
- package/cli/dist/generator/resource-templates.js +2 -4
- package/cli/dist/generator/template-engine.js +5 -3
- package/cli/dist/utils/cold-preflight.js +3 -2
- package/cli/dist/utils/config-explain.js +197 -0
- package/cli/dist/utils/demo-reset.js +22 -0
- package/cli/dist/utils/discovery-paths.js +7 -1
- package/cli/dist/utils/host-port-plan.js +8 -3
- package/cli/dist/utils/port-conflict-fix.js +16 -14
- package/cli/dist/utils/port-occupant.js +35 -0
- package/cli/dist/utils/project-features.js +7 -16
- package/cli/dist/utils/project-templates.js +8 -8
- package/cli/dist/utils/resource-features.js +5 -5
- package/cli/dist/utils/service-manifest.js +1 -0
- package/cli/dist/utils/service-urls.js +11 -0
- package/cli/dist/utils/startup-report.js +4 -3
- package/cli/dist/utils/tar.js +33 -28
- package/cli/dist/utils/tilt-process.js +4 -3
- package/cli/dist/utils/windows-doctor.js +18 -15
- package/cli/dist-bundle/{chunk-IGN5UC3P.js → chunk-4J2FLFUA.js} +14 -7
- package/cli/dist-bundle/{chunk-ETKUVKJ2.js → chunk-BMHPPX2Q.js} +10 -10
- package/cli/dist-bundle/chunk-BP2M4OJ4.js +2 -0
- package/cli/dist-bundle/{chunk-53L2JN7G.js → chunk-CGTWXWBU.js} +1 -1
- package/cli/dist-bundle/chunk-GPUJ55MI.js +3 -0
- package/cli/dist-bundle/{chunk-5QBYM33Q.js → chunk-LUUJ4JB7.js} +1 -1
- package/cli/dist-bundle/chunk-PEZQEZVV.js +17 -0
- package/cli/dist-bundle/{chunk-MWOVYIBB.js → chunk-SFOFTVYM.js} +5 -5
- package/cli/dist-bundle/{chunk-3XODDLRJ.js → chunk-TIYTGN5R.js} +1 -1
- package/cli/dist-bundle/{chunk-YNCBBJ4V.js → chunk-U42XZWXB.js} +1 -1
- package/cli/dist-bundle/chunk-ULMQVD7J.js +122 -0
- package/cli/dist-bundle/chunk-V36PT2HF.js +24 -0
- package/cli/dist-bundle/{chunk-I7TGIK5N.js → chunk-VMVTECCK.js} +1 -1
- package/cli/dist-bundle/{chunk-DU2BN46C.js → chunk-Z5QEE4SW.js} +1 -1
- package/cli/dist-bundle/cli.js +607 -375
- package/cli/dist-bundle/cold-preflight-3BNDOLGS.js +2 -0
- package/cli/dist-bundle/{demo-stage-OHNTP2UJ.js → demo-stage-G4W23BXW.js} +1 -1
- package/cli/dist-bundle/{extension-fetch-VEFSCG4B.js → extension-fetch-KIPYDIRM.js} +1 -1
- package/cli/dist-bundle/{open-XROJ6YEY.js → open-HQFT6WDC.js} +1 -1
- package/cli/dist-bundle/project-JZO2CPZZ.js +2 -0
- package/cli/dist-bundle/{project-templates-EYSQG2EO.js → project-templates-IMJ3OX5I.js} +1 -1
- package/cli/dist-bundle/{services-BIMF6MVU.js → services-GLFKZBBZ.js} +1 -1
- package/cli/dist-bundle/{tilt-port-owner-ZY5M2JC4.js → tilt-port-owner-PJN4MPYE.js} +1 -1
- package/cli/package.json +1 -1
- package/package.json +1 -1
- package/runtime/engine/topologies/platform/docker/compose/compose.star +40 -4
- package/runtime/engine/topologies/platform/docker/dockerfile/language_dockerfile.star +6 -0
- package/runtime/engine/topologies/platform/docker/dockerfile/next_dockerfile.star +83 -0
- package/runtime/engine/topologies/platform/docker/index.star +4 -0
- package/runtime/engine/topologies/platform/docker/layers/prisma/filesystem_setup.star +7 -0
- package/runtime/engine/topologies/platform/docker/networking/traefik.star +85 -46
- package/runtime/engine/topologies/tilt/common/utils_output.star +1 -0
- package/runtime/engine/topologies/tilt/generators/validators.star +1 -1
- package/runtime/engine/topologies/tilt/generators/vite/frontend.star +2 -2
- package/runtime/engine/topologies/tilt/generators/vite/generators.star +3 -0
- package/runtime/engine/topologies/tilt/manifest/constants.star +3 -0
- package/runtime/engine/topologies/tilt/manifest/parser.star +9 -3
- package/runtime/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +8 -3
- package/runtime/engine/topologies/tilt/resources/orchestrator/generators/dependencies/resolver.star +50 -24
- package/runtime/engine/topologies/tilt/resources/orchestrator/generators/env.star +11 -2
- package/runtime/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +23 -9
- package/cli/dist-bundle/chunk-43XWL2PB.js +0 -61
- package/cli/dist-bundle/chunk-5F7YP5IR.js +0 -17
- package/cli/dist-bundle/chunk-K6SOCO6V.js +0 -3
- package/cli/dist-bundle/chunk-YHYRGWUH.js +0 -24
- package/cli/dist-bundle/chunk-YL56J2WX.js +0 -2
- package/cli/dist-bundle/cold-preflight-ZOPR7PWJ.js +0 -2
- package/cli/dist-bundle/project-7PTIOHGF.js +0 -2
|
@@ -291,7 +291,8 @@ export async function checkColima() {
|
|
|
291
291
|
name: "Colima",
|
|
292
292
|
didPass: false,
|
|
293
293
|
message: status.problem ?? "The Colima VM is not running",
|
|
294
|
-
fix: status.fix
|
|
294
|
+
fix: status.fix ??
|
|
295
|
+
"Start Colima: colima start\n Verify: colima status\n Docs: https://github.com/abiosoft/colima",
|
|
295
296
|
};
|
|
296
297
|
}
|
|
297
298
|
export async function checkDockerRuntime() {
|
|
@@ -309,7 +310,7 @@ export async function checkDockerRuntime() {
|
|
|
309
310
|
name: CONTAINER_RUNTIME_CHECK,
|
|
310
311
|
didPass: false,
|
|
311
312
|
message: "Docker is not running",
|
|
312
|
-
fix:
|
|
313
|
+
fix: "Start Docker Desktop, enable WSL2 integration, then retry: flisk doctor\n Verify: docker ps\n Docs: https://docs.docker.com/desktop/wsl/",
|
|
313
314
|
};
|
|
314
315
|
}
|
|
315
316
|
}
|
|
@@ -327,8 +328,8 @@ export async function checkDockerRuntime() {
|
|
|
327
328
|
return {
|
|
328
329
|
name: CONTAINER_RUNTIME_CHECK,
|
|
329
330
|
didPass: false,
|
|
330
|
-
message: "Docker daemon is not responding",
|
|
331
|
-
fix:
|
|
331
|
+
message: "Docker daemon is not responding (timeout)",
|
|
332
|
+
fix: "Restart Docker daemon: systemctl restart docker (Linux) or restart Docker Desktop/Colima\n Verify: docker ps\n Docs: https://docs.docker.com/config/daemon/",
|
|
332
333
|
};
|
|
333
334
|
}
|
|
334
335
|
}
|
|
@@ -349,7 +350,7 @@ export async function checkDockerRuntime() {
|
|
|
349
350
|
name: CONTAINER_RUNTIME_CHECK,
|
|
350
351
|
didPass: false,
|
|
351
352
|
message: "Colima is installed but not running",
|
|
352
|
-
fix:
|
|
353
|
+
fix: "Start Colima: colima start\n Verify: colima status\n Docs: https://github.com/abiosoft/colima",
|
|
353
354
|
};
|
|
354
355
|
}
|
|
355
356
|
if (await succeeds("podman ps")) {
|
|
@@ -364,14 +365,14 @@ export async function checkDockerRuntime() {
|
|
|
364
365
|
name: CONTAINER_RUNTIME_CHECK,
|
|
365
366
|
didPass: false,
|
|
366
367
|
message: "Docker daemon is not running",
|
|
367
|
-
fix:
|
|
368
|
+
fix: "Start Docker daemon: systemctl start docker (Linux) or start Docker Desktop\n Verify: docker ps\n Docs: https://docs.docker.com/engine/daemon/",
|
|
368
369
|
};
|
|
369
370
|
}
|
|
370
371
|
return {
|
|
371
372
|
name: CONTAINER_RUNTIME_CHECK,
|
|
372
373
|
didPass: false,
|
|
373
|
-
message: "Docker is not
|
|
374
|
-
fix:
|
|
374
|
+
message: "Docker is not installed",
|
|
375
|
+
fix: "Install Docker: https://docs.docker.com/get-docker/\n Verify: docker --version\n After install, start Docker and retry: flisk doctor",
|
|
375
376
|
};
|
|
376
377
|
}
|
|
377
378
|
export const checkDockerCompose = createExecCheck("Docker Compose", "docker compose version", "Docker Compose plugin available", "Docker Compose plugin not found", "Install Docker Compose: https://docs.docker.com/compose/install/");
|
|
@@ -442,7 +443,7 @@ export async function checkDockerVersions(exec = execAsync) {
|
|
|
442
443
|
name: "Docker Versions",
|
|
443
444
|
didPass: false,
|
|
444
445
|
message: `Docker is too old for generated healthchecks (they use start_interval):\n ${problems.join("\n ")}`,
|
|
445
|
-
fix: "Update Docker Desktop, or
|
|
446
|
+
fix: "Update Docker: Docker Desktop (auto-updates), or Linux: apt/yum/dnf update docker-ce docker-compose-plugin\n Verify: docker version --format '{{.Server.Version}}' && docker compose version --short\n Docs: https://docs.docker.com/engine/install/",
|
|
446
447
|
};
|
|
447
448
|
}
|
|
448
449
|
checkDockerVersions.requiresContainerRuntime = true;
|
|
@@ -457,7 +458,7 @@ export async function checkTilt(exec = execAsync) {
|
|
|
457
458
|
name: "Tilt CLI",
|
|
458
459
|
didPass: false,
|
|
459
460
|
message: "Tilt CLI not found",
|
|
460
|
-
fix:
|
|
461
|
+
fix: "Install Tilt: curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash\n Verify: tilt version\n Docs: https://docs.tilt.dev/install.html",
|
|
461
462
|
};
|
|
462
463
|
}
|
|
463
464
|
if (!versionMeetsMinimum(raw, MIN_TILT_VERSION)) {
|
|
@@ -465,7 +466,7 @@ export async function checkTilt(exec = execAsync) {
|
|
|
465
466
|
name: "Tilt CLI",
|
|
466
467
|
didPass: false,
|
|
467
468
|
message: `Tilt ${raw || "version unknown"} is below the required v${MIN_TILT_VERSION.join(".")} floor`,
|
|
468
|
-
fix: `
|
|
469
|
+
fix: `Upgrade Tilt to v${MIN_TILT_VERSION.join(".")} or newer: https://docs.tilt.dev/install.html\n Verify: tilt version\n If using Homebrew: brew upgrade tilt`,
|
|
469
470
|
};
|
|
470
471
|
}
|
|
471
472
|
return { name: "Tilt CLI", didPass: true, message: `Tilt ${raw} is available` };
|
|
@@ -490,7 +491,7 @@ export async function checkDockerOperatingSystem(exec = execAsync) {
|
|
|
490
491
|
name: "Docker OS",
|
|
491
492
|
didPass: false,
|
|
492
493
|
message: `Docker is using the ${osType || "unknown"} engine; Flisk requires Linux containers`,
|
|
493
|
-
fix: "Switch Docker Desktop to Linux containers
|
|
494
|
+
fix: "Switch Docker Desktop to Linux containers: Settings > General > Use the WSL 2 based engine\n Or on Windows: docker context use default (if using WSL2 backend)\n Verify: docker info --format '{{.OSType}}'\n Docs: https://docs.docker.com/desktop/wsl/",
|
|
494
495
|
};
|
|
495
496
|
}
|
|
496
497
|
return { name: "Docker OS", didPass: true, message: "Docker is using Linux containers" };
|
|
@@ -522,7 +523,7 @@ export async function checkBun(exec = execAsync) {
|
|
|
522
523
|
name: "Bun",
|
|
523
524
|
didPass: false,
|
|
524
525
|
message: `Bun ${rawVersion || "version unknown"} is below the required ${MIN_BUN_VERSION.join(".")} floor`,
|
|
525
|
-
fix: `
|
|
526
|
+
fix: `Upgrade Bun to ${MIN_BUN_VERSION.join(".")} or newer: https://bun.sh/install\n Verify: bun --version\n If using npm: npm install -g bun@latest`,
|
|
526
527
|
};
|
|
527
528
|
}
|
|
528
529
|
return {
|
|
@@ -535,8 +536,8 @@ export async function checkBun(exec = execAsync) {
|
|
|
535
536
|
return {
|
|
536
537
|
name: "Bun",
|
|
537
538
|
didPass: false,
|
|
538
|
-
message: "Bun is not
|
|
539
|
-
fix:
|
|
539
|
+
message: "Bun is not installed",
|
|
540
|
+
fix: "Install Bun: curl -fsSL https://bun.sh/install | bash\n Verify: bun --version\n Docs: https://bun.sh/docs/installation",
|
|
540
541
|
};
|
|
541
542
|
}
|
|
542
543
|
}
|
|
@@ -27,6 +27,7 @@ async function tiltResourceNames(port) {
|
|
|
27
27
|
}
|
|
28
28
|
export const logsCommand = new Command("logs")
|
|
29
29
|
.description("Print a bounded snapshot of recent logs from the running stack")
|
|
30
|
+
.showHelpAfterError("\nService names are not positional. Use: flisk logs --service <name>")
|
|
30
31
|
.option("-s, --service <names...>", "Only these services (Tilt resource names)")
|
|
31
32
|
.option("--tail <n>", "Number of most recent lines to return", String(DEFAULT_LOG_TAIL))
|
|
32
33
|
.option("--since <duration>", "Only logs newer than this duration, for example 30s, 5m, 1h")
|
|
@@ -214,6 +214,7 @@ const DEFAULT_PROJECT_JSON = {
|
|
|
214
214
|
overrides: {},
|
|
215
215
|
};
|
|
216
216
|
export const projectCommand = addYesOption(new Command("project"))
|
|
217
|
+
.alias("init")
|
|
217
218
|
.description("Initialize or validate project-level master configuration")
|
|
218
219
|
.argument("[template]", `Clone a starter example instead of a blank project (${Object.keys(PROJECT_TEMPLATES).join(", ")})`)
|
|
219
220
|
.option("--path <dir>", "Directory to clone the template into (default: the template's repo name)")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) 2026 Flisk Landscape contributors
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { existsSync, mkdirSync } from "node:fs";
|
|
4
|
-
import { isAbsolute, relative, resolve } from "node:path";
|
|
4
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
import { DEFAULT_AGENT_RUNTIME, resolveAgentDurability, resolveAgentRuntime, } from "../agent-runtimes/registry.js";
|
|
@@ -90,7 +90,7 @@ export function resolveResourcePort(resourceType, value, assignedPort, resources
|
|
|
90
90
|
}
|
|
91
91
|
return port;
|
|
92
92
|
}
|
|
93
|
-
export function createServiceJson(name, type, stack, port, extraFeatures = [], frameworkId, languageId, agent) {
|
|
93
|
+
export function createServiceJson(name, type, stack, port, extraFeatures = [], frameworkId, languageId, agent, basePath) {
|
|
94
94
|
const framework = resolveFrontendFramework(type, frameworkId);
|
|
95
95
|
// Only an explicit selection is persisted: a missing `language` has always meant Bun.
|
|
96
96
|
const language = languageId === undefined ? undefined : resolveBackendLanguage(type, languageId);
|
|
@@ -129,11 +129,15 @@ export function createServiceJson(name, type, stack, port, extraFeatures = [], f
|
|
|
129
129
|
stack,
|
|
130
130
|
port,
|
|
131
131
|
...(framework ? { framework: framework.id } : {}),
|
|
132
|
+
// A server framework (Next.js) is served under /<name>/ with a base path, so the engine's default /health probe finds
|
|
133
|
+
// nothing there. Probe the framework's own health route under the same prefix.
|
|
134
|
+
...(framework?.kind === "server" ? { healthCheckPath: `/${name}/health` } : {}),
|
|
132
135
|
...(backendFramework && type !== "mcp" && type !== "agent"
|
|
133
136
|
? { framework: backendFramework.id }
|
|
134
137
|
: {}),
|
|
135
138
|
...(language ? { language: language.id } : {}),
|
|
136
139
|
...(type === "agent" && agent ? { agent } : {}),
|
|
140
|
+
...(type === "frontend" && basePath ? { basePath } : {}),
|
|
137
141
|
};
|
|
138
142
|
}
|
|
139
143
|
export function createByoServiceJson(name, stack, port, options) {
|
|
@@ -173,20 +177,26 @@ export function createPackageJson(name, type, frameworkId, prismaEnabled = false
|
|
|
173
177
|
version: "0.0.1",
|
|
174
178
|
type: "module",
|
|
175
179
|
scripts: {
|
|
176
|
-
...(isFrontend
|
|
180
|
+
...(isFrontend && !framework?.scripts
|
|
177
181
|
? {
|
|
178
182
|
predev: requireGeneratedViteConfig(`${AUTOGENERATED_DIR}/vite.config.frontend.autogenerated.ts`),
|
|
179
183
|
prebuild: requireGeneratedViteConfig(`${AUTOGENERATED_DIR}/vite.config.build.autogenerated.ts`),
|
|
180
184
|
}
|
|
181
185
|
: {}),
|
|
182
186
|
dev: isFrontend
|
|
183
|
-
?
|
|
187
|
+
? (framework?.scripts?.dev ??
|
|
188
|
+
`vite --config ${AUTOGENERATED_DIR}/vite.config.frontend.autogenerated.ts`)
|
|
184
189
|
: "bun run --watch src/index.ts",
|
|
185
190
|
build: isFrontend
|
|
186
|
-
?
|
|
191
|
+
? (framework?.scripts?.build ??
|
|
192
|
+
`tsc && vite build --config ${AUTOGENERATED_DIR}/vite.config.build.autogenerated.ts`)
|
|
187
193
|
: "tsc",
|
|
188
|
-
// Backends and workers run `start` in their production image; doctor checks for it.
|
|
189
|
-
...(isFrontend
|
|
194
|
+
// Backends and workers run `start` in their production image; doctor checks for it. A server framework brings its own.
|
|
195
|
+
...(isFrontend
|
|
196
|
+
? framework?.scripts?.start
|
|
197
|
+
? { start: framework.scripts.start }
|
|
198
|
+
: {}
|
|
199
|
+
: { start: "bun run dist/index.js" }),
|
|
190
200
|
test: "vitest",
|
|
191
201
|
lint: "biome check .",
|
|
192
202
|
"lint:fix": "biome check . --write",
|
|
@@ -220,7 +230,7 @@ export function createPackageJson(name, type, frameworkId, prismaEnabled = false
|
|
|
220
230
|
"@biomejs/biome": "^2.5.13",
|
|
221
231
|
// vitest 5 peers on vite ^6.4.0 || ^7 || ^8; vite 5 makes `vitest run` fail at startup with
|
|
222
232
|
// ERR_PACKAGE_PATH_NOT_EXPORTED ('./module-runner'), in every resource once it is hoisted.
|
|
223
|
-
...(isFrontend ? { vite: "^6.4.1" } : {}),
|
|
233
|
+
...(isFrontend && !framework?.scripts ? { vite: "^6.4.1" } : {}),
|
|
224
234
|
...(drizzleEnabled ? { "drizzle-kit": DRIZZLE_KIT_RANGE, "@types/pg": "^8.11.0" } : {}),
|
|
225
235
|
...(framework?.devDependencies ?? {}),
|
|
226
236
|
...(backendFramework?.devDependencies ?? {}),
|
|
@@ -269,6 +279,9 @@ export default defineConfig({
|
|
|
269
279
|
`;
|
|
270
280
|
export function createResourceTsconfig(resourceType, frameworkId) {
|
|
271
281
|
const framework = resolveFrontendFramework(resourceType, frameworkId);
|
|
282
|
+
// A server framework (Next.js) owns its compiler settings: the SPA template's `rootDir`, `outDir` and declaration output do not apply.
|
|
283
|
+
if (framework?.tsconfig)
|
|
284
|
+
return framework.tsconfig;
|
|
272
285
|
const backendFramework = resolveBackendFramework(resourceType, frameworkId);
|
|
273
286
|
return {
|
|
274
287
|
...TSCONFIG_TEMPLATE,
|
|
@@ -380,7 +393,7 @@ export const resourceCommand = addYesOption(new Command("resource"))
|
|
|
380
393
|
.description("Create a new resource (service) from scratch, or register an existing one")
|
|
381
394
|
.argument("[name]", "Resource name (kebab-case)")
|
|
382
395
|
.option("-t, --type <type>", "Resource type: backend, frontend, worker, mcp, agent, bring-your-own, sdk", "backend")
|
|
383
|
-
.option("--framework <id>", "Framework: frontend react (default), vue, svelte, preact, lit, solid, qwik, vanilla, tanstack-router; backend hono (default), express, elysia, fastify, nestjs, koa, h3 (list them with --frameworks)")
|
|
396
|
+
.option("--framework <id>", "Framework: frontend react (default), vue, svelte, preact, lit, solid, qwik, vanilla, tanstack-router, next; backend hono (default), express, elysia, fastify, nestjs, koa, h3 (list them with --frameworks)")
|
|
384
397
|
.option("--frameworks", "List registered frameworks for the resource type and exit (--type frontend, --type backend)")
|
|
385
398
|
.option("--language <id>", "Backend language: bun (default), python, go, rust, node")
|
|
386
399
|
.option("-s, --stack <stack>", "Stack to assign resource to", "default")
|
|
@@ -394,6 +407,7 @@ export const resourceCommand = addYesOption(new Command("resource"))
|
|
|
394
407
|
.option("--restart <policy>", "Compose restart policy: no, on-failure, unless-stopped, always (bring-your-own only; use no for one-shot jobs)")
|
|
395
408
|
.option("--image <name>", "Docker image name instead of building from Dockerfile (for bring-your-own)")
|
|
396
409
|
.option("--port <port>", "Port for the resource, within its type's range (default: next free port in that range)")
|
|
410
|
+
.option("--base-path <path>", "Base path for frontend routing (e.g., /storefront). Default is /{stack}s. Frontend only.")
|
|
397
411
|
.option("--feature <feature...>", "Enable a resource feature (for example: prisma, drizzle)")
|
|
398
412
|
.option("--runtime <id>", "Agent runtime (for agent only): custom (default)")
|
|
399
413
|
.option("--durability <kind>", "Where agent sessions are checkpointed (for agent only): files (default, named volume) or postgres")
|
|
@@ -757,6 +771,10 @@ This file contains the resource configuration for Flisk.
|
|
|
757
771
|
}
|
|
758
772
|
if (resourceType === "frontend") {
|
|
759
773
|
mkdirSync(resolve(fullPath, "public"), { recursive: true });
|
|
774
|
+
// A framework can keep its files in folders of its own (Next.js: src/app).
|
|
775
|
+
for (const file of frontendFramework?.createFiles(resourceName) ?? []) {
|
|
776
|
+
mkdirSync(dirname(resolve(fullPath, file.filename)), { recursive: true });
|
|
777
|
+
}
|
|
760
778
|
}
|
|
761
779
|
if (dddEnabled) {
|
|
762
780
|
for (const layer of ["domain", "application", "infrastructure", "presentation"]) {
|
|
@@ -766,11 +784,19 @@ This file contains the resource configuration for Flisk.
|
|
|
766
784
|
// Prepare file generation tasks
|
|
767
785
|
const prismaEnabled = requestedFeatures.includes("prisma");
|
|
768
786
|
const drizzleEnabled = requestedFeatures.includes("drizzle");
|
|
769
|
-
const serviceJson = createServiceJson(resourceName, resourceType, stackName, assignedPort, [...(dddEnabled ? ["ddd"] : []), ...requestedFeatures], scaffoldFrameworkId, options.language === undefined ? undefined : backendLanguage?.id, agentConfig);
|
|
787
|
+
const serviceJson = createServiceJson(resourceName, resourceType, stackName, assignedPort, [...(dddEnabled ? ["ddd"] : []), ...requestedFeatures], scaffoldFrameworkId, options.language === undefined ? undefined : backendLanguage?.id, agentConfig, resourceType === "frontend" ? options.basePath : undefined);
|
|
770
788
|
const packageJson = createPackageJson(resourceName, resourceType, scaffoldFrameworkId, prismaEnabled, agentConfig, drizzleEnabled);
|
|
771
789
|
if (prismaEnabled && resourceType === "backend") {
|
|
772
790
|
serviceJson.dependsOn = [`${resourceName}-migrator`];
|
|
773
791
|
}
|
|
792
|
+
// A frontend is wired to the first backend already in its stack, so its starter can call that API out of the box.
|
|
793
|
+
const frontendBackendName = resourceType === "frontend"
|
|
794
|
+
? allResources.find((resource) => resource.stack === stackName && resource.type === "backend")?.name
|
|
795
|
+
: undefined;
|
|
796
|
+
if (frontendBackendName) {
|
|
797
|
+
serviceJson.backendName = frontendBackendName;
|
|
798
|
+
serviceJson.dependsOn = [frontendBackendName];
|
|
799
|
+
}
|
|
774
800
|
const tasks = [
|
|
775
801
|
{
|
|
776
802
|
type: "json",
|
|
@@ -829,7 +855,9 @@ This file contains the resource configuration for Flisk.
|
|
|
829
855
|
});
|
|
830
856
|
}
|
|
831
857
|
else if (resourceType === "frontend") {
|
|
832
|
-
tasks.push(...(frontendFramework
|
|
858
|
+
tasks.push(...(frontendFramework
|
|
859
|
+
?.createFiles(resourceName, { backendName: frontendBackendName })
|
|
860
|
+
.map((file) => ({
|
|
833
861
|
...file,
|
|
834
862
|
type: "text",
|
|
835
863
|
})) ?? []));
|
|
@@ -55,6 +55,15 @@ export const statusCommand = new Command("status")
|
|
|
55
55
|
// The same look at Tilt feeds readiness and each service's state, so `flisk status` and `flisk ui` cannot disagree.
|
|
56
56
|
let serviceStates = null;
|
|
57
57
|
const tiltPort = getTiltPollingPort(process.env.TILT_PORT);
|
|
58
|
+
// Port plan is best effort; service URLs are shown in both JSON and terminal output.
|
|
59
|
+
let portPlan = null;
|
|
60
|
+
try {
|
|
61
|
+
const root = findProjectRoot();
|
|
62
|
+
portPlan = root ? await getHostPortPlan(root, { inspectDocker: false }) : null;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Port planning failed; service URLs will be omitted from terminal output.
|
|
66
|
+
}
|
|
58
67
|
if (tiltAvailable) {
|
|
59
68
|
const text = await tiltGetUiResources(tiltPort);
|
|
60
69
|
// `tilt get` has no -f flag; resources come from the running Tilt's API on its port, the same source as readiness.
|
|
@@ -89,14 +98,6 @@ export const statusCommand = new Command("status")
|
|
|
89
98
|
}
|
|
90
99
|
}
|
|
91
100
|
if (options.json) {
|
|
92
|
-
let portPlan = null;
|
|
93
|
-
try {
|
|
94
|
-
const root = findProjectRoot();
|
|
95
|
-
portPlan = root ? await getHostPortPlan(root, { inspectDocker: false }) : null;
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
// Port planning is best effort; stack ports then list only the Tilt UI.
|
|
99
|
-
}
|
|
100
101
|
const errors = queryError ? [{ code: "TILT_STATUS_UNAVAILABLE", message: queryError }] : [];
|
|
101
102
|
const data = {
|
|
102
103
|
tilt: {
|
|
@@ -178,9 +179,17 @@ export const statusCommand = new Command("status")
|
|
|
178
179
|
: state.status === "error"
|
|
179
180
|
? chalk.red("✗")
|
|
180
181
|
: chalk.yellow("…");
|
|
181
|
-
|
|
182
|
+
const ports = buildServicePorts(resource, portPlan?.ingressHttp);
|
|
183
|
+
const urlPart = ports.url ? ` → ${ports.url}` : "";
|
|
184
|
+
showDetail(`${mark} ${resource.name}${urlPart} ${state.status}${state.reason ? ` (${state.reason})` : ""}`);
|
|
182
185
|
}
|
|
183
186
|
}
|
|
187
|
+
else if (discovery.resources.length > 0) {
|
|
188
|
+
// Without a running Tilt, no service state can be read. Say so instead of printing nothing.
|
|
189
|
+
console.log();
|
|
190
|
+
console.log(chalk.bold("Services:"));
|
|
191
|
+
showDetail(`unknown: no Tilt answers on port ${tiltPort}, so nothing is confirmed running. Start with: flisk up`);
|
|
192
|
+
}
|
|
184
193
|
if (options.tilt && tiltAvailable) {
|
|
185
194
|
console.log();
|
|
186
195
|
showStep("Tilt Resources:");
|
package/cli/dist/commands/up.js
CHANGED
|
@@ -24,10 +24,11 @@ import { nativeWindowsStackRefusal } from "../utils/native-windows.js";
|
|
|
24
24
|
import { findProjectRoot, getPackageVersion } from "../utils/paths.js";
|
|
25
25
|
import { findAvailablePort } from "../utils/port-assignment.js";
|
|
26
26
|
import { formatPortFallbackNotice } from "../utils/port-fallback-notice.js";
|
|
27
|
+
import { findPortOccupant } from "../utils/port-occupant.js";
|
|
27
28
|
import { isApiServiceType } from "../utils/resource-kind.js";
|
|
28
29
|
import { enforceSchemaVersionGate } from "../utils/schema-version-gate.js";
|
|
29
30
|
import { enforceServiceConfigGate, warnSchemaVersions } from "../utils/service-config-checks.js";
|
|
30
|
-
import { appendHealthPath, resolveSubdomainBases } from "../utils/service-urls.js";
|
|
31
|
+
import { appendHealthPath, resolveServiceUrl, resolveSubdomainBases, } from "../utils/service-urls.js";
|
|
31
32
|
import { discoverResourcesStrict, discoverResourcesWithProblems, discoverStacks, getResourcesForStack, stackExists, warnAboutSkippedFiles, } from "../utils/services.js";
|
|
32
33
|
import { buildSmokePlans, formatSmokeFailure, runSmokePlans, safeSmokeDeps, } from "../utils/smoke.js";
|
|
33
34
|
import { buildStartupReport, formatStartupReport, isStartupStalled, } from "../utils/startup-report.js";
|
|
@@ -38,16 +39,53 @@ import { parseTiltPort, resolveTiltPort, secondUpAction, stopTiltForUp, } from "
|
|
|
38
39
|
import { findUnknownServices, resolveOnlySelection } from "../utils/up-only.js";
|
|
39
40
|
import { tiltGetUiResources, waitForTiltResourcesReady } from "../utils/up-readiness.js";
|
|
40
41
|
import { enableDiscoveredStacks } from "./project.js";
|
|
41
|
-
export function formatUpSuccess(port,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
export function formatUpSuccess(port, options = {}) {
|
|
43
|
+
const { appUrls = [], serviceHealth = [], showLogsHint = serviceHealth.length > 0 } = options;
|
|
44
|
+
const lines = [];
|
|
45
|
+
lines.push("Flisk is up.");
|
|
46
|
+
lines.push(`Tilt UI: http://localhost:${port}`);
|
|
47
|
+
if (serviceHealth.length > 0) {
|
|
48
|
+
lines.push("Services:");
|
|
49
|
+
for (const svc of serviceHealth.slice(0, 10)) {
|
|
50
|
+
const icon = svc.status === "ready" ? "✓" : svc.status === "failed" ? "✗" : "⟳";
|
|
51
|
+
const urlPart = svc.url ? ` → ${svc.url}` : "";
|
|
52
|
+
lines.push(` ${icon} ${svc.name}${urlPart}`);
|
|
53
|
+
}
|
|
54
|
+
if (serviceHealth.length > 10) {
|
|
55
|
+
lines.push(` … and ${serviceHealth.length - 10} more`);
|
|
56
|
+
}
|
|
57
|
+
// Health check URLs for test infrastructure and monitoring
|
|
58
|
+
lines.push("Health checks:");
|
|
59
|
+
for (const svc of serviceHealth.slice(0, 10)) {
|
|
60
|
+
const icon = svc.status === "ready" ? "✓" : svc.status === "failed" ? "✗" : "⟳";
|
|
61
|
+
if (svc.url) {
|
|
62
|
+
const healthUrl = svc.url.endsWith("/health") ? svc.url : `${svc.url}/health`;
|
|
63
|
+
lines.push(` ${icon} ${svc.name}: ${healthUrl}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (serviceHealth.length > 10) {
|
|
67
|
+
lines.push(` … and ${serviceHealth.length - 10} more`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else if (appUrls.length > 0) {
|
|
71
|
+
lines.push("App URLs:");
|
|
72
|
+
for (const url of appUrls.slice(0, 5)) {
|
|
73
|
+
lines.push(` ${url}`);
|
|
74
|
+
}
|
|
75
|
+
if (appUrls.length > 5) {
|
|
76
|
+
lines.push(` … and ${appUrls.length - 5} more`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
lines.push("App URLs:");
|
|
81
|
+
lines.push(" run: flisk networks");
|
|
82
|
+
}
|
|
83
|
+
lines.push("Stop: flisk down");
|
|
84
|
+
if (showLogsHint) {
|
|
85
|
+
lines.push("Logs: flisk logs <service>");
|
|
86
|
+
}
|
|
87
|
+
lines.push("Next: flisk help # or visit https://flisk.sh/docs");
|
|
88
|
+
return lines;
|
|
51
89
|
}
|
|
52
90
|
/**
|
|
53
91
|
* The native Windows boundary for a command that starts a stack.
|
|
@@ -366,8 +404,13 @@ export const upCommand = new Command("up")
|
|
|
366
404
|
console.log(chalk.gray(`Refreshed runtime assets: ${copiedAssets.join(", ")}`));
|
|
367
405
|
}
|
|
368
406
|
const newlyEnabledStacks = enableDiscoveredStacks(projectRoot, discoveredResources);
|
|
369
|
-
if (newlyEnabledStacks.length > 0
|
|
370
|
-
|
|
407
|
+
if (newlyEnabledStacks.length > 0) {
|
|
408
|
+
// project.json just changed, so the generated spec.master is now stale. Regenerate it here, or the next
|
|
409
|
+
// `flisk config verify` reports it out of sync.
|
|
410
|
+
await generateMasterConfigs(projectRoot, { quiet: options.quiet });
|
|
411
|
+
if (!options.quiet) {
|
|
412
|
+
console.log(chalk.gray(`Added ${newlyEnabledStacks.join(", ")} to the pre_alpha phase in .flisk/project.json`));
|
|
413
|
+
}
|
|
371
414
|
}
|
|
372
415
|
}
|
|
373
416
|
let servicesToStart;
|
|
@@ -413,7 +456,7 @@ export const upCommand = new Command("up")
|
|
|
413
456
|
console.log(chalk.blue("\n🌍 Frontend URLs:"));
|
|
414
457
|
for (const svc of frontends) {
|
|
415
458
|
const basePath = svc.config?.basePath ?? `/${svc.name}`;
|
|
416
|
-
console.log(chalk.gray(` - ${svc.name}: ${appBase}${chalk.cyan(
|
|
459
|
+
console.log(chalk.gray(` - ${svc.name}: ${appBase}${chalk.cyan(basePath)}`));
|
|
417
460
|
}
|
|
418
461
|
}
|
|
419
462
|
if (backends.length > 0) {
|
|
@@ -605,14 +648,35 @@ export const upCommand = new Command("up")
|
|
|
605
648
|
blocked: [],
|
|
606
649
|
starting: [],
|
|
607
650
|
};
|
|
608
|
-
const reportLines = formatStartupReport(report, readiness.timedOut);
|
|
651
|
+
const reportLines = formatStartupReport(report, readiness.timedOut, (port) => findPortOccupant(port));
|
|
609
652
|
if (reportLines.length > 0) {
|
|
610
653
|
showError(reportLines.join("\n"));
|
|
611
654
|
}
|
|
612
655
|
}
|
|
613
656
|
}
|
|
614
657
|
if (uiReady && !options.quiet && !startupFailed) {
|
|
615
|
-
|
|
658
|
+
const deferred = getDeferredResourceNames(discoveredResources);
|
|
659
|
+
const dependsOn = Object.fromEntries(discoveredResources.map((r) => [r.name, r.config?.dependsOn ?? []]));
|
|
660
|
+
const readiness = await waitForTiltResourcesReady(port, {
|
|
661
|
+
deferred,
|
|
662
|
+
isStalled: (text) => isStartupStalled(text, dependsOn, deferred),
|
|
663
|
+
});
|
|
664
|
+
const serviceHealth = readiness.enabled.map((name) => {
|
|
665
|
+
const failed = readiness.failures.find((f) => f.name === name);
|
|
666
|
+
const svc = discoveredResources.find((r) => r.name === name);
|
|
667
|
+
const isReady = !failed && !readiness.failures.some((f) => f.name === name);
|
|
668
|
+
const isStarting = !isReady && !failed;
|
|
669
|
+
const url = svc ? resolveServiceUrl(svc, hostPortPlan.ingressHttp) : undefined;
|
|
670
|
+
return {
|
|
671
|
+
name,
|
|
672
|
+
status: failed ? "failed" : isStarting ? "starting" : "ready",
|
|
673
|
+
url,
|
|
674
|
+
};
|
|
675
|
+
});
|
|
676
|
+
for (const line of formatUpSuccess(port, {
|
|
677
|
+
serviceHealth,
|
|
678
|
+
showLogsHint: true,
|
|
679
|
+
}))
|
|
616
680
|
console.log(chalk.blue(line));
|
|
617
681
|
printedSuccess = true;
|
|
618
682
|
}
|
|
@@ -673,7 +737,28 @@ export const upCommand = new Command("up")
|
|
|
673
737
|
process.exit(1);
|
|
674
738
|
}
|
|
675
739
|
if (!options.quiet && result.exitCode === 0 && !printedSuccess) {
|
|
676
|
-
|
|
740
|
+
const deferred = getDeferredResourceNames(discoveredResources);
|
|
741
|
+
const dependsOn = Object.fromEntries(discoveredResources.map((r) => [r.name, r.config?.dependsOn ?? []]));
|
|
742
|
+
const readiness = await waitForTiltResourcesReady(port, {
|
|
743
|
+
deferred,
|
|
744
|
+
isStalled: (text) => isStartupStalled(text, dependsOn, deferred),
|
|
745
|
+
});
|
|
746
|
+
const serviceHealth = readiness.enabled.map((name) => {
|
|
747
|
+
const failed = readiness.failures.find((f) => f.name === name);
|
|
748
|
+
const svc = discoveredResources.find((r) => r.name === name);
|
|
749
|
+
const isReady = !failed && !readiness.failures.some((f) => f.name === name);
|
|
750
|
+
const isStarting = !isReady && !failed;
|
|
751
|
+
const url = svc ? resolveServiceUrl(svc, hostPortPlan.ingressHttp) : undefined;
|
|
752
|
+
return {
|
|
753
|
+
name,
|
|
754
|
+
status: failed ? "failed" : isStarting ? "starting" : "ready",
|
|
755
|
+
url,
|
|
756
|
+
};
|
|
757
|
+
});
|
|
758
|
+
for (const line of formatUpSuccess(port, {
|
|
759
|
+
serviceHealth,
|
|
760
|
+
showLogsHint: true,
|
|
761
|
+
}))
|
|
677
762
|
console.log(chalk.blue(line));
|
|
678
763
|
}
|
|
679
764
|
};
|