@tezx/devtools 1.0.4 โ 1.0.5
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/cjs/devtools/dumpRoutes.js +3 -4
- package/cjs/devtools/index.js +4 -8
- 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 +112 -83
- package/cjs/index.js +27 -10
- package/devtools/dumpRoutes.d.ts +2 -3
- package/devtools/dumpRoutes.js +3 -4
- package/devtools/index.d.ts +1 -5
- package/devtools/index.js +2 -7
- 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 +112 -83
- package/index.d.ts +2 -2
- package/index.js +27 -10
- package/package.json +1 -1
package/html/routes.js
CHANGED
|
@@ -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,7 @@
|
|
|
1
|
-
import { Callback, TezX } from "tezx";
|
|
1
|
+
import { Callback, Context, TezX } from "tezx";
|
|
2
2
|
import { Tab, TabType } from "./html/index.js";
|
|
3
3
|
export type Options = {
|
|
4
|
-
extraTabs?: TabType;
|
|
4
|
+
extraTabs?: (ctx: Context) => Promise<TabType> | TabType;
|
|
5
5
|
disableTabs?: Tab[];
|
|
6
6
|
};
|
|
7
7
|
export declare function DevTools(app: TezX<any>, options?: Options): Callback;
|
package/index.js
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
import { html as htmlTab } from "./html/index.js";
|
|
2
|
-
export function DevTools(app, options = { disableTabs: []
|
|
2
|
+
export function DevTools(app, options = { disableTabs: [] }) {
|
|
3
3
|
let { disableTabs, extraTabs } = options;
|
|
4
|
-
return (ctx) => {
|
|
5
|
-
let
|
|
4
|
+
return async (ctx) => {
|
|
5
|
+
let extraTabs = await (typeof options.extraTabs === "function"
|
|
6
|
+
? options.extraTabs(ctx)
|
|
7
|
+
: []);
|
|
8
|
+
let html = [
|
|
9
|
+
...(disableTabs?.length
|
|
10
|
+
? htmlTab(ctx, app)?.filter((r) => !disableTabs?.includes(r?.tab))
|
|
11
|
+
: htmlTab(ctx, app)),
|
|
12
|
+
...extraTabs,
|
|
13
|
+
];
|
|
6
14
|
let tab = ctx.req.query?._tab || html?.[0]?.tab;
|
|
7
15
|
const navbar = `
|
|
8
16
|
<header>
|
|
9
17
|
<div class="tabs">
|
|
10
18
|
<img src="http://papernxt.com/favicon.ico" style="height:32px;"/>
|
|
11
|
-
${html?.map(r => `<a href = "?_tab=${r?.tab}" class="${tab === r?.tab ?
|
|
19
|
+
${html?.map((r) => `<a href = "?_tab=${r?.tab}" class="${tab === r?.tab ? "active" : ""}" > ${r?.label} </a>`)?.join("\n")}
|
|
12
20
|
</div>
|
|
13
21
|
<div class="tabs">
|
|
14
22
|
<a class="toggle-dark" onclick="toggleTheme()">๐ Toggle Dark</a>
|
|
15
23
|
</div>
|
|
16
24
|
</header>
|
|
17
25
|
`;
|
|
18
|
-
let find = html.find(r => r?.tab == tab);
|
|
26
|
+
let find = html.find((r) => r?.tab == tab);
|
|
19
27
|
return ctx.html `
|
|
20
28
|
<!DOCTYPE html>
|
|
21
29
|
<html lang="en">
|
|
@@ -91,7 +99,13 @@ export function DevTools(app, options = { disableTabs: [], extraTabs: [] }) {
|
|
|
91
99
|
gap: 6px;
|
|
92
100
|
align-items: center;
|
|
93
101
|
}
|
|
94
|
-
|
|
102
|
+
.toolbar ,.action{
|
|
103
|
+
display: flex;
|
|
104
|
+
padding: 16px;
|
|
105
|
+
align-items: center;
|
|
106
|
+
gap: 6px;
|
|
107
|
+
flex-wrap: wrap;
|
|
108
|
+
}
|
|
95
109
|
.tabs a {
|
|
96
110
|
padding: 0.4rem 0.8rem;
|
|
97
111
|
text-decoration: none;
|
|
@@ -177,8 +191,10 @@ export function DevTools(app, options = { disableTabs: [], extraTabs: [] }) {
|
|
|
177
191
|
}
|
|
178
192
|
|
|
179
193
|
.table-container {
|
|
180
|
-
overflow
|
|
194
|
+
overflow: auto;
|
|
195
|
+
margin-top: 16px;
|
|
181
196
|
border-radius: 0.5rem;
|
|
197
|
+
height: 75vh;
|
|
182
198
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
183
199
|
}
|
|
184
200
|
|
|
@@ -201,6 +217,8 @@ export function DevTools(app, options = { disableTabs: [], extraTabs: [] }) {
|
|
|
201
217
|
thead {
|
|
202
218
|
background-color: var(--accent);
|
|
203
219
|
color: white;
|
|
220
|
+
position: sticky;
|
|
221
|
+
top: 0px;
|
|
204
222
|
}
|
|
205
223
|
|
|
206
224
|
/* Light mode zebra striping */
|
|
@@ -226,9 +244,8 @@ export function DevTools(app, options = { disableTabs: [], extraTabs: [] }) {
|
|
|
226
244
|
color: red;
|
|
227
245
|
font-weight: bold;
|
|
228
246
|
}
|
|
229
|
-
|
|
247
|
+
/**
|
|
230
248
|
@media (max-width: 768px) {
|
|
231
|
-
|
|
232
249
|
table,
|
|
233
250
|
thead,
|
|
234
251
|
tbody,
|
|
@@ -262,6 +279,7 @@ export function DevTools(app, options = { disableTabs: [], extraTabs: [] }) {
|
|
|
262
279
|
color: var(--accent);
|
|
263
280
|
}
|
|
264
281
|
}
|
|
282
|
+
**/
|
|
265
283
|
</style>
|
|
266
284
|
</head>
|
|
267
285
|
<body>
|
|
@@ -274,7 +292,6 @@ export function DevTools(app, options = { disableTabs: [], extraTabs: [] }) {
|
|
|
274
292
|
${find?.content}
|
|
275
293
|
</section>
|
|
276
294
|
<script>
|
|
277
|
-
|
|
278
295
|
const themeCookieName = "tezx-theme";
|
|
279
296
|
|
|
280
297
|
function setCookie(name, value, days = 30) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tezx/devtools",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Developer tools for the TezX framework, including route inspector, cookie manager, and real-time diagnostics. Lightweight and plug-and-play compatible with Node.js, Bun, and Deno.",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "index.js",
|