@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.
- package/package.json +1 -1
- package/src/index.js +126 -702
package/package.json
CHANGED
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
|
-
|
|
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
|
|
17
|
-
--cloudflare Add Cloudflare Workers
|
|
18
|
-
--remult Add Remult ORM
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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 (
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
//
|
|
109
|
-
const
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
...
|
|
114
|
+
...extras,
|
|
146
115
|
` optimizeDeps: { exclude: ['ripple'] },`,
|
|
147
|
-
` plugins: [`, ...
|
|
116
|
+
` plugins: [`, ...plugins, ` ],`, `})`, ``
|
|
148
117
|
].join('\n'));
|
|
149
118
|
|
|
150
|
-
//
|
|
151
|
-
const
|
|
152
|
-
if (style === 'pandacss')
|
|
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
|
|
130
|
+
paths,
|
|
162
131
|
},
|
|
163
|
-
include: ['**/*.ts', '**/*.tsx', '**/*.tsrx']
|
|
132
|
+
include: ['**/*.ts', '**/*.tsx', '**/*.tsrx'],
|
|
164
133
|
}, null, 2) + '\n');
|
|
165
134
|
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
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,
|
|
139
|
+
name,
|
|
140
|
+
...(remult ? { main: '+server.ts' } : { main: 'vike:server-entry' }),
|
|
481
141
|
compatibility_date: '2026-06-01',
|
|
482
|
-
compatibility_flags: ['nodejs_compat']
|
|
483
|
-
}
|
|
484
|
-
|
|
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
|
|
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
|
-
}
|
|
510
|
-
|
|
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
|
-
|
|
516
|
-
`
|
|
517
|
-
`
|
|
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
|
-
`
|
|
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, [
|
|
562
|
-
`export { app }`,
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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
|
-
//
|
|
607
|
-
if (remult && !cloudflare) {
|
|
608
|
-
|
|
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
|
-
]
|
|
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
|
-
//
|
|
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' });
|