express-file-cluster 0.2.1 → 0.2.3
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/auth/index.cjs +2 -2
- package/dist/auth/index.d.cts +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.js +1 -1
- package/dist/{chunk-DQGWFWBV.js → chunk-BHMFPULA.js} +1 -1
- package/dist/chunk-BHMFPULA.js.map +1 -0
- package/dist/{chunk-Z5YNIVQG.js → chunk-LPZEKFVN.js} +2 -4
- package/dist/{chunk-Z5YNIVQG.js.map → chunk-LPZEKFVN.js.map} +1 -1
- package/dist/{chunk-QB3X6LKZ.cjs → chunk-UUF2OYRW.cjs} +1 -1
- package/dist/{chunk-QB3X6LKZ.cjs.map → chunk-UUF2OYRW.cjs.map} +1 -1
- package/dist/{chunk-UDE6S53C.cjs → chunk-XH7O25BL.cjs} +2 -4
- package/dist/chunk-XH7O25BL.cjs.map +1 -0
- package/dist/cli/index.cjs +66 -5
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +89 -28
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +615 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +608 -10
- package/dist/index.js.map +1 -1
- package/dist/tasks/index.cjs +2 -2
- package/dist/tasks/index.d.cts +1 -1
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/{types-Dj9-sLOK.d.cts → types-BZehD43d.d.cts} +16 -3
- package/dist/{types-Dj9-sLOK.d.ts → types-BZehD43d.d.ts} +16 -3
- package/package.json +8 -8
- package/dist/chunk-DQGWFWBV.js.map +0 -1
- package/dist/chunk-UDE6S53C.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
configureAuth
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-BHMFPULA.js";
|
|
4
4
|
import {
|
|
5
5
|
registerTask,
|
|
6
6
|
setEnqueueImpl,
|
|
7
7
|
taskRegistry
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-LPZEKFVN.js";
|
|
9
9
|
import {
|
|
10
10
|
scanDir
|
|
11
11
|
} from "./chunk-MZPG5HXL.js";
|
|
12
12
|
|
|
13
13
|
// src/index.ts
|
|
14
|
+
import fs2 from "fs";
|
|
14
15
|
import express from "express";
|
|
15
16
|
import cors from "cors";
|
|
16
17
|
import cookieParser from "cookie-parser";
|
|
17
18
|
import cluster2 from "cluster";
|
|
18
19
|
import os2 from "os";
|
|
20
|
+
import path2 from "path";
|
|
19
21
|
|
|
20
22
|
// src/router/mount.ts
|
|
21
23
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
@@ -25,9 +27,12 @@ function asyncWrap(handler) {
|
|
|
25
27
|
};
|
|
26
28
|
}
|
|
27
29
|
async function mountRoutes(app, routes) {
|
|
30
|
+
const mounted = [];
|
|
28
31
|
for (const route of routes) {
|
|
29
32
|
const mod = await import(route.filePath);
|
|
30
33
|
const routeMiddlewares = Array.isArray(mod["middlewares"]) ? mod["middlewares"] : [];
|
|
34
|
+
const raw = mod["meta"];
|
|
35
|
+
const meta = raw != null && typeof raw === "object" && !Array.isArray(raw) ? raw : void 0;
|
|
31
36
|
const implemented = [];
|
|
32
37
|
const unimplemented = [];
|
|
33
38
|
for (const method of HTTP_METHODS) {
|
|
@@ -52,7 +57,9 @@ async function mountRoutes(app, routes) {
|
|
|
52
57
|
}
|
|
53
58
|
});
|
|
54
59
|
}
|
|
60
|
+
mounted.push({ ...route, methods: implemented, ...meta !== void 0 && { meta } });
|
|
55
61
|
}
|
|
62
|
+
return mounted;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
// src/cluster/index.ts
|
|
@@ -96,9 +103,7 @@ async function loadMongoose() {
|
|
|
96
103
|
mongoose = await import("mongoose");
|
|
97
104
|
return mongoose;
|
|
98
105
|
} catch {
|
|
99
|
-
throw new Error(
|
|
100
|
-
"[EFC] mongoose is not installed. Run: npm install mongoose"
|
|
101
|
-
);
|
|
106
|
+
throw new Error("[EFC] mongoose is not installed. Run: npm install mongoose");
|
|
102
107
|
}
|
|
103
108
|
}
|
|
104
109
|
async function connectMongo(url) {
|
|
@@ -311,7 +316,9 @@ async function initBullMQ(opts) {
|
|
|
311
316
|
setEnqueueImpl(async (name, payload) => {
|
|
312
317
|
const def = taskRegistry.get(name);
|
|
313
318
|
if (!def) {
|
|
314
|
-
throw new Error(
|
|
319
|
+
throw new Error(
|
|
320
|
+
`[EFC] Cannot enqueue unknown task: "${name}". Is the task file in tasksDir?`
|
|
321
|
+
);
|
|
315
322
|
}
|
|
316
323
|
await queue.add(name, payload, {
|
|
317
324
|
attempts: def.options.retries ?? 3,
|
|
@@ -337,6 +344,568 @@ function parseRedisUrl(url) {
|
|
|
337
344
|
}
|
|
338
345
|
}
|
|
339
346
|
|
|
347
|
+
// src/dashboard.ts
|
|
348
|
+
var HTTP_STATUS = {
|
|
349
|
+
200: "OK",
|
|
350
|
+
201: "Created",
|
|
351
|
+
202: "Accepted",
|
|
352
|
+
204: "No Content",
|
|
353
|
+
400: "Bad Request",
|
|
354
|
+
401: "Unauthorized",
|
|
355
|
+
403: "Forbidden",
|
|
356
|
+
404: "Not Found",
|
|
357
|
+
405: "Method Not Allowed",
|
|
358
|
+
409: "Conflict",
|
|
359
|
+
422: "Unprocessable Entity",
|
|
360
|
+
500: "Internal Server Error",
|
|
361
|
+
503: "Service Unavailable"
|
|
362
|
+
};
|
|
363
|
+
function generateDashboard(routes, basePath, port, projectName, projectVersion) {
|
|
364
|
+
const safeRoutes = JSON.stringify(
|
|
365
|
+
routes.map((r) => ({ urlPath: r.urlPath, methods: r.methods, params: r.params, meta: r.meta ?? null }))
|
|
366
|
+
).replace(/<\/script>/gi, "<\\/script>");
|
|
367
|
+
const safeHttpStatus = JSON.stringify(HTTP_STATUS);
|
|
368
|
+
return `<!DOCTYPE html>
|
|
369
|
+
<html lang="en">
|
|
370
|
+
<head>
|
|
371
|
+
<meta charset="UTF-8" />
|
|
372
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
373
|
+
<title>${projectName} \u2014 API Reference</title>
|
|
374
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
375
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
376
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
377
|
+
<style>
|
|
378
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
379
|
+
:root {
|
|
380
|
+
--bg: #080b0f;
|
|
381
|
+
--bg-card: #0d1117;
|
|
382
|
+
--bg-raised: #111820;
|
|
383
|
+
--border: #1e2832;
|
|
384
|
+
--border-lit: #263040;
|
|
385
|
+
--text: #e2e8f0;
|
|
386
|
+
--text-dim: #64748b;
|
|
387
|
+
--text-muted: #334155;
|
|
388
|
+
--accent: #F0A030;
|
|
389
|
+
--accent-dim: rgba(240,160,48,.12);
|
|
390
|
+
--accent-glow: rgba(240,160,48,.22);
|
|
391
|
+
--purple: #a78bfa;
|
|
392
|
+
--blue: #60a5fa;
|
|
393
|
+
--orange: #fb923c;
|
|
394
|
+
--green: #7AAE8E;
|
|
395
|
+
--radius: 10px;
|
|
396
|
+
--radius-lg: 16px;
|
|
397
|
+
--font-mono: 'JetBrains Mono', monospace;
|
|
398
|
+
--font-sans: 'Inter', sans-serif;
|
|
399
|
+
}
|
|
400
|
+
html { scroll-behavior: smooth; }
|
|
401
|
+
body {
|
|
402
|
+
background: var(--bg); color: var(--text);
|
|
403
|
+
font-family: var(--font-sans); font-size: 16px;
|
|
404
|
+
line-height: 1.6; -webkit-font-smoothing: antialiased;
|
|
405
|
+
position: relative;
|
|
406
|
+
}
|
|
407
|
+
body::before {
|
|
408
|
+
content: ''; position: fixed; inset: 0; z-index: 0;
|
|
409
|
+
background-image:
|
|
410
|
+
linear-gradient(var(--border) 1px, transparent 1px),
|
|
411
|
+
linear-gradient(90deg, var(--border) 1px, transparent 1px);
|
|
412
|
+
background-size: 60px 60px; opacity: .28; pointer-events: none;
|
|
413
|
+
}
|
|
414
|
+
section, footer, .page-hero, .docs-layout { position: relative; z-index: 1; }
|
|
415
|
+
a { text-decoration: none; color: inherit; }
|
|
416
|
+
em { font-style: italic; }
|
|
417
|
+
code {
|
|
418
|
+
font-family: var(--font-mono); font-size: .85em;
|
|
419
|
+
background: var(--bg-raised); border: 1px solid var(--border);
|
|
420
|
+
border-radius: 4px; padding: 1px 6px; color: var(--accent);
|
|
421
|
+
}
|
|
422
|
+
.container { max-width: 1140px; margin: 0 auto; padding: 0 24px; }
|
|
423
|
+
::-webkit-scrollbar { width: 6px; }
|
|
424
|
+
::-webkit-scrollbar-track { background: var(--bg); }
|
|
425
|
+
::-webkit-scrollbar-thumb { background: var(--border-lit); border-radius: 3px; }
|
|
426
|
+
|
|
427
|
+
/* \u2500\u2500 PAGE HERO \u2500\u2500 */
|
|
428
|
+
.page-hero {
|
|
429
|
+
padding: 48px 0 56px; border-bottom: 1px solid var(--border);
|
|
430
|
+
}
|
|
431
|
+
.page-hero .hero-glow {
|
|
432
|
+
position: absolute; top: -40px; left: 10%; width: 500px; height: 350px;
|
|
433
|
+
background: radial-gradient(ellipse, rgba(240,160,48,.1) 0%, transparent 68%);
|
|
434
|
+
pointer-events: none;
|
|
435
|
+
}
|
|
436
|
+
.page-title {
|
|
437
|
+
font-size: clamp(2rem, 5vw, 3.4rem); font-weight: 800;
|
|
438
|
+
line-height: 1.1; letter-spacing: -.02em; margin-bottom: 24px;
|
|
439
|
+
display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap;
|
|
440
|
+
}
|
|
441
|
+
.project-version {
|
|
442
|
+
font-family: var(--font-mono); font-size: clamp(.85rem, 2vw, 1.1rem);
|
|
443
|
+
font-weight: 500; color: var(--accent);
|
|
444
|
+
background: var(--accent-dim); border: 1px solid rgba(240,160,48,.3);
|
|
445
|
+
border-radius: 6px; padding: 2px 10px; letter-spacing: .5px;
|
|
446
|
+
}
|
|
447
|
+
.hero-meta { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 40px; }
|
|
448
|
+
.base-url-pill {
|
|
449
|
+
display: flex; align-items: center; gap: 8px;
|
|
450
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
451
|
+
border-radius: 7px; padding: 8px 14px;
|
|
452
|
+
}
|
|
453
|
+
.base-url-label { font-size: .72rem; font-family: var(--font-mono);
|
|
454
|
+
color: var(--text-muted); text-transform: uppercase; letter-spacing: .5px; }
|
|
455
|
+
.base-url-val { font-family: var(--font-mono); font-size: .8rem; color: var(--accent); }
|
|
456
|
+
.count-pill {
|
|
457
|
+
font-family: var(--font-mono); font-size: .72rem; color: var(--text-dim);
|
|
458
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
459
|
+
border-radius: 7px; padding: 8px 14px;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/* \u2500\u2500 TOOLBAR \u2500\u2500 */
|
|
463
|
+
.toolbar {
|
|
464
|
+
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
|
|
465
|
+
}
|
|
466
|
+
.method-filters { display: flex; gap: 4px; flex-wrap: wrap; }
|
|
467
|
+
.filter-btn {
|
|
468
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
469
|
+
color: var(--text-dim); font-family: var(--font-mono);
|
|
470
|
+
font-size: .72rem; font-weight: 600; letter-spacing: .5px;
|
|
471
|
+
padding: 6px 12px; border-radius: 6px; cursor: pointer;
|
|
472
|
+
transition: all .15s;
|
|
473
|
+
}
|
|
474
|
+
.filter-btn:hover { border-color: var(--border-lit); color: var(--text); }
|
|
475
|
+
.filter-btn.active { background: var(--accent); color: #0d0d1a; border-color: var(--accent); }
|
|
476
|
+
.filter-btn[data-method="GET"].active { background: rgba(96,165,250,.18); color: #60a5fa; border-color: rgba(96,165,250,.4); }
|
|
477
|
+
.filter-btn[data-method="POST"].active { background: rgba(122,174,142,.18); color: #7AAE8E; border-color: rgba(122,174,142,.4); }
|
|
478
|
+
.filter-btn[data-method="PUT"].active { background: rgba(240,160,48,.18); color: #F0A030; border-color: rgba(240,160,48,.4); }
|
|
479
|
+
.filter-btn[data-method="PATCH"].active { background: rgba(251,191,36,.18); color: #fbbf24; border-color: rgba(251,191,36,.4); }
|
|
480
|
+
.filter-btn[data-method="DELETE"].active { background: rgba(248,113,113,.18); color: #f87171; border-color: rgba(248,113,113,.4); }
|
|
481
|
+
.filter-btn[data-method="HEAD"].active { background: rgba(167,139,250,.18); color: #a78bfa; border-color: rgba(167,139,250,.4); }
|
|
482
|
+
.search-wrap { margin-left: auto; }
|
|
483
|
+
.search-input {
|
|
484
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
485
|
+
color: var(--text); font-family: var(--font-sans); font-size: .85rem;
|
|
486
|
+
padding: 7px 14px; border-radius: 7px; width: 240px;
|
|
487
|
+
outline: none; transition: border-color .2s;
|
|
488
|
+
}
|
|
489
|
+
.search-input::placeholder { color: var(--text-muted); }
|
|
490
|
+
.search-input:focus { border-color: var(--border-lit); }
|
|
491
|
+
|
|
492
|
+
/* \u2500\u2500 DOCS LAYOUT \u2500\u2500 */
|
|
493
|
+
.docs-layout {
|
|
494
|
+
display: grid; grid-template-columns: 220px 1fr;
|
|
495
|
+
gap: 48px; padding: 52px 0 80px; max-width: 1140px;
|
|
496
|
+
margin: 0 auto; padding-left: 24px; padding-right: 24px;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/* \u2500\u2500 SIDEBAR \u2500\u2500 */
|
|
500
|
+
.docs-sidebar { position: sticky; top: 24px; height: fit-content; }
|
|
501
|
+
.sidebar-section { margin-bottom: 28px; }
|
|
502
|
+
.sidebar-label {
|
|
503
|
+
font-family: var(--font-mono); font-size: .68rem; font-weight: 600;
|
|
504
|
+
color: var(--text-muted); letter-spacing: 1.2px; text-transform: uppercase;
|
|
505
|
+
margin-bottom: 8px; padding: 0 4px;
|
|
506
|
+
}
|
|
507
|
+
.sidebar-link {
|
|
508
|
+
display: flex; align-items: center; gap: 8px;
|
|
509
|
+
padding: 7px 10px; border-radius: 6px;
|
|
510
|
+
border-left: 2px solid transparent;
|
|
511
|
+
font-size: .82rem; color: var(--text-dim); cursor: pointer;
|
|
512
|
+
transition: background .15s, color .15s, border-color .15s;
|
|
513
|
+
text-overflow: ellipsis; overflow: hidden; white-space: nowrap;
|
|
514
|
+
}
|
|
515
|
+
.sidebar-link:hover { background: var(--bg-raised); color: var(--text); }
|
|
516
|
+
.sidebar-link.active { color: var(--accent); border-left-color: var(--accent); background: var(--accent-dim); }
|
|
517
|
+
.sidebar-method-dot {
|
|
518
|
+
width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
|
|
519
|
+
}
|
|
520
|
+
.sidebar-path { font-family: var(--font-mono); font-size: .75rem; }
|
|
521
|
+
|
|
522
|
+
/* \u2500\u2500 ENDPOINT CARDS \u2500\u2500 */
|
|
523
|
+
.endpoint-card {
|
|
524
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
525
|
+
border-radius: var(--radius-lg); margin-bottom: 20px;
|
|
526
|
+
overflow: hidden; transition: border-color .2s;
|
|
527
|
+
animation: fadeUp .45s ease both;
|
|
528
|
+
}
|
|
529
|
+
.endpoint-card:hover { border-color: var(--border-lit); }
|
|
530
|
+
@keyframes fadeUp {
|
|
531
|
+
from { opacity: 0; transform: translateY(16px); }
|
|
532
|
+
to { opacity: 1; transform: translateY(0); }
|
|
533
|
+
}
|
|
534
|
+
@media (prefers-reduced-motion: reduce) {
|
|
535
|
+
.endpoint-card { animation: none; }
|
|
536
|
+
}
|
|
537
|
+
.endpoint-header {
|
|
538
|
+
display: flex; align-items: center; gap: 14px;
|
|
539
|
+
padding: 20px 24px 18px; border-bottom: 1px solid var(--border);
|
|
540
|
+
flex-wrap: wrap;
|
|
541
|
+
}
|
|
542
|
+
.method-badges { display: flex; gap: 6px; flex-wrap: wrap; }
|
|
543
|
+
.method-badge {
|
|
544
|
+
font-family: var(--font-mono); font-size: .7rem; font-weight: 600;
|
|
545
|
+
letter-spacing: .8px; padding: 4px 10px; border-radius: 5px;
|
|
546
|
+
border: 1px solid; text-transform: uppercase;
|
|
547
|
+
}
|
|
548
|
+
.endpoint-path {
|
|
549
|
+
font-family: var(--font-mono); font-size: .9rem; color: var(--text);
|
|
550
|
+
background: none; border: none; padding: 0;
|
|
551
|
+
word-break: break-all;
|
|
552
|
+
}
|
|
553
|
+
.endpoint-body { padding: 18px 24px 24px; }
|
|
554
|
+
.endpoint-desc { font-size: .9rem; color: var(--text-dim); line-height: 1.7; margin-bottom: 20px; }
|
|
555
|
+
|
|
556
|
+
/* \u2500\u2500 CODE BLOCKS \u2500\u2500 */
|
|
557
|
+
.example-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
558
|
+
.code-block-wrap {
|
|
559
|
+
background: var(--bg-raised); border: 1px solid var(--border);
|
|
560
|
+
border-radius: var(--radius); overflow: hidden;
|
|
561
|
+
}
|
|
562
|
+
.code-bar {
|
|
563
|
+
display: flex; align-items: center; gap: 6px;
|
|
564
|
+
padding: 9px 14px; border-bottom: 1px solid var(--border);
|
|
565
|
+
background: var(--bg-card);
|
|
566
|
+
}
|
|
567
|
+
.dot { width: 11px; height: 11px; border-radius: 50%; }
|
|
568
|
+
.dot-r { background: #ff5f57; }
|
|
569
|
+
.dot-y { background: #febc2e; }
|
|
570
|
+
.dot-g { background: #28c840; }
|
|
571
|
+
.code-label { font-family: var(--font-mono); font-size: .68rem; color: var(--text-dim); margin-left: 4px; }
|
|
572
|
+
pre.code-pre {
|
|
573
|
+
font-family: var(--font-mono); font-size: .78rem; line-height: 1.7;
|
|
574
|
+
padding: 16px 18px; overflow-x: auto; tab-size: 2; white-space: pre;
|
|
575
|
+
}
|
|
576
|
+
.c-dim { color: var(--text-muted); }
|
|
577
|
+
.c-green { color: #7AAE8E; }
|
|
578
|
+
.c-blue { color: #60a5fa; }
|
|
579
|
+
.c-orange { color: #fb923c; }
|
|
580
|
+
.c-purple { color: #a78bfa; }
|
|
581
|
+
.c-accent { color: var(--accent); }
|
|
582
|
+
.status-2xx { color: #7AAE8E; }
|
|
583
|
+
.status-4xx { color: #f87171; }
|
|
584
|
+
.status-5xx { color: #fb923c; }
|
|
585
|
+
|
|
586
|
+
/* \u2500\u2500 EMPTY STATE \u2500\u2500 */
|
|
587
|
+
.empty-state {
|
|
588
|
+
text-align: center; padding: 80px 24px;
|
|
589
|
+
color: var(--text-muted); font-size: .9rem;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/* \u2500\u2500 RESPONSIVE \u2500\u2500 */
|
|
593
|
+
@media (max-width: 900px) {
|
|
594
|
+
.docs-layout { grid-template-columns: 1fr; }
|
|
595
|
+
.docs-sidebar { position: static; }
|
|
596
|
+
.example-grid { grid-template-columns: 1fr; }
|
|
597
|
+
.search-wrap { margin-left: 0; width: 100%; }
|
|
598
|
+
.search-input { width: 100%; }
|
|
599
|
+
}
|
|
600
|
+
@media (max-width: 600px) {
|
|
601
|
+
.page-hero { padding: 32px 0 32px; }
|
|
602
|
+
.page-title { font-size: 2rem; }
|
|
603
|
+
}
|
|
604
|
+
</style>
|
|
605
|
+
</head>
|
|
606
|
+
<body>
|
|
607
|
+
|
|
608
|
+
<div class="page-hero">
|
|
609
|
+
<div class="hero-glow"></div>
|
|
610
|
+
<div class="container">
|
|
611
|
+
<h1 class="page-title">${projectName}${projectVersion ? `<span class="project-version">v${projectVersion}</span>` : ""}</h1>
|
|
612
|
+
<div class="hero-meta">
|
|
613
|
+
<div class="base-url-pill">
|
|
614
|
+
<span class="base-url-label">Base</span>
|
|
615
|
+
<span class="base-url-val">http://localhost:${port}${basePath}</span>
|
|
616
|
+
</div>
|
|
617
|
+
<div class="count-pill" id="countPill"></div>
|
|
618
|
+
</div>
|
|
619
|
+
<div class="toolbar">
|
|
620
|
+
<div class="method-filters" id="methodFilters">
|
|
621
|
+
<button class="filter-btn active" data-method="ALL">ALL</button>
|
|
622
|
+
</div>
|
|
623
|
+
<div class="search-wrap">
|
|
624
|
+
<input type="text" id="searchInput" class="search-input" placeholder="Search endpoints\u2026" />
|
|
625
|
+
</div>
|
|
626
|
+
</div>
|
|
627
|
+
</div>
|
|
628
|
+
</div>
|
|
629
|
+
|
|
630
|
+
<div class="docs-layout">
|
|
631
|
+
<aside class="docs-sidebar" id="sidebar"></aside>
|
|
632
|
+
<main id="endpoints"></main>
|
|
633
|
+
</div>
|
|
634
|
+
|
|
635
|
+
<script>
|
|
636
|
+
const ROUTES = ${safeRoutes};
|
|
637
|
+
const BASE_PATH = ${JSON.stringify(basePath)};
|
|
638
|
+
const HTTP_STATUS = ${safeHttpStatus};
|
|
639
|
+
|
|
640
|
+
const METHOD_STYLE = {
|
|
641
|
+
GET: { bg: 'rgba(96,165,250,.14)', color: '#60a5fa', border: 'rgba(96,165,250,.35)' },
|
|
642
|
+
POST: { bg: 'rgba(122,174,142,.14)', color: '#7AAE8E', border: 'rgba(122,174,142,.35)' },
|
|
643
|
+
PUT: { bg: 'rgba(240,160,48,.14)', color: '#F0A030', border: 'rgba(240,160,48,.35)' },
|
|
644
|
+
PATCH: { bg: 'rgba(251,191,36,.14)', color: '#fbbf24', border: 'rgba(251,191,36,.35)' },
|
|
645
|
+
DELETE: { bg: 'rgba(248,113,113,.14)', color: '#f87171', border: 'rgba(248,113,113,.35)' },
|
|
646
|
+
HEAD: { bg: 'rgba(167,139,250,.14)', color: '#a78bfa', border: 'rgba(167,139,250,.35)' },
|
|
647
|
+
OPTIONS: { bg: 'rgba(148,163,184,.14)', color: '#94a3b8', border: 'rgba(148,163,184,.35)' },
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
function esc(s) {
|
|
651
|
+
return String(s)
|
|
652
|
+
.replace(/&/g,'&').replace(/</g,'<')
|
|
653
|
+
.replace(/>/g,'>').replace(/"/g,'"');
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function methodBadge(m) {
|
|
657
|
+
const s = METHOD_STYLE[m] || METHOD_STYLE.GET;
|
|
658
|
+
return '<span class="method-badge" style="background:' + s.bg + ';color:' + s.color + ';border-color:' + s.border + '">' + m + '</span>';
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function joinPath(base, p) {
|
|
662
|
+
return (base.replace(/\\/+$/, '') + p) || '/';
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function sidebarDot(method) {
|
|
666
|
+
const s = METHOD_STYLE[method] || METHOD_STYLE.GET;
|
|
667
|
+
return '<span class="sidebar-method-dot" style="background:' + s.color + '"></span>';
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function statusClass(code) {
|
|
671
|
+
if (code >= 200 && code < 300) return 'status-2xx';
|
|
672
|
+
if (code >= 400 && code < 500) return 'status-4xx';
|
|
673
|
+
if (code >= 500) return 'status-5xx';
|
|
674
|
+
return '';
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function renderSchema(val, indent) {
|
|
678
|
+
const pad = ' '.repeat(indent || 0);
|
|
679
|
+
if (val === null) return '<span class="c-dim">null</span>';
|
|
680
|
+
if (typeof val === 'string') {
|
|
681
|
+
const isDate = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/.test(val);
|
|
682
|
+
return isDate ? '<span class="c-green">Date</span>' : '<span class="c-purple">String</span>';
|
|
683
|
+
}
|
|
684
|
+
if (typeof val === 'number') return '<span class="c-orange">Number</span>';
|
|
685
|
+
if (typeof val === 'boolean') return '<span class="c-purple">Boolean</span>';
|
|
686
|
+
if (Array.isArray(val)) {
|
|
687
|
+
if (!val.length) return '[]';
|
|
688
|
+
return '[\\n' + pad + ' ' + renderSchema(val[0], (indent || 0) + 1) + '\\n' + pad + ']';
|
|
689
|
+
}
|
|
690
|
+
if (typeof val === 'object') {
|
|
691
|
+
const keys = Object.keys(val);
|
|
692
|
+
if (!keys.length) return '{}';
|
|
693
|
+
const inner = keys.map(function(k) {
|
|
694
|
+
return pad + ' <span class="c-blue">"' + esc(k) + '"</span>: ' + renderSchema(val[k], (indent || 0) + 1);
|
|
695
|
+
}).join(',\\n');
|
|
696
|
+
return '{\\n' + inner + '\\n' + pad + '}';
|
|
697
|
+
}
|
|
698
|
+
return esc(String(val));
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function codeBlock(label, content) {
|
|
702
|
+
return '<div class="code-block-wrap">' +
|
|
703
|
+
'<div class="code-bar">' +
|
|
704
|
+
'<span class="dot dot-r"></span>' +
|
|
705
|
+
'<span class="dot dot-y"></span>' +
|
|
706
|
+
'<span class="dot dot-g"></span>' +
|
|
707
|
+
'<span class="code-label">' + esc(label) + '</span>' +
|
|
708
|
+
'</div>' +
|
|
709
|
+
'<pre class="code-pre">' + content + '</pre>' +
|
|
710
|
+
'</div>';
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function buildRequestContent(route, example) {
|
|
714
|
+
const req = example || {};
|
|
715
|
+
const method = route.methods[0] || 'GET';
|
|
716
|
+
let path = joinPath(BASE_PATH, route.urlPath);
|
|
717
|
+
if (req.params) {
|
|
718
|
+
for (const [k, v] of Object.entries(req.params)) {
|
|
719
|
+
path = path.replace(':' + k, String(v));
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (req.query && Object.keys(req.query).length) {
|
|
723
|
+
path += '?' + new URLSearchParams(req.query).toString();
|
|
724
|
+
}
|
|
725
|
+
let lines = '<span class="c-accent">' + esc(method) + '</span> <span class="c-blue">' + esc(path) + '</span>';
|
|
726
|
+
const headers = { ...(req.headers || {}) };
|
|
727
|
+
if (req.body !== undefined && !headers['Content-Type']) {
|
|
728
|
+
headers['Content-Type'] = 'application/json';
|
|
729
|
+
}
|
|
730
|
+
if (Object.keys(headers).length) {
|
|
731
|
+
lines += '\\n' + Object.entries(headers)
|
|
732
|
+
.map(([k, v]) => '<span class="c-dim">' + esc(k) + ':</span> ' + esc(String(v)))
|
|
733
|
+
.join('\\n');
|
|
734
|
+
}
|
|
735
|
+
if (req.body !== undefined) {
|
|
736
|
+
lines += '\\n\\n' + renderSchema(req.body, 0);
|
|
737
|
+
}
|
|
738
|
+
return lines;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function buildResponseContent(example) {
|
|
742
|
+
const resp = example || {};
|
|
743
|
+
const code = resp.status || 200;
|
|
744
|
+
const text = HTTP_STATUS[code] || 'OK';
|
|
745
|
+
const cls = statusClass(code);
|
|
746
|
+
let lines = 'HTTP/1.1 <span class="' + cls + '">' + code + ' ' + esc(text) + '</span>';
|
|
747
|
+
if (resp.body !== undefined) {
|
|
748
|
+
lines += '\\n<span class="c-dim">Content-Type:</span> application/json\\n\\n';
|
|
749
|
+
lines += renderSchema(resp.body, 0);
|
|
750
|
+
}
|
|
751
|
+
return lines;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function renderCard(route, index) {
|
|
755
|
+
const id = 'ep-' + index;
|
|
756
|
+
const fullPath = joinPath(BASE_PATH, route.urlPath);
|
|
757
|
+
const badges = route.methods.map(methodBadge).join('');
|
|
758
|
+
const meta = route.meta;
|
|
759
|
+
const desc = meta && meta.description
|
|
760
|
+
? '<p class="endpoint-desc">' + esc(meta.description) + '</p>'
|
|
761
|
+
: '';
|
|
762
|
+
let examples = '';
|
|
763
|
+
if (meta) {
|
|
764
|
+
const reqHtml = meta.request !== undefined
|
|
765
|
+
? codeBlock('request', buildRequestContent(route, meta.request))
|
|
766
|
+
: '';
|
|
767
|
+
const respHtml = meta.response !== undefined
|
|
768
|
+
? codeBlock('response', buildResponseContent(meta.response))
|
|
769
|
+
: '';
|
|
770
|
+
if (reqHtml || respHtml) {
|
|
771
|
+
examples = '<div class="example-grid">' + reqHtml + respHtml + '</div>';
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const delay = (index * 0.06).toFixed(2) + 's';
|
|
775
|
+
return '<div class="endpoint-card" id="' + id + '" data-methods="' + route.methods.join(',') + '" data-path="' + esc(fullPath) + '" style="animation-delay:' + delay + '">' +
|
|
776
|
+
'<div class="endpoint-header">' +
|
|
777
|
+
'<div class="method-badges">' + badges + '</div>' +
|
|
778
|
+
'<code class="endpoint-path">' + esc(fullPath) + '</code>' +
|
|
779
|
+
'</div>' +
|
|
780
|
+
'<div class="endpoint-body">' + desc + examples + '</div>' +
|
|
781
|
+
'</div>';
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function buildMethodFilters() {
|
|
785
|
+
const allMethods = [...new Set(ROUTES.flatMap(r => r.methods))].sort();
|
|
786
|
+
const filtersEl = document.getElementById('methodFilters');
|
|
787
|
+
allMethods.forEach(m => {
|
|
788
|
+
const btn = document.createElement('button');
|
|
789
|
+
btn.className = 'filter-btn';
|
|
790
|
+
btn.dataset.method = m;
|
|
791
|
+
btn.textContent = m;
|
|
792
|
+
filtersEl.appendChild(btn);
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function buildSidebar(routes) {
|
|
797
|
+
const sidebar = document.getElementById('sidebar');
|
|
798
|
+
if (!routes.length) { sidebar.innerHTML = ''; return; }
|
|
799
|
+
|
|
800
|
+
const label = document.createElement('div');
|
|
801
|
+
label.className = 'sidebar-label';
|
|
802
|
+
label.textContent = 'Endpoints';
|
|
803
|
+
sidebar.appendChild(label);
|
|
804
|
+
|
|
805
|
+
routes.forEach((route, i) => {
|
|
806
|
+
const link = document.createElement('div');
|
|
807
|
+
link.className = 'sidebar-link';
|
|
808
|
+
link.dataset.target = 'ep-' + ROUTES.indexOf(route);
|
|
809
|
+
link.innerHTML = sidebarDot(route.methods[0] || 'GET') +
|
|
810
|
+
'<span class="sidebar-path">' + esc(joinPath(BASE_PATH, route.urlPath)) + '</span>';
|
|
811
|
+
link.addEventListener('click', () => {
|
|
812
|
+
const target = document.getElementById(link.dataset.target);
|
|
813
|
+
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
814
|
+
});
|
|
815
|
+
sidebar.appendChild(link);
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
let activeFilter = 'ALL';
|
|
820
|
+
let searchQuery = '';
|
|
821
|
+
|
|
822
|
+
function applyFilters() {
|
|
823
|
+
const cards = document.querySelectorAll('.endpoint-card');
|
|
824
|
+
let visible = 0;
|
|
825
|
+
cards.forEach(card => {
|
|
826
|
+
const methods = card.dataset.methods.split(',');
|
|
827
|
+
const path = card.dataset.path || '';
|
|
828
|
+
const matchMethod = activeFilter === 'ALL' || methods.includes(activeFilter);
|
|
829
|
+
const matchSearch = !searchQuery || path.toLowerCase().includes(searchQuery);
|
|
830
|
+
const show = matchMethod && matchSearch;
|
|
831
|
+
card.style.display = show ? '' : 'none';
|
|
832
|
+
if (show) visible++;
|
|
833
|
+
});
|
|
834
|
+
document.getElementById('empty-state').style.display = visible ? 'none' : 'block';
|
|
835
|
+
|
|
836
|
+
// Sync sidebar visibility
|
|
837
|
+
document.querySelectorAll('.sidebar-link').forEach(link => {
|
|
838
|
+
const targetId = link.dataset.target;
|
|
839
|
+
const targetCard = document.getElementById(targetId);
|
|
840
|
+
link.style.display = targetCard && targetCard.style.display !== 'none' ? '' : 'none';
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function init() {
|
|
845
|
+
const count = ROUTES.length;
|
|
846
|
+
document.getElementById('countPill').textContent = count + ' endpoint' + (count !== 1 ? 's' : '');
|
|
847
|
+
|
|
848
|
+
buildMethodFilters();
|
|
849
|
+
|
|
850
|
+
const container = document.getElementById('endpoints');
|
|
851
|
+
container.innerHTML = ROUTES.map((r, i) => renderCard(r, i)).join('');
|
|
852
|
+
|
|
853
|
+
// Add empty state after cards
|
|
854
|
+
const emptyEl = document.createElement('div');
|
|
855
|
+
emptyEl.id = 'empty-state';
|
|
856
|
+
emptyEl.className = 'empty-state';
|
|
857
|
+
emptyEl.style.display = 'none';
|
|
858
|
+
emptyEl.innerHTML = '<p>No endpoints match your filter.</p>';
|
|
859
|
+
container.after(emptyEl);
|
|
860
|
+
|
|
861
|
+
buildSidebar(ROUTES);
|
|
862
|
+
|
|
863
|
+
// Method filter clicks
|
|
864
|
+
document.getElementById('methodFilters').addEventListener('click', e => {
|
|
865
|
+
const btn = e.target.closest('.filter-btn');
|
|
866
|
+
if (!btn) return;
|
|
867
|
+
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
|
868
|
+
btn.classList.add('active');
|
|
869
|
+
activeFilter = btn.dataset.method;
|
|
870
|
+
applyFilters();
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
// Search
|
|
874
|
+
document.getElementById('searchInput').addEventListener('input', e => {
|
|
875
|
+
searchQuery = e.target.value.trim().toLowerCase();
|
|
876
|
+
applyFilters();
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
// Scroll spy for sidebar
|
|
880
|
+
const cardEls = document.querySelectorAll('.endpoint-card');
|
|
881
|
+
const sidebarLinks = document.querySelectorAll('.sidebar-link');
|
|
882
|
+
const observer = new IntersectionObserver(entries => {
|
|
883
|
+
entries.forEach(entry => {
|
|
884
|
+
if (entry.isIntersecting) {
|
|
885
|
+
const id = entry.target.id;
|
|
886
|
+
sidebarLinks.forEach(l => l.classList.toggle('active', l.dataset.target === id));
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
}, { rootMargin: '-80px 0px -60% 0px' });
|
|
890
|
+
cardEls.forEach(el => observer.observe(el));
|
|
891
|
+
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
try {
|
|
895
|
+
init();
|
|
896
|
+
} catch (e) {
|
|
897
|
+
document.body.insertAdjacentHTML('afterbegin',
|
|
898
|
+
'<div style="position:fixed;top:60px;left:0;right:0;z-index:9999;background:#7f1d1d;color:#fca5a5;padding:16px 24px;font-family:monospace;font-size:13px;border-bottom:1px solid #ef4444">' +
|
|
899
|
+
'<strong>Dashboard JS error:</strong> ' + (e && e.message ? e.message : String(e)) +
|
|
900
|
+
(e && e.stack ? '<pre style="margin-top:8px;white-space:pre-wrap">' + e.stack + '</pre>' : '') +
|
|
901
|
+
'</div>'
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
</script>
|
|
905
|
+
</body>
|
|
906
|
+
</html>`;
|
|
907
|
+
}
|
|
908
|
+
|
|
340
909
|
// src/errors.ts
|
|
341
910
|
var HttpError = class _HttpError extends Error {
|
|
342
911
|
statusCode;
|
|
@@ -383,12 +952,32 @@ async function ignite(config) {
|
|
|
383
952
|
const {
|
|
384
953
|
port: _port,
|
|
385
954
|
workers,
|
|
386
|
-
apiDir,
|
|
387
955
|
globalMiddlewares = [],
|
|
388
956
|
onWorkerReady,
|
|
389
957
|
onWorkerCrash,
|
|
390
958
|
onError
|
|
391
959
|
} = config;
|
|
960
|
+
function resolveConventionDir(name) {
|
|
961
|
+
const candidates = [
|
|
962
|
+
...process.argv[1] ? [path2.join(path2.dirname(process.argv[1]), name)] : [],
|
|
963
|
+
path2.join(process.cwd(), "src", name),
|
|
964
|
+
path2.join(process.cwd(), name),
|
|
965
|
+
path2.join(process.cwd(), "dist", name)
|
|
966
|
+
];
|
|
967
|
+
return candidates.find((c) => fs2.existsSync(c)) ?? candidates[0];
|
|
968
|
+
}
|
|
969
|
+
const apiDir = resolveConventionDir("api");
|
|
970
|
+
const tasksDir = resolveConventionDir("tasks");
|
|
971
|
+
const basePath = config.basePath ?? "/v1/api";
|
|
972
|
+
function readProjectMeta() {
|
|
973
|
+
try {
|
|
974
|
+
const raw = fs2.readFileSync(path2.join(process.cwd(), "package.json"), "utf8");
|
|
975
|
+
const pkg = JSON.parse(raw);
|
|
976
|
+
return { name: pkg.name ?? "API", version: pkg.version ?? "" };
|
|
977
|
+
} catch {
|
|
978
|
+
return { name: "API", version: "" };
|
|
979
|
+
}
|
|
980
|
+
}
|
|
392
981
|
const envPort = process.env["PORT"] != null ? Number(process.env["PORT"]) : NaN;
|
|
393
982
|
const port = _port != null && !Number.isNaN(_port) ? _port : !Number.isNaN(envPort) ? envPort : 3e3;
|
|
394
983
|
const databaseUrl = config.databaseUrl ?? process.env["DATABASE_URL"];
|
|
@@ -439,8 +1028,8 @@ async function ignite(config) {
|
|
|
439
1028
|
...cookieDomain !== void 0 && { cookieDomain }
|
|
440
1029
|
});
|
|
441
1030
|
}
|
|
442
|
-
if (
|
|
443
|
-
await scanTasks(
|
|
1031
|
+
if (tasksDir) {
|
|
1032
|
+
await scanTasks(tasksDir);
|
|
444
1033
|
}
|
|
445
1034
|
if (config.tasks) {
|
|
446
1035
|
if (config.tasks.backend === "bullmq") {
|
|
@@ -451,7 +1040,16 @@ async function ignite(config) {
|
|
|
451
1040
|
}
|
|
452
1041
|
}
|
|
453
1042
|
const routes = scanDir(apiDir);
|
|
454
|
-
|
|
1043
|
+
const apiRouter = express.Router();
|
|
1044
|
+
const mounted = await mountRoutes(apiRouter, routes);
|
|
1045
|
+
app.use(basePath, apiRouter);
|
|
1046
|
+
if (config.dashboard && process.env["NODE_ENV"] === "development") {
|
|
1047
|
+
app.get("/", (_req, res) => {
|
|
1048
|
+
const { name, version } = readProjectMeta();
|
|
1049
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
1050
|
+
res.send(generateDashboard(mounted, basePath, port, name, version));
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
455
1053
|
if (onError) {
|
|
456
1054
|
app.use(onError);
|
|
457
1055
|
} else {
|