@tezx/devtools 1.0.4 → 1.0.6
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/README.md +143 -1
- package/cjs/devtools/dumpRoutes.js +4 -13
- package/cjs/devtools/index.js +5 -15
- package/cjs/devtools/middlewares.js +29 -19
- package/cjs/html/cookies.js +146 -127
- package/cjs/html/env.js +145 -0
- package/cjs/html/index.js +22 -8
- package/cjs/html/middlewares.js +184 -0
- package/cjs/html/routes.js +114 -85
- package/cjs/index.js +39 -12
- package/devtools/dumpRoutes.d.ts +2 -4
- package/devtools/dumpRoutes.js +2 -10
- package/devtools/index.d.ts +3 -10
- package/devtools/index.js +2 -12
- package/devtools/middlewares.d.ts +6 -6
- package/devtools/middlewares.js +28 -18
- package/html/cookies.js +146 -127
- package/html/env.d.ts +2 -0
- package/html/env.js +142 -0
- package/html/index.d.ts +2 -2
- package/html/index.js +22 -8
- package/html/middlewares.d.ts +7 -0
- package/html/middlewares.js +181 -0
- package/html/routes.js +113 -84
- package/index.d.ts +3 -2
- package/index.js +36 -12
- package/package.json +1 -1
package/html/env.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
export function EnvInspector(ctx) {
|
|
2
|
+
const env = ctx.env;
|
|
3
|
+
const entries = Object.entries(env);
|
|
4
|
+
const tableRows = entries.length
|
|
5
|
+
? entries
|
|
6
|
+
.map(([key, value], i) => {
|
|
7
|
+
const safeKey = key.replace(/"/g, """);
|
|
8
|
+
const safeValue = value.replace(/`/g, "\\`").replace(/"/g, """);
|
|
9
|
+
return `
|
|
10
|
+
<tr data-key="${safeKey.toLowerCase()}" data-value="${value.toLowerCase()}">
|
|
11
|
+
<td>${i + 1}</td>
|
|
12
|
+
<td>${safeKey}</td>
|
|
13
|
+
<td>${value}</td>
|
|
14
|
+
<td><button onclick="copyRowEnv('${safeKey}', \`${safeValue}\`)">📋</button></td>
|
|
15
|
+
</tr>
|
|
16
|
+
`;
|
|
17
|
+
})
|
|
18
|
+
.join("")
|
|
19
|
+
: `
|
|
20
|
+
<tr>
|
|
21
|
+
<td colspan="4">
|
|
22
|
+
<p>⚠️ <strong>No environment variables found.</strong></p>
|
|
23
|
+
<pre style="margin-top: 1rem; background: #f1f5f9; padding: 1rem; border-radius: 0.5rem;">
|
|
24
|
+
const env = loadEnv();
|
|
25
|
+
|
|
26
|
+
export const app = new TezX({
|
|
27
|
+
env: env,
|
|
28
|
+
debugMode: true,
|
|
29
|
+
// basePath: 'v1',
|
|
30
|
+
allowDuplicateMw: true,
|
|
31
|
+
});
|
|
32
|
+
</pre>
|
|
33
|
+
</td>
|
|
34
|
+
</tr>`;
|
|
35
|
+
return `
|
|
36
|
+
<style>
|
|
37
|
+
table td button {
|
|
38
|
+
background: #e2e8f0;
|
|
39
|
+
border: none;
|
|
40
|
+
padding: 0.3rem 0.6rem;
|
|
41
|
+
border-radius: 0.375rem;
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
}
|
|
44
|
+
table td button:hover {
|
|
45
|
+
background: #cbd5e1;
|
|
46
|
+
}
|
|
47
|
+
pre#json-output {
|
|
48
|
+
margin-top: 1rem;
|
|
49
|
+
padding: 1rem;
|
|
50
|
+
border-radius: 0.5rem;
|
|
51
|
+
background: #f9fafb;
|
|
52
|
+
border: 1px solid #e5e7eb;
|
|
53
|
+
font-size: 0.875rem;
|
|
54
|
+
overflow-x: auto;
|
|
55
|
+
display: none;
|
|
56
|
+
}
|
|
57
|
+
.search-container {
|
|
58
|
+
margin-bottom: 1rem;
|
|
59
|
+
}
|
|
60
|
+
.search-container input {
|
|
61
|
+
padding: 0.5rem;
|
|
62
|
+
width: 100%;
|
|
63
|
+
max-width: 400px;
|
|
64
|
+
border: 1px solid #e5e7eb;
|
|
65
|
+
border-radius: 0.375rem;
|
|
66
|
+
}
|
|
67
|
+
</style>
|
|
68
|
+
|
|
69
|
+
<div class="tabs">
|
|
70
|
+
<a onclick="exportEnv()">📤 Export JSON</a>
|
|
71
|
+
<a onclick="copyAllEnv()">📋 Copy All</a>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div class="search-container">
|
|
75
|
+
<input type="text" id="env-search" placeholder="🔍 Search environment variables..." oninput="filterEnvTable()" />
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="table-container">
|
|
79
|
+
<table>
|
|
80
|
+
<thead>
|
|
81
|
+
<tr>
|
|
82
|
+
<th>#</th>
|
|
83
|
+
<th>Key</th>
|
|
84
|
+
<th>Value</th>
|
|
85
|
+
<th>Copy</th>
|
|
86
|
+
</tr>
|
|
87
|
+
</thead>
|
|
88
|
+
<tbody id="env-body">
|
|
89
|
+
${tableRows}
|
|
90
|
+
</tbody>
|
|
91
|
+
</table>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<pre id="json-output">${JSON.stringify(env, null, 2)}</pre>
|
|
95
|
+
|
|
96
|
+
<script>
|
|
97
|
+
function exportEnv() {
|
|
98
|
+
const output = document.getElementById('json-output');
|
|
99
|
+
const blob = new Blob([output.textContent], { type: 'application/json' });
|
|
100
|
+
const url = URL.createObjectURL(blob);
|
|
101
|
+
const a = document.createElement('a');
|
|
102
|
+
a.href = url;
|
|
103
|
+
a.download = 'env.json';
|
|
104
|
+
a.click();
|
|
105
|
+
URL.revokeObjectURL(url);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function copyAllEnv() {
|
|
109
|
+
const output = document.getElementById('json-output');
|
|
110
|
+
navigator.clipboard.writeText(output.textContent).then(() => {
|
|
111
|
+
alert('✅ All environment variables copied!');
|
|
112
|
+
}).catch(err => {
|
|
113
|
+
alert('❌ Failed to copy: ' + err);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function copyRowEnv(key, value) {
|
|
118
|
+
const text = \`\${key}=\${value}\`;
|
|
119
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
120
|
+
alert(\`✅ Copied: \${text}\`);
|
|
121
|
+
}).catch(err => {
|
|
122
|
+
alert('❌ Failed to copy: ' + err);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function filterEnvTable() {
|
|
127
|
+
const input = document.getElementById('env-search').value.toLowerCase();
|
|
128
|
+
const rows = document.querySelectorAll('#env-body tr');
|
|
129
|
+
|
|
130
|
+
rows.forEach(row => {
|
|
131
|
+
const key = row.getAttribute('data-key') || '';
|
|
132
|
+
const value = row.getAttribute('data-value') || '';
|
|
133
|
+
if (key.includes(input) || value.includes(input)) {
|
|
134
|
+
row.style.display = '';
|
|
135
|
+
} else {
|
|
136
|
+
row.style.display = 'none';
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
</script>
|
|
141
|
+
`;
|
|
142
|
+
}
|
package/html/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Context, TezX } from "tezx";
|
|
2
|
-
export type Tab =
|
|
2
|
+
export type Tab = "cookies" | "routes" | ".env" | "middlewares";
|
|
3
3
|
export type TabType = {
|
|
4
4
|
doc_title: string;
|
|
5
5
|
label: string;
|
|
6
|
-
tab: Tab;
|
|
6
|
+
tab: Tab | string;
|
|
7
7
|
content: string;
|
|
8
8
|
}[];
|
|
9
9
|
export declare function html(ctx: Context, app: TezX): TabType;
|
package/html/index.js
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
|
-
import { Routes } from "./routes.js";
|
|
2
1
|
import { CookiesInspector } from "./cookies.js";
|
|
2
|
+
import { EnvInspector } from "./env.js";
|
|
3
|
+
import { Middlewares } from "./middlewares.js";
|
|
4
|
+
import { Routes } from "./routes.js";
|
|
3
5
|
export function html(ctx, app) {
|
|
4
6
|
let tabDb = [
|
|
5
7
|
{
|
|
6
8
|
doc_title: "DevTools - Route Inspector",
|
|
7
9
|
label: "Routes",
|
|
8
|
-
tab:
|
|
9
|
-
content: Routes(ctx, app)
|
|
10
|
+
tab: "routes",
|
|
11
|
+
content: Routes(ctx, app),
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
doc_title: "DevTools - Middleware Inspector",
|
|
15
|
+
label: "Middlewares",
|
|
16
|
+
tab: "middleware",
|
|
17
|
+
content: Middlewares(ctx, app),
|
|
10
18
|
},
|
|
11
19
|
{
|
|
12
|
-
tab:
|
|
13
|
-
label:
|
|
14
|
-
doc_title:
|
|
15
|
-
content: CookiesInspector(ctx)
|
|
16
|
-
}
|
|
20
|
+
tab: "cookies",
|
|
21
|
+
label: "Cookies",
|
|
22
|
+
doc_title: "DevTools - Cookie Inspector",
|
|
23
|
+
content: CookiesInspector(ctx),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
tab: ".env",
|
|
27
|
+
label: "Environment",
|
|
28
|
+
doc_title: "DevTools - Environment",
|
|
29
|
+
content: EnvInspector(ctx),
|
|
30
|
+
},
|
|
17
31
|
];
|
|
18
32
|
return tabDb;
|
|
19
33
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Context, TezX } from "tezx";
|
|
2
|
+
export type MiddlewareEntry = {
|
|
3
|
+
pattern: string;
|
|
4
|
+
type: "static" | "wildcard" | "optional params" | "dynamic params";
|
|
5
|
+
appliedMiddlewares: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare function Middlewares(ctx: Context, app: TezX): string;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { dumpMiddlewares } from "../devtools/index.js";
|
|
2
|
+
export function Middlewares(ctx, app) {
|
|
3
|
+
const allRoutes = dumpMiddlewares(app);
|
|
4
|
+
const totalRoutes = allRoutes.length;
|
|
5
|
+
const middlewareRoutes = {};
|
|
6
|
+
for (const route of allRoutes) {
|
|
7
|
+
for (const mw of route.appliedMiddlewares || []) {
|
|
8
|
+
(middlewareRoutes[mw] ||= []).push(route);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const rawJSON = JSON.stringify(allRoutes, null, 2);
|
|
12
|
+
const toCSV = (data) => {
|
|
13
|
+
const headers = ["pattern", "type", "appliedMiddlewares"];
|
|
14
|
+
const csvRows = [
|
|
15
|
+
headers.join(","),
|
|
16
|
+
...data.map((r) => headers.map((h) => JSON.stringify(r[h] ?? "")).join(",")),
|
|
17
|
+
];
|
|
18
|
+
return csvRows.join("\n");
|
|
19
|
+
};
|
|
20
|
+
const csvString = toCSV(allRoutes).replace(/"/g, """);
|
|
21
|
+
return `
|
|
22
|
+
<style>
|
|
23
|
+
.download {
|
|
24
|
+
margin-top: 1rem;
|
|
25
|
+
display: flex;
|
|
26
|
+
gap: 1rem;
|
|
27
|
+
}
|
|
28
|
+
.download a {
|
|
29
|
+
padding: 0.5rem 1rem;
|
|
30
|
+
border: 1px solid var(--accent);
|
|
31
|
+
color: var(--accent);
|
|
32
|
+
text-decoration: none;
|
|
33
|
+
border-radius: 6px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.download a:hover {
|
|
37
|
+
background: var(--accent);
|
|
38
|
+
color: white;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#searchBar {
|
|
42
|
+
margin: 1rem 0;
|
|
43
|
+
display: flex;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#searchBar input {
|
|
47
|
+
flex: 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
table td button {
|
|
51
|
+
background: #e2e8f0;
|
|
52
|
+
border: none;
|
|
53
|
+
padding: 0.2rem 0.5rem;
|
|
54
|
+
border-radius: 0.375rem;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
margin-left: 0.5rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
table td button:hover {
|
|
60
|
+
background: #cbd5e1;
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
63
|
+
|
|
64
|
+
<div class="tabs toolbar">
|
|
65
|
+
<a class="tab-btn active counting" data-count="${totalRoutes}" onclick="showTab('routes')">📋 Routes</a>
|
|
66
|
+
<a class="tab-btn counting" data-count="${Object.keys(middlewareRoutes).length}" onclick="showTab('middlewares')">🧩 Middlewares</a>
|
|
67
|
+
<a class="tab-btn" onclick="showTab('json')">🧾 Raw JSON</a>
|
|
68
|
+
<a class="tab-btn" onclick="showTab('export')">📤 Export</a>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div id="searchBar">
|
|
72
|
+
<input type="text" id="search" placeholder="Filter by pattern/type/middleware..." />
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<!-- ROUTES -->
|
|
76
|
+
<div id="routesTab" class="table-container">
|
|
77
|
+
<table id="routesTable">
|
|
78
|
+
<thead>
|
|
79
|
+
<tr>
|
|
80
|
+
<th>#</th>
|
|
81
|
+
<th>Pattern</th>
|
|
82
|
+
<th>Type</th>
|
|
83
|
+
<th>Middlewares</th>
|
|
84
|
+
</tr>
|
|
85
|
+
</thead>
|
|
86
|
+
<tbody>
|
|
87
|
+
${allRoutes
|
|
88
|
+
.map((r, i) => `
|
|
89
|
+
<tr>
|
|
90
|
+
<td>${i + 1}</td>
|
|
91
|
+
<td>
|
|
92
|
+
${r.pattern}
|
|
93
|
+
<button onclick="copyText(\`${r.pattern}\`)">📋</button>
|
|
94
|
+
</td>
|
|
95
|
+
<td>
|
|
96
|
+
<span style="
|
|
97
|
+
display: inline-block;
|
|
98
|
+
padding: 0.2rem 0.5rem;
|
|
99
|
+
border-radius: 0.375rem;
|
|
100
|
+
color: white;
|
|
101
|
+
font-size: 0.75rem;
|
|
102
|
+
background-color: ${r.type === "static"
|
|
103
|
+
? "#16a34a"
|
|
104
|
+
: r.type === "wildcard"
|
|
105
|
+
? "#f97316"
|
|
106
|
+
: r.type === "optional params"
|
|
107
|
+
? "#3b82f6"
|
|
108
|
+
: r.type === "dynamic params"
|
|
109
|
+
? "#9333ea"
|
|
110
|
+
: "#6b7280"};
|
|
111
|
+
">
|
|
112
|
+
${r.type}
|
|
113
|
+
</span>
|
|
114
|
+
</td>
|
|
115
|
+
<td>${(r.appliedMiddlewares || []).join(", ")}</td>
|
|
116
|
+
</tr>
|
|
117
|
+
`)
|
|
118
|
+
.join("")}
|
|
119
|
+
</tbody>
|
|
120
|
+
</table>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- MIDDLEWARES -->
|
|
124
|
+
<div id="middlewaresTab" style="display:none">
|
|
125
|
+
${Object.entries(middlewareRoutes)
|
|
126
|
+
.map(([mw, routes]) => `
|
|
127
|
+
<h3>🔹 ${mw} (${routes.length})</h3>
|
|
128
|
+
<ul>
|
|
129
|
+
${routes.map((r) => `<li><code>${r.pattern}</code> <span>(${r.type})</span></li>`).join("")}
|
|
130
|
+
</ul>
|
|
131
|
+
`)
|
|
132
|
+
.join("")}
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<!-- RAW JSON -->
|
|
136
|
+
<div id="jsonTab" style="display:none">
|
|
137
|
+
<div class="json-view"><pre>${rawJSON}</pre></div>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<!-- EXPORT -->
|
|
141
|
+
<div id="exportTab" style="display:none">
|
|
142
|
+
<div class="download">
|
|
143
|
+
<a href="data:text/json;charset=utf-8,${encodeURIComponent(rawJSON)}" download="middlewares.json">📥 JSON</a>
|
|
144
|
+
<a href="data:text/csv;charset=utf-8,${csvString}" download="middlewares.csv">📥 CSV</a>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<script>
|
|
149
|
+
const tabs = ['routes', 'middlewares', 'json', 'export'];
|
|
150
|
+
const tabBtns = document.querySelectorAll('.tab-btn');
|
|
151
|
+
|
|
152
|
+
function showTab(tab) {
|
|
153
|
+
tabs.forEach(t => {
|
|
154
|
+
document.getElementById(t + 'Tab').style.display = t === tab ? 'block' : 'none';
|
|
155
|
+
document.getElementById(t + 'Tab').classList.toggle('active', t === tab);
|
|
156
|
+
});
|
|
157
|
+
tabBtns.forEach(btn => {
|
|
158
|
+
const active = btn.textContent.toLowerCase().includes(tab);
|
|
159
|
+
btn.classList.toggle('active', active);
|
|
160
|
+
});
|
|
161
|
+
document.getElementById('searchBar').style.display = (tab === 'routes') ? 'flex' : 'none';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
document.getElementById('search').addEventListener('input', (e) => {
|
|
165
|
+
const keyword = e.target.value.toLowerCase();
|
|
166
|
+
const rows = document.querySelectorAll('#routesTable tbody tr');
|
|
167
|
+
rows.forEach(row => {
|
|
168
|
+
row.style.display = row.textContent.toLowerCase().includes(keyword) ? '' : 'none';
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
function copyText(text) {
|
|
173
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
174
|
+
alert('✅ Copied: ' + text);
|
|
175
|
+
}).catch(err => {
|
|
176
|
+
alert('❌ Failed to copy: ' + err);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
</script>
|
|
180
|
+
`;
|
|
181
|
+
}
|
package/html/routes.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dumpRoutes } from "../devtools";
|
|
1
|
+
import { dumpRoutes } from "../devtools/index.js";
|
|
2
2
|
export function Routes(ctx, app) {
|
|
3
3
|
const allRoutes = dumpRoutes(app);
|
|
4
4
|
const totalRoutes = allRoutes.length;
|
|
@@ -12,14 +12,14 @@ export function Routes(ctx, app) {
|
|
|
12
12
|
}
|
|
13
13
|
const rawJSON = JSON.stringify(allRoutes, null, 2);
|
|
14
14
|
const toCSV = (data) => {
|
|
15
|
-
const headers = [
|
|
15
|
+
const headers = ["method", "endpoint", "pattern", "appliedMiddlewares"];
|
|
16
16
|
const csvRows = [
|
|
17
|
-
headers.join(
|
|
18
|
-
...data.map(r => headers.map(h => JSON.stringify(r[h] ??
|
|
17
|
+
headers.join(","),
|
|
18
|
+
...data.map((r) => headers.map((h) => JSON.stringify(r[h] ?? "")).join(",")),
|
|
19
19
|
];
|
|
20
|
-
return csvRows.join(
|
|
20
|
+
return csvRows.join("\n");
|
|
21
21
|
};
|
|
22
|
-
const csvString = toCSV(allRoutes).replace(/"/g,
|
|
22
|
+
const csvString = toCSV(allRoutes).replace(/"/g, """);
|
|
23
23
|
return `
|
|
24
24
|
<style>
|
|
25
25
|
.download {
|
|
@@ -49,107 +49,136 @@ export function Routes(ctx, app) {
|
|
|
49
49
|
#searchBar input {
|
|
50
50
|
flex: 1;
|
|
51
51
|
}
|
|
52
|
-
</style>
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
table td button {
|
|
54
|
+
background: #e2e8f0;
|
|
55
|
+
border: none;
|
|
56
|
+
padding: 0.2rem 0.5rem;
|
|
57
|
+
border-radius: 0.375rem;
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
margin-left: 0.5rem;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
table td button:hover {
|
|
63
|
+
background: #cbd5e1;
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
66
|
+
|
|
67
|
+
<div class="tabs toolbar">
|
|
55
68
|
<a class="tab-btn active counting" data-count="${totalRoutes}" onclick="showTab('routes')">📋 Routes</a>
|
|
56
|
-
<a class="tab-btn counting" data-count="${Object.keys(middlewareStats).length}" onclick="showTab('stats')">📈
|
|
57
|
-
Stats</a>
|
|
69
|
+
<a class="tab-btn counting" data-count="${Object.keys(middlewareStats).length}" onclick="showTab('stats')">📈 Stats</a>
|
|
58
70
|
<a class="tab-btn" onclick="showTab('middlewares')">🧩 Middlewares</a>
|
|
59
71
|
<a class="tab-btn" onclick="showTab('json')">🧾 Raw JSON</a>
|
|
60
72
|
<a class="tab-btn" onclick="showTab('export')">📤 Export</a>
|
|
61
|
-
</div>
|
|
73
|
+
</div>
|
|
62
74
|
|
|
63
|
-
<div id="searchBar"
|
|
75
|
+
<div id="searchBar">
|
|
64
76
|
<input type="text" id="search" placeholder="Filter by method/path/middleware..." />
|
|
65
|
-
</div>
|
|
77
|
+
</div>
|
|
66
78
|
|
|
67
|
-
<!-- ROUTES -->
|
|
68
|
-
<div id="routesTab">
|
|
79
|
+
<!-- ROUTES -->
|
|
80
|
+
<div id="routesTab" class="table-container">
|
|
69
81
|
<table id="routesTable">
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
<thead>
|
|
83
|
+
<tr>
|
|
84
|
+
<th>#</th>
|
|
85
|
+
<th>Method</th>
|
|
86
|
+
<th>Endpoint</th>
|
|
87
|
+
<th>Pattern</th>
|
|
88
|
+
<th>Middlewares</th>
|
|
89
|
+
</tr>
|
|
90
|
+
</thead>
|
|
91
|
+
<tbody>
|
|
92
|
+
${allRoutes
|
|
93
|
+
.map((r, i) => `
|
|
94
|
+
<tr>
|
|
95
|
+
<td>${i + 1}</td>
|
|
96
|
+
<td>${r.method}</td>
|
|
97
|
+
<td>
|
|
98
|
+
${r.endpoint}
|
|
99
|
+
<button onclick="copyText(\`${r.endpoint}\`)">📋</button>
|
|
100
|
+
</td>
|
|
101
|
+
<td>
|
|
102
|
+
${r.pattern}
|
|
103
|
+
<button onclick="copyText(\`${r.pattern}\`)">📋</button>
|
|
104
|
+
</td>
|
|
105
|
+
<td>${(r.appliedMiddlewares || []).join(", ")}</td>
|
|
106
|
+
</tr>
|
|
107
|
+
`)
|
|
108
|
+
.join("")}
|
|
109
|
+
</tbody>
|
|
89
110
|
</table>
|
|
90
|
-
</div>
|
|
111
|
+
</div>
|
|
91
112
|
|
|
92
|
-
<!-- STATS -->
|
|
93
|
-
<div id="statsTab" style="display:none">
|
|
113
|
+
<!-- STATS -->
|
|
114
|
+
<div id="statsTab" style="display:none">
|
|
94
115
|
<div class="json-view">
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
116
|
+
Total Routes: ${totalRoutes}<br />
|
|
117
|
+
Middleware Used: ${Object.keys(middlewareStats).length}<br />
|
|
118
|
+
<pre>
|
|
119
|
+
${Object.entries(middlewareStats)
|
|
120
|
+
.map(([mw, count]) => `- ${mw}: ${count} routes`)
|
|
121
|
+
.join("\n")}
|
|
122
|
+
</pre>
|
|
99
123
|
</div>
|
|
100
|
-
</div>
|
|
101
|
-
|
|
102
|
-
<!-- MIDDLEWARES -->
|
|
103
|
-
<div id="middlewaresTab" style="display:none">
|
|
104
|
-
${Object.entries(middlewareRoutes)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<!-- MIDDLEWARES -->
|
|
127
|
+
<div id="middlewaresTab" style="display:none">
|
|
128
|
+
${Object.entries(middlewareRoutes)
|
|
129
|
+
.map(([mw, routes]) => `
|
|
130
|
+
<h3>🔹 ${mw} (${routes.length})</h3>
|
|
131
|
+
<ul>
|
|
132
|
+
${routes.map((r) => `<li><code>${r.method} ${r.endpoint}</code></li>`).join("")}
|
|
133
|
+
</ul>
|
|
134
|
+
`)
|
|
135
|
+
.join("")}
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<!-- RAW JSON -->
|
|
139
|
+
<div id="jsonTab" style="display:none">
|
|
140
|
+
<div class="json-view"><pre>${rawJSON}</pre></div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<!-- EXPORT -->
|
|
144
|
+
<div id="exportTab" style="display:none">
|
|
119
145
|
<div class="download">
|
|
120
|
-
|
|
121
|
-
|
|
146
|
+
<a href="data:text/json;charset=utf-8,${encodeURIComponent(rawJSON)}" download="routes.json">📥 JSON</a>
|
|
147
|
+
<a href="data:text/csv;charset=utf-8,${csvString}" download="routes.csv">📥 CSV</a>
|
|
122
148
|
</div>
|
|
123
|
-
</div>
|
|
149
|
+
</div>
|
|
124
150
|
|
|
125
|
-
<script>
|
|
151
|
+
<script>
|
|
126
152
|
const tabs = ['routes', 'stats', 'middlewares', 'json', 'export'];
|
|
127
153
|
const tabBtns = document.querySelectorAll('.tab-btn');
|
|
128
154
|
|
|
129
155
|
function showTab(tab) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
tabBtns.forEach(btn => {
|
|
140
|
-
const active = btn.textContent.toLowerCase().includes(tab);
|
|
141
|
-
btn.classList.toggle('active', active);
|
|
142
|
-
});
|
|
143
|
-
document.getElementById('searchBar').style.display = (tab === 'routes') ? 'flex' : 'none';
|
|
156
|
+
tabs.forEach(t => {
|
|
157
|
+
document.getElementById(t + 'Tab').style.display = t === tab ? 'block' : 'none';
|
|
158
|
+
document.getElementById(t + 'Tab').classList.toggle('active', t === tab);
|
|
159
|
+
});
|
|
160
|
+
tabBtns.forEach(btn => {
|
|
161
|
+
const active = btn.textContent.toLowerCase().includes(tab);
|
|
162
|
+
btn.classList.toggle('active', active);
|
|
163
|
+
});
|
|
164
|
+
document.getElementById('searchBar').style.display = (tab === 'routes') ? 'flex' : 'none';
|
|
144
165
|
}
|
|
145
166
|
|
|
146
167
|
document.getElementById('search').addEventListener('input', (e) => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
168
|
+
const keyword = e.target.value.toLowerCase();
|
|
169
|
+
const rows = document.querySelectorAll('#routesTable tbody tr');
|
|
170
|
+
rows.forEach(row => {
|
|
171
|
+
row.style.display = row.textContent.toLowerCase().includes(keyword) ? '' : 'none';
|
|
172
|
+
});
|
|
152
173
|
});
|
|
153
|
-
|
|
174
|
+
|
|
175
|
+
function copyText(text) {
|
|
176
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
177
|
+
alert('✅ Copied: ' + text);
|
|
178
|
+
}).catch(err => {
|
|
179
|
+
alert('❌ Failed to copy: ' + err);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
</script>
|
|
154
183
|
`;
|
|
155
184
|
}
|
package/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Callback, TezX } from "tezx";
|
|
1
|
+
import { Callback, Context, TezX } from "tezx";
|
|
2
2
|
import { Tab, TabType } from "./html/index.js";
|
|
3
|
+
export { dumpMiddlewares, dumpRoutes } from "./devtools/index.js";
|
|
3
4
|
export type Options = {
|
|
4
|
-
extraTabs?: TabType;
|
|
5
|
+
extraTabs?: (ctx: Context) => Promise<TabType> | TabType;
|
|
5
6
|
disableTabs?: Tab[];
|
|
6
7
|
};
|
|
7
8
|
export declare function DevTools(app: TezX<any>, options?: Options): Callback;
|