@flisk-sh/flisk 1.4.5 → 1.4.7
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/README.md +37 -14
- package/cli/dist/agent-runtimes/custom.js +94 -13
- package/cli/dist/commands/agent.js +144 -123
- package/cli/dist/commands/doctor.js +62 -12
- package/cli/dist/commands/down.js +33 -1
- package/cli/dist/commands/logs.js +107 -3
- package/cli/dist/commands/project.js +27 -0
- package/cli/dist/commands/resource.js +3 -1
- package/cli/dist/commands/status.js +82 -34
- package/cli/dist/commands/ui.js +41 -2
- package/cli/dist/commands/up.js +173 -12
- package/cli/dist/commands/upgrade.js +2 -2
- package/cli/dist/components/DetailPanel.js +23 -3
- package/cli/dist/mcp/tools.js +9 -6
- package/cli/dist/types/index.js +0 -2
- package/cli/dist/utils/agent-format.js +18 -0
- package/cli/dist/utils/agent-sessions.js +122 -0
- package/cli/dist/utils/agent-skills.js +64 -0
- package/cli/dist/utils/bun-requirement.js +7 -4
- package/cli/dist/utils/cold-preflight.js +55 -32
- package/cli/dist/utils/doctor-fix-first.js +93 -0
- package/cli/dist/utils/doctor-native.js +96 -0
- package/cli/dist/utils/doctor-report.js +3 -0
- package/cli/dist/utils/native-project.js +41 -0
- package/cli/dist/utils/native-runtime.js +516 -0
- package/cli/dist/utils/native-service.js +95 -0
- package/cli/dist/utils/native-up.js +156 -0
- package/cli/dist/utils/platform.js +1 -1
- package/cli/dist/utils/project-templates.js +1 -1
- package/cli/dist/utils/service-manifest.js +10 -1
- package/cli/dist/utils/services.js +11 -1
- package/cli/dist/utils/startup-report.js +45 -3
- package/cli/dist/utils/status-groups.js +23 -0
- package/cli/dist/utils/tilt-logs.js +8 -0
- package/cli/dist/utils/up-hints.js +42 -0
- package/cli/dist/utils/up-progress.js +117 -0
- package/cli/dist/utils/up-readiness.js +5 -0
- package/cli/dist-bundle/{chunk-Z5QEE4SW.js → chunk-6OW2GVQV.js} +1 -1
- package/cli/dist-bundle/chunk-B2JOM3HN.js +35 -0
- package/cli/dist-bundle/{chunk-VMVTECCK.js → chunk-F2DLIM75.js} +1 -1
- package/cli/dist-bundle/chunk-L6O7TT3K.js +18 -0
- package/cli/dist-bundle/chunk-MFY7RZ76.js +130 -0
- package/cli/dist-bundle/{chunk-SFOFTVYM.js → chunk-MYAYJF4Q.js} +1 -1
- package/cli/dist-bundle/{chunk-LUUJ4JB7.js → chunk-P7GZ6NGM.js} +1 -1
- package/cli/dist-bundle/{chunk-BP2M4OJ4.js → chunk-QEBN6ZVQ.js} +1 -1
- package/cli/dist-bundle/{chunk-4J2FLFUA.js → chunk-T44DEEL6.js} +1 -1
- package/cli/dist-bundle/{chunk-U42XZWXB.js → chunk-TIPSERWO.js} +1 -1
- package/cli/dist-bundle/{chunk-V36PT2HF.js → chunk-VYPB7MY5.js} +1 -1
- package/cli/dist-bundle/{chunk-TIYTGN5R.js → chunk-XHENT4KO.js} +1 -1
- package/cli/dist-bundle/{chunk-CGTWXWBU.js → chunk-YTBTCAWY.js} +1 -1
- package/cli/dist-bundle/cli.js +486 -396
- package/cli/dist-bundle/cold-preflight-DQVJUGBB.js +2 -0
- package/cli/dist-bundle/{demo-stage-G4W23BXW.js → demo-stage-F4YA7UL2.js} +1 -1
- package/cli/dist-bundle/{open-HQFT6WDC.js → open-ZUSSVPD3.js} +1 -1
- package/cli/dist-bundle/project-JFCTPRP5.js +2 -0
- package/cli/dist-bundle/{project-templates-IMJ3OX5I.js → project-templates-2VQPUBSQ.js} +1 -1
- package/cli/dist-bundle/{services-GLFKZBBZ.js → services-DOHDHOCT.js} +1 -1
- package/cli/dist-bundle/{tilt-port-owner-PJN4MPYE.js → tilt-port-owner-XODQHXHX.js} +1 -1
- package/cli/package.json +1 -1
- package/cli/templates/agent-skills/flisk-agent-ops/SKILL.md +39 -0
- package/cli/templates/agent-skills/flisk-build/SKILL.md +85 -0
- package/cli/templates/agent-skills/flisk-doctor/SKILL.md +28 -0
- package/cli/templates/agent-skills/flisk-recipe-ai-agent/SKILL.md +45 -0
- package/cli/templates/agent-skills/flisk-recipe-blog/SKILL.md +36 -0
- package/cli/templates/agent-skills/flisk-recipe-saas/SKILL.md +39 -0
- package/cli/templates/agent-skills/flisk-recipe-shop/SKILL.md +41 -0
- package/cli/templates/agent-skills/flisk-recipe-starters/SKILL.md +46 -0
- package/cli/templates/agent-skills/flisk-resource/SKILL.md +27 -0
- package/cli/templates/agent-skills/flisk-status/SKILL.md +25 -0
- package/cli/templates/agent-skills/flisk-troubleshoot/SKILL.md +227 -0
- package/cli/templates/agent-skills/flisk-up/SKILL.md +37 -0
- package/examples/{tdk-example → flisk-example}/README.md +2 -2
- package/package.json +2 -2
- package/runtime/discovery/AGENTS.md +1 -1
- package/runtime/discovery/TILT_DISCOVERY.star +1 -1
- package/runtime/discovery/deterministic.star +1 -1
- package/runtime/engine/AGENTS.md +3 -3
- package/runtime/engine/README.md +1 -1
- package/runtime/engine/REFACTORING_MANIFEST_SYSTEM.md +1 -1
- package/runtime/engine/assets/docker/wake-gateway/decide.ts +1 -1
- package/runtime/engine/assets/docker/wake-gateway/server.ts +2 -2
- package/runtime/engine/assets/scripts/mac-m1-resource-monitor.sh +6 -6
- package/runtime/engine/schemas/service-schema.json +87 -21
- package/runtime/engine/scripts/debug-container.sh +1 -1
- package/runtime/engine/scripts/generate-manifest.sh +2 -2
- package/runtime/engine/topologies/platform/docker/compose/traefik_standalone.star +19 -4
- package/runtime/engine/topologies/platform/docker/constants.star +2 -2
- package/runtime/engine/topologies/platform/docker/networking/traefik_static_routes.star +2 -2
- package/runtime/engine/topologies/tilt/manifest/mapper.star +1 -1
- package/runtime/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +8 -2
- package/runtime/ext/ide-components/README.md +1 -1
- package/runtime/ext/ui-enhancements/extension.yaml +2 -2
- package/runtime/specs/specs/TILT_SERVICE_DEFAULTS.star +1 -1
- package/runtime/specs/specs/TILT_TECH_STACK.star +1 -1
- package/cli/dist-bundle/chunk-BMHPPX2Q.js +0 -35
- package/cli/dist-bundle/chunk-PEZQEZVV.js +0 -17
- package/cli/dist-bundle/chunk-ULMQVD7J.js +0 -122
- package/cli/dist-bundle/cold-preflight-3BNDOLGS.js +0 -2
- package/cli/dist-bundle/project-JZO2CPZZ.js +0 -2
- /package/examples/{tdk-example → flisk-example}/LICENSE +0 -0
- /package/examples/{tdk-example → flisk-example}/TILT_SERVICE_DEFAULTS.star +0 -0
- /package/examples/{tdk-example → flisk-example}/TILT_TECH_STACK.star +0 -0
- /package/examples/{tdk-example → flisk-example}/Tiltfile +0 -0
- /package/examples/{tdk-example → flisk-example}/services/platform/messaging/docker-compose.yml +0 -0
- /package/examples/{tdk-example → flisk-example}/services/shop/orders-api/package.json +0 -0
- /package/examples/{tdk-example → flisk-example}/services/shop/orders-api/service.json +0 -0
- /package/examples/{tdk-example → flisk-example}/services/shop/orders-api/src/index.ts +0 -0
- /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/index.html +0 -0
- /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/package.json +0 -0
- /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/public/.gitkeep +0 -0
- /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/service.json +0 -0
- /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/src/main.tsx +0 -0
- /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/src/orders.tsx +0 -0
- /package/examples/{tdk-example → flisk-example}/services/shop/orders-app/src/style.css +0 -0
- /package/examples/{tdk-example → flisk-example}/services/shop/orders-worker/package.json +0 -0
- /package/examples/{tdk-example → flisk-example}/services/shop/orders-worker/service.json +0 -0
- /package/examples/{tdk-example → flisk-example}/services/shop/orders-worker/src/index.ts +0 -0
|
@@ -14,7 +14,9 @@ import { AUTOGENERATED_DIR, CONTAINER_RUNTIME_CHECK, MASTER_CONFIG_FILES, PACKAG
|
|
|
14
14
|
import { isPathDiscovered, readDiscoveryPaths } from "../utils/discovery-paths.js";
|
|
15
15
|
import { checkCrashLoopingContainers } from "../utils/doctor-crashloop.js";
|
|
16
16
|
import { applyDoctorFixes, checkDuplicateFliskInstalls, hasFailedActions, planDoctorFixes, projectTiltPids, } from "../utils/doctor-fix.js";
|
|
17
|
+
import { formatFixFirst, rankDoctorFixes } from "../utils/doctor-fix-first.js";
|
|
17
18
|
import { checkDiskSpace, dockerDesktopStuck } from "../utils/doctor-host.js";
|
|
19
|
+
import { checkNativeCommands, checkNativeDependsOn, checkNativeRuntime, checkNativeServiceHealth, } from "../utils/doctor-native.js";
|
|
18
20
|
import { collectDoctorChecks, createDoctorReport, getDoctorExitCode, } from "../utils/doctor-report.js";
|
|
19
21
|
import { checkHealthRoutes, checkIngressPorts, checkPrivateNpmRegistry, checkTiltResourceHealth, projectConfigEnablesVerdaccio, summarizeAdvertisedProbes, summarizeServiceProbes, summarizeSmokeResults, } from "../utils/doctor-runtime.js";
|
|
20
22
|
import { checkStarlarkLoadExports } from "../utils/doctor-starlark.js";
|
|
@@ -25,6 +27,7 @@ import { evaluateFliskVersionFloor } from "../utils/flisk-version.js";
|
|
|
25
27
|
import { formatCount } from "../utils/formatting.js";
|
|
26
28
|
import { getHostPortPlan, resolveRuntimeIngressPort } from "../utils/host-port-config.js";
|
|
27
29
|
import { createHostPortPlan } from "../utils/host-port-plan.js";
|
|
30
|
+
import { findNativeProject } from "../utils/native-project.js";
|
|
28
31
|
import { findProjectRoot, getPackageVersion } from "../utils/paths.js";
|
|
29
32
|
import { promptConfirm } from "../utils/prompt.js";
|
|
30
33
|
import { EnvUnreadableError, envSecretValues, redactSecrets, redactValue, } from "../utils/secret-redaction.js";
|
|
@@ -1214,6 +1217,15 @@ function printDoctorResults(list) {
|
|
|
1214
1217
|
}
|
|
1215
1218
|
export function renderDoctorOutput(machineResults, projectResults, inProject, options = {}, allResults) {
|
|
1216
1219
|
const results = allResults ?? [...machineResults, ...projectResults];
|
|
1220
|
+
const printFixFirst = () => {
|
|
1221
|
+
const lines = formatFixFirst(rankDoctorFixes(results));
|
|
1222
|
+
if (lines.length === 0)
|
|
1223
|
+
return;
|
|
1224
|
+
console.log(chalk.bold("Fix first"));
|
|
1225
|
+
for (const line of lines)
|
|
1226
|
+
console.log(line);
|
|
1227
|
+
console.log("");
|
|
1228
|
+
};
|
|
1217
1229
|
if (options.all) {
|
|
1218
1230
|
console.log(chalk.bold("Machine"));
|
|
1219
1231
|
printDoctorResults(machineResults);
|
|
@@ -1228,10 +1240,12 @@ export function renderDoctorOutput(machineResults, projectResults, inProject, op
|
|
|
1228
1240
|
}
|
|
1229
1241
|
console.log("");
|
|
1230
1242
|
console.log(formatDoctorSummary(results));
|
|
1243
|
+
printFixFirst();
|
|
1231
1244
|
}
|
|
1232
1245
|
else {
|
|
1233
1246
|
printDoctorResults(results);
|
|
1234
1247
|
console.log("");
|
|
1248
|
+
printFixFirst();
|
|
1235
1249
|
const allPassed = results.every((check) => check.didPass || check.isSkipped || check.isWarning);
|
|
1236
1250
|
if (allPassed && !inProject) {
|
|
1237
1251
|
console.log(`${chalk.gray("○")} ${chalk.gray("Not in a Flisk project, so project checks were skipped")}`);
|
|
@@ -1285,8 +1299,13 @@ export const doctorCommand = new Command("doctor")
|
|
|
1285
1299
|
process.exit(2);
|
|
1286
1300
|
return;
|
|
1287
1301
|
}
|
|
1302
|
+
// A project whose services all declare `command` runs as local processes: doctor checks only what they need (#2231).
|
|
1303
|
+
const nativeProject = findNativeProject();
|
|
1304
|
+
const nativeResources = nativeProject?.resources ?? null;
|
|
1288
1305
|
if (!options.json) {
|
|
1289
|
-
console.log(
|
|
1306
|
+
console.log(nativeResources
|
|
1307
|
+
? "Flisk doctor checks what this project's local processes need; its services declare commands, so Docker and Tilt are not checked."
|
|
1308
|
+
: "Flisk doctor checks your local Docker + Tilt development environment; it does not check cluster deployments.");
|
|
1290
1309
|
}
|
|
1291
1310
|
const host = detectHost();
|
|
1292
1311
|
if (process.platform === "win32") {
|
|
@@ -1343,10 +1362,15 @@ export const doctorCommand = new Command("doctor")
|
|
|
1343
1362
|
console.log(`\n${chalk.bold("🔍 Flisk Doctor")}\n`);
|
|
1344
1363
|
console.log("Checking environment...\n");
|
|
1345
1364
|
}
|
|
1346
|
-
|
|
1365
|
+
// A directory of command services is a project for doctor even before `flisk up` writes its .flisk/project.json.
|
|
1366
|
+
const projectRoot = findProjectRoot() ?? nativeProject?.projectRoot ?? null;
|
|
1347
1367
|
// Repairs run before the checks so the checks report the state after the fix.
|
|
1348
1368
|
let fixActions;
|
|
1349
|
-
if (options.fix) {
|
|
1369
|
+
if (options.fix && nativeResources) {
|
|
1370
|
+
// The repairs are for Docker networks and Tilt probes, which a project of local processes does not have.
|
|
1371
|
+
fixActions = [];
|
|
1372
|
+
}
|
|
1373
|
+
else if (options.fix) {
|
|
1350
1374
|
try {
|
|
1351
1375
|
let projectName;
|
|
1352
1376
|
if (projectRoot) {
|
|
@@ -1388,16 +1412,19 @@ export const doctorCommand = new Command("doctor")
|
|
|
1388
1412
|
if (!options.json)
|
|
1389
1413
|
printFixActions(fixActions, options.dryRun);
|
|
1390
1414
|
}
|
|
1415
|
+
// The host port plan places the gateway and Postgres containers; a project of local processes has neither.
|
|
1391
1416
|
let hostPortPlan;
|
|
1392
1417
|
let portPlanError;
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1418
|
+
if (!nativeResources) {
|
|
1419
|
+
try {
|
|
1420
|
+
hostPortPlan = await getHostPortPlan(projectRoot ?? process.cwd());
|
|
1421
|
+
}
|
|
1422
|
+
catch (error) {
|
|
1423
|
+
hostPortPlan = null;
|
|
1424
|
+
portPlanError = error instanceof Error ? error.message : String(error);
|
|
1425
|
+
}
|
|
1399
1426
|
}
|
|
1400
|
-
const
|
|
1427
|
+
const containerMachineChecks = [
|
|
1401
1428
|
() => hostPortPlan
|
|
1402
1429
|
? {
|
|
1403
1430
|
name: "Host Ports",
|
|
@@ -1434,7 +1461,7 @@ export const doctorCommand = new Command("doctor")
|
|
|
1434
1461
|
requiresContainerRuntime("Docker Network Pool", () => checkDockerNetworkCapacity()),
|
|
1435
1462
|
() => checkDuplicateFliskInstalls(),
|
|
1436
1463
|
];
|
|
1437
|
-
const
|
|
1464
|
+
const containerProjectChecks = [
|
|
1438
1465
|
() => checkFliskVersion(),
|
|
1439
1466
|
checkMasterConfigs,
|
|
1440
1467
|
checkGeneratedProjectRuntimeAssets,
|
|
@@ -1495,8 +1522,31 @@ export const doctorCommand = new Command("doctor")
|
|
|
1495
1522
|
// When Tilt is up, surface red resources (Traefik/apps never started).
|
|
1496
1523
|
requiresContainerRuntime("Tilt Resources", checkTiltResourceHealth),
|
|
1497
1524
|
];
|
|
1525
|
+
const machineChecks = nativeResources
|
|
1526
|
+
? [
|
|
1527
|
+
() => checkNativeRuntime(nativeResources),
|
|
1528
|
+
() => checkNativeCommands(nativeResources),
|
|
1529
|
+
() => checkWslProjectLocation(findProjectRoot() ?? process.cwd(), options.strict),
|
|
1530
|
+
() => checkDuplicateFliskInstalls(),
|
|
1531
|
+
]
|
|
1532
|
+
: containerMachineChecks;
|
|
1533
|
+
const projectChecks = nativeResources
|
|
1534
|
+
? [
|
|
1535
|
+
() => checkFliskVersion(),
|
|
1536
|
+
() => checkDuplicateResourceNames(),
|
|
1537
|
+
() => checkDependsOnShape(),
|
|
1538
|
+
() => checkNativeDependsOn(nativeResources),
|
|
1539
|
+
() => checkCircularDependencies(),
|
|
1540
|
+
() => checkServicePorts(),
|
|
1541
|
+
() => checkSchemaVersions(undefined, options.strict),
|
|
1542
|
+
() => checkDuplicateResourcePorts(),
|
|
1543
|
+
]
|
|
1544
|
+
: containerProjectChecks;
|
|
1545
|
+
if (options.ping && nativeResources && projectRoot) {
|
|
1546
|
+
projectChecks.push(() => checkNativeServiceHealth(projectRoot, nativeResources, pingTimeout));
|
|
1547
|
+
}
|
|
1498
1548
|
// Runs last: needs routable services (and working Traefik) to mean anything.
|
|
1499
|
-
if (options.ping) {
|
|
1549
|
+
if (options.ping && !nativeResources) {
|
|
1500
1550
|
// Probe the port a running stack is actually on, not the one the next `flisk up` would pick.
|
|
1501
1551
|
const pingPort = projectRoot
|
|
1502
1552
|
? resolveRuntimeIngressPort(projectRoot, hostPortPlan?.ingressHttp)
|
|
@@ -8,8 +8,10 @@ import { pruneProjectNetworks, stopProjectTiltUp, waitForTiltUpExit, } from "../
|
|
|
8
8
|
import { errorFactories, handleTiltFailure, withTiltCheck } from "../utils/errors.js";
|
|
9
9
|
import { createJsonEmitter } from "../utils/json-output.js";
|
|
10
10
|
import { writeMachineError } from "../utils/machine-output.js";
|
|
11
|
+
import { readNativeRunState, stopNativeRun } from "../utils/native-runtime.js";
|
|
12
|
+
import { isNativeProject } from "../utils/native-service.js";
|
|
11
13
|
import { findProjectRoot } from "../utils/paths.js";
|
|
12
|
-
import { discoverResources, discoverStacks } from "../utils/services.js";
|
|
14
|
+
import { discoverResources, discoverResourcesFromRoot, discoverStacks } from "../utils/services.js";
|
|
13
15
|
import { buildTiltDownArgs, getTiltfilePath, runTilt } from "../utils/tilt.js";
|
|
14
16
|
function allStackNames() {
|
|
15
17
|
try {
|
|
@@ -20,6 +22,17 @@ function allStackNames() {
|
|
|
20
22
|
return undefined;
|
|
21
23
|
}
|
|
22
24
|
}
|
|
25
|
+
/** True when `flisk down` stops local processes instead of Tilt: a native run is on record, or every service declares `command`. */
|
|
26
|
+
function isNativeRun(projectRoot) {
|
|
27
|
+
if (readNativeRunState(projectRoot))
|
|
28
|
+
return true;
|
|
29
|
+
try {
|
|
30
|
+
return isNativeProject(discoverResourcesFromRoot(projectRoot));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
23
36
|
function pruneNetworksOrWarn(json) {
|
|
24
37
|
const root = findProjectRoot();
|
|
25
38
|
if (!root)
|
|
@@ -64,6 +77,25 @@ export const downCommand = addYesOption(new Command("down"))
|
|
|
64
77
|
}
|
|
65
78
|
}
|
|
66
79
|
const emit = options.json ? createJsonEmitter("DOWN_FAILED", "flisk down") : undefined;
|
|
80
|
+
// A project of `command` services (or one with a native run on record) stops without Docker or Tilt.
|
|
81
|
+
const projectRoot = findProjectRoot();
|
|
82
|
+
if (isNativeRun(projectRoot)) {
|
|
83
|
+
if (emit && options.dryRun) {
|
|
84
|
+
emit({ ok: true, dryRun: true, runtime: "native" });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (handleDryRun(options, "not stopping local processes", "stop the local processes of this project")) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const stopped = await stopNativeRun(projectRoot);
|
|
91
|
+
if (!options.json) {
|
|
92
|
+
console.log(stopped.wasRunning
|
|
93
|
+
? chalk.blue(`Stopped local processes: ${stopped.services.join(", ")}`)
|
|
94
|
+
: chalk.gray("Nothing is running for this project."));
|
|
95
|
+
}
|
|
96
|
+
emit?.({ ok: true, runtime: "native", stopped: stopped.services });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
67
99
|
await withTiltCheck(async () => {
|
|
68
100
|
if (emit && options.dryRun) {
|
|
69
101
|
emit({ ok: true, dryRun: true });
|
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { STANDARD_PORTS } from "../utils/constants.js";
|
|
5
|
+
import { getDeferredResourceNames } from "../utils/doctor-runtime.js";
|
|
5
6
|
import { errorFactories, FliskError, runCommand, showErrorAndExit } from "../utils/errors.js";
|
|
6
7
|
import { createMachineEnvelope } from "../utils/machine-output.js";
|
|
8
|
+
import { nativeServiceStates, readNativeLogLines } from "../utils/native-runtime.js";
|
|
9
|
+
import { isNativeProject } from "../utils/native-service.js";
|
|
7
10
|
import { findProjectRoot } from "../utils/paths.js";
|
|
8
11
|
import { EnvUnreadableError, envSecretValues, redactSecrets, redactValue, } from "../utils/secret-redaction.js";
|
|
12
|
+
import { deriveServiceStates } from "../utils/service-runtime-state.js";
|
|
13
|
+
import { discoverResourcesFromRoot } from "../utils/services.js";
|
|
14
|
+
import { unhealthyServiceNames } from "../utils/status-groups.js";
|
|
9
15
|
import { isTiltAvailable, runTilt } from "../utils/tilt.js";
|
|
10
|
-
import { DEFAULT_LOG_TAIL, isTiltConnectionFailure, isValidPort, isValidSince, isValidTail, MAX_LOG_TAIL, parseTiltLogLines, } from "../utils/tilt-logs.js";
|
|
16
|
+
import { DEFAULT_LOG_TAIL, isTiltConnectionFailure, isValidPort, isValidSince, isValidTail, MAX_LOG_TAIL, parseTiltLogLines, sinceMilliseconds, } from "../utils/tilt-logs.js";
|
|
11
17
|
import { tiltUnreachableMessage } from "../utils/tilt-unreachable-message.js";
|
|
12
18
|
/** Resource names Tilt knows about, or null when they cannot be listed (the logs call then reports the real error). */
|
|
13
19
|
async function tiltResourceNames(port) {
|
|
@@ -25,12 +31,56 @@ async function tiltResourceNames(port) {
|
|
|
25
31
|
return null;
|
|
26
32
|
}
|
|
27
33
|
}
|
|
34
|
+
/** Raw `tilt get uiresources` JSON, or null when Tilt cannot be read. */
|
|
35
|
+
async function tiltResourcesJson(port) {
|
|
36
|
+
try {
|
|
37
|
+
const result = await runTilt("get", ["uiresources", "-o", "json", "--port", port], {
|
|
38
|
+
inheritStdio: false,
|
|
39
|
+
});
|
|
40
|
+
return result.exitCode === 0 ? result.stdout : null;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Failing or pending services among the project's, from the running Tilt; null when Tilt cannot be read. */
|
|
47
|
+
async function tiltUnhealthyNames(projectRoot, port) {
|
|
48
|
+
const text = await tiltResourcesJson(port);
|
|
49
|
+
if (!text)
|
|
50
|
+
return null;
|
|
51
|
+
let resources = [];
|
|
52
|
+
try {
|
|
53
|
+
resources = discoverResourcesFromRoot(projectRoot);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Without discovery there are no dependsOn edges; Tilt's own states still decide.
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const states = deriveServiceStates(text, Object.fromEntries(resources.map((r) => [r.name, r.config?.dependsOn ?? []])), getDeferredResourceNames(resources));
|
|
60
|
+
return unhealthyServiceNames(Object.keys(states).map((name) => ({ name })), states);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** The project's services when every one declares `command` (they run as local processes), else null. */
|
|
67
|
+
function nativeResources(projectRoot) {
|
|
68
|
+
try {
|
|
69
|
+
const resources = discoverResourcesFromRoot(projectRoot);
|
|
70
|
+
return isNativeProject(resources) ? resources : null;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Discovery problems are reported by the commands that start the stack; logs still asks Tilt.
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
28
77
|
export const logsCommand = new Command("logs")
|
|
29
78
|
.description("Print a bounded snapshot of recent logs from the running stack")
|
|
30
79
|
.showHelpAfterError("\nService names are not positional. Use: flisk logs --service <name>")
|
|
31
80
|
.option("-s, --service <names...>", "Only these services (Tilt resource names)")
|
|
32
81
|
.option("--tail <n>", "Number of most recent lines to return", String(DEFAULT_LOG_TAIL))
|
|
33
82
|
.option("--since <duration>", "Only logs newer than this duration, for example 30s, 5m, 1h")
|
|
83
|
+
.option("--unhealthy", "Only services that are failing or still starting", false)
|
|
34
84
|
.option("--port <n>", "Tilt UI port (default: TILT_PORT or 10350)")
|
|
35
85
|
.option("--json", "Output one JSON object and exit", false)
|
|
36
86
|
.action(async (options) => {
|
|
@@ -64,6 +114,19 @@ export const logsCommand = new Command("logs")
|
|
|
64
114
|
fail("USAGE", "--port must be a valid port number", 2);
|
|
65
115
|
const tail = Number(options.tail);
|
|
66
116
|
const services = options.service ?? [];
|
|
117
|
+
const reportAllHealthy = () => {
|
|
118
|
+
if (options.json) {
|
|
119
|
+
console.log(JSON.stringify(createMachineEnvelope({
|
|
120
|
+
services,
|
|
121
|
+
tail,
|
|
122
|
+
since: options.since ?? null,
|
|
123
|
+
lines: [],
|
|
124
|
+
unhealthy: [],
|
|
125
|
+
})));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
console.log("All services are healthy; nothing to show with --unhealthy.");
|
|
129
|
+
};
|
|
67
130
|
const action = async () => {
|
|
68
131
|
const projectRoot = findProjectRoot();
|
|
69
132
|
if (!projectRoot) {
|
|
@@ -81,6 +144,37 @@ export const logsCommand = new Command("logs")
|
|
|
81
144
|
"Fix the read permission on .env, or move it out of the project",
|
|
82
145
|
]);
|
|
83
146
|
}
|
|
147
|
+
// A project of `command` services keeps its output in .flisk/native/logs, so no Tilt is involved.
|
|
148
|
+
const resources = nativeResources(projectRoot);
|
|
149
|
+
if (resources) {
|
|
150
|
+
const known = resources.map((resource) => resource.name);
|
|
151
|
+
const unknown = services.filter((name) => !known.includes(name));
|
|
152
|
+
if (unknown.length > 0) {
|
|
153
|
+
const error = errorFactories.unknownServices(unknown, known);
|
|
154
|
+
fail("UNKNOWN_SERVICE", error.message, error.exitCode, error.suggestions);
|
|
155
|
+
}
|
|
156
|
+
let selected = services.length > 0 ? services : known;
|
|
157
|
+
if (options.unhealthy) {
|
|
158
|
+
const states = nativeServiceStates(projectRoot, known);
|
|
159
|
+
const bad = new Set(unhealthyServiceNames(resources, states));
|
|
160
|
+
selected = selected.filter((name) => bad.has(name));
|
|
161
|
+
if (selected.length === 0)
|
|
162
|
+
return reportAllHealthy();
|
|
163
|
+
}
|
|
164
|
+
const lines = readNativeLogLines(projectRoot, selected, {
|
|
165
|
+
tail,
|
|
166
|
+
sinceMs: options.since === undefined ? undefined : sinceMilliseconds(options.since),
|
|
167
|
+
});
|
|
168
|
+
if (options.json) {
|
|
169
|
+
console.log(JSON.stringify(redactValue(createMachineEnvelope({ services, tail, since: options.since ?? null, lines }), secrets)));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const width = Math.max(0, ...lines.map((line) => line.resource.length));
|
|
173
|
+
for (const line of lines) {
|
|
174
|
+
console.log(redactSecrets(`${line.resource.padEnd(width)} | ${line.text}`, secrets));
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
84
178
|
if (!(await isTiltAvailable())) {
|
|
85
179
|
fail("TILT_MISSING", "Tilt is not installed. Run: flisk doctor");
|
|
86
180
|
}
|
|
@@ -93,14 +187,24 @@ export const logsCommand = new Command("logs")
|
|
|
93
187
|
fail("UNKNOWN_SERVICE", error.message, error.exitCode, suggestions);
|
|
94
188
|
}
|
|
95
189
|
}
|
|
190
|
+
let selectedServices = services;
|
|
191
|
+
if (options.unhealthy) {
|
|
192
|
+
const bad = await tiltUnhealthyNames(projectRoot, portText);
|
|
193
|
+
if (bad === null)
|
|
194
|
+
fail("TILT_NOT_RUNNING", tiltUnreachableMessage(portText));
|
|
195
|
+
selectedServices =
|
|
196
|
+
services.length > 0 ? services.filter((n) => bad?.includes(n)) : (bad ?? []);
|
|
197
|
+
if (selectedServices.length === 0)
|
|
198
|
+
return reportAllHealthy();
|
|
199
|
+
}
|
|
96
200
|
const args = ["--port", portText, "--tail", String(tail)];
|
|
97
201
|
if (options.since !== undefined)
|
|
98
202
|
args.push("--since", options.since);
|
|
99
203
|
if (options.json)
|
|
100
204
|
args.push("--json");
|
|
101
205
|
// `--` so a service name that starts with "-" is never read as a Tilt flag.
|
|
102
|
-
if (
|
|
103
|
-
args.push("--", ...
|
|
206
|
+
if (selectedServices.length > 0)
|
|
207
|
+
args.push("--", ...selectedServices);
|
|
104
208
|
const result = await runTilt("logs", args, { inheritStdio: false });
|
|
105
209
|
if (result.exitCode !== 0) {
|
|
106
210
|
const detail = result.stderr.trim();
|
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import { Command } from "commander";
|
|
10
10
|
import { generateMasterConfigs, readProjectConfig } from "../generator/template-engine.js";
|
|
11
|
+
import { installAgentSkills } from "../utils/agent-skills.js";
|
|
11
12
|
import { addYesOption } from "../utils/command-helpers.js";
|
|
12
13
|
import { MASTER_CONFIG_FILES, PROJECT_JSON } from "../utils/constants.js";
|
|
13
14
|
import { clearDiscoveryCache } from "../utils/discovery-context.js";
|
|
@@ -213,6 +214,17 @@ const DEFAULT_PROJECT_JSON = {
|
|
|
213
214
|
},
|
|
214
215
|
overrides: {},
|
|
215
216
|
};
|
|
217
|
+
/**
|
|
218
|
+
* Writes .flisk/project.json with the defaults `flisk project --yes` uses, and nothing else: no generated Tiltfile, Compose file,
|
|
219
|
+
* .env or engine copy. `flisk up` uses it to adopt a directory of `command` services as a project, so `flisk down` and
|
|
220
|
+
* `flisk doctor` find it afterwards.
|
|
221
|
+
*/
|
|
222
|
+
export function writeDefaultProjectConfig(projectRoot) {
|
|
223
|
+
const projectConfig = JSON.parse(JSON.stringify(DEFAULT_PROJECT_JSON));
|
|
224
|
+
projectConfig.project.name = basename(projectRoot) || "my-project";
|
|
225
|
+
ensureDirectory(join(projectRoot, ".flisk"));
|
|
226
|
+
writeJsonFile(join(projectRoot, ".flisk", PROJECT_JSON), projectConfig);
|
|
227
|
+
}
|
|
216
228
|
export const projectCommand = addYesOption(new Command("project"))
|
|
217
229
|
.alias("init")
|
|
218
230
|
.description("Initialize or validate project-level master configuration")
|
|
@@ -222,6 +234,7 @@ export const projectCommand = addYesOption(new Command("project"))
|
|
|
222
234
|
.option("--force", "Overwrite existing configuration (dangerous)")
|
|
223
235
|
.option("--config-file <path>", "Load project config from existing JSON file")
|
|
224
236
|
.option("-q, --quiet", "Only print what fails; used by `flisk demo`", false)
|
|
237
|
+
.option("--no-agent-skills", "Do not write the Flisk agent skills (.claude/skills, .agents/skills) and AGENTS.md")
|
|
225
238
|
.action(async (template, options) => {
|
|
226
239
|
const { assertMachineReadyOrExit, detectRuntimePrerequisites, formatRuntimeNextSteps } = await import("../utils/cold-preflight.js");
|
|
227
240
|
// Project setup only writes files: it must not need a running Docker daemon
|
|
@@ -482,6 +495,20 @@ export const projectCommand = addYesOption(new Command("project"))
|
|
|
482
495
|
if (ignored.length > 0 && !quiet) {
|
|
483
496
|
showSuccess(`Added to .gitignore: ${ignored.join(", ")}`);
|
|
484
497
|
}
|
|
498
|
+
// A coding agent opened in the project then knows to build and run the app with Flisk.
|
|
499
|
+
// The project works without them, so a failure here is a warning, not an exit.
|
|
500
|
+
if (options.agentSkills !== false) {
|
|
501
|
+
try {
|
|
502
|
+
const skillFiles = installAgentSkills(projectRoot);
|
|
503
|
+
if (skillFiles.length > 0 && !quiet) {
|
|
504
|
+
showSuccess("Added agent skills: .claude/skills, .agents/skills and AGENTS.md (start with flisk-build)");
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
509
|
+
console.log(chalk.yellow(`⚠️ Could not add the agent skills: ${reason}`));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
485
512
|
if (!quiet) {
|
|
486
513
|
console.log(chalk.green("\n✅ Project configuration complete!"));
|
|
487
514
|
showDetail("\nGenerated files in .flisk/.flisk-out/:", 0);
|
|
@@ -231,7 +231,9 @@ export function createPackageJson(name, type, frameworkId, prismaEnabled = false
|
|
|
231
231
|
// vitest 5 peers on vite ^6.4.0 || ^7 || ^8; vite 5 makes `vitest run` fail at startup with
|
|
232
232
|
// ERR_PACKAGE_PATH_NOT_EXPORTED ('./module-runner'), in every resource once it is hoisted.
|
|
233
233
|
...(isFrontend && !framework?.scripts ? { vite: "^6.4.1" } : {}),
|
|
234
|
-
...(drizzleEnabled ? { "drizzle-kit": DRIZZLE_KIT_RANGE
|
|
234
|
+
...(drizzleEnabled ? { "drizzle-kit": DRIZZLE_KIT_RANGE } : {}),
|
|
235
|
+
// Both import 'pg' (src/db.ts, the agent's checkpoint store); without the types the image's `tsc` fails with TS7016.
|
|
236
|
+
...(drizzleEnabled || isPostgresAgent ? { "@types/pg": "^8.11.0" } : {}),
|
|
235
237
|
...(framework?.devDependencies ?? {}),
|
|
236
238
|
...(backendFramework?.devDependencies ?? {}),
|
|
237
239
|
},
|
|
@@ -8,9 +8,12 @@ import { runCommand } from "../utils/errors.js";
|
|
|
8
8
|
import { formatCount, showDetail, showStep } from "../utils/formatting.js";
|
|
9
9
|
import { getHostPortPlan } from "../utils/host-port-config.js";
|
|
10
10
|
import { createMachineEnvelope, writeMachineError } from "../utils/machine-output.js";
|
|
11
|
+
import { nativeServiceStates } from "../utils/native-runtime.js";
|
|
12
|
+
import { isNativeProject } from "../utils/native-service.js";
|
|
11
13
|
import { findProjectRoot } from "../utils/paths.js";
|
|
12
14
|
import { deriveServiceStates } from "../utils/service-runtime-state.js";
|
|
13
15
|
import { groupSharedPostgres, readProjectName } from "../utils/shared-infra.js";
|
|
16
|
+
import { groupServicesByStack, unhealthyServiceNames } from "../utils/status-groups.js";
|
|
14
17
|
import { buildServicePorts, buildStackPorts } from "../utils/status-ports.js";
|
|
15
18
|
import { isTiltAvailable, runTilt } from "../utils/tilt.js";
|
|
16
19
|
import { getTiltPollingPort } from "../utils/tilt-startup.js";
|
|
@@ -22,6 +25,7 @@ export const statusCommand = new Command("status")
|
|
|
22
25
|
.option("--stacks", "Show stack information (default)", true)
|
|
23
26
|
.option("--resources", "Show all discovered resources", false)
|
|
24
27
|
.option("--tilt", "Show tilt resource status", false)
|
|
28
|
+
.option("--unhealthy", "Show only services that are failing or still starting", false)
|
|
25
29
|
.action(async (options) => {
|
|
26
30
|
const action = async () => {
|
|
27
31
|
// Discover first: outside a project this fails before any status line is printed.
|
|
@@ -31,19 +35,30 @@ export const statusCommand = new Command("status")
|
|
|
31
35
|
const sharedInfraGroup = projectName
|
|
32
36
|
? groupSharedPostgres(projectName, discovery.stacks)
|
|
33
37
|
: null;
|
|
38
|
+
// A project of `command` services runs as local processes: its state comes from the run on record, not from Tilt.
|
|
39
|
+
const nativeStates = projectRoot && isNativeProject(discovery.resources)
|
|
40
|
+
? nativeServiceStates(projectRoot, discovery.resources.map((resource) => resource.name))
|
|
41
|
+
: null;
|
|
34
42
|
let tiltAvailable = false;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
if (!nativeStates) {
|
|
44
|
+
try {
|
|
45
|
+
tiltAvailable = await isTiltAvailable();
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Tilt not available (spawn error)
|
|
49
|
+
tiltAvailable = false;
|
|
50
|
+
}
|
|
41
51
|
}
|
|
42
52
|
if (!options.json) {
|
|
43
53
|
showStep("Flisk Status\n");
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
54
|
+
if (nativeStates) {
|
|
55
|
+
console.log(chalk.bold("Runtime:"), "local processes (no Docker, no Tilt)");
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log(chalk.bold("Tilt:"), tiltAvailable ? chalk.green("available") : chalk.red("not found"));
|
|
59
|
+
if (!tiltAvailable) {
|
|
60
|
+
showDetail("Install Tilt: https://docs.tilt.dev/install.html");
|
|
61
|
+
}
|
|
47
62
|
}
|
|
48
63
|
console.log();
|
|
49
64
|
}
|
|
@@ -53,7 +68,7 @@ export const statusCommand = new Command("status")
|
|
|
53
68
|
// Tilt answers or its output cannot be read.
|
|
54
69
|
let readiness = null;
|
|
55
70
|
// The same look at Tilt feeds readiness and each service's state, so `flisk status` and `flisk ui` cannot disagree.
|
|
56
|
-
let serviceStates =
|
|
71
|
+
let serviceStates = nativeStates;
|
|
57
72
|
const tiltPort = getTiltPollingPort(process.env.TILT_PORT);
|
|
58
73
|
// Port plan is best effort; service URLs are shown in both JSON and terminal output.
|
|
59
74
|
let portPlan = null;
|
|
@@ -99,7 +114,23 @@ export const statusCommand = new Command("status")
|
|
|
99
114
|
}
|
|
100
115
|
if (options.json) {
|
|
101
116
|
const errors = queryError ? [{ code: "TILT_STATUS_UNAVAILABLE", message: queryError }] : [];
|
|
117
|
+
// A native run answers the readiness question agents poll (data.tilt.readiness.ready) from its own run state.
|
|
118
|
+
if (nativeStates) {
|
|
119
|
+
const states = Object.entries(nativeStates);
|
|
120
|
+
const isRunning = states.some(([, state]) => state.status !== "unknown");
|
|
121
|
+
readiness = isRunning
|
|
122
|
+
? {
|
|
123
|
+
ready: states.every(([, state]) => state.status === "ready"),
|
|
124
|
+
failures: [],
|
|
125
|
+
pending: states.filter(([, state]) => state.status !== "ready").length,
|
|
126
|
+
timedOut: false,
|
|
127
|
+
enabled: states.map(([name]) => name),
|
|
128
|
+
}
|
|
129
|
+
: null;
|
|
130
|
+
}
|
|
102
131
|
const data = {
|
|
132
|
+
// Only a project of local processes says so; a container project's report keeps its shape.
|
|
133
|
+
...(nativeStates ? { runtime: "native" } : {}),
|
|
103
134
|
tilt: {
|
|
104
135
|
available: tiltAvailable,
|
|
105
136
|
resourcesQueried: options.tilt && tiltAvailable,
|
|
@@ -126,9 +157,12 @@ export const statusCommand = new Command("status")
|
|
|
126
157
|
name: stack.name,
|
|
127
158
|
resourceCount: stack.resourceCount,
|
|
128
159
|
resources: stack.resources.map((resource) => resource.name),
|
|
160
|
+
healthyCount: stack.resources.filter((resource) => serviceStates?.[resource.name]?.status === "ready").length,
|
|
129
161
|
})),
|
|
130
162
|
unassignedResources: discovery.unassignedResources.map((resource) => resource.name),
|
|
131
163
|
sharedInfra: sharedInfraGroup,
|
|
164
|
+
// Names of the failing or pending services, so a caller need not filter `resources` itself.
|
|
165
|
+
unhealthy: unhealthyServiceNames(discovery.resources, serviceStates),
|
|
132
166
|
};
|
|
133
167
|
console.log(JSON.stringify(createMachineEnvelope(data, errors)));
|
|
134
168
|
if (errors.length > 0) {
|
|
@@ -139,27 +173,29 @@ export const statusCommand = new Command("status")
|
|
|
139
173
|
}
|
|
140
174
|
console.log(chalk.bold("Resources:"), `${discovery.resources.length} discovered`);
|
|
141
175
|
console.log(chalk.bold("Stacks:"), `${discovery.stacks.length} defined`);
|
|
142
|
-
|
|
176
|
+
const hasStates = serviceStates !== null && Object.keys(serviceStates).length > 0;
|
|
177
|
+
const unhealthyOnly = Boolean(options.unhealthy);
|
|
178
|
+
// With service states the grouped "Services:" section below replaces the plain per-stack counts.
|
|
179
|
+
if (!hasStates && !unhealthyOnly) {
|
|
143
180
|
for (const stack of discovery.stacks) {
|
|
144
|
-
|
|
145
|
-
showDetail(`${stack.name}: ${formatCount(resourcesInStack, "resource")}`);
|
|
181
|
+
showDetail(`${stack.name}: ${formatCount(stack.resources.length, "resource")}`);
|
|
146
182
|
if (options.verbose) {
|
|
147
183
|
for (const resource of stack.resources) {
|
|
148
184
|
showDetail(`${resource.name}`, 6);
|
|
149
185
|
}
|
|
150
186
|
}
|
|
151
187
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
188
|
+
if (discovery.unassignedResources.length > 0) {
|
|
189
|
+
console.log();
|
|
190
|
+
console.log(chalk.yellow(`${formatCount(discovery.unassignedResources.length, "resource")} not in any stack:`));
|
|
191
|
+
if (options.verbose) {
|
|
192
|
+
for (const resource of discovery.unassignedResources) {
|
|
193
|
+
showDetail(`${resource.name}`);
|
|
194
|
+
}
|
|
159
195
|
}
|
|
160
196
|
}
|
|
161
197
|
}
|
|
162
|
-
if (sharedInfraGroup) {
|
|
198
|
+
if (sharedInfraGroup && !unhealthyOnly) {
|
|
163
199
|
console.log();
|
|
164
200
|
console.log(chalk.bold("Shared infrastructure:"));
|
|
165
201
|
showDetail(`${sharedInfraGroup.name} (one instance, ${formatCount(sharedInfraGroup.databases.length, "database")})`);
|
|
@@ -167,21 +203,33 @@ export const statusCommand = new Command("status")
|
|
|
167
203
|
showDetail(` ${database.name} (stack ${database.stack}): ${database.services.join(", ")}`);
|
|
168
204
|
}
|
|
169
205
|
}
|
|
170
|
-
if (
|
|
206
|
+
if (hasStates && serviceStates) {
|
|
207
|
+
const states = serviceStates;
|
|
171
208
|
console.log();
|
|
172
|
-
console.log(chalk.bold("Services:"));
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
209
|
+
console.log(chalk.bold(unhealthyOnly ? "Unhealthy services:" : "Services:"));
|
|
210
|
+
const unhealthy = new Set(unhealthyServiceNames(discovery.resources, states));
|
|
211
|
+
if (unhealthyOnly && unhealthy.size === 0) {
|
|
212
|
+
showDetail(chalk.green("All services are healthy."));
|
|
213
|
+
}
|
|
214
|
+
for (const group of groupServicesByStack(discovery.stacks, discovery.unassignedResources, states)) {
|
|
215
|
+
const shown = group.resources.filter((resource) => states[resource.name] && (!unhealthyOnly || unhealthy.has(resource.name)));
|
|
216
|
+
if (unhealthyOnly && shown.length === 0)
|
|
176
217
|
continue;
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
218
|
+
const summary = `${group.healthy}/${group.total} healthy`;
|
|
219
|
+
showDetail(`${chalk.bold(group.name)}: ${group.healthy === group.total ? chalk.green(summary) : chalk.yellow(summary)}`);
|
|
220
|
+
for (const resource of shown) {
|
|
221
|
+
const state = states[resource.name];
|
|
222
|
+
if (!state)
|
|
223
|
+
continue;
|
|
224
|
+
const mark = state.status === "ready"
|
|
225
|
+
? chalk.green("✓")
|
|
226
|
+
: state.status === "error"
|
|
227
|
+
? chalk.red("✗")
|
|
228
|
+
: chalk.yellow("…");
|
|
229
|
+
const ports = buildServicePorts(resource, portPlan?.ingressHttp);
|
|
230
|
+
const urlPart = ports.url ? ` → ${ports.url}` : "";
|
|
231
|
+
showDetail(`${mark} ${resource.name}${urlPart} ${state.status}${state.reason ? ` (${state.reason})` : ""}`, 6);
|
|
232
|
+
}
|
|
185
233
|
}
|
|
186
234
|
}
|
|
187
235
|
else if (discovery.resources.length > 0) {
|