webspresso 0.0.13 ā 0.0.15
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 +5 -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
package/README.md
CHANGED
|
@@ -782,14 +782,10 @@ Webspresso includes a minimal, Eloquent-inspired ORM built on Knex with Zod sche
|
|
|
782
782
|
### Quick Start
|
|
783
783
|
|
|
784
784
|
```javascript
|
|
785
|
-
const {
|
|
786
|
-
const { createSchemaHelpers, defineModel, createDatabase } = require('webspresso');
|
|
785
|
+
const { zdb, defineModel, createDatabase } = require('webspresso');
|
|
787
786
|
|
|
788
|
-
// 1.
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
// 2. Define your schema with database metadata
|
|
792
|
-
const UserSchema = z.object({
|
|
787
|
+
// 1. Define your schema with database metadata
|
|
788
|
+
const UserSchema = zdb.schema({
|
|
793
789
|
id: zdb.id(),
|
|
794
790
|
email: zdb.string({ unique: true, index: true }),
|
|
795
791
|
name: zdb.string({ maxLength: 100 }),
|
|
@@ -841,6 +837,7 @@ The `zdb` helpers wrap Zod schemas with database column metadata:
|
|
|
841
837
|
| `zdb.datetime(opts)` | DATETIME column | `nullable` |
|
|
842
838
|
| `zdb.timestamp(opts)` | TIMESTAMP column | `auto: 'create'\|'update'`, `nullable` |
|
|
843
839
|
| `zdb.json(opts)` | JSON column | `nullable` |
|
|
840
|
+
| `zdb.array(itemSchema, opts)` | ARRAY column (stored as JSON) | `nullable` |
|
|
844
841
|
| `zdb.enum(values, opts)` | ENUM column | `default`, `nullable` |
|
|
845
842
|
| `zdb.foreignKey(table, opts)` | Foreign key (bigint) | `referenceColumn`, `nullable` |
|
|
846
843
|
| `zdb.foreignUuid(table, opts)` | Foreign key (uuid) | `referenceColumn`, `nullable` |
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add Tailwind Command
|
|
3
|
+
* Add Tailwind CSS to the project with build process
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
function registerCommand(program) {
|
|
11
|
+
program
|
|
12
|
+
.command('add tailwind')
|
|
13
|
+
.description('Add Tailwind CSS to the project with build process')
|
|
14
|
+
.action(async () => {
|
|
15
|
+
if (!fs.existsSync('package.json')) {
|
|
16
|
+
console.error('ā Not a Webspresso project! Run this command in your project directory.');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log('\nšØ Adding Tailwind CSS to your project...\n');
|
|
21
|
+
|
|
22
|
+
// Read package.json
|
|
23
|
+
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
|
|
24
|
+
|
|
25
|
+
// Add dev dependencies
|
|
26
|
+
if (!packageJson.devDependencies) {
|
|
27
|
+
packageJson.devDependencies = {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
packageJson.devDependencies['tailwindcss'] = '^3.4.1';
|
|
31
|
+
packageJson.devDependencies['postcss'] = '^8.4.35';
|
|
32
|
+
packageJson.devDependencies['autoprefixer'] = '^10.4.17';
|
|
33
|
+
|
|
34
|
+
// Add build scripts
|
|
35
|
+
if (!packageJson.scripts) {
|
|
36
|
+
packageJson.scripts = {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
packageJson.scripts['build:css'] = 'tailwindcss -i ./src/input.css -o ./public/css/style.css --minify';
|
|
40
|
+
packageJson.scripts['watch:css'] = 'tailwindcss -i ./src/input.css -o ./public/css/style.css --watch';
|
|
41
|
+
|
|
42
|
+
// Update dev script to include CSS watch
|
|
43
|
+
if (packageJson.scripts.dev) {
|
|
44
|
+
packageJson.scripts.dev = 'npm run watch:css & node --watch server.js';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Update start script to build CSS
|
|
48
|
+
if (packageJson.scripts.start) {
|
|
49
|
+
packageJson.scripts.start = 'npm run build:css && NODE_ENV=production node server.js';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2) + '\n');
|
|
53
|
+
console.log('ā
Updated package.json');
|
|
54
|
+
|
|
55
|
+
// Create src directory if it doesn't exist
|
|
56
|
+
if (!fs.existsSync('src')) {
|
|
57
|
+
fs.mkdirSync('src', { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Create input.css
|
|
61
|
+
const inputCss = `@tailwind base;
|
|
62
|
+
@tailwind components;
|
|
63
|
+
@tailwind utilities;
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
fs.writeFileSync('src/input.css', inputCss);
|
|
67
|
+
console.log('ā
Created src/input.css');
|
|
68
|
+
|
|
69
|
+
// Create tailwind.config.js
|
|
70
|
+
const tailwindConfig = `/** @type {import('tailwindcss').Config} */
|
|
71
|
+
module.exports = {
|
|
72
|
+
content: [
|
|
73
|
+
'./pages/**/*.{njk,js}',
|
|
74
|
+
'./views/**/*.njk',
|
|
75
|
+
'./src/**/*.js'
|
|
76
|
+
],
|
|
77
|
+
theme: {
|
|
78
|
+
extend: {},
|
|
79
|
+
},
|
|
80
|
+
plugins: [],
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
fs.writeFileSync('tailwind.config.js', tailwindConfig);
|
|
85
|
+
console.log('ā
Created tailwind.config.js');
|
|
86
|
+
|
|
87
|
+
// Create postcss.config.js
|
|
88
|
+
const postcssConfig = `module.exports = {
|
|
89
|
+
plugins: {
|
|
90
|
+
tailwindcss: {},
|
|
91
|
+
autoprefixer: {},
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
fs.writeFileSync('postcss.config.js', postcssConfig);
|
|
97
|
+
console.log('ā
Created postcss.config.js');
|
|
98
|
+
|
|
99
|
+
// Check if layout.njk exists and update it (before creating CSS)
|
|
100
|
+
const layoutPath = 'views/layout.njk';
|
|
101
|
+
if (fs.existsSync(layoutPath)) {
|
|
102
|
+
let layoutContent = fs.readFileSync(layoutPath, 'utf-8');
|
|
103
|
+
|
|
104
|
+
// Remove CDN script if exists
|
|
105
|
+
layoutContent = layoutContent.replace(
|
|
106
|
+
/<script src="https:\/\/cdn\.tailwindcss\.com"><\/script>/g,
|
|
107
|
+
''
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Add local CSS link if not exists
|
|
111
|
+
if (!layoutContent.includes('/css/style.css')) {
|
|
112
|
+
layoutContent = layoutContent.replace(
|
|
113
|
+
/(<\/head>)/,
|
|
114
|
+
' <link rel="stylesheet" href="/css/style.css">\n$1'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fs.writeFileSync(layoutPath, layoutContent);
|
|
119
|
+
console.log('ā
Updated views/layout.njk');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Create public/css directory
|
|
123
|
+
if (!fs.existsSync('public/css')) {
|
|
124
|
+
fs.mkdirSync('public/css', { recursive: true });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Create placeholder CSS
|
|
128
|
+
fs.writeFileSync('public/css/style.css', '/* Run npm run build:css */\n');
|
|
129
|
+
console.log('ā
Created public/css/style.css');
|
|
130
|
+
|
|
131
|
+
// Try to build CSS if tailwindcss is already installed
|
|
132
|
+
const tailwindBin = path.join(process.cwd(), 'node_modules', '.bin', 'tailwindcss');
|
|
133
|
+
if (fs.existsSync(tailwindBin)) {
|
|
134
|
+
try {
|
|
135
|
+
console.log('\nšØ Building Tailwind CSS from your templates...');
|
|
136
|
+
execSync('npm run build:css', { stdio: 'inherit', cwd: process.cwd() });
|
|
137
|
+
console.log('ā
Tailwind CSS built successfully!\n');
|
|
138
|
+
} catch (err) {
|
|
139
|
+
console.log('\nā ļø CSS build failed. Run "npm run build:css" manually.\n');
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
console.log('\nā
Tailwind CSS added successfully!\n');
|
|
143
|
+
console.log('Next steps:');
|
|
144
|
+
console.log(' npm install');
|
|
145
|
+
console.log(' npm run build:css');
|
|
146
|
+
console.log(' npm run dev\n');
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = { registerCommand };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Command
|
|
3
|
+
* Add a new API endpoint to the current project
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
function registerCommand(program) {
|
|
11
|
+
program
|
|
12
|
+
.command('api')
|
|
13
|
+
.description('Add a new API endpoint to the current project')
|
|
14
|
+
.action(async () => {
|
|
15
|
+
if (!fs.existsSync('pages')) {
|
|
16
|
+
console.error('ā Not a Webspresso project! Run this command in your project directory.');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const answers = await inquirer.prompt([
|
|
21
|
+
{
|
|
22
|
+
type: 'input',
|
|
23
|
+
name: 'route',
|
|
24
|
+
message: 'API route path (e.g., /api/users or /api/users/[id]):',
|
|
25
|
+
validate: (input) => {
|
|
26
|
+
if (!input.startsWith('/api/')) {
|
|
27
|
+
return 'API route must start with /api/';
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: 'list',
|
|
34
|
+
name: 'method',
|
|
35
|
+
message: 'HTTP method:',
|
|
36
|
+
choices: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
|
37
|
+
default: 'GET'
|
|
38
|
+
}
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const route = answers.route.replace(/^\/api\//, '');
|
|
42
|
+
const routePath = path.join('pages', 'api', route);
|
|
43
|
+
const dirPath = path.dirname(routePath);
|
|
44
|
+
const fileName = path.basename(routePath);
|
|
45
|
+
|
|
46
|
+
// Create directory
|
|
47
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
48
|
+
|
|
49
|
+
// Create API file
|
|
50
|
+
const apiFile = path.join(dirPath, `${fileName}.${answers.method.toLowerCase()}.js`);
|
|
51
|
+
|
|
52
|
+
const apiContent = `/**
|
|
53
|
+
* ${answers.method} ${answers.route}
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
module.exports = async function handler(req, res) {
|
|
57
|
+
res.json({
|
|
58
|
+
message: 'Hello from ${answers.route}',
|
|
59
|
+
method: '${answers.method}',
|
|
60
|
+
timestamp: new Date().toISOString()
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
fs.writeFileSync(apiFile, apiContent);
|
|
66
|
+
console.log(`\nā
Created ${apiFile}\n`);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { registerCommand };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DB Make Command
|
|
3
|
+
* Create a new migration file
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { loadDbConfig } = require('../utils/db');
|
|
9
|
+
const { getDefaultMigrationContent } = require('../utils/migration');
|
|
10
|
+
|
|
11
|
+
function registerCommand(program) {
|
|
12
|
+
program
|
|
13
|
+
.command('db:make <name>')
|
|
14
|
+
.description('Create a new migration file')
|
|
15
|
+
.option('-c, --config <path>', 'Path to database config file')
|
|
16
|
+
.option('-m, --model <model>', 'Generate migration from model (requires models directory)')
|
|
17
|
+
.action(async (name, options) => {
|
|
18
|
+
const { config, path: configPath } = loadDbConfig(options.config);
|
|
19
|
+
console.log(`\nš¦ Using config: ${configPath}\n`);
|
|
20
|
+
|
|
21
|
+
const migrationDir = config.migrations?.directory || './migrations';
|
|
22
|
+
|
|
23
|
+
// Ensure migrations directory exists
|
|
24
|
+
if (!fs.existsSync(migrationDir)) {
|
|
25
|
+
fs.mkdirSync(migrationDir, { recursive: true });
|
|
26
|
+
console.log(`Created directory: ${migrationDir}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Generate filename with timestamp
|
|
30
|
+
const now = new Date();
|
|
31
|
+
const timestamp = [
|
|
32
|
+
now.getFullYear(),
|
|
33
|
+
String(now.getMonth() + 1).padStart(2, '0'),
|
|
34
|
+
String(now.getDate()).padStart(2, '0'),
|
|
35
|
+
'_',
|
|
36
|
+
String(now.getHours()).padStart(2, '0'),
|
|
37
|
+
String(now.getMinutes()).padStart(2, '0'),
|
|
38
|
+
String(now.getSeconds()).padStart(2, '0'),
|
|
39
|
+
].join('');
|
|
40
|
+
|
|
41
|
+
const filename = `${timestamp}_${name}.js`;
|
|
42
|
+
const filepath = path.join(migrationDir, filename);
|
|
43
|
+
|
|
44
|
+
let content;
|
|
45
|
+
|
|
46
|
+
if (options.model) {
|
|
47
|
+
// Try to load model and generate migration from schema
|
|
48
|
+
const modelsDir = config.models || './models';
|
|
49
|
+
const modelPath = path.resolve(process.cwd(), modelsDir, `${options.model}.js`);
|
|
50
|
+
|
|
51
|
+
if (fs.existsSync(modelPath)) {
|
|
52
|
+
try {
|
|
53
|
+
const model = require(modelPath);
|
|
54
|
+
const { scaffoldMigration } = require('../../core/orm/migrations/scaffold');
|
|
55
|
+
content = scaffoldMigration(model);
|
|
56
|
+
console.log(`Generated migration from model: ${options.model}`);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.warn(`ā ļø Could not generate from model: ${err.message}`);
|
|
59
|
+
console.log(' Creating empty migration instead.\n');
|
|
60
|
+
content = getDefaultMigrationContent(name);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
console.warn(`ā ļø Model not found: ${modelPath}`);
|
|
64
|
+
console.log(' Creating empty migration instead.\n');
|
|
65
|
+
content = getDefaultMigrationContent(name);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
content = getDefaultMigrationContent(name);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fs.writeFileSync(filepath, content);
|
|
72
|
+
console.log(`ā
Created: ${filepath}\n`);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { registerCommand };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DB Migrate Command
|
|
3
|
+
* Run pending database migrations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { loadDbConfig, createDbInstance } = require('../utils/db');
|
|
7
|
+
|
|
8
|
+
function registerCommand(program) {
|
|
9
|
+
program
|
|
10
|
+
.command('db:migrate')
|
|
11
|
+
.description('Run pending database migrations')
|
|
12
|
+
.option('-e, --env <environment>', 'Environment (development, production)', 'development')
|
|
13
|
+
.option('-c, --config <path>', 'Path to database config file')
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
const { config, path: configPath } = loadDbConfig(options.config);
|
|
16
|
+
console.log(`\nš¦ Using config: ${configPath}`);
|
|
17
|
+
console.log(` Environment: ${options.env}\n`);
|
|
18
|
+
|
|
19
|
+
const knex = await createDbInstance(config, options.env);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const migrationConfig = config.migrations || {};
|
|
23
|
+
const [batch, migrations] = await knex.migrate.latest(migrationConfig);
|
|
24
|
+
|
|
25
|
+
if (migrations.length === 0) {
|
|
26
|
+
console.log('ā
Already up to date.\n');
|
|
27
|
+
} else {
|
|
28
|
+
console.log(`Running migrations (batch ${batch}):`);
|
|
29
|
+
for (const m of migrations) {
|
|
30
|
+
console.log(` ā ${m}`);
|
|
31
|
+
}
|
|
32
|
+
console.log(`\nā
Done. ${migrations.length} migration(s) completed.\n`);
|
|
33
|
+
}
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error('ā Migration failed:', err.message);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
} finally {
|
|
38
|
+
await knex.destroy();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { registerCommand };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DB Rollback Command
|
|
3
|
+
* Rollback the last batch of migrations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { loadDbConfig, createDbInstance } = require('../utils/db');
|
|
7
|
+
|
|
8
|
+
function registerCommand(program) {
|
|
9
|
+
program
|
|
10
|
+
.command('db:rollback')
|
|
11
|
+
.description('Rollback the last batch of migrations')
|
|
12
|
+
.option('-e, --env <environment>', 'Environment (development, production)', 'development')
|
|
13
|
+
.option('-c, --config <path>', 'Path to database config file')
|
|
14
|
+
.option('-a, --all', 'Rollback all migrations')
|
|
15
|
+
.action(async (options) => {
|
|
16
|
+
const { config, path: configPath } = loadDbConfig(options.config);
|
|
17
|
+
console.log(`\nš¦ Using config: ${configPath}`);
|
|
18
|
+
console.log(` Environment: ${options.env}\n`);
|
|
19
|
+
|
|
20
|
+
const knex = await createDbInstance(config, options.env);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const migrationConfig = {
|
|
24
|
+
...(config.migrations || {}),
|
|
25
|
+
...(options.all ? { all: true } : {}),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const [batch, migrations] = await knex.migrate.rollback(migrationConfig);
|
|
29
|
+
|
|
30
|
+
if (migrations.length === 0) {
|
|
31
|
+
console.log('ā
Nothing to rollback.\n');
|
|
32
|
+
} else {
|
|
33
|
+
console.log(`Rolling back${options.all ? ' all' : ''} migrations:`);
|
|
34
|
+
for (const m of migrations) {
|
|
35
|
+
console.log(` ā ${m}`);
|
|
36
|
+
}
|
|
37
|
+
console.log(`\nā
Done. ${migrations.length} migration(s) rolled back.\n`);
|
|
38
|
+
}
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error('ā Rollback failed:', err.message);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
} finally {
|
|
43
|
+
await knex.destroy();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { registerCommand };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DB Status Command
|
|
3
|
+
* Show migration status
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { loadDbConfig, createDbInstance } = require('../utils/db');
|
|
7
|
+
|
|
8
|
+
function registerCommand(program) {
|
|
9
|
+
program
|
|
10
|
+
.command('db:status')
|
|
11
|
+
.description('Show migration status')
|
|
12
|
+
.option('-e, --env <environment>', 'Environment (development, production)', 'development')
|
|
13
|
+
.option('-c, --config <path>', 'Path to database config file')
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
const { config, path: configPath } = loadDbConfig(options.config);
|
|
16
|
+
console.log(`\nš¦ Using config: ${configPath}`);
|
|
17
|
+
console.log(` Environment: ${options.env}\n`);
|
|
18
|
+
|
|
19
|
+
const knex = await createDbInstance(config, options.env);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const migrationConfig = config.migrations || {};
|
|
23
|
+
const [completed, pending] = await knex.migrate.list(migrationConfig);
|
|
24
|
+
|
|
25
|
+
console.log('Migration Status');
|
|
26
|
+
console.log('================\n');
|
|
27
|
+
|
|
28
|
+
// Sort all migrations by name
|
|
29
|
+
const all = [
|
|
30
|
+
...completed.map(m => ({ name: m.name || m, completed: true })),
|
|
31
|
+
...pending.map(m => ({ name: m.name || m, completed: false })),
|
|
32
|
+
].sort((a, b) => a.name.localeCompare(b.name));
|
|
33
|
+
|
|
34
|
+
if (all.length === 0) {
|
|
35
|
+
console.log(' No migrations found.\n');
|
|
36
|
+
} else {
|
|
37
|
+
for (const m of all) {
|
|
38
|
+
const status = m.completed ? 'ā' : 'ā';
|
|
39
|
+
const suffix = m.completed ? '' : ' (pending)';
|
|
40
|
+
console.log(` ${status} ${m.name}${suffix}`);
|
|
41
|
+
}
|
|
42
|
+
console.log(`\n Total: ${all.length} (${completed.length} completed, ${pending.length} pending)\n`);
|
|
43
|
+
}
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error('ā Failed to get status:', err.message);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
} finally {
|
|
48
|
+
await knex.destroy();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { registerCommand };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev Command
|
|
3
|
+
* Start development server
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const { spawn } = require('child_process');
|
|
8
|
+
|
|
9
|
+
function registerCommand(program) {
|
|
10
|
+
program
|
|
11
|
+
.command('dev')
|
|
12
|
+
.description('Start development server')
|
|
13
|
+
.option('-p, --port <port>', 'Port number', '3000')
|
|
14
|
+
.option('--no-css', 'Skip CSS watch (if Tailwind is set up)')
|
|
15
|
+
.action((options) => {
|
|
16
|
+
if (!fs.existsSync('server.js')) {
|
|
17
|
+
console.error('ā server.js not found! Make sure you are in a Webspresso project.');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
process.env.PORT = options.port;
|
|
22
|
+
process.env.NODE_ENV = 'development';
|
|
23
|
+
|
|
24
|
+
const hasTailwind = fs.existsSync('tailwind.config.js') && fs.existsSync('src/input.css');
|
|
25
|
+
const shouldWatchCss = hasTailwind && options.css !== false;
|
|
26
|
+
|
|
27
|
+
if (shouldWatchCss) {
|
|
28
|
+
console.log(`\nš Starting development server on port ${options.port}...`);
|
|
29
|
+
console.log(' Watching CSS and server files...\n');
|
|
30
|
+
|
|
31
|
+
// Start CSS watch
|
|
32
|
+
const cssWatch = spawn('npm', ['run', 'watch:css'], {
|
|
33
|
+
stdio: 'inherit',
|
|
34
|
+
shell: true
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Start server
|
|
38
|
+
const server = spawn('node', ['--watch', 'server.js'], {
|
|
39
|
+
stdio: 'inherit',
|
|
40
|
+
shell: true,
|
|
41
|
+
env: { ...process.env, PORT: options.port, NODE_ENV: 'development' }
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Handle exit
|
|
45
|
+
const cleanup = () => {
|
|
46
|
+
cssWatch.kill();
|
|
47
|
+
server.kill();
|
|
48
|
+
process.exit(0);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
process.on('SIGINT', cleanup);
|
|
52
|
+
process.on('SIGTERM', cleanup);
|
|
53
|
+
|
|
54
|
+
cssWatch.on('exit', cleanup);
|
|
55
|
+
server.on('exit', cleanup);
|
|
56
|
+
} else {
|
|
57
|
+
console.log(`\nš Starting development server on port ${options.port}...\n`);
|
|
58
|
+
|
|
59
|
+
const { spawn } = require('child_process');
|
|
60
|
+
const child = spawn('node', ['--watch', 'server.js'], {
|
|
61
|
+
stdio: 'inherit',
|
|
62
|
+
shell: true,
|
|
63
|
+
env: { ...process.env, PORT: options.port, NODE_ENV: 'development' }
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
child.on('exit', (code) => {
|
|
67
|
+
process.exit(code || 0);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { registerCommand };
|