@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.
Files changed (83) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +119 -43
  3. package/cli/dist/backend-languages/bun.js +5 -0
  4. package/cli/dist/backend-languages/go.js +3 -1
  5. package/cli/dist/cli.js +9 -0
  6. package/cli/dist/commands/completion.js +8 -1
  7. package/cli/dist/commands/config.js +130 -55
  8. package/cli/dist/commands/doctor.js +16 -15
  9. package/cli/dist/commands/logs.js +1 -0
  10. package/cli/dist/commands/project.js +1 -0
  11. package/cli/dist/commands/resource.js +39 -11
  12. package/cli/dist/commands/status.js +18 -9
  13. package/cli/dist/commands/up.js +102 -17
  14. package/cli/dist/frontend-frameworks/next.js +197 -0
  15. package/cli/dist/frontend-frameworks/react.js +81 -12
  16. package/cli/dist/frontend-frameworks/registry.js +5 -4
  17. package/cli/dist/frontend-frameworks/vue.js +82 -12
  18. package/cli/dist/generator/resource-templates.js +2 -4
  19. package/cli/dist/generator/template-engine.js +5 -3
  20. package/cli/dist/utils/cold-preflight.js +3 -2
  21. package/cli/dist/utils/config-explain.js +197 -0
  22. package/cli/dist/utils/demo-reset.js +22 -0
  23. package/cli/dist/utils/discovery-paths.js +7 -1
  24. package/cli/dist/utils/host-port-plan.js +8 -3
  25. package/cli/dist/utils/port-conflict-fix.js +16 -14
  26. package/cli/dist/utils/port-occupant.js +35 -0
  27. package/cli/dist/utils/project-features.js +7 -16
  28. package/cli/dist/utils/project-templates.js +8 -8
  29. package/cli/dist/utils/resource-features.js +5 -5
  30. package/cli/dist/utils/service-manifest.js +1 -0
  31. package/cli/dist/utils/service-urls.js +11 -0
  32. package/cli/dist/utils/startup-report.js +4 -3
  33. package/cli/dist/utils/tar.js +33 -28
  34. package/cli/dist/utils/tilt-process.js +4 -3
  35. package/cli/dist/utils/windows-doctor.js +18 -15
  36. package/cli/dist-bundle/{chunk-IGN5UC3P.js → chunk-4J2FLFUA.js} +14 -7
  37. package/cli/dist-bundle/{chunk-ETKUVKJ2.js → chunk-BMHPPX2Q.js} +10 -10
  38. package/cli/dist-bundle/chunk-BP2M4OJ4.js +2 -0
  39. package/cli/dist-bundle/{chunk-53L2JN7G.js → chunk-CGTWXWBU.js} +1 -1
  40. package/cli/dist-bundle/chunk-GPUJ55MI.js +3 -0
  41. package/cli/dist-bundle/{chunk-5QBYM33Q.js → chunk-LUUJ4JB7.js} +1 -1
  42. package/cli/dist-bundle/chunk-PEZQEZVV.js +17 -0
  43. package/cli/dist-bundle/{chunk-MWOVYIBB.js → chunk-SFOFTVYM.js} +5 -5
  44. package/cli/dist-bundle/{chunk-3XODDLRJ.js → chunk-TIYTGN5R.js} +1 -1
  45. package/cli/dist-bundle/{chunk-YNCBBJ4V.js → chunk-U42XZWXB.js} +1 -1
  46. package/cli/dist-bundle/chunk-ULMQVD7J.js +122 -0
  47. package/cli/dist-bundle/chunk-V36PT2HF.js +24 -0
  48. package/cli/dist-bundle/{chunk-I7TGIK5N.js → chunk-VMVTECCK.js} +1 -1
  49. package/cli/dist-bundle/{chunk-DU2BN46C.js → chunk-Z5QEE4SW.js} +1 -1
  50. package/cli/dist-bundle/cli.js +607 -375
  51. package/cli/dist-bundle/cold-preflight-3BNDOLGS.js +2 -0
  52. package/cli/dist-bundle/{demo-stage-OHNTP2UJ.js → demo-stage-G4W23BXW.js} +1 -1
  53. package/cli/dist-bundle/{extension-fetch-VEFSCG4B.js → extension-fetch-KIPYDIRM.js} +1 -1
  54. package/cli/dist-bundle/{open-XROJ6YEY.js → open-HQFT6WDC.js} +1 -1
  55. package/cli/dist-bundle/project-JZO2CPZZ.js +2 -0
  56. package/cli/dist-bundle/{project-templates-EYSQG2EO.js → project-templates-IMJ3OX5I.js} +1 -1
  57. package/cli/dist-bundle/{services-BIMF6MVU.js → services-GLFKZBBZ.js} +1 -1
  58. package/cli/dist-bundle/{tilt-port-owner-ZY5M2JC4.js → tilt-port-owner-PJN4MPYE.js} +1 -1
  59. package/cli/package.json +1 -1
  60. package/package.json +1 -1
  61. package/runtime/engine/topologies/platform/docker/compose/compose.star +40 -4
  62. package/runtime/engine/topologies/platform/docker/dockerfile/language_dockerfile.star +6 -0
  63. package/runtime/engine/topologies/platform/docker/dockerfile/next_dockerfile.star +83 -0
  64. package/runtime/engine/topologies/platform/docker/index.star +4 -0
  65. package/runtime/engine/topologies/platform/docker/layers/prisma/filesystem_setup.star +7 -0
  66. package/runtime/engine/topologies/platform/docker/networking/traefik.star +85 -46
  67. package/runtime/engine/topologies/tilt/common/utils_output.star +1 -0
  68. package/runtime/engine/topologies/tilt/generators/validators.star +1 -1
  69. package/runtime/engine/topologies/tilt/generators/vite/frontend.star +2 -2
  70. package/runtime/engine/topologies/tilt/generators/vite/generators.star +3 -0
  71. package/runtime/engine/topologies/tilt/manifest/constants.star +3 -0
  72. package/runtime/engine/topologies/tilt/manifest/parser.star +9 -3
  73. package/runtime/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +8 -3
  74. package/runtime/engine/topologies/tilt/resources/orchestrator/generators/dependencies/resolver.star +50 -24
  75. package/runtime/engine/topologies/tilt/resources/orchestrator/generators/env.star +11 -2
  76. package/runtime/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +23 -9
  77. package/cli/dist-bundle/chunk-43XWL2PB.js +0 -61
  78. package/cli/dist-bundle/chunk-5F7YP5IR.js +0 -17
  79. package/cli/dist-bundle/chunk-K6SOCO6V.js +0 -3
  80. package/cli/dist-bundle/chunk-YHYRGWUH.js +0 -24
  81. package/cli/dist-bundle/chunk-YL56J2WX.js +0 -2
  82. package/cli/dist-bundle/cold-preflight-ZOPR7PWJ.js +0 -2
  83. package/cli/dist-bundle/project-7PTIOHGF.js +0 -2
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Next.js (App Router) is a `server` provider: it owns its build and dev server, so there is no Vite config and no nginx.
3
+ * Under `flisk up` the engine runs `next dev` in a Node image (live reload through Tilt), served under `/<name>/` by Traefik.
4
+ * The engine writes `.autogenerated/.env.frontend.autogenerated`; the generated Dockerfile copies it to `.env`, so
5
+ * `next.config.mjs` and the page read `FLISK_BASE_PATH` and `NEXT_PUBLIC_API_URL` without any wiring in the app.
6
+ */
7
+ const NEXT_CONFIG = `// FLISK_BASE_PATH is set by flisk up (the route this app is served under, for example /web). On the host it is unset,
8
+ // so \`next dev\` serves from the root.
9
+ const basePath = process.env.FLISK_BASE_PATH ?? '';
10
+
11
+ /** @type {import('next').NextConfig} */
12
+ const nextConfig = {
13
+ basePath,
14
+ // Flisk's route is /<name>/; with trailingSlash the page answers there instead of redirecting.
15
+ trailingSlash: true,
16
+ output: 'standalone',
17
+ reactStrictMode: true,
18
+ };
19
+
20
+ export default nextConfig;
21
+ `;
22
+ const STARTER_STYLES = `const page = { maxWidth: 640, margin: '0 auto', padding: '3rem 1.5rem', fontFamily: 'system-ui, sans-serif', color: '#1f2933' };
23
+ const muted = { color: '#616e7c', marginTop: 0 };
24
+ `;
25
+ function renderPage(name, backendName) {
26
+ return `${backendName ? "import HealthCard from './health-card';\n\n" : ""}${STARTER_STYLES}
27
+ export default function Home() {
28
+ return (
29
+ <main style={page}>
30
+ <h1>${name}</h1>
31
+ <p style={muted}>Flisk frontend (Next.js)</p>
32
+ ${backendName ? "\n <HealthCard />\n" : ""}
33
+ <p>Edit <code>src/app/page.tsx</code> and save. It live-reloads.</p>
34
+ <ul>
35
+ <li><a href="http://localhost:10350">Tilt UI</a>: logs and resource status</li>
36
+ <li><code>flisk status</code>: every resource in this project</li>
37
+ </ul>
38
+ </main>
39
+ );
40
+ }
41
+ `;
42
+ }
43
+ function renderHealthCard(backendName) {
44
+ return `'use client';
45
+
46
+ import { useEffect, useState } from 'react';
47
+
48
+ // Set by flisk up from the backend's route; empty on the host, where the browser calls the same origin.
49
+ const API_URL = process.env.NEXT_PUBLIC_API_URL ?? '';
50
+ const BACKEND_NAME = '${backendName}';
51
+
52
+ const card = { border: '1px solid #d9e2ec', borderRadius: 8, padding: '1rem 1.25rem', margin: '1.5rem 0' };
53
+ const cardHeader = { display: 'flex', justifyContent: 'space-between', alignItems: 'center' };
54
+ const pre = { background: '#f5f7fa', borderRadius: 6, padding: '0.75rem', overflowX: 'auto' as const };
55
+
56
+ interface Health {
57
+ ok: boolean;
58
+ body: unknown;
59
+ }
60
+
61
+ export default function HealthCard() {
62
+ const [health, setHealth] = useState<Health | null>(null);
63
+
64
+ useEffect(() => {
65
+ const controller = new AbortController();
66
+ fetch(API_URL + '/health', { signal: controller.signal })
67
+ .then(async (response) => {
68
+ // Read the body as text first: a non-JSON error page must show its real status, not an unreachable backend.
69
+ const text = await response.text();
70
+ let body: unknown = text;
71
+ try {
72
+ body = JSON.parse(text);
73
+ } catch {
74
+ // keep the raw text
75
+ }
76
+ setHealth({ ok: response.ok, body: { status: response.status, body } });
77
+ })
78
+ .catch((error: unknown) => {
79
+ if (!controller.signal.aborted) setHealth({ ok: false, body: { error: String(error) } });
80
+ });
81
+ return () => controller.abort();
82
+ }, []);
83
+
84
+ return (
85
+ <section style={card}>
86
+ <div style={cardHeader}>
87
+ <strong>Backend: {BACKEND_NAME}</strong>
88
+ {health === null && <span>checking…</span>}
89
+ {health?.ok === true && <span style={{ color: '#0a7f3f' }}>ok</span>}
90
+ {health?.ok === false && <span style={{ color: '#b42318' }}>unreachable</span>}
91
+ </div>
92
+ {health && <pre style={pre}>{JSON.stringify(health.body, null, 2)}</pre>}
93
+ </section>
94
+ );
95
+ }
96
+ `;
97
+ }
98
+ export const nextFrontendProvider = {
99
+ id: "next",
100
+ label: "Next.js (App Router)",
101
+ kind: "server",
102
+ dependencies: {
103
+ next: "^15.5.0",
104
+ react: "^19.0.0",
105
+ "react-dom": "^19.0.0",
106
+ },
107
+ devDependencies: {
108
+ "@types/react": "^19.0.0",
109
+ "@types/react-dom": "^19.0.0",
110
+ // `next build` type-checks through the TypeScript JavaScript API.
111
+ typescript: "^5.9.0",
112
+ // The image installs with npm, which enforces peer ranges: vitest 5 peers on @types/node ^22 || >=24, and the
113
+ // scaffold's ^20 otherwise fails `npm install` with ERESOLVE.
114
+ "@types/node": "^22.0.0",
115
+ },
116
+ compilerOptions: {},
117
+ scripts: {
118
+ dev: "next dev",
119
+ build: "next build",
120
+ start: "next start",
121
+ },
122
+ tsconfig: {
123
+ compilerOptions: {
124
+ target: "ES2022",
125
+ lib: ["dom", "dom.iterable", "esnext"],
126
+ allowJs: true,
127
+ skipLibCheck: true,
128
+ strict: true,
129
+ noEmit: true,
130
+ esModuleInterop: true,
131
+ module: "esnext",
132
+ moduleResolution: "bundler",
133
+ resolveJsonModule: true,
134
+ isolatedModules: true,
135
+ jsx: "preserve",
136
+ incremental: true,
137
+ plugins: [{ name: "next" }],
138
+ },
139
+ include: ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", ".next/types/**/*.ts"],
140
+ exclude: ["node_modules"],
141
+ },
142
+ createFiles(name, options = {}) {
143
+ return [
144
+ {
145
+ filename: "next.config.mjs",
146
+ content: NEXT_CONFIG,
147
+ description: "Generating Next.js config",
148
+ emoji: "⚙️",
149
+ },
150
+ {
151
+ filename: "src/app/layout.tsx",
152
+ content: `import type { ReactNode } from 'react';
153
+
154
+ export const metadata = { title: '${name}' };
155
+
156
+ export default function RootLayout({ children }: { children: ReactNode }) {
157
+ return (
158
+ <html lang="en">
159
+ <body>{children}</body>
160
+ </html>
161
+ );
162
+ }
163
+ `,
164
+ description: "Generating Next.js root layout",
165
+ emoji: "💻",
166
+ },
167
+ {
168
+ filename: "src/app/page.tsx",
169
+ content: renderPage(name, options.backendName),
170
+ description: "Generating Next.js home page",
171
+ emoji: "💻",
172
+ },
173
+ {
174
+ filename: "src/app/health/route.ts",
175
+ content: `// The engine probes /<name>/health; the base path puts this route under the same prefix.
176
+ export const dynamic = 'force-static';
177
+
178
+ export function GET() {
179
+ return new Response('ok');
180
+ }
181
+ `,
182
+ description: "Generating health route",
183
+ emoji: "🩺",
184
+ },
185
+ ...(options.backendName
186
+ ? [
187
+ {
188
+ filename: "src/app/health-card.tsx",
189
+ content: renderHealthCard(options.backendName),
190
+ description: "Generating backend health card",
191
+ emoji: "💻",
192
+ },
193
+ ]
194
+ : []),
195
+ ];
196
+ },
197
+ };
@@ -1,3 +1,80 @@
1
+ const STARTER_STYLES = `const page = { maxWidth: 640, margin: '0 auto', padding: '3rem 1.5rem', fontFamily: 'system-ui, sans-serif', color: '#1f2933' };
2
+ const muted = { color: '#616e7c', marginTop: 0 };
3
+ const card = { border: '1px solid #d9e2ec', borderRadius: 8, padding: '1rem 1.25rem', margin: '1.5rem 0' };
4
+ const cardHeader = { display: 'flex', justifyContent: 'space-between', alignItems: 'center' };
5
+ const pre = { background: '#f5f7fa', borderRadius: 6, padding: '0.75rem', overflowX: 'auto' };
6
+ `;
7
+ /** Hints and links shared by both starters. They need no backend, so they appear in every frontend. */
8
+ const STARTER_HINTS = ` <p>Edit <code>src/App.tsx</code> and save. It live-reloads.</p>
9
+ <ul>
10
+ <li><a href="http://localhost:10350">Tilt UI</a>: logs and resource status</li>
11
+ <li><code>flisk status</code>: every resource in this project</li>
12
+ </ul>`;
13
+ function renderAppWithBackend(name, backendName) {
14
+ return `/// <reference types="vite/client" />
15
+ import { useEffect, useState } from 'react';
16
+
17
+ const API_URL = import.meta.env.VITE_API_URL ?? '';
18
+ const BACKEND_NAME = '${backendName}';
19
+
20
+ ${STARTER_STYLES}
21
+ interface Health {
22
+ ok: boolean;
23
+ body: unknown;
24
+ }
25
+
26
+ function App() {
27
+ const [health, setHealth] = useState<Health | null>(null);
28
+
29
+ useEffect(() => {
30
+ const controller = new AbortController();
31
+ fetch(API_URL + '/health', { signal: controller.signal })
32
+ .then(async (response) => setHealth({ ok: response.ok, body: await response.json() }))
33
+ .catch((error: unknown) => {
34
+ if (!controller.signal.aborted) setHealth({ ok: false, body: { error: String(error) } });
35
+ });
36
+ return () => controller.abort();
37
+ }, []);
38
+
39
+ return (
40
+ <main style={page}>
41
+ <h1>${name}</h1>
42
+ <p style={muted}>Flisk frontend</p>
43
+
44
+ <section style={card}>
45
+ <div style={cardHeader}>
46
+ <strong>Backend: {BACKEND_NAME}</strong>
47
+ {health === null && <span>checking…</span>}
48
+ {health?.ok === true && <span style={{ color: '#0a7f3f' }}>ok</span>}
49
+ {health?.ok === false && <span style={{ color: '#b42318' }}>unreachable</span>}
50
+ </div>
51
+ {health && <pre>{JSON.stringify(health.body, null, 2)}</pre>}
52
+ </section>
53
+
54
+ ${STARTER_HINTS}
55
+ </main>
56
+ );
57
+ }
58
+
59
+ export default App;
60
+ `;
61
+ }
62
+ function renderAppWithoutBackend(name) {
63
+ return `${STARTER_STYLES}
64
+ function App() {
65
+ return (
66
+ <main style={page}>
67
+ <h1>${name}</h1>
68
+ <p style={muted}>Flisk frontend</p>
69
+
70
+ ${STARTER_HINTS}
71
+ </main>
72
+ );
73
+ }
74
+
75
+ export default App;
76
+ `;
77
+ }
1
78
  export const reactFrontendProvider = {
2
79
  id: "react",
3
80
  label: "React + Vite",
@@ -13,7 +90,7 @@ export const reactFrontendProvider = {
13
90
  compilerOptions: {
14
91
  jsx: "react-jsx",
15
92
  },
16
- createFiles(name) {
93
+ createFiles(name, options = {}) {
17
94
  return [
18
95
  {
19
96
  filename: "index.html",
@@ -50,17 +127,9 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
50
127
  },
51
128
  {
52
129
  filename: "src/App.tsx",
53
- content: `function App() {
54
- return (
55
- <div style={{ padding: '2rem', fontFamily: 'system-ui' }}>
56
- <h1>${name}</h1>
57
- <p>Frontend resource created with Flisk</p>
58
- </div>
59
- );
60
- }
61
-
62
- export default App;
63
- `,
130
+ content: options.backendName
131
+ ? renderAppWithBackend(name, options.backendName)
132
+ : renderAppWithoutBackend(name),
64
133
  description: "Generating React app",
65
134
  emoji: "💻",
66
135
  },
@@ -2,6 +2,7 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  import { FliskError } from "../utils/errors.js";
4
4
  import { litFrontendProvider } from "./lit.js";
5
+ import { nextFrontendProvider } from "./next.js";
5
6
  import { preactFrontendProvider } from "./preact.js";
6
7
  import { qwikFrontendProvider } from "./qwik.js";
7
8
  import { reactFrontendProvider } from "./react.js";
@@ -21,11 +22,11 @@ export const FRONTEND_FRAMEWORKS = {
21
22
  qwik: qwikFrontendProvider,
22
23
  vanilla: vanillaFrontendProvider,
23
24
  "tanstack-router": tanstackRouterFrontendProvider,
25
+ next: nextFrontendProvider,
24
26
  };
25
- // Frameworks that own their own server or build config. They are never scaffolded as Vite SPA providers; they go through
26
- // `flisk resource <name> --type bring-your-own` instead.
27
+ // Meta-frameworks that are not registered providers. They are never scaffolded as Vite SPA providers; they go through
28
+ // `flisk resource <name> --type bring-your-own` instead. Next.js is a registered `server` provider, so it is not listed here.
27
29
  export const META_FRAMEWORK_IDS = [
28
- "next",
29
30
  "nuxt",
30
31
  "sveltekit",
31
32
  "astro",
@@ -49,7 +50,7 @@ export function listFrontendFrameworks() {
49
50
  return Object.entries(FRONTEND_FRAMEWORKS).map(([id, provider]) => ({
50
51
  id,
51
52
  label: provider.label,
52
- kind: "vite-spa",
53
+ kind: provider.kind ?? "vite-spa",
53
54
  verified: VERIFIED_FRONTEND_FRAMEWORKS.includes(id),
54
55
  command: `flisk resource <name> --type frontend --framework ${id}`,
55
56
  }));
@@ -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: `<script setup lang="ts">
44
- const message = 'Frontend resource created with Flisk';
45
- </script>
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, expect } from 'vitest';
125
+ return `import { describe, it } from 'vitest';
126
126
 
127
127
  describe('${name}', () => {
128
- it('should pass a basic test', () => {
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 (_error) { }
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 = [];
@@ -61,7 +61,7 @@ export function requireVerifiedDockerVersions(result) {
61
61
  fix: result.fix ?? "Start Docker and ensure Docker Engine 25+ and Compose 2.20.2+ are installed.",
62
62
  };
63
63
  }
64
- function usesPrisma(projectRoot) {
64
+ export function usesPrisma(projectRoot) {
65
65
  try {
66
66
  return discoverResourcesFromRoot(projectRoot).some((resource) => {
67
67
  return resource.config?.featuresEnabled?.includes("prisma") ?? false;
@@ -120,9 +120,10 @@ export async function runColdPreflight(opts = {}) {
120
120
  const bunPath = bunNeeded ? findOnPath("bun") : undefined;
121
121
  if (bunPath) {
122
122
  try {
123
+ // A cold start of bun can pass 3s on a busy machine; a timeout there reads as "not installed".
123
124
  const version = execFileSync(bunPath, ["--version"], {
124
125
  encoding: "utf-8",
125
- timeout: 3000,
126
+ timeout: 10_000,
126
127
  }).trim();
127
128
  bun = bunMeetsFloor(version);
128
129
  }