kreftjs 0.0.0-stage → 0.0.2-beta

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.
Files changed (184) hide show
  1. package/README.md +103 -2
  2. package/bin/add.js +1074 -0
  3. package/bin/artisan-more.js +994 -0
  4. package/bin/artisan.js +1877 -0
  5. package/bin/backend/app.js +161 -0
  6. package/bin/backend/cache.js +196 -0
  7. package/bin/backend/collection.js +117 -0
  8. package/bin/backend/command.js +45 -0
  9. package/bin/backend/config.js +234 -0
  10. package/bin/backend/error-page.js +773 -0
  11. package/bin/backend/events.js +49 -0
  12. package/bin/backend/factory.js +223 -0
  13. package/bin/backend/fake.js +180 -0
  14. package/bin/backend/form.js +102 -0
  15. package/bin/backend/gate.js +79 -0
  16. package/bin/backend/gc.js +69 -0
  17. package/bin/backend/html.js +322 -0
  18. package/bin/backend/http.js +652 -0
  19. package/bin/backend/index.js +452 -0
  20. package/bin/backend/mail.js +77 -0
  21. package/bin/backend/mcp.js +433 -0
  22. package/bin/backend/migrator.js +370 -0
  23. package/bin/backend/model.js +781 -0
  24. package/bin/backend/namespace.js +213 -0
  25. package/bin/backend/notifications.js +68 -0
  26. package/bin/backend/qr.js +164 -0
  27. package/bin/backend/query.js +387 -0
  28. package/bin/backend/queue.js +412 -0
  29. package/bin/backend/redis.js +479 -0
  30. package/bin/backend/schedule.js +112 -0
  31. package/bin/backend/schema.js +321 -0
  32. package/bin/backend/seo-pages.js +100 -0
  33. package/bin/backend/storage.js +153 -0
  34. package/bin/backend/support.js +567 -0
  35. package/bin/backend/typescript.js +166 -0
  36. package/bin/build.js +540 -0
  37. package/bin/db-api.js +249 -0
  38. package/bin/db.js +275 -0
  39. package/bin/deps.js +138 -0
  40. package/bin/electron.js +509 -0
  41. package/bin/env.js +97 -0
  42. package/bin/guard.js +80 -0
  43. package/bin/kreftjs-config.js +40 -0
  44. package/bin/kreftjs.js +1373 -0
  45. package/bin/lang.js +241 -0
  46. package/bin/layout.js +74 -0
  47. package/bin/no-auth.js +162 -0
  48. package/bin/prerender.js +258 -0
  49. package/bin/safe-json.js +24 -0
  50. package/bin/server-routes.js +154 -0
  51. package/bin/ssr.js +133 -0
  52. package/bin/terminal.js +195 -0
  53. package/bin/to-ts.js +36 -0
  54. package/bin/update.js +237 -0
  55. package/bin/urls-file.js +27 -0
  56. package/bin/vite.js +228 -0
  57. package/docs/Docs.kreftjs +557 -0
  58. package/docs/README.md +2302 -0
  59. package/docs/markdown.js +186 -0
  60. package/editors/vscode/README.md +13 -0
  61. package/editors/vscode/extension.js +183 -0
  62. package/editors/vscode/language-configuration.json +17 -0
  63. package/editors/vscode/package.json +57 -0
  64. package/editors/vscode/snippets/kreftjs.json +21 -0
  65. package/editors/vscode/syntaxes/kreftjs.tmLanguage.json +207 -0
  66. package/kreftjs.d.ts +579 -0
  67. package/kreftjs.js +5108 -0
  68. package/package.json +97 -4
  69. package/server.d.ts +1028 -0
  70. package/server.js +8 -0
  71. package/templates/app/README.md +127 -0
  72. package/templates/app/_env.example +64 -0
  73. package/templates/app/_gitignore +5 -0
  74. package/templates/app/_vscode/settings.json +9 -0
  75. package/templates/app/backend/app/Auth/Session.js +177 -0
  76. package/templates/app/backend/app/Auth/Throttle.js +29 -0
  77. package/templates/app/backend/app/Console/Commands/.gitkeep +0 -0
  78. package/templates/app/backend/app/Events/.gitkeep +0 -0
  79. package/templates/app/backend/app/Http/Controllers/Admin/DashboardController.js +34 -0
  80. package/templates/app/backend/app/Http/Controllers/Admin/UserController.js +53 -0
  81. package/templates/app/backend/app/Http/Controllers/Auth/ApiTokenController.js +47 -0
  82. package/templates/app/backend/app/Http/Controllers/Auth/AuthenticatedSessionController.js +41 -0
  83. package/templates/app/backend/app/Http/Controllers/Auth/ConfirmablePasswordController.js +14 -0
  84. package/templates/app/backend/app/Http/Controllers/Auth/EmailVerificationController.js +23 -0
  85. package/templates/app/backend/app/Http/Controllers/Auth/NewPasswordController.js +29 -0
  86. package/templates/app/backend/app/Http/Controllers/Auth/PasswordResetLinkController.js +20 -0
  87. package/templates/app/backend/app/Http/Controllers/Auth/RegisteredUserController.js +19 -0
  88. package/templates/app/backend/app/Http/Controllers/Auth/SessionController.js +36 -0
  89. package/templates/app/backend/app/Http/Controllers/Auth/TwoFactorChallengeController.js +41 -0
  90. package/templates/app/backend/app/Http/Controllers/Auth/TwoFactorController.js +63 -0
  91. package/templates/app/backend/app/Http/Controllers/ProfileController.js +66 -0
  92. package/templates/app/backend/app/Http/Kernel.js +8 -0
  93. package/templates/app/backend/app/Http/Middleware/Authenticate.js +84 -0
  94. package/templates/app/backend/app/Http/Middleware/CheckAbilities.js +17 -0
  95. package/templates/app/backend/app/Http/Middleware/CheckForAnyAbility.js +12 -0
  96. package/templates/app/backend/app/Http/Middleware/EnsureEmailIsVerified.js +11 -0
  97. package/templates/app/backend/app/Http/Middleware/EnsureHasRole.js +11 -0
  98. package/templates/app/backend/app/Http/Middleware/EnsureIsAdmin.js +11 -0
  99. package/templates/app/backend/app/Http/Middleware/RequirePassword.js +15 -0
  100. package/templates/app/backend/app/Http/Requests/Auth/LoginRequest.js +45 -0
  101. package/templates/app/backend/app/Http/Resources/.gitkeep +1 -0
  102. package/templates/app/backend/app/Jobs/.gitkeep +0 -0
  103. package/templates/app/backend/app/Listeners/.gitkeep +0 -0
  104. package/templates/app/backend/app/Mail/.gitkeep +0 -0
  105. package/templates/app/backend/app/Models/PersonalAccessToken.js +21 -0
  106. package/templates/app/backend/app/Models/User.js +64 -0
  107. package/templates/app/backend/app/Notifications/.gitkeep +0 -0
  108. package/templates/app/backend/app/Observers/.gitkeep +0 -0
  109. package/templates/app/backend/app/Policies/.gitkeep +0 -0
  110. package/templates/app/backend/app/Providers/AppServiceProvider.js +12 -0
  111. package/templates/app/backend/app/Rules/.gitkeep +1 -0
  112. package/templates/app/backend/config/app.js +16 -0
  113. package/templates/app/backend/config/auth.js +34 -0
  114. package/templates/app/backend/config/database.js +38 -0
  115. package/templates/app/backend/config/redis.js +28 -0
  116. package/templates/app/backend/config/session.js +36 -0
  117. package/templates/app/backend/database/migrations/0001_01_01_000000_create_users_table.js +49 -0
  118. package/templates/app/backend/database/migrations/0001_01_01_000001_create_cache_table.js +13 -0
  119. package/templates/app/backend/database/migrations/0001_01_01_000002_create_jobs_table.js +26 -0
  120. package/templates/app/backend/database/migrations/0001_01_01_000003_create_notifications_table.js +18 -0
  121. package/templates/app/backend/database/seeders/DatabaseSeeder.js +15 -0
  122. package/templates/app/backend/lang/ar/messages.json +10 -0
  123. package/templates/app/backend/lang/ar/validation.json +21 -0
  124. package/templates/app/backend/lang/en/messages.json +10 -0
  125. package/templates/app/backend/lang/en/validation.json +19 -0
  126. package/templates/app/backend/routes/api.js +25 -0
  127. package/templates/app/backend/routes/auth.js +52 -0
  128. package/templates/app/backend/routes/console.js +15 -0
  129. package/templates/app/frontend/App.kreftjs +4 -0
  130. package/templates/app/frontend/components/Field.kreftjs +19 -0
  131. package/templates/app/frontend/components/StatCard.kreftjs +47 -0
  132. package/templates/app/frontend/components/profile/ApiTokens.kreftjs +70 -0
  133. package/templates/app/frontend/components/profile/Sessions.kreftjs +42 -0
  134. package/templates/app/frontend/components/profile/TwoFactor.kreftjs +90 -0
  135. package/templates/app/frontend/errors/ErrorMonitor.kreftjs +264 -0
  136. package/templates/app/frontend/index.html +79 -0
  137. package/templates/app/frontend/kreftjs.d.ts +579 -0
  138. package/templates/app/frontend/kreftjs.js +5108 -0
  139. package/templates/app/frontend/layouts/app.kreftjs +718 -0
  140. package/templates/app/frontend/layouts/docs.kreftjs +37 -0
  141. package/templates/app/frontend/layouts/guest.kreftjs +18 -0
  142. package/templates/app/frontend/pages/Dashboard.kreftjs +273 -0
  143. package/templates/app/frontend/pages/ErrorMonitor.kreftjs +96 -0
  144. package/templates/app/frontend/pages/Home.kreftjs +617 -0
  145. package/templates/app/frontend/pages/Profile.kreftjs +104 -0
  146. package/templates/app/frontend/pages/Users.kreftjs +240 -0
  147. package/templates/app/frontend/pages/auth/ConfirmPassword.kreftjs +33 -0
  148. package/templates/app/frontend/pages/auth/ForgotPassword.kreftjs +36 -0
  149. package/templates/app/frontend/pages/auth/Login.kreftjs +89 -0
  150. package/templates/app/frontend/pages/auth/Register.kreftjs +48 -0
  151. package/templates/app/frontend/pages/auth/ResetPassword.kreftjs +45 -0
  152. package/templates/app/frontend/pages/auth/VerifyEmail.kreftjs +59 -0
  153. package/templates/app/frontend/routes/web.kreftjs +34 -0
  154. package/templates/app/frontend/tests/components/Counter.kreftjs +15 -0
  155. package/templates/app/frontend/tests/pages/About.kreftjs +12 -0
  156. package/templates/app/frontend/tests/pages/Test.kreftjs +31 -0
  157. package/templates/app/frontend/ui.css +517 -0
  158. package/templates/app/lang/ar/auth.json +6 -0
  159. package/templates/app/lang/ar/messages.json +6 -0
  160. package/templates/app/lang/ar/pagination.json +5 -0
  161. package/templates/app/lang/ar/passwords.json +8 -0
  162. package/templates/app/lang/ar/validation.json +22 -0
  163. package/templates/app/lang/ar.json +10 -0
  164. package/templates/app/lang/en/auth.json +6 -0
  165. package/templates/app/lang/en/messages.json +6 -0
  166. package/templates/app/lang/en/pagination.json +5 -0
  167. package/templates/app/lang/en/passwords.json +8 -0
  168. package/templates/app/lang/en/validation.json +22 -0
  169. package/templates/app/lang/en.json +10 -0
  170. package/templates/errors/401.kreftjs +11 -0
  171. package/templates/errors/402.kreftjs +11 -0
  172. package/templates/errors/403.kreftjs +11 -0
  173. package/templates/errors/404.kreftjs +11 -0
  174. package/templates/errors/419.kreftjs +11 -0
  175. package/templates/errors/429.kreftjs +11 -0
  176. package/templates/errors/500.kreftjs +11 -0
  177. package/templates/errors/503.kreftjs +11 -0
  178. package/templates/errors/ErrorMonitor.kreftjs +502 -0
  179. package/templates/errors/error.kreftjs +11 -0
  180. package/templates/errors/minimal.kreftjs +25 -0
  181. package/tools/dev-client.js +125 -0
  182. package/tools/devtools.js +172 -0
  183. package/tools/vite-plugin-kreftjs.d.ts +52 -0
  184. package/tools/vite-plugin-kreftjs.js +466 -0
@@ -0,0 +1,773 @@
1
+ /*
2
+ * Developer Error Details & Interactive Error Monitoring Page for Kreft
3
+ * Renders Kreft's own high-precision geometric UI (matching ui.css tokens, dark/light mode,
4
+ * 1px borders, monochrome active states, and dark terminal code preview).
5
+ */
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ function parseStack(stack) {
10
+ const lines = String(stack || '').split('\n').slice(1);
11
+ const frames = [];
12
+ for (const line of lines) {
13
+ const trimmed = line.trim();
14
+ if (!trimmed.startsWith('at ')) continue;
15
+ const m = trimmed.match(/^at\s+(?:(async\s+)?([^\s(]+)\s+\((.+):(\d+):(\d+)\)|(.+):(\d+):(\d+))$/);
16
+ if (m) {
17
+ const isMethod = Boolean(m[3]);
18
+ const file = isMethod ? m[3] : m[6];
19
+ const lineNum = parseInt(isMethod ? m[4] : m[7], 10);
20
+ const colNum = parseInt(isMethod ? m[5] : m[8], 10);
21
+ const fn = isMethod ? (m[1] ? 'async ' : '') + m[2] : '<anonymous>';
22
+ const isApp = !file.includes('node_modules') && !file.startsWith('node:') && !file.includes('internal/');
23
+ frames.push({ fn, file, line: lineNum, col: colNum, isApp });
24
+ } else {
25
+ frames.push({ raw: trimmed, isApp: false });
26
+ }
27
+ }
28
+ return frames;
29
+ }
30
+
31
+ function getCodeSnippet(filePath, targetLine, radius = 6) {
32
+ if (!filePath || !fs.existsSync(filePath)) return null;
33
+ try {
34
+ const content = fs.readFileSync(filePath, 'utf8');
35
+ const allLines = content.split(/\r?\n/);
36
+ const start = Math.max(1, targetLine - radius);
37
+ const end = Math.min(allLines.length, targetLine + radius);
38
+ const snippet = [];
39
+ for (let i = start; i <= end; i++) {
40
+ snippet.push({
41
+ num: i,
42
+ code: allLines[i - 1] ?? '',
43
+ isTarget: i === targetLine,
44
+ });
45
+ }
46
+ return { start, end, lines: snippet };
47
+ } catch (_) {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ function levenshtein(a, b) {
53
+ const matrix = Array.from({ length: a.length + 1 }, () => Array(b.length + 1).fill(0));
54
+ for (let i = 0; i <= a.length; i++) matrix[i][0] = i;
55
+ for (let j = 0; j <= b.length; j++) matrix[0][j] = j;
56
+ for (let i = 1; i <= a.length; i++) {
57
+ for (let j = 1; j <= b.length; j++) {
58
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
59
+ matrix[i][j] = Math.min(
60
+ matrix[i - 1][j] + 1,
61
+ matrix[i][j - 1] + 1,
62
+ matrix[i - 1][j - 1] + cost
63
+ );
64
+ }
65
+ }
66
+ return matrix[a.length][b.length];
67
+ }
68
+
69
+ function generateSmartSolution(err, topFrame) {
70
+ const msg = String(err && err.message || err || '');
71
+
72
+ // 1. Method typo on models or objects
73
+ const methodMatch = msg.match(/([a-zA-Z0-9_$]+)\.([a-zA-Z0-9_$]+) is not a function/i) || msg.match(/Call to undefined method ([^\s(:]+)(?:::|\.)([^\s(]+)/i);
74
+ if (methodMatch) {
75
+ const caller = methodMatch[1];
76
+ const fnName = methodMatch[2] || caller;
77
+ const commonMethods = ['query', 'find', 'where', 'get', 'first', 'create', 'update', 'delete', 'save', 'count', 'paginate', 'all', 'select'];
78
+ const close = commonMethods.find(m => Math.abs(m.length - fnName.length) <= 2 && (fnName.includes(m) || m.includes(fnName) || levenshtein(m, fnName) <= 2));
79
+ if (close) {
80
+ return {
81
+ title: 'Unknown Model Method',
82
+ message: `Did you mean <code>${escapeHtml(caller ? caller + '.' + close + '()' : '.' + close + '()')}</code>?`
83
+ };
84
+ }
85
+ }
86
+
87
+ // 2. Reading property of undefined/null
88
+ const undefMatch = msg.match(/Cannot read propert(?:y|ies) of (undefined|null) \(reading '([^']+)'\)/i);
89
+ if (undefMatch) {
90
+ const prop = undefMatch[2];
91
+ return {
92
+ title: 'Null Reference Guard',
93
+ message: `The target object is <code>${undefMatch[1]}</code> when reading <code>.${escapeHtml(prop)}</code>. Use optional chaining <code>?.${escapeHtml(prop)}</code> or verify the record exists.`
94
+ };
95
+ }
96
+
97
+ // 3. Variable not defined
98
+ const refMatch = msg.match(/([a-zA-Z0-9_$]+) is not defined/i);
99
+ if (refMatch) {
100
+ const varName = refMatch[1];
101
+ return {
102
+ title: 'Undefined Identifier',
103
+ message: `Identifier <code>${escapeHtml(varName)}</code> is referenced but not declared. Check your imports or action arguments.`
104
+ };
105
+ }
106
+
107
+ // 4. Missing Database Table
108
+ if (msg.includes('no such table') || msg.includes('does not exist')) {
109
+ return {
110
+ title: 'Pending Database Migration',
111
+ message: `The queried table does not exist yet. Run <code>kreftjs db:migrate</code> to apply pending migrations.`
112
+ };
113
+ }
114
+
115
+ // 5. Default contextual suggestion
116
+ if (topFrame && topFrame.file) {
117
+ return {
118
+ title: 'Diagnostic Hint',
119
+ message: `Inspect line <strong>${topFrame.line}</strong> in <code>${escapeHtml(path.basename(topFrame.file))}</code> and verify input values.`
120
+ };
121
+ }
122
+
123
+ return {
124
+ title: 'Diagnostic Hint',
125
+ message: 'Select any stack frame below to inspect the source lines around the exception.'
126
+ };
127
+ }
128
+
129
+ function escapeHtml(str) {
130
+ return String(str ?? '')
131
+ .replace(/&/g, '&amp;')
132
+ .replace(/</g, '&lt;')
133
+ .replace(/>/g, '&gt;')
134
+ .replace(/"/g, '&quot;')
135
+ .replace(/'/g, '&#39;');
136
+ }
137
+
138
+ const SENSITIVE_KEY_RE = /(password|passwd|secret|token|api_key|apikey|private_key|recovery_code|authorization|cookie|set-cookie|xsrf|csrf|challenge|credit_card|cvv)/i;
139
+
140
+ function redactSensitive(obj, depth = 0) {
141
+ if (depth > 6 || obj === null || obj === undefined) return obj;
142
+ if (Array.isArray(obj)) return obj.map((item) => redactSensitive(item, depth + 1));
143
+ if (typeof obj !== 'object') return obj;
144
+ const out = {};
145
+ for (const [k, v] of Object.entries(obj)) {
146
+ if (SENSITIVE_KEY_RE.test(k)) {
147
+ out[k] = '******** [REDACTED]';
148
+ } else if (v && typeof v === 'object') {
149
+ out[k] = redactSensitive(v, depth + 1);
150
+ } else {
151
+ out[k] = v;
152
+ }
153
+ }
154
+ return out;
155
+ }
156
+
157
+ function renderErrorPage(err, req = null, { status = 500 } = {}) {
158
+ const errName = err && err.name ? err.name : 'Error';
159
+ const errMessage = err && err.message ? err.message : String(err);
160
+ const stack = err && err.stack ? err.stack : '';
161
+ const frames = parseStack(stack);
162
+
163
+ for (const f of frames) {
164
+ if (f.file && f.line) {
165
+ f.snippet = getCodeSnippet(f.file, f.line);
166
+ }
167
+ }
168
+
169
+ const topAppFrame = frames.find((f) => f.isApp && f.file) || frames.find((f) => f.file) || null;
170
+ const initialSnippet = topAppFrame && topAppFrame.snippet ? topAppFrame.snippet : null;
171
+ const smartSolution = generateSmartSolution(err, topAppFrame);
172
+
173
+ const method = req ? (req.method || 'GET').toUpperCase() : 'GET';
174
+ const url = req ? (req.url || '/') : '/';
175
+ const headers = redactSensitive(req ? (req.headers || {}) : {});
176
+ const query = redactSensitive(req && req.query ? req.query : {});
177
+ const body = redactSensitive(req && req.body ? req.body : {});
178
+ const params = redactSensitive(req && req.params ? req.params : {});
179
+ const ip = req && typeof req.ip === 'function' ? req.ip() : (req && req.ip) || '127.0.0.1';
180
+
181
+ const memory = Math.round(process.memoryUsage().heapUsed / 1024 / 1024) + ' MB';
182
+ const nodeVersion = process.version;
183
+ const kreftVersion = require('../../package.json').version;
184
+ const internalCount = frames.filter((f) => !f.isApp).length;
185
+
186
+ const safeJson = (val) => JSON.stringify(val).replace(/</g, '\\u003c').replace(/[\u2028\u2029]/g, (c) => '\\u' + c.charCodeAt(0).toString(16));
187
+ const framesJson = safeJson(frames.map((f, i) => ({
188
+ id: i,
189
+ fn: f.fn || '<anonymous>',
190
+ file: f.file || f.raw || '',
191
+ line: f.line || 0,
192
+ col: f.col || 0,
193
+ isApp: Boolean(f.isApp),
194
+ snippet: f.snippet || null,
195
+ })));
196
+
197
+ return `<!DOCTYPE html>
198
+ <html lang="en">
199
+ <head>
200
+ <meta charset="utf-8">
201
+ <meta name="viewport" content="width=device-width, initial-scale=1">
202
+ <title>${escapeHtml(errName)} — Kreft Diagnostic</title>
203
+ <style>
204
+ :root {
205
+ color-scheme: light dark;
206
+ --background: #ffffff;
207
+ --foreground: #09090b;
208
+ --card: #ffffff;
209
+ --border: #e4e4e7;
210
+ --accent: #f4f4f5;
211
+ --primary: #09090b;
212
+ --primary-foreground: #fafafa;
213
+ --muted: #71717a;
214
+ --destructive: #dc2626;
215
+ --success: #10b981;
216
+ --success-bg: #ecfdf5;
217
+ --terminal-bg: #09090b;
218
+ --terminal-fg: #f4f4f5;
219
+ --grid-line: rgba(0, 0, 0, 0.04);
220
+ --font: 'Geist', ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
221
+ --mono: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
222
+ }
223
+ @media (prefers-color-scheme: dark) {
224
+ :root {
225
+ --background: #09090b;
226
+ --foreground: #f4f4f5;
227
+ --card: #111115;
228
+ --border: #27272a;
229
+ --accent: #18181b;
230
+ --primary: #f4f4f5;
231
+ --primary-foreground: #09090b;
232
+ --muted: #a1a1aa;
233
+ --destructive: #ef4444;
234
+ --success: #10b981;
235
+ --success-bg: #064e3b;
236
+ --terminal-bg: #050507;
237
+ --terminal-fg: #f4f4f5;
238
+ --grid-line: rgba(255, 255, 255, 0.04);
239
+ }
240
+ }
241
+ *, *::before, *::after {
242
+ box-sizing: border-box;
243
+ border-radius: 0;
244
+ box-shadow: none;
245
+ }
246
+ body {
247
+ margin: 0;
248
+ font-family: var(--font);
249
+ font-size: 14px;
250
+ line-height: 1.6;
251
+ background-color: var(--background);
252
+ background-image:
253
+ linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
254
+ linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
255
+ background-size: 32px 32px;
256
+ color: var(--foreground);
257
+ -webkit-font-smoothing: antialiased;
258
+ }
259
+ .shell {
260
+ max-width: 1120px;
261
+ margin: 0 auto;
262
+ padding: 32px 24px 64px;
263
+ display: grid;
264
+ gap: 16px;
265
+ }
266
+ .card {
267
+ background: var(--card);
268
+ border: 1px solid var(--border);
269
+ padding: 24px;
270
+ }
271
+ .head-card {
272
+ border-inline-start: 3px solid var(--destructive);
273
+ }
274
+ .top-bar {
275
+ display: flex;
276
+ justify-content: space-between;
277
+ align-items: center;
278
+ gap: 12px;
279
+ flex-wrap: wrap;
280
+ margin-bottom: 14px;
281
+ }
282
+ .badges, .actions {
283
+ display: flex;
284
+ gap: 8px;
285
+ align-items: center;
286
+ flex-wrap: wrap;
287
+ }
288
+ .badge {
289
+ display: inline-flex;
290
+ align-items: center;
291
+ height: 24px;
292
+ padding: 0 10px;
293
+ font-family: var(--mono);
294
+ font-size: 11px;
295
+ font-weight: 600;
296
+ text-transform: uppercase;
297
+ border: 1px solid var(--border);
298
+ background: var(--background);
299
+ color: var(--foreground);
300
+ }
301
+ .badge.danger {
302
+ background: var(--destructive);
303
+ border-color: var(--destructive);
304
+ color: #ffffff;
305
+ }
306
+ .badge.solid {
307
+ background: var(--primary);
308
+ border-color: var(--primary);
309
+ color: var(--primary-foreground);
310
+ }
311
+ .badge.success {
312
+ background: var(--success-bg);
313
+ border-color: var(--success);
314
+ color: var(--success);
315
+ }
316
+ .btn {
317
+ display: inline-flex;
318
+ align-items: center;
319
+ justify-content: center;
320
+ height: 30px;
321
+ padding: 0 12px;
322
+ font-family: var(--font);
323
+ font-size: 12px;
324
+ font-weight: 600;
325
+ color: var(--foreground);
326
+ background: var(--background);
327
+ border: 1px solid var(--border);
328
+ cursor: pointer;
329
+ text-decoration: none;
330
+ }
331
+ .btn:hover {
332
+ background: var(--accent);
333
+ border-color: var(--foreground);
334
+ }
335
+ .btn.primary {
336
+ background: var(--primary);
337
+ border-color: var(--primary);
338
+ color: var(--primary-foreground);
339
+ }
340
+ .err-msg {
341
+ font-family: var(--mono);
342
+ font-size: 20px;
343
+ line-height: 1.35;
344
+ margin: 0 0 8px;
345
+ word-break: break-word;
346
+ }
347
+ .err-loc {
348
+ margin: 0;
349
+ font-size: 13px;
350
+ color: var(--muted);
351
+ }
352
+ code {
353
+ font-family: var(--mono);
354
+ font-size: 0.88em;
355
+ background: var(--accent);
356
+ border: 1px solid var(--border);
357
+ padding: 2px 6px;
358
+ }
359
+ .hint-box {
360
+ border: 1px solid var(--border);
361
+ background: var(--accent);
362
+ padding: 14px 16px;
363
+ margin-top: 16px;
364
+ }
365
+ .hint-head {
366
+ display: flex;
367
+ align-items: center;
368
+ gap: 10px;
369
+ margin-bottom: 6px;
370
+ font-size: 13px;
371
+ }
372
+ .hint-body {
373
+ margin: 0;
374
+ font-size: 13px;
375
+ font-family: var(--mono);
376
+ }
377
+ .tabs {
378
+ display: flex;
379
+ border-bottom: 1px solid var(--border);
380
+ gap: 4px;
381
+ }
382
+ .tab-btn {
383
+ border: 0;
384
+ border-bottom: 2px solid transparent;
385
+ background: none;
386
+ color: var(--muted);
387
+ height: 36px;
388
+ padding: 0 14px;
389
+ font-family: var(--font);
390
+ font-size: 13px;
391
+ font-weight: 600;
392
+ cursor: pointer;
393
+ }
394
+ .tab-btn:hover {
395
+ color: var(--foreground);
396
+ background: var(--accent);
397
+ }
398
+ .tab-btn.active {
399
+ color: var(--foreground);
400
+ border-bottom-color: var(--foreground);
401
+ }
402
+ .workbench {
403
+ display: grid;
404
+ grid-template-columns: 320px minmax(0, 1fr);
405
+ border: 1px solid var(--border);
406
+ background: var(--card);
407
+ }
408
+ .frame-pane {
409
+ border-inline-end: 1px solid var(--border);
410
+ display: flex;
411
+ flex-direction: column;
412
+ }
413
+ .pane-bar {
414
+ display: flex;
415
+ justify-content: space-between;
416
+ align-items: center;
417
+ padding: 8px 14px;
418
+ border-bottom: 1px solid var(--border);
419
+ background: var(--accent);
420
+ font-size: 12px;
421
+ font-weight: 600;
422
+ color: var(--muted);
423
+ min-height: 40px;
424
+ }
425
+ .frame-list {
426
+ display: flex;
427
+ flex-direction: column;
428
+ max-height: 460px;
429
+ overflow-y: auto;
430
+ }
431
+ .frame-row {
432
+ padding: 10px 14px;
433
+ border-bottom: 1px solid var(--border);
434
+ border-inline-start: 3px solid transparent;
435
+ cursor: pointer;
436
+ }
437
+ .frame-row:hover {
438
+ background: var(--accent);
439
+ }
440
+ .frame-row.active {
441
+ background: var(--accent);
442
+ border-inline-start-color: var(--foreground);
443
+ }
444
+ .frame-row.vendor {
445
+ opacity: 0.6;
446
+ }
447
+ .frame-top {
448
+ display: flex;
449
+ justify-content: space-between;
450
+ align-items: center;
451
+ gap: 8px;
452
+ margin-bottom: 3px;
453
+ }
454
+ .frame-name {
455
+ font-family: var(--mono);
456
+ font-size: 12px;
457
+ font-weight: 600;
458
+ color: var(--foreground);
459
+ }
460
+ .frame-tag {
461
+ font-family: var(--mono);
462
+ font-size: 10px;
463
+ text-transform: uppercase;
464
+ padding: 1px 5px;
465
+ border: 1px solid var(--border);
466
+ color: var(--muted);
467
+ }
468
+ .frame-loc {
469
+ font-family: var(--mono);
470
+ font-size: 11px;
471
+ color: var(--muted);
472
+ word-break: break-all;
473
+ }
474
+ .source-pane {
475
+ display: flex;
476
+ flex-direction: column;
477
+ min-width: 0;
478
+ }
479
+ .terminal-code {
480
+ background: var(--terminal-bg);
481
+ color: var(--terminal-fg);
482
+ padding: 14px 0;
483
+ font-family: var(--mono);
484
+ font-size: 13px;
485
+ line-height: 1.7;
486
+ overflow-x: auto;
487
+ flex: 1;
488
+ }
489
+ .code-row {
490
+ display: flex;
491
+ padding: 1px 16px;
492
+ border-inline-start: 3px solid transparent;
493
+ }
494
+ .code-row.target {
495
+ background: rgba(239, 68, 68, 0.18);
496
+ border-inline-start-color: var(--destructive);
497
+ color: #ffffff;
498
+ font-weight: 600;
499
+ }
500
+ .line-no {
501
+ width: 44px;
502
+ text-align: end;
503
+ padding-inline-end: 16px;
504
+ color: #71717a;
505
+ user-select: none;
506
+ flex-shrink: 0;
507
+ }
508
+ .code-row.target .line-no {
509
+ color: var(--destructive);
510
+ font-weight: 700;
511
+ }
512
+ .line-text {
513
+ white-space: pre;
514
+ }
515
+ .tab-view {
516
+ display: none;
517
+ }
518
+ .tab-view.active {
519
+ display: block;
520
+ }
521
+ .cards-grid {
522
+ display: grid;
523
+ grid-template-columns: repeat(2, minmax(0, 1fr));
524
+ gap: 16px;
525
+ }
526
+ table {
527
+ width: 100%;
528
+ border-collapse: collapse;
529
+ font-size: 13px;
530
+ border: 1px solid var(--border);
531
+ }
532
+ th, td {
533
+ text-align: start;
534
+ padding: 10px 14px;
535
+ border-bottom: 1px solid var(--border);
536
+ }
537
+ th {
538
+ width: 160px;
539
+ font-family: var(--mono);
540
+ font-size: 11px;
541
+ text-transform: uppercase;
542
+ color: var(--muted);
543
+ background: var(--accent);
544
+ }
545
+ pre.json-code {
546
+ font-family: var(--mono);
547
+ background: var(--terminal-bg);
548
+ color: var(--terminal-fg);
549
+ border: 1px solid var(--border);
550
+ padding: 16px;
551
+ font-size: 12px;
552
+ overflow-x: auto;
553
+ margin: 0;
554
+ }
555
+ @media (max-width: 820px) {
556
+ .workbench, .cards-grid { grid-template-columns: minmax(0, 1fr); }
557
+ .frame-pane { border-inline-end: 0; border-bottom: 1px solid var(--border); }
558
+ }
559
+ </style>
560
+ </head>
561
+ <body>
562
+ <div class="shell">
563
+ <section class="card head-card">
564
+ <div class="top-bar">
565
+ <div class="badges">
566
+ <span class="badge danger">HTTP ${escapeHtml(status)}</span>
567
+ <span class="badge solid">${escapeHtml(errName)}</span>
568
+ <span class="badge">Kreft v${escapeHtml(kreftVersion)}</span>
569
+ <span class="badge">Node ${escapeHtml(nodeVersion)}</span>
570
+ </div>
571
+ <div class="actions">
572
+ <button class="btn" id="copy-report-btn" onclick="copyDiagnosticReport()">Copy Report</button>
573
+ <a href="/docs" class="btn">Docs</a>
574
+ </div>
575
+ </div>
576
+
577
+ <h1 class="err-msg">${escapeHtml(errMessage)}</h1>
578
+ ${topAppFrame ? `<p class="err-loc">in <code>${escapeHtml(topAppFrame.file)}:${escapeHtml(topAppFrame.line)}</code> · ${escapeHtml(topAppFrame.fn || '<anonymous>')}</p>` : ''}
579
+
580
+ ${smartSolution ? `
581
+ <div class="hint-box">
582
+ <div class="hint-head">
583
+ <span class="badge success">Fix Suggestion</span>
584
+ <strong>${escapeHtml(smartSolution.title)}</strong>
585
+ </div>
586
+ <p class="hint-body">${smartSolution.message}</p>
587
+ </div>` : ''}
588
+ </section>
589
+
590
+ <div class="tabs" role="tablist">
591
+ <button class="tab-btn active" onclick="switchTab('tab-stack', this)">Stack Trace (${frames.length})</button>
592
+ <button class="tab-btn" onclick="switchTab('tab-context', this)">Request &amp; Runtime</button>
593
+ <button class="tab-btn" onclick="switchTab('tab-share', this)">Markdown Report</button>
594
+ </div>
595
+
596
+ <section id="tab-stack" class="workbench tab-view active" style="display: grid;">
597
+ <aside class="frame-pane">
598
+ <div class="pane-bar">
599
+ <span>Frames</span>
600
+ ${internalCount > 0 ? `<button class="btn" id="vendor-toggle-btn" style="height:24px;padding:0 8px;font-size:11px;" onclick="toggleVendorFrames()">Show internal (${internalCount})</button>` : ''}
601
+ </div>
602
+ <div class="frame-list" id="frames-container">
603
+ ${frames.map((f, i) => `
604
+ <div class="frame-row${f === topAppFrame ? ' active' : ''}${f.isApp ? ' app' : ' vendor'}"
605
+ data-frame-index="${i}"
606
+ onclick="selectFrame(${i})"
607
+ style="${!f.isApp ? 'display: none;' : ''}">
608
+ <div class="frame-top">
609
+ <span class="frame-name">${escapeHtml(f.fn || '<anonymous>')}</span>
610
+ <span class="frame-tag">${f.isApp ? 'app' : 'core'}</span>
611
+ </div>
612
+ <div class="frame-loc">${escapeHtml(f.file || f.raw || '')}:${escapeHtml(f.line || 1)}</div>
613
+ </div>`).join('')}
614
+ </div>
615
+ </aside>
616
+
617
+ <main class="source-pane">
618
+ <div class="pane-bar">
619
+ <code id="editor-file-label">${escapeHtml(topAppFrame ? topAppFrame.file + ':' + topAppFrame.line : 'source.js')}</code>
620
+ <span id="editor-lines-label">${initialSnippet ? 'Lines ' + initialSnippet.start + '–' + initialSnippet.end : ''}</span>
621
+ </div>
622
+ <div class="terminal-code" id="editor-code-container">
623
+ ${initialSnippet ? initialSnippet.lines.map(l => `
624
+ <div class="code-row${l.isTarget ? ' target' : ''}">
625
+ <span class="line-no">${l.num}</span>
626
+ <span class="line-text">${escapeHtml(l.code)}</span>
627
+ </div>`).join('') : '<p style="padding: 16px; color: #a1a1aa;">No source snippet available for this frame.</p>'}
628
+ </div>
629
+ </main>
630
+ </section>
631
+
632
+ <section id="tab-context" class="tab-view">
633
+ <div class="cards-grid">
634
+ <div class="card">
635
+ <h3 style="margin-top:0;">HTTP Request</h3>
636
+ <table>
637
+ <tr><th>Method</th><td><code>${escapeHtml(method)}</code></td></tr>
638
+ <tr><th>URL</th><td><code>${escapeHtml(url)}</code></td></tr>
639
+ <tr><th>Client IP</th><td><code>${escapeHtml(ip)}</code></td></tr>
640
+ </table>
641
+ ${Object.keys(params).length ? `
642
+ <h4 style="margin: 16px 0 8px;">Route Parameters</h4>
643
+ <table>
644
+ ${Object.entries(params).map(([k, v]) => `<tr><th>${escapeHtml(k)}</th><td>${escapeHtml(typeof v === 'object' ? JSON.stringify(v) : v)}</td></tr>`).join('')}
645
+ </table>` : ''}
646
+ ${Object.keys(query).length ? `
647
+ <h4 style="margin: 16px 0 8px;">Query Parameters</h4>
648
+ <table>
649
+ ${Object.entries(query).map(([k, v]) => `<tr><th>${escapeHtml(k)}</th><td>${escapeHtml(typeof v === 'object' ? JSON.stringify(v) : v)}</td></tr>`).join('')}
650
+ </table>` : ''}
651
+ ${body && Object.keys(body).length ? `
652
+ <h4 style="margin: 16px 0 8px;">Request Body</h4>
653
+ <pre class="json-code">${escapeHtml(JSON.stringify(body, null, 2))}</pre>` : ''}
654
+ </div>
655
+
656
+ <div class="card">
657
+ <h3 style="margin-top:0;">Kreft Runtime &amp; Headers</h3>
658
+ <table style="margin-bottom: 16px;">
659
+ <tr><th>Kreft</th><td><code>v${escapeHtml(kreftVersion)}</code></td></tr>
660
+ <tr><th>Node.js</th><td><code>${escapeHtml(nodeVersion)}</code></td></tr>
661
+ <tr><th>Heap Memory</th><td><code>${escapeHtml(memory)}</code></td></tr>
662
+ <tr><th>Platform</th><td><code>${escapeHtml(process.platform)} (${escapeHtml(process.arch)})</code></td></tr>
663
+ </table>
664
+ <h4 style="margin: 16px 0 8px;">Headers</h4>
665
+ <table>
666
+ ${Object.entries(headers).map(([k, v]) => `<tr><th>${escapeHtml(k)}</th><td>${escapeHtml(Array.isArray(v) ? v.join(', ') : v)}</td></tr>`).join('')}
667
+ </table>
668
+ </div>
669
+ </div>
670
+ </section>
671
+
672
+ <section id="tab-share" class="tab-view">
673
+ <div class="card">
674
+ <div class="top-bar">
675
+ <h3 style="margin:0;">Diagnostic Markdown Report</h3>
676
+ <div class="actions">
677
+ <button class="btn primary" onclick="copyDiagnosticReport()">Copy Markdown</button>
678
+ <button class="btn" onclick="copyJsonReport()">Copy JSON</button>
679
+ </div>
680
+ </div>
681
+ <pre class="json-code" id="markdown-preview-block"># Kreft Diagnostic Report
682
+ - **Exception:** \`${escapeHtml(errName)}\` (HTTP ${escapeHtml(status)})
683
+ - **Message:** ${escapeHtml(errMessage)}
684
+ - **Location:** \`${escapeHtml(topAppFrame ? topAppFrame.file + ':' + topAppFrame.line : 'unknown')}\`
685
+
686
+ \`\`\`text
687
+ ${escapeHtml(stack)}
688
+ \`\`\`</pre>
689
+ </div>
690
+ </section>
691
+ </div>
692
+
693
+ <script>
694
+ const allFrames = ${framesJson};
695
+ let vendorExpanded = false;
696
+
697
+ function switchTab(tabId, btn) {
698
+ document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
699
+ document.querySelectorAll('.tab-view').forEach(v => {
700
+ v.classList.remove('active');
701
+ v.style.display = 'none';
702
+ });
703
+ btn.classList.add('active');
704
+ const target = document.getElementById(tabId);
705
+ target.classList.add('active');
706
+ target.style.display = tabId === 'tab-stack' ? 'grid' : 'block';
707
+ }
708
+
709
+ function toggleVendorFrames() {
710
+ vendorExpanded = !vendorExpanded;
711
+ document.querySelectorAll('.frame-row.vendor').forEach(el => {
712
+ el.style.display = vendorExpanded ? 'block' : 'none';
713
+ });
714
+ const btn = document.getElementById('vendor-toggle-btn');
715
+ if (btn) btn.textContent = (vendorExpanded ? 'Hide internal' : 'Show internal') + ' (${internalCount})';
716
+ }
717
+
718
+ function selectFrame(index) {
719
+ document.querySelectorAll('.frame-row').forEach(r => r.classList.remove('active'));
720
+ const activeRow = document.querySelector('[data-frame-index="' + index + '"]');
721
+ if (activeRow) activeRow.classList.add('active');
722
+
723
+ const f = allFrames[index];
724
+ if (!f) return;
725
+
726
+ document.getElementById('editor-file-label').textContent = (f.file || 'Unknown') + ':' + (f.line || 1);
727
+ const container = document.getElementById('editor-code-container');
728
+
729
+ if (f.snippet && f.snippet.lines) {
730
+ document.getElementById('editor-lines-label').textContent = 'Lines ' + f.snippet.start + '–' + f.snippet.end;
731
+ container.innerHTML = f.snippet.lines.map(l => {
732
+ return '<div class="code-row' + (l.isTarget ? ' target' : '') + '">' +
733
+ '<span class="line-no">' + l.num + '</span>' +
734
+ '<span class="line-text">' + escapeHtmlText(l.code) + '</span>' +
735
+ '</div>';
736
+ }).join('');
737
+ } else {
738
+ document.getElementById('editor-lines-label').textContent = '';
739
+ container.innerHTML = '<p style="padding: 16px; color: #a1a1aa;">Source file not accessible or internal runtime frame.</p>';
740
+ }
741
+ }
742
+
743
+ function escapeHtmlText(str) {
744
+ return String(str || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
745
+ }
746
+
747
+ function copyDiagnosticReport() {
748
+ const text = document.getElementById('markdown-preview-block').textContent;
749
+ navigator.clipboard.writeText(text).then(() => {
750
+ const btn = document.getElementById('copy-report-btn');
751
+ if (!btn) return;
752
+ const orig = btn.textContent;
753
+ btn.textContent = 'Copied Markdown';
754
+ setTimeout(() => { btn.textContent = orig; }, 2000);
755
+ });
756
+ }
757
+
758
+ function copyJsonReport() {
759
+ const data = {
760
+ name: ${safeJson(errName)},
761
+ message: ${safeJson(errMessage)},
762
+ status: ${Number(status) || 500},
763
+ frames: allFrames,
764
+ timestamp: new Date().toISOString()
765
+ };
766
+ navigator.clipboard.writeText(JSON.stringify(data, null, 2));
767
+ }
768
+ </script>
769
+ </body>
770
+ </html>`;
771
+ }
772
+
773
+ module.exports = { renderErrorPage, parseStack, getCodeSnippet, generateSmartSolution };