webspresso 0.0.97 → 0.0.98
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 +153 -5
- package/.agents/ARCHITECTURE.md +10 -0
- package/.agents/CLI.md +195 -1
- package/.agents/PLUGINS.md +153 -9
- package/.agents/QUEUE.md +43 -0
- package/bin/commands/add-esbuild.js +80 -0
- package/bin/commands/add-tailwind.js +76 -31
- package/bin/commands/analytics-prune.js +57 -0
- package/bin/commands/api.js +207 -15
- package/bin/commands/build.js +99 -0
- package/bin/commands/dev.js +64 -35
- package/bin/commands/doctor.js +26 -0
- package/bin/commands/email-prune.js +47 -0
- package/bin/commands/make-job.js +86 -0
- package/bin/commands/make-model.js +250 -0
- package/bin/commands/new.js +45 -28
- package/bin/commands/openapi.js +102 -0
- package/bin/commands/page.js +6 -3
- package/bin/commands/queue-prune.js +59 -0
- package/bin/commands/routes.js +52 -0
- package/bin/commands/service.js +6 -3
- package/bin/commands/typegen.js +106 -0
- package/bin/commands/upload-prune.js +79 -0
- package/bin/webspresso.js +22 -0
- package/core/applySchema.js +13 -6
- package/core/auth/hash.js +21 -12
- package/core/compression/middleware.js +23 -17
- package/core/openapi/build-from-api-routes.js +160 -29
- package/core/orm/cache/fingerprint.js +15 -1
- package/core/orm/cache/memory-provider.js +27 -2
- package/core/orm/index.js +12 -7
- package/core/orm/model.js +49 -7
- package/core/queue/adapters/database.js +94 -0
- package/core/queue/adapters/memory.js +62 -0
- package/core/queue/adapters/redis.js +80 -0
- package/core/queue/manager.js +123 -0
- package/core/ssr/stream.js +11 -0
- package/index.d.ts +660 -10
- package/index.js +44 -96
- package/package.json +1 -1
- package/plugins/admin-panel/api.js +279 -17
- package/plugins/admin-panel/app.js +114 -22
- package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +314 -9
- package/plugins/admin-panel/client/parts/04-field-renderers.js +264 -67
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +10 -5
- package/plugins/admin-panel/client/parts/06-login-setup-forms.js +119 -28
- package/plugins/admin-panel/client/parts/07-model-list.js +49 -8
- package/plugins/admin-panel/client/parts/08-record-list.js +243 -23
- package/plugins/admin-panel/client/parts/09-record-form.js +59 -12
- package/plugins/admin-panel/client/parts/10-export-registry.js +23 -0
- package/plugins/admin-panel/core/admin-module.js +13 -7
- package/plugins/admin-panel/core/api-extensions.js +30 -2
- package/plugins/admin-panel/core/registry.js +96 -16
- package/plugins/admin-panel/index.js +96 -0
- package/plugins/admin-panel/lib/login-rate-limiter.js +119 -0
- package/plugins/admin-panel/modules/admin-users.js +16 -1
- package/plugins/admin-panel/modules/bulk-actions.js +13 -5
- package/plugins/admin-panel/modules/custom-pages.js +492 -3
- package/plugins/admin-panel/modules/dashboard.js +283 -0
- package/plugins/admin-panel/modules/menu.js +56 -18
- package/plugins/admin-panel/modules/user-management.js +24 -6
- package/plugins/audit-log/admin-component.js +11 -0
- package/plugins/audit-log/api-handlers.js +34 -0
- package/plugins/audit-log/index.js +129 -3
- package/plugins/audit-log/purge.js +60 -10
- package/plugins/dashboard/app.js +152 -27
- package/plugins/dashboard/index.js +143 -39
- package/plugins/dashboard/styles.js +78 -0
- package/plugins/data-exchange/export-xlsx.js +9 -2
- package/plugins/data-exchange/import.js +9 -2
- package/plugins/data-exchange/index.js +6 -3
- package/plugins/email/index.js +132 -2
- package/plugins/email/purge.js +60 -10
- package/plugins/index.js +4 -0
- package/plugins/orm-cache-admin/index.js +4 -2
- package/plugins/queue/index.js +11 -0
- package/plugins/schema-explorer/admin-component.js +714 -0
- package/plugins/schema-explorer/relations-graph.js +205 -0
- package/plugins/schema-explorer.js +248 -69
- package/plugins/site-analytics/admin-component.js +23 -0
- package/plugins/site-analytics/api-handlers.js +42 -0
- package/plugins/site-analytics/index.js +147 -2
- package/plugins/site-analytics/purge.js +168 -0
- package/plugins/swagger.js +261 -13
- package/plugins/upload/index.js +26 -0
- package/plugins/upload/orphan-pruner.js +292 -0
- package/src/api/api-loader.js +3 -1
- package/src/bundle/index.js +517 -0
- package/src/bundle/manifest.js +165 -0
- package/src/dev/banner.js +296 -0
- package/src/dev/colors.js +70 -0
- package/src/dev/index.js +62 -0
- package/src/dev/profiler.js +354 -0
- package/src/dev/typegen.js +497 -0
- package/src/dev-reload/index.js +611 -0
- package/src/file-router.js +210 -62
- package/src/helpers.js +958 -98
- package/src/njk-frontmatter.js +12 -0
- package/src/pages/page-loader.js +61 -11
- package/src/server.js +629 -63
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Webspresso Development Startup Banner & Route Table Formatter
|
|
5
|
+
* Beautiful terminal dashboard with zero external dependencies
|
|
6
|
+
* @module src/dev/banner
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { colors: defaultColors, stripAnsi } = require('./colors');
|
|
10
|
+
|
|
11
|
+
let pkgVersion = '1.0.0';
|
|
12
|
+
try {
|
|
13
|
+
const pkg = require('../../package.json');
|
|
14
|
+
if (pkg && pkg.version) pkgVersion = pkg.version;
|
|
15
|
+
} catch (_) {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Formats a clean, readable ASCII table of registered application routes
|
|
19
|
+
* @param {import('../routing/route-table').RouteTable|Array} routeTable
|
|
20
|
+
* @param {Object} [options]
|
|
21
|
+
* @param {boolean} [options.colors=true]
|
|
22
|
+
* @param {number} [options.limit=100]
|
|
23
|
+
* @returns {string} Formatted route table
|
|
24
|
+
*/
|
|
25
|
+
function formatRouteTable(routeTable, options = {}) {
|
|
26
|
+
const useColors = options.colors !== false;
|
|
27
|
+
const c = useColors ? defaultColors : {
|
|
28
|
+
cyan: (s) => s,
|
|
29
|
+
green: (s) => s,
|
|
30
|
+
yellow: (s) => s,
|
|
31
|
+
red: (s) => s,
|
|
32
|
+
magenta: (s) => s,
|
|
33
|
+
white: (s) => s,
|
|
34
|
+
gray: (s) => s,
|
|
35
|
+
bold: (s) => s,
|
|
36
|
+
dim: (s) => s,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const rawRoutes = Array.isArray(routeTable)
|
|
40
|
+
? routeTable
|
|
41
|
+
: (routeTable && typeof routeTable.list === 'function'
|
|
42
|
+
? routeTable.list()
|
|
43
|
+
: (routeTable && routeTable.routes ? routeTable.routes : []));
|
|
44
|
+
|
|
45
|
+
if (!rawRoutes || rawRoutes.length === 0) {
|
|
46
|
+
return c.gray(' (no routes registered)');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Sort: API first or alphabetical by path
|
|
50
|
+
const sorted = [...rawRoutes].sort((a, b) => {
|
|
51
|
+
const pa = a.path || '';
|
|
52
|
+
const pb = b.path || '';
|
|
53
|
+
return pa.localeCompare(pb) || (a.method || '').localeCompare(b.method || '');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const limit = options.limit || 100;
|
|
57
|
+
const visible = sorted.slice(0, limit);
|
|
58
|
+
|
|
59
|
+
const lines = [];
|
|
60
|
+
lines.push(` ${c.bold('🛣️ Discovered Routes')} ${c.gray(`(${sorted.length} total)`)}:`);
|
|
61
|
+
|
|
62
|
+
for (const r of visible) {
|
|
63
|
+
const m = (r.method || 'GET').toUpperCase();
|
|
64
|
+
let mCol = m.padEnd(7);
|
|
65
|
+
if (m === 'GET') mCol = c.cyan(mCol);
|
|
66
|
+
else if (m === 'POST') mCol = c.green(mCol);
|
|
67
|
+
else if (m === 'PUT' || m === 'PATCH') mCol = c.yellow(mCol);
|
|
68
|
+
else if (m === 'DELETE') mCol = c.red(mCol);
|
|
69
|
+
else mCol = c.white(mCol);
|
|
70
|
+
|
|
71
|
+
const p = (r.path || '/').padEnd(28);
|
|
72
|
+
const typeLabel = r.type === 'api' ? c.magenta('[API] ') : c.gray('[SSR] ');
|
|
73
|
+
const source = r.source || r.file || '';
|
|
74
|
+
const cleanSource = source ? c.dim(`→ ${source.replace(/\\/g, '/')}`) : '';
|
|
75
|
+
|
|
76
|
+
lines.push(` ${mCol} ${c.bold(p)} ${typeLabel} ${cleanSource}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (sorted.length > limit) {
|
|
80
|
+
lines.push(c.gray(` ... and ${sorted.length - limit} more routes (use webspresso routes to list all)`));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return lines.join('\n');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Gathers system and application summary metrics for banner display
|
|
88
|
+
* @param {Object} context
|
|
89
|
+
* @returns {Object}
|
|
90
|
+
*/
|
|
91
|
+
function collectBannerSummary(context = {}) {
|
|
92
|
+
const {
|
|
93
|
+
routeTable,
|
|
94
|
+
db,
|
|
95
|
+
serviceRegistry,
|
|
96
|
+
pluginManager,
|
|
97
|
+
port = 3000,
|
|
98
|
+
host = 'localhost',
|
|
99
|
+
} = context;
|
|
100
|
+
|
|
101
|
+
// 1. Routes summary
|
|
102
|
+
const routes = Array.isArray(routeTable)
|
|
103
|
+
? routeTable
|
|
104
|
+
: (routeTable && typeof routeTable.list === 'function' ? routeTable.list() : (routeTable?.routes || []));
|
|
105
|
+
const apiRoutes = routes.filter((r) => r.type === 'api' || (r.path && r.path.startsWith('/api')));
|
|
106
|
+
const pageRoutes = routes.filter((r) => r.type === 'page' || !apiRoutes.includes(r));
|
|
107
|
+
|
|
108
|
+
// 2. Database client
|
|
109
|
+
let dbDialect = 'None';
|
|
110
|
+
if (db && db.knex) {
|
|
111
|
+
const client = db.knex.client;
|
|
112
|
+
const clientName = client?.config?.client || client?.dialect || 'Connected';
|
|
113
|
+
dbDialect = String(clientName);
|
|
114
|
+
if (dbDialect === 'better-sqlite3' || dbDialect === 'sqlite3') {
|
|
115
|
+
dbDialect = 'SQLite (' + dbDialect + ')';
|
|
116
|
+
} else if (dbDialect === 'pg') {
|
|
117
|
+
dbDialect = 'PostgreSQL';
|
|
118
|
+
} else if (dbDialect === 'mysql' || dbDialect === 'mysql2') {
|
|
119
|
+
dbDialect = 'MySQL';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 3. Models
|
|
124
|
+
let modelsList = [];
|
|
125
|
+
if (db && typeof db.getAllModels === 'function') {
|
|
126
|
+
try {
|
|
127
|
+
modelsList = db.getAllModels().map((m) => (typeof m === 'object' ? m.name : m));
|
|
128
|
+
} catch (_) {}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 4. Services
|
|
132
|
+
let servicesList = [];
|
|
133
|
+
if (serviceRegistry) {
|
|
134
|
+
if (serviceRegistry.services instanceof Map) {
|
|
135
|
+
servicesList = Array.from(serviceRegistry.services.keys());
|
|
136
|
+
} else if (typeof serviceRegistry.list === 'function') {
|
|
137
|
+
servicesList = serviceRegistry.list().map((s) => (typeof s === 'object' ? s.name : s));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 5. Plugins
|
|
142
|
+
let pluginsList = [];
|
|
143
|
+
let hasAdmin = false;
|
|
144
|
+
if (pluginManager && pluginManager.plugins instanceof Map) {
|
|
145
|
+
pluginsList = Array.from(pluginManager.plugins.keys());
|
|
146
|
+
hasAdmin = pluginManager.plugins.has('adminPanel') || pluginManager.plugins.has('admin');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// URLs
|
|
150
|
+
const baseUrl = `http://${host}:${port}`;
|
|
151
|
+
const adminUrl = hasAdmin ? `${baseUrl}/_admin` : null;
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
version: pkgVersion,
|
|
155
|
+
nodeVersion: process.version,
|
|
156
|
+
port,
|
|
157
|
+
host,
|
|
158
|
+
urls: {
|
|
159
|
+
local: baseUrl,
|
|
160
|
+
admin: adminUrl,
|
|
161
|
+
},
|
|
162
|
+
counts: {
|
|
163
|
+
routesTotal: routes.length,
|
|
164
|
+
apiRoutes: apiRoutes.length,
|
|
165
|
+
pageRoutes: pageRoutes.length,
|
|
166
|
+
models: modelsList.length,
|
|
167
|
+
services: servicesList.length,
|
|
168
|
+
plugins: pluginsList.length,
|
|
169
|
+
},
|
|
170
|
+
dbDialect,
|
|
171
|
+
models: modelsList,
|
|
172
|
+
services: servicesList,
|
|
173
|
+
plugins: pluginsList,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Formats the full development startup banner with frame, URLs, and summaries
|
|
179
|
+
* @param {Object} context
|
|
180
|
+
* @param {Object} [options]
|
|
181
|
+
* @returns {{ bannerText: string, plainText: string, summary: Object }}
|
|
182
|
+
*/
|
|
183
|
+
function formatDevBanner(context = {}, options = {}) {
|
|
184
|
+
const useColors = options.colors !== false;
|
|
185
|
+
const c = useColors ? defaultColors : {
|
|
186
|
+
cyan: (s) => s,
|
|
187
|
+
green: (s) => s,
|
|
188
|
+
yellow: (s) => s,
|
|
189
|
+
red: (s) => s,
|
|
190
|
+
magenta: (s) => s,
|
|
191
|
+
white: (s) => s,
|
|
192
|
+
gray: (s) => s,
|
|
193
|
+
bold: (s) => s,
|
|
194
|
+
dim: (s) => s,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const summary = collectBannerSummary(context);
|
|
198
|
+
const showRouteTable = options.showRouteTable ?? false;
|
|
199
|
+
|
|
200
|
+
const width = 64;
|
|
201
|
+
const topBorder = '┌' + '─'.repeat(width) + '┐';
|
|
202
|
+
const bottomBorder = '└' + '─'.repeat(width) + '┘';
|
|
203
|
+
|
|
204
|
+
function padLine(content, rawLength) {
|
|
205
|
+
const pad = Math.max(0, width - rawLength);
|
|
206
|
+
return '│ ' + content + ' '.repeat(pad > 2 ? pad - 2 : 0) + '│';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const lines = [];
|
|
210
|
+
lines.push('');
|
|
211
|
+
lines.push(c.cyan(topBorder));
|
|
212
|
+
|
|
213
|
+
// Header line
|
|
214
|
+
const title = `☕ ${c.bold('Webspresso')} ${c.dim(`v${summary.version}`)}`;
|
|
215
|
+
const nodeTag = c.gray(`Node ${summary.nodeVersion}`);
|
|
216
|
+
const rawHeader = `☕ Webspresso v${summary.version} Node ${summary.nodeVersion}`;
|
|
217
|
+
const spaceCount = Math.max(2, width - 4 - rawHeader.length);
|
|
218
|
+
lines.push('│ ' + title + ' '.repeat(spaceCount) + nodeTag + ' │');
|
|
219
|
+
|
|
220
|
+
lines.push('│' + ' '.repeat(width) + '│');
|
|
221
|
+
|
|
222
|
+
// URLs
|
|
223
|
+
const localStr = `${c.green('➜')} ${c.bold('Local:')} ${c.cyan(summary.urls.local)}`;
|
|
224
|
+
lines.push(padLine(localStr, `➜ Local: ${summary.urls.local}`.length));
|
|
225
|
+
|
|
226
|
+
if (summary.urls.admin) {
|
|
227
|
+
const adminStr = `${c.green('➜')} ${c.bold('Admin:')} ${c.cyan(summary.urls.admin)}`;
|
|
228
|
+
lines.push(padLine(adminStr, `➜ Admin: ${summary.urls.admin}`.length));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const dbStr = `${c.gray('•')} ${c.bold('Database:')} ${summary.dbDialect}`;
|
|
232
|
+
lines.push(padLine(dbStr, `• Database: ${summary.dbDialect}`.length));
|
|
233
|
+
|
|
234
|
+
lines.push('│' + ' '.repeat(width) + '│');
|
|
235
|
+
|
|
236
|
+
// Summary header
|
|
237
|
+
const sumHdr = c.bold('📦 Summary:');
|
|
238
|
+
lines.push(padLine(sumHdr, '📦 Summary:'.length));
|
|
239
|
+
|
|
240
|
+
// Route counts
|
|
241
|
+
const routeCountStr = ` ${c.gray('•')} Routes: ${c.bold(String(summary.counts.routesTotal))} ${c.gray(`(${summary.counts.apiRoutes} API, ${summary.counts.pageRoutes} Pages)`)}`;
|
|
242
|
+
lines.push(padLine(routeCountStr, ` • Routes: ${summary.counts.routesTotal} (${summary.counts.apiRoutes} API, ${summary.counts.pageRoutes} Pages)`.length));
|
|
243
|
+
|
|
244
|
+
// Models
|
|
245
|
+
const modelsPreview = summary.models.length > 0 ? ` ${c.gray(`(${summary.models.slice(0, 3).join(', ')}${summary.models.length > 3 ? '...' : ''})`)}` : '';
|
|
246
|
+
const modelsPlain = summary.models.length > 0 ? ` (${summary.models.slice(0, 3).join(', ')}${summary.models.length > 3 ? '...' : ''})` : '';
|
|
247
|
+
const modelStr = ` ${c.gray('•')} Models: ${c.bold(String(summary.counts.models))}${modelsPreview}`;
|
|
248
|
+
lines.push(padLine(modelStr, ` • Models: ${summary.counts.models}${modelsPlain}`.length));
|
|
249
|
+
|
|
250
|
+
// Services
|
|
251
|
+
const servicesPreview = summary.services.length > 0 ? ` ${c.gray(`(${summary.services.slice(0, 3).join(', ')}${summary.services.length > 3 ? '...' : ''})`)}` : '';
|
|
252
|
+
const servicesPlain = summary.services.length > 0 ? ` (${summary.services.slice(0, 3).join(', ')}${summary.services.length > 3 ? '...' : ''})` : '';
|
|
253
|
+
const servStr = ` ${c.gray('•')} Services: ${c.bold(String(summary.counts.services))}${servicesPreview}`;
|
|
254
|
+
lines.push(padLine(servStr, ` • Services: ${summary.counts.services}${servicesPlain}`.length));
|
|
255
|
+
|
|
256
|
+
// Plugins
|
|
257
|
+
const pluginsPreview = summary.plugins.length > 0 ? ` ${c.gray(`(${summary.plugins.slice(0, 4).join(', ')}${summary.plugins.length > 4 ? '...' : ''})`)}` : '';
|
|
258
|
+
const pluginsPlain = summary.plugins.length > 0 ? ` (${summary.plugins.slice(0, 4).join(', ')}${summary.plugins.length > 4 ? '...' : ''})` : '';
|
|
259
|
+
const plugStr = ` ${c.gray('•')} Plugins: ${c.bold(String(summary.counts.plugins))}${pluginsPreview}`;
|
|
260
|
+
lines.push(padLine(plugStr, ` • Plugins: ${summary.counts.plugins}${pluginsPlain}`.length));
|
|
261
|
+
|
|
262
|
+
lines.push(c.cyan(bottomBorder));
|
|
263
|
+
lines.push('');
|
|
264
|
+
|
|
265
|
+
// Route Table append
|
|
266
|
+
if (showRouteTable && context.routeTable) {
|
|
267
|
+
lines.push(formatRouteTable(context.routeTable, { colors: useColors }));
|
|
268
|
+
lines.push('');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const bannerText = lines.join('\n');
|
|
272
|
+
const plainText = stripAnsi(bannerText);
|
|
273
|
+
|
|
274
|
+
return { bannerText, plainText, summary };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Prints the development banner to console or designated logger
|
|
279
|
+
* @param {Object} context
|
|
280
|
+
* @param {Object} [options]
|
|
281
|
+
* @returns {void}
|
|
282
|
+
*/
|
|
283
|
+
function printDevBanner(context = {}, options = {}) {
|
|
284
|
+
const logger = options.logger || context.logger || console;
|
|
285
|
+
const { bannerText } = formatDevBanner(context, options);
|
|
286
|
+
if (logger && typeof logger.log === 'function') {
|
|
287
|
+
logger.log(bannerText);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
module.exports = {
|
|
292
|
+
formatRouteTable,
|
|
293
|
+
collectBannerSummary,
|
|
294
|
+
formatDevBanner,
|
|
295
|
+
printDevBanner,
|
|
296
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Webspresso Dev CLI Colors
|
|
5
|
+
* Lightweight, zero-dependency ANSI color utilities with NO_COLOR & TTY detection
|
|
6
|
+
* @module src/dev/colors
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const isColorSupported = Boolean(
|
|
10
|
+
!process.env.NO_COLOR &&
|
|
11
|
+
(process.env.FORCE_COLOR || (process.stdout ? process.stdout.isTTY !== false : true))
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates color formatter helper functions
|
|
16
|
+
* @param {boolean} [enabled=true]
|
|
17
|
+
*/
|
|
18
|
+
function createColors(enabled = isColorSupported) {
|
|
19
|
+
function wrap(open, close) {
|
|
20
|
+
return (str) => {
|
|
21
|
+
if (!enabled || str == null || str === '') return String(str ?? '');
|
|
22
|
+
return `${open}${str}${close}`;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
isColorSupported: enabled,
|
|
28
|
+
bold: wrap('\x1b[1m', '\x1b[22m'),
|
|
29
|
+
dim: wrap('\x1b[2m', '\x1b[22m'),
|
|
30
|
+
italic: wrap('\x1b[3m', '\x1b[23m'),
|
|
31
|
+
underline: wrap('\x1b[4m', '\x1b[24m'),
|
|
32
|
+
|
|
33
|
+
// Colors
|
|
34
|
+
black: wrap('\x1b[30m', '\x1b[39m'),
|
|
35
|
+
red: wrap('\x1b[31m', '\x1b[39m'),
|
|
36
|
+
green: wrap('\x1b[32m', '\x1b[39m'),
|
|
37
|
+
yellow: wrap('\x1b[33m', '\x1b[39m'),
|
|
38
|
+
blue: wrap('\x1b[34m', '\x1b[39m'),
|
|
39
|
+
magenta: wrap('\x1b[35m', '\x1b[39m'),
|
|
40
|
+
cyan: wrap('\x1b[36m', '\x1b[39m'),
|
|
41
|
+
white: wrap('\x1b[37m', '\x1b[39m'),
|
|
42
|
+
gray: wrap('\x1b[90m', '\x1b[39m'),
|
|
43
|
+
|
|
44
|
+
// Backgrounds
|
|
45
|
+
bgRed: wrap('\x1b[41m', '\x1b[49m'),
|
|
46
|
+
bgGreen: wrap('\x1b[42m', '\x1b[49m'),
|
|
47
|
+
bgYellow: wrap('\x1b[43m', '\x1b[49m'),
|
|
48
|
+
bgBlue: wrap('\x1b[44m', '\x1b[49m'),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const defaultColors = createColors();
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Strip all ANSI escape sequences from a string
|
|
56
|
+
* @param {string} str
|
|
57
|
+
* @returns {string}
|
|
58
|
+
*/
|
|
59
|
+
function stripAnsi(str) {
|
|
60
|
+
if (typeof str !== 'string') return '';
|
|
61
|
+
// Match standard ANSI escape codes
|
|
62
|
+
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
colors: defaultColors,
|
|
67
|
+
createColors,
|
|
68
|
+
stripAnsi,
|
|
69
|
+
isColorSupported,
|
|
70
|
+
};
|
package/src/dev/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Webspresso Development & DX Tooling
|
|
5
|
+
* Zero-dependency banner, route table introspection, request profiler, and N+1 query detection
|
|
6
|
+
* @module src/dev
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { colors, createColors, stripAnsi, isColorSupported } = require('./colors');
|
|
10
|
+
const {
|
|
11
|
+
formatRouteTable,
|
|
12
|
+
collectBannerSummary,
|
|
13
|
+
formatDevBanner,
|
|
14
|
+
printDevBanner,
|
|
15
|
+
} = require('./banner');
|
|
16
|
+
const {
|
|
17
|
+
profilerStorage,
|
|
18
|
+
attachProfilerToKnex,
|
|
19
|
+
extractTableFromSql,
|
|
20
|
+
detectNPlusOne,
|
|
21
|
+
formatRequestLog,
|
|
22
|
+
createDevProfilerMiddleware,
|
|
23
|
+
} = require('./profiler');
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
zodToTsType,
|
|
27
|
+
collectModelMetadata,
|
|
28
|
+
collectServiceMetadata,
|
|
29
|
+
collectRouteMetadata,
|
|
30
|
+
generateDeclarationCode,
|
|
31
|
+
generateTypes,
|
|
32
|
+
} = require('./typegen');
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
// Colors & ANSI
|
|
36
|
+
colors,
|
|
37
|
+
createColors,
|
|
38
|
+
stripAnsi,
|
|
39
|
+
isColorSupported,
|
|
40
|
+
|
|
41
|
+
// Banner & Route Table
|
|
42
|
+
formatRouteTable,
|
|
43
|
+
collectBannerSummary,
|
|
44
|
+
formatDevBanner,
|
|
45
|
+
printDevBanner,
|
|
46
|
+
|
|
47
|
+
// Profiler & N+1 Detection
|
|
48
|
+
profilerStorage,
|
|
49
|
+
attachProfilerToKnex,
|
|
50
|
+
extractTableFromSql,
|
|
51
|
+
detectNPlusOne,
|
|
52
|
+
formatRequestLog,
|
|
53
|
+
createDevProfilerMiddleware,
|
|
54
|
+
|
|
55
|
+
// TypeGen
|
|
56
|
+
zodToTsType,
|
|
57
|
+
collectModelMetadata,
|
|
58
|
+
collectServiceMetadata,
|
|
59
|
+
collectRouteMetadata,
|
|
60
|
+
generateDeclarationCode,
|
|
61
|
+
generateTypes,
|
|
62
|
+
};
|