maxserver 0.1.5 → 0.1.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/archive/setupRoutesOld.js +132 -0
- package/bin/init.js +21 -1
- package/package.json +1 -1
- package/src/setupRoutes.js +29 -42
- package/templates/package.json +1 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* 🚀 AUTO-LOADER
|
|
4
|
+
* Scans src/ for files with "// METHOD /url" comments.
|
|
5
|
+
* Automatically registers them as Fastify routes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { pathToFileURL } from "url";
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
// Matches lines like: // GET /api/v1/users
|
|
15
|
+
const ROUTE_REGEX = /^\/\/\s*(GET|POST|PUT|PATCH|DELETE)\s+(.+)$/gm;
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Recursively finds all .js files in a directory.
|
|
20
|
+
* Skips node_modules and dotfiles.
|
|
21
|
+
*/
|
|
22
|
+
function walk(dir, out = []) {
|
|
23
|
+
if (!fs.existsSync(dir)) return out;
|
|
24
|
+
|
|
25
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
26
|
+
if (e.name === "node_modules") continue;
|
|
27
|
+
if (e.name.startsWith(".")) continue;
|
|
28
|
+
|
|
29
|
+
const full = path.join(dir, e.name);
|
|
30
|
+
if (e.isDirectory()) {
|
|
31
|
+
walk(full, out);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!e.name.endsWith(".js")) continue;
|
|
36
|
+
out.push(full);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extracts method and URL from the file's "magic comment".
|
|
45
|
+
* Enforces strict "One Route Per File" policy.
|
|
46
|
+
*/
|
|
47
|
+
function getRoute(file) {
|
|
48
|
+
const text = fs.readFileSync(file, "utf8");
|
|
49
|
+
const matches = [...text.matchAll(ROUTE_REGEX)];
|
|
50
|
+
|
|
51
|
+
if (matches.length === 0) return null;
|
|
52
|
+
|
|
53
|
+
// Warn if user accidentally defines multiple routes in one file
|
|
54
|
+
if (matches.length > 1) {
|
|
55
|
+
console.warn(
|
|
56
|
+
`⚠️ Ignored "${file}": Found ${matches.length} route comments. ` +
|
|
57
|
+
`Only 1 allowed per file.`
|
|
58
|
+
);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const m = matches[0];
|
|
63
|
+
const method = m[1].toLowerCase();
|
|
64
|
+
// Normalize URL: Remove all leading slashes, then add exactly one
|
|
65
|
+
const url = "/" + m[2].trim().replace(/^\/+/, "");
|
|
66
|
+
|
|
67
|
+
return { method, url };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Safe dynamic import that handles Windows paths correctly.
|
|
73
|
+
*/
|
|
74
|
+
async function importDefault(file) {
|
|
75
|
+
return (await import(pathToFileURL(file).href)).default;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
export async function setupRoutes(app) {
|
|
80
|
+
const seen = new Map();
|
|
81
|
+
const root = path.resolve(app.maxserver.routesDir || "src");
|
|
82
|
+
|
|
83
|
+
for (const file of walk(root)) {
|
|
84
|
+
// Skip schema files; they are loaded alongside their route file
|
|
85
|
+
if (file.endsWith(".schema.js")) continue;
|
|
86
|
+
|
|
87
|
+
const info = getRoute(file);
|
|
88
|
+
if (!info) continue;
|
|
89
|
+
|
|
90
|
+
// 🛡️ Collision Detection: Ensure no two files claim the same route
|
|
91
|
+
const key = info.method + " " + info.url;
|
|
92
|
+
if (seen.has(key)) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Duplicate route "${key}" detected:\n` +
|
|
95
|
+
`1. ${seen.get(key)}\n` +
|
|
96
|
+
`2. ${file}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
seen.set(key, file);
|
|
100
|
+
|
|
101
|
+
// Import the route handler
|
|
102
|
+
const handler = await importDefault(file);
|
|
103
|
+
if (typeof handler !== "function") {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Route "${key}" in "${file}" must export a default function.`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 🤝 Schema Loading: Look for sibling .schema.js file
|
|
110
|
+
const schemaFile = file.replace(/\.js$/, ".schema.js");
|
|
111
|
+
let raw = {};
|
|
112
|
+
|
|
113
|
+
if (fs.existsSync(schemaFile)) {
|
|
114
|
+
const loaded = await importDefault(schemaFile);
|
|
115
|
+
// 🛡️ Guard: Ensure export is a valid object before using it
|
|
116
|
+
if (loaded && typeof loaded === "object") raw = loaded;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ✨ Magic: Extract 'auth' and 'routeOptions' specifically
|
|
120
|
+
let { auth, routeOptions = {}, ...schema } = raw;
|
|
121
|
+
|
|
122
|
+
// Inject 'auth' into config if present (Syntactic Sugar)
|
|
123
|
+
if (auth !== undefined) {
|
|
124
|
+
routeOptions = {
|
|
125
|
+
...routeOptions,
|
|
126
|
+
config: { ...(routeOptions.config || {}), auth: !!auth },
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
app[info.method](info.url, { ...routeOptions, schema }, handler);
|
|
131
|
+
}
|
|
132
|
+
}
|
package/bin/init.js
CHANGED
|
@@ -45,11 +45,31 @@ function resolveArgs() {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Renames template files to dotfiles and clones vscode settings.
|
|
50
|
+
*/
|
|
48
51
|
function fixDotfiles(dir) {
|
|
49
|
-
|
|
52
|
+
// 1. Standard dotfile renames
|
|
53
|
+
for (const f of ["env", "gitignore"]) {
|
|
50
54
|
const src = path.join(dir, f);
|
|
51
55
|
if (fs.existsSync(src)) fs.renameSync(src, path.join(dir, "." + f));
|
|
52
56
|
}
|
|
57
|
+
|
|
58
|
+
// 2. Specialized vscode handling (Root and src)
|
|
59
|
+
const vscodeSrc = path.join(dir, "vscode");
|
|
60
|
+
|
|
61
|
+
if (fs.existsSync(vscodeSrc)) {
|
|
62
|
+
const srcDir = path.join(dir, "src");
|
|
63
|
+
|
|
64
|
+
// Ensure src/ exists before cloning settings into it
|
|
65
|
+
if (!fs.existsSync(srcDir)) fs.mkdirSync(srcDir);
|
|
66
|
+
|
|
67
|
+
// Copy to src/.vscode
|
|
68
|
+
fs.cpSync(vscodeSrc, path.join(srcDir, ".vscode"), { recursive: true });
|
|
69
|
+
|
|
70
|
+
// Rename root vscode to .vscode
|
|
71
|
+
fs.renameSync(vscodeSrc, path.join(dir, ".vscode"));
|
|
72
|
+
}
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
|
package/package.json
CHANGED
package/src/setupRoutes.js
CHANGED
|
@@ -2,27 +2,24 @@
|
|
|
2
2
|
* 🚀 AUTO-LOADER
|
|
3
3
|
* Scans src/ for files with "// METHOD /url" comments.
|
|
4
4
|
* Automatically registers them as Fastify routes.
|
|
5
|
+
* Also registers lonely .schema.js files as global shared schemas.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import fs from "fs";
|
|
8
9
|
import path from "path";
|
|
9
10
|
import { pathToFileURL } from "url";
|
|
10
11
|
|
|
11
|
-
|
|
12
12
|
// Matches lines like: // GET /api/v1/users
|
|
13
13
|
const ROUTE_REGEX = /^\/\/\s*(GET|POST|PUT|PATCH|DELETE)\s+(.+)$/gm;
|
|
14
14
|
|
|
15
|
-
|
|
16
15
|
/**
|
|
17
16
|
* Recursively finds all .js files in a directory.
|
|
18
|
-
* Skips node_modules and dotfiles.
|
|
19
17
|
*/
|
|
20
18
|
function walk(dir, out = []) {
|
|
21
19
|
if (!fs.existsSync(dir)) return out;
|
|
22
20
|
|
|
23
21
|
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
24
|
-
if (e.name === "node_modules") continue;
|
|
25
|
-
if (e.name.startsWith(".")) continue;
|
|
22
|
+
if (e.name === "node_modules" || e.name.startsWith(".")) continue;
|
|
26
23
|
|
|
27
24
|
const full = path.join(dir, e.name);
|
|
28
25
|
if (e.isDirectory()) {
|
|
@@ -30,17 +27,14 @@ function walk(dir, out = []) {
|
|
|
30
27
|
continue;
|
|
31
28
|
}
|
|
32
29
|
|
|
33
|
-
if (
|
|
34
|
-
out.push(full);
|
|
30
|
+
if (e.name.endsWith(".js")) out.push(full);
|
|
35
31
|
}
|
|
36
32
|
|
|
37
33
|
return out;
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
|
|
41
36
|
/**
|
|
42
37
|
* Extracts method and URL from the file's "magic comment".
|
|
43
|
-
* Enforces strict "One Route Per File" policy.
|
|
44
38
|
*/
|
|
45
39
|
function getRoute(file) {
|
|
46
40
|
const text = fs.readFileSync(file, "utf8");
|
|
@@ -48,76 +42,69 @@ function getRoute(file) {
|
|
|
48
42
|
|
|
49
43
|
if (matches.length === 0) return null;
|
|
50
44
|
|
|
51
|
-
// Warn if user accidentally defines multiple routes in one file
|
|
52
45
|
if (matches.length > 1) {
|
|
53
|
-
console.warn(
|
|
54
|
-
`⚠️ Ignored "${file}": Found ${matches.length} route comments. ` +
|
|
55
|
-
`Only 1 allowed per file.`
|
|
56
|
-
);
|
|
46
|
+
console.warn(`⚠️ Ignored "${file}": Only 1 route allowed per file.`);
|
|
57
47
|
return null;
|
|
58
48
|
}
|
|
59
49
|
|
|
60
50
|
const m = matches[0];
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return { method, url };
|
|
51
|
+
return {
|
|
52
|
+
method: m[1].toLowerCase(),
|
|
53
|
+
url: "/" + m[2].trim().replace(/^\/+/, "")
|
|
54
|
+
};
|
|
66
55
|
}
|
|
67
56
|
|
|
68
|
-
|
|
69
57
|
/**
|
|
70
|
-
* Safe dynamic import
|
|
58
|
+
* Safe dynamic import for ESM.
|
|
71
59
|
*/
|
|
72
60
|
async function importDefault(file) {
|
|
73
61
|
return (await import(pathToFileURL(file).href)).default;
|
|
74
62
|
}
|
|
75
63
|
|
|
76
|
-
|
|
77
64
|
export async function setupRoutes(app) {
|
|
78
|
-
const seen = new Map();
|
|
79
65
|
const root = path.resolve(app.maxserver.routesDir || "src");
|
|
66
|
+
const files = walk(root);
|
|
80
67
|
|
|
81
|
-
|
|
82
|
-
|
|
68
|
+
// 1. Pass One: Register Global Schemas (Lonely .schema.js files)
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
if (file.endsWith(".schema.js")) {
|
|
71
|
+
const hasHandler = fs.existsSync(file.replace(".schema.js", ".js"));
|
|
72
|
+
|
|
73
|
+
if (!hasHandler) {
|
|
74
|
+
const schema = await importDefault(file);
|
|
75
|
+
if (schema?.$id) app.addSchema(schema);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 2. Pass Two: Register Routes
|
|
81
|
+
const seen = new Map();
|
|
82
|
+
for (const file of files) {
|
|
83
83
|
if (file.endsWith(".schema.js")) continue;
|
|
84
84
|
|
|
85
85
|
const info = getRoute(file);
|
|
86
86
|
if (!info) continue;
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (seen.has(key)) {
|
|
91
|
-
throw new Error(
|
|
92
|
-
`Duplicate route "${key}" detected:\n` +
|
|
93
|
-
`1. ${seen.get(key)}\n` +
|
|
94
|
-
`2. ${file}`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
88
|
+
const key = `${info.method} ${info.url}`;
|
|
89
|
+
if (seen.has(key)) throw new Error(`Duplicate route "${key}" detected.`);
|
|
97
90
|
seen.set(key, file);
|
|
98
91
|
|
|
99
|
-
// Import the route handler
|
|
100
92
|
const handler = await importDefault(file);
|
|
101
93
|
if (typeof handler !== "function") {
|
|
102
|
-
throw new Error(
|
|
103
|
-
`Route "${key}" in "${file}" must export a default function.`
|
|
104
|
-
);
|
|
94
|
+
throw new Error(`Route "${key}" in "${file}" must export a default function.`);
|
|
105
95
|
}
|
|
106
96
|
|
|
107
|
-
// 🤝 Schema Loading: Look for sibling .schema.js file
|
|
108
97
|
const schemaFile = file.replace(/\.js$/, ".schema.js");
|
|
109
98
|
let raw = {};
|
|
110
99
|
|
|
111
100
|
if (fs.existsSync(schemaFile)) {
|
|
112
101
|
const loaded = await importDefault(schemaFile);
|
|
113
|
-
// 🛡️ Guard: Ensure export is a valid object before using it
|
|
114
102
|
if (loaded && typeof loaded === "object") raw = loaded;
|
|
115
103
|
}
|
|
116
104
|
|
|
117
|
-
// ✨ Magic: Extract 'auth' and 'routeOptions' specifically
|
|
118
105
|
let { auth, routeOptions = {}, ...schema } = raw;
|
|
119
106
|
|
|
120
|
-
// Inject 'auth'
|
|
107
|
+
// Inject 'auth' config for the global authentication hook
|
|
121
108
|
if (auth !== undefined) {
|
|
122
109
|
routeOptions = {
|
|
123
110
|
...routeOptions,
|