@cioky/vike-create 0.5.5 → 0.5.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +126 -702
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cioky/vike-create",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "Scaffold a Vike + Ripple TS project",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -1,40 +1,29 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync, mkdirSync, writeFileSync } from 'fs';
3
- import { join, resolve } from 'path';
2
+ import { readFileSync, mkdirSync, writeFileSync, cpSync, existsSync } from 'fs';
3
+ import { join, resolve, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
4
5
  import { execSync } from 'child_process';
5
6
 
6
- // --- arg parse ---
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const tplDir = join(__dirname, '../templates');
9
+
10
+ // ── Arg parse ─────────────────────────────────────────────
7
11
  const args = process.argv.slice(2);
8
12
  if (args.includes('--help') || args.includes('-h')) {
9
- console.log(`
10
- @cioky/vike-create — Scaffold a Vike + Ripple TS project
13
+ console.log(`@cioky/vike-create — Scaffold a Vike + Ripple TS project
11
14
 
12
- Usage:
13
- @cioky/vike-create [name] [options]
15
+ Usage: @cioky/vike-create [name] [options]
14
16
 
15
17
  Options:
16
- --style <name> CSS framework: tailwind (default), pandacss, none
17
- --cloudflare Add Cloudflare Workers configuration
18
- --remult Add Remult ORM (DO-based realtime with CF, SSE without)
18
+ --style <name> CSS: tailwind (default), pandacss, none
19
+ --cloudflare Add Cloudflare Workers config
20
+ --remult Add Remult ORM
19
21
  --betterauth Add Better Auth (requires --remult)
20
- --help, -h Show this help message
21
-
22
- Examples:
23
- @cioky/vike-create my-app
24
- @cioky/vike-create my-app --style pandacss
25
- @cioky/vike-create my-app --cloudflare
26
- @cioky/vike-create my-app --remult
27
- @cioky/vike-create my-app --remult --cloudflare
28
- `);
22
+ --help, -h Show this help`);
29
23
  process.exit(0);
30
24
  }
31
25
 
32
- let name = null;
33
- let style = 'tailwind';
34
- let cloudflare = false;
35
- let remult = false;
36
- let betterauth = false;
37
-
26
+ let name = null, style = 'tailwind', cloudflare = false, remult = false, betterauth = false;
38
27
  for (let i = 0; i < args.length; i++) {
39
28
  if (args[i] === '--style' && args[i + 1]) { style = args[++i]; continue; }
40
29
  if (args[i] === '--cloudflare') { cloudflare = true; continue; }
@@ -42,71 +31,66 @@ for (let i = 0; i < args.length; i++) {
42
31
  if (args[i] === '--betterauth') { betterauth = true; continue; }
43
32
  if (!args[i].startsWith('--') && !name) name = args[i];
44
33
  }
45
- if (!name && args.length && !args[0].startsWith('--')) name = args[0];
46
- if (!name) name = 'my-vike-app';
34
+ name = name || args[0] || 'my-vike-app';
47
35
  if (!['tailwind', 'pandacss', 'none'].includes(style)) {
48
- console.error(`Unknown style "${style}". Use tailwind, pandacss, or none.`);
49
- process.exit(1);
50
- }
51
- if (betterauth && !remult) {
52
- console.error('--betterauth requires --remult.');
53
- process.exit(1);
36
+ console.error(`Unknown style "${style}". Use tailwind, pandacss, or none.`); process.exit(1);
54
37
  }
38
+ if (betterauth && !remult) { console.error('--betterauth requires --remult.'); process.exit(1); }
55
39
 
56
40
  const root = resolve(process.cwd(), name);
41
+
42
+ // ── Create dirs ───────────────────────────────────────────
57
43
  mkdirSync(join(root, 'renderer'), { recursive: true });
58
44
  mkdirSync(join(root, 'src'), { recursive: true });
59
45
  mkdirSync(join(root, 'pages', 'index'), { recursive: true });
60
-
61
- // --- package.json ---
62
- const deps = {
63
- vike: '0.4.259',
64
- '@cioky/vike-core': '0.5.6',
65
- '@ripple-ts/vite-plugin': '0.3.85',
66
- ripple: '0.3.85',
67
- };
68
- const devDeps = { vite: '8.1.0', typescript: '5.9.3', '@tsrx/typescript-plugin': '0.3.85' };
69
- if (style === 'tailwind') {
70
- deps['@cioky/vike-tailwindcss'] = 'latest';
71
- deps['@tailwindcss/vite'] = 'latest';
46
+ mkdirSync(join(root, 'pages', 'about'), { recursive: true });
47
+ if (remult && cloudflare) {
48
+ mkdirSync(join(root, 'server'), { recursive: true });
49
+ mkdirSync(join(root, 'lib'), { recursive: true });
72
50
  }
73
- if (style === 'pandacss') {
74
- deps['@cioky/vike-pandacss'] = '0.1.0';
75
- deps['@pandacss/dev'] = '1.11.3';
51
+ if (betterauth) {
52
+ mkdirSync(join(root, 'entities'), { recursive: true });
53
+ mkdirSync(join(root, 'pages', 'login'), { recursive: true });
54
+ mkdirSync(join(root, 'pages', 'register'), { recursive: true });
55
+ mkdirSync(join(root, 'pages', 'dashboard'), { recursive: true });
56
+ mkdirSync(join(root, 'server'), { recursive: true });
76
57
  }
77
- if (cloudflare) {
78
- devDeps['@cloudflare/vite-plugin'] = '1.42.2';
79
- devDeps['@cloudflare/workers-types'] = '4.20260624.1';
80
- devDeps.wrangler = '4.104.0';
58
+ if (cloudflare) mkdirSync(join(root, '.wrangler'), { recursive: true });
59
+
60
+ // ── Copy templates ───────────────────────────────────────
61
+ function copyTemplates(dir) {
62
+ const src = join(tplDir, dir);
63
+ if (!existsSync(src)) return;
64
+ cpSync(src, root, { recursive: true, filter: (s) => !s.includes('node_modules') });
81
65
  }
66
+ copyTemplates('base');
67
+ if (style === 'pandacss') copyTemplates('pandacss');
68
+ else if (style === 'none') copyTemplates('none');
69
+ if (remult && cloudflare) copyTemplates('remult-cf');
70
+ else if (remult) copyTemplates('remult');
71
+ if (betterauth) copyTemplates('betterauth');
72
+
73
+ // ── Dynamic: package.json ─────────────────────────────────
74
+ const deps = { vike: '0.4.259', '@cioky/vike-core': '0.5.6', '@ripple-ts/vite-plugin': '0.3.85', ripple: '0.3.85' };
75
+ const devDeps = { vite: '8.1.0', typescript: '5.9.3', '@tsrx/typescript-plugin': '0.3.85' };
76
+
77
+ if (style === 'tailwind') { deps['@cioky/vike-tailwindcss'] = 'latest'; deps['@tailwindcss/vite'] = 'latest'; }
78
+ if (style === 'pandacss') { deps['@cioky/vike-pandacss'] = '0.1.0'; deps['@pandacss/dev'] = '1.11.3'; }
79
+ if (cloudflare) { devDeps['@cloudflare/vite-plugin'] = '1.42.2'; devDeps['@cloudflare/workers-types'] = '4.20260624.1'; devDeps.wrangler = '4.104.0'; }
82
80
  if (remult) {
83
81
  deps.remult = '3.3.13';
84
- if (cloudflare) {
85
- deps['remult-partykit'] = '1.1.0';
86
- deps.partyserver = '0.5.8';
87
- deps.hono = '4.12.27';
88
- deps['@vikejs/hono'] = '0.2.1';
89
- }
90
- }
91
- if (betterauth) {
92
- deps['better-auth'] = '1.6.20';
93
- deps['@nerdfolio/remult-better-auth'] = '0.4.3';
82
+ if (cloudflare) { deps['remult-partykit'] = '1.1.0'; deps.partyserver = '0.5.8'; deps.hono = '4.12.27'; deps['@vikejs/hono'] = '0.2.1'; }
94
83
  }
84
+ if (betterauth) { deps['better-auth'] = '1.6.20'; deps['@nerdfolio/remult-better-auth'] = '0.4.3'; }
85
+
95
86
  const scripts = { dev: 'vite', build: 'vite build', preview: 'vite preview', check: 'tsrx-tsc --noEmit', postinstall: 'rm -f node_modules/~ && ln -sf .. node_modules/~' };
96
- if (style === 'pandacss') {
97
- scripts.codegen = 'panda codegen';
98
- scripts.prepare = 'panda codegen';
99
- }
100
- if (cloudflare) {
101
- scripts.types = 'wrangler types --env-interface Env worker-configuration.d.ts';
102
- }
103
- writeFileSync(join(root, 'package.json'), JSON.stringify({
104
- name, private: true, type: 'module',
105
- scripts, dependencies: deps, devDependencies: devDeps
106
- }, null, 2) + '\n');
87
+ if (style === 'pandacss') { scripts.codegen = 'panda codegen'; scripts.prepare = 'panda codegen'; }
88
+ if (cloudflare) scripts.types = 'wrangler types --env-interface Env worker-configuration.d.ts';
89
+
90
+ writeFileSync(join(root, 'package.json'), JSON.stringify({ name, private: true, type: 'module', scripts, dependencies: deps, devDependencies: devDeps }, null, 2) + '\n');
107
91
 
108
- // --- vite.config.ts ---
109
- const viteImports = [
92
+ // ── Dynamic: vite.config.ts ───────────────────────────────
93
+ const imports = [
110
94
  `import { defineConfig } from 'vite'`,
111
95
  `import { fileURLToPath } from 'node:url'`,
112
96
  `import { dirname } from 'node:path'`,
@@ -114,674 +98,116 @@ const viteImports = [
114
98
  `import { ripple } from '@ripple-ts/vite-plugin'`,
115
99
  `import vikeRipple from '@cioky/vike-core'`,
116
100
  ];
117
- const vitePlugins = [
118
- ` vike(),`,
119
- ` vikeRipple(),`,
120
- ` ripple({ excludeRippleExternalModules: true }),`,
121
- ];
122
- if (cloudflare) {
123
- viteImports.unshift(`import { cloudflare } from '@cloudflare/vite-plugin'`);
124
- vitePlugins.unshift(` cloudflare({ viteEnvironment: { name: 'ssr' } }),`);
125
- }
126
- if (style === 'tailwind') {
127
- viteImports.push(
128
- `import vikeRippleTailwindcss from '@cioky/vike-tailwindcss'`,
129
- `import tailwindcss from '@tailwindcss/vite'`
130
- );
131
- vitePlugins.push(` vikeRippleTailwindcss(),`, ` tailwindcss(),`);
132
- }
133
- if (style === 'pandacss') {
134
- viteImports.push(`import vikeRipplePandacss from '@cioky/vike-pandacss'`);
135
- vitePlugins.push(` vikeRipplePandacss(),`);
136
- }
137
- const vitExtras = [];
138
- if (cloudflare) vitExtras.push(` environments: { ssr: { consumer: 'server' } },`);
139
- if (style === 'pandacss') vitExtras.push(` css: { postcss: './postcss.config.js' },`);
140
- writeFileSync(join(root, 'vite.config.ts'), [
141
- ...viteImports, ``,
101
+ const plugins = [` vike(),`, ` vikeRipple(),`, ` ripple({ excludeRippleExternalModules: true }),`];
102
+ const extras = [];
103
+
104
+ if (cloudflare) { imports.unshift(`import { cloudflare } from '@cloudflare/vite-plugin'`); plugins.unshift(` cloudflare({ viteEnvironment: { name: 'ssr' } }),`); }
105
+ if (style === 'tailwind') { imports.push(`import vikeRippleTailwindcss from '@cioky/vike-tailwindcss'`, `import tailwindcss from '@tailwindcss/vite'`); plugins.push(` vikeRippleTailwindcss(),`, ` tailwindcss(),`); }
106
+ if (style === 'pandacss') { imports.push(`import vikeRipplePandacss from '@cioky/vike-pandacss'`); plugins.push(` vikeRipplePandacss(),`); }
107
+ if (cloudflare) extras.push(` environments: { ssr: { consumer: 'server' } },`);
108
+ if (style === 'pandacss') extras.push(` css: { postcss: './postcss.config.js' },`);
109
+
110
+ writeFileSync(join(root, 'vite.config.ts'), [...imports, ``,
142
111
  `const __dirname = dirname(fileURLToPath(import.meta.url))`,
143
112
  `export default defineConfig({`,
144
113
  ` resolve: { alias: { '~': __dirname } },`,
145
- ...vitExtras,
114
+ ...extras,
146
115
  ` optimizeDeps: { exclude: ['ripple'] },`,
147
- ` plugins: [`, ...vitePlugins, ` ],`, `})`, ``
116
+ ` plugins: [`, ...plugins, ` ],`, `})`, ``
148
117
  ].join('\n'));
149
118
 
150
- // --- tsconfig.json ---
151
- const tsPaths = { '~/*': ['./*'] };
152
- if (style === 'pandacss') tsPaths['~styled-system/*'] = ['./styled-system/*'];
119
+ // ── Dynamic: tsconfig.json ────────────────────────────────
120
+ const paths = { '~/*': ['./*'] };
121
+ if (style === 'pandacss') paths['~styled-system/*'] = ['./styled-system/*'];
122
+
153
123
  writeFileSync(join(root, 'tsconfig.json'), JSON.stringify({
154
124
  compilerOptions: {
155
125
  strict: true, module: 'ESNext', moduleResolution: 'bundler',
156
126
  target: 'ESNext', jsx: 'preserve', jsxImportSource: 'ripple',
157
127
  esModuleInterop: true, isolatedModules: true,
158
- experimentalDecorators: true,
159
- verbatimModuleSyntax: true, skipLibCheck: true,
128
+ experimentalDecorators: true, verbatimModuleSyntax: true, skipLibCheck: true,
160
129
  ...(cloudflare ? { types: ['@cloudflare/workers-types'] } : { types: ['vike/client'] }),
161
- paths: tsPaths
130
+ paths,
162
131
  },
163
- include: ['**/*.ts', '**/*.tsx', '**/*.tsrx']
132
+ include: ['**/*.ts', '**/*.tsx', '**/*.tsrx'],
164
133
  }, null, 2) + '\n');
165
134
 
166
- // --- renderer/+config.ts ---
167
- writeFileSync(join(root, 'renderer', '+config.ts'), [
168
- `export default {`,
169
- ` extends: ['import:@cioky/vike-core/config:default'],`,
170
- ` server: true,`,
171
- `}`, ``
172
- ].join('\n'));
173
-
174
- // --- pages/+Layout.tsrx ---
175
- if (style === 'pandacss') {
176
- writeFileSync(join(root, 'pages', '+Layout.tsrx'), [
177
- `import { type TSRXElement } from 'ripple'`,
178
- `import { css } from '~/styled-system/css'`,
179
- `import '~/src/styles.css'`,
180
- ``,
181
- `export function Layout({ children }: { children: TSRXElement }) @{`,
182
- ` <div class={css({ minH: 'screen', bg: 'white', color: 'gray.900' })}>`,
183
- ` <nav class={css({ display: 'flex', gap: '4', borderBottom: '1px', px: '4', py: '3', fontSize: 'sm' })}>`,
184
- ` <a href="/" data-vike-link class={css({ fontWeight: 600, color: 'gray.700', _hover: { color: 'black' } })}>Home</a>`,
185
- ` <a href="/about" data-vike-link class={css({ color: 'gray.500', _hover: { color: 'black' } })}>About</a>`,
186
- ` </nav>`,
187
- ` {children}`,
188
- ` </div>`,
189
- `}`,
190
- ``
191
- ].join('\n'));
192
- } else if (style === 'none') {
193
- writeFileSync(join(root, 'pages', '+Layout.tsrx'), [
194
- `import { type TSRXElement } from 'ripple'`, ``,
195
- `export function Layout({ children }: { children: TSRXElement }) @{`,
196
- ` <div>`, ` {children}`, ` </div>`,
197
- `}`, ``
198
- ].join('\n'));
199
- } else {
200
- writeFileSync(join(root, 'pages', '+Layout.tsrx'), [
201
- `import { type TSRXElement } from 'ripple'`, ``,
202
- `export function Layout({ children }: { children: TSRXElement }) @{`,
203
- ` <div class="min-h-screen bg-white text-gray-900">`,
204
- ` <nav class="flex gap-4 border-b px-4 py-3 text-sm">`,
205
- ` <a href="/" data-vike-link class="font-semibold text-gray-700 hover:text-black">Home</a>`,
206
- ` <a href="/about" data-vike-link class="text-gray-500 hover:text-black">About</a>`,
207
- ` </nav>`, ` {children}`, ` </div>`,
208
- `}`, ``
209
- ].join('\n'));
210
- }
211
-
212
- // --- pages/index/+Page.tsrx ---
213
- if (style === 'pandacss') {
214
- writeFileSync(join(root, 'pages', 'index', '+Page.tsrx'), [
215
- `import { css } from '~/styled-system/css'`, ``,
216
- `export function Page() @{`,
217
- ` <>`,
218
- ` <head><title>Home</title></head>`,
219
- ` <section class={css({ minH: 'screen', display: 'flex', flexDir: 'column', alignItems: 'center', justifyContent: 'center', gap: '4', p: '8' })}>`,
220
- ` <h1 class={css({ fontSize: '4xl', fontWeight: 'bold' })}>Hello, Vike + Ripple!</h1>`,
221
- ` <p class={css({ fontSize: 'lg', color: 'blue.600' })}>With Panda CSS</p>`,
222
- ` </section>`,
223
- ` </>`,
224
- `}`, ``
225
- ].join('\n'));
226
- } else if (style === 'tailwind') {
227
- writeFileSync(join(root, 'pages', 'index', '+Page.tsrx'), [
228
- `import '../../tailwind.css'`, ``,
229
- `export function Page() @{`,
230
- ` <>`,
231
- ` <head><title>Home</title></head>`,
232
- ` <section class="min-h-screen flex flex-col items-center justify-center gap-4 p-8">`,
233
- ` <h1 class="text-4xl font-bold">Hello, Vike + Ripple!</h1>`,
234
- ` <p class="text-lg text-blue-600">With Tailwind CSS v4</p>`,
235
- ` </section>`,
236
- ` </>`,
237
- `}`, ``
238
- ].join('\n'));
239
- } else {
240
- writeFileSync(join(root, 'pages', 'index', '+Page.tsrx'), [
241
- `export function Page() @{`,
242
- ` <>`,
243
- ` <head><title>Home</title></head>`,
244
- ` <section>`, ` <h1>Hello, Vike + Ripple!</h1>`, ` </section>`,
245
- ` </>`,
246
- `}`, ``
247
- ].join('\n'));
248
- }
249
-
250
- // --- pages/about/+Page.tsrx ---
251
- mkdirSync(join(root, 'pages', 'about'), { recursive: true });
252
- if (style === 'pandacss') {
253
- writeFileSync(join(root, 'pages', 'about', '+Page.tsrx'), [
254
- `import { css } from '~/styled-system/css'`, ``,
255
- `export function Page() @{`,
256
- ` <>`,
257
- ` <head><title>About</title></head>`,
258
- ` <section class={css({ mx: 'auto', maxW: '2xl', p: '8' })}>`,
259
- ` <h1 class={css({ fontSize: '3xl', fontWeight: 'bold', mb: '4' })}>About</h1>`,
260
- ` <p class={css({ color: 'gray.600' })}>This scaffold was created by @cioky/vike-create.</p>`,
261
- ` <p class={css({ color: 'gray.600' })}>Scaffolded with Panda CSS + Ripple TS plugin.</p>`,
262
- ` </section>`,
263
- ` </>`,
264
- `}`, ``
265
- ].join('\n'));
266
- } else if (style !== 'none') {
267
- writeFileSync(join(root, 'pages', 'about', '+Page.tsrx'), [
268
- `export function Page() @{`,
269
- ` <>`,
270
- ` <head><title>About</title></head>`,
271
- ` <section class="mx-auto max-w-2xl p-8">`,
272
- ` <h1 class="text-3xl font-bold mb-4">About</h1>`,
273
- ` <p class="text-gray-600">This scaffold was created by @cioky/vike-create.</p>`,
274
- ` </section>`,
275
- ` </>`,
276
- `}`, ``
277
- ].join('\n'));
278
- } else {
279
- writeFileSync(join(root, 'pages', 'about', '+Page.tsrx'), [
280
- `export function Page() @{`,
281
- ` <>`,
282
- ` <head><title>About</title></head>`,
283
- ` <section>`, ` <h1>About</h1>`,
284
- ` <p>This scaffold was created by @cioky/vike-create.</p>`,
285
- ` </>`,
286
- `}`, ``
287
- ].join('\n'));
288
- }
289
-
290
- // --- Better Auth pages ---
291
- if (betterauth) {
292
- mkdirSync(join(root, 'pages', 'login'), { recursive: true });
293
- mkdirSync(join(root, 'pages', 'register'), { recursive: true });
294
- mkdirSync(join(root, 'pages', 'dashboard'), { recursive: true });
295
- writeFileSync(join(root, 'pages', 'login', '+Page.tsrx'), [
296
- `import { css } from '~/styled-system/css'`,
297
- `import { track } from 'ripple'`,
298
- `import { createAuthClient } from 'better-auth/client'`,
299
- ``,
300
- `const authClient = createAuthClient({ baseURL: typeof window !== 'undefined' ? window.location.origin : '' })`,
301
- ``,
302
- `export function Page() @{`,
303
- ` let &[email] = track('')`,
304
- ` let &[password] = track('')`,
305
- ` let &[errorMsg] = track('')`,
306
- ` let &[loading] = track(false)`,
307
- ``,
308
- ` function handleSubmit(e: Event) {`,
309
- ` e.preventDefault()`,
310
- ` loading = true`,
311
- ` errorMsg = ''`,
312
- ` authClient.signIn.email({ email, password }).then(function(res: unknown) {`,
313
- ` const r = res as { error?: { message?: string; status?: string } }`,
314
- ` if (r.error) {`,
315
- ` errorMsg = r.error.message || r.error.status || 'Sign in failed'`,
316
- ` } else {`,
317
- ` window.location.href = '/dashboard'`,
318
- ` }`,
319
- ` loading = false`,
320
- ` }).catch(function(err: Error) {`,
321
- ` errorMsg = err.message`,
322
- ` loading = false`,
323
- ` })`,
324
- ` }`,
325
- ``,
326
- ` <>`,
327
- ` <head><title>Sign In</title></head>`,
328
- ` <section class={css({ maxW: 'md', mx: 'auto', mt: '20', p: '8' })}>`,
329
- ` <form onSubmit={handleSubmit} class={css({ display: 'flex', flexDir: 'column', gap: '4' })}>`,
330
- ` <input type="email" placeholder="Email" value={email} onInput={(e) => { if (e.target) email = (e.target as HTMLInputElement).value }} required class={css({ p: '3', border: '1px', borderRadius: 'md', borderColor: 'gray.300', w: 'full' })} />`,
331
- ` <input type="password" placeholder="Password" value={password} onInput={(e) => { if (e.target) password = (e.target as HTMLInputElement).value }} required class={css({ p: '3', border: '1px', borderRadius: 'md', borderColor: 'gray.300', w: 'full' })} />`,
332
- ` @if (errorMsg) {`,
333
- ` <p class={css({ color: 'red.500', fontSize: 'sm' })}>{errorMsg}</p>`,
334
- ` }`,
335
- ` <button type="submit" disabled={loading} class={css({ p: '3', bg: 'blue.600', color: 'white', borderRadius: 'md', fontWeight: 'bold', cursor: 'pointer', _hover: { bg: 'blue.700' }, _disabled: { opacity: 0.5 } })}>`,
336
- ` {loading ? 'Signing in...' : 'Sign In'}`,
337
- ` </button>`,
338
- ` <p class={css({ textAlign: 'center', fontSize: 'sm', color: 'gray.600' })}>`,
339
- ` Don't have an account? <a href="/register" class={css({ color: 'blue.600', textDecoration: 'underline' })}>Register</a>`,
340
- ` </p>`,
341
- ` </form>`,
342
- ` </section>`,
343
- ` </>`,
344
- `}`,
345
- ``
346
- ].join('\n'));
347
- writeFileSync(join(root, 'pages', 'register', '+Page.tsrx'), [
348
- `import { css } from '~/styled-system/css'`,
349
- `import { track } from 'ripple'`,
350
- `import { createAuthClient } from 'better-auth/client'`,
351
- ``,
352
- `const authClient = createAuthClient({ baseURL: typeof window !== 'undefined' ? window.location.origin : '' })`,
353
- ``,
354
- `export function Page() @{`,
355
- ` let &[name] = track('')`,
356
- ` let &[email] = track('')`,
357
- ` let &[password] = track('')`,
358
- ` let &[errorMsg] = track('')`,
359
- ` let &[loading] = track(false)`,
360
- ``,
361
- ` function handleSubmit(e: Event) {`,
362
- ` e.preventDefault()`,
363
- ` loading = true`,
364
- ` errorMsg = ''`,
365
- ` authClient.signUp.email({ name, email, password }).then(function(res: unknown) {`,
366
- ` const r = res as { error?: { message?: string; status?: string } }`,
367
- ` if (r.error) {`,
368
- ` errorMsg = r.error.message || r.error.status || 'Registration failed'`,
369
- ` } else {`,
370
- ` window.location.href = '/dashboard'`,
371
- ` }`,
372
- ` loading = false`,
373
- ` }).catch(function(err: Error) {`,
374
- ` errorMsg = err.message`,
375
- ` loading = false`,
376
- ` })`,
377
- ` }`,
378
- ``,
379
- ` <>`,
380
- ` <head><title>Register</title></head>`,
381
- ` <section class={css({ maxW: 'md', mx: 'auto', mt: '20', p: '8' })}>`,
382
- ` <form onSubmit={handleSubmit} class={css({ display: 'flex', flexDir: 'column', gap: '4' })}>`,
383
- ` <input type="text" placeholder="Name" value={name} onInput={(e) => { if (e.target) name = (e.target as HTMLInputElement).value }} required class={css({ p: '3', border: '1px', borderRadius: 'md', borderColor: 'gray.300', w: 'full' })} />`,
384
- ` <input type="email" placeholder="Email" value={email} onInput={(e) => { if (e.target) email = (e.target as HTMLInputElement).value }} required class={css({ p: '3', border: '1px', borderRadius: 'md', borderColor: 'gray.300', w: 'full' })} />`,
385
- ` <input type="password" placeholder="Password" value={password} onInput={(e) => { if (e.target) password = (e.target as HTMLInputElement).value }} required class={css({ p: '3', border: '1px', borderRadius: 'md', borderColor: 'gray.300', w: 'full' })} />`,
386
- ` @if (errorMsg) {`,
387
- ` <p class={css({ color: 'red.500', fontSize: 'sm' })}>{errorMsg}</p>`,
388
- ` }`,
389
- ` <button type="submit" disabled={loading} class={css({ p: '3', bg: 'blue.600', color: 'white', borderRadius: 'md', fontWeight: 'bold', cursor: 'pointer', _hover: { bg: 'blue.700' }, _disabled: { opacity: 0.5 } })}>`,
390
- ` {loading ? 'Creating account...' : 'Register'}`,
391
- ` </button>`,
392
- ` <p class={css({ textAlign: 'center', fontSize: 'sm', color: 'gray.600' })}>`,
393
- ` Already have an account? <a href="/login" class={css({ color: 'blue.600', textDecoration: 'underline' })}>Sign in</a>`,
394
- ` </p>`,
395
- ` </form>`,
396
- ` </section>`,
397
- ` </>`,
398
- `}`,
399
- ``
400
- ].join('\n'));
401
- writeFileSync(join(root, 'pages', 'dashboard', '+data.server.ts'), [
402
- `import type { PageContextServer } from 'vike/types'`,
403
- ``,
404
- `export type Data = {`,
405
- ` user: { name?: string; email?: string; id?: string } | null`,
406
- `}`,
407
- ``,
408
- `export default async function data(pageContext: PageContextServer): Promise<Data> {`,
409
- ` return { user: (pageContext as unknown as Record<string, unknown>).user as Data["user"] ?? null }`,
410
- `}`,
411
- ``
412
- ].join('\n'));
413
- writeFileSync(join(root, 'pages', 'dashboard', '+Page.tsrx'), [
414
- `import { css } from '~/styled-system/css'`,
415
- `import type { Data } from './+data.server'`,
416
- ``,
417
- `export function Page(data: Data) @{`,
418
- ` const { user } = data`,
419
- ``,
420
- ` <>`,
421
- ` <head><title>Dashboard</title></head>`,
422
- ` <section class={css({ maxW: '2xl', mx: 'auto', mt: '20', p: '8' })}>`,
423
- ` @if (!user) {`,
424
- ` <div class={css({ textAlign: 'center', py: '10' })}>`,
425
- ` <h1 class={css({ fontSize: '2xl', fontWeight: 'bold', mb: '4' })}>Not Authenticated</h1>`,
426
- ` <p class={css({ color: 'gray.600', mb: '6' })}>Please sign in to view this page.</p>`,
427
- ` <a href="/login" class={css({ display: 'inline-block', p: '3', bg: 'blue.600', color: 'white', borderRadius: 'md', textDecoration: 'none' })}>Sign In</a>`,
428
- ` </div>`,
429
- ` } @else {`,
430
- ` <div>`,
431
- ` <h1 class={css({ fontSize: '2xl', fontWeight: 'bold', mb: '6' })}>Dashboard</h1>`,
432
- ` <div class={css({ bg: 'gray.50', p: '6', borderRadius: 'lg', border: '1px', borderColor: 'gray.200' })}>`,
433
- ` <p class={css({ mb: '2' })}><strong>Welcome, {user.name || user.email}!</strong></p>`,
434
- ` <p class={css({ color: 'gray.600', fontSize: 'sm' })}>Email: {user.email}</p>`,
435
- ` <p class={css({ color: 'gray.600', fontSize: 'sm' })}>User ID: {user.id}</p>`,
436
- ` </div>`,
437
- ` <div class={css({ mt: '6', display: 'flex', gap: '4' })}>`,
438
- ` <a href="/" class={css({ color: 'blue.600' })}>Home</a>`,
439
- ` <a href="/about" class={css({ color: 'blue.600' })}>About</a>`,
440
- ` </div>`,
441
- ` </div>`,
442
- ` }`,
443
- ` </section>`,
444
- ` </>`,
445
- `}`,
446
- ``
447
- ].join('\n'));
448
- }
449
-
450
- // --- style-specific files ---
451
- if (style === 'tailwind') {
452
- writeFileSync(join(root, 'tailwind.css'), [`@import "tailwindcss";`, ``].join('\n'));
453
- }
454
- if (style === 'pandacss') {
455
- writeFileSync(join(root, 'panda.config.ts'), [
456
- `import { defineConfig } from '@pandacss/dev'`,
457
- `import { pluginRipple } from '@cioky/vike-pandacss/panda-plugin'`,
458
- `export default defineConfig({`,
459
- ` preflight: true,`,
460
- ` include: ['./pages/**/*.{tsrx,tsx}', './renderer/**/*.{ts,tsx}'],`,
461
- ` exclude: [],`,
462
- ` plugins: [pluginRipple()],`,
463
- ` theme: { extend: {} },`,
464
- ` outdir: 'styled-system',`,
465
- `})`, ``
466
- ].join('\n'));
467
- writeFileSync(join(root, 'postcss.config.js'), [
468
- `export default { plugins: { '@pandacss/dev/postcss': {} } }`, ``
469
- ].join('\n'));
470
- writeFileSync(join(root, 'src', 'styles.css'), [
471
- `@layer reset, base, tokens, recipes, utilities;`, ``
472
- ].join('\n'));
473
- }
474
-
475
- // --- CF basic ---
476
- if (cloudflare && !(remult && cloudflare)) {
477
- mkdirSync(join(root, '.wrangler'), { recursive: true });
478
- writeFileSync(join(root, 'wrangler.jsonc'), JSON.stringify({
135
+ // ── Dynamic: wrangler.jsonc (cloudflare) ──────────────────
136
+ if (cloudflare) {
137
+ const wrangler = {
479
138
  $schema: 'node_modules/wrangler/config-schema.json',
480
- name, main: 'vike:server-entry',
139
+ name,
140
+ ...(remult ? { main: '+server.ts' } : { main: 'vike:server-entry' }),
481
141
  compatibility_date: '2026-06-01',
482
- compatibility_flags: ['nodejs_compat']
483
- }, null, 2) + '\n');
484
- writeFileSync(join(root, '.gitignore'), `node_modules/\ndist/\n.wrangler/\n*.log\n.env\n`);
485
- }
486
-
487
- // --- Remult + CF ---
488
- if (remult && cloudflare) {
489
- mkdirSync(join(root, 'server'), { recursive: true });
490
- mkdirSync(join(root, 'lib'), { recursive: true });
491
- mkdirSync(join(root, '.wrangler'), { recursive: true });
492
- writeFileSync(join(root, 'wrangler.jsonc'), JSON.stringify({
493
- $schema: 'node_modules/wrangler/config-schema.json', name, main: '+server.ts',
494
- compatibility_date: '2026-06-01', compatibility_flags: ['nodejs_compat'],
495
- d1_databases: [{ binding: 'DB', database_name: name, database_id: 'your-database-id-here' }],
496
- durable_objects: {
142
+ compatibility_flags: ['nodejs_compat'],
143
+ };
144
+ if (remult) {
145
+ wrangler.d1_databases = [{ binding: 'DB', database_name: name, database_id: 'your-database-id-here' }];
146
+ wrangler.durable_objects = {
497
147
  bindings: [
498
148
  { name: 'REMULT_ROOM', class_name: 'RemultPubSubRoom' },
499
- { name: 'REMULT_LIVE_QUERY_STORAGE', class_name: 'RemultLiveQueryStorageRoom' }
500
- ]
501
- },
502
- migrations: [{ tag: 'v1', new_sqlite_classes: ['RemultPubSubRoom', 'RemultLiveQueryStorageRoom'] }],
503
- vars: {
149
+ { name: 'REMULT_LIVE_QUERY_STORAGE', class_name: 'RemultLiveQueryStorageRoom' },
150
+ ],
151
+ };
152
+ wrangler.migrations = [{ tag: 'v1', new_sqlite_classes: ['RemultPubSubRoom', 'RemultLiveQueryStorageRoom'] }];
153
+ wrangler.vars = {
504
154
  BETTER_AUTH_URL: 'http://localhost:3000',
505
155
  BETTER_AUTH_SECRET: 'dev-secret-change-in-production!!',
506
156
  MAX_CONNECTIONS_PER_SHARD: '100',
507
- REALTIME_LIVE_QUERY_ROOM_MODE: 'global'
508
- }
509
- }, null, 2) + '\n');
510
- let honoBody = [
157
+ REALTIME_LIVE_QUERY_ROOM_MODE: 'global',
158
+ };
159
+ }
160
+ writeFileSync(join(root, 'wrangler.jsonc'), JSON.stringify(wrangler, null, 2) + '\n');
161
+ writeFileSync(join(root, '.gitignore'), `node_modules/\ndist/\n.wrangler/\n*.log\n.env\n`);
162
+ }
163
+
164
+ // ── Dynamic: server/hono.ts (with betterauth overrides) ───
165
+ if (betterauth && remult && cloudflare) {
166
+ const hono = [
167
+ `import { Hono } from 'hono'`,
168
+ `import { remultApi } from 'remult/remult-hono'`,
169
+ `import { D1DataProvider, D1BindingClient } from 'remult/remult-d1'`,
170
+ `import { SqlDatabase } from 'remult'`,
171
+ `import vike from '@vikejs/hono'`,
172
+ `import { getAuth } from './better-auth'`,
511
173
  ``,
512
174
  `const app = new Hono<{ Variables: { user: unknown; session: unknown } }>()`,
513
175
  `let db: D1Database`,
514
176
  ``,
515
- `// Universal middleware: exposes auth instance + session to Vike pageContext`,
516
- `const authMiddleware = async (`,
517
- ` _request: Request,`,
518
- ` context: Record<string, unknown>,`,
519
- ` runtime: { hono?: { env: Cloudflare.Env } }`,
520
- `) => {`,
521
- ` const env = runtime.hono?.env`,
522
- ` if (!env) return`,
177
+ `app.use('/api/*', async (c, next) => { db = (c.env as Cloudflare.Env).DB; await next() })`,
178
+ `app.use('/api/auth/*', async (c) => {`,
179
+ ` const env = c.env as Cloudflare.Env`,
523
180
  ` const auth = await getAuth(env.DB, env.BETTER_AUTH_SECRET, env.BETTER_AUTH_URL)`,
524
- ` if (!auth) return`,
525
- ` context.auth = auth`,
526
- ` const session = await auth.api.getSession({ headers: _request.headers }).catch(() => null)`,
527
- ` if (session) {`,
528
- ` context.user = session.user`,
529
- ` context.session = session.session`,
530
- ` }`,
531
- `}`,
532
- ``,
533
- `app.use('/api/*', async (c, next) => {`,
534
- ` db = (c.env as Cloudflare.Env).DB`,
535
- ` await next()`,
181
+ ` if (!auth) return c.text('Auth not available', 500)`,
182
+ ` return auth.handler(c.req.raw)`,
536
183
  `})`,
537
- ]
538
- if (betterauth) {
539
- honoBody.push(
540
- `app.use('/api/auth/*', async (c) => {`,
541
- ` const env = c.env as Cloudflare.Env`,
542
- ` const auth = await getAuth(env.DB, env.BETTER_AUTH_SECRET, env.BETTER_AUTH_URL)`,
543
- ` if (!auth) return c.text('Auth not available', 500)`,
544
- ` return auth.handler(c.req.raw)`,
545
- `})`,
546
- )
547
- }
548
- honoBody.push(
549
184
  `app.route('/api', remultApi({`,
550
185
  ` dataProvider: async () => new SqlDatabase(new D1DataProvider(new D1BindingClient(db))),`,
551
186
  ` entities: [],`,
552
187
  ` getUser: async () => undefined,`,
553
188
  `}))`,
554
- )
555
- honoBody.push(
556
189
  `app.use('/party/*', async (c) => {`,
557
190
  ` const env = c.env as Cloudflare.Env`,
558
191
  ` const ns = env.REMULT_ROOM`,
559
192
  ` return ns.get(ns.idFromName('global')).fetch(c.req.raw)`,
560
193
  `})`,
561
- `vike(app, [authMiddleware])`,
562
- `export { app }`, ``
563
- )
564
- writeFileSync(join(root, '+server.ts'), [
565
- `import { RemultLiveQueryStorageRoom, RemultPartyRoom, resolveRoomIdFromChannel } from 'remult-partykit/durable-object'`,
566
- `import { app } from './server/hono'`, ``,
567
- `class PubSubRoom extends RemultPartyRoom<Cloudflare.Env> {`,
568
- ` static options = { hibernate: false }`,
569
- ` override options = { resolveRoomId: resolveRoomIdFromChannel };`,
570
- ` override async onError(_connection: import('partyserver').Connection, error: unknown) {`,
571
- ` console.error('PubSubRoom error:', error)`,
572
- ` }`,
573
- `}`, ``,
574
- `export default { fetch: app.fetch }`,
575
- `export { RemultLiveQueryStorageRoom, PubSubRoom as RemultPubSubRoom }`, ``
576
- ].join('\n'));
577
- const honoImports = [
578
- `import { Hono } from 'hono'`,
579
- `import { D1DataProvider, D1BindingClient } from 'remult/remult-d1'`,
580
- `import { SqlDatabase } from 'remult'`,
581
- `import { remultApi } from 'remult/remult-hono'`,
582
- `import vike from '@vikejs/hono'`,
583
- ]
584
- if (betterauth) {
585
- honoImports.push(
586
- `import { getAuth } from './better-auth'`,
587
- )
588
- }
589
- writeFileSync(join(root, 'server', 'hono.ts'), [...honoImports, ...honoBody].join('\n'))
590
- writeFileSync(join(root, 'lib', 'remult-client.ts'), [
591
- `import { RemultPartySubscriptionClient } from 'remult-partykit'`,
592
- `import { remult } from 'remult'`,
593
- `export function initRemultRealtime(host: string) {`,
594
- ` const client = new RemultPartySubscriptionClient({`,
595
- ` getSocketUrl: (roomName: string) => {`,
596
- ` const wsHost = host.replace(/^http/, 'ws')`,
597
- ` return \`\${wsHost}/party/remult?room=\${roomName}\``,
598
- ` },`,
599
- ` })`,
600
- ` remult.apiClient.subscriptionClient = client`,
601
- `}`, ``
602
- ].join('\n'));
603
- writeFileSync(join(root, '.gitignore'), `node_modules/\ndist/\n.wrangler/\n*.log\n.env\n`);
194
+ `vike(app, [])`,
195
+ `export { app }`,
196
+ ``,
197
+ ];
198
+ writeFileSync(join(root, 'server', 'hono.ts'), hono.join('\n'));
604
199
  }
605
200
 
606
- // --- Remult only ---
607
- if (remult && !cloudflare) {
608
- mkdirSync(join(root, 'server'), { recursive: true });
609
- writeFileSync(join(root, 'server', 'remult.ts'), [
201
+ // ── Dynamic: remult-only server ───────────────────────────
202
+ if (remult && !cloudflare && !existsSync(join(root, 'server', 'remult.ts'))) {
203
+ const remultTpl = [
610
204
  `import { remult } from 'remult'`,
611
- `export const api = remult({ entities: [], getUser: async () => undefined })`, ``
612
- ].join('\n'));
613
- }
614
- if (betterauth) {
615
- mkdirSync(join(root, 'entities'), { recursive: true });
616
- writeFileSync(join(root, 'entities', 'auth.ts'), [
617
- `import { Allow, Entity, Fields, Relations, Validators } from 'remult'`,
618
- ``,
619
- `const Roles = { admin: 'admin' }`,
620
- ``,
621
- `@Entity('user', { allowApiCrud: Roles.admin, allowApiRead: Allow.authenticated })`,
622
- `export class User {`,
623
- ` @Fields.string({ required: true, minLength: 8, maxLength: 40, validate: Validators.unique(), allowApiUpdate: false })`,
624
- ` id!: string`,
625
- ` @Fields.string({ required: true })`,
626
- ` name = ''`,
627
- ` @Fields.string({})`,
628
- ` email = ''`,
629
- ` @Fields.boolean({})`,
630
- ` emailVerified = false`,
631
- ` @Fields.string({ required: false })`,
632
- ` image = ''`,
633
- ` @Fields.createdAt({})`,
634
- ` createdAt!: Date`,
635
- ` @Fields.updatedAt({})`,
636
- ` updatedAt!: Date`,
637
- `}`,
638
- ``,
639
- `@Entity('session', { allowApiCrud: Roles.admin })`,
640
- `export class Session {`,
641
- ` @Fields.string({ required: true, minLength: 8, maxLength: 40, validate: Validators.unique(), allowApiUpdate: false })`,
642
- ` id!: string`,
643
- ` @Fields.date({ required: true })`,
644
- ` expiresAt = new Date()`,
645
- ` @Fields.string({})`,
646
- ` token = ''`,
647
- ` @Fields.createdAt({})`,
648
- ` createdAt!: Date`,
649
- ` @Fields.updatedAt({ required: true, allowApiUpdate: false })`,
650
- ` updatedAt!: Date`,
651
- ` @Fields.string({ required: false })`,
652
- ` ipAddress = ''`,
653
- ` @Fields.string({ required: false })`,
654
- ` userAgent = ''`,
655
- ` @Fields.string({ required: true })`,
656
- ` userId = ''`,
657
- ` @Relations.toOne(() => User, 'id')`,
658
- ` user!: User`,
659
- `}`,
660
- ``,
661
- `@Entity('account', { allowApiCrud: Roles.admin })`,
662
- `export class Account {`,
663
- ` @Fields.string({ required: true, minLength: 8, maxLength: 40, validate: Validators.unique(), allowApiUpdate: false })`,
664
- ` id!: string`,
665
- ` @Fields.string({ required: true, allowApiUpdate: false })`,
666
- ` accountId = ''`,
667
- ` @Fields.string({ required: true, allowApiUpdate: false })`,
668
- ` providerId = ''`,
669
- ` @Fields.string({ required: true })`,
670
- ` userId = ''`,
671
- ` @Relations.toOne(() => User, 'id')`,
672
- ` user!: User`,
673
- ` @Fields.string({ required: false, allowApiUpdate: false })`,
674
- ` accessToken = ''`,
675
- ` @Fields.string({ required: false, allowApiUpdate: false })`,
676
- ` refreshToken = ''`,
677
- ` @Fields.string({ required: false })`,
678
- ` idToken = ''`,
679
- ` @Fields.date({ required: false })`,
680
- ` accessTokenExpiresAt = new Date()`,
681
- ` @Fields.date({ required: false })`,
682
- ` refreshTokenExpiresAt = new Date()`,
683
- ` @Fields.string({ required: false })`,
684
- ` scope = ''`,
685
- ` @Fields.string({ required: false, allowApiUpdate: false })`,
686
- ` password = ''`,
687
- ` @Fields.createdAt({ required: true, defaultValue: () => new Date(), allowApiUpdate: false })`,
688
- ` createdAt!: Date`,
689
- ` @Fields.updatedAt({ required: true, allowApiUpdate: false })`,
690
- ` updatedAt!: Date`,
691
- `}`,
692
- ``,
693
- `@Entity('verification', { allowApiCrud: Roles.admin })`,
694
- `export class Verification {`,
695
- ` @Fields.string({ required: true, minLength: 8, maxLength: 40, validate: Validators.unique(), allowApiUpdate: false })`,
696
- ` id!: string`,
697
- ` @Fields.string({ required: true })`,
698
- ` identifier = ''`,
699
- ` @Fields.string({ required: true })`,
700
- ` value = ''`,
701
- ` @Fields.date({ required: true })`,
702
- ` expiresAt = new Date()`,
703
- ` @Fields.createdAt({ required: true, defaultValue: () => new Date(), allowApiUpdate: false })`,
704
- ` createdAt!: Date`,
705
- ` @Fields.updatedAt({ required: true, defaultValue: () => new Date(), allowApiUpdate: false })`,
706
- ` updatedAt!: Date`,
707
- `}`,
708
- ``
709
- ].join('\n'));
710
- mkdirSync(join(root, 'server'), { recursive: true });
711
- writeFileSync(join(root, 'server', 'better-auth.ts'), [
712
- `import { betterAuth } from 'better-auth'`,
713
- `import { remultAdapter } from '@nerdfolio/remult-better-auth'`,
714
- `import { User, Session, Account, Verification } from '../entities/auth'`,
715
- `import type { BetterAuthOptions } from 'better-auth'`,
716
- `import type { ClassType } from 'remult'`,
717
- `import { SqlDatabase, withRemult } from 'remult'`,
718
- `import { D1BindingClient, D1DataProvider } from 'remult/remult-d1'`,
719
- ``,
720
- `export function getAuthConfig(db: D1Database, secret: string, url?: string): BetterAuthOptions {`,
721
- ` const dataProvider = new SqlDatabase(new D1DataProvider(new D1BindingClient(db)))`,
722
- ``,
723
- ` withRemult(`,
724
- ` async (remult) => {`,
725
- ` const entities = [User, Session, Account, Verification] as ClassType<unknown>[]`,
726
- ` const metadata = entities.map((e) => remult.repo(e).metadata)`,
727
- ` await dataProvider.ensureSchema(metadata)`,
728
- ` },`,
729
- ` { dataProvider }`,
730
- ` ).catch((e: unknown) => console.error('Schema init failed:', e))`,
731
- ``,
732
- ` return {`,
733
- ` secret,`,
734
- ` baseURL: url,`,
735
- ` database: remultAdapter({`,
736
- ` authEntities: { User, Session, Account, Verification },`,
737
- ` dataProvider`,
738
- ` }),`,
739
- ` emailAndPassword: { enabled: true }`,
740
- ` }`,
741
- `}`,
742
- ``,
743
- `let _auth: ReturnType<typeof betterAuth> | null = null`,
744
- `let _schemaInit: Promise<void> | null = null`,
745
- ``,
746
- `async function ensureSchema(db: D1Database) {`,
747
- ` if (!_schemaInit) {`,
748
- ` _schemaInit = (async () => {`,
749
- ` const dp = new SqlDatabase(new D1DataProvider(new D1BindingClient(db)))`,
750
- ` await withRemult(`,
751
- ` async (remult) => {`,
752
- ` const entities = [User, Session, Account, Verification] as ClassType<unknown>[]`,
753
- ` const metadata = entities.map((e) => remult.repo(e).metadata)`,
754
- ` await dp.ensureSchema(metadata)`,
755
- ` },`,
756
- ` { dataProvider: dp }`,
757
- ` )`,
758
- ` })()`,
759
- ` }`,
760
- ` return _schemaInit`,
761
- `}`,
762
- ``,
763
- `export async function getAuth(db: D1Database, secret: string, url?: string) {`,
764
- ` if (!_auth) {`,
765
- ` await ensureSchema(db)`,
766
- ` const dp = new SqlDatabase(new D1DataProvider(new D1BindingClient(db)))`,
767
- ` _auth = betterAuth<BetterAuthOptions>({`,
768
- ` secret,`,
769
- ` baseURL: url,`,
770
- ` database: remultAdapter({`,
771
- ` authEntities: { User, Session, Account, Verification },`,
772
- ` dataProvider: dp`,
773
- ` }),`,
774
- ` emailAndPassword: { enabled: true }`,
775
- ` })`,
776
- ` }`,
777
- ` return _auth`,
778
- `}`,
779
- ``,
780
- ].join('\n'));
781
- ``
205
+ `export const api = remult({ entities: [], getUser: async () => undefined })`, ``,
206
+ ];
207
+ writeFileSync(join(root, 'server', 'remult.ts'), remultTpl.join('\n'));
782
208
  }
783
209
 
784
- // --- install ---
210
+ // ── Install + setup ───────────────────────────────────────
785
211
  let label = `style: ${style}`;
786
212
  if (cloudflare) label += ', CF Workers';
787
213
  if (remult) label += ', Remult';
@@ -799,8 +225,6 @@ if (style === 'pandacss') {
799
225
  console.log(`\n Running @cioky/vike-pandacss setup...`);
800
226
  execSync('npx --yes @cioky/vike-pandacss setup', { cwd: root, stdio: 'inherit' });
801
227
  }
802
-
803
-
804
228
  if (cloudflare) {
805
229
  console.log(`\n Generating worker types...`);
806
230
  execSync('npm run types', { cwd: root, stdio: 'inherit' });