webspresso 0.0.95 → 0.0.97
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/.agents/ADMIN.md +225 -0
- package/.agents/AGENTS.md +72 -0
- package/.agents/ARCHITECTURE.md +119 -0
- package/.agents/AUTH.md +103 -0
- package/.agents/CLI.md +115 -0
- package/.agents/COMPRESSION.md +59 -0
- package/.agents/CONVENTIONS.md +46 -0
- package/.agents/EMAIL.md +141 -0
- package/.agents/ERRORS.md +201 -0
- package/.agents/MCP.md +103 -0
- package/.agents/ORM.md +230 -0
- package/.agents/PLUGINS.md +419 -0
- package/.agents/QUEUE.md +113 -0
- package/.agents/REALTIME.md +213 -0
- package/.agents/ROUTING.md +292 -0
- package/.agents/SERVICES.md +341 -0
- package/.agents/SHUTDOWN.md +67 -0
- package/.agents/STREAMING.md +73 -0
- package/.agents/WORKFLOW.md +27 -0
- package/.agents/internal/DOCUMENTATION_REVIEW.md +485 -0
- package/.agents/internal/TECHNICAL_REVIEW.md +428 -0
- package/.agents/skills/webspresso-admin-and-plugins/SKILL.md +122 -0
- package/.agents/skills/webspresso-framework/SKILL.md +56 -0
- package/.agents/skills/webspresso-orm-and-database/SKILL.md +119 -0
- package/.agents/skills/webspresso-routing-and-api/SKILL.md +114 -0
- package/.agents/skills/webspresso-services/SKILL.md +101 -0
- package/.cursorrules +14 -0
- package/AGENTS.md +175 -0
- package/CLAUDE.md +54 -0
- package/README.md +18 -14
- package/bin/commands/admin-password.js +2 -1
- package/bin/commands/admin-setup.js +1 -1
- package/bin/commands/agents-init.js +209 -0
- package/bin/commands/api.js +114 -48
- package/bin/commands/audit-prune.js +1 -1
- package/bin/commands/doctor.js +35 -0
- package/bin/commands/favicon-generate.js +1 -1
- package/bin/commands/new.js +13 -2
- package/bin/commands/orm-map.js +5 -4
- package/bin/commands/page.js +75 -89
- package/bin/commands/polar-migrate.js +1 -1
- package/bin/commands/seed.js +1 -1
- package/bin/commands/service.js +77 -0
- package/bin/webspresso.js +2 -2
- package/core/auth/hash.js +10 -5
- package/core/compileSchema.js +1 -1
- package/core/errors/middleware.js +22 -2
- package/core/errors/normalize.js +29 -2
- package/{utils/schemaCache.js → core/schema-cache.js} +2 -3
- package/index.d.ts +306 -0
- package/index.js +35 -1
- package/package.json +36 -14
- package/plugins/admin-panel/app.js +10 -9
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +22 -1
- package/plugins/admin-panel/core/api-extensions.js +4 -0
- package/plugins/basic-auth/index.js +3 -1
- package/plugins/content/client/inline-edit.js +12 -12
- package/plugins/cors.js +6 -7
- package/plugins/csrf/index.js +3 -2
- package/plugins/csv/engine.js +447 -0
- package/plugins/csv/index.js +94 -0
- package/plugins/csv/middleware.js +80 -0
- package/plugins/csv/services.js +138 -0
- package/plugins/file-manager/admin-component.js +613 -0
- package/plugins/file-manager/api-handlers.js +261 -0
- package/plugins/file-manager/core.js +601 -0
- package/plugins/file-manager/index.js +139 -0
- package/plugins/file-manager/security.js +217 -0
- package/plugins/index.js +45 -79
- package/plugins/mcp/transports/sse.js +4 -2
- package/plugins/site-analytics/admin-component.js +7 -8
- package/plugins/xlsx/engine.js +397 -0
- package/plugins/xlsx/index.js +91 -0
- package/plugins/xlsx/middleware.js +87 -0
- package/plugins/xlsx/services.js +118 -0
- package/src/api/api-loader.js +233 -0
- package/src/api/define-api.js +35 -0
- package/src/discovery/file-route-parser.js +201 -0
- package/src/discovery/scan-routes.js +265 -0
- package/src/file-router.js +9 -1
- package/src/modules/define-module.js +99 -0
- package/src/modules/module-discovery.js +117 -0
- package/src/pages/define-page.js +36 -0
- package/src/pages/page-loader.js +214 -0
- package/src/routing/mount-discovered-routes.js +78 -0
- package/src/routing/route-table.js +191 -0
- package/src/server.js +112 -15
- package/src/services/builtins/file-manager.js +178 -0
- package/src/services/discovery.js +6 -1
- package/src/services/index.js +6 -11
- package/bin/commands/skill.js +0 -187
- package/templates/skills/webspresso-usage/REFERENCE-framework.md +0 -289
- package/templates/skills/webspresso-usage/REFERENCE-kernel.md +0 -93
- package/templates/skills/webspresso-usage/SKILL.md +0 -48
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { loadDbConfig } = require('../utils/db');
|
|
9
|
-
const { generateAdminUsersMigration } = require('../../plugins/admin-panel/migration-template');
|
|
10
9
|
|
|
11
10
|
function registerCommand(program) {
|
|
12
11
|
program
|
|
@@ -53,6 +52,7 @@ function registerCommand(program) {
|
|
|
53
52
|
const filepath = path.join(migrationDir, filename);
|
|
54
53
|
|
|
55
54
|
// Generate migration content
|
|
55
|
+
const { generateAdminUsersMigration } = require('../../plugins/admin-panel/migration-template');
|
|
56
56
|
const content = generateAdminUsersMigration();
|
|
57
57
|
|
|
58
58
|
// Write migration file
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agents Init Command
|
|
5
|
+
* Scaffold and synchronize AI Agent guidelines (.agents/, AGENTS.md, CLAUDE.md, .cursorrules)
|
|
6
|
+
* @module bin/commands/agents-init
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const SOURCE_ROOT = path.resolve(__dirname, '../../');
|
|
13
|
+
const SOURCE_AGENTS_DIR = path.join(SOURCE_ROOT, '.agents');
|
|
14
|
+
const SOURCE_AGENTS_MD = path.join(SOURCE_ROOT, 'AGENTS.md');
|
|
15
|
+
const SOURCE_CLAUDE_MD = path.join(SOURCE_ROOT, 'CLAUDE.md');
|
|
16
|
+
const SOURCE_CURSORRULES = path.join(SOURCE_ROOT, '.cursorrules');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Copies a file if force is true or if target file does not exist.
|
|
20
|
+
* @param {string} src
|
|
21
|
+
* @param {string} dest
|
|
22
|
+
* @param {boolean} force
|
|
23
|
+
* @returns {'created'|'overwritten'|'skipped'}
|
|
24
|
+
*/
|
|
25
|
+
function copyFileSafe(src, dest, force = false) {
|
|
26
|
+
if (!fs.existsSync(src)) return 'skipped';
|
|
27
|
+
const destExists = fs.existsSync(dest);
|
|
28
|
+
|
|
29
|
+
if (destExists && !force) {
|
|
30
|
+
return 'skipped';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const destDir = path.dirname(dest);
|
|
34
|
+
if (!fs.existsSync(destDir)) {
|
|
35
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fs.copyFileSync(src, dest);
|
|
39
|
+
return destExists ? 'overwritten' : 'created';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Recursively copies a directory safely.
|
|
44
|
+
* @param {string} srcDir
|
|
45
|
+
* @param {string} destDir
|
|
46
|
+
* @param {boolean} force
|
|
47
|
+
* @param {string[]} installed
|
|
48
|
+
* @param {string[]} skipped
|
|
49
|
+
* @param {string} baseRel
|
|
50
|
+
*/
|
|
51
|
+
function copyDirSafe(srcDir, destDir, force = false, installed = [], skipped = [], baseRel = '') {
|
|
52
|
+
if (!fs.existsSync(srcDir)) return;
|
|
53
|
+
if (!fs.existsSync(destDir)) {
|
|
54
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
60
|
+
const destPath = path.join(destDir, entry.name);
|
|
61
|
+
const relPath = path.join(baseRel, entry.name);
|
|
62
|
+
|
|
63
|
+
if (entry.isDirectory()) {
|
|
64
|
+
copyDirSafe(srcPath, destPath, force, installed, skipped, relPath);
|
|
65
|
+
} else {
|
|
66
|
+
const status = copyFileSafe(srcPath, destPath, force);
|
|
67
|
+
if (status === 'created' || status === 'overwritten') {
|
|
68
|
+
installed.push(relPath);
|
|
69
|
+
} else {
|
|
70
|
+
skipped.push(relPath);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Scaffolds AI Agent rules and guidebook files into target project directory.
|
|
78
|
+
* @param {string} targetDir
|
|
79
|
+
* @param {Object} [options]
|
|
80
|
+
* @param {boolean} [options.force=false]
|
|
81
|
+
* @param {boolean} [options.claude=true]
|
|
82
|
+
* @param {boolean} [options.cursor=true]
|
|
83
|
+
* @param {boolean} [options.guides=true]
|
|
84
|
+
* @returns {{ installed: string[], skipped: string[], errors: string[] }}
|
|
85
|
+
*/
|
|
86
|
+
function scaffoldAgentsFiles(targetDir, options = {}) {
|
|
87
|
+
const force = options.force === true;
|
|
88
|
+
const includeClaude = options.claude !== false;
|
|
89
|
+
const includeCursor = options.cursor !== false;
|
|
90
|
+
const includeGuides = options.guides !== false;
|
|
91
|
+
|
|
92
|
+
const result = {
|
|
93
|
+
installed: [],
|
|
94
|
+
skipped: [],
|
|
95
|
+
errors: [],
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (!fs.existsSync(targetDir)) {
|
|
99
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 1. Root AGENTS.md
|
|
103
|
+
if (fs.existsSync(SOURCE_AGENTS_MD)) {
|
|
104
|
+
const destAgentsMd = path.join(targetDir, 'AGENTS.md');
|
|
105
|
+
const status = copyFileSafe(SOURCE_AGENTS_MD, destAgentsMd, force);
|
|
106
|
+
if (status === 'created' || status === 'overwritten') {
|
|
107
|
+
result.installed.push('AGENTS.md');
|
|
108
|
+
} else {
|
|
109
|
+
result.skipped.push('AGENTS.md');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 2. CLAUDE.md
|
|
114
|
+
if (includeClaude && fs.existsSync(SOURCE_CLAUDE_MD)) {
|
|
115
|
+
const destClaudeMd = path.join(targetDir, 'CLAUDE.md');
|
|
116
|
+
const status = copyFileSafe(SOURCE_CLAUDE_MD, destClaudeMd, force);
|
|
117
|
+
if (status === 'created' || status === 'overwritten') {
|
|
118
|
+
result.installed.push('CLAUDE.md');
|
|
119
|
+
} else {
|
|
120
|
+
result.skipped.push('CLAUDE.md');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 3. .cursorrules
|
|
125
|
+
if (includeCursor && fs.existsSync(SOURCE_CURSORRULES)) {
|
|
126
|
+
const destCursor = path.join(targetDir, '.cursorrules');
|
|
127
|
+
const status = copyFileSafe(SOURCE_CURSORRULES, destCursor, force);
|
|
128
|
+
if (status === 'created' || status === 'overwritten') {
|
|
129
|
+
result.installed.push('.cursorrules');
|
|
130
|
+
} else {
|
|
131
|
+
result.skipped.push('.cursorrules');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 4. .agents/*.md Guidebook and skills/ directory
|
|
136
|
+
if (includeGuides && fs.existsSync(SOURCE_AGENTS_DIR)) {
|
|
137
|
+
const destAgentsDir = path.join(targetDir, '.agents');
|
|
138
|
+
if (!fs.existsSync(destAgentsDir)) {
|
|
139
|
+
fs.mkdirSync(destAgentsDir, { recursive: true });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const entries = fs.readdirSync(SOURCE_AGENTS_DIR, { withFileTypes: true });
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
if (entry.name === 'internal') continue; // exclude internal reviews from user projects
|
|
145
|
+
const srcPath = path.join(SOURCE_AGENTS_DIR, entry.name);
|
|
146
|
+
const destPath = path.join(destAgentsDir, entry.name);
|
|
147
|
+
const relPath = path.join('.agents', entry.name);
|
|
148
|
+
|
|
149
|
+
if (entry.isDirectory()) {
|
|
150
|
+
copyDirSafe(srcPath, destPath, force, result.installed, result.skipped, relPath);
|
|
151
|
+
} else if (entry.name.endsWith('.md')) {
|
|
152
|
+
const status = copyFileSafe(srcPath, destPath, force);
|
|
153
|
+
if (status === 'created' || status === 'overwritten') {
|
|
154
|
+
result.installed.push(relPath);
|
|
155
|
+
} else {
|
|
156
|
+
result.skipped.push(relPath);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function registerCommand(program) {
|
|
166
|
+
program
|
|
167
|
+
.command('agents:init')
|
|
168
|
+
.alias('agents')
|
|
169
|
+
.alias('agents:setup')
|
|
170
|
+
.alias('ai:init')
|
|
171
|
+
.description('Scaffold AI Agent guidelines (.agents/, AGENTS.md, CLAUDE.md, .cursorrules)')
|
|
172
|
+
.option('-p, --path <path>', 'Target project directory', process.cwd())
|
|
173
|
+
.option('-f, --force', 'Overwrite existing agent guideline files', false)
|
|
174
|
+
.option('--no-claude', 'Skip generating CLAUDE.md')
|
|
175
|
+
.option('--no-cursor', 'Skip generating .cursorrules')
|
|
176
|
+
.option('--no-guides', 'Skip generating .agents/ topic guide bundle')
|
|
177
|
+
.action(async (options) => {
|
|
178
|
+
const targetDir = path.resolve(options.path || process.cwd());
|
|
179
|
+
console.log(`\n🤖 Initializing AI Agent Guidelines in: ${targetDir}\n`);
|
|
180
|
+
|
|
181
|
+
const result = scaffoldAgentsFiles(targetDir, {
|
|
182
|
+
force: options.force,
|
|
183
|
+
claude: options.claude,
|
|
184
|
+
cursor: options.cursor,
|
|
185
|
+
guides: options.guides,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if (result.installed.length > 0) {
|
|
189
|
+
console.log('✅ Installed/Updated:');
|
|
190
|
+
for (const file of result.installed) {
|
|
191
|
+
console.log(` + ${file}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (result.skipped.length > 0) {
|
|
196
|
+
console.log('\n⚠️ Skipped (already exists, use --force to overwrite):');
|
|
197
|
+
for (const file of result.skipped) {
|
|
198
|
+
console.log(` - ${file}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log('\n🎉 AI Agent Guidelines ready! Agents (Cursor, Claude Code, Antigravity) will follow strict Webspresso standards.\n');
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = {
|
|
207
|
+
registerCommand,
|
|
208
|
+
scaffoldAgentsFiles,
|
|
209
|
+
};
|
package/bin/commands/api.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* API Command
|
|
3
|
-
* Add a new API endpoint
|
|
5
|
+
* Add a new API endpoint with modern Webspresso schema & handler contract
|
|
6
|
+
* @module bin/commands/api
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
9
|
let _inquirer;
|
|
@@ -13,64 +16,127 @@ const inquirer = {
|
|
|
13
16
|
const fs = require('fs');
|
|
14
17
|
const path = require('path');
|
|
15
18
|
|
|
19
|
+
const VALID_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
20
|
+
|
|
21
|
+
function parseRouteAndMethod(rawInput, optionMethod) {
|
|
22
|
+
let route = String(rawInput || '').trim();
|
|
23
|
+
let method = optionMethod ? String(optionMethod).toUpperCase() : null;
|
|
24
|
+
|
|
25
|
+
// Normalize /api/ prefix if present
|
|
26
|
+
route = route.replace(/^\/?api\/?/, '');
|
|
27
|
+
|
|
28
|
+
// Check if route has method suffix (e.g. notes.post or notes.post.js or [id].get)
|
|
29
|
+
for (const m of VALID_METHODS) {
|
|
30
|
+
const lower = m.toLowerCase();
|
|
31
|
+
if (route.endsWith(`.${lower}.js`)) {
|
|
32
|
+
method = m;
|
|
33
|
+
route = route.slice(0, -(lower.length + 4));
|
|
34
|
+
break;
|
|
35
|
+
} else if (route.endsWith(`.${lower}`)) {
|
|
36
|
+
method = m;
|
|
37
|
+
route = route.slice(0, -(lower.length + 1));
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
route: route.replace(/^\/+|\/+$/g, ''),
|
|
44
|
+
method: method || 'GET',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
16
48
|
function registerCommand(program) {
|
|
17
49
|
program
|
|
18
|
-
.command('api')
|
|
19
|
-
.description('Add a new API endpoint to
|
|
20
|
-
.
|
|
21
|
-
|
|
22
|
-
|
|
50
|
+
.command('api [route]')
|
|
51
|
+
.description('Add a new API endpoint to pages/api/ with schema validation and handler')
|
|
52
|
+
.option('-m, --method <method>', 'HTTP method (GET, POST, PUT, PATCH, DELETE)', 'GET')
|
|
53
|
+
.option('--auth', 'Protect with session auth middleware')
|
|
54
|
+
.option('--jwt', 'Protect with JWT bearer middleware')
|
|
55
|
+
.action(async (routeArg, options) => {
|
|
56
|
+
const pagesDir = path.resolve(process.cwd(), 'pages');
|
|
57
|
+
if (!fs.existsSync(pagesDir)) {
|
|
58
|
+
console.error('❌ Not a Webspresso project! (pages/ directory not found)');
|
|
23
59
|
process.exit(1);
|
|
24
60
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
|
|
62
|
+
let route;
|
|
63
|
+
let method;
|
|
64
|
+
|
|
65
|
+
if (routeArg) {
|
|
66
|
+
const parsed = parseRouteAndMethod(routeArg, options.method);
|
|
67
|
+
route = parsed.route;
|
|
68
|
+
method = parsed.method;
|
|
69
|
+
} else {
|
|
70
|
+
const answers = await inquirer.prompt([
|
|
71
|
+
{
|
|
72
|
+
type: 'input',
|
|
73
|
+
name: 'route',
|
|
74
|
+
message: 'API route path (e.g., users or users/[id] or /api/notes):',
|
|
75
|
+
validate: (input) => {
|
|
76
|
+
if (!input || !input.trim()) return 'API route is required';
|
|
77
|
+
return true;
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: 'list',
|
|
82
|
+
name: 'method',
|
|
83
|
+
message: 'HTTP method:',
|
|
84
|
+
choices: VALID_METHODS,
|
|
85
|
+
default: options.method ? options.method.toUpperCase() : 'GET',
|
|
86
|
+
},
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const parsed = parseRouteAndMethod(answers.route, answers.method);
|
|
90
|
+
route = parsed.route;
|
|
91
|
+
method = parsed.method;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const methodUpper = String(method).toUpperCase();
|
|
95
|
+
const methodLower = methodUpper.toLowerCase();
|
|
96
|
+
const routePath = path.join(pagesDir, 'api', route);
|
|
49
97
|
const dirPath = path.dirname(routePath);
|
|
50
98
|
const fileName = path.basename(routePath);
|
|
51
|
-
|
|
52
|
-
// Create directory
|
|
99
|
+
|
|
53
100
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
101
|
+
|
|
102
|
+
const apiFile = path.join(dirPath, `${fileName}.${methodLower}.js`);
|
|
103
|
+
|
|
104
|
+
const middlewares = [];
|
|
105
|
+
if (options.auth) middlewares.push("'auth'");
|
|
106
|
+
if (options.jwt) middlewares.push("'jwt'");
|
|
107
|
+
const middlewareStr = middlewares.length > 0 ? `\n middleware: [${middlewares.join(', ')}],` : '';
|
|
108
|
+
|
|
109
|
+
const hasParams = route.includes('[');
|
|
110
|
+
const hasBody = ['POST', 'PUT', 'PATCH'].includes(methodUpper);
|
|
111
|
+
|
|
112
|
+
const apiContent = `'use strict';
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* ${methodUpper} /api/${route}
|
|
60
116
|
*/
|
|
117
|
+
module.exports = {
|
|
118
|
+
// Input validation schema (Zod)
|
|
119
|
+
schema: ({ z }) => ({
|
|
120
|
+
${hasParams ? 'params: z.object({ id: z.string().min(1) }),\n ' : ''}${hasBody ? 'body: z.object({\n // Define body schema here\n }),\n ' : ''}query: z.object({\n // Define query params here\n }),
|
|
121
|
+
}),${middlewareStr}
|
|
61
122
|
|
|
62
|
-
|
|
63
|
-
res
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
123
|
+
// Request Handler
|
|
124
|
+
handler: async (req, res) => {
|
|
125
|
+
// req.db.getRepository('ModelName') -> Access ORM models
|
|
126
|
+
// req.service('service.name', payload) -> Invoke service
|
|
127
|
+
// req.input -> Type-safe validated body, query, params
|
|
128
|
+
return res.json({
|
|
129
|
+
success: true,
|
|
130
|
+
message: 'Hello from /api/${route}',
|
|
131
|
+
timestamp: new Date().toISOString(),
|
|
132
|
+
});
|
|
133
|
+
},
|
|
68
134
|
};
|
|
69
135
|
`;
|
|
70
|
-
|
|
71
|
-
fs.writeFileSync(apiFile, apiContent);
|
|
72
|
-
console.log(`\n✅ Created ${apiFile}\n`);
|
|
136
|
+
|
|
137
|
+
fs.writeFileSync(apiFile, apiContent, 'utf8');
|
|
138
|
+
console.log(`\n✅ Created API endpoint:\n ${path.relative(process.cwd(), apiFile)}\n`);
|
|
73
139
|
});
|
|
74
140
|
}
|
|
75
141
|
|
|
76
|
-
module.exports = { registerCommand };
|
|
142
|
+
module.exports = { registerCommand, parseRouteAndMethod };
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const { loadDbConfig, createDbInstance } = require('../utils/db');
|
|
6
|
-
const { purgeAuditLogs } = require('../../plugins/audit-log/purge');
|
|
7
6
|
|
|
8
7
|
function registerCommand(program) {
|
|
9
8
|
program
|
|
@@ -28,6 +27,7 @@ function registerCommand(program) {
|
|
|
28
27
|
const olderThan = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
29
28
|
|
|
30
29
|
try {
|
|
30
|
+
const { purgeAuditLogs } = require('../../plugins/audit-log/purge');
|
|
31
31
|
const deleted = await purgeAuditLogs(knex, {
|
|
32
32
|
tableName: options.table,
|
|
33
33
|
olderThan,
|
package/bin/commands/doctor.js
CHANGED
|
@@ -99,6 +99,23 @@ function registerCommand(program) {
|
|
|
99
99
|
warnings += 1;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// Check anti-pattern directories
|
|
103
|
+
const forbiddenRoutes = [path.join(cwd, 'routes'), path.join(cwd, 'src', 'routes')];
|
|
104
|
+
for (const p of forbiddenRoutes) {
|
|
105
|
+
if (fs.existsSync(p) && fs.statSync(p).isDirectory()) {
|
|
106
|
+
line('⚠', `Anti-pattern detected: "${path.relative(cwd, p)}" folder found. Webspresso uses 100% file-based routing; move endpoints to "pages/api/".`);
|
|
107
|
+
warnings += 1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const forbiddenControllers = [path.join(cwd, 'controllers'), path.join(cwd, 'src', 'controllers')];
|
|
112
|
+
for (const p of forbiddenControllers) {
|
|
113
|
+
if (fs.existsSync(p) && fs.statSync(p).isDirectory()) {
|
|
114
|
+
line('⚠', `Anti-pattern detected: "${path.relative(cwd, p)}" folder found. Encapsulate business logic in "services/<domain>/<action>.js" instead of controllers.`);
|
|
115
|
+
warnings += 1;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
102
119
|
const pagesPath = path.join(cwd, 'pages');
|
|
103
120
|
if (fs.existsSync(pagesPath) && fs.statSync(pagesPath).isDirectory()) {
|
|
104
121
|
line('✓', 'pages/ directory present');
|
|
@@ -119,6 +136,8 @@ function registerCommand(program) {
|
|
|
119
136
|
return name;
|
|
120
137
|
}
|
|
121
138
|
|
|
139
|
+
const VALID_API_METHODS = ['.get.js', '.post.js', '.put.js', '.patch.js', '.delete.js'];
|
|
140
|
+
|
|
122
141
|
function scanRoutes(dirPath, relativeDir = '', isApi = false) {
|
|
123
142
|
const children = fs.readdirSync(dirPath);
|
|
124
143
|
const kept = [];
|
|
@@ -148,6 +167,12 @@ function registerCommand(program) {
|
|
|
148
167
|
const ext = path.extname(child);
|
|
149
168
|
if (isApi) {
|
|
150
169
|
if (ext === '.js') {
|
|
170
|
+
const hasMethod = VALID_API_METHODS.some((m) => child.endsWith(m));
|
|
171
|
+
if (!hasMethod && !child.startsWith('_')) {
|
|
172
|
+
line('⚠', `API route "pages/${relPath}" is missing an HTTP method suffix (e.g. .get.js, .post.js).`);
|
|
173
|
+
warnings += 1;
|
|
174
|
+
routingChecksPassed = false;
|
|
175
|
+
}
|
|
151
176
|
const base = path.basename(child, ext);
|
|
152
177
|
kept.push({ name: base, isDir: false, original: child });
|
|
153
178
|
}
|
|
@@ -190,6 +215,16 @@ function registerCommand(program) {
|
|
|
190
215
|
warnings += 1;
|
|
191
216
|
}
|
|
192
217
|
|
|
218
|
+
console.log('\nAI Agent Guidelines');
|
|
219
|
+
console.log('-------------------');
|
|
220
|
+
const agentsMdPath = path.join(cwd, 'AGENTS.md');
|
|
221
|
+
const agentsDirPath = path.join(cwd, '.agents');
|
|
222
|
+
if (fs.existsSync(agentsMdPath) && fs.existsSync(agentsDirPath)) {
|
|
223
|
+
line('✓', 'AGENTS.md and .agents/ topic guides present');
|
|
224
|
+
} else {
|
|
225
|
+
line('○', 'No AGENTS.md / .agents/ found. Tip: run "npx webspresso agents:init" to scaffold AI agent guidelines.');
|
|
226
|
+
}
|
|
227
|
+
|
|
193
228
|
console.log('\nEnvironment files');
|
|
194
229
|
console.log('-----------------');
|
|
195
230
|
|
package/bin/commands/new.js
CHANGED
|
@@ -14,6 +14,7 @@ const fs = require('fs');
|
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const { runInstallation, startDevServer } = require('../utils/project');
|
|
16
16
|
const { getSeedFileTemplate } = require('../utils/seed');
|
|
17
|
+
const { scaffoldAgentsFiles } = require('./agents-init');
|
|
17
18
|
|
|
18
19
|
/** `.` / `./` mean scaffold into the current working directory */
|
|
19
20
|
function isCurrentDirAlias(name) {
|
|
@@ -45,6 +46,7 @@ function registerCommand(program) {
|
|
|
45
46
|
.description('Create a new Webspresso project')
|
|
46
47
|
.option('-t, --template <template>', 'Template to use (minimal, full)', 'minimal')
|
|
47
48
|
.option('--no-tailwind', 'Skip Tailwind CSS setup')
|
|
49
|
+
.option('--no-agents', 'Skip AI Agent guidelines setup (.agents/, AGENTS.md, CLAUDE.md, .cursorrules)')
|
|
48
50
|
.option('-i, --install', 'Auto install dependencies and build CSS')
|
|
49
51
|
.option('-y, --yes', 'Non-interactive: no database, skip install unless -i/--install, skip dev server')
|
|
50
52
|
.action(async (projectNameArg, options) => {
|
|
@@ -718,8 +720,17 @@ module.exports = {
|
|
|
718
720
|
|
|
719
721
|
console.log('✅ Tailwind CSS setup complete!');
|
|
720
722
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
+
|
|
724
|
+
// Scaffold AI Agent guidelines unless --no-agents is specified
|
|
725
|
+
if (options.agents !== false) {
|
|
726
|
+
try {
|
|
727
|
+
console.log('\n🤖 Setting up AI Agent guidelines (.agents/, AGENTS.md, CLAUDE.md, .cursorrules)...\n');
|
|
728
|
+
scaffoldAgentsFiles(projectPath, { force: true, claude: true, cursor: true, guides: true });
|
|
729
|
+
} catch (e) {
|
|
730
|
+
// Non-fatal
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
723
734
|
// Auto install if requested or ask interactively
|
|
724
735
|
if (autoInstall) {
|
|
725
736
|
await runInstallation(projectPath, useTailwind);
|
package/bin/commands/orm-map.js
CHANGED
|
@@ -6,10 +6,6 @@ const fs = require('fs');
|
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { execFileSync } = require('child_process');
|
|
9
|
-
const { getWebspressoOrmForProject } = require('../utils/resolve-webspresso-orm');
|
|
10
|
-
const { loadProjectModels } = require('../utils/orm-map-load');
|
|
11
|
-
const { buildSnapshot, buildMermaidErDiagram } = require('../utils/orm-map-snapshot');
|
|
12
|
-
const { buildOrmMapHtml, readPackageName } = require('../utils/orm-map-html');
|
|
13
9
|
|
|
14
10
|
function ensureOpenInsideTrustedRoots(absFile, cwd) {
|
|
15
11
|
if (!fs.existsSync(absFile)) {
|
|
@@ -72,6 +68,11 @@ function registerCommand(program) {
|
|
|
72
68
|
.option('-e, --env <environment>', 'Config environment', 'development')
|
|
73
69
|
.option('-m, --models <dir>', 'Models directory (overrides config default ./models)')
|
|
74
70
|
.action((options) => {
|
|
71
|
+
const { getWebspressoOrmForProject } = require('../utils/resolve-webspresso-orm');
|
|
72
|
+
const { loadProjectModels } = require('../utils/orm-map-load');
|
|
73
|
+
const { buildSnapshot, buildMermaidErDiagram } = require('../utils/orm-map-snapshot');
|
|
74
|
+
const { buildOrmMapHtml, readPackageName } = require('../utils/orm-map-html');
|
|
75
|
+
|
|
75
76
|
const cwd = process.cwd();
|
|
76
77
|
const { modelsDir, loaded, errors } = loadProjectModels(cwd, {
|
|
77
78
|
modelsOverride: options.models,
|