webspresso 0.0.108 → 0.0.110
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/LICENSE +21 -0
- package/README.md +3 -3
- package/bin/commands/add-esbuild.js +7 -3
- package/bin/commands/add-tailwind.js +9 -5
- package/bin/commands/build.js +20 -4
- package/bin/commands/dev.js +57 -7
- package/bin/commands/doctor.js +42 -1
- package/bin/commands/favicon-generate.js +10 -3
- package/bin/commands/new.js +8 -3
- package/bin/commands/start.js +3 -1
- package/bin/utils/add-command.js +16 -0
- package/core/app.js +45 -3
- package/core/auth/authenticate.js +3 -3
- package/core/auth/hash.js +167 -43
- package/core/auth/index.js +13 -1
- package/core/auth/jwt.js +21 -2
- package/core/auth/manager.js +63 -14
- package/core/auth/middleware.js +4 -2
- package/core/auth/oauth.js +5 -1
- package/core/auth/tokens.js +7 -2
- package/core/auth/totp.js +42 -6
- package/core/auth/user-agent.js +2 -0
- package/core/client/adapter/datastar.js +1401 -0
- package/core/client/api.js +1337 -0
- package/core/client/controller.js +1706 -0
- package/core/client/debug.js +367 -0
- package/core/client/devtool.js +1134 -0
- package/core/client/forms.js +1164 -0
- package/core/client/fragments.js +894 -0
- package/core/client/index.d.ts +59 -0
- package/core/client/index.js +391 -0
- package/core/client/lite.js +175 -0
- package/core/client/server-fragments.js +868 -0
- package/core/client/storage.js +339 -0
- package/core/client/utils.js +281 -0
- package/core/compression/middleware.js +277 -26
- package/core/context/app-context.js +25 -3
- package/core/context/request.js +32 -4
- package/core/errors/middleware.js +14 -0
- package/core/events/dispatcher.js +33 -0
- package/core/forms/flash.js +136 -102
- package/core/health/middleware.js +36 -6
- package/core/http/pipeline.js +45 -6
- package/core/index.js +25 -7
- package/core/kernel/view.js +41 -10
- package/core/metrics/middleware.js +30 -6
- package/core/metrics/registry.js +13 -5
- package/core/orm/cache/fingerprint.js +32 -11
- package/core/orm/cache/layer.js +19 -2
- package/core/orm/hidden.js +145 -0
- package/core/orm/model.js +48 -2
- package/core/orm/query-builder.js +343 -6
- package/core/orm/query-filters.js +14 -2
- package/core/orm/repository.js +64 -13
- package/core/orm/schema-helpers.js +9 -6
- package/core/orm/transaction.js +50 -6
- package/core/orm/types.js +4 -0
- package/core/queue/adapters/database.js +184 -2
- package/core/queue/adapters/memory.js +153 -5
- package/core/queue/adapters/redis.js +59 -4
- package/core/queue/job.js +14 -1
- package/core/queue/manager.js +115 -0
- package/core/resilience/circuit-breaker.js +27 -8
- package/core/services/builtins/exchange.js +174 -7
- package/core/services/builtins/file-manager.js +36 -0
- package/core/services/builtins/mail.js +27 -0
- package/core/services/builtins/queue.js +144 -0
- package/core/services/builtins/security.js +118 -0
- package/core/services/builtins/webhook.js +110 -0
- package/core/services/executor.js +38 -16
- package/core/services/index.js +4 -1
- package/core/sse/stream.js +28 -5
- package/core/utils/timeout.js +8 -1
- package/core/validation/compiler.js +10 -7
- package/index.d.ts +1320 -74
- package/index.js +128 -8
- package/package.json +75 -27
- package/plugins/admin-panel/client/parts/08-record-list.js +717 -70
- package/plugins/admin-panel/index.js +19 -8
- package/plugins/csrf/index.js +3 -3
- package/plugins/data-exchange/export.js +214 -0
- package/plugins/data-exchange/import.js +48 -5
- package/plugins/data-exchange/index.js +70 -28
- package/plugins/data-exchange/preview.js +266 -0
- package/plugins/data-exchange/template.js +242 -0
- package/plugins/email/admin-component.js +783 -141
- package/plugins/email/api-handlers.js +51 -1
- package/plugins/email/index.js +5 -0
- package/plugins/email/service.js +48 -0
- package/plugins/file-manager/admin-component.js +1546 -70
- package/plugins/file-manager/api-handlers.js +166 -1
- package/plugins/file-manager/core.js +598 -8
- package/plugins/file-manager/index.js +8 -0
- package/plugins/index.js +7 -0
- package/plugins/mcp/auth.js +3 -3
- package/plugins/oauth/index.js +36 -3
- package/plugins/queue/admin-component.js +1092 -0
- package/plugins/queue/api-handlers.js +248 -0
- package/plugins/queue/index.js +69 -0
- package/plugins/rest-resources/index.js +7 -2
- package/plugins/security/admin-component.js +689 -0
- package/plugins/security/api-handlers.js +169 -0
- package/plugins/security/audit.js +153 -0
- package/plugins/security/bruteforce.js +244 -0
- package/plugins/security/firewall.js +459 -0
- package/plugins/security/headers.js +214 -0
- package/plugins/security/honeypot.js +234 -0
- package/plugins/security/incident-recorder.js +137 -0
- package/plugins/security/index.js +211 -0
- package/plugins/seo-checker/analyzer.js +20 -7
- package/plugins/upload/index.js +36 -0
- package/plugins/webhooks/admin-component.js +702 -175
- package/plugins/webhooks/api-handlers.js +75 -1
- package/plugins/webhooks/dispatcher.js +290 -11
- package/plugins/webhooks/index.js +37 -2
- package/plugins/webhooks/sender.js +56 -0
- package/plugins/xlsx/engine.js +19 -4
- package/src/api/api-loader.js +19 -1
- package/src/bundle/index.js +443 -17
- package/src/client-runtime/bootstrap-swup.js +9 -0
- package/src/client-runtime/mount.js +15 -20
- package/src/client-runtime/resolve.js +3 -2
- package/src/dev/api-typegen.js +1300 -0
- package/src/dev/banner.js +10 -1
- package/src/dev/colors.js +1 -1
- package/src/dev/index.js +7 -1
- package/src/dev/typegen.js +173 -17
- package/src/dev-reload/index.js +1 -1
- package/src/discovery/file-route-parser.js +17 -3
- package/src/discovery/scan-routes.js +4 -1
- package/src/file-router.js +253 -69
- package/src/helpers.js +46 -26
- package/src/njk-frontmatter.js +30 -4
- package/src/routing/cascading.js +26 -8
- package/src/routing/mount-discovered-routes.js +76 -0
- package/src/server.js +231 -70
- package/views/partials/webspresso-client-runtime.njk +1 -9
- package/.agents/ADMIN.md +0 -426
- package/.agents/AGENTS.md +0 -96
- package/.agents/ARCHITECTURE.md +0 -163
- package/.agents/AUTH.md +0 -581
- package/.agents/CACHE.md +0 -148
- package/.agents/CLI.md +0 -649
- package/.agents/COMPRESSION.md +0 -59
- package/.agents/CONTEXT.md +0 -152
- package/.agents/CONVENTIONS.md +0 -50
- package/.agents/EARLY_HINTS.md +0 -120
- package/.agents/EMAIL.md +0 -223
- package/.agents/ERRORS.md +0 -260
- package/.agents/EVENTS.md +0 -179
- package/.agents/FLAGS.md +0 -118
- package/.agents/HEALTH.md +0 -118
- package/.agents/LIMITS.md +0 -109
- package/.agents/LOGGING.md +0 -93
- package/.agents/MCP.md +0 -103
- package/.agents/METRICS.md +0 -127
- package/.agents/MIGRATION_JOBS.md +0 -95
- package/.agents/ORM.md +0 -700
- package/.agents/PLUGINS.md +0 -729
- package/.agents/QUEUE.md +0 -290
- package/.agents/RATE_LIMIT.md +0 -147
- package/.agents/REALTIME.md +0 -213
- package/.agents/RESILIENCE.md +0 -235
- package/.agents/ROUTING.md +0 -605
- package/.agents/SCALE_OUT.md +0 -109
- package/.agents/SECRETS.md +0 -117
- package/.agents/SERVICES.md +0 -341
- package/.agents/SHUTDOWN.md +0 -74
- package/.agents/STREAMING.md +0 -108
- package/.agents/WORKFLOW.md +0 -27
- package/.agents/internal/DOCUMENTATION_REVIEW.md +0 -485
- package/.agents/internal/TECHNICAL_REVIEW.md +0 -428
- package/.agents/skills/webspresso-admin-and-plugins/SKILL.md +0 -122
- package/.agents/skills/webspresso-framework/SKILL.md +0 -89
- package/.agents/skills/webspresso-orm-and-database/SKILL.md +0 -127
- package/.agents/skills/webspresso-routing-and-api/SKILL.md +0 -130
- package/.agents/skills/webspresso-services/SKILL.md +0 -134
- package/.cursorrules +0 -14
- package/AGENTS.md +0 -203
- package/CLAUDE.md +0 -54
- package/core/kernel/run-demo.js +0 -58
- package/plugins/admin-panel/styles.js +0 -9
- package/plugins/data-exchange/export-xlsx.js +0 -119
- package/src/client-runtime/bootstrap-alpine-swup.js +0 -34
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Webspresso Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://nodejs.org)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
A batteries-included, full-stack Node.js framework combining Next.js/Nuxt-style file-based routing, server-rendered Nunjucks streaming, a Knex-based ORM with ambient transactions,
|
|
8
|
+
A batteries-included, full-stack Node.js framework combining Next.js/Nuxt-style file-based routing, server-rendered Nunjucks streaming, a Knex-based ORM with ambient transactions, built-in native dual authentication, background queues, and a customizable Mithril.js SPA Admin Panel.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -33,10 +33,10 @@ Open **`http://localhost:7331`** in your browser.
|
|
|
33
33
|
| **Routing & SSR** | 100% file-based routing (`pages/`), method suffixes (`.get.js`, `.post.js`), nested layouts (`_layout.njk`), directory-level error boundaries (`_error.njk`), loading skeletons (`_loading.njk`), and out-of-order chunked response streaming (`res.renderStream`). |
|
|
34
34
|
| **Performance & Cold Start** | Single-pass unified route discovery, dev mode JIT on-demand route evaluation, production AOT route manifest (`webspresso build`), AOT Nunjucks template precompilation (zero AST parsing at runtime), and on-demand lazy plugin mounting. |
|
|
35
35
|
| **Database & ORM** | Built-in Knex ORM with `defineModel`, `zdb` schema builder, Repository pattern (`db.getRepository('Model')`), relations (`belongsTo`, `hasMany`), cascade soft-delete & restore, and ambient transactions via `AsyncLocalStorage`. |
|
|
36
|
-
| **Dual Auth & Sessions** | Stateful cookies for SSR + stateless HS256 JWT rotation for APIs, magic links, active device management & remote session revocation, CSRF protection, and
|
|
36
|
+
| **Dual Auth & Sessions** | Stateful cookies for SSR + stateless HS256 JWT rotation for APIs, magic links, active device management & remote session revocation, CSRF protection, and built-in native auth. |
|
|
37
37
|
| **Services & Queues** | File-based auto-discovery in `services/` with declarative Zod validation, role-based access guards, and ACID transaction propagation. Background worker queues in `jobs/`. |
|
|
38
38
|
| **Admin Panel SPA** | Integrated Mithril.js single-page admin panel mounted at `/_admin` with auto-layout wrapping, CRUD views, custom dashboard widgets, and custom field renderers. Supports on-demand lazy mounting (`adminPanelPlugin({ lazy: true })`). |
|
|
39
|
-
| **Plugin Ecosystem** | 20+ official plugins: Polar.sh billing, Webhooks dispatcher with HMAC signatures, Realtime (WebSocket/SSE), Site Analytics, API Keys, File Manager, CSV/Excel generation, On-demand Lazy Plugins (`lazyPlugin`), and more. |
|
|
39
|
+
| **Plugin Ecosystem** | 20+ official plugins: Polar.sh billing, Webhooks dispatcher with HMAC signatures, Realtime client layer (WebSocket/SSE/Socket.IO adapters), Site Analytics, API Keys, File Manager, CSV/Excel generation, On-demand Lazy Plugins (`lazyPlugin`), and more. |
|
|
40
40
|
| **DevOps & Production** | Built-in streaming zlib compression (`brotli`/`gzip`), socket connection draining & graceful shutdown, `webspresso deploy:check` production readiness auditor, and Docker scaffolding. |
|
|
41
41
|
|
|
42
42
|
---
|
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
+
const { getAddCommand } = require('../utils/add-command');
|
|
8
9
|
|
|
9
10
|
function registerCommand(program) {
|
|
10
|
-
program
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
configureCommand(program.command('add:esbuild'));
|
|
12
|
+
configureCommand(getAddCommand(program).command('esbuild'));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function configureCommand(command) {
|
|
16
|
+
command
|
|
13
17
|
.description('Add esbuild client bundling and incremental watch to the project')
|
|
14
18
|
.action(async () => {
|
|
15
19
|
if (!fs.existsSync('package.json')) {
|
|
@@ -6,11 +6,15 @@
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { execSync } = require('child_process');
|
|
9
|
+
const { getAddCommand } = require('../utils/add-command');
|
|
9
10
|
|
|
10
11
|
function registerCommand(program) {
|
|
11
|
-
program
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
configureCommand(program.command('add:tailwind'));
|
|
13
|
+
configureCommand(getAddCommand(program).command('tailwind'));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function configureCommand(command) {
|
|
17
|
+
command
|
|
14
18
|
.description('Add Tailwind CSS to the project with build process')
|
|
15
19
|
.option('--v4', 'Use Tailwind CSS v4 (Oxide engine, LightningCSS, zero postcss config)')
|
|
16
20
|
.option('--v3', 'Use Tailwind CSS v3 (legacy PostCSS engine)')
|
|
@@ -65,9 +69,9 @@ function registerCommand(program) {
|
|
|
65
69
|
packageJson.scripts.dev = 'npm run watch:css & node --watch server.js';
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
// Update start script to build CSS
|
|
72
|
+
// Update start script to build CSS (webspresso build runs Tailwind) and start cross-platform
|
|
69
73
|
if (packageJson.scripts.start) {
|
|
70
|
-
packageJson.scripts.start = '
|
|
74
|
+
packageJson.scripts.start = 'webspresso build && webspresso start';
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2) + '\n');
|
package/bin/commands/build.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { execSync } = require('child_process');
|
|
9
|
-
const { buildBundle } = require('../../src/bundle');
|
|
9
|
+
const { buildBundle, findPageEntryPoints } = require('../../src/bundle');
|
|
10
|
+
const { generateTypes, checkTemplateFormContracts } = require('../../src/dev/typegen');
|
|
10
11
|
|
|
11
12
|
function registerCommand(program) {
|
|
12
13
|
program
|
|
@@ -56,11 +57,14 @@ function registerCommand(program) {
|
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
// 2. Build JS Bundle with manifest.json (esbuild)
|
|
60
|
+
// 2. Build JS Bundle with manifest.json (esbuild or fallback)
|
|
60
61
|
if (options.js !== false) {
|
|
61
|
-
const
|
|
62
|
+
const discoveredClientPages = findPageEntryPoints(cwd);
|
|
63
|
+
const hasClientEntry =
|
|
64
|
+
fs.existsSync(path.join(cwd, 'src', 'client', 'main.js')) ||
|
|
62
65
|
fs.existsSync(path.join(cwd, 'src', 'client', 'main.ts')) ||
|
|
63
|
-
fs.existsSync(path.join(cwd, 'src', 'client', 'index.js'))
|
|
66
|
+
fs.existsSync(path.join(cwd, 'src', 'client', 'index.js')) ||
|
|
67
|
+
Object.keys(discoveredClientPages).length > 0;
|
|
64
68
|
const hasEsbuild = Boolean(
|
|
65
69
|
(packageJson.devDependencies && packageJson.devDependencies.esbuild) ||
|
|
66
70
|
(packageJson.dependencies && packageJson.dependencies.esbuild)
|
|
@@ -122,6 +126,18 @@ function registerCommand(program) {
|
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
|
|
129
|
+
// 5. Generate TypeScript & API Route Manifest (.webspresso/types.d.ts & .webspresso/api.d.ts) and check HTML form contracts
|
|
130
|
+
try {
|
|
131
|
+
generateTypes({ rootDir: cwd, silent: true });
|
|
132
|
+
const formCheck = checkTemplateFormContracts(cwd);
|
|
133
|
+
for (const w of formCheck.warnings) {
|
|
134
|
+
console.warn(`⚠️ [Form Check] ${w.message}`);
|
|
135
|
+
}
|
|
136
|
+
for (const e of formCheck.errors) {
|
|
137
|
+
console.warn(`❌ [Form Contract Mismatch] ${e.message}`);
|
|
138
|
+
}
|
|
139
|
+
} catch (_) {}
|
|
140
|
+
|
|
125
141
|
console.log('\n✨ Asset build complete!\n');
|
|
126
142
|
if (cssBuilt) console.log(' ✓ CSS: public/css/style.css');
|
|
127
143
|
if (jsBuilt) console.log(' ✓ JS: public/dist/ & manifest.json');
|
package/bin/commands/dev.js
CHANGED
|
@@ -4,24 +4,60 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
7
8
|
const { spawn } = require('child_process');
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Build node --watch arguments with additional watch paths
|
|
12
|
+
* @param {string} [projectRoot=process.cwd()]
|
|
11
13
|
* @returns {string[]} Node arguments
|
|
12
14
|
*/
|
|
13
|
-
function buildWatchArgs() {
|
|
15
|
+
function buildWatchArgs(projectRoot = process.cwd()) {
|
|
14
16
|
const args = ['--watch'];
|
|
15
17
|
|
|
16
|
-
// Add watch paths for server-side code directories
|
|
17
|
-
//
|
|
18
|
-
|
|
18
|
+
// Add watch paths for server-side code directories:
|
|
19
|
+
// - pages: File-based routing and companion loaders
|
|
20
|
+
// - models: ORM database models
|
|
21
|
+
// - services: Business logic services
|
|
22
|
+
// - jobs: Background queues and scheduled cron workers
|
|
23
|
+
// - listeners: Decoupled events and signals
|
|
24
|
+
// - modules / src/modules: Modular domain packages
|
|
25
|
+
// - plugins / src/plugins: Local plugin extensions
|
|
26
|
+
// - config: App config and credentials
|
|
27
|
+
// - migrations: DB migration definitions
|
|
28
|
+
const watchPaths = [
|
|
29
|
+
'pages',
|
|
30
|
+
'models',
|
|
31
|
+
'services',
|
|
32
|
+
'jobs',
|
|
33
|
+
'listeners',
|
|
34
|
+
'modules',
|
|
35
|
+
'plugins',
|
|
36
|
+
'config',
|
|
37
|
+
'migrations',
|
|
38
|
+
'src/models',
|
|
39
|
+
'src/services',
|
|
40
|
+
'src/jobs',
|
|
41
|
+
'src/listeners',
|
|
42
|
+
'src/modules',
|
|
43
|
+
'src/plugins',
|
|
44
|
+
];
|
|
19
45
|
|
|
20
46
|
for (const dir of watchPaths) {
|
|
21
|
-
|
|
47
|
+
const fullPath = path.isAbsolute(dir) ? dir : path.join(projectRoot, dir);
|
|
48
|
+
if (fs.existsSync(fullPath)) {
|
|
22
49
|
args.push(`--watch-path=./${dir}`);
|
|
23
50
|
}
|
|
24
51
|
}
|
|
52
|
+
|
|
53
|
+
// Also watch .env files if present
|
|
54
|
+
const envFiles = ['.env', '.env.local', '.env.development'];
|
|
55
|
+
for (const envFile of envFiles) {
|
|
56
|
+
const fullPath = path.join(projectRoot, envFile);
|
|
57
|
+
if (fs.existsSync(fullPath)) {
|
|
58
|
+
args.push(`--watch-path=./${envFile}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
25
61
|
|
|
26
62
|
args.push('server.js');
|
|
27
63
|
return args;
|
|
@@ -31,7 +67,7 @@ function registerCommand(program) {
|
|
|
31
67
|
program
|
|
32
68
|
.command('dev')
|
|
33
69
|
.description('Start development server')
|
|
34
|
-
.option('-p, --port <port>', 'Port number
|
|
70
|
+
.option('-p, --port <port>', 'Port number (default: $PORT or 7331)')
|
|
35
71
|
.option('--no-css', 'Skip CSS watch (if Tailwind is set up)')
|
|
36
72
|
.option('--no-js', 'Skip client JS watch (if esbuild / watch:js is set up)')
|
|
37
73
|
.action((options) => {
|
|
@@ -40,6 +76,7 @@ function registerCommand(program) {
|
|
|
40
76
|
process.exit(1);
|
|
41
77
|
}
|
|
42
78
|
|
|
79
|
+
options.port = options.port || process.env.PORT || '7331';
|
|
43
80
|
process.env.PORT = options.port;
|
|
44
81
|
process.env.NODE_ENV = 'development';
|
|
45
82
|
|
|
@@ -74,6 +111,19 @@ function registerCommand(program) {
|
|
|
74
111
|
if (shouldWatchCss) watchingLabels.push('CSS');
|
|
75
112
|
if (shouldWatchJs) watchingLabels.push('JS (esbuild)');
|
|
76
113
|
|
|
114
|
+
// Generate initial .webspresso/types.d.ts and .webspresso/api.d.ts and warn on dynamic/mismatched HTML form fields
|
|
115
|
+
try {
|
|
116
|
+
const { generateTypes, checkTemplateFormContracts } = require('../../src/dev/typegen');
|
|
117
|
+
generateTypes({ rootDir: process.cwd(), silent: true });
|
|
118
|
+
const formCheck = checkTemplateFormContracts(process.cwd());
|
|
119
|
+
for (const w of formCheck.warnings) {
|
|
120
|
+
console.warn(`⚠️ [Form Check] ${w.message}`);
|
|
121
|
+
}
|
|
122
|
+
for (const e of formCheck.errors) {
|
|
123
|
+
console.warn(`❌ [Form Contract Mismatch] ${e.message}`);
|
|
124
|
+
}
|
|
125
|
+
} catch (_) {}
|
|
126
|
+
|
|
77
127
|
console.log(`\n🚀 Starting development server on port ${options.port}...`);
|
|
78
128
|
console.log(` Watching: ${watchingLabels.join(', ')}\n`);
|
|
79
129
|
|
|
@@ -124,4 +174,4 @@ function registerCommand(program) {
|
|
|
124
174
|
});
|
|
125
175
|
}
|
|
126
176
|
|
|
127
|
-
module.exports = { registerCommand };
|
|
177
|
+
module.exports = { registerCommand, buildWatchArgs };
|
package/bin/commands/doctor.js
CHANGED
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { resolveDbConfigIfExists, createDbInstance } = require('../utils/db');
|
|
8
|
+
const { isClientBrowserFile } = require('../../src/discovery/file-route-parser');
|
|
9
|
+
const {
|
|
10
|
+
discoverClientControllers,
|
|
11
|
+
findDefaultEntryPoints,
|
|
12
|
+
validateClientBundleBoundaries,
|
|
13
|
+
} = require('../../src/bundle');
|
|
14
|
+
const { checkTemplateFormContracts } = require('../../src/dev/typegen');
|
|
8
15
|
|
|
9
16
|
/**
|
|
10
17
|
* @param {string} enginesNode - e.g. ">=18.0.0", "^20.1.0"
|
|
@@ -136,13 +143,17 @@ function registerCommand(program) {
|
|
|
136
143
|
return name;
|
|
137
144
|
}
|
|
138
145
|
|
|
139
|
-
const VALID_API_METHODS = [
|
|
146
|
+
const VALID_API_METHODS = [
|
|
147
|
+
'.get.js', '.post.js', '.put.js', '.patch.js', '.delete.js',
|
|
148
|
+
'.get.ts', '.post.ts', '.put.ts', '.patch.ts', '.delete.ts',
|
|
149
|
+
];
|
|
140
150
|
|
|
141
151
|
function scanRoutes(dirPath, relativeDir = '', isApi = false) {
|
|
142
152
|
const children = fs.readdirSync(dirPath);
|
|
143
153
|
const kept = [];
|
|
144
154
|
|
|
145
155
|
for (const child of children) {
|
|
156
|
+
if (isClientBrowserFile(child)) continue;
|
|
146
157
|
const fullPath = path.join(dirPath, child);
|
|
147
158
|
const relPath = relativeDir ? path.join(relativeDir, child) : child;
|
|
148
159
|
const stats = fs.statSync(fullPath);
|
|
@@ -215,6 +226,36 @@ function registerCommand(program) {
|
|
|
215
226
|
warnings += 1;
|
|
216
227
|
}
|
|
217
228
|
|
|
229
|
+
console.log('\nClient Behavior & Form Contracts');
|
|
230
|
+
console.log('--------------------------------');
|
|
231
|
+
try {
|
|
232
|
+
const controllers = discoverClientControllers(cwd);
|
|
233
|
+
const entries = findDefaultEntryPoints(cwd);
|
|
234
|
+
validateClientBundleBoundaries(entries, cwd);
|
|
235
|
+
line('✓', `Client controllers verified (${controllers.length} controller(s), zero boundary leaks or collisions)`);
|
|
236
|
+
} catch (clientErr) {
|
|
237
|
+
line('✗', clientErr.message || String(clientErr));
|
|
238
|
+
errors += 1;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const formCheck = checkTemplateFormContracts(cwd);
|
|
243
|
+
for (const w of formCheck.warnings) {
|
|
244
|
+
line('⚠', w.message);
|
|
245
|
+
warnings += 1;
|
|
246
|
+
}
|
|
247
|
+
for (const e of formCheck.errors) {
|
|
248
|
+
line('✗', e.message);
|
|
249
|
+
errors += 1;
|
|
250
|
+
}
|
|
251
|
+
if (formCheck.errors.length === 0 && formCheck.warnings.length === 0) {
|
|
252
|
+
line('✓', `Static HTML form contracts verified (${formCheck.checkedFormsCount} API form(s) checked)`);
|
|
253
|
+
}
|
|
254
|
+
} catch (formErr) {
|
|
255
|
+
line('⚠', `Form contract check skipped: ${formErr.message}`);
|
|
256
|
+
warnings += 1;
|
|
257
|
+
}
|
|
258
|
+
|
|
218
259
|
console.log('\nTailwind CSS');
|
|
219
260
|
console.log('------------');
|
|
220
261
|
const inputCssPath = path.join(cwd, 'src', 'input.css');
|
|
@@ -6,9 +6,16 @@
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
function getSharp() {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
if (process.env.NODE_ENV === 'test' || process.env.VITEST) {
|
|
10
|
+
return require('../../tests/mocks/sharp');
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
return require('sharp');
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"The 'sharp' package is required to generate favicons. Install it with: npm install sharp"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
const APPLE_SIZES = [57, 60, 72, 76, 114, 120, 144, 152, 180];
|
package/bin/commands/new.js
CHANGED
|
@@ -12,6 +12,8 @@ const inquirer = {
|
|
|
12
12
|
};
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const FRAMEWORK_VERSION = require('../../package.json').version;
|
|
15
17
|
const { runInstallation, startDevServer } = require('../utils/project');
|
|
16
18
|
const { getSeedFileTemplate } = require('../utils/seed');
|
|
17
19
|
const { scaffoldAgentsFiles } = require('./agents-init');
|
|
@@ -250,10 +252,13 @@ function registerCommand(program) {
|
|
|
250
252
|
main: 'server.js',
|
|
251
253
|
scripts: {
|
|
252
254
|
dev: 'webspresso dev',
|
|
253
|
-
|
|
255
|
+
build: 'webspresso build',
|
|
256
|
+
// `webspresso start` sets NODE_ENV=production cross-platform; build first so the
|
|
257
|
+
// AOT route manifest and precompiled templates are used in production.
|
|
258
|
+
start: 'webspresso build && webspresso start'
|
|
254
259
|
},
|
|
255
260
|
dependencies: {
|
|
256
|
-
webspresso:
|
|
261
|
+
webspresso: `^${FRAMEWORK_VERSION}`,
|
|
257
262
|
dotenv: '^16.3.1',
|
|
258
263
|
zod: '^3.23.0'
|
|
259
264
|
}
|
|
@@ -728,7 +733,7 @@ module.exports = {
|
|
|
728
733
|
}
|
|
729
734
|
|
|
730
735
|
updatedPackageJson.scripts.dev = 'webspresso dev';
|
|
731
|
-
updatedPackageJson.scripts.start = '
|
|
736
|
+
updatedPackageJson.scripts.start = 'webspresso build && webspresso start';
|
|
732
737
|
|
|
733
738
|
fs.writeFileSync(
|
|
734
739
|
path.join(projectPath, 'package.json'),
|
package/bin/commands/start.js
CHANGED
|
@@ -10,13 +10,15 @@ function registerCommand(program) {
|
|
|
10
10
|
program
|
|
11
11
|
.command('start')
|
|
12
12
|
.description('Start production server')
|
|
13
|
-
.option('-p, --port <port>', 'Port number
|
|
13
|
+
.option('-p, --port <port>', 'Port number (default: $PORT or 7331)')
|
|
14
14
|
.action((options) => {
|
|
15
15
|
if (!fs.existsSync('server.js')) {
|
|
16
16
|
console.error('❌ server.js not found! Make sure you are in a Webspresso project.');
|
|
17
17
|
process.exit(1);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// Respect the platform-provided PORT (Cloud Run, Render, Heroku...) unless --port is given
|
|
21
|
+
options.port = options.port || process.env.PORT || '7331';
|
|
20
22
|
process.env.PORT = options.port;
|
|
21
23
|
process.env.NODE_ENV = 'production';
|
|
22
24
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared `add` parent command
|
|
3
|
+
* Lets integrations register as `webspresso add <name>` subcommands without
|
|
4
|
+
* colliding with each other (each also registers its own `add:<name>` form).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function getAddCommand(program) {
|
|
8
|
+
const existing = program.commands.find((cmd) => cmd.name() === 'add');
|
|
9
|
+
if (existing) return existing;
|
|
10
|
+
|
|
11
|
+
return program
|
|
12
|
+
.command('add')
|
|
13
|
+
.description('Add an integration to the project (e.g. add tailwind, add esbuild)');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { getAddCommand };
|
package/core/app.js
CHANGED
|
@@ -176,9 +176,52 @@ class CoreApp {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
// 6.5. Background Queue & Jobs auto-discovery
|
|
180
|
+
let resolvedQueue = options.queue;
|
|
181
|
+
if (resolvedQueue === undefined) {
|
|
182
|
+
const candidateJobsDir = options.jobsDir
|
|
183
|
+
? (path.isAbsolute(options.jobsDir) ? options.jobsDir : path.join(this.rootDir, options.jobsDir))
|
|
184
|
+
: (fs.existsSync(path.join(this.rootDir, 'jobs'))
|
|
185
|
+
? path.join(this.rootDir, 'jobs')
|
|
186
|
+
: (fs.existsSync(path.join(this.rootDir, 'src/jobs'))
|
|
187
|
+
? path.join(this.rootDir, 'src/jobs')
|
|
188
|
+
: null));
|
|
189
|
+
if (candidateJobsDir && fs.existsSync(candidateJobsDir)) {
|
|
190
|
+
const { createQueueManager } = require('./queue');
|
|
191
|
+
resolvedQueue = createQueueManager({
|
|
192
|
+
jobsDir: candidateJobsDir,
|
|
193
|
+
autoStart: options.queueAutoStart !== undefined ? Boolean(options.queueAutoStart) : (process.env.NODE_ENV !== 'test'),
|
|
194
|
+
timeouts: options.timeouts,
|
|
195
|
+
adapter: options.queueAdapter,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
} else if (resolvedQueue && typeof resolvedQueue === 'object' && typeof resolvedQueue.dispatch !== 'function') {
|
|
199
|
+
const { createQueueManager } = require('./queue');
|
|
200
|
+
resolvedQueue = createQueueManager({
|
|
201
|
+
autoStart: options.queueAutoStart !== undefined ? Boolean(options.queueAutoStart) : (process.env.NODE_ENV !== 'test'),
|
|
202
|
+
timeouts: options.timeouts,
|
|
203
|
+
...resolvedQueue,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (resolvedQueue && typeof resolvedQueue !== 'boolean') {
|
|
208
|
+
this.queue = resolvedQueue;
|
|
209
|
+
if (this.shutdownManager) {
|
|
210
|
+
this.shutdownManager.registerDisposer('queue', () => {
|
|
211
|
+
if (typeof resolvedQueue.drain === 'function') {
|
|
212
|
+
return resolvedQueue.drain();
|
|
213
|
+
} else if (typeof resolvedQueue.stop === 'function') {
|
|
214
|
+
return resolvedQueue.stop();
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
this.queue = null;
|
|
220
|
+
}
|
|
221
|
+
|
|
179
222
|
// 7. Events Subsystem
|
|
180
223
|
this.events = options.events || createEventDispatcher({
|
|
181
|
-
queue:
|
|
224
|
+
queue: this.queue,
|
|
182
225
|
logger: this.logger,
|
|
183
226
|
});
|
|
184
227
|
this.shutdownManager.onShutdown(() => {
|
|
@@ -238,9 +281,8 @@ class CoreApp {
|
|
|
238
281
|
this.flags = null;
|
|
239
282
|
}
|
|
240
283
|
|
|
241
|
-
// 10. Database
|
|
284
|
+
// 10. Database
|
|
242
285
|
this.db = options.db ?? null;
|
|
243
|
-
this.queue = options.queue ?? null;
|
|
244
286
|
|
|
245
287
|
// 10. Module Services discovery (if modules directory exists)
|
|
246
288
|
this.moduleMiddlewares = {};
|
|
@@ -25,10 +25,10 @@ async function authenticateRequest(req, res, authManager) {
|
|
|
25
25
|
req.auth = authManager.createRequestAuth(req, res);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// 1. Check Bearer JWT token header first
|
|
28
|
+
// 1. Check Bearer JWT token header first (case-insensitive per RFC 6750)
|
|
29
29
|
const authHeader = req.headers?.authorization || req.headers?.Authorization;
|
|
30
|
-
if (authHeader &&
|
|
31
|
-
const token = authHeader.
|
|
30
|
+
if (authHeader && /^Bearer\s+/i.test(authHeader)) {
|
|
31
|
+
const token = authHeader.replace(/^Bearer\s+/i, '').trim();
|
|
32
32
|
try {
|
|
33
33
|
const payload = authManager.verifyJwt(token);
|
|
34
34
|
req.token = token;
|