@flisk-sh/flisk 1.4.2 → 1.4.6
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 +148 -49
- 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 +66 -27
- package/cli/dist/commands/down.js +33 -1
- package/cli/dist/commands/logs.js +39 -1
- package/cli/dist/commands/project.js +12 -0
- package/cli/dist/commands/resource.js +39 -11
- package/cli/dist/commands/status.js +57 -19
- package/cli/dist/commands/up.js +221 -18
- package/cli/dist/commands/upgrade.js +2 -2
- 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/bun-requirement.js +7 -4
- package/cli/dist/utils/cold-preflight.js +55 -34
- 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/doctor-native.js +96 -0
- package/cli/dist/utils/host-port-plan.js +8 -3
- package/cli/dist/utils/native-project.js +41 -0
- package/cli/dist/utils/native-runtime.js +513 -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/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 +9 -9
- package/cli/dist/utils/resource-features.js +5 -5
- package/cli/dist/utils/service-manifest.js +20 -3
- package/cli/dist/utils/service-urls.js +11 -0
- package/cli/dist/utils/services.js +15 -2
- package/cli/dist/utils/startup-report.js +4 -3
- package/cli/dist/utils/tar.js +33 -28
- package/cli/dist/utils/tilt-logs.js +8 -0
- package/cli/dist/utils/tilt-process.js +4 -3
- package/cli/dist/utils/windows-doctor.js +18 -15
- package/cli/dist-bundle/chunk-BIVAAWBX.js +24 -0
- package/cli/dist-bundle/{chunk-YNCBBJ4V.js → chunk-C4UFBXRD.js} +1 -1
- package/cli/dist-bundle/{chunk-53L2JN7G.js → chunk-CAEMWNPF.js} +1 -1
- package/cli/dist-bundle/chunk-CDKEHFW6.js +35 -0
- package/cli/dist-bundle/{chunk-5QBYM33Q.js → chunk-DPQK5TIS.js} +1 -1
- package/cli/dist-bundle/chunk-ETXZ5LUG.js +18 -0
- package/cli/dist-bundle/chunk-GPUJ55MI.js +3 -0
- package/cli/dist-bundle/chunk-GT76VMUL.js +130 -0
- package/cli/dist-bundle/{chunk-IGN5UC3P.js → chunk-IDNES5PE.js} +14 -7
- package/cli/dist-bundle/{chunk-MWOVYIBB.js → chunk-MUDE4DZD.js} +5 -5
- package/cli/dist-bundle/chunk-QEBN6ZVQ.js +2 -0
- package/cli/dist-bundle/{chunk-I7TGIK5N.js → chunk-R7DWXISM.js} +1 -1
- package/cli/dist-bundle/{chunk-DU2BN46C.js → chunk-U5FCQF42.js} +1 -1
- package/cli/dist-bundle/{chunk-3XODDLRJ.js → chunk-WCGBVETR.js} +1 -1
- package/cli/dist-bundle/cli.js +626 -389
- package/cli/dist-bundle/cold-preflight-KUWOL2Q2.js +2 -0
- package/cli/dist-bundle/{demo-stage-OHNTP2UJ.js → demo-stage-QCC4IWXR.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-AZNX4AC5.js} +1 -1
- package/cli/dist-bundle/project-5SWIGCNP.js +2 -0
- package/cli/dist-bundle/{project-templates-EYSQG2EO.js → project-templates-2VQPUBSQ.js} +1 -1
- package/cli/dist-bundle/{services-BIMF6MVU.js → services-EY5WKPHQ.js} +1 -1
- package/cli/dist-bundle/{tilt-port-owner-ZY5M2JC4.js → tilt-port-owner-UBU6JWOA.js} +1 -1
- package/cli/package.json +1 -1
- 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 +99 -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/compose.star +40 -4
- 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/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/platform/docker/networking/traefik_static_routes.star +2 -2
- 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/mapper.star +1 -1
- package/runtime/engine/topologies/tilt/manifest/parser.star +9 -3
- package/runtime/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +9 -4
- 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/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-43XWL2PB.js +0 -61
- package/cli/dist-bundle/chunk-5F7YP5IR.js +0 -17
- package/cli/dist-bundle/chunk-ETKUVKJ2.js +0 -35
- 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
- /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
|
@@ -1,3 +1,81 @@
|
|
|
1
|
+
const STARTER_STYLE = `<style scoped>
|
|
2
|
+
main { max-width: 640px; margin: 0 auto; padding: 3rem 1.5rem; font-family: system-ui, sans-serif; color: #1f2933; }
|
|
3
|
+
.muted { color: #616e7c; margin-top: 0; }
|
|
4
|
+
.card { border: 1px solid #d9e2ec; border-radius: 8px; padding: 1rem 1.25rem; margin: 1.5rem 0; }
|
|
5
|
+
.card-header { display: flex; justify-content: space-between; align-items: center; }
|
|
6
|
+
.ok { color: #0a7f3f; }
|
|
7
|
+
.unreachable { color: #b42318; }
|
|
8
|
+
pre { background: #f5f7fa; border-radius: 6px; padding: 0.75rem; overflow-x: auto; }
|
|
9
|
+
</style>`;
|
|
10
|
+
/** Hints and links shared by both starters. They need no backend, so they appear in every frontend. */
|
|
11
|
+
const STARTER_HINTS = ` <p>Edit <code>src/App.vue</code> and save. It live-reloads.</p>
|
|
12
|
+
<ul>
|
|
13
|
+
<li><a href="http://localhost:10350">Tilt UI</a>: logs and resource status</li>
|
|
14
|
+
<li><code>flisk status</code>: every resource in this project</li>
|
|
15
|
+
</ul>`;
|
|
16
|
+
function renderAppWithBackend(name, backendName) {
|
|
17
|
+
return `<script setup lang="ts">
|
|
18
|
+
import { onMounted, ref } from 'vue';
|
|
19
|
+
|
|
20
|
+
const API_URL = import.meta.env.VITE_API_URL ?? '';
|
|
21
|
+
const BACKEND_NAME = '${backendName}';
|
|
22
|
+
|
|
23
|
+
interface Health {
|
|
24
|
+
ok: boolean;
|
|
25
|
+
body: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const health = ref<Health | null>(null);
|
|
29
|
+
|
|
30
|
+
onMounted(async () => {
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(API_URL + '/health');
|
|
33
|
+
health.value = { ok: response.ok, body: await response.json() };
|
|
34
|
+
} catch (error) {
|
|
35
|
+
health.value = { ok: false, body: { error: String(error) } };
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<main>
|
|
42
|
+
<h1>${name}</h1>
|
|
43
|
+
<p class="muted">Flisk frontend</p>
|
|
44
|
+
|
|
45
|
+
<section class="card">
|
|
46
|
+
<div class="card-header">
|
|
47
|
+
<strong>Backend: {{ BACKEND_NAME }}</strong>
|
|
48
|
+
<span v-if="health === null">checking…</span>
|
|
49
|
+
<span v-else-if="health.ok" class="ok">ok</span>
|
|
50
|
+
<span v-else class="unreachable">unreachable</span>
|
|
51
|
+
</div>
|
|
52
|
+
<pre v-if="health">{{ JSON.stringify(health.body, null, 2) }}</pre>
|
|
53
|
+
</section>
|
|
54
|
+
|
|
55
|
+
${STARTER_HINTS}
|
|
56
|
+
</main>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
${STARTER_STYLE}
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
function renderAppWithoutBackend(name) {
|
|
63
|
+
return `<script setup lang="ts">
|
|
64
|
+
const appName = '${name}';
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<main>
|
|
69
|
+
<h1>{{ appName }}</h1>
|
|
70
|
+
<p class="muted">Flisk frontend</p>
|
|
71
|
+
|
|
72
|
+
${STARTER_HINTS}
|
|
73
|
+
</main>
|
|
74
|
+
</template>
|
|
75
|
+
|
|
76
|
+
${STARTER_STYLE}
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
1
79
|
export const vueFrontendProvider = {
|
|
2
80
|
id: "vue",
|
|
3
81
|
label: "Vue + Vite",
|
|
@@ -8,7 +86,7 @@ export const vueFrontendProvider = {
|
|
|
8
86
|
"@vitejs/plugin-vue": "^5.2.4",
|
|
9
87
|
},
|
|
10
88
|
compilerOptions: {},
|
|
11
|
-
createFiles(name) {
|
|
89
|
+
createFiles(name, options = {}) {
|
|
12
90
|
return [
|
|
13
91
|
{
|
|
14
92
|
filename: "index.html",
|
|
@@ -40,17 +118,9 @@ createApp(App).mount('#app');
|
|
|
40
118
|
},
|
|
41
119
|
{
|
|
42
120
|
filename: "src/App.vue",
|
|
43
|
-
content:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<template>
|
|
48
|
-
<main style="padding: 2rem; font-family: system-ui">
|
|
49
|
-
<h1>${name}</h1>
|
|
50
|
-
<p>{{ message }}</p>
|
|
51
|
-
</main>
|
|
52
|
-
</template>
|
|
53
|
-
`,
|
|
121
|
+
content: options.backendName
|
|
122
|
+
? renderAppWithBackend(name, options.backendName)
|
|
123
|
+
: renderAppWithoutBackend(name),
|
|
54
124
|
description: "Generating Vue app",
|
|
55
125
|
emoji: "💻",
|
|
56
126
|
},
|
|
@@ -122,12 +122,10 @@ main().catch((err) => {
|
|
|
122
122
|
`;
|
|
123
123
|
}
|
|
124
124
|
export function getTestTemplate(name) {
|
|
125
|
-
return `import { describe, it
|
|
125
|
+
return `import { describe, it } from 'vitest';
|
|
126
126
|
|
|
127
127
|
describe('${name}', () => {
|
|
128
|
-
it('
|
|
129
|
-
expect(true).toBe(true);
|
|
130
|
-
});
|
|
128
|
+
it.todo('add a real test for ${name}');
|
|
131
129
|
});
|
|
132
130
|
`;
|
|
133
131
|
}
|
|
@@ -34,11 +34,13 @@ export function loadTemplate(filename) {
|
|
|
34
34
|
const triedPaths = [];
|
|
35
35
|
for (const templateDir of templateDirs) {
|
|
36
36
|
const templatePath = path.join(templateDir, filename);
|
|
37
|
-
triedPaths.push(templatePath);
|
|
38
37
|
try {
|
|
39
38
|
return fs.readFileSync(templatePath, "utf-8");
|
|
40
39
|
}
|
|
41
|
-
catch (
|
|
40
|
+
catch (error) {
|
|
41
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
42
|
+
triedPaths.push(`${templatePath} (${reason})`);
|
|
43
|
+
}
|
|
42
44
|
}
|
|
43
45
|
throw new Error(`Failed to load template ${filename} from any of these paths: ${triedPaths.join(", ")}`);
|
|
44
46
|
}
|
|
@@ -707,7 +709,7 @@ async function vendorFliskExtension(projectRoot, options = {}) {
|
|
|
707
709
|
console.warn(`⚠️ Premium overlay skipped: ${err.message}`);
|
|
708
710
|
}
|
|
709
711
|
}
|
|
710
|
-
const FLISK_GENERATED_MARKER = "SYSTEM-GENERATED BY Flisk CLI";
|
|
712
|
+
export const FLISK_GENERATED_MARKER = "SYSTEM-GENERATED BY Flisk CLI";
|
|
711
713
|
export function verifyMasterConfigs(projectRoot, options) {
|
|
712
714
|
const errors = [];
|
|
713
715
|
const warnings = [];
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { isNativeService } from "./native-service.js";
|
|
1
2
|
import { isApiServiceType } from "./resource-kind.js";
|
|
2
3
|
/**
|
|
3
4
|
* Bun runs the generated JS services. A project without any has nothing for Bun to run, so neither `flisk doctor` nor
|
|
4
|
-
* `flisk up` needs it.
|
|
5
|
+
* `flisk up` needs it. A `command` service runs whatever its command names, so it never needs Bun on Flisk's behalf.
|
|
6
|
+
* Doctor and the start-up preflight share this rule so they cannot disagree.
|
|
5
7
|
*/
|
|
6
8
|
export function projectNeedsBun(resources) {
|
|
7
|
-
return resources.some((resource) =>
|
|
8
|
-
resource.config?.appType
|
|
9
|
-
|
|
9
|
+
return resources.some((resource) => !isNativeService(resource) &&
|
|
10
|
+
(isApiServiceType(resource.config?.appType) ||
|
|
11
|
+
resource.config?.appType === "frontend" ||
|
|
12
|
+
resource.config?.appType === "worker"));
|
|
10
13
|
}
|
|
@@ -11,9 +11,19 @@ import { checkColima } from "./colima.js";
|
|
|
11
11
|
import { PROJECT_JSON } from "./constants.js";
|
|
12
12
|
import { checkNatsBroker } from "./doctor-wiring.js";
|
|
13
13
|
import { getHostPortPlan } from "./host-port-config.js";
|
|
14
|
+
import { isNativeProject } from "./native-service.js";
|
|
14
15
|
import { findProjectRoot } from "./paths.js";
|
|
15
16
|
import { discoverResourcesFromRoot } from "./services.js";
|
|
16
17
|
import { findOnPath } from "./which.js";
|
|
18
|
+
/** Checks a project of `command` services needs: no container runtime, no Tilt, no gateway or database ports. */
|
|
19
|
+
export const NATIVE_CHECK_IDS = ["node", "bun"];
|
|
20
|
+
function blockingCheckIds(scope) {
|
|
21
|
+
if (scope === "scaffold")
|
|
22
|
+
return SCAFFOLD_CHECK_IDS;
|
|
23
|
+
if (scope === "native")
|
|
24
|
+
return NATIVE_CHECK_IDS;
|
|
25
|
+
return MACHINE_CHECK_IDS;
|
|
26
|
+
}
|
|
17
27
|
/** Checks that only the local runtime (starting a stack) needs. */
|
|
18
28
|
export const RUNTIME_CHECK_IDS = ["docker", "compose", "tilt"];
|
|
19
29
|
/** Checks that writing project configuration needs. */
|
|
@@ -61,7 +71,7 @@ export function requireVerifiedDockerVersions(result) {
|
|
|
61
71
|
fix: result.fix ?? "Start Docker and ensure Docker Engine 25+ and Compose 2.20.2+ are installed.",
|
|
62
72
|
};
|
|
63
73
|
}
|
|
64
|
-
function usesPrisma(projectRoot) {
|
|
74
|
+
export function usesPrisma(projectRoot) {
|
|
65
75
|
try {
|
|
66
76
|
return discoverResourcesFromRoot(projectRoot).some((resource) => {
|
|
67
77
|
return resource.config?.featuresEnabled?.includes("prisma") ?? false;
|
|
@@ -71,11 +81,35 @@ function usesPrisma(projectRoot) {
|
|
|
71
81
|
return false;
|
|
72
82
|
}
|
|
73
83
|
}
|
|
84
|
+
async function hostPortsItem(root, scope) {
|
|
85
|
+
try {
|
|
86
|
+
// Scaffolding must not talk to Docker at all, so the port plan is planned
|
|
87
|
+
// from the host ports alone.
|
|
88
|
+
const plan = await getHostPortPlan(root, scope === "scaffold" ? { inspectDocker: false } : {});
|
|
89
|
+
return {
|
|
90
|
+
id: "ports",
|
|
91
|
+
ok: true,
|
|
92
|
+
message: `HTTP ${plan.ingressHttp}, HTTPS ${plan.ingressHttps}, Postgres ${plan.postgres}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
return {
|
|
97
|
+
id: "ports",
|
|
98
|
+
ok: false,
|
|
99
|
+
message: error instanceof Error ? error.message : String(error),
|
|
100
|
+
fix: "Set FLISK_HTTP_PORT, FLISK_HTTPS_PORT, or FLISK_POSTGRES_PORT to available host ports.",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
74
104
|
export async function runColdPreflight(opts = {}) {
|
|
75
105
|
const cwd = opts.cwd ?? process.cwd();
|
|
76
|
-
const scope = opts.scope ?? "runtime";
|
|
77
106
|
const projectRoot = findProjectRoot(cwd);
|
|
78
107
|
const inProject = Boolean(projectRoot);
|
|
108
|
+
const resources = projectRoot ? discoverResourcesFromRoot(projectRoot) : [];
|
|
109
|
+
// A project whose services all declare `command` runs them as local processes (#2231): Docker, Compose, Tilt, the
|
|
110
|
+
// gateway/database host ports and the container wiring checks do not apply, so they are not run or reported.
|
|
111
|
+
const isNative = isNativeProject(resources);
|
|
112
|
+
const scope = isNative ? "native" : (opts.scope ?? "runtime");
|
|
79
113
|
const items = [];
|
|
80
114
|
const nodeVersion = process.versions.node;
|
|
81
115
|
items.push({
|
|
@@ -115,14 +149,15 @@ export async function runColdPreflight(opts = {}) {
|
|
|
115
149
|
items.push(await safeCheck("tilt", checkTilt, "Tilt CLI not found"));
|
|
116
150
|
}
|
|
117
151
|
// Outside a project Bun stays required: `flisk project` is about to create services that need it.
|
|
118
|
-
const bunNeeded = projectRoot ? projectNeedsBun(
|
|
152
|
+
const bunNeeded = projectRoot ? projectNeedsBun(resources) : true;
|
|
119
153
|
let bun = false;
|
|
120
154
|
const bunPath = bunNeeded ? findOnPath("bun") : undefined;
|
|
121
155
|
if (bunPath) {
|
|
122
156
|
try {
|
|
157
|
+
// A cold start of bun can pass 3s on a busy machine; a timeout there reads as "not installed".
|
|
123
158
|
const version = execFileSync(bunPath, ["--version"], {
|
|
124
159
|
encoding: "utf-8",
|
|
125
|
-
timeout:
|
|
160
|
+
timeout: 10_000,
|
|
126
161
|
}).trim();
|
|
127
162
|
bun = bunMeetsFloor(version);
|
|
128
163
|
}
|
|
@@ -140,24 +175,9 @@ export async function runColdPreflight(opts = {}) {
|
|
|
140
175
|
fix: bun ? undefined : "curl -fsSL https://bun.sh/install | bash",
|
|
141
176
|
}
|
|
142
177
|
: { id: "bun", ok: true, message: "Bun not needed: no generated JS services" });
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const plan = await getHostPortPlan(projectRoot ?? cwd, scope === "scaffold" ? { inspectDocker: false } : {});
|
|
147
|
-
items.push({
|
|
148
|
-
id: "ports",
|
|
149
|
-
ok: true,
|
|
150
|
-
message: `HTTP ${plan.ingressHttp}, HTTPS ${plan.ingressHttps}, Postgres ${plan.postgres}`,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
items.push({
|
|
155
|
-
id: "ports",
|
|
156
|
-
ok: false,
|
|
157
|
-
message: error instanceof Error ? error.message : String(error),
|
|
158
|
-
fix: "Set FLISK_HTTP_PORT, FLISK_HTTPS_PORT, or FLISK_POSTGRES_PORT to available host ports.",
|
|
159
|
-
});
|
|
160
|
-
}
|
|
178
|
+
// The host port plan places the gateway and Postgres, which only the container runtime starts.
|
|
179
|
+
if (scope !== "native")
|
|
180
|
+
items.push(await hostPortsItem(projectRoot ?? cwd, scope));
|
|
161
181
|
// Project wiring (NATS, Prisma database) describes a running stack, so it is
|
|
162
182
|
// only probed for the runtime scope.
|
|
163
183
|
if (projectRoot && scope === "runtime") {
|
|
@@ -197,18 +217,20 @@ export async function runColdPreflight(opts = {}) {
|
|
|
197
217
|
}
|
|
198
218
|
}
|
|
199
219
|
const failures = items.filter((check) => !check.ok).slice(0, 8);
|
|
200
|
-
const blockingIds = scope
|
|
220
|
+
const blockingIds = blockingCheckIds(scope);
|
|
201
221
|
const machineFailed = failures.some((check) => blockingIds.includes(check.id));
|
|
202
222
|
const bunFailed = failures.some((check) => check.id === "bun");
|
|
203
223
|
const header = failures.length
|
|
204
224
|
? machineFailed
|
|
205
|
-
? scope === "
|
|
206
|
-
?
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
:
|
|
225
|
+
? scope === "native"
|
|
226
|
+
? "Cannot start the local processes. Fix the machine checks below."
|
|
227
|
+
: scope === "scaffold"
|
|
228
|
+
? bunFailed
|
|
229
|
+
? `Project setup blocked. Bun ${BUN_FLOOR_LABEL} must be installed on PATH.\nFix the machine checks below.`
|
|
230
|
+
: "Project setup blocked. Fix the machine checks below."
|
|
231
|
+
: bunFailed
|
|
232
|
+
? `Cold start blocked. Bun ${BUN_FLOOR_LABEL} must be installed on PATH.\nPrisma/NATS configuration is project-specific and generated after \`flisk project\`. Fix the machine checks below.`
|
|
233
|
+
: "Cold start blocked. Bun/Prisma/NATS are not the first failure.\nThey are generated after `flisk project`. Fix the machine checks below."
|
|
212
234
|
: "Machine is ready. Project wiring is not."
|
|
213
235
|
: "";
|
|
214
236
|
const footer = failures.length
|
|
@@ -216,7 +238,7 @@ export async function runColdPreflight(opts = {}) {
|
|
|
216
238
|
: inProject
|
|
217
239
|
? "Environment ready for Flisk"
|
|
218
240
|
: `This machine is ready for Flisk.\n${readyNextStep()}`;
|
|
219
|
-
return { ok: failures.length === 0, inProject, items: failures, header, footer };
|
|
241
|
+
return { ok: failures.length === 0, inProject, scope, items: failures, header, footer };
|
|
220
242
|
}
|
|
221
243
|
/** Formats only failed preflight checks and the applicable readiness message. */
|
|
222
244
|
export function formatColdPreflight(result, options = {}) {
|
|
@@ -232,9 +254,8 @@ export function formatColdPreflight(result, options = {}) {
|
|
|
232
254
|
}
|
|
233
255
|
/** Exits before machine-dependent command work when any machine check fails. */
|
|
234
256
|
export async function assertMachineReadyOrExit(opts = {}) {
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
-
const blockingIds = scope === "scaffold" ? SCAFFOLD_CHECK_IDS : MACHINE_CHECK_IDS;
|
|
257
|
+
const result = await runColdPreflight({ scope: opts.scope });
|
|
258
|
+
const blockingIds = blockingCheckIds(result.scope);
|
|
238
259
|
const machineFailure = result.items.some((check) => blockingIds.includes(check.id));
|
|
239
260
|
if (machineFailure) {
|
|
240
261
|
console.error(formatColdPreflight(result));
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Copyright (c) 2026 Flisk Landscape contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
4
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { FLISK_GENERATED_MARKER, ProjectConfigNotFoundError, verifyMasterConfigs, } from "../generator/template-engine.js";
|
|
6
|
+
import { AUTOGENERATED_DIR, PACKAGE_JSON, PROJECT_JSON, SERVICE_JSON, TILTIGNORE_FILE, } from "./constants.js";
|
|
7
|
+
import { FliskError } from "./errors.js";
|
|
8
|
+
import { isFliskGeneratedPath } from "./generated-paths.js";
|
|
9
|
+
/** Snapshot directory `flisk up` writes next to a service's `.autogenerated/` outputs. */
|
|
10
|
+
const CONTRACT_DIR = ".flisk-contract";
|
|
11
|
+
/** Generated service file that is exempt from the hand-edit check. */
|
|
12
|
+
const SERVICE_YAML = "service.yaml";
|
|
13
|
+
/**
|
|
14
|
+
* Explains one project file using the generated-path allowlist and the hand-edit check that `flisk up` runs.
|
|
15
|
+
* `file` resolves against the current directory, as shell paths do. Throws a FliskError for a path outside the
|
|
16
|
+
* project, a missing file, or a directory.
|
|
17
|
+
*/
|
|
18
|
+
export function explainConfigFile(projectRoot, file) {
|
|
19
|
+
const absolute = resolve(file);
|
|
20
|
+
const rel = relative(projectRoot, absolute).split(sep).join("/");
|
|
21
|
+
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute(rel)) {
|
|
22
|
+
throw new FliskError(`${file} is outside the project`, ["Pass a file inside the project root"]);
|
|
23
|
+
}
|
|
24
|
+
if (!existsSync(absolute)) {
|
|
25
|
+
throw new FliskError(`File not found: ${rel}`, ["Check the path and try again"]);
|
|
26
|
+
}
|
|
27
|
+
if (!statSync(absolute).isFile()) {
|
|
28
|
+
throw new FliskError(`Not a file: ${rel}`, ["Pass a file, not a directory"]);
|
|
29
|
+
}
|
|
30
|
+
const isEditedNow = isHandEditedNow(projectRoot, rel);
|
|
31
|
+
if (!isFliskGeneratedPath(rel)) {
|
|
32
|
+
return userOwned(rel, isEditedNow);
|
|
33
|
+
}
|
|
34
|
+
const segments = rel.split("/");
|
|
35
|
+
const autogenIndex = segments.indexOf(AUTOGENERATED_DIR);
|
|
36
|
+
if (autogenIndex >= 0) {
|
|
37
|
+
return explainServiceOutput(projectRoot, rel, segments, autogenIndex, isEditedNow);
|
|
38
|
+
}
|
|
39
|
+
// The allowlist names these paths, but the path alone does not show who wrote the file. Flisk creates
|
|
40
|
+
// package.json only when it is missing and never overwrites an existing one, so it is the user's to edit.
|
|
41
|
+
if (rel === PACKAGE_JSON) {
|
|
42
|
+
return {
|
|
43
|
+
file: rel,
|
|
44
|
+
ownership: "user-owned",
|
|
45
|
+
reason: "Flisk creates package.json only when it is missing and never overwrites an existing one.",
|
|
46
|
+
source: null,
|
|
47
|
+
driftOnUp: "none",
|
|
48
|
+
isEditedNow,
|
|
49
|
+
next: ["Edit it directly."],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// A root .tiltignore without Flisk's marker belongs to the team, and Flisk leaves it in place.
|
|
53
|
+
if (rel === TILTIGNORE_FILE &&
|
|
54
|
+
!readFileSync(absolute, "utf-8").includes(FLISK_GENERATED_MARKER)) {
|
|
55
|
+
return {
|
|
56
|
+
file: rel,
|
|
57
|
+
ownership: "user-owned",
|
|
58
|
+
reason: "This .tiltignore has no Flisk generated marker, so Flisk leaves it in place.",
|
|
59
|
+
source: null,
|
|
60
|
+
driftOnUp: "none",
|
|
61
|
+
isEditedNow,
|
|
62
|
+
next: ["Edit it directly."],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (isProjectOutput(rel)) {
|
|
66
|
+
return {
|
|
67
|
+
file: rel,
|
|
68
|
+
ownership: "generated",
|
|
69
|
+
reason: "Generated from .flisk/project.json.",
|
|
70
|
+
source: `.flisk/${PROJECT_JSON}`,
|
|
71
|
+
driftOnUp: "none",
|
|
72
|
+
isEditedNow,
|
|
73
|
+
next: [
|
|
74
|
+
`Edit .flisk/${PROJECT_JSON} instead.`,
|
|
75
|
+
"`flisk config regenerate` rewrites this file from project.json and discards edits.",
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
file: rel,
|
|
81
|
+
ownership: "generated",
|
|
82
|
+
reason: "Written by Flisk; this file does not record the input it is generated from.",
|
|
83
|
+
source: null,
|
|
84
|
+
driftOnUp: "none",
|
|
85
|
+
isEditedNow,
|
|
86
|
+
next: ["Do not edit it; Flisk writes it when it generates outputs."],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function userOwned(rel, isEditedNow) {
|
|
90
|
+
const base = { file: rel, ownership: "user-owned", isEditedNow };
|
|
91
|
+
if (rel === `.flisk/${PROJECT_JSON}`) {
|
|
92
|
+
return {
|
|
93
|
+
...base,
|
|
94
|
+
reason: "Flisk input: the project config that generated outputs are built from.",
|
|
95
|
+
source: null,
|
|
96
|
+
driftOnUp: "none",
|
|
97
|
+
next: ["Edit it, then run `flisk config regenerate`."],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (rel.split("/").pop() === SERVICE_JSON) {
|
|
101
|
+
return {
|
|
102
|
+
...base,
|
|
103
|
+
reason: "Flisk input: the service manifest that generated service files are built from.",
|
|
104
|
+
source: null,
|
|
105
|
+
driftOnUp: "none",
|
|
106
|
+
next: ["Edit it, then run `flisk up` to regenerate the service's outputs."],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
...base,
|
|
111
|
+
reason: "Not in Flisk's generated-file allowlist, so Flisk does not write it.",
|
|
112
|
+
source: null,
|
|
113
|
+
driftOnUp: "none",
|
|
114
|
+
next: ["Edit it directly."],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function explainServiceOutput(projectRoot, rel, segments, autogenIndex, isEditedNow) {
|
|
118
|
+
const serviceDir = segments.slice(0, autogenIndex).join("/");
|
|
119
|
+
const source = serviceDir ? `${serviceDir}/${SERVICE_JSON}` : SERVICE_JSON;
|
|
120
|
+
if (!existsSync(join(projectRoot, source))) {
|
|
121
|
+
return {
|
|
122
|
+
file: rel,
|
|
123
|
+
ownership: "unknown",
|
|
124
|
+
reason: `${AUTOGENERATED_DIR}/ has no ${SERVICE_JSON} beside it, so Flisk cannot tell which input produces this file.`,
|
|
125
|
+
source: null,
|
|
126
|
+
driftOnUp: "unknown",
|
|
127
|
+
isEditedNow,
|
|
128
|
+
next: ["Find where the file came from before editing it."],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const inside = segments.slice(autogenIndex + 1);
|
|
132
|
+
if (inside[0] === CONTRACT_DIR) {
|
|
133
|
+
return {
|
|
134
|
+
file: rel,
|
|
135
|
+
ownership: "generated",
|
|
136
|
+
reason: "Flisk's snapshot of the generated files; `flisk up` reads it to detect hand edits.",
|
|
137
|
+
source,
|
|
138
|
+
driftOnUp: "none",
|
|
139
|
+
isEditedNow,
|
|
140
|
+
next: ["Do not edit it; Flisk writes it when it generates outputs."],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const insidePath = inside.join("/");
|
|
144
|
+
if (insidePath === SERVICE_YAML) {
|
|
145
|
+
return {
|
|
146
|
+
file: rel,
|
|
147
|
+
ownership: "generated",
|
|
148
|
+
reason: "Runtime discovery file written by the Tilt extension; exempt from the hand-edit check.",
|
|
149
|
+
source,
|
|
150
|
+
driftOnUp: "none",
|
|
151
|
+
isEditedNow,
|
|
152
|
+
next: [`Do not edit it; edit ${source} instead.`],
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const snapshot = join(projectRoot, serviceDir, AUTOGENERATED_DIR, CONTRACT_DIR, insidePath);
|
|
156
|
+
if (!existsSync(snapshot)) {
|
|
157
|
+
return {
|
|
158
|
+
file: rel,
|
|
159
|
+
ownership: "generated",
|
|
160
|
+
reason: `Generated from ${source}. Flisk has no snapshot of this file, so it cannot tell whether an edit would block flisk up.`,
|
|
161
|
+
source,
|
|
162
|
+
driftOnUp: "unknown",
|
|
163
|
+
isEditedNow,
|
|
164
|
+
next: ["Run `flisk up` to write the snapshots, then explain the file again."],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
file: rel,
|
|
169
|
+
ownership: "generated",
|
|
170
|
+
reason: `Generated from ${source}.`,
|
|
171
|
+
source,
|
|
172
|
+
driftOnUp: "blocks-up",
|
|
173
|
+
isEditedNow,
|
|
174
|
+
next: [
|
|
175
|
+
`Edit ${source} instead, then run \`flisk up\`.`,
|
|
176
|
+
"Discard a hand edit with `flisk config regenerate`.",
|
|
177
|
+
"Keep a hand edit on purpose with `flisk up --ignore-drift`.",
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function isProjectOutput(rel) {
|
|
182
|
+
return (rel.startsWith(".flisk/.flisk-out/") ||
|
|
183
|
+
rel === "Tiltfile" ||
|
|
184
|
+
/^TILT_[^/]+\.star$/.test(rel) ||
|
|
185
|
+
rel === TILTIGNORE_FILE);
|
|
186
|
+
}
|
|
187
|
+
/** The same hand-edit list `flisk up` blocks on, so explain and the gate cannot disagree. */
|
|
188
|
+
function isHandEditedNow(projectRoot, rel) {
|
|
189
|
+
try {
|
|
190
|
+
return verifyMasterConfigs(projectRoot).handEdited.includes(rel);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (error instanceof ProjectConfigNotFoundError)
|
|
194
|
+
return false;
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Copyright (c) 2026 Flisk Landscape contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
/**
|
|
7
|
+
* Stop the stack of an existing demo folder before the folder is deleted. Deleting the folder alone leaves its
|
|
8
|
+
* containers running, and a standalone Traefik that stays up keeps port 80 for the next run.
|
|
9
|
+
* Returns true when a Flisk project was found and its `flisk down` ran.
|
|
10
|
+
*/
|
|
11
|
+
export function stopDemoStack(projectDir, runDown = runFliskDown) {
|
|
12
|
+
if (!existsSync(join(projectDir, ".flisk", "project.json")))
|
|
13
|
+
return false;
|
|
14
|
+
runDown(projectDir);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
function runFliskDown(projectDir) {
|
|
18
|
+
execFileSync(process.execPath, [process.argv[1] ?? "flisk", "down", "--yes"], {
|
|
19
|
+
cwd: projectDir,
|
|
20
|
+
stdio: "inherit",
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -52,12 +52,18 @@ export function readDiscoveryPaths(projectRoot) {
|
|
|
52
52
|
const configPath = join(projectRoot, ".flisk", PROJECT_JSON);
|
|
53
53
|
if (!existsSync(configPath))
|
|
54
54
|
return DEFAULT_DISCOVERY_PATHS;
|
|
55
|
+
let reason = "discovery.paths must be an array of strings";
|
|
55
56
|
try {
|
|
56
57
|
const paths = JSON.parse(readFileSync(configPath, "utf-8"))?.discovery?.paths;
|
|
58
|
+
if (paths === undefined)
|
|
59
|
+
return DEFAULT_DISCOVERY_PATHS;
|
|
57
60
|
if (Array.isArray(paths) && paths.every((p) => typeof p === "string"))
|
|
58
61
|
return paths;
|
|
59
62
|
}
|
|
60
|
-
catch {
|
|
63
|
+
catch (error) {
|
|
64
|
+
reason = String(error);
|
|
65
|
+
}
|
|
66
|
+
console.warn(`Ignoring discovery configuration in ${configPath}: ${reason}; using default paths: ${DEFAULT_DISCOVERY_PATHS.join(", ")}`);
|
|
61
67
|
return DEFAULT_DISCOVERY_PATHS;
|
|
62
68
|
}
|
|
63
69
|
/** Whether Tilt's discovery would find a service.json in `resourceDir` given `patterns`. */
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { formatCount } from "./formatting.js";
|
|
2
|
+
import { findMissingPrograms, isPortListening, isProcessAlive, localUrl, planNativeRun, readNativeRunState, } from "./native-runtime.js";
|
|
3
|
+
import { isNativeService } from "./native-service.js";
|
|
4
|
+
import { findOnPath } from "./which.js";
|
|
5
|
+
/**
|
|
6
|
+
* Doctor checks for a project whose services all declare `command` (#2231). Such a project runs as local processes, so doctor
|
|
7
|
+
* checks what those processes need (their programs, their dependsOn, their ports) instead of Docker, Compose and Tilt.
|
|
8
|
+
*/
|
|
9
|
+
/** Says which runtime doctor checked, so a missing Docker check reads as intended and not as forgotten. */
|
|
10
|
+
export function checkNativeRuntime(resources) {
|
|
11
|
+
return {
|
|
12
|
+
name: "Runtime",
|
|
13
|
+
didPass: true,
|
|
14
|
+
message: `Local processes: ${formatCount(resources.length, "service")} declare a command, so Docker, Compose and Tilt are not needed`,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Each service's command starts a program that is installed. */
|
|
18
|
+
export function checkNativeCommands(resources, find = findOnPath) {
|
|
19
|
+
const missing = findMissingPrograms(planNativeRun(resources.filter(isNativeService)).services, find);
|
|
20
|
+
if (missing.length === 0) {
|
|
21
|
+
return {
|
|
22
|
+
name: "Service commands",
|
|
23
|
+
didPass: true,
|
|
24
|
+
message: "Every service command starts a program that is installed",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
name: "Service commands",
|
|
29
|
+
didPass: false,
|
|
30
|
+
message: `${formatCount(missing.length, "service")} run a program that is not on PATH:\n ${missing.map(({ name, program }) => `${name}: ${program}`).join("\n ")}`,
|
|
31
|
+
fix: 'Install the program, or change "command" in the service.json',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** A dependsOn name must be another service of the project: local processes cannot start a database or gateway for it. */
|
|
35
|
+
export function checkNativeDependsOn(resources) {
|
|
36
|
+
const names = new Set(resources.map((resource) => resource.name));
|
|
37
|
+
const problems = resources.flatMap((resource) => {
|
|
38
|
+
const deps = resource.config?.dependsOn;
|
|
39
|
+
return (Array.isArray(deps) ? deps : [])
|
|
40
|
+
.filter((dep) => !names.has(dep))
|
|
41
|
+
.map((dep) => `${resource.name} dependsOn "${dep}", which is not a service of this project`);
|
|
42
|
+
});
|
|
43
|
+
if (problems.length === 0) {
|
|
44
|
+
return {
|
|
45
|
+
name: "Service dependencies",
|
|
46
|
+
didPass: true,
|
|
47
|
+
message: "Every dependsOn names a service of this project",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
name: "Service dependencies",
|
|
52
|
+
didPass: false,
|
|
53
|
+
message: problems.join("\n "),
|
|
54
|
+
fix: "Add the missing service with a command, or remove it from dependsOn. A database or the gateway still needs the Docker path.",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* When a native run is up, each service answers on its health URL (or accepts connections on its port). Skipped when nothing
|
|
59
|
+
* runs: doctor is also used before the first `flisk up`.
|
|
60
|
+
*/
|
|
61
|
+
export async function checkNativeServiceHealth(projectRoot, resources, timeoutMs) {
|
|
62
|
+
const state = readNativeRunState(projectRoot);
|
|
63
|
+
if (!state || !isProcessAlive(state.pid)) {
|
|
64
|
+
return {
|
|
65
|
+
name: "Service Health",
|
|
66
|
+
didPass: true,
|
|
67
|
+
isSkipped: true,
|
|
68
|
+
message: "Not running - start it with flisk up",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const failing = [];
|
|
72
|
+
for (const plan of planNativeRun(resources.filter(isNativeService)).services) {
|
|
73
|
+
if (plan.port === undefined)
|
|
74
|
+
continue;
|
|
75
|
+
const answered = plan.healthUrl
|
|
76
|
+
? await fetch(plan.healthUrl, { signal: AbortSignal.timeout(timeoutMs) })
|
|
77
|
+
.then((response) => response.status < 400)
|
|
78
|
+
.catch(() => false)
|
|
79
|
+
: await isPortListening(plan.port, timeoutMs);
|
|
80
|
+
if (!answered)
|
|
81
|
+
failing.push(`${plan.name}: no answer from ${plan.healthUrl ?? localUrl(plan.port)}`);
|
|
82
|
+
}
|
|
83
|
+
if (failing.length === 0) {
|
|
84
|
+
return {
|
|
85
|
+
name: "Service Health",
|
|
86
|
+
didPass: true,
|
|
87
|
+
message: "Every running service answers",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
name: "Service Health",
|
|
92
|
+
didPass: false,
|
|
93
|
+
message: failing.join("\n "),
|
|
94
|
+
fix: "Read the service output in .flisk/native/logs/<service>.log, or the flisk up terminal",
|
|
95
|
+
};
|
|
96
|
+
}
|