@stacksjs/error-handling 0.70.258 → 0.70.260
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/dist/error-page-highlighter.js +1 -39
- package/dist/error-page-renderer.js +1 -21
- package/dist/error-page-styles.js +2 -2
- package/dist/error-page-template.js +21 -166
- package/dist/error-page-view-model.js +3 -166
- package/dist/error-page.js +8 -351
- package/dist/handler.js +6 -107
- package/dist/http.js +1 -165
- package/dist/index.js +1 -20
- package/dist/model.js +1 -8
- package/dist/utils.js +1 -17
- package/package.json +16 -9
|
@@ -1,39 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
let sharedHighlighter = null, sharedCss = null;
|
|
3
|
-
async function getHighlighter() {
|
|
4
|
-
if (!sharedHighlighter)
|
|
5
|
-
sharedHighlighter = await createHighlighter({
|
|
6
|
-
theme: "github-light",
|
|
7
|
-
cache: !0
|
|
8
|
-
});
|
|
9
|
-
return sharedHighlighter;
|
|
10
|
-
}
|
|
11
|
-
export function languageForFile(file) {
|
|
12
|
-
const ext = file.includes(".") ? `.${file.split(".").pop()}` : file, byExt = detectLanguageFromExtension(file) ?? getLanguageByExtension(ext);
|
|
13
|
-
if (byExt)
|
|
14
|
-
return byExt.id;
|
|
15
|
-
if (ext === ".php")
|
|
16
|
-
return "php";
|
|
17
|
-
if (ext === ".sql")
|
|
18
|
-
return "sql";
|
|
19
|
-
return "text";
|
|
20
|
-
}
|
|
21
|
-
export async function highlightSnippet(code, file, highlightLine, startingLine) {
|
|
22
|
-
const highlighter = await getHighlighter(), lang = languageForFile(file), relativeHighlight = Math.max(1, highlightLine - startingLine + 1), result = await highlighter.highlight(code, lang, {
|
|
23
|
-
lineNumbers: !0,
|
|
24
|
-
highlightLines: [relativeHighlight]
|
|
25
|
-
});
|
|
26
|
-
if (result.css && !sharedCss)
|
|
27
|
-
sharedCss = result.css;
|
|
28
|
-
return {
|
|
29
|
-
html: result.html,
|
|
30
|
-
css: sharedCss ?? result.css ?? ""
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
export function getSharedHighlighterCss() {
|
|
34
|
-
return sharedCss ?? "";
|
|
35
|
-
}
|
|
36
|
-
export function resetHighlighterCache() {
|
|
37
|
-
sharedHighlighter = null;
|
|
38
|
-
sharedCss = null;
|
|
39
|
-
}
|
|
1
|
+
import{createHighlighter,detectLanguageFromExtension,getLanguageByExtension}from"ts-syntax-highlighter";let sharedHighlighter=null,sharedCss=null;async function getHighlighter(){if(!sharedHighlighter)sharedHighlighter=await createHighlighter({theme:"github-light",cache:!0});return sharedHighlighter}export function languageForFile(file){const ext=file.includes(".")?`.${file.split(".").pop()}`:file,byExt=detectLanguageFromExtension(file)??getLanguageByExtension(ext);if(byExt)return byExt.id;if(ext===".php")return"php";if(ext===".sql")return"sql";return"text"}export async function highlightSnippet(code,file,highlightLine,startingLine){const highlighter=await getHighlighter(),lang=languageForFile(file),relativeHighlight=Math.max(1,highlightLine-startingLine+1),result=await highlighter.highlight(code,lang,{lineNumbers:!0,highlightLines:[relativeHighlight]});if(result.css&&!sharedCss)sharedCss=result.css;return{html:result.html,css:sharedCss??result.css??""}}export function getSharedHighlighterCss(){return sharedCss??""}export function resetHighlighterCache(){sharedHighlighter=null;sharedCss=null}
|
|
@@ -1,21 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { buildErrorPageViewModel } from "./error-page-view-model";
|
|
4
|
-
const VIEWS_DIR = join(dirname(fileURLToPath(import.meta.url)), "views/errors");
|
|
5
|
-
let renderTemplateFn = null;
|
|
6
|
-
async function getRenderTemplate() {
|
|
7
|
-
if (!renderTemplateFn)
|
|
8
|
-
renderTemplateFn = (await import("@stacksjs/stx")).renderTemplate;
|
|
9
|
-
return renderTemplateFn;
|
|
10
|
-
}
|
|
11
|
-
export async function renderDevErrorPage(opts) {
|
|
12
|
-
const viewModel = await buildErrorPageViewModel(opts);
|
|
13
|
-
return (await getRenderTemplate())(join(VIEWS_DIR, "show.stx"), {
|
|
14
|
-
context: viewModel,
|
|
15
|
-
layout: join(VIEWS_DIR, "layout.stx"),
|
|
16
|
-
injectCSS: !0,
|
|
17
|
-
templateOnly: !0,
|
|
18
|
-
processClientScripts: !1,
|
|
19
|
-
title: viewModel.pageTitle
|
|
20
|
-
});
|
|
21
|
-
}
|
|
1
|
+
import{dirname,join}from"node:path";import{fileURLToPath}from"node:url";import{buildErrorPageViewModel}from"./error-page-view-model";const VIEWS_DIR=join(dirname(fileURLToPath(import.meta.url)),"views/errors");let renderTemplateFn=null;async function getRenderTemplate(){if(!renderTemplateFn)renderTemplateFn=(await import("@stacksjs/stx")).renderTemplate;return renderTemplateFn}export async function renderDevErrorPage(opts){const viewModel=await buildErrorPageViewModel(opts);return(await getRenderTemplate())(join(VIEWS_DIR,"show.stx"),{context:viewModel,layout:join(VIEWS_DIR,"layout.stx"),injectCSS:!0,templateOnly:!0,processClientScripts:!1,title:viewModel.pageTitle})}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const ERROR_PAGE_CSS
|
|
1
|
+
export const ERROR_PAGE_CSS=`
|
|
2
2
|
:root {
|
|
3
3
|
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
4
4
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
@@ -617,4 +617,4 @@ export const ERROR_PAGE_CSS = `
|
|
|
617
617
|
@media (prefers-color-scheme: dark) {
|
|
618
618
|
.production-link { color: var(--blue-500); }
|
|
619
619
|
}
|
|
620
|
-
`;
|
|
620
|
+
`;
|
|
@@ -1,178 +1,36 @@
|
|
|
1
|
-
var {require}=import.meta;import
|
|
2
|
-
export function
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
export function readCodeSnippet(filePath, line, radius = 6) {
|
|
6
|
-
try {
|
|
7
|
-
const fs = require("node:fs");
|
|
8
|
-
if (!filePath || !fs.existsSync(filePath))
|
|
9
|
-
return null;
|
|
10
|
-
const allLines = fs.readFileSync(filePath, "utf-8").split(`
|
|
11
|
-
`), start = Math.max(0, line - radius - 1), end = Math.min(allLines.length, line + radius);
|
|
12
|
-
return {
|
|
13
|
-
lines: allLines.slice(start, end).map((code, i) => ({
|
|
14
|
-
number: start + i + 1,
|
|
15
|
-
content: code,
|
|
16
|
-
highlight: start + i + 1 === line
|
|
17
|
-
}))
|
|
18
|
-
};
|
|
19
|
-
} catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
export function renderCodeSnippet(snippet) {
|
|
24
|
-
return `<div class="code-block">${snippet.lines.map((l) => `
|
|
25
|
-
<div class="code-line${l.highlight ? " highlight" : ""}">
|
|
1
|
+
var {require}=import.meta;import{ERROR_PAGE_CSS}from"./error-page-styles";export function escapeHtml(str){return str.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}export function readCodeSnippet(filePath,line,radius=6){try{const fs=require("node:fs");if(!filePath||!fs.existsSync(filePath))return null;const allLines=fs.readFileSync(filePath,"utf-8").split(`
|
|
2
|
+
`),start=Math.max(0,line-radius-1),end=Math.min(allLines.length,line+radius);return{lines:allLines.slice(start,end).map((code,i)=>({number:start+i+1,content:code,highlight:start+i+1===line}))}}catch{return null}}export function renderCodeSnippet(snippet){return`<div class="code-block">${snippet.lines.map((l)=>`
|
|
3
|
+
<div class="code-line${l.highlight?" highlight":""}">
|
|
26
4
|
<span class="code-ln">${l.number}</span>
|
|
27
|
-
<span class="code-txt">${escapeHtml(l.content
|
|
5
|
+
<span class="code-txt">${escapeHtml(l.content||" ")}</span>
|
|
28
6
|
</div>`).join("")}
|
|
29
|
-
</div
|
|
30
|
-
}
|
|
31
|
-
export function renderTraceFrame(frame, index, snippetLines) {
|
|
32
|
-
const snippet = readCodeSnippet(frame.absoluteFile, frame.line, snippetLines), snippetHtml = snippet ? renderCodeSnippet(snippet) : "";
|
|
33
|
-
return `<div class="trace-frame${index === 0 ? " expanded" : ""}" data-frame>
|
|
7
|
+
</div>`}export function renderTraceFrame(frame,index,snippetLines){const snippet=readCodeSnippet(frame.absoluteFile,frame.line,snippetLines),snippetHtml=snippet?renderCodeSnippet(snippet):"";return`<div class="trace-frame${index===0?" expanded":""}" data-frame>
|
|
34
8
|
<div class="trace-frame-header" onclick="this.parentElement.classList.toggle('expanded')">
|
|
35
|
-
<span class="trace-frame-fn">${escapeHtml(frame.function
|
|
9
|
+
<span class="trace-frame-fn">${escapeHtml(frame.function||"<anonymous>")}</span>
|
|
36
10
|
<span class="trace-frame-file">${escapeHtml(frame.file)}:${frame.line}</span>
|
|
37
11
|
</div>
|
|
38
|
-
${snippetHtml
|
|
39
|
-
</div
|
|
40
|
-
}
|
|
41
|
-
export function renderVendorGroup(frames, snippetLines) {
|
|
42
|
-
const count = frames.length, label = count === 1 ? "1 framework frame" : `${count} framework frames`, inner = frames.map((f, i) => renderTraceFrame(f, i + 1, snippetLines)).join("");
|
|
43
|
-
return `<div class="trace-vendor" data-vendor-group>
|
|
12
|
+
${snippetHtml?`<div class="trace-frame-body">${snippetHtml}</div>`:""}
|
|
13
|
+
</div>`}export function renderVendorGroup(frames,snippetLines){const count=frames.length,label=count===1?"1 framework frame":`${count} framework frames`,inner=frames.map((f,i)=>renderTraceFrame(f,i+1,snippetLines)).join("");return`<div class="trace-vendor" data-vendor-group>
|
|
44
14
|
<div class="trace-vendor-toggle" onclick="this.parentElement.classList.toggle('expanded')">
|
|
45
15
|
<span>${escapeHtml(label)}</span>
|
|
46
16
|
</div>
|
|
47
17
|
<div class="trace-vendor-frames">${inner}</div>
|
|
48
|
-
</div
|
|
49
|
-
}
|
|
50
|
-
export function groupTraceFrames(frames) {
|
|
51
|
-
const groups = [];
|
|
52
|
-
let vendorBatch = [];
|
|
53
|
-
const flushVendor = () => {
|
|
54
|
-
if (vendorBatch.length > 0) {
|
|
55
|
-
groups.push(vendorBatch.length === 1 ? vendorBatch[0] : [...vendorBatch]);
|
|
56
|
-
vendorBatch = [];
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
for (const frame of frames)
|
|
60
|
-
if (frame.isFramework)
|
|
61
|
-
vendorBatch.push(frame);
|
|
62
|
-
else {
|
|
63
|
-
flushVendor();
|
|
64
|
-
groups.push(frame);
|
|
65
|
-
}
|
|
66
|
-
flushVendor();
|
|
67
|
-
return groups;
|
|
68
|
-
}
|
|
69
|
-
export function renderExceptionTrace(frames, snippetLines) {
|
|
70
|
-
if (frames.length === 0)
|
|
71
|
-
return "";
|
|
72
|
-
return `<section class="trace">
|
|
18
|
+
</div>`}export function groupTraceFrames(frames){const groups=[];let vendorBatch=[];const flushVendor=()=>{if(vendorBatch.length>0){groups.push(vendorBatch.length===1?vendorBatch[0]:[...vendorBatch]);vendorBatch=[]}};for(const frame of frames)if(frame.isFramework)vendorBatch.push(frame);else{flushVendor();groups.push(frame)}flushVendor();return groups}export function renderExceptionTrace(frames,snippetLines){if(frames.length===0)return"";return`<section class="trace">
|
|
73
19
|
<h2 class="trace-title">Exception trace</h2>
|
|
74
|
-
${groupTraceFrames(frames).map((group,
|
|
75
|
-
|
|
76
|
-
return renderVendorGroup(group, snippetLines);
|
|
77
|
-
return renderTraceFrame(group, i, snippetLines);
|
|
78
|
-
}).join("")}
|
|
79
|
-
</section>`;
|
|
80
|
-
}
|
|
81
|
-
export function renderContextTabs(opts) {
|
|
82
|
-
const tabs = [];
|
|
83
|
-
if (opts.request) {
|
|
84
|
-
const headerRows = Object.entries(opts.request.headers).map(([k, v]) => `<tr><td>${escapeHtml(k)}</td><td>${escapeHtml(v)}</td></tr>`).join("");
|
|
85
|
-
tabs.push({
|
|
86
|
-
id: "headers",
|
|
87
|
-
label: "Headers",
|
|
88
|
-
html: headerRows ? `<table class="kv-table">${headerRows}</table>` : '<p style="color:var(--neutral-500);font-size:0.875rem">No headers</p>'
|
|
89
|
-
});
|
|
90
|
-
const bodyContent = opts.request.body ? `<div class="json-block">${escapeHtml(JSON.stringify(opts.request.body, null, 2))}</div>` : '<p style="color:var(--neutral-500);font-size:0.875rem">// No request body</p>';
|
|
91
|
-
tabs.push({
|
|
92
|
-
id: "body",
|
|
93
|
-
label: "Body",
|
|
94
|
-
html: bodyContent
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
if (opts.routing) {
|
|
98
|
-
const rows = [
|
|
99
|
-
opts.routing.controller ? `<tr><td>controller</td><td>${escapeHtml(opts.routing.controller)}</td></tr>` : "",
|
|
100
|
-
opts.routing.routeName ? `<tr><td>route name</td><td>${escapeHtml(opts.routing.routeName)}</td></tr>` : "",
|
|
101
|
-
opts.routing.middleware?.length ? `<tr><td>middleware</td><td>${escapeHtml(opts.routing.middleware.join(", "))}</td></tr>` : ""
|
|
102
|
-
].filter(Boolean).join("");
|
|
103
|
-
if (rows)
|
|
104
|
-
tabs.push({
|
|
105
|
-
id: "routing",
|
|
106
|
-
label: "Routing",
|
|
107
|
-
html: `<table class="kv-table">${rows}</table>`
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
if (opts.user) {
|
|
111
|
-
const rows = [
|
|
112
|
-
opts.user.id !== void 0 ? `<tr><td>id</td><td>${escapeHtml(String(opts.user.id))}</td></tr>` : "",
|
|
113
|
-
opts.user.email ? `<tr><td>email</td><td>${escapeHtml(opts.user.email)}</td></tr>` : "",
|
|
114
|
-
opts.user.name ? `<tr><td>name</td><td>${escapeHtml(opts.user.name)}</td></tr>` : ""
|
|
115
|
-
].filter(Boolean).join("");
|
|
116
|
-
if (rows)
|
|
117
|
-
tabs.push({
|
|
118
|
-
id: "user",
|
|
119
|
-
label: "User",
|
|
120
|
-
html: `<table class="kv-table">${rows}</table>`
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
if (opts.queries && opts.queries.length > 0)
|
|
124
|
-
tabs.push({
|
|
125
|
-
id: "queries",
|
|
126
|
-
label: `Queries (${opts.queries.length})`,
|
|
127
|
-
html: opts.queries.map((q) => `
|
|
20
|
+
${groupTraceFrames(frames).map((group,i)=>{if(Array.isArray(group))return renderVendorGroup(group,snippetLines);return renderTraceFrame(group,i,snippetLines)}).join("")}
|
|
21
|
+
</section>`}export function renderContextTabs(opts){const tabs=[];if(opts.request){const headerRows=Object.entries(opts.request.headers).map(([k,v])=>`<tr><td>${escapeHtml(k)}</td><td>${escapeHtml(v)}</td></tr>`).join("");tabs.push({id:"headers",label:"Headers",html:headerRows?`<table class="kv-table">${headerRows}</table>`:'<p style="color:var(--neutral-500);font-size:0.875rem">No headers</p>'});const bodyContent=opts.request.body?`<div class="json-block">${escapeHtml(JSON.stringify(opts.request.body,null,2))}</div>`:'<p style="color:var(--neutral-500);font-size:0.875rem">// No request body</p>';tabs.push({id:"body",label:"Body",html:bodyContent})}if(opts.routing){const rows=[opts.routing.controller?`<tr><td>controller</td><td>${escapeHtml(opts.routing.controller)}</td></tr>`:"",opts.routing.routeName?`<tr><td>route name</td><td>${escapeHtml(opts.routing.routeName)}</td></tr>`:"",opts.routing.middleware?.length?`<tr><td>middleware</td><td>${escapeHtml(opts.routing.middleware.join(", "))}</td></tr>`:""].filter(Boolean).join("");if(rows)tabs.push({id:"routing",label:"Routing",html:`<table class="kv-table">${rows}</table>`})}if(opts.user){const rows=[opts.user.id!==void 0?`<tr><td>id</td><td>${escapeHtml(String(opts.user.id))}</td></tr>`:"",opts.user.email?`<tr><td>email</td><td>${escapeHtml(opts.user.email)}</td></tr>`:"",opts.user.name?`<tr><td>name</td><td>${escapeHtml(opts.user.name)}</td></tr>`:""].filter(Boolean).join("");if(rows)tabs.push({id:"user",label:"User",html:`<table class="kv-table">${rows}</table>`})}if(opts.queries&&opts.queries.length>0)tabs.push({id:"queries",label:`Queries (${opts.queries.length})`,html:opts.queries.map((q)=>`
|
|
128
22
|
<div class="query-item">
|
|
129
23
|
${escapeHtml(q.query)}
|
|
130
|
-
${q.time
|
|
131
|
-
</div>`).join("")
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
id: "
|
|
136
|
-
label: "Environment",
|
|
137
|
-
html: `<table class="kv-table">
|
|
138
|
-
<tr><td>runtime</td><td>${typeof process < "u" ? escapeHtml(process.version) : "N/A"}</td></tr>
|
|
139
|
-
<tr><td>platform</td><td>${typeof process < "u" ? escapeHtml(process.platform) : "N/A"}</td></tr>
|
|
140
|
-
<tr><td>arch</td><td>${typeof process < "u" ? escapeHtml(process.arch) : "N/A"}</td></tr>
|
|
141
|
-
</table>`
|
|
142
|
-
});
|
|
143
|
-
if (tabs.length === 0)
|
|
144
|
-
return "";
|
|
145
|
-
const tabButtons = tabs.map((t, i) => `<button class="context-tab${i === 0 ? " active" : ""}" data-tab="${t.id}" type="button">${escapeHtml(t.label)}</button>`).join(""), panels = tabs.map((t, i) => `<div class="context-panel${i === 0 ? " active" : ""}" data-panel="${t.id}">${t.html}</div>`).join("");
|
|
146
|
-
return `<section class="context">
|
|
24
|
+
${q.time!==void 0?`<div class="query-time">${q.time.toFixed(2)}ms${q.connection?` \u2022 ${escapeHtml(q.connection)}`:""}</div>`:""}
|
|
25
|
+
</div>`).join("")});if(opts.showEnvironment)tabs.push({id:"environment",label:"Environment",html:`<table class="kv-table">
|
|
26
|
+
<tr><td>runtime</td><td>${typeof process<"u"?escapeHtml(process.version):"N/A"}</td></tr>
|
|
27
|
+
<tr><td>platform</td><td>${typeof process<"u"?escapeHtml(process.platform):"N/A"}</td></tr>
|
|
28
|
+
<tr><td>arch</td><td>${typeof process<"u"?escapeHtml(process.arch):"N/A"}</td></tr>
|
|
29
|
+
</table>`});if(tabs.length===0)return"";const tabButtons=tabs.map((t,i)=>`<button class="context-tab${i===0?" active":""}" data-tab="${t.id}" type="button">${escapeHtml(t.label)}</button>`).join(""),panels=tabs.map((t,i)=>`<div class="context-panel${i===0?" active":""}" data-panel="${t.id}">${t.html}</div>`).join("");return`<section class="context">
|
|
147
30
|
<div class="context-tabs">${tabButtons}</div>
|
|
148
31
|
${panels}
|
|
149
|
-
</section
|
|
150
|
-
}
|
|
151
|
-
export function buildErrorMarkdown(opts) {
|
|
152
|
-
const lines = [
|
|
153
|
-
`# ${opts.statusTitle}`,
|
|
154
|
-
"",
|
|
155
|
-
`## ${opts.errorName}`,
|
|
156
|
-
"",
|
|
157
|
-
opts.errorMessage,
|
|
158
|
-
""
|
|
159
|
-
];
|
|
160
|
-
if (opts.file)
|
|
161
|
-
lines.push(`**${opts.file}${opts.line ? `:${opts.line}` : ""}**`, "");
|
|
162
|
-
if (opts.framework)
|
|
163
|
-
lines.push(`**${opts.framework.name.toUpperCase()}** ${opts.framework.version ?? ""}`.trim(), "");
|
|
164
|
-
lines.push(`**${opts.status}**`, "");
|
|
165
|
-
if (opts.request)
|
|
166
|
-
lines.push(`\`${opts.request.method}\` ${opts.request.url}`, "");
|
|
167
|
-
if (opts.frames.length > 0) {
|
|
168
|
-
lines.push("## Exception trace", "");
|
|
169
|
-
for (const frame of opts.frames.slice(0, 20))
|
|
170
|
-
lines.push(`\`${frame.function || "<anonymous>"}\` \u2014 ${frame.file}:${frame.line}`);
|
|
171
|
-
}
|
|
172
|
-
return lines.join(`
|
|
173
|
-
`);
|
|
174
|
-
}
|
|
175
|
-
export const ERROR_PAGE_SCRIPT = `
|
|
32
|
+
</section>`}export function buildErrorMarkdown(opts){const lines=[`# ${opts.statusTitle}`,"",`## ${opts.errorName}`,"",opts.errorMessage,""];if(opts.file)lines.push(`**${opts.file}${opts.line?`:${opts.line}`:""}**`,"");if(opts.framework)lines.push(`**${opts.framework.name.toUpperCase()}** ${opts.framework.version??""}`.trim(),"");lines.push(`**${opts.status}**`,"");if(opts.request)lines.push(`\`${opts.request.method}\` ${opts.request.url}`,"");if(opts.frames.length>0){lines.push("## Exception trace","");for(const frame of opts.frames.slice(0,20))lines.push(`\`${frame.function||"<anonymous>"}\` \u2014 ${frame.file}:${frame.line}`)}return lines.join(`
|
|
33
|
+
`)}export const ERROR_PAGE_SCRIPT=`
|
|
176
34
|
function initErrorPage() {
|
|
177
35
|
const copyBtn = document.getElementById('copy-markdown');
|
|
178
36
|
const markdown = document.getElementById('error-markdown');
|
|
@@ -204,9 +62,7 @@ function initErrorPage() {
|
|
|
204
62
|
});
|
|
205
63
|
}
|
|
206
64
|
document.addEventListener('DOMContentLoaded', initErrorPage);
|
|
207
|
-
`;
|
|
208
|
-
export function wrapErrorPage(body, markdown) {
|
|
209
|
-
return `<!DOCTYPE html>
|
|
65
|
+
`;export function wrapErrorPage(body,markdown){return`<!DOCTYPE html>
|
|
210
66
|
<html lang="en">
|
|
211
67
|
<head>
|
|
212
68
|
<meta charset="UTF-8">
|
|
@@ -220,5 +76,4 @@ export function wrapErrorPage(body, markdown) {
|
|
|
220
76
|
<script id="error-markdown" type="text/plain">${escapeHtml(markdown)}</script>
|
|
221
77
|
<script>${ERROR_PAGE_SCRIPT}</script>
|
|
222
78
|
</body>
|
|
223
|
-
</html
|
|
224
|
-
}
|
|
79
|
+
</html>`}
|
|
@@ -1,166 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
function parseStackTrace(stack, basePaths, options = {}) {
|
|
5
|
-
if (!stack)
|
|
6
|
-
return [];
|
|
7
|
-
const lines = stack.split(`
|
|
8
|
-
`).slice(1), frames = [], includeAll = options.includeFrameworkFrames === !0;
|
|
9
|
-
for (const line of lines) {
|
|
10
|
-
const match = line.match(/^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/);
|
|
11
|
-
if (match) {
|
|
12
|
-
let file = match[2];
|
|
13
|
-
if (file === void 0)
|
|
14
|
-
continue;
|
|
15
|
-
const original = file, isFramework = isFrameworkFrame(original);
|
|
16
|
-
if (basePaths) {
|
|
17
|
-
for (const basePath of basePaths)
|
|
18
|
-
if (file.startsWith(basePath)) {
|
|
19
|
-
file = file.slice(basePath.length + 1);
|
|
20
|
-
break;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
if (!includeAll && isFramework)
|
|
24
|
-
continue;
|
|
25
|
-
frames.push({
|
|
26
|
-
function: match[1] || "<anonymous>",
|
|
27
|
-
file,
|
|
28
|
-
absoluteFile: original,
|
|
29
|
-
isFramework,
|
|
30
|
-
line: parseInt(match[3] ?? "0", 10),
|
|
31
|
-
column: parseInt(match[4] ?? "0", 10)
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (!includeAll && frames.length === 0)
|
|
36
|
-
return parseStackTrace(stack, basePaths, { includeFrameworkFrames: !0 });
|
|
37
|
-
return frames;
|
|
38
|
-
}
|
|
39
|
-
function groupFrames(frames) {
|
|
40
|
-
const groups = [];
|
|
41
|
-
let vendorBatch = [];
|
|
42
|
-
const flushVendor = () => {
|
|
43
|
-
if (vendorBatch.length > 0) {
|
|
44
|
-
groups.push(vendorBatch.length === 1 ? vendorBatch[0] : [...vendorBatch]);
|
|
45
|
-
vendorBatch = [];
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
for (const frame of frames)
|
|
49
|
-
if (frame.isFramework)
|
|
50
|
-
vendorBatch.push(frame);
|
|
51
|
-
else {
|
|
52
|
-
flushVendor();
|
|
53
|
-
groups.push(frame);
|
|
54
|
-
}
|
|
55
|
-
flushVendor();
|
|
56
|
-
return groups;
|
|
57
|
-
}
|
|
58
|
-
async function buildFrameView(frame, index, snippetLines) {
|
|
59
|
-
const snippet = readCodeSnippet(frame.absoluteFile, frame.line, snippetLines);
|
|
60
|
-
let codeHtml = "";
|
|
61
|
-
if (snippet) {
|
|
62
|
-
const code = snippet.lines.map((l) => l.content).join(`
|
|
63
|
-
`), startingLine = snippet.lines[0]?.number ?? frame.line;
|
|
64
|
-
codeHtml = (await highlightSnippet(code, frame.file, frame.line, startingLine)).html;
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
function: frame.function || "<anonymous>",
|
|
68
|
-
file: frame.file,
|
|
69
|
-
line: frame.line,
|
|
70
|
-
expanded: index === 0,
|
|
71
|
-
hasCode: codeHtml.length > 0,
|
|
72
|
-
codeHtml,
|
|
73
|
-
isVendor: frame.isFramework
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
export async function buildErrorPageViewModel(opts) {
|
|
77
|
-
const { error, status, config } = opts, statusTitle = HTTP_ERROR_MAP[status]?.title ?? "Error", frames = parseStackTrace(error.stack, config.basePaths, {
|
|
78
|
-
includeFrameworkFrames: config.showFrameworkFrames === !0
|
|
79
|
-
}), topFrame = frames[0], runtimeVersion = typeof process < "u" ? process.version.replace(/^v/, "") : "", runtimeLabel = typeof process < "u" && process.versions.bun ? "BUN" : "NODE", errorCode = error.code, traceGroups = [];
|
|
80
|
-
let frameIndex = 0;
|
|
81
|
-
for (const group of groupFrames(frames))
|
|
82
|
-
if (Array.isArray(group)) {
|
|
83
|
-
const vendorFrames = await Promise.all(group.map((f, i) => buildFrameView(f, frameIndex + i, config.snippetLines ?? 8)));
|
|
84
|
-
traceGroups.push({ type: "vendor", frames: vendorFrames, vendorCount: vendorFrames.length });
|
|
85
|
-
frameIndex += vendorFrames.length;
|
|
86
|
-
} else {
|
|
87
|
-
traceGroups.push({
|
|
88
|
-
type: "frame",
|
|
89
|
-
frames: [await buildFrameView(group, frameIndex, config.snippetLines ?? 8)],
|
|
90
|
-
vendorCount: 0
|
|
91
|
-
});
|
|
92
|
-
frameIndex += 1;
|
|
93
|
-
}
|
|
94
|
-
const headers = opts.request ? Object.entries(opts.request.headers).map(([key, value]) => ({ key, value })) : [], routing = [];
|
|
95
|
-
if (opts.routing?.controller)
|
|
96
|
-
routing.push({ key: "controller", value: opts.routing.controller });
|
|
97
|
-
if (opts.routing?.routeName)
|
|
98
|
-
routing.push({ key: "route name", value: opts.routing.routeName });
|
|
99
|
-
if (opts.routing?.middleware?.length)
|
|
100
|
-
routing.push({ key: "middleware", value: opts.routing.middleware.join(", ") });
|
|
101
|
-
const userRows = [];
|
|
102
|
-
if (opts.user?.id !== void 0)
|
|
103
|
-
userRows.push({ key: "id", value: String(opts.user.id) });
|
|
104
|
-
if (opts.user?.email)
|
|
105
|
-
userRows.push({ key: "email", value: opts.user.email });
|
|
106
|
-
if (opts.user?.name)
|
|
107
|
-
userRows.push({ key: "name", value: opts.user.name });
|
|
108
|
-
const environmentRows = config.showEnvironment && typeof process < "u" ? [
|
|
109
|
-
{ key: "runtime", value: process.version },
|
|
110
|
-
{ key: "platform", value: process.platform },
|
|
111
|
-
{ key: "arch", value: process.arch }
|
|
112
|
-
] : [], queries = (opts.queries ?? []).slice(0, 100).map((q) => ({
|
|
113
|
-
connection: q.connection ?? "default",
|
|
114
|
-
sql: q.query,
|
|
115
|
-
sqlHtml: escapeHtml(q.query),
|
|
116
|
-
time: q.time !== void 0 ? q.time.toFixed(2) : "0.00"
|
|
117
|
-
})), bodyContent = opts.request?.body ? JSON.stringify(opts.request.body, null, 2) : "// No request body", routeParamsJson = opts.request?.queryParams && Object.keys(opts.request.queryParams).length > 0 ? JSON.stringify(opts.request.queryParams, null, 2) : "", markdown = buildErrorMarkdown({
|
|
118
|
-
statusTitle,
|
|
119
|
-
errorName: error.name || "Error",
|
|
120
|
-
errorMessage: error.message,
|
|
121
|
-
status,
|
|
122
|
-
file: topFrame?.file,
|
|
123
|
-
line: topFrame?.line,
|
|
124
|
-
request: opts.request,
|
|
125
|
-
framework: opts.framework,
|
|
126
|
-
frames
|
|
127
|
-
}), { getSharedHighlighterCss } = await import("./error-page-highlighter");
|
|
128
|
-
return {
|
|
129
|
-
pageTitle: statusTitle,
|
|
130
|
-
statusTitle,
|
|
131
|
-
markdownJson: JSON.stringify(markdown),
|
|
132
|
-
exceptionClass: error.name || "Error",
|
|
133
|
-
message: error.message,
|
|
134
|
-
fileLine: topFrame ? `${topFrame.file}:${topFrame.line}` : "",
|
|
135
|
-
statusCode: status,
|
|
136
|
-
errorCode: errorCode !== void 0 ? String(errorCode) : "0",
|
|
137
|
-
frameworkLabel: opts.framework?.name?.toUpperCase() ?? "STACKS",
|
|
138
|
-
frameworkVersion: opts.framework?.version ?? "",
|
|
139
|
-
runtimeLabel,
|
|
140
|
-
runtimeVersion,
|
|
141
|
-
requestMethod: opts.request?.method ?? "GET",
|
|
142
|
-
requestUrl: opts.request?.url ?? "",
|
|
143
|
-
hasRequest: !!opts.request,
|
|
144
|
-
hintsHtml: renderHttpErrorHints(status),
|
|
145
|
-
traceGroups,
|
|
146
|
-
queries,
|
|
147
|
-
queryCount: queries.length,
|
|
148
|
-
headers,
|
|
149
|
-
bodyContent,
|
|
150
|
-
routing,
|
|
151
|
-
routeParamsJson,
|
|
152
|
-
userRows,
|
|
153
|
-
environmentRows,
|
|
154
|
-
hasUser: userRows.length > 0,
|
|
155
|
-
hasEnvironment: environmentRows.length > 0,
|
|
156
|
-
hasQueries: queries.length > 0 && config.showQueries !== !1,
|
|
157
|
-
hasHeaders: headers.length > 0 && config.showRequest !== !1,
|
|
158
|
-
hasBody: config.showRequest !== !1,
|
|
159
|
-
hasRouting: routing.length > 0,
|
|
160
|
-
hasRouteParams: routeParamsJson.length > 0,
|
|
161
|
-
highlightCss: getSharedHighlighterCss(),
|
|
162
|
-
enableCopyMarkdown: config.enableCopyMarkdown !== !1
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export { parseStackTrace, groupFrames, languageForFile };
|
|
1
|
+
import{HTTP_ERRORS as HTTP_ERROR_MAP,isFrameworkFrame,renderHttpErrorHints}from"./error-page";import{highlightSnippet,languageForFile}from"./error-page-highlighter";import{buildErrorMarkdown,escapeHtml,readCodeSnippet}from"./error-page-template";function parseStackTrace(stack,basePaths,options={}){if(!stack)return[];const lines=stack.split(`
|
|
2
|
+
`).slice(1),frames=[],includeAll=options.includeFrameworkFrames===!0;for(const line of lines){const match=line.match(/^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/);if(match){let file=match[2];if(file===void 0)continue;const original=file,isFramework=isFrameworkFrame(original);if(basePaths){for(const basePath of basePaths)if(file.startsWith(basePath)){file=file.slice(basePath.length+1);break}}if(!includeAll&&isFramework)continue;frames.push({function:match[1]||"<anonymous>",file,absoluteFile:original,isFramework,line:parseInt(match[3]??"0",10),column:parseInt(match[4]??"0",10)})}}if(!includeAll&&frames.length===0)return parseStackTrace(stack,basePaths,{includeFrameworkFrames:!0});return frames}function groupFrames(frames){const groups=[];let vendorBatch=[];const flushVendor=()=>{if(vendorBatch.length>0){groups.push(vendorBatch.length===1?vendorBatch[0]:[...vendorBatch]);vendorBatch=[]}};for(const frame of frames)if(frame.isFramework)vendorBatch.push(frame);else{flushVendor();groups.push(frame)}flushVendor();return groups}async function buildFrameView(frame,index,snippetLines){const snippet=readCodeSnippet(frame.absoluteFile,frame.line,snippetLines);let codeHtml="";if(snippet){const code=snippet.lines.map((l)=>l.content).join(`
|
|
3
|
+
`),startingLine=snippet.lines[0]?.number??frame.line;codeHtml=(await highlightSnippet(code,frame.file,frame.line,startingLine)).html}return{function:frame.function||"<anonymous>",file:frame.file,line:frame.line,expanded:index===0,hasCode:codeHtml.length>0,codeHtml,isVendor:frame.isFramework}}export async function buildErrorPageViewModel(opts){const{error,status,config}=opts,statusTitle=HTTP_ERROR_MAP[status]?.title??"Error",frames=parseStackTrace(error.stack,config.basePaths,{includeFrameworkFrames:config.showFrameworkFrames===!0}),topFrame=frames[0],runtimeVersion=typeof process<"u"?process.version.replace(/^v/,""):"",runtimeLabel=typeof process<"u"&&process.versions.bun?"BUN":"NODE",errorCode=error.code,traceGroups=[];let frameIndex=0;for(const group of groupFrames(frames))if(Array.isArray(group)){const vendorFrames=await Promise.all(group.map((f,i)=>buildFrameView(f,frameIndex+i,config.snippetLines??8)));traceGroups.push({type:"vendor",frames:vendorFrames,vendorCount:vendorFrames.length});frameIndex+=vendorFrames.length}else{traceGroups.push({type:"frame",frames:[await buildFrameView(group,frameIndex,config.snippetLines??8)],vendorCount:0});frameIndex+=1}const headers=opts.request?Object.entries(opts.request.headers).map(([key,value])=>({key,value})):[],routing=[];if(opts.routing?.controller)routing.push({key:"controller",value:opts.routing.controller});if(opts.routing?.routeName)routing.push({key:"route name",value:opts.routing.routeName});if(opts.routing?.middleware?.length)routing.push({key:"middleware",value:opts.routing.middleware.join(", ")});const userRows=[];if(opts.user?.id!==void 0)userRows.push({key:"id",value:String(opts.user.id)});if(opts.user?.email)userRows.push({key:"email",value:opts.user.email});if(opts.user?.name)userRows.push({key:"name",value:opts.user.name});const environmentRows=config.showEnvironment&&typeof process<"u"?[{key:"runtime",value:process.version},{key:"platform",value:process.platform},{key:"arch",value:process.arch}]:[],queries=(opts.queries??[]).slice(0,100).map((q)=>({connection:q.connection??"default",sql:q.query,sqlHtml:escapeHtml(q.query),time:q.time!==void 0?q.time.toFixed(2):"0.00"})),bodyContent=opts.request?.body?JSON.stringify(opts.request.body,null,2):"// No request body",routeParamsJson=opts.request?.queryParams&&Object.keys(opts.request.queryParams).length>0?JSON.stringify(opts.request.queryParams,null,2):"",markdown=buildErrorMarkdown({statusTitle,errorName:error.name||"Error",errorMessage:error.message,status,file:topFrame?.file,line:topFrame?.line,request:opts.request,framework:opts.framework,frames}),{getSharedHighlighterCss}=await import("./error-page-highlighter");return{pageTitle:statusTitle,statusTitle,markdownJson:JSON.stringify(markdown),exceptionClass:error.name||"Error",message:error.message,fileLine:topFrame?`${topFrame.file}:${topFrame.line}`:"",statusCode:status,errorCode:errorCode!==void 0?String(errorCode):"0",frameworkLabel:opts.framework?.name?.toUpperCase()??"STACKS",frameworkVersion:opts.framework?.version??"",runtimeLabel,runtimeVersion,requestMethod:opts.request?.method??"GET",requestUrl:opts.request?.url??"",hasRequest:!!opts.request,hintsHtml:renderHttpErrorHints(status),traceGroups,queries,queryCount:queries.length,headers,bodyContent,routing,routeParamsJson,userRows,environmentRows,hasUser:userRows.length>0,hasEnvironment:environmentRows.length>0,hasQueries:queries.length>0&&config.showQueries!==!1,hasHeaders:headers.length>0&&config.showRequest!==!1,hasBody:config.showRequest!==!1,hasRouting:routing.length>0,hasRouteParams:routeParamsJson.length>0,highlightCss:getSharedHighlighterCss(),enableCopyMarkdown:config.enableCopyMarkdown!==!1}}export{parseStackTrace,groupFrames,languageForFile};
|
package/dist/error-page.js
CHANGED
|
@@ -1,234 +1,5 @@
|
|
|
1
|
-
var {require}=import.meta;const DOCS_BASE
|
|
2
|
-
export const HTTP_ERRORS =
|
|
3
|
-
400: {
|
|
4
|
-
status: 400,
|
|
5
|
-
title: "Bad Request",
|
|
6
|
-
message: "The request was malformed or invalid.",
|
|
7
|
-
docLink: `${DOCS_BASE}/400`,
|
|
8
|
-
commonCauses: [
|
|
9
|
-
"JSON body is missing or has a syntax error",
|
|
10
|
-
"A required field is absent from the payload",
|
|
11
|
-
"Content-Type header does not match the body format"
|
|
12
|
-
],
|
|
13
|
-
suggestion: "Inspect the request body and Content-Type \u2014 most 400s come from malformed JSON or a missing required field."
|
|
14
|
-
},
|
|
15
|
-
401: {
|
|
16
|
-
status: 401,
|
|
17
|
-
title: "Unauthorized",
|
|
18
|
-
message: "Authentication is required to access this resource.",
|
|
19
|
-
docLink: `${DOCS_BASE}/401`,
|
|
20
|
-
commonCauses: [
|
|
21
|
-
"No Authorization header was sent",
|
|
22
|
-
"The bearer token expired",
|
|
23
|
-
"The session cookie was cleared"
|
|
24
|
-
],
|
|
25
|
-
suggestion: "Confirm a valid `Authorization: Bearer <token>` header is sent and the token has not expired."
|
|
26
|
-
},
|
|
27
|
-
403: {
|
|
28
|
-
status: 403,
|
|
29
|
-
title: "Forbidden",
|
|
30
|
-
message: "You do not have permission to access this resource.",
|
|
31
|
-
docLink: `${DOCS_BASE}/403`,
|
|
32
|
-
commonCauses: [
|
|
33
|
-
"The authenticated user lacks the required ability or role",
|
|
34
|
-
"A Gate or policy denied access (see app/Gates.ts)",
|
|
35
|
-
"The token was issued without the needed ability"
|
|
36
|
-
],
|
|
37
|
-
suggestion: "Check Gates / policies and the abilities encoded in the access token."
|
|
38
|
-
},
|
|
39
|
-
404: {
|
|
40
|
-
status: 404,
|
|
41
|
-
title: "Not Found",
|
|
42
|
-
message: "The requested resource could not be found.",
|
|
43
|
-
docLink: `${DOCS_BASE}/404`,
|
|
44
|
-
commonCauses: [
|
|
45
|
-
"The route is not registered in app/Routes.ts",
|
|
46
|
-
"A typo in the URL path",
|
|
47
|
-
"A model lookup returned no row (ModelNotFoundError)"
|
|
48
|
-
],
|
|
49
|
-
suggestion: "Run `buddy route:list` to see registered routes, or verify the model exists with the given id."
|
|
50
|
-
},
|
|
51
|
-
405: {
|
|
52
|
-
status: 405,
|
|
53
|
-
title: "Method Not Allowed",
|
|
54
|
-
message: "The request method is not supported for this resource.",
|
|
55
|
-
docLink: `${DOCS_BASE}/405`,
|
|
56
|
-
commonCauses: [
|
|
57
|
-
"The route is registered for a different HTTP method",
|
|
58
|
-
"A form posted GET when the route expects POST"
|
|
59
|
-
],
|
|
60
|
-
suggestion: "Confirm the HTTP method in `app/Routes.ts` matches what the client sent."
|
|
61
|
-
},
|
|
62
|
-
408: {
|
|
63
|
-
status: 408,
|
|
64
|
-
title: "Request Timeout",
|
|
65
|
-
message: "The request took too long to complete.",
|
|
66
|
-
docLink: `${DOCS_BASE}/408`,
|
|
67
|
-
commonCauses: [
|
|
68
|
-
"A long-running query or external API call exceeded the timeout",
|
|
69
|
-
"The client uploaded a slow body that stalled"
|
|
70
|
-
],
|
|
71
|
-
suggestion: "Move slow work into a queued job, or raise the route timeout if the work is genuinely long."
|
|
72
|
-
},
|
|
73
|
-
409: {
|
|
74
|
-
status: 409,
|
|
75
|
-
title: "Conflict",
|
|
76
|
-
message: "The request conflicts with the current state of the resource.",
|
|
77
|
-
docLink: `${DOCS_BASE}/409`,
|
|
78
|
-
commonCauses: [
|
|
79
|
-
"A unique-constraint violation (duplicate email, slug, etc.)",
|
|
80
|
-
"Optimistic locking detected a stale write"
|
|
81
|
-
],
|
|
82
|
-
suggestion: "Re-fetch the resource and retry, or surface the conflict to the user."
|
|
83
|
-
},
|
|
84
|
-
410: {
|
|
85
|
-
status: 410,
|
|
86
|
-
title: "Gone",
|
|
87
|
-
message: "The requested resource is no longer available.",
|
|
88
|
-
docLink: `${DOCS_BASE}/410`,
|
|
89
|
-
commonCauses: ["The resource was permanently deleted", "A signed URL expired"],
|
|
90
|
-
suggestion: "Issue a fresh signed URL or fall back to the canonical resource."
|
|
91
|
-
},
|
|
92
|
-
422: {
|
|
93
|
-
status: 422,
|
|
94
|
-
title: "Unprocessable Entity",
|
|
95
|
-
message: "The request was well-formed but could not be processed.",
|
|
96
|
-
docLink: `${DOCS_BASE}/422`,
|
|
97
|
-
commonCauses: [
|
|
98
|
-
"Validation rules from the action / model rejected the payload",
|
|
99
|
-
"A field value is outside the allowed range or shape"
|
|
100
|
-
],
|
|
101
|
-
suggestion: "Inspect `errors` in the response body \u2014 each key maps to a failing field."
|
|
102
|
-
},
|
|
103
|
-
429: {
|
|
104
|
-
status: 429,
|
|
105
|
-
title: "Too Many Requests",
|
|
106
|
-
message: "You have exceeded the rate limit.",
|
|
107
|
-
docLink: `${DOCS_BASE}/429`,
|
|
108
|
-
commonCauses: ["Rate-limit middleware tripped on this IP / token", "A retry loop is hammering the endpoint"],
|
|
109
|
-
suggestion: "Honor the `Retry-After` response header and back off before retrying."
|
|
110
|
-
},
|
|
111
|
-
500: {
|
|
112
|
-
status: 500,
|
|
113
|
-
title: "Internal Server Error",
|
|
114
|
-
message: "An unexpected error occurred on the server.",
|
|
115
|
-
docLink: `${DOCS_BASE}/500`,
|
|
116
|
-
commonCauses: [
|
|
117
|
-
"An unhandled exception in an action or middleware",
|
|
118
|
-
"A failing database connection or migration",
|
|
119
|
-
"A misconfigured environment variable"
|
|
120
|
-
],
|
|
121
|
-
suggestion: "Check server logs for the original stack trace \u2014 the error page above shows the throw site in dev."
|
|
122
|
-
},
|
|
123
|
-
502: {
|
|
124
|
-
status: 502,
|
|
125
|
-
title: "Bad Gateway",
|
|
126
|
-
message: "The server received an invalid response from an upstream server.",
|
|
127
|
-
docLink: `${DOCS_BASE}/502`,
|
|
128
|
-
commonCauses: ["An upstream HTTP API returned a malformed response", "A reverse proxy could not reach the origin"],
|
|
129
|
-
suggestion: "Verify the upstream service is healthy and returning the expected content type."
|
|
130
|
-
},
|
|
131
|
-
503: {
|
|
132
|
-
status: 503,
|
|
133
|
-
title: "Service Unavailable",
|
|
134
|
-
message: "The service is temporarily unavailable.",
|
|
135
|
-
docLink: `${DOCS_BASE}/503`,
|
|
136
|
-
commonCauses: ["Maintenance mode is enabled", "A health check is failing", "A dependency (db, redis, queue) is down"],
|
|
137
|
-
suggestion: "Run `buddy doctor` and check dependent services."
|
|
138
|
-
},
|
|
139
|
-
504: {
|
|
140
|
-
status: 504,
|
|
141
|
-
title: "Gateway Timeout",
|
|
142
|
-
message: "The upstream server did not respond in time.",
|
|
143
|
-
docLink: `${DOCS_BASE}/504`,
|
|
144
|
-
commonCauses: ["An upstream HTTP call exceeded its deadline", "A long-running database query timed out"],
|
|
145
|
-
suggestion: "Move the work to a queued job or raise the upstream timeout if the latency is expected."
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
import {
|
|
149
|
-
buildErrorMarkdown,
|
|
150
|
-
escapeHtml,
|
|
151
|
-
renderExceptionTrace,
|
|
152
|
-
wrapErrorPage
|
|
153
|
-
} from "./error-page-template";
|
|
154
|
-
import { ERROR_PAGE_CSS } from "./error-page-styles";
|
|
155
|
-
|
|
156
|
-
export { ERROR_PAGE_CSS };
|
|
157
|
-
const FRAMEWORK_FRAME_FRAGMENTS = [
|
|
158
|
-
"/storage/framework/core/",
|
|
159
|
-
"/node_modules/@stacksjs/",
|
|
160
|
-
"node:internal/",
|
|
161
|
-
"bun:wrap"
|
|
162
|
-
];
|
|
163
|
-
export function isFrameworkFrame(file) {
|
|
164
|
-
if (!file)
|
|
165
|
-
return !1;
|
|
166
|
-
return FRAMEWORK_FRAME_FRAGMENTS.some((f) => file.includes(f));
|
|
167
|
-
}
|
|
168
|
-
function parseStackTrace(stack, basePaths, options = {}) {
|
|
169
|
-
if (!stack)
|
|
170
|
-
return [];
|
|
171
|
-
const lines = stack.split(`
|
|
172
|
-
`).slice(1), frames = [], includeAll = options.includeFrameworkFrames === !0;
|
|
173
|
-
for (const line of lines) {
|
|
174
|
-
const match = line.match(/^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/);
|
|
175
|
-
if (match) {
|
|
176
|
-
let file = match[2];
|
|
177
|
-
if (file === void 0)
|
|
178
|
-
continue;
|
|
179
|
-
const original = file, isFramework = isFrameworkFrame(original);
|
|
180
|
-
if (basePaths) {
|
|
181
|
-
for (const basePath of basePaths)
|
|
182
|
-
if (file.startsWith(basePath)) {
|
|
183
|
-
file = file.slice(basePath.length + 1);
|
|
184
|
-
break;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (!includeAll && isFramework)
|
|
188
|
-
continue;
|
|
189
|
-
frames.push({
|
|
190
|
-
function: match[1] || "<anonymous>",
|
|
191
|
-
file,
|
|
192
|
-
absoluteFile: original,
|
|
193
|
-
isFramework,
|
|
194
|
-
line: parseInt(match[3] ?? "0", 10),
|
|
195
|
-
column: parseInt(match[4] ?? "0", 10)
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (!includeAll && frames.length === 0)
|
|
200
|
-
return parseStackTrace(stack, basePaths, { includeFrameworkFrames: !0 });
|
|
201
|
-
return frames;
|
|
202
|
-
}
|
|
203
|
-
function loadCustomErrorPage(status, title, message) {
|
|
204
|
-
let resourcesPath;
|
|
205
|
-
try {
|
|
206
|
-
const mod = require("@stacksjs/path");
|
|
207
|
-
resourcesPath = mod.resourcesPath ?? mod.path?.resourcesPath;
|
|
208
|
-
} catch {}
|
|
209
|
-
if (!resourcesPath)
|
|
210
|
-
return null;
|
|
211
|
-
const fs = require("node:fs"), candidates = [
|
|
212
|
-
resourcesPath(`views/errors/${status}.html`),
|
|
213
|
-
resourcesPath("views/errors/error.html")
|
|
214
|
-
];
|
|
215
|
-
for (const filePath of candidates)
|
|
216
|
-
try {
|
|
217
|
-
if (!fs.existsSync(filePath))
|
|
218
|
-
continue;
|
|
219
|
-
return fs.readFileSync(filePath, "utf-8").replace(/\{\{\s*status\s*\}\}/g, escapeHtml(String(status))).replace(/\{\{\s*title\s*\}\}/g, escapeHtml(title)).replace(/\{\{\s*message\s*\}\}/g, escapeHtml(message));
|
|
220
|
-
} catch {}
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
export function renderProductionErrorPage(status) {
|
|
224
|
-
const httpError = HTTP_ERRORS[status] || {
|
|
225
|
-
status,
|
|
226
|
-
title: "Error",
|
|
227
|
-
message: "An unexpected error occurred."
|
|
228
|
-
}, custom = loadCustomErrorPage(httpError.status, httpError.title, httpError.message);
|
|
229
|
-
if (custom !== null)
|
|
230
|
-
return custom;
|
|
231
|
-
return `<!DOCTYPE html>
|
|
1
|
+
var {require}=import.meta;const DOCS_BASE="https://stacksjs.org/docs/errors";export const HTTP_ERRORS={400:{status:400,title:"Bad Request",message:"The request was malformed or invalid.",docLink:`${DOCS_BASE}/400`,commonCauses:["JSON body is missing or has a syntax error","A required field is absent from the payload","Content-Type header does not match the body format"],suggestion:"Inspect the request body and Content-Type \u2014 most 400s come from malformed JSON or a missing required field."},401:{status:401,title:"Unauthorized",message:"Authentication is required to access this resource.",docLink:`${DOCS_BASE}/401`,commonCauses:["No Authorization header was sent","The bearer token expired","The session cookie was cleared"],suggestion:"Confirm a valid `Authorization: Bearer <token>` header is sent and the token has not expired."},403:{status:403,title:"Forbidden",message:"You do not have permission to access this resource.",docLink:`${DOCS_BASE}/403`,commonCauses:["The authenticated user lacks the required ability or role","A Gate or policy denied access (see app/Gates.ts)","The token was issued without the needed ability"],suggestion:"Check Gates / policies and the abilities encoded in the access token."},404:{status:404,title:"Not Found",message:"The requested resource could not be found.",docLink:`${DOCS_BASE}/404`,commonCauses:["The route is not registered in app/Routes.ts","A typo in the URL path","A model lookup returned no row (ModelNotFoundError)"],suggestion:"Run `buddy route:list` to see registered routes, or verify the model exists with the given id."},405:{status:405,title:"Method Not Allowed",message:"The request method is not supported for this resource.",docLink:`${DOCS_BASE}/405`,commonCauses:["The route is registered for a different HTTP method","A form posted GET when the route expects POST"],suggestion:"Confirm the HTTP method in `app/Routes.ts` matches what the client sent."},408:{status:408,title:"Request Timeout",message:"The request took too long to complete.",docLink:`${DOCS_BASE}/408`,commonCauses:["A long-running query or external API call exceeded the timeout","The client uploaded a slow body that stalled"],suggestion:"Move slow work into a queued job, or raise the route timeout if the work is genuinely long."},409:{status:409,title:"Conflict",message:"The request conflicts with the current state of the resource.",docLink:`${DOCS_BASE}/409`,commonCauses:["A unique-constraint violation (duplicate email, slug, etc.)","Optimistic locking detected a stale write"],suggestion:"Re-fetch the resource and retry, or surface the conflict to the user."},410:{status:410,title:"Gone",message:"The requested resource is no longer available.",docLink:`${DOCS_BASE}/410`,commonCauses:["The resource was permanently deleted","A signed URL expired"],suggestion:"Issue a fresh signed URL or fall back to the canonical resource."},422:{status:422,title:"Unprocessable Entity",message:"The request was well-formed but could not be processed.",docLink:`${DOCS_BASE}/422`,commonCauses:["Validation rules from the action / model rejected the payload","A field value is outside the allowed range or shape"],suggestion:"Inspect `errors` in the response body \u2014 each key maps to a failing field."},429:{status:429,title:"Too Many Requests",message:"You have exceeded the rate limit.",docLink:`${DOCS_BASE}/429`,commonCauses:["Rate-limit middleware tripped on this IP / token","A retry loop is hammering the endpoint"],suggestion:"Honor the `Retry-After` response header and back off before retrying."},500:{status:500,title:"Internal Server Error",message:"An unexpected error occurred on the server.",docLink:`${DOCS_BASE}/500`,commonCauses:["An unhandled exception in an action or middleware","A failing database connection or migration","A misconfigured environment variable"],suggestion:"Check server logs for the original stack trace \u2014 the error page above shows the throw site in dev."},502:{status:502,title:"Bad Gateway",message:"The server received an invalid response from an upstream server.",docLink:`${DOCS_BASE}/502`,commonCauses:["An upstream HTTP API returned a malformed response","A reverse proxy could not reach the origin"],suggestion:"Verify the upstream service is healthy and returning the expected content type."},503:{status:503,title:"Service Unavailable",message:"The service is temporarily unavailable.",docLink:`${DOCS_BASE}/503`,commonCauses:["Maintenance mode is enabled","A health check is failing","A dependency (db, redis, queue) is down"],suggestion:"Run `buddy doctor` and check dependent services."},504:{status:504,title:"Gateway Timeout",message:"The upstream server did not respond in time.",docLink:`${DOCS_BASE}/504`,commonCauses:["An upstream HTTP call exceeded its deadline","A long-running database query timed out"],suggestion:"Move the work to a queued job or raise the upstream timeout if the latency is expected."}};import{buildErrorMarkdown,escapeHtml,renderExceptionTrace,wrapErrorPage}from"./error-page-template";import{ERROR_PAGE_CSS}from"./error-page-styles";export{ERROR_PAGE_CSS};const FRAMEWORK_FRAME_FRAGMENTS=["/storage/framework/core/","/node_modules/@stacksjs/","node:internal/","bun:wrap"];export function isFrameworkFrame(file){if(!file)return!1;return FRAMEWORK_FRAME_FRAGMENTS.some((f)=>file.includes(f))}function parseStackTrace(stack,basePaths,options={}){if(!stack)return[];const lines=stack.split(`
|
|
2
|
+
`).slice(1),frames=[],includeAll=options.includeFrameworkFrames===!0;for(const line of lines){const match=line.match(/^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/);if(match){let file=match[2];if(file===void 0)continue;const original=file,isFramework=isFrameworkFrame(original);if(basePaths){for(const basePath of basePaths)if(file.startsWith(basePath)){file=file.slice(basePath.length+1);break}}if(!includeAll&&isFramework)continue;frames.push({function:match[1]||"<anonymous>",file,absoluteFile:original,isFramework,line:parseInt(match[3]??"0",10),column:parseInt(match[4]??"0",10)})}}if(!includeAll&&frames.length===0)return parseStackTrace(stack,basePaths,{includeFrameworkFrames:!0});return frames}function loadCustomErrorPage(status,title,message){let resourcesPath;try{const mod=require("@stacksjs/path");resourcesPath=mod.resourcesPath??mod.path?.resourcesPath}catch{}if(!resourcesPath)return null;const fs=require("node:fs"),candidates=[resourcesPath(`views/errors/${status}.html`),resourcesPath("views/errors/error.html")];for(const filePath of candidates)try{if(!fs.existsSync(filePath))continue;return fs.readFileSync(filePath,"utf-8").replace(/\{\{\s*status\s*\}\}/g,escapeHtml(String(status))).replace(/\{\{\s*title\s*\}\}/g,escapeHtml(title)).replace(/\{\{\s*message\s*\}\}/g,escapeHtml(message))}catch{}return null}export function renderProductionErrorPage(status){const httpError=HTTP_ERRORS[status]||{status,title:"Error",message:"An unexpected error occurred."},custom=loadCustomErrorPage(httpError.status,httpError.title,httpError.message);if(custom!==null)return custom;return`<!DOCTYPE html>
|
|
232
3
|
<html lang="en">
|
|
233
4
|
<head>
|
|
234
5
|
<meta charset="UTF-8">
|
|
@@ -244,130 +15,16 @@ export function renderProductionErrorPage(status) {
|
|
|
244
15
|
<a href="/" class="production-link">\u2190 Back to Home</a>
|
|
245
16
|
</div>
|
|
246
17
|
</body>
|
|
247
|
-
</html
|
|
248
|
-
}
|
|
249
|
-
export function renderHttpErrorHints(status) {
|
|
250
|
-
const info = HTTP_ERRORS[status];
|
|
251
|
-
if (!info)
|
|
252
|
-
return "";
|
|
253
|
-
const hasCauses = Array.isArray(info.commonCauses) && info.commonCauses.length > 0, hasSuggestion = typeof info.suggestion === "string" && info.suggestion.length > 0, hasDoc = typeof info.docLink === "string" && info.docLink.length > 0;
|
|
254
|
-
if (!hasCauses && !hasSuggestion && !hasDoc)
|
|
255
|
-
return "";
|
|
256
|
-
const causes = hasCauses ? `<ul class="error-hint-causes">${info.commonCauses.map((c) => `<li>${escapeHtml(c)}</li>`).join("")}</ul>` : "", suggestion = hasSuggestion ? `<p class="error-hint-suggestion">${escapeHtml(info.suggestion)}</p>` : "", doc = hasDoc ? `<a class="text-blue-600 text-sm dark:text-blue-500 hover:underline" href="${escapeHtml(info.docLink)}" target="_blank" rel="noreferrer noopener">Read the docs \u2192</a>` : "";
|
|
257
|
-
return `<section class="p-4 bg-amber-200/30 dark:bg-amber-950/40 border border-amber-200 rounded-xl dark:border-amber-800 shadow-xs">
|
|
18
|
+
</html>`}export function renderHttpErrorHints(status){const info=HTTP_ERRORS[status];if(!info)return"";const hasCauses=Array.isArray(info.commonCauses)&&info.commonCauses.length>0,hasSuggestion=typeof info.suggestion==="string"&&info.suggestion.length>0,hasDoc=typeof info.docLink==="string"&&info.docLink.length>0;if(!hasCauses&&!hasSuggestion&&!hasDoc)return"";const causes=hasCauses?`<ul class="error-hint-causes">${info.commonCauses.map((c)=>`<li>${escapeHtml(c)}</li>`).join("")}</ul>`:"",suggestion=hasSuggestion?`<p class="error-hint-suggestion">${escapeHtml(info.suggestion)}</p>`:"",doc=hasDoc?`<a class="text-blue-600 text-sm dark:text-blue-500 hover:underline" href="${escapeHtml(info.docLink)}" target="_blank" rel="noreferrer noopener">Read the docs \u2192</a>`:"";return`<section class="p-4 bg-amber-200/30 dark:bg-amber-950/40 border border-amber-200 rounded-xl dark:border-amber-800 shadow-xs">
|
|
258
19
|
<div class="mb-2 font-semibold text-amber-900 text-sm dark:text-amber-300">Likely causes & next steps</div>
|
|
259
20
|
<div class="text-neutral-700 text-sm dark:text-neutral-300">${causes}${suggestion}${doc}</div>
|
|
260
|
-
</section
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
export class ErrorPageHandler {
|
|
264
|
-
config;
|
|
265
|
-
framework;
|
|
266
|
-
request;
|
|
267
|
-
routing;
|
|
268
|
-
user;
|
|
269
|
-
queries = [];
|
|
270
|
-
constructor(config) {
|
|
271
|
-
this.config = {
|
|
272
|
-
appName: "App",
|
|
273
|
-
theme: "auto",
|
|
274
|
-
showEnvironment: !0,
|
|
275
|
-
showQueries: !0,
|
|
276
|
-
showRequest: !0,
|
|
277
|
-
enableCopyMarkdown: !0,
|
|
278
|
-
snippetLines: 8,
|
|
279
|
-
...config
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
setFramework(name, version) {
|
|
283
|
-
this.framework = { name, version };
|
|
284
|
-
return this;
|
|
285
|
-
}
|
|
286
|
-
setRequest(request) {
|
|
287
|
-
if (request instanceof Request) {
|
|
288
|
-
const url = new URL(request.url);
|
|
289
|
-
this.request = {
|
|
290
|
-
method: request.method,
|
|
291
|
-
url: request.url,
|
|
292
|
-
headers: Object.fromEntries(request.headers.entries()),
|
|
293
|
-
queryParams: Object.fromEntries(url.searchParams.entries())
|
|
294
|
-
};
|
|
295
|
-
} else
|
|
296
|
-
this.request = request;
|
|
297
|
-
return this;
|
|
298
|
-
}
|
|
299
|
-
setRouting(routing) {
|
|
300
|
-
this.routing = routing;
|
|
301
|
-
return this;
|
|
302
|
-
}
|
|
303
|
-
setUser(user) {
|
|
304
|
-
this.user = user;
|
|
305
|
-
return this;
|
|
306
|
-
}
|
|
307
|
-
addQuery(query, time, connection) {
|
|
308
|
-
this.queries.push({ query, time, connection });
|
|
309
|
-
return this;
|
|
310
|
-
}
|
|
311
|
-
async render(error, status = 500) {
|
|
312
|
-
try {
|
|
313
|
-
const { renderDevErrorPage } = await import("./error-page-renderer");
|
|
314
|
-
return await renderDevErrorPage({
|
|
315
|
-
error,
|
|
316
|
-
status,
|
|
317
|
-
config: this.config,
|
|
318
|
-
framework: this.framework,
|
|
319
|
-
request: this.request,
|
|
320
|
-
routing: this.routing,
|
|
321
|
-
user: this.user,
|
|
322
|
-
queries: this.queries
|
|
323
|
-
});
|
|
324
|
-
} catch (renderError) {
|
|
325
|
-
console.error("[ErrorPageHandler] STX render failed, using fallback:", renderError);
|
|
326
|
-
return this.renderFallback(error, status);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
renderFallback(error, status) {
|
|
330
|
-
const frames = parseStackTrace(error.stack, this.config.basePaths, {
|
|
331
|
-
includeFrameworkFrames: this.config.showFrameworkFrames === !0
|
|
332
|
-
}), statusTitle = HTTP_ERRORS[status]?.title ?? "Error", topFrame = frames[0], body = `
|
|
21
|
+
</section>`}export class ErrorPageHandler{config;framework;request;routing;user;queries=[];constructor(config){this.config={appName:"App",theme:"auto",showEnvironment:!0,showQueries:!0,showRequest:!0,enableCopyMarkdown:!0,snippetLines:8,...config}}setFramework(name,version){this.framework={name,version};return this}setRequest(request){if(request instanceof Request){const url=new URL(request.url);this.request={method:request.method,url:request.url,headers:Object.fromEntries(request.headers.entries()),queryParams:Object.fromEntries(url.searchParams.entries())}}else this.request=request;return this}setRouting(routing){this.routing=routing;return this}setUser(user){this.user=user;return this}addQuery(query,time,connection){this.queries.push({query,time,connection});return this}async render(error,status=500){try{const{renderDevErrorPage}=await import("./error-page-renderer");return await renderDevErrorPage({error,status,config:this.config,framework:this.framework,request:this.request,routing:this.routing,user:this.user,queries:this.queries})}catch(renderError){console.error("[ErrorPageHandler] STX render failed, using fallback:",renderError);return this.renderFallback(error,status)}}renderFallback(error,status){const frames=parseStackTrace(error.stack,this.config.basePaths,{includeFrameworkFrames:this.config.showFrameworkFrames===!0}),statusTitle=HTTP_ERRORS[status]?.title??"Error",topFrame=frames[0],body=`
|
|
333
22
|
<header class="header"><div class="header-title"><span class="header-dot"></span><span>${escapeHtml(statusTitle)}</span></div></header>
|
|
334
23
|
<section class="summary">
|
|
335
|
-
<h1>${escapeHtml(error.name
|
|
336
|
-
${topFrame
|
|
24
|
+
<h1>${escapeHtml(error.name||"Error")}</h1>
|
|
25
|
+
${topFrame?`<div class="summary-file">${escapeHtml(topFrame.file)}:${topFrame.line}</div>`:""}
|
|
337
26
|
<p class="summary-message">${escapeHtml(error.message)}</p>
|
|
338
27
|
</section>
|
|
339
28
|
${renderHttpErrorHints(status)}
|
|
340
|
-
${renderExceptionTrace(frames,
|
|
341
|
-
`, markdown =
|
|
342
|
-
statusTitle,
|
|
343
|
-
errorName: error.name || "Error",
|
|
344
|
-
errorMessage: error.message,
|
|
345
|
-
status,
|
|
346
|
-
file: topFrame?.file,
|
|
347
|
-
line: topFrame?.line,
|
|
348
|
-
request: this.request,
|
|
349
|
-
framework: this.framework,
|
|
350
|
-
frames
|
|
351
|
-
});
|
|
352
|
-
return wrapErrorPage(body, markdown);
|
|
353
|
-
}
|
|
354
|
-
async handleError(error, status = 500) {
|
|
355
|
-
const html = await this.render(error, status);
|
|
356
|
-
return new Response(html, {
|
|
357
|
-
status,
|
|
358
|
-
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
export function createErrorHandler(config) {
|
|
363
|
-
return new ErrorPageHandler(config);
|
|
364
|
-
}
|
|
365
|
-
export async function renderErrorPage(error, status = 500, config) {
|
|
366
|
-
return createErrorHandler(config).render(error, status);
|
|
367
|
-
}
|
|
368
|
-
export async function renderError(error, status = 500) {
|
|
369
|
-
return renderErrorPage(error, status);
|
|
370
|
-
}
|
|
371
|
-
export async function errorResponse(error, status = 500, config) {
|
|
372
|
-
return createErrorHandler(config).handleError(error, status);
|
|
373
|
-
}
|
|
29
|
+
${renderExceptionTrace(frames,this.config.snippetLines??8)}
|
|
30
|
+
`,markdown=buildErrorMarkdown({statusTitle,errorName:error.name||"Error",errorMessage:error.message,status,file:topFrame?.file,line:topFrame?.line,request:this.request,framework:this.framework,frames});return wrapErrorPage(body,markdown)}async handleError(error,status=500){const html=await this.render(error,status);return new Response(html,{status,headers:{"Content-Type":"text/html; charset=utf-8"}})}}export function createErrorHandler(config){return new ErrorPageHandler(config)}export async function renderErrorPage(error,status=500,config){return createErrorHandler(config).render(error,status)}export async function renderError(error,status=500){return renderErrorPage(error,status)}export async function errorResponse(error,status=500,config){return createErrorHandler(config).handleError(error,status)}
|
package/dist/handler.js
CHANGED
|
@@ -1,108 +1,7 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import { dirname } from "node:path";
|
|
3
|
-
import * as process from "node:process";
|
|
4
|
-
function italic(str) {
|
|
5
|
-
return `\x1B[3m${str}\x1B[23m`;
|
|
6
|
-
}
|
|
7
|
-
function stripAnsi(str) {
|
|
8
|
-
return str.replace(/\x1B\[[0-9;]*m/g, "");
|
|
9
|
-
}
|
|
10
|
-
import * as path from "@stacksjs/path";
|
|
11
|
-
import { ExitCode } from "@stacksjs/types";
|
|
12
|
-
|
|
13
|
-
export class ErrorHandler {
|
|
14
|
-
static isTestEnvironment = !1;
|
|
15
|
-
static shouldExitProcess = !1;
|
|
16
|
-
static handle(err, options) {
|
|
17
|
-
if (!this.isTestEnvironment)
|
|
18
|
-
this.shouldExitProcess = options?.shouldExit === !0;
|
|
19
|
-
if (options?.silent !== !0)
|
|
20
|
-
this.writeErrorToConsole(err);
|
|
21
|
-
let errorMessage;
|
|
22
|
-
if (options?.message)
|
|
23
|
-
errorMessage = options.message;
|
|
24
|
-
else if (err instanceof Error)
|
|
25
|
-
errorMessage = err.message;
|
|
26
|
-
else if (typeof err === "string")
|
|
27
|
-
errorMessage = err;
|
|
28
|
-
else
|
|
29
|
-
errorMessage = JSON.stringify(err);
|
|
30
|
-
const error = Error(errorMessage);
|
|
31
|
-
if (err instanceof Error)
|
|
32
|
-
Object.assign(error, err);
|
|
33
|
-
this.writeErrorToFile(error).catch((e) => console.error(e));
|
|
34
|
-
return error;
|
|
35
|
-
}
|
|
36
|
-
static handleError(err, options) {
|
|
37
|
-
this.handle(err, options);
|
|
38
|
-
return err;
|
|
39
|
-
}
|
|
40
|
-
static async writeErrorToFile(err, context) {
|
|
41
|
-
if (!(err instanceof Error)) {
|
|
42
|
-
console.error("Error is not an instance of Error:", err);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
const contextStr = context ? ` | url=${context.url || "N/A"} method=${context.method || "N/A"} user=${context.userId || "anonymous"}` : "", stackLine = err.stack ? `
|
|
1
|
+
import fs from"node:fs";import{dirname}from"node:path";import*as process from"node:process";function italic(str){return`\x1B[3m${str}\x1B[23m`}function stripAnsi(str){return str.replace(/\x1B\[[0-9;]*m/g,"")}import*as path from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";export class ErrorHandler{static isTestEnvironment=!1;static shouldExitProcess=!1;static handle(err,options){if(!this.isTestEnvironment)this.shouldExitProcess=options?.shouldExit===!0;if(options?.silent!==!0)this.writeErrorToConsole(err);let errorMessage;if(options?.message)errorMessage=options.message;else if(err instanceof Error)errorMessage=err.message;else if(typeof err==="string")errorMessage=err;else errorMessage=JSON.stringify(err);const error=Error(errorMessage);if(err instanceof Error)Object.assign(error,err);this.writeErrorToFile(error).catch((e)=>console.error(e));return error}static handleError(err,options){this.handle(err,options);return err}static async writeErrorToFile(err,context){if(!(err instanceof Error)){console.error("Error is not an instance of Error:",err);return}const contextStr=context?` | url=${context.url||"N/A"} method=${context.method||"N/A"} user=${context.userId||"anonymous"}`:"",stackLine=err.stack?`
|
|
46
2
|
${err.stack.split(`
|
|
47
|
-
`).slice(1,
|
|
48
|
-
`)}
|
|
49
|
-
`,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
await fs.promises.appendFile(logFilePath, formattedError);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.error("Failed to write to error file:", error);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
static writeErrorToConsole(err) {
|
|
58
|
-
let errorString;
|
|
59
|
-
if (err instanceof Error)
|
|
60
|
-
errorString = err.message;
|
|
61
|
-
else if (typeof err === "string")
|
|
62
|
-
errorString = err;
|
|
63
|
-
else
|
|
64
|
-
errorString = JSON.stringify(err);
|
|
65
|
-
console.error(errorString);
|
|
66
|
-
if (errorString.includes("bunx --bun cdk destroy") || errorString === `Failed to execute command: ${italic("bunx --bun eslint . --fix")}` || errorString === `Failed to execute command: ${italic("bun storage/framework/core/actions/src/lint/fix.ts")}`) {
|
|
67
|
-
if (!this.isTestEnvironment) {
|
|
68
|
-
console.log("No need to worry. The edge function is currently being destroyed. Please run `buddy undeploy` shortly again, and continue doing so until it succeeds running.");
|
|
69
|
-
console.log("Hoping to see you back soon!");
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (this.shouldExitProcess)
|
|
73
|
-
process.exit(ExitCode.FatalError);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
let defaultLogPath = "storage/logs/stacks.log";
|
|
77
|
-
export function setLogPath(path) {
|
|
78
|
-
defaultLogPath = path;
|
|
79
|
-
}
|
|
80
|
-
export async function writeToLogFile(message, options) {
|
|
81
|
-
const formattedMessage = `[${new Date().toISOString()}] ${message}
|
|
82
|
-
`, logFile = options?.logFile ?? defaultLogPath, dirPath = dirname(logFile);
|
|
83
|
-
await fs.promises.mkdir(dirPath, { recursive: !0 });
|
|
84
|
-
await fs.promises.appendFile(logFile, formattedMessage);
|
|
85
|
-
}
|
|
86
|
-
export function handleError(err, options) {
|
|
87
|
-
let errorMessage, contextData, errorOptions;
|
|
88
|
-
if (options && typeof options === "object" && (("shouldExit" in options) || ("silent" in options)))
|
|
89
|
-
errorOptions = options;
|
|
90
|
-
else if (options !== void 0)
|
|
91
|
-
contextData = options && typeof options === "object" ? options : { error: options };
|
|
92
|
-
const errMsg = err instanceof Error ? err.message : typeof err === "string" ? err : JSON.stringify(err);
|
|
93
|
-
if (errorOptions?.message)
|
|
94
|
-
errorMessage = `${errMsg}: ${errorOptions.message}`;
|
|
95
|
-
else
|
|
96
|
-
errorMessage = errMsg;
|
|
97
|
-
let logMessage = `ERROR: ${stripAnsi(errorMessage)}`;
|
|
98
|
-
if (contextData)
|
|
99
|
-
logMessage += `
|
|
100
|
-
Context: ${JSON.stringify(contextData, null, 2)}`;
|
|
101
|
-
writeToLogFile(logMessage).catch((err) => {
|
|
102
|
-
console.error("Failed to write error log:", err);
|
|
103
|
-
});
|
|
104
|
-
const error = Error(errorMessage);
|
|
105
|
-
if (err instanceof Error)
|
|
106
|
-
Object.assign(error, err);
|
|
107
|
-
return ErrorHandler.handle(error, errorOptions);
|
|
108
|
-
}
|
|
3
|
+
`).slice(1,4).join(`
|
|
4
|
+
`)}`:"",formattedError=`[${new Date().toISOString()}] ${err.name}: ${err.message}${contextStr}${stackLine}
|
|
5
|
+
`,logFilePath=path.logsPath("stacks.log")??path.logsPath("errors.log");try{await fs.promises.mkdir(path.dirname(logFilePath),{recursive:!0});await fs.promises.appendFile(logFilePath,formattedError)}catch(error){console.error("Failed to write to error file:",error)}}static writeErrorToConsole(err){let errorString;if(err instanceof Error)errorString=err.message;else if(typeof err==="string")errorString=err;else errorString=JSON.stringify(err);console.error(errorString);if(errorString.includes("bunx --bun cdk destroy")||errorString===`Failed to execute command: ${italic("bunx --bun eslint . --fix")}`||errorString===`Failed to execute command: ${italic("bun storage/framework/core/actions/src/lint/fix.ts")}`){if(!this.isTestEnvironment){console.log("No need to worry. The edge function is currently being destroyed. Please run `buddy undeploy` shortly again, and continue doing so until it succeeds running.");console.log("Hoping to see you back soon!")}}if(this.shouldExitProcess)process.exit(ExitCode.FatalError)}}let defaultLogPath="storage/logs/stacks.log";export function setLogPath(path){defaultLogPath=path}export async function writeToLogFile(message,options){const formattedMessage=`[${new Date().toISOString()}] ${message}
|
|
6
|
+
`,logFile=options?.logFile??defaultLogPath,dirPath=dirname(logFile);await fs.promises.mkdir(dirPath,{recursive:!0});await fs.promises.appendFile(logFile,formattedMessage)}export function handleError(err,options){let errorMessage,contextData,errorOptions;if(options&&typeof options==="object"&&(("shouldExit"in options)||("silent"in options)))errorOptions=options;else if(options!==void 0)contextData=options&&typeof options==="object"?options:{error:options};const errMsg=err instanceof Error?err.message:typeof err==="string"?err:JSON.stringify(err);if(errorOptions?.message)errorMessage=`${errMsg}: ${errorOptions.message}`;else errorMessage=errMsg;let logMessage=`ERROR: ${stripAnsi(errorMessage)}`;if(contextData)logMessage+=`
|
|
7
|
+
Context: ${JSON.stringify(contextData,null,2)}`;writeToLogFile(logMessage).catch((err)=>{console.error("Failed to write error log:",err)});const error=Error(errorMessage);if(err instanceof Error)Object.assign(error,err);return ErrorHandler.handle(error,errorOptions)}
|
package/dist/http.js
CHANGED
|
@@ -1,165 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createErrorHandler,
|
|
3
|
-
HTTP_ERRORS,
|
|
4
|
-
renderProductionErrorPage
|
|
5
|
-
} from "./error-page";
|
|
6
|
-
|
|
7
|
-
export class HttpError extends Error {
|
|
8
|
-
status;
|
|
9
|
-
details;
|
|
10
|
-
constructor(status, message, details) {
|
|
11
|
-
super(message);
|
|
12
|
-
this.status = status;
|
|
13
|
-
this.name = httpStatusName(status);
|
|
14
|
-
if (details !== void 0)
|
|
15
|
-
this.details = details;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function httpStatusName(status) {
|
|
19
|
-
switch (status) {
|
|
20
|
-
case 400:
|
|
21
|
-
return "Bad Request";
|
|
22
|
-
case 401:
|
|
23
|
-
return "Unauthorized";
|
|
24
|
-
case 402:
|
|
25
|
-
return "Payment Required";
|
|
26
|
-
case 403:
|
|
27
|
-
return "Forbidden";
|
|
28
|
-
case 404:
|
|
29
|
-
return "Not Found";
|
|
30
|
-
case 405:
|
|
31
|
-
return "Method Not Allowed";
|
|
32
|
-
case 408:
|
|
33
|
-
return "Request Timeout";
|
|
34
|
-
case 409:
|
|
35
|
-
return "Conflict";
|
|
36
|
-
case 410:
|
|
37
|
-
return "Gone";
|
|
38
|
-
case 413:
|
|
39
|
-
return "Payload Too Large";
|
|
40
|
-
case 415:
|
|
41
|
-
return "Unsupported Media Type";
|
|
42
|
-
case 422:
|
|
43
|
-
return "Unprocessable Entity";
|
|
44
|
-
case 423:
|
|
45
|
-
return "Locked";
|
|
46
|
-
case 425:
|
|
47
|
-
return "Too Early";
|
|
48
|
-
case 426:
|
|
49
|
-
return "Upgrade Required";
|
|
50
|
-
case 428:
|
|
51
|
-
return "Precondition Required";
|
|
52
|
-
case 429:
|
|
53
|
-
return "Too Many Requests";
|
|
54
|
-
case 431:
|
|
55
|
-
return "Request Header Fields Too Large";
|
|
56
|
-
case 451:
|
|
57
|
-
return "Unavailable For Legal Reasons";
|
|
58
|
-
case 500:
|
|
59
|
-
return "Internal Server Error";
|
|
60
|
-
case 501:
|
|
61
|
-
return "Not Implemented";
|
|
62
|
-
case 502:
|
|
63
|
-
return "Bad Gateway";
|
|
64
|
-
case 503:
|
|
65
|
-
return "Service Unavailable";
|
|
66
|
-
case 504:
|
|
67
|
-
return "Gateway Timeout";
|
|
68
|
-
case 507:
|
|
69
|
-
return "Insufficient Storage";
|
|
70
|
-
case 508:
|
|
71
|
-
return "Loop Detected";
|
|
72
|
-
case 511:
|
|
73
|
-
return "Network Authentication Required";
|
|
74
|
-
default:
|
|
75
|
-
return status >= 400 && status < 500 ? "Client Error" : "Server Error";
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export class HttpErrorHandler {
|
|
80
|
-
handler = createErrorHandler();
|
|
81
|
-
isDevelopment;
|
|
82
|
-
constructor(options) {
|
|
83
|
-
this.isDevelopment = options?.isDevelopment ?? !0;
|
|
84
|
-
if (options?.config)
|
|
85
|
-
this.handler = createErrorHandler(options.config);
|
|
86
|
-
this.handler.setFramework("Stacks");
|
|
87
|
-
}
|
|
88
|
-
setRequest(request) {
|
|
89
|
-
this.handler.setRequest(request);
|
|
90
|
-
return this;
|
|
91
|
-
}
|
|
92
|
-
setRouting(routing) {
|
|
93
|
-
this.handler.setRouting(routing);
|
|
94
|
-
return this;
|
|
95
|
-
}
|
|
96
|
-
addQuery(query, time, connection) {
|
|
97
|
-
this.handler.addQuery(query, time, connection);
|
|
98
|
-
return this;
|
|
99
|
-
}
|
|
100
|
-
async handle(error, status = 500) {
|
|
101
|
-
if (this.isDevelopment)
|
|
102
|
-
return this.handler.handleError(error, status);
|
|
103
|
-
return new Response(renderProductionErrorPage(status), {
|
|
104
|
-
status,
|
|
105
|
-
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
notFound(message) {
|
|
109
|
-
const error = new HttpError(404, message || "The requested resource could not be found.");
|
|
110
|
-
return this.handle(error, 404);
|
|
111
|
-
}
|
|
112
|
-
serverError(error) {
|
|
113
|
-
return this.handle(error, 500);
|
|
114
|
-
}
|
|
115
|
-
forbidden(message) {
|
|
116
|
-
const error = new HttpError(403, message || "You do not have permission to access this resource.");
|
|
117
|
-
return this.handle(error, 403);
|
|
118
|
-
}
|
|
119
|
-
unauthorized(message) {
|
|
120
|
-
const error = new HttpError(401, message || "Authentication is required to access this resource.");
|
|
121
|
-
return this.handle(error, 401);
|
|
122
|
-
}
|
|
123
|
-
badRequest(message) {
|
|
124
|
-
const error = new HttpError(400, message || "The request was malformed or invalid.");
|
|
125
|
-
return this.handle(error, 400);
|
|
126
|
-
}
|
|
127
|
-
validationError(message) {
|
|
128
|
-
const error = new HttpError(422, message || "The request was well-formed but could not be processed.");
|
|
129
|
-
return this.handle(error, 422);
|
|
130
|
-
}
|
|
131
|
-
tooManyRequests(message) {
|
|
132
|
-
const error = new HttpError(429, message || "You have exceeded the rate limit.");
|
|
133
|
-
return this.handle(error, 429);
|
|
134
|
-
}
|
|
135
|
-
serviceUnavailable(message) {
|
|
136
|
-
const error = new HttpError(503, message || "The service is temporarily unavailable.");
|
|
137
|
-
return this.handle(error, 503);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
export function createHttpErrorHandler(options) {
|
|
141
|
-
return new HttpErrorHandler(options);
|
|
142
|
-
}
|
|
143
|
-
export async function renderHttpError(error, request, options) {
|
|
144
|
-
const handler = createHttpErrorHandler({
|
|
145
|
-
isDevelopment: options?.isDevelopment,
|
|
146
|
-
config: options?.config
|
|
147
|
-
});
|
|
148
|
-
if (request)
|
|
149
|
-
handler.setRequest(request);
|
|
150
|
-
return handler.handle(error, options?.status);
|
|
151
|
-
}
|
|
152
|
-
export function errorMiddleware(options) {
|
|
153
|
-
const handler = createHttpErrorHandler(options);
|
|
154
|
-
return async (error, request) => {
|
|
155
|
-
handler.setRequest(request);
|
|
156
|
-
let status = 500;
|
|
157
|
-
if (error instanceof HttpError)
|
|
158
|
-
status = error.status;
|
|
159
|
-
else if ("status" in error && typeof error.status === "number")
|
|
160
|
-
status = error.status;
|
|
161
|
-
else if ("statusCode" in error && typeof error.statusCode === "number")
|
|
162
|
-
status = error.statusCode;
|
|
163
|
-
return handler.handle(error, status);
|
|
164
|
-
};
|
|
165
|
-
}
|
|
1
|
+
import{createErrorHandler,HTTP_ERRORS,renderProductionErrorPage}from"./error-page";export class HttpError extends Error{status;details;constructor(status,message,details){super(message);this.status=status;this.name=httpStatusName(status);if(details!==void 0)this.details=details}}function httpStatusName(status){switch(status){case 400:return"Bad Request";case 401:return"Unauthorized";case 402:return"Payment Required";case 403:return"Forbidden";case 404:return"Not Found";case 405:return"Method Not Allowed";case 408:return"Request Timeout";case 409:return"Conflict";case 410:return"Gone";case 413:return"Payload Too Large";case 415:return"Unsupported Media Type";case 422:return"Unprocessable Entity";case 423:return"Locked";case 425:return"Too Early";case 426:return"Upgrade Required";case 428:return"Precondition Required";case 429:return"Too Many Requests";case 431:return"Request Header Fields Too Large";case 451:return"Unavailable For Legal Reasons";case 500:return"Internal Server Error";case 501:return"Not Implemented";case 502:return"Bad Gateway";case 503:return"Service Unavailable";case 504:return"Gateway Timeout";case 507:return"Insufficient Storage";case 508:return"Loop Detected";case 511:return"Network Authentication Required";default:return status>=400&&status<500?"Client Error":"Server Error"}}export class HttpErrorHandler{handler=createErrorHandler();isDevelopment;constructor(options){this.isDevelopment=options?.isDevelopment??!0;if(options?.config)this.handler=createErrorHandler(options.config);this.handler.setFramework("Stacks")}setRequest(request){this.handler.setRequest(request);return this}setRouting(routing){this.handler.setRouting(routing);return this}addQuery(query,time,connection){this.handler.addQuery(query,time,connection);return this}async handle(error,status=500){if(this.isDevelopment)return this.handler.handleError(error,status);return new Response(renderProductionErrorPage(status),{status,headers:{"Content-Type":"text/html; charset=utf-8"}})}notFound(message){const error=new HttpError(404,message||"The requested resource could not be found.");return this.handle(error,404)}serverError(error){return this.handle(error,500)}forbidden(message){const error=new HttpError(403,message||"You do not have permission to access this resource.");return this.handle(error,403)}unauthorized(message){const error=new HttpError(401,message||"Authentication is required to access this resource.");return this.handle(error,401)}badRequest(message){const error=new HttpError(400,message||"The request was malformed or invalid.");return this.handle(error,400)}validationError(message){const error=new HttpError(422,message||"The request was well-formed but could not be processed.");return this.handle(error,422)}tooManyRequests(message){const error=new HttpError(429,message||"You have exceeded the rate limit.");return this.handle(error,429)}serviceUnavailable(message){const error=new HttpError(503,message||"The service is temporarily unavailable.");return this.handle(error,503)}}export function createHttpErrorHandler(options){return new HttpErrorHandler(options)}export async function renderHttpError(error,request,options){const handler=createHttpErrorHandler({isDevelopment:options?.isDevelopment,config:options?.config});if(request)handler.setRequest(request);return handler.handle(error,options?.status)}export function errorMiddleware(options){const handler=createHttpErrorHandler(options);return async(error,request)=>{handler.setRequest(request);let status=500;if(error instanceof HttpError)status=error.status;else if("status"in error&&typeof error.status==="number")status=error.status;else if("statusCode"in error&&typeof error.statusCode==="number")status=error.statusCode;return handler.handle(error,status)}}
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from "./http";
|
|
3
|
-
export * from "./model";
|
|
4
|
-
export * from "./utils";
|
|
5
|
-
export {
|
|
6
|
-
err,
|
|
7
|
-
fromPromise,
|
|
8
|
-
ok
|
|
9
|
-
} from "ts-error-handling";
|
|
10
|
-
export {
|
|
11
|
-
createErrorHandler,
|
|
12
|
-
ERROR_PAGE_CSS,
|
|
13
|
-
ErrorPageHandler,
|
|
14
|
-
errorResponse,
|
|
15
|
-
HTTP_ERRORS,
|
|
16
|
-
renderError,
|
|
17
|
-
renderErrorPage,
|
|
18
|
-
renderHttpErrorHints,
|
|
19
|
-
renderProductionErrorPage
|
|
20
|
-
} from "./error-page";
|
|
1
|
+
export*from"./handler";export*from"./http";export*from"./model";export*from"./utils";export{err,fromPromise,ok}from"ts-error-handling";export{createErrorHandler,ERROR_PAGE_CSS,ErrorPageHandler,errorResponse,HTTP_ERRORS,renderError,renderErrorPage,renderHttpErrorHints,renderProductionErrorPage}from"./error-page";
|
package/dist/model.js
CHANGED
|
@@ -1,8 +1 @@
|
|
|
1
|
-
export class ModelNotFoundException extends Error
|
|
2
|
-
status;
|
|
3
|
-
constructor(status, message) {
|
|
4
|
-
super(message);
|
|
5
|
-
this.status = status;
|
|
6
|
-
this.name = "ModelNotFoundException";
|
|
7
|
-
}
|
|
8
|
-
}
|
|
1
|
+
export class ModelNotFoundException extends Error{status;constructor(status,message){super(message);this.status=status;this.name="ModelNotFoundException"}}
|
package/dist/utils.js
CHANGED
|
@@ -1,17 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export function rescue(fn, fallback, onError) {
|
|
3
|
-
try {
|
|
4
|
-
const result = fn();
|
|
5
|
-
if (result instanceof Promise)
|
|
6
|
-
return result.catch((error) => {
|
|
7
|
-
if (onError)
|
|
8
|
-
onError(ErrorHandler.handle(error));
|
|
9
|
-
return fallback;
|
|
10
|
-
});
|
|
11
|
-
return result;
|
|
12
|
-
} catch (error) {
|
|
13
|
-
if (onError)
|
|
14
|
-
onError(ErrorHandler.handle(error));
|
|
15
|
-
return fallback;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
1
|
+
import{ErrorHandler}from"./handler";export function rescue(fn,fallback,onError){try{const result=fn();if(result instanceof Promise)return result.catch((error)=>{if(onError)onError(ErrorHandler.handle(error));return fallback});return result}catch(error){if(onError)onError(ErrorHandler.handle(error));return fallback}}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/error-handling",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.260",
|
|
5
5
|
"description": "Type safe error handling.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -33,9 +33,16 @@
|
|
|
33
33
|
"default": "./dist/index.js"
|
|
34
34
|
},
|
|
35
35
|
"./*": {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
36
|
+
"types": "./dist/*.d.ts",
|
|
37
|
+
"bun": "./dist/*.js",
|
|
38
|
+
"import": "./dist/*.js",
|
|
39
|
+
"default": "./dist/*.js"
|
|
40
|
+
},
|
|
41
|
+
"./*.js": {
|
|
42
|
+
"types": "./dist/*.d.ts",
|
|
43
|
+
"bun": "./dist/*.js",
|
|
44
|
+
"import": "./dist/*.js",
|
|
45
|
+
"default": "./dist/*.js"
|
|
39
46
|
}
|
|
40
47
|
},
|
|
41
48
|
"module": "dist/index.js",
|
|
@@ -55,12 +62,12 @@
|
|
|
55
62
|
"ts-syntax-highlighter": "^0.2.1"
|
|
56
63
|
},
|
|
57
64
|
"devDependencies": {
|
|
58
|
-
"@stacksjs/cli": "0.70.
|
|
59
|
-
"@stacksjs/config": "0.70.
|
|
65
|
+
"@stacksjs/cli": "0.70.260",
|
|
66
|
+
"@stacksjs/config": "0.70.260",
|
|
60
67
|
"better-dx": "^0.2.17",
|
|
61
|
-
"@stacksjs/path": "0.70.
|
|
62
|
-
"@stacksjs/types": "0.70.
|
|
63
|
-
"@stacksjs/validation": "0.70.
|
|
68
|
+
"@stacksjs/path": "0.70.260",
|
|
69
|
+
"@stacksjs/types": "0.70.260",
|
|
70
|
+
"@stacksjs/validation": "0.70.260"
|
|
64
71
|
},
|
|
65
72
|
"sideEffects": false
|
|
66
73
|
}
|