create-moria 0.3.0 → 0.3.1
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/.turbo/turbo-build.log +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +370 -66
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +425 -83
package/.turbo/turbo-build.log
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* create-moria
|
|
3
3
|
*
|
|
4
4
|
* Interactive project scaffolder for MoriaJS.
|
|
5
|
-
*
|
|
5
|
+
* Generates a complete, runnable project with two template options:
|
|
6
|
+
*
|
|
7
|
+
* default — Full-featured: SSR page, API route, middleware, client hydration
|
|
8
|
+
* minimal — Bare API server: single API route, no SSR/UI
|
|
6
9
|
*
|
|
7
10
|
* Usage:
|
|
8
11
|
* npx create-moria my-app
|
|
12
|
+
* npx create-moria my-app --template minimal
|
|
9
13
|
* npx create-moria my-app --typescript
|
|
10
|
-
* npx create-moria my-app --javascript
|
|
11
14
|
*/
|
|
12
15
|
export declare const cli: import("cac").CAC;
|
|
13
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAUH,eAAO,MAAM,GAAG,mBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,25 +2,293 @@
|
|
|
2
2
|
* create-moria
|
|
3
3
|
*
|
|
4
4
|
* Interactive project scaffolder for MoriaJS.
|
|
5
|
-
*
|
|
5
|
+
* Generates a complete, runnable project with two template options:
|
|
6
|
+
*
|
|
7
|
+
* default — Full-featured: SSR page, API route, middleware, client hydration
|
|
8
|
+
* minimal — Bare API server: single API route, no SSR/UI
|
|
6
9
|
*
|
|
7
10
|
* Usage:
|
|
8
11
|
* npx create-moria my-app
|
|
12
|
+
* npx create-moria my-app --template minimal
|
|
9
13
|
* npx create-moria my-app --typescript
|
|
10
|
-
* npx create-moria my-app --javascript
|
|
11
14
|
*/
|
|
12
15
|
import { cac } from 'cac';
|
|
13
16
|
import pc from 'picocolors';
|
|
14
17
|
import prompts from 'prompts';
|
|
15
18
|
import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
16
19
|
import { join, resolve } from 'node:path';
|
|
17
|
-
const VERSION = '0.
|
|
20
|
+
const VERSION = '0.3.0';
|
|
18
21
|
export const cli = cac('create-moria');
|
|
22
|
+
// ─── Template file generators ─────────────────────────
|
|
23
|
+
function tsconfigContent() {
|
|
24
|
+
return JSON.stringify({
|
|
25
|
+
compilerOptions: {
|
|
26
|
+
target: 'ES2022',
|
|
27
|
+
module: 'ESNext',
|
|
28
|
+
moduleResolution: 'bundler',
|
|
29
|
+
esModuleInterop: true,
|
|
30
|
+
strict: true,
|
|
31
|
+
skipLibCheck: true,
|
|
32
|
+
outDir: 'dist',
|
|
33
|
+
rootDir: 'src',
|
|
34
|
+
declaration: true,
|
|
35
|
+
declarationMap: true,
|
|
36
|
+
sourceMap: true,
|
|
37
|
+
},
|
|
38
|
+
include: ['src'],
|
|
39
|
+
exclude: ['node_modules', 'dist'],
|
|
40
|
+
}, null, 4);
|
|
41
|
+
}
|
|
42
|
+
function envContent(db) {
|
|
43
|
+
const lines = [
|
|
44
|
+
'# MoriaJS Environment Variables',
|
|
45
|
+
'',
|
|
46
|
+
'# Server',
|
|
47
|
+
'PORT=3000',
|
|
48
|
+
'',
|
|
49
|
+
'# Database',
|
|
50
|
+
];
|
|
51
|
+
if (db === 'pg') {
|
|
52
|
+
lines.push('DATABASE_URL=postgresql://user:password@localhost:5432/mydb');
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
lines.push('# SQLite uses a local file (configured in moria.config.ts)');
|
|
56
|
+
}
|
|
57
|
+
lines.push('', '# Auth', 'JWT_SECRET=change-me-to-a-random-secret', '');
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
function gitignoreContent() {
|
|
61
|
+
return `node_modules/
|
|
62
|
+
dist/
|
|
63
|
+
.env
|
|
64
|
+
*.db
|
|
65
|
+
.turbo/
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
function moriaConfigTs(db) {
|
|
69
|
+
const dbConfig = db === 'sqlite'
|
|
70
|
+
? ` adapter: 'sqlite',\n filename: './dev.db',`
|
|
71
|
+
: ` adapter: 'pg',\n url: process.env.DATABASE_URL,`;
|
|
72
|
+
return `import { defineConfig } from '@moriajs/core';
|
|
73
|
+
|
|
74
|
+
export default defineConfig({
|
|
75
|
+
server: {
|
|
76
|
+
port: Number(process.env.PORT) || 3000,
|
|
77
|
+
},
|
|
78
|
+
database: {
|
|
79
|
+
${dbConfig}
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
function moriaConfigJs(db) {
|
|
85
|
+
const dbConfig = db === 'sqlite'
|
|
86
|
+
? ` adapter: 'sqlite',\n filename: './dev.db',`
|
|
87
|
+
: ` adapter: 'pg',\n url: process.env.DATABASE_URL,`;
|
|
88
|
+
return `/** @type {import('@moriajs/core').MoriaConfig} */
|
|
89
|
+
export default {
|
|
90
|
+
server: {
|
|
91
|
+
port: Number(process.env.PORT) || 3000,
|
|
92
|
+
},
|
|
93
|
+
database: {
|
|
94
|
+
${dbConfig}
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
// ─── Default template source files ─────────────────────
|
|
100
|
+
function srcIndexTs() {
|
|
101
|
+
return `/**
|
|
102
|
+
* App entry point — starts the MoriaJS server.
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
import { createApp } from '@moriajs/core';
|
|
106
|
+
import path from 'node:path';
|
|
107
|
+
|
|
108
|
+
const __appRoot = path.resolve(import.meta.dirname, '..');
|
|
109
|
+
|
|
110
|
+
async function main() {
|
|
111
|
+
const { default: config } = await import('../moria.config.js');
|
|
112
|
+
|
|
113
|
+
const app = await createApp({
|
|
114
|
+
config: {
|
|
115
|
+
...config,
|
|
116
|
+
mode: 'development',
|
|
117
|
+
rootDir: __appRoot,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const address = await app.listen();
|
|
122
|
+
console.log(\`\\n🏔️ MoriaJS running at \${address}\\n\`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
main().catch((err) => {
|
|
126
|
+
console.error('Failed to start:', err);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
});
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
function srcEntryClientTs() {
|
|
132
|
+
return `/**
|
|
133
|
+
* Client-side entry point.
|
|
134
|
+
* Hydrates the server-rendered page to make it interactive.
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
import { hydrate, getHydrationData } from '@moriajs/renderer';
|
|
138
|
+
import HomePage from './routes/pages/index.js';
|
|
139
|
+
|
|
140
|
+
const data = getHydrationData();
|
|
141
|
+
const root = document.getElementById('app');
|
|
142
|
+
|
|
143
|
+
if (root) {
|
|
144
|
+
hydrate(HomePage, root, data);
|
|
145
|
+
}
|
|
146
|
+
`;
|
|
147
|
+
}
|
|
148
|
+
function srcMiddlewareTs() {
|
|
149
|
+
return `/**
|
|
150
|
+
* Root middleware — runs on every request.
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
import { defineMiddleware } from '@moriajs/core';
|
|
154
|
+
|
|
155
|
+
export default defineMiddleware(async (request) => {
|
|
156
|
+
request.log.info(\`→ \${request.method} \${request.url}\`);
|
|
157
|
+
});
|
|
158
|
+
`;
|
|
159
|
+
}
|
|
160
|
+
function srcApiHelloTs() {
|
|
161
|
+
return `/**
|
|
162
|
+
* GET /api/hello
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
166
|
+
|
|
167
|
+
export function GET(_request: FastifyRequest, _reply: FastifyReply) {
|
|
168
|
+
return {
|
|
169
|
+
message: 'Hello from MoriaJS! 🏔️',
|
|
170
|
+
timestamp: new Date().toISOString(),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
function srcApiHelloJs() {
|
|
176
|
+
return `/**
|
|
177
|
+
* GET /api/hello
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
export function GET(_request, _reply) {
|
|
181
|
+
return {
|
|
182
|
+
message: 'Hello from MoriaJS! 🏔️',
|
|
183
|
+
timestamp: new Date().toISOString(),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
`;
|
|
187
|
+
}
|
|
188
|
+
function srcPageIndexTs() {
|
|
189
|
+
return `/**
|
|
190
|
+
* Home page — server-rendered Mithril.js component.
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
import m from 'mithril';
|
|
194
|
+
|
|
195
|
+
/** Server-side data loader */
|
|
196
|
+
export async function getServerData() {
|
|
197
|
+
return {
|
|
198
|
+
title: 'Welcome to MoriaJS',
|
|
199
|
+
features: [
|
|
200
|
+
'File-based routing',
|
|
201
|
+
'SSR + client hydration',
|
|
202
|
+
'Middleware system',
|
|
203
|
+
'Database with Kysely',
|
|
204
|
+
'JWT authentication',
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Page component */
|
|
210
|
+
export default {
|
|
211
|
+
title: 'MoriaJS App',
|
|
212
|
+
view(vnode: m.Vnode<{ serverData?: { title: string; features: string[] } }>) {
|
|
213
|
+
const data = vnode.attrs?.serverData;
|
|
214
|
+
return m('main', { style: 'max-width:640px;margin:2rem auto;font-family:system-ui;padding:0 1rem' }, [
|
|
215
|
+
m('h1', { style: 'font-size:2.5rem;margin-bottom:0.5rem' }, '🏔️ ' + (data?.title ?? 'MoriaJS')),
|
|
216
|
+
m('p', { style: 'color:#666;font-size:1.1rem' }, 'Your full-stack Mithril.js app is up and running.'),
|
|
217
|
+
m('hr', { style: 'border:none;border-top:1px solid #eee;margin:1.5rem 0' }),
|
|
218
|
+
m('h2', { style: 'font-size:1.3rem' }, 'Features'),
|
|
219
|
+
m('ul', { style: 'line-height:1.8' },
|
|
220
|
+
(data?.features ?? []).map((f: string) => m('li', f))
|
|
221
|
+
),
|
|
222
|
+
m('hr', { style: 'border:none;border-top:1px solid #eee;margin:1.5rem 0' }),
|
|
223
|
+
m('p', { style: 'color:#999;font-size:0.9rem' }, [
|
|
224
|
+
'Edit ',
|
|
225
|
+
m('code', 'src/routes/pages/index.ts'),
|
|
226
|
+
' to get started.',
|
|
227
|
+
]),
|
|
228
|
+
]);
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
function srcIndexJs() {
|
|
234
|
+
return `/**
|
|
235
|
+
* App entry point — starts the MoriaJS server.
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
import { createApp } from '@moriajs/core';
|
|
239
|
+
import path from 'node:path';
|
|
240
|
+
import { fileURLToPath } from 'node:url';
|
|
241
|
+
|
|
242
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
243
|
+
const __appRoot = path.resolve(__dirname, '..');
|
|
244
|
+
|
|
245
|
+
async function main() {
|
|
246
|
+
const { default: config } = await import('../moria.config.js');
|
|
247
|
+
|
|
248
|
+
const app = await createApp({
|
|
249
|
+
config: {
|
|
250
|
+
...config,
|
|
251
|
+
mode: 'development',
|
|
252
|
+
rootDir: __appRoot,
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const address = await app.listen();
|
|
257
|
+
console.log(\`\\n🏔️ MoriaJS running at \${address}\\n\`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
main().catch((err) => {
|
|
261
|
+
console.error('Failed to start:', err);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
});
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
266
|
+
function srcMiddlewareJs() {
|
|
267
|
+
return `/**
|
|
268
|
+
* Root middleware — runs on every request.
|
|
269
|
+
*/
|
|
270
|
+
|
|
271
|
+
export default async function middleware(request) {
|
|
272
|
+
request.log.info(\`→ \${request.method} \${request.url}\`);
|
|
273
|
+
}
|
|
274
|
+
`;
|
|
275
|
+
}
|
|
276
|
+
// ─── Helpers ────────────────────────────────────────────
|
|
277
|
+
function write(filePath, content) {
|
|
278
|
+
writeFileSync(filePath, content);
|
|
279
|
+
}
|
|
280
|
+
function mkdirs(...dirs) {
|
|
281
|
+
for (const dir of dirs) {
|
|
282
|
+
mkdirSync(dir, { recursive: true });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// ─── CLI command ────────────────────────────────────────
|
|
19
286
|
cli
|
|
20
287
|
.command('[project-name]', 'Create a new MoriaJS project')
|
|
288
|
+
.option('--template <template>', 'Template: default | minimal', { default: '' })
|
|
21
289
|
.option('--typescript', 'Use TypeScript (default)')
|
|
22
290
|
.option('--javascript', 'Use JavaScript')
|
|
23
|
-
.option('--db <adapter>', 'Database adapter: pg | sqlite', { default: '
|
|
291
|
+
.option('--db <adapter>', 'Database adapter: pg | sqlite', { default: '' })
|
|
24
292
|
.action(async (projectName, options) => {
|
|
25
293
|
console.log();
|
|
26
294
|
console.log(pc.cyan('🏔️ create-moria') + pc.dim(` v${VERSION}`));
|
|
@@ -34,6 +302,16 @@ cli
|
|
|
34
302
|
message: 'Project name:',
|
|
35
303
|
initial: 'my-moria-app',
|
|
36
304
|
},
|
|
305
|
+
{
|
|
306
|
+
type: options?.template ? null : 'select',
|
|
307
|
+
name: 'template',
|
|
308
|
+
message: 'Template:',
|
|
309
|
+
choices: [
|
|
310
|
+
{ title: 'Default — SSR page, API, middleware, full-featured', value: 'default' },
|
|
311
|
+
{ title: 'Minimal — API-only, no SSR/UI', value: 'minimal' },
|
|
312
|
+
],
|
|
313
|
+
initial: 0,
|
|
314
|
+
},
|
|
37
315
|
{
|
|
38
316
|
type: options?.typescript || options?.javascript ? null : 'select',
|
|
39
317
|
name: 'language',
|
|
@@ -56,85 +334,111 @@ cli
|
|
|
56
334
|
},
|
|
57
335
|
]);
|
|
58
336
|
const name = projectName ?? answers.projectName;
|
|
337
|
+
if (!name) {
|
|
338
|
+
console.log(pc.red('Error: project name is required.'));
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
const template = options?.template || answers.template || 'default';
|
|
59
342
|
const lang = options?.javascript ? 'js' : (answers.language ?? 'ts');
|
|
60
|
-
const db = options?.db
|
|
343
|
+
const db = options?.db || answers.database || 'sqlite';
|
|
61
344
|
const dir = resolve(process.cwd(), name);
|
|
345
|
+
const ext = lang === 'ts' ? 'ts' : 'js';
|
|
346
|
+
const isTS = lang === 'ts';
|
|
62
347
|
if (existsSync(dir)) {
|
|
63
348
|
console.log(pc.red(`Error: directory "${name}" already exists.`));
|
|
64
349
|
process.exit(1);
|
|
65
350
|
}
|
|
66
351
|
console.log();
|
|
67
352
|
console.log(pc.green(`Creating MoriaJS project in ${pc.bold(dir)}`));
|
|
68
|
-
console.log(pc.dim(`
|
|
69
|
-
console.log(pc.dim(`
|
|
353
|
+
console.log(pc.dim(` Template : ${template}`));
|
|
354
|
+
console.log(pc.dim(` Language : ${isTS ? 'TypeScript' : 'JavaScript'}`));
|
|
355
|
+
console.log(pc.dim(` Database : ${db}`));
|
|
70
356
|
console.log();
|
|
71
|
-
// Create
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
357
|
+
// ─── Create directory structure ─────────────────────
|
|
358
|
+
if (template === 'default') {
|
|
359
|
+
mkdirs(join(dir, 'src', 'routes', 'api'), join(dir, 'src', 'routes', 'pages'));
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
mkdirs(join(dir, 'src', 'routes', 'api'));
|
|
363
|
+
}
|
|
364
|
+
// ─── package.json ───────────────────────────────────
|
|
365
|
+
const deps = {
|
|
366
|
+
'@moriajs/core': '^0.3.0',
|
|
367
|
+
'@moriajs/db': '^0.3.0',
|
|
368
|
+
'@moriajs/auth': '^0.3.0',
|
|
369
|
+
};
|
|
370
|
+
if (template === 'default') {
|
|
371
|
+
deps['@moriajs/renderer'] = '^0.3.0';
|
|
372
|
+
deps['@moriajs/ui'] = '^0.3.0';
|
|
373
|
+
deps['mithril'] = '^2.2.0';
|
|
374
|
+
}
|
|
375
|
+
const devDeps = {
|
|
376
|
+
tsx: '^4.0.0',
|
|
377
|
+
};
|
|
378
|
+
if (isTS) {
|
|
379
|
+
devDeps['typescript'] = '^5.7.0';
|
|
380
|
+
devDeps['@types/node'] = '^22.0.0';
|
|
381
|
+
if (template === 'default') {
|
|
382
|
+
devDeps['@types/mithril'] = '^2.2.0';
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const pkgJson = {
|
|
76
386
|
name,
|
|
77
387
|
version: '0.1.0',
|
|
78
388
|
type: 'module',
|
|
79
389
|
private: true,
|
|
80
390
|
scripts: {
|
|
81
|
-
dev:
|
|
82
|
-
build: '
|
|
83
|
-
start:
|
|
84
|
-
},
|
|
85
|
-
dependencies: {
|
|
86
|
-
'@moriajs/core': 'latest',
|
|
87
|
-
'@moriajs/renderer': 'latest',
|
|
88
|
-
'@moriajs/db': 'latest',
|
|
89
|
-
'@moriajs/auth': 'latest',
|
|
90
|
-
'@moriajs/ui': 'latest',
|
|
91
|
-
mithril: '^2.2.0',
|
|
391
|
+
dev: `tsx watch src/index.${ext}`,
|
|
392
|
+
build: isTS ? 'tsc' : 'echo "No build step for JS"',
|
|
393
|
+
start: `node dist/index.js`,
|
|
92
394
|
},
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
console.log(pc.green('✅ Project created successfully!'));
|
|
395
|
+
dependencies: deps,
|
|
396
|
+
devDependencies: devDeps,
|
|
397
|
+
};
|
|
398
|
+
write(join(dir, 'package.json'), JSON.stringify(pkgJson, null, 2) + '\n');
|
|
399
|
+
// ─── Config ─────────────────────────────────────────
|
|
400
|
+
write(join(dir, `moria.config.${ext}`), isTS ? moriaConfigTs(db) : moriaConfigJs(db));
|
|
401
|
+
// ─── tsconfig.json ──────────────────────────────────
|
|
402
|
+
if (isTS) {
|
|
403
|
+
write(join(dir, 'tsconfig.json'), tsconfigContent() + '\n');
|
|
404
|
+
}
|
|
405
|
+
// ─── .env ───────────────────────────────────────────
|
|
406
|
+
write(join(dir, '.env'), envContent(db));
|
|
407
|
+
// ─── .gitignore ─────────────────────────────────────
|
|
408
|
+
write(join(dir, '.gitignore'), gitignoreContent());
|
|
409
|
+
// ─── Source files ───────────────────────────────────
|
|
410
|
+
if (template === 'default') {
|
|
411
|
+
// App entry
|
|
412
|
+
write(join(dir, `src/index.${ext}`), isTS ? srcIndexTs() : srcIndexJs());
|
|
413
|
+
// Client entry
|
|
414
|
+
if (isTS) {
|
|
415
|
+
write(join(dir, 'src/entry-client.ts'), srcEntryClientTs());
|
|
416
|
+
}
|
|
417
|
+
// Root middleware
|
|
418
|
+
write(join(dir, `src/routes/_middleware.${ext}`), isTS ? srcMiddlewareTs() : srcMiddlewareJs());
|
|
419
|
+
// API route
|
|
420
|
+
write(join(dir, `src/routes/api/hello.${ext}`), isTS ? srcApiHelloTs() : srcApiHelloJs());
|
|
421
|
+
// SSR page
|
|
422
|
+
if (isTS) {
|
|
423
|
+
write(join(dir, 'src/routes/pages/index.ts'), srcPageIndexTs());
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
// Minimal template — just entry + one API route
|
|
428
|
+
write(join(dir, `src/index.${ext}`), isTS ? srcIndexTs() : srcIndexJs());
|
|
429
|
+
write(join(dir, `src/routes/api/hello.${ext}`), isTS ? srcApiHelloTs() : srcApiHelloJs());
|
|
430
|
+
}
|
|
431
|
+
// ─── Done ───────────────────────────────────────────
|
|
432
|
+
const filesCount = template === 'default' ? (isTS ? 9 : 7) : (isTS ? 6 : 5);
|
|
433
|
+
console.log(pc.green(`✅ Created ${filesCount} files`));
|
|
133
434
|
console.log();
|
|
134
435
|
console.log(pc.cyan(' Next steps:'));
|
|
135
|
-
console.log(
|
|
136
|
-
console.log(pc.
|
|
137
|
-
console.log(pc.
|
|
436
|
+
console.log();
|
|
437
|
+
console.log(pc.white(` cd ${name}`));
|
|
438
|
+
console.log(pc.white(' pnpm install'));
|
|
439
|
+
console.log(pc.white(' pnpm dev'));
|
|
440
|
+
console.log();
|
|
441
|
+
console.log(pc.dim(` Then open http://localhost:3000`));
|
|
138
442
|
console.log();
|
|
139
443
|
});
|
|
140
444
|
cli.version(VERSION);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC1B,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,OAAO,GAAG,OAAO,CAAC;AAExB,MAAM,CAAC,MAAM,GAAG,GAAG,GAAG,CAAC,cAAc,CAAC,CAAC;AAEvC,yDAAyD;AAEzD,SAAS,eAAe;IACpB,OAAO,IAAI,CAAC,SAAS,CACjB;QACI,eAAe,EAAE;YACb,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;YAChB,gBAAgB,EAAE,SAAS;YAC3B,eAAe,EAAE,IAAI;YACrB,MAAM,EAAE,IAAI;YACZ,YAAY,EAAE,IAAI;YAClB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,IAAI;YACjB,cAAc,EAAE,IAAI;YACpB,SAAS,EAAE,IAAI;SAClB;QACD,OAAO,EAAE,CAAC,KAAK,CAAC;QAChB,OAAO,EAAE,CAAC,cAAc,EAAE,MAAM,CAAC;KACpC,EACD,IAAI,EACJ,CAAC,CACJ,CAAC;AACN,CAAC;AAED,SAAS,UAAU,CAAC,EAAU;IAC1B,MAAM,KAAK,GAAG;QACV,iCAAiC;QACjC,EAAE;QACF,UAAU;QACV,WAAW;QACX,EAAE;QACF,YAAY;KACf,CAAC;IACF,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;IAC9E,CAAC;SAAM,CAAC;QACJ,KAAK,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAC;IAC7E,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,yCAAyC,EAAE,EAAE,CAAC,CAAC;IACxE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,gBAAgB;IACrB,OAAO;;;;;CAKV,CAAC;AACF,CAAC;AAED,SAAS,aAAa,CAAC,EAAU;IAC7B,MAAM,QAAQ,GACV,EAAE,KAAK,QAAQ;QACX,CAAC,CAAC,mDAAmD;QACrD,CAAC,CAAC,wDAAwD,CAAC;IACnE,OAAO;;;;;;;EAOT,QAAQ;;;CAGT,CAAC;AACF,CAAC;AAED,SAAS,aAAa,CAAC,EAAU;IAC7B,MAAM,QAAQ,GACV,EAAE,KAAK,QAAQ;QACX,CAAC,CAAC,mDAAmD;QACrD,CAAC,CAAC,wDAAwD,CAAC;IACnE,OAAO;;;;;;EAMT,QAAQ;;;CAGT,CAAC;AACF,CAAC;AAED,0DAA0D;AAE1D,SAAS,UAAU;IACf,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BV,CAAC;AACF,CAAC;AAED,SAAS,gBAAgB;IACrB,OAAO;;;;;;;;;;;;;;CAcV,CAAC;AACF,CAAC;AAED,SAAS,eAAe;IACpB,OAAO;;;;;;;;;CASV,CAAC;AACF,CAAC;AAED,SAAS,aAAa;IAClB,OAAO;;;;;;;;;;;;CAYV,CAAC;AACF,CAAC;AAED,SAAS,aAAa;IAClB,OAAO;;;;;;;;;;CAUV,CAAC;AACF,CAAC;AAED,SAAS,cAAc;IACnB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CV,CAAC;AACF,CAAC;AAED,SAAS,UAAU;IACf,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BV,CAAC;AACF,CAAC;AAED,SAAS,eAAe;IACpB,OAAO;;;;;;;CAOV,CAAC;AACF,CAAC;AAED,2DAA2D;AAE3D,SAAS,KAAK,CAAC,QAAgB,EAAE,OAAe;IAC5C,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,MAAM,CAAC,GAAG,IAAc;IAC7B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxC,CAAC;AACL,CAAC;AAED,2DAA2D;AAE3D,GAAG;KACE,OAAO,CAAC,gBAAgB,EAAE,8BAA8B,CAAC;KACzD,MAAM,CAAC,uBAAuB,EAAE,6BAA6B,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;KAC/E,MAAM,CAAC,cAAc,EAAE,0BAA0B,CAAC;KAClD,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAAC;KACxC,MAAM,CAAC,gBAAgB,EAAE,+BAA+B,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;KAC1E,MAAM,CAAC,KAAK,EAAE,WAAoB,EAAE,OAAiC,EAAE,EAAE;IACtE,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC,CAAC;IACxD,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,+CAA+C;IAC/C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC;QAC1B;YACI,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM;YACjC,IAAI,EAAE,aAAa;YACnB,OAAO,EAAE,eAAe;YACxB,OAAO,EAAE,cAAc;SAC1B;QACD;YACI,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ;YACzC,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,WAAW;YACpB,OAAO,EAAE;gBACL,EAAE,KAAK,EAAE,oDAAoD,EAAE,KAAK,EAAE,SAAS,EAAE;gBACjF,EAAE,KAAK,EAAE,+BAA+B,EAAE,KAAK,EAAE,SAAS,EAAE;aAC/D;YACD,OAAO,EAAE,CAAC;SACb;QACD;YACI,IAAI,EAAE,OAAO,EAAE,UAAU,IAAI,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ;YAClE,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,WAAW;YACpB,OAAO,EAAE;gBACL,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE;gBACpC,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE;aACvC;YACD,OAAO,EAAE,CAAC;SACb;QACD;YACI,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ;YACnC,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,WAAW;YACpB,OAAO,EAAE;gBACL,EAAE,KAAK,EAAE,iCAAiC,EAAE,KAAK,EAAE,QAAQ,EAAE;gBAC7D,EAAE,KAAK,EAAE,+BAA+B,EAAE,KAAK,EAAE,IAAI,EAAE;aAC1D;YACD,OAAO,EAAE,CAAC;SACb;KACJ,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IAChD,IAAI,CAAC,IAAI,EAAE,CAAC;QACR,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC,CAAC;QACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,QAAQ,GAAI,OAAO,EAAE,QAAmB,IAAI,OAAO,CAAC,QAAQ,IAAI,SAAS,CAAC;IAChF,MAAM,IAAI,GAAG,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC;IACrE,MAAM,EAAE,GAAI,OAAO,EAAE,EAAa,IAAI,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC;IACnE,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IAE3B,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,qBAAqB,IAAI,mBAAmB,CAAC,CAAC,CAAC;QAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,+BAA+B,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,gBAAgB,QAAQ,EAAE,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,gBAAgB,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;IAC1E,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,uDAAuD;IACvD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,CACF,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,EACjC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CACtC,CAAC;IACN,CAAC;SAAM,CAAC;QACJ,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED,uDAAuD;IACvD,MAAM,IAAI,GAA2B;QACjC,eAAe,EAAE,QAAQ;QACzB,aAAa,EAAE,QAAQ;QACvB,eAAe,EAAE,QAAQ;KAC5B,CAAC;IAEF,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,CAAC,mBAAmB,CAAC,GAAG,QAAQ,CAAC;QACrC,IAAI,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC;QAC/B,IAAI,CAAC,SAAS,CAAC,GAAG,QAAQ,CAAC;IAC/B,CAAC;IAED,MAAM,OAAO,GAA2B;QACpC,GAAG,EAAE,QAAQ;KAChB,CAAC;IAEF,IAAI,IAAI,EAAE,CAAC;QACP,OAAO,CAAC,YAAY,CAAC,GAAG,QAAQ,CAAC;QACjC,OAAO,CAAC,aAAa,CAAC,GAAG,SAAS,CAAC;QACnC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,CAAC,gBAAgB,CAAC,GAAG,QAAQ,CAAC;QACzC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG;QACZ,IAAI;QACJ,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,IAAI;QACb,OAAO,EAAE;YACL,GAAG,EAAE,uBAAuB,GAAG,EAAE;YACjC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,6BAA6B;YACnD,KAAK,EAAE,oBAAoB;SAC9B;QACD,YAAY,EAAE,IAAI;QAClB,eAAe,EAAE,OAAO;KAC3B,CAAC;IAEF,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAE1E,uDAAuD;IACvD,KAAK,CACD,IAAI,CAAC,GAAG,EAAE,gBAAgB,GAAG,EAAE,CAAC,EAChC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,CAC/C,CAAC;IAEF,uDAAuD;IACvD,IAAI,IAAI,EAAE,CAAC;QACP,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC,CAAC;IAChE,CAAC;IAED,uDAAuD;IACvD,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;IAEzC,uDAAuD;IACvD,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAEnD,uDAAuD;IACvD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACzB,YAAY;QACZ,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;QAEzE,eAAe;QACf,IAAI,IAAI,EAAE,CAAC;YACP,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,qBAAqB,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAChE,CAAC;QAED,kBAAkB;QAClB,KAAK,CACD,IAAI,CAAC,GAAG,EAAE,0BAA0B,GAAG,EAAE,CAAC,EAC1C,IAAI,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,eAAe,EAAE,CAC/C,CAAC;QAEF,YAAY;QACZ,KAAK,CACD,IAAI,CAAC,GAAG,EAAE,wBAAwB,GAAG,EAAE,CAAC,EACxC,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAC3C,CAAC;QAEF,WAAW;QACX,IAAI,IAAI,EAAE,CAAC;YACP,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,2BAA2B,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;QACpE,CAAC;IACL,CAAC;SAAM,CAAC;QACJ,gDAAgD;QAChD,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;QACzE,KAAK,CACD,IAAI,CAAC,GAAG,EAAE,wBAAwB,GAAG,EAAE,CAAC,EACxC,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAC3C,CAAC;IACN,CAAC;IAED,uDAAuD;IACvD,MAAM,UAAU,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE5E,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,aAAa,UAAU,QAAQ,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC;IACtC,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC;IACtC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;IACpC,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,EAAE,CAAC;AAClB,CAAC,CAAC,CAAC;AAEP,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AACrB,GAAG,CAAC,IAAI,EAAE,CAAC"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* create-moria
|
|
3
3
|
*
|
|
4
4
|
* Interactive project scaffolder for MoriaJS.
|
|
5
|
-
*
|
|
5
|
+
* Generates a complete, runnable project with two template options:
|
|
6
|
+
*
|
|
7
|
+
* default — Full-featured: SSR page, API route, middleware, client hydration
|
|
8
|
+
* minimal — Bare API server: single API route, no SSR/UI
|
|
6
9
|
*
|
|
7
10
|
* Usage:
|
|
8
11
|
* npx create-moria my-app
|
|
12
|
+
* npx create-moria my-app --template minimal
|
|
9
13
|
* npx create-moria my-app --typescript
|
|
10
|
-
* npx create-moria my-app --javascript
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
16
|
import { cac } from 'cac';
|
|
@@ -16,15 +19,304 @@ import prompts from 'prompts';
|
|
|
16
19
|
import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
17
20
|
import { join, resolve } from 'node:path';
|
|
18
21
|
|
|
19
|
-
const VERSION = '0.
|
|
22
|
+
const VERSION = '0.3.0';
|
|
20
23
|
|
|
21
24
|
export const cli = cac('create-moria');
|
|
22
25
|
|
|
26
|
+
// ─── Template file generators ─────────────────────────
|
|
27
|
+
|
|
28
|
+
function tsconfigContent(): string {
|
|
29
|
+
return JSON.stringify(
|
|
30
|
+
{
|
|
31
|
+
compilerOptions: {
|
|
32
|
+
target: 'ES2022',
|
|
33
|
+
module: 'ESNext',
|
|
34
|
+
moduleResolution: 'bundler',
|
|
35
|
+
esModuleInterop: true,
|
|
36
|
+
strict: true,
|
|
37
|
+
skipLibCheck: true,
|
|
38
|
+
outDir: 'dist',
|
|
39
|
+
rootDir: 'src',
|
|
40
|
+
declaration: true,
|
|
41
|
+
declarationMap: true,
|
|
42
|
+
sourceMap: true,
|
|
43
|
+
},
|
|
44
|
+
include: ['src'],
|
|
45
|
+
exclude: ['node_modules', 'dist'],
|
|
46
|
+
},
|
|
47
|
+
null,
|
|
48
|
+
4
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function envContent(db: string): string {
|
|
53
|
+
const lines = [
|
|
54
|
+
'# MoriaJS Environment Variables',
|
|
55
|
+
'',
|
|
56
|
+
'# Server',
|
|
57
|
+
'PORT=3000',
|
|
58
|
+
'',
|
|
59
|
+
'# Database',
|
|
60
|
+
];
|
|
61
|
+
if (db === 'pg') {
|
|
62
|
+
lines.push('DATABASE_URL=postgresql://user:password@localhost:5432/mydb');
|
|
63
|
+
} else {
|
|
64
|
+
lines.push('# SQLite uses a local file (configured in moria.config.ts)');
|
|
65
|
+
}
|
|
66
|
+
lines.push('', '# Auth', 'JWT_SECRET=change-me-to-a-random-secret', '');
|
|
67
|
+
return lines.join('\n');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function gitignoreContent(): string {
|
|
71
|
+
return `node_modules/
|
|
72
|
+
dist/
|
|
73
|
+
.env
|
|
74
|
+
*.db
|
|
75
|
+
.turbo/
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function moriaConfigTs(db: string): string {
|
|
80
|
+
const dbConfig =
|
|
81
|
+
db === 'sqlite'
|
|
82
|
+
? ` adapter: 'sqlite',\n filename: './dev.db',`
|
|
83
|
+
: ` adapter: 'pg',\n url: process.env.DATABASE_URL,`;
|
|
84
|
+
return `import { defineConfig } from '@moriajs/core';
|
|
85
|
+
|
|
86
|
+
export default defineConfig({
|
|
87
|
+
server: {
|
|
88
|
+
port: Number(process.env.PORT) || 3000,
|
|
89
|
+
},
|
|
90
|
+
database: {
|
|
91
|
+
${dbConfig}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function moriaConfigJs(db: string): string {
|
|
98
|
+
const dbConfig =
|
|
99
|
+
db === 'sqlite'
|
|
100
|
+
? ` adapter: 'sqlite',\n filename: './dev.db',`
|
|
101
|
+
: ` adapter: 'pg',\n url: process.env.DATABASE_URL,`;
|
|
102
|
+
return `/** @type {import('@moriajs/core').MoriaConfig} */
|
|
103
|
+
export default {
|
|
104
|
+
server: {
|
|
105
|
+
port: Number(process.env.PORT) || 3000,
|
|
106
|
+
},
|
|
107
|
+
database: {
|
|
108
|
+
${dbConfig}
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Default template source files ─────────────────────
|
|
115
|
+
|
|
116
|
+
function srcIndexTs(): string {
|
|
117
|
+
return `/**
|
|
118
|
+
* App entry point — starts the MoriaJS server.
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
import { createApp } from '@moriajs/core';
|
|
122
|
+
import path from 'node:path';
|
|
123
|
+
|
|
124
|
+
const __appRoot = path.resolve(import.meta.dirname, '..');
|
|
125
|
+
|
|
126
|
+
async function main() {
|
|
127
|
+
const { default: config } = await import('../moria.config.js');
|
|
128
|
+
|
|
129
|
+
const app = await createApp({
|
|
130
|
+
config: {
|
|
131
|
+
...config,
|
|
132
|
+
mode: 'development',
|
|
133
|
+
rootDir: __appRoot,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const address = await app.listen();
|
|
138
|
+
console.log(\`\\n🏔️ MoriaJS running at \${address}\\n\`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
main().catch((err) => {
|
|
142
|
+
console.error('Failed to start:', err);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
});
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function srcEntryClientTs(): string {
|
|
149
|
+
return `/**
|
|
150
|
+
* Client-side entry point.
|
|
151
|
+
* Hydrates the server-rendered page to make it interactive.
|
|
152
|
+
*/
|
|
153
|
+
|
|
154
|
+
import { hydrate, getHydrationData } from '@moriajs/renderer';
|
|
155
|
+
import HomePage from './routes/pages/index.js';
|
|
156
|
+
|
|
157
|
+
const data = getHydrationData();
|
|
158
|
+
const root = document.getElementById('app');
|
|
159
|
+
|
|
160
|
+
if (root) {
|
|
161
|
+
hydrate(HomePage, root, data);
|
|
162
|
+
}
|
|
163
|
+
`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function srcMiddlewareTs(): string {
|
|
167
|
+
return `/**
|
|
168
|
+
* Root middleware — runs on every request.
|
|
169
|
+
*/
|
|
170
|
+
|
|
171
|
+
import { defineMiddleware } from '@moriajs/core';
|
|
172
|
+
|
|
173
|
+
export default defineMiddleware(async (request) => {
|
|
174
|
+
request.log.info(\`→ \${request.method} \${request.url}\`);
|
|
175
|
+
});
|
|
176
|
+
`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function srcApiHelloTs(): string {
|
|
180
|
+
return `/**
|
|
181
|
+
* GET /api/hello
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
185
|
+
|
|
186
|
+
export function GET(_request: FastifyRequest, _reply: FastifyReply) {
|
|
187
|
+
return {
|
|
188
|
+
message: 'Hello from MoriaJS! 🏔️',
|
|
189
|
+
timestamp: new Date().toISOString(),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function srcApiHelloJs(): string {
|
|
196
|
+
return `/**
|
|
197
|
+
* GET /api/hello
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
export function GET(_request, _reply) {
|
|
201
|
+
return {
|
|
202
|
+
message: 'Hello from MoriaJS! 🏔️',
|
|
203
|
+
timestamp: new Date().toISOString(),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function srcPageIndexTs(): string {
|
|
210
|
+
return `/**
|
|
211
|
+
* Home page — server-rendered Mithril.js component.
|
|
212
|
+
*/
|
|
213
|
+
|
|
214
|
+
import m from 'mithril';
|
|
215
|
+
|
|
216
|
+
/** Server-side data loader */
|
|
217
|
+
export async function getServerData() {
|
|
218
|
+
return {
|
|
219
|
+
title: 'Welcome to MoriaJS',
|
|
220
|
+
features: [
|
|
221
|
+
'File-based routing',
|
|
222
|
+
'SSR + client hydration',
|
|
223
|
+
'Middleware system',
|
|
224
|
+
'Database with Kysely',
|
|
225
|
+
'JWT authentication',
|
|
226
|
+
],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Page component */
|
|
231
|
+
export default {
|
|
232
|
+
title: 'MoriaJS App',
|
|
233
|
+
view(vnode: m.Vnode<{ serverData?: { title: string; features: string[] } }>) {
|
|
234
|
+
const data = vnode.attrs?.serverData;
|
|
235
|
+
return m('main', { style: 'max-width:640px;margin:2rem auto;font-family:system-ui;padding:0 1rem' }, [
|
|
236
|
+
m('h1', { style: 'font-size:2.5rem;margin-bottom:0.5rem' }, '🏔️ ' + (data?.title ?? 'MoriaJS')),
|
|
237
|
+
m('p', { style: 'color:#666;font-size:1.1rem' }, 'Your full-stack Mithril.js app is up and running.'),
|
|
238
|
+
m('hr', { style: 'border:none;border-top:1px solid #eee;margin:1.5rem 0' }),
|
|
239
|
+
m('h2', { style: 'font-size:1.3rem' }, 'Features'),
|
|
240
|
+
m('ul', { style: 'line-height:1.8' },
|
|
241
|
+
(data?.features ?? []).map((f: string) => m('li', f))
|
|
242
|
+
),
|
|
243
|
+
m('hr', { style: 'border:none;border-top:1px solid #eee;margin:1.5rem 0' }),
|
|
244
|
+
m('p', { style: 'color:#999;font-size:0.9rem' }, [
|
|
245
|
+
'Edit ',
|
|
246
|
+
m('code', 'src/routes/pages/index.ts'),
|
|
247
|
+
' to get started.',
|
|
248
|
+
]),
|
|
249
|
+
]);
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function srcIndexJs(): string {
|
|
256
|
+
return `/**
|
|
257
|
+
* App entry point — starts the MoriaJS server.
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
import { createApp } from '@moriajs/core';
|
|
261
|
+
import path from 'node:path';
|
|
262
|
+
import { fileURLToPath } from 'node:url';
|
|
263
|
+
|
|
264
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
265
|
+
const __appRoot = path.resolve(__dirname, '..');
|
|
266
|
+
|
|
267
|
+
async function main() {
|
|
268
|
+
const { default: config } = await import('../moria.config.js');
|
|
269
|
+
|
|
270
|
+
const app = await createApp({
|
|
271
|
+
config: {
|
|
272
|
+
...config,
|
|
273
|
+
mode: 'development',
|
|
274
|
+
rootDir: __appRoot,
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const address = await app.listen();
|
|
279
|
+
console.log(\`\\n🏔️ MoriaJS running at \${address}\\n\`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
main().catch((err) => {
|
|
283
|
+
console.error('Failed to start:', err);
|
|
284
|
+
process.exit(1);
|
|
285
|
+
});
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function srcMiddlewareJs(): string {
|
|
290
|
+
return `/**
|
|
291
|
+
* Root middleware — runs on every request.
|
|
292
|
+
*/
|
|
293
|
+
|
|
294
|
+
export default async function middleware(request) {
|
|
295
|
+
request.log.info(\`→ \${request.method} \${request.url}\`);
|
|
296
|
+
}
|
|
297
|
+
`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ─── Helpers ────────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
function write(filePath: string, content: string) {
|
|
303
|
+
writeFileSync(filePath, content);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function mkdirs(...dirs: string[]) {
|
|
307
|
+
for (const dir of dirs) {
|
|
308
|
+
mkdirSync(dir, { recursive: true });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ─── CLI command ────────────────────────────────────────
|
|
313
|
+
|
|
23
314
|
cli
|
|
24
315
|
.command('[project-name]', 'Create a new MoriaJS project')
|
|
316
|
+
.option('--template <template>', 'Template: default | minimal', { default: '' })
|
|
25
317
|
.option('--typescript', 'Use TypeScript (default)')
|
|
26
318
|
.option('--javascript', 'Use JavaScript')
|
|
27
|
-
.option('--db <adapter>', 'Database adapter: pg | sqlite', { default: '
|
|
319
|
+
.option('--db <adapter>', 'Database adapter: pg | sqlite', { default: '' })
|
|
28
320
|
.action(async (projectName?: string, options?: Record<string, unknown>) => {
|
|
29
321
|
console.log();
|
|
30
322
|
console.log(pc.cyan('🏔️ create-moria') + pc.dim(` v${VERSION}`));
|
|
@@ -39,6 +331,16 @@ cli
|
|
|
39
331
|
message: 'Project name:',
|
|
40
332
|
initial: 'my-moria-app',
|
|
41
333
|
},
|
|
334
|
+
{
|
|
335
|
+
type: options?.template ? null : 'select',
|
|
336
|
+
name: 'template',
|
|
337
|
+
message: 'Template:',
|
|
338
|
+
choices: [
|
|
339
|
+
{ title: 'Default — SSR page, API, middleware, full-featured', value: 'default' },
|
|
340
|
+
{ title: 'Minimal — API-only, no SSR/UI', value: 'minimal' },
|
|
341
|
+
],
|
|
342
|
+
initial: 0,
|
|
343
|
+
},
|
|
42
344
|
{
|
|
43
345
|
type: options?.typescript || options?.javascript ? null : 'select',
|
|
44
346
|
name: 'language',
|
|
@@ -62,9 +364,17 @@ cli
|
|
|
62
364
|
]);
|
|
63
365
|
|
|
64
366
|
const name = projectName ?? answers.projectName;
|
|
367
|
+
if (!name) {
|
|
368
|
+
console.log(pc.red('Error: project name is required.'));
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const template = (options?.template as string) || answers.template || 'default';
|
|
65
373
|
const lang = options?.javascript ? 'js' : (answers.language ?? 'ts');
|
|
66
|
-
const db = (options?.db as string)
|
|
374
|
+
const db = (options?.db as string) || answers.database || 'sqlite';
|
|
67
375
|
const dir = resolve(process.cwd(), name);
|
|
376
|
+
const ext = lang === 'ts' ? 'ts' : 'js';
|
|
377
|
+
const isTS = lang === 'ts';
|
|
68
378
|
|
|
69
379
|
if (existsSync(dir)) {
|
|
70
380
|
console.log(pc.red(`Error: directory "${name}" already exists.`));
|
|
@@ -73,94 +383,126 @@ cli
|
|
|
73
383
|
|
|
74
384
|
console.log();
|
|
75
385
|
console.log(pc.green(`Creating MoriaJS project in ${pc.bold(dir)}`));
|
|
76
|
-
console.log(pc.dim(`
|
|
77
|
-
console.log(pc.dim(`
|
|
386
|
+
console.log(pc.dim(` Template : ${template}`));
|
|
387
|
+
console.log(pc.dim(` Language : ${isTS ? 'TypeScript' : 'JavaScript'}`));
|
|
388
|
+
console.log(pc.dim(` Database : ${db}`));
|
|
78
389
|
console.log();
|
|
79
390
|
|
|
80
|
-
// Create
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
name,
|
|
90
|
-
version: '0.1.0',
|
|
91
|
-
type: 'module',
|
|
92
|
-
private: true,
|
|
93
|
-
scripts: {
|
|
94
|
-
dev: 'moria dev',
|
|
95
|
-
build: 'moria build',
|
|
96
|
-
start: 'moria start',
|
|
97
|
-
},
|
|
98
|
-
dependencies: {
|
|
99
|
-
'@moriajs/core': 'latest',
|
|
100
|
-
'@moriajs/renderer': 'latest',
|
|
101
|
-
'@moriajs/db': 'latest',
|
|
102
|
-
'@moriajs/auth': 'latest',
|
|
103
|
-
'@moriajs/ui': 'latest',
|
|
104
|
-
mithril: '^2.2.0',
|
|
105
|
-
},
|
|
106
|
-
devDependencies:
|
|
107
|
-
lang === 'ts'
|
|
108
|
-
? {
|
|
109
|
-
'@moriajs/cli': 'latest',
|
|
110
|
-
typescript: '^5.7.0',
|
|
111
|
-
'@types/mithril': '^2.2.0',
|
|
112
|
-
}
|
|
113
|
-
: {
|
|
114
|
-
'@moriajs/cli': 'latest',
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
null,
|
|
118
|
-
2
|
|
119
|
-
)
|
|
120
|
-
);
|
|
391
|
+
// ─── Create directory structure ─────────────────────
|
|
392
|
+
if (template === 'default') {
|
|
393
|
+
mkdirs(
|
|
394
|
+
join(dir, 'src', 'routes', 'api'),
|
|
395
|
+
join(dir, 'src', 'routes', 'pages')
|
|
396
|
+
);
|
|
397
|
+
} else {
|
|
398
|
+
mkdirs(join(dir, 'src', 'routes', 'api'));
|
|
399
|
+
}
|
|
121
400
|
|
|
122
|
-
//
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
401
|
+
// ─── package.json ───────────────────────────────────
|
|
402
|
+
const deps: Record<string, string> = {
|
|
403
|
+
'@moriajs/core': '^0.3.0',
|
|
404
|
+
'@moriajs/db': '^0.3.0',
|
|
405
|
+
'@moriajs/auth': '^0.3.0',
|
|
406
|
+
};
|
|
127
407
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
408
|
+
if (template === 'default') {
|
|
409
|
+
deps['@moriajs/renderer'] = '^0.3.0';
|
|
410
|
+
deps['@moriajs/ui'] = '^0.3.0';
|
|
411
|
+
deps['mithril'] = '^2.2.0';
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const devDeps: Record<string, string> = {
|
|
415
|
+
tsx: '^4.0.0',
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
if (isTS) {
|
|
419
|
+
devDeps['typescript'] = '^5.7.0';
|
|
420
|
+
devDeps['@types/node'] = '^22.0.0';
|
|
421
|
+
if (template === 'default') {
|
|
422
|
+
devDeps['@types/mithril'] = '^2.2.0';
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const pkgJson = {
|
|
427
|
+
name,
|
|
428
|
+
version: '0.1.0',
|
|
429
|
+
type: 'module',
|
|
430
|
+
private: true,
|
|
431
|
+
scripts: {
|
|
432
|
+
dev: `tsx watch src/index.${ext}`,
|
|
433
|
+
build: isTS ? 'tsc' : 'echo "No build step for JS"',
|
|
434
|
+
start: `node dist/index.js`,
|
|
435
|
+
},
|
|
436
|
+
dependencies: deps,
|
|
437
|
+
devDependencies: devDeps,
|
|
438
|
+
};
|
|
149
439
|
|
|
150
|
-
|
|
440
|
+
write(join(dir, 'package.json'), JSON.stringify(pkgJson, null, 2) + '\n');
|
|
151
441
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
join(dir,
|
|
155
|
-
|
|
442
|
+
// ─── Config ─────────────────────────────────────────
|
|
443
|
+
write(
|
|
444
|
+
join(dir, `moria.config.${ext}`),
|
|
445
|
+
isTS ? moriaConfigTs(db) : moriaConfigJs(db)
|
|
156
446
|
);
|
|
157
447
|
|
|
158
|
-
|
|
448
|
+
// ─── tsconfig.json ──────────────────────────────────
|
|
449
|
+
if (isTS) {
|
|
450
|
+
write(join(dir, 'tsconfig.json'), tsconfigContent() + '\n');
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ─── .env ───────────────────────────────────────────
|
|
454
|
+
write(join(dir, '.env'), envContent(db));
|
|
455
|
+
|
|
456
|
+
// ─── .gitignore ─────────────────────────────────────
|
|
457
|
+
write(join(dir, '.gitignore'), gitignoreContent());
|
|
458
|
+
|
|
459
|
+
// ─── Source files ───────────────────────────────────
|
|
460
|
+
if (template === 'default') {
|
|
461
|
+
// App entry
|
|
462
|
+
write(join(dir, `src/index.${ext}`), isTS ? srcIndexTs() : srcIndexJs());
|
|
463
|
+
|
|
464
|
+
// Client entry
|
|
465
|
+
if (isTS) {
|
|
466
|
+
write(join(dir, 'src/entry-client.ts'), srcEntryClientTs());
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Root middleware
|
|
470
|
+
write(
|
|
471
|
+
join(dir, `src/routes/_middleware.${ext}`),
|
|
472
|
+
isTS ? srcMiddlewareTs() : srcMiddlewareJs()
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
// API route
|
|
476
|
+
write(
|
|
477
|
+
join(dir, `src/routes/api/hello.${ext}`),
|
|
478
|
+
isTS ? srcApiHelloTs() : srcApiHelloJs()
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
// SSR page
|
|
482
|
+
if (isTS) {
|
|
483
|
+
write(join(dir, 'src/routes/pages/index.ts'), srcPageIndexTs());
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
// Minimal template — just entry + one API route
|
|
487
|
+
write(join(dir, `src/index.${ext}`), isTS ? srcIndexTs() : srcIndexJs());
|
|
488
|
+
write(
|
|
489
|
+
join(dir, `src/routes/api/hello.${ext}`),
|
|
490
|
+
isTS ? srcApiHelloTs() : srcApiHelloJs()
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ─── Done ───────────────────────────────────────────
|
|
495
|
+
const filesCount = template === 'default' ? (isTS ? 9 : 7) : (isTS ? 6 : 5);
|
|
496
|
+
|
|
497
|
+
console.log(pc.green(`✅ Created ${filesCount} files`));
|
|
159
498
|
console.log();
|
|
160
499
|
console.log(pc.cyan(' Next steps:'));
|
|
161
|
-
console.log(
|
|
162
|
-
console.log(pc.
|
|
163
|
-
console.log(pc.
|
|
500
|
+
console.log();
|
|
501
|
+
console.log(pc.white(` cd ${name}`));
|
|
502
|
+
console.log(pc.white(' pnpm install'));
|
|
503
|
+
console.log(pc.white(' pnpm dev'));
|
|
504
|
+
console.log();
|
|
505
|
+
console.log(pc.dim(` Then open http://localhost:3000`));
|
|
164
506
|
console.log();
|
|
165
507
|
});
|
|
166
508
|
|