webspresso 0.0.13 → 0.0.14
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/README.md +4 -7
- package/bin/commands/add-tailwind.js +151 -0
- package/bin/commands/api.js +70 -0
- package/bin/commands/db-make.js +76 -0
- package/bin/commands/db-migrate.js +43 -0
- package/bin/commands/db-rollback.js +48 -0
- package/bin/commands/db-status.js +53 -0
- package/bin/commands/dev.js +73 -0
- package/bin/commands/new.js +634 -0
- package/bin/commands/page.js +134 -0
- package/bin/commands/seed.js +154 -0
- package/bin/commands/start.js +30 -0
- package/bin/utils/db.js +54 -0
- package/bin/utils/migration.js +36 -0
- package/bin/utils/project.js +97 -0
- package/bin/utils/seed.js +112 -0
- package/bin/webspresso.js +24 -1696
- package/core/orm/index.js +14 -1
- package/core/orm/migrations/scaffold.js +5 -0
- package/core/orm/model.js +8 -0
- package/core/orm/schema-helpers.js +39 -1
- package/core/orm/seeder.js +56 -3
- package/core/orm/types.js +28 -1
- package/index.js +2 -1
- package/package.json +1 -1
- package/plugins/admin-panel/admin-user-model.js +42 -0
- package/plugins/admin-panel/api.js +436 -0
- package/plugins/admin-panel/app.js +68 -0
- package/plugins/admin-panel/auth.js +157 -0
- package/plugins/admin-panel/components.js +359 -0
- package/plugins/admin-panel/field-renderers/array.js +57 -0
- package/plugins/admin-panel/field-renderers/basic.js +205 -0
- package/plugins/admin-panel/field-renderers/file-upload.js +124 -0
- package/plugins/admin-panel/field-renderers/index.js +93 -0
- package/plugins/admin-panel/field-renderers/json.js +52 -0
- package/plugins/admin-panel/field-renderers/relations.js +96 -0
- package/plugins/admin-panel/field-renderers/rich-text.js +83 -0
- package/plugins/admin-panel/index.js +187 -0
- package/plugins/admin-panel/migration-template.js +39 -0
- package/plugins/admin-panel/styles.js +9 -0
- package/plugins/index.js +2 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* New Project Command
|
|
3
|
+
* Creates a new Webspresso project
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { runInstallation, startDevServer } = require('../utils/project');
|
|
10
|
+
const { getSeedFileTemplate } = require('../utils/seed');
|
|
11
|
+
|
|
12
|
+
function registerCommand(program) {
|
|
13
|
+
program
|
|
14
|
+
.command('new [project-name]')
|
|
15
|
+
.description('Create a new Webspresso project')
|
|
16
|
+
.option('-t, --template <template>', 'Template to use (minimal, full)', 'minimal')
|
|
17
|
+
.option('--no-tailwind', 'Skip Tailwind CSS setup')
|
|
18
|
+
.option('-i, --install', 'Auto install dependencies and build CSS')
|
|
19
|
+
.action(async (projectNameArg, options) => {
|
|
20
|
+
const useTailwind = options.tailwind !== false;
|
|
21
|
+
const autoInstall = options.install === true;
|
|
22
|
+
|
|
23
|
+
let projectName;
|
|
24
|
+
let projectPath;
|
|
25
|
+
let useCurrentDir = false;
|
|
26
|
+
|
|
27
|
+
if (!projectNameArg) {
|
|
28
|
+
// No project name provided - ask if they want to use current directory
|
|
29
|
+
const currentDirName = path.basename(process.cwd());
|
|
30
|
+
const currentDirFiles = fs.readdirSync(process.cwd());
|
|
31
|
+
const hasExistingFiles = currentDirFiles.some(f => !f.startsWith('.'));
|
|
32
|
+
|
|
33
|
+
const { useCurrent } = await inquirer.prompt([
|
|
34
|
+
{
|
|
35
|
+
type: 'confirm',
|
|
36
|
+
name: 'useCurrent',
|
|
37
|
+
message: `Install in current directory (${currentDirName})?`,
|
|
38
|
+
default: true
|
|
39
|
+
}
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
if (useCurrent) {
|
|
43
|
+
useCurrentDir = true;
|
|
44
|
+
projectPath = process.cwd();
|
|
45
|
+
|
|
46
|
+
// Check for existing Webspresso files
|
|
47
|
+
if (fs.existsSync(path.join(projectPath, 'server.js')) ||
|
|
48
|
+
fs.existsSync(path.join(projectPath, 'pages'))) {
|
|
49
|
+
console.error('❌ Current directory already contains a Webspresso project!');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Warn if there are existing files
|
|
54
|
+
if (hasExistingFiles) {
|
|
55
|
+
const { continueAnyway } = await inquirer.prompt([
|
|
56
|
+
{
|
|
57
|
+
type: 'confirm',
|
|
58
|
+
name: 'continueAnyway',
|
|
59
|
+
message: '⚠️ Current directory is not empty. Continue anyway?',
|
|
60
|
+
default: false
|
|
61
|
+
}
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
if (!continueAnyway) {
|
|
65
|
+
console.log('Cancelled.');
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Ask for project name (for package.json)
|
|
71
|
+
const { name } = await inquirer.prompt([
|
|
72
|
+
{
|
|
73
|
+
type: 'input',
|
|
74
|
+
name: 'name',
|
|
75
|
+
message: 'Project name:',
|
|
76
|
+
default: currentDirName,
|
|
77
|
+
validate: (input) => {
|
|
78
|
+
if (!input.trim()) return 'Project name is required';
|
|
79
|
+
if (!/^[a-z0-9-_]+$/i.test(input)) return 'Project name can only contain letters, numbers, hyphens, and underscores';
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
projectName = name;
|
|
86
|
+
} else {
|
|
87
|
+
// Ask for directory name
|
|
88
|
+
const { dirName } = await inquirer.prompt([
|
|
89
|
+
{
|
|
90
|
+
type: 'input',
|
|
91
|
+
name: 'dirName',
|
|
92
|
+
message: 'Project directory name:',
|
|
93
|
+
validate: (input) => {
|
|
94
|
+
if (!input.trim()) return 'Directory name is required';
|
|
95
|
+
if (!/^[a-z0-9-_]+$/i.test(input)) return 'Directory name can only contain letters, numbers, hyphens, and underscores';
|
|
96
|
+
if (fs.existsSync(path.resolve(input))) return `Directory ${input} already exists!`;
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
projectName = dirName;
|
|
103
|
+
projectPath = path.resolve(dirName);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
projectName = projectNameArg;
|
|
107
|
+
projectPath = path.resolve(projectNameArg);
|
|
108
|
+
|
|
109
|
+
if (fs.existsSync(projectPath)) {
|
|
110
|
+
console.error(`❌ Directory ${projectName} already exists!`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log(`\n🚀 Creating new Webspresso project: ${projectName}\n`);
|
|
116
|
+
|
|
117
|
+
// Ask about database
|
|
118
|
+
const { useDatabase, databaseType } = await inquirer.prompt([
|
|
119
|
+
{
|
|
120
|
+
type: 'confirm',
|
|
121
|
+
name: 'useDatabase',
|
|
122
|
+
message: 'Will you use a database?',
|
|
123
|
+
default: false
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
type: 'list',
|
|
127
|
+
name: 'databaseType',
|
|
128
|
+
message: 'Which database?',
|
|
129
|
+
choices: [
|
|
130
|
+
{ name: 'SQLite (better-sqlite3)', value: 'better-sqlite3' },
|
|
131
|
+
{ name: 'PostgreSQL (pg)', value: 'pg' },
|
|
132
|
+
{ name: 'MySQL (mysql2)', value: 'mysql2' },
|
|
133
|
+
{ name: 'None', value: null }
|
|
134
|
+
],
|
|
135
|
+
default: 'better-sqlite3',
|
|
136
|
+
when: (answers) => answers.useDatabase
|
|
137
|
+
}
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
// Ask about seed data if database is selected
|
|
141
|
+
let useSeed = false;
|
|
142
|
+
if (useDatabase && databaseType) {
|
|
143
|
+
const { generateSeed } = await inquirer.prompt([
|
|
144
|
+
{
|
|
145
|
+
type: 'confirm',
|
|
146
|
+
name: 'generateSeed',
|
|
147
|
+
message: 'Generate seed data based on existing models?',
|
|
148
|
+
default: false
|
|
149
|
+
}
|
|
150
|
+
]);
|
|
151
|
+
useSeed = generateSeed;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Create directory structure (skip root if using current dir)
|
|
155
|
+
if (!useCurrentDir) {
|
|
156
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
157
|
+
}
|
|
158
|
+
fs.mkdirSync(path.join(projectPath, 'pages'), { recursive: true });
|
|
159
|
+
fs.mkdirSync(path.join(projectPath, 'pages', 'locales'), { recursive: true });
|
|
160
|
+
fs.mkdirSync(path.join(projectPath, 'views'), { recursive: true });
|
|
161
|
+
fs.mkdirSync(path.join(projectPath, 'public'), { recursive: true });
|
|
162
|
+
|
|
163
|
+
// Create models directory if database is selected
|
|
164
|
+
if (useDatabase && databaseType) {
|
|
165
|
+
fs.mkdirSync(path.join(projectPath, 'models'), { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Create package.json
|
|
169
|
+
const packageJson = {
|
|
170
|
+
name: projectName,
|
|
171
|
+
version: '1.0.0',
|
|
172
|
+
description: 'Webspresso project',
|
|
173
|
+
main: 'server.js',
|
|
174
|
+
scripts: {
|
|
175
|
+
dev: 'node --watch server.js',
|
|
176
|
+
start: 'NODE_ENV=production node server.js'
|
|
177
|
+
},
|
|
178
|
+
dependencies: {
|
|
179
|
+
webspresso: '*',
|
|
180
|
+
dotenv: '^16.3.1'
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Add database driver if selected
|
|
185
|
+
if (useDatabase && databaseType) {
|
|
186
|
+
const dbDrivers = {
|
|
187
|
+
'better-sqlite3': '^9.0.0',
|
|
188
|
+
'pg': '^8.0.0',
|
|
189
|
+
'mysql2': '^3.0.0'
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
packageJson.dependencies[databaseType] = dbDrivers[databaseType];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Add faker if seed is selected
|
|
196
|
+
if (useSeed) {
|
|
197
|
+
packageJson.dependencies['@faker-js/faker'] = '^8.0.0';
|
|
198
|
+
packageJson.scripts.seed = 'node seeds/index.js';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fs.writeFileSync(
|
|
202
|
+
path.join(projectPath, 'package.json'),
|
|
203
|
+
JSON.stringify(packageJson, null, 2) + '\n'
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Create server.js
|
|
207
|
+
const serverJs = `require('dotenv').config();
|
|
208
|
+
const { createApp } = require('webspresso');
|
|
209
|
+
const path = require('path');
|
|
210
|
+
|
|
211
|
+
const { app } = createApp({
|
|
212
|
+
pagesDir: path.join(__dirname, 'pages'),
|
|
213
|
+
viewsDir: path.join(__dirname, 'views'),
|
|
214
|
+
publicDir: path.join(__dirname, 'public')
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const PORT = process.env.PORT || 3000;
|
|
218
|
+
|
|
219
|
+
app.listen(PORT, () => {
|
|
220
|
+
console.log(\`🚀 Server running at http://localhost:\${PORT}\`);
|
|
221
|
+
});
|
|
222
|
+
`;
|
|
223
|
+
|
|
224
|
+
fs.writeFileSync(path.join(projectPath, 'server.js'), serverJs);
|
|
225
|
+
|
|
226
|
+
// Create .env.example
|
|
227
|
+
let envExample = `PORT=3000
|
|
228
|
+
NODE_ENV=development
|
|
229
|
+
DEFAULT_LOCALE=en
|
|
230
|
+
SUPPORTED_LOCALES=en,tr
|
|
231
|
+
BASE_URL=http://localhost:3000
|
|
232
|
+
`;
|
|
233
|
+
|
|
234
|
+
if (useDatabase && databaseType) {
|
|
235
|
+
if (databaseType === 'better-sqlite3') {
|
|
236
|
+
envExample += `DATABASE_URL=sqlite:./database.sqlite
|
|
237
|
+
`;
|
|
238
|
+
} else if (databaseType === 'pg') {
|
|
239
|
+
envExample += `DATABASE_URL=postgresql://user:password@localhost:5432/dbname
|
|
240
|
+
`;
|
|
241
|
+
} else if (databaseType === 'mysql2') {
|
|
242
|
+
envExample += `DATABASE_URL=mysql://user:password@localhost:3306/dbname
|
|
243
|
+
`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
fs.writeFileSync(path.join(projectPath, '.env.example'), envExample);
|
|
248
|
+
|
|
249
|
+
// Create database config if database is selected
|
|
250
|
+
if (useDatabase && databaseType) {
|
|
251
|
+
let dbConfig = '';
|
|
252
|
+
|
|
253
|
+
if (databaseType === 'better-sqlite3') {
|
|
254
|
+
dbConfig = `module.exports = {
|
|
255
|
+
client: 'better-sqlite3',
|
|
256
|
+
connection: {
|
|
257
|
+
filename: process.env.DATABASE_URL?.replace('sqlite:', '') || './database.sqlite'
|
|
258
|
+
},
|
|
259
|
+
migrations: {
|
|
260
|
+
directory: './migrations',
|
|
261
|
+
tableName: 'knex_migrations',
|
|
262
|
+
},
|
|
263
|
+
useNullAsDefault: true
|
|
264
|
+
};
|
|
265
|
+
`;
|
|
266
|
+
} else if (databaseType === 'pg') {
|
|
267
|
+
dbConfig = `module.exports = {
|
|
268
|
+
client: 'pg',
|
|
269
|
+
connection: process.env.DATABASE_URL,
|
|
270
|
+
migrations: {
|
|
271
|
+
directory: './migrations',
|
|
272
|
+
tableName: 'knex_migrations',
|
|
273
|
+
},
|
|
274
|
+
pool: {
|
|
275
|
+
min: 2,
|
|
276
|
+
max: 10
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
`;
|
|
280
|
+
} else if (databaseType === 'mysql2') {
|
|
281
|
+
dbConfig = `module.exports = {
|
|
282
|
+
client: 'mysql2',
|
|
283
|
+
connection: process.env.DATABASE_URL,
|
|
284
|
+
migrations: {
|
|
285
|
+
directory: './migrations',
|
|
286
|
+
tableName: 'knex_migrations',
|
|
287
|
+
},
|
|
288
|
+
pool: {
|
|
289
|
+
min: 2,
|
|
290
|
+
max: 10
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
fs.writeFileSync(path.join(projectPath, 'webspresso.db.js'), dbConfig);
|
|
297
|
+
|
|
298
|
+
// Create migrations directory
|
|
299
|
+
fs.mkdirSync(path.join(projectPath, 'migrations'), { recursive: true });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Create seed files if seed is selected
|
|
303
|
+
if (useSeed) {
|
|
304
|
+
fs.mkdirSync(path.join(projectPath, 'seeds'), { recursive: true });
|
|
305
|
+
|
|
306
|
+
const seedIndex = getSeedFileTemplate();
|
|
307
|
+
|
|
308
|
+
fs.writeFileSync(path.join(projectPath, 'seeds', 'index.js'), seedIndex);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Create .gitignore
|
|
312
|
+
const gitignore = `node_modules/
|
|
313
|
+
.env
|
|
314
|
+
.env.local
|
|
315
|
+
.DS_Store
|
|
316
|
+
coverage/
|
|
317
|
+
*.log
|
|
318
|
+
`;
|
|
319
|
+
|
|
320
|
+
fs.writeFileSync(path.join(projectPath, '.gitignore'), gitignore);
|
|
321
|
+
|
|
322
|
+
// Create default layout
|
|
323
|
+
let layoutNjk;
|
|
324
|
+
|
|
325
|
+
if (useTailwind) {
|
|
326
|
+
layoutNjk = `<!DOCTYPE html>
|
|
327
|
+
<html lang="{{ locale or 'en' }}">
|
|
328
|
+
<head>
|
|
329
|
+
<meta charset="UTF-8">
|
|
330
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
331
|
+
<title>{{ meta.title or 'Webspresso' }}</title>
|
|
332
|
+
{% if meta.description %}
|
|
333
|
+
<meta name="description" content="{{ meta.description }}">
|
|
334
|
+
{% endif %}
|
|
335
|
+
{% if meta.canonical %}
|
|
336
|
+
<link rel="canonical" href="{{ meta.canonical }}">
|
|
337
|
+
{% else %}
|
|
338
|
+
<link rel="canonical" href="{{ fsy.canonical() }}">
|
|
339
|
+
{% endif %}
|
|
340
|
+
<link rel="stylesheet" href="/css/style.css">
|
|
341
|
+
</head>
|
|
342
|
+
<body class="min-h-screen flex flex-col bg-gray-50">
|
|
343
|
+
<nav class="bg-white shadow-sm border-b border-gray-200">
|
|
344
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
345
|
+
<div class="flex justify-between h-16">
|
|
346
|
+
<div class="flex items-center">
|
|
347
|
+
<a href="/" class="text-xl font-bold text-gray-900 hover:text-gray-700">
|
|
348
|
+
{{ t('site.name') or 'Webspresso' }}
|
|
349
|
+
</a>
|
|
350
|
+
</div>
|
|
351
|
+
<div class="flex items-center space-x-4">
|
|
352
|
+
<a href="/" class="text-gray-600 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium {% if fsy.isPath('/') %}text-blue-600 bg-blue-50{% endif %}">
|
|
353
|
+
{{ t('nav.home') or 'Home' }}
|
|
354
|
+
</a>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
</nav>
|
|
359
|
+
|
|
360
|
+
<main class="flex-1 max-w-7xl mx-auto py-6 sm:px-6 lg:px-8 w-full">
|
|
361
|
+
{% block content %}{% endblock %}
|
|
362
|
+
</main>
|
|
363
|
+
|
|
364
|
+
<footer class="bg-white border-t border-gray-200">
|
|
365
|
+
<div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
|
|
366
|
+
<p class="text-center text-sm text-gray-500">
|
|
367
|
+
{{ t('footer.copyright') or '© 2025 Webspresso. All rights reserved.' }}
|
|
368
|
+
</p>
|
|
369
|
+
</div>
|
|
370
|
+
</footer>
|
|
371
|
+
</body>
|
|
372
|
+
</html>
|
|
373
|
+
`;
|
|
374
|
+
} else {
|
|
375
|
+
layoutNjk = `<!DOCTYPE html>
|
|
376
|
+
<html lang="{{ locale or 'en' }}">
|
|
377
|
+
<head>
|
|
378
|
+
<meta charset="UTF-8">
|
|
379
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
380
|
+
<title>{{ meta.title or 'Webspresso' }}</title>
|
|
381
|
+
{% if meta.description %}
|
|
382
|
+
<meta name="description" content="{{ meta.description }}">
|
|
383
|
+
{% endif %}
|
|
384
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
385
|
+
</head>
|
|
386
|
+
<body>
|
|
387
|
+
<main>
|
|
388
|
+
{% block content %}{% endblock %}
|
|
389
|
+
</main>
|
|
390
|
+
</body>
|
|
391
|
+
</html>
|
|
392
|
+
`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
fs.writeFileSync(path.join(projectPath, 'views', 'layout.njk'), layoutNjk);
|
|
396
|
+
|
|
397
|
+
// Create index page
|
|
398
|
+
let indexNjk;
|
|
399
|
+
|
|
400
|
+
if (useTailwind) {
|
|
401
|
+
indexNjk = `{% extends "layout.njk" %}
|
|
402
|
+
|
|
403
|
+
{% block content %}
|
|
404
|
+
<div class="px-4 py-16 sm:px-6 lg:px-8">
|
|
405
|
+
<div class="text-center">
|
|
406
|
+
<h1 class="text-4xl font-bold text-gray-900 sm:text-5xl md:text-6xl">
|
|
407
|
+
{{ t('welcome') or 'Welcome to Webspresso' }}
|
|
408
|
+
</h1>
|
|
409
|
+
<p class="mt-3 max-w-md mx-auto text-base text-gray-500 sm:text-lg md:mt-5 md:text-xl md:max-w-3xl">
|
|
410
|
+
{{ t('description') or 'Start building your SSR app!' }}
|
|
411
|
+
</p>
|
|
412
|
+
<div class="mt-5 max-w-md mx-auto sm:flex sm:justify-center md:mt-8">
|
|
413
|
+
<div class="rounded-md shadow">
|
|
414
|
+
<a href="/" class="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 md:py-4 md:text-lg md:px-10">
|
|
415
|
+
Get started
|
|
416
|
+
</a>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
{% endblock %}
|
|
422
|
+
`;
|
|
423
|
+
} else {
|
|
424
|
+
indexNjk = `{% extends "layout.njk" %}
|
|
425
|
+
|
|
426
|
+
{% block content %}
|
|
427
|
+
<div>
|
|
428
|
+
<h1>{{ t('welcome') or 'Welcome to Webspresso' }}</h1>
|
|
429
|
+
<p>{{ t('description') or 'Start building your SSR app!' }}</p>
|
|
430
|
+
</div>
|
|
431
|
+
{% endblock %}
|
|
432
|
+
`;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
fs.writeFileSync(path.join(projectPath, 'pages', 'index.njk'), indexNjk);
|
|
436
|
+
|
|
437
|
+
// Create locales
|
|
438
|
+
const enJson = {
|
|
439
|
+
site: {
|
|
440
|
+
name: 'Webspresso'
|
|
441
|
+
},
|
|
442
|
+
nav: {
|
|
443
|
+
home: 'Home'
|
|
444
|
+
},
|
|
445
|
+
footer: {
|
|
446
|
+
copyright: '© 2025 Webspresso. All rights reserved.'
|
|
447
|
+
},
|
|
448
|
+
welcome: 'Welcome to Webspresso',
|
|
449
|
+
description: 'Start building your SSR app!'
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
fs.writeFileSync(
|
|
453
|
+
path.join(projectPath, 'pages', 'locales', 'en.json'),
|
|
454
|
+
JSON.stringify(enJson, null, 2) + '\n'
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
const trJson = {
|
|
458
|
+
site: {
|
|
459
|
+
name: 'Webspresso'
|
|
460
|
+
},
|
|
461
|
+
nav: {
|
|
462
|
+
home: 'Ana Sayfa'
|
|
463
|
+
},
|
|
464
|
+
footer: {
|
|
465
|
+
copyright: '© 2025 Webspresso. Tüm hakları saklıdır.'
|
|
466
|
+
},
|
|
467
|
+
welcome: 'Webspresso\'ya Hoş Geldiniz',
|
|
468
|
+
description: 'SSR uygulamanızı oluşturmaya başlayın!'
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
fs.writeFileSync(
|
|
472
|
+
path.join(projectPath, 'pages', 'locales', 'tr.json'),
|
|
473
|
+
JSON.stringify(trJson, null, 2) + '\n'
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
// Create README
|
|
477
|
+
const readme = `# ${projectName}
|
|
478
|
+
|
|
479
|
+
Webspresso project
|
|
480
|
+
|
|
481
|
+
## Getting Started
|
|
482
|
+
|
|
483
|
+
\`\`\`bash
|
|
484
|
+
npm install
|
|
485
|
+
npm run dev
|
|
486
|
+
\`\`\`
|
|
487
|
+
|
|
488
|
+
Visit http://localhost:3000
|
|
489
|
+
`;
|
|
490
|
+
|
|
491
|
+
fs.writeFileSync(path.join(projectPath, 'README.md'), readme);
|
|
492
|
+
|
|
493
|
+
// Add Tailwind if requested (default: true)
|
|
494
|
+
if (useTailwind) {
|
|
495
|
+
console.log('\n🎨 Setting up Tailwind CSS...\n');
|
|
496
|
+
|
|
497
|
+
// Create src directory
|
|
498
|
+
fs.mkdirSync(path.join(projectPath, 'src'), { recursive: true });
|
|
499
|
+
|
|
500
|
+
// Create input.css
|
|
501
|
+
const inputCss = `@tailwind base;
|
|
502
|
+
@tailwind components;
|
|
503
|
+
@tailwind utilities;
|
|
504
|
+
`;
|
|
505
|
+
fs.writeFileSync(path.join(projectPath, 'src', 'input.css'), inputCss);
|
|
506
|
+
|
|
507
|
+
// Create tailwind.config.js
|
|
508
|
+
const tailwindConfig = `/** @type {import('tailwindcss').Config} */
|
|
509
|
+
module.exports = {
|
|
510
|
+
content: [
|
|
511
|
+
'./pages/**/*.{njk,js}',
|
|
512
|
+
'./views/**/*.njk',
|
|
513
|
+
'./src/**/*.js'
|
|
514
|
+
],
|
|
515
|
+
theme: {
|
|
516
|
+
extend: {},
|
|
517
|
+
},
|
|
518
|
+
plugins: [],
|
|
519
|
+
}
|
|
520
|
+
`;
|
|
521
|
+
fs.writeFileSync(path.join(projectPath, 'tailwind.config.js'), tailwindConfig);
|
|
522
|
+
|
|
523
|
+
// Create postcss.config.js
|
|
524
|
+
const postcssConfig = `module.exports = {
|
|
525
|
+
plugins: {
|
|
526
|
+
tailwindcss: {},
|
|
527
|
+
autoprefixer: {},
|
|
528
|
+
},
|
|
529
|
+
}
|
|
530
|
+
`;
|
|
531
|
+
fs.writeFileSync(path.join(projectPath, 'postcss.config.js'), postcssConfig);
|
|
532
|
+
|
|
533
|
+
// Create public/css directory
|
|
534
|
+
fs.mkdirSync(path.join(projectPath, 'public', 'css'), { recursive: true });
|
|
535
|
+
|
|
536
|
+
// Create placeholder CSS (will be replaced by build)
|
|
537
|
+
fs.writeFileSync(path.join(projectPath, 'public', 'css', 'style.css'), '/* Run npm run build:css */\n');
|
|
538
|
+
|
|
539
|
+
// Update package.json
|
|
540
|
+
const updatedPackageJson = JSON.parse(fs.readFileSync(path.join(projectPath, 'package.json'), 'utf-8'));
|
|
541
|
+
updatedPackageJson.devDependencies = updatedPackageJson.devDependencies || {};
|
|
542
|
+
updatedPackageJson.devDependencies['tailwindcss'] = '^3.4.1';
|
|
543
|
+
updatedPackageJson.devDependencies['postcss'] = '^8.4.35';
|
|
544
|
+
updatedPackageJson.devDependencies['autoprefixer'] = '^10.4.17';
|
|
545
|
+
|
|
546
|
+
updatedPackageJson.scripts['build:css'] = 'tailwindcss -i ./src/input.css -o ./public/css/style.css --minify';
|
|
547
|
+
updatedPackageJson.scripts['watch:css'] = 'tailwindcss -i ./src/input.css -o ./public/css/style.css --watch';
|
|
548
|
+
updatedPackageJson.scripts.dev = 'npm run watch:css & node --watch server.js';
|
|
549
|
+
updatedPackageJson.scripts.start = 'npm run build:css && NODE_ENV=production node server.js';
|
|
550
|
+
|
|
551
|
+
fs.writeFileSync(
|
|
552
|
+
path.join(projectPath, 'package.json'),
|
|
553
|
+
JSON.stringify(updatedPackageJson, null, 2) + '\n'
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
console.log('✅ Tailwind CSS setup complete!');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
// Auto install if requested or ask interactively
|
|
561
|
+
if (autoInstall) {
|
|
562
|
+
await runInstallation(projectPath, useTailwind);
|
|
563
|
+
|
|
564
|
+
// Ask if user wants to start dev server
|
|
565
|
+
const { shouldStartDev } = await inquirer.prompt([
|
|
566
|
+
{
|
|
567
|
+
type: 'confirm',
|
|
568
|
+
name: 'shouldStartDev',
|
|
569
|
+
message: 'Start development server?',
|
|
570
|
+
default: true
|
|
571
|
+
}
|
|
572
|
+
]);
|
|
573
|
+
|
|
574
|
+
if (shouldStartDev) {
|
|
575
|
+
startDevServer(projectPath, useTailwind);
|
|
576
|
+
} else {
|
|
577
|
+
console.log('✅ Project ready!\n');
|
|
578
|
+
console.log('Start developing:');
|
|
579
|
+
if (!useCurrentDir) {
|
|
580
|
+
console.log(` cd ${projectName}`);
|
|
581
|
+
}
|
|
582
|
+
console.log(' npm run dev\n');
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
// Ask if user wants to install dependencies
|
|
586
|
+
const { shouldInstall } = await inquirer.prompt([
|
|
587
|
+
{
|
|
588
|
+
type: 'confirm',
|
|
589
|
+
name: 'shouldInstall',
|
|
590
|
+
message: 'Install dependencies and build CSS now?',
|
|
591
|
+
default: true
|
|
592
|
+
}
|
|
593
|
+
]);
|
|
594
|
+
|
|
595
|
+
if (shouldInstall) {
|
|
596
|
+
await runInstallation(projectPath, useTailwind);
|
|
597
|
+
|
|
598
|
+
// Ask if user wants to start dev server
|
|
599
|
+
const { shouldStartDev } = await inquirer.prompt([
|
|
600
|
+
{
|
|
601
|
+
type: 'confirm',
|
|
602
|
+
name: 'shouldStartDev',
|
|
603
|
+
message: 'Start development server?',
|
|
604
|
+
default: true
|
|
605
|
+
}
|
|
606
|
+
]);
|
|
607
|
+
|
|
608
|
+
if (shouldStartDev) {
|
|
609
|
+
startDevServer(projectPath, useTailwind);
|
|
610
|
+
} else {
|
|
611
|
+
console.log('\n✅ Project ready!\n');
|
|
612
|
+
console.log('Start developing:');
|
|
613
|
+
if (!useCurrentDir) {
|
|
614
|
+
console.log(` cd ${projectName}`);
|
|
615
|
+
}
|
|
616
|
+
console.log(' npm run dev\n');
|
|
617
|
+
}
|
|
618
|
+
} else {
|
|
619
|
+
console.log('\n✅ Project created successfully!\n');
|
|
620
|
+
console.log('Next steps:');
|
|
621
|
+
if (!useCurrentDir) {
|
|
622
|
+
console.log(` cd ${projectName}`);
|
|
623
|
+
}
|
|
624
|
+
console.log(' npm install');
|
|
625
|
+
if (useTailwind) {
|
|
626
|
+
console.log(' npm run build:css');
|
|
627
|
+
}
|
|
628
|
+
console.log(' npm run dev\n');
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
module.exports = { registerCommand };
|