@jay-framework/stack-route-scanner 0.14.0 → 0.15.0
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/index.d.mts +4 -29
- package/dist/index.mjs +55 -58
- package/package.json +5 -3
package/dist/index.d.mts
CHANGED
|
@@ -14,9 +14,9 @@ type JayRoute = {
|
|
|
14
14
|
jayHtmlPath: string;
|
|
15
15
|
compPath: string;
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Explicit params declared via <script type="application/jay-params"> in the jay-html.
|
|
18
|
+
* Used by static override routes to provide param values.
|
|
19
|
+
* e.g., /products/ceramic-flower-vase declares { slug: 'ceramic-flower-vase' }.
|
|
20
20
|
*/
|
|
21
21
|
inferredParams?: Record<string, string>;
|
|
22
22
|
};
|
|
@@ -30,33 +30,8 @@ interface ScanFilesOptions {
|
|
|
30
30
|
* Static routes come before dynamic routes at the same position.
|
|
31
31
|
*/
|
|
32
32
|
declare function sortRoutesByPriority(routes: JayRoutes): JayRoutes;
|
|
33
|
-
/**
|
|
34
|
-
* Result of param inference for logging/debugging.
|
|
35
|
-
*/
|
|
36
|
-
interface ParamInferenceResult {
|
|
37
|
-
staticRoute: string;
|
|
38
|
-
dynamicRoute: string;
|
|
39
|
-
inferredParams: Record<string, string>;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Infer params for static routes based on sibling dynamic routes.
|
|
43
|
-
*
|
|
44
|
-
* For each fully-static route, find a sibling dynamic route and map
|
|
45
|
-
* the static segment values to the param names from the dynamic route.
|
|
46
|
-
*
|
|
47
|
-
* Example:
|
|
48
|
-
* /products/ceramic-flower-vase (static)
|
|
49
|
-
* /products/[slug] (dynamic sibling)
|
|
50
|
-
* → inferredParams: { slug: 'ceramic-flower-vase' }
|
|
51
|
-
*
|
|
52
|
-
* Returns the routes with inferred params added, plus an inference log.
|
|
53
|
-
*/
|
|
54
|
-
declare function inferParamsForStaticRoutes(routes: JayRoutes): {
|
|
55
|
-
routes: JayRoutes;
|
|
56
|
-
inferenceLog: ParamInferenceResult[];
|
|
57
|
-
};
|
|
58
33
|
declare function scanRoutes(baseDir: string, options: ScanFilesOptions): Promise<JayRoutes>;
|
|
59
34
|
|
|
60
35
|
declare function routeToExpressRoute(route: JayRoute): string;
|
|
61
36
|
|
|
62
|
-
export { type JayRoute, type JayRouteParam, JayRouteParamType, type JayRouteSegment, type JayRoutes, type
|
|
37
|
+
export { type JayRoute, type JayRouteParam, JayRouteParamType, type JayRouteSegment, type JayRoutes, type ScanFilesOptions, routeToExpressRoute, scanRoutes, sortRoutesByPriority };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { promises } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { parse } from "node-html-parser";
|
|
4
|
+
import YAML from "yaml";
|
|
3
5
|
import { getLogger } from "@jay-framework/logger";
|
|
4
6
|
var JayRouteParamType = /* @__PURE__ */ ((JayRouteParamType2) => {
|
|
5
7
|
JayRouteParamType2[JayRouteParamType2["single"] = 0] = "single";
|
|
@@ -35,6 +37,15 @@ async function scanDirectory(BASE_DIR, directory, options) {
|
|
|
35
37
|
routes = [...routes, ...await scanDirectory(BASE_DIR, fullPath, options)];
|
|
36
38
|
} else if (item.name === options.jayHtmlFilename) {
|
|
37
39
|
const route = convertToRoutePath(BASE_DIR, fullPath, options);
|
|
40
|
+
const { params, validations } = await parseJayParams(fullPath);
|
|
41
|
+
if (params) {
|
|
42
|
+
route.inferredParams = params;
|
|
43
|
+
}
|
|
44
|
+
if (validations.length > 0) {
|
|
45
|
+
for (const v of validations) {
|
|
46
|
+
getLogger().warn(`[route-scanner] ${route.rawRoute}: ${v}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
38
49
|
routes.push(route);
|
|
39
50
|
}
|
|
40
51
|
}
|
|
@@ -76,73 +87,60 @@ function compareRoutes(a, b) {
|
|
|
76
87
|
function sortRoutesByPriority(routes) {
|
|
77
88
|
return [...routes].sort(compareRoutes);
|
|
78
89
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
90
|
+
function dedentYaml(text) {
|
|
91
|
+
const lines = text.split("\n").filter((l) => l.trim().length > 0);
|
|
92
|
+
if (lines.length === 0)
|
|
93
|
+
return "";
|
|
94
|
+
const minIndent = Math.min(...lines.map((l) => l.match(/^\s*/)?.[0].length ?? 0));
|
|
95
|
+
return lines.map((l) => l.slice(minIndent)).join("\n");
|
|
81
96
|
}
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return
|
|
88
|
-
if (staticRoute.segments.length === 0)
|
|
89
|
-
return false;
|
|
90
|
-
for (let i = 0; i < staticRoute.segments.length; i++) {
|
|
91
|
-
const staticSeg = staticRoute.segments[i];
|
|
92
|
-
const dynSeg = dynamicRoute.segments[i];
|
|
93
|
-
if (typeof staticSeg !== "string")
|
|
94
|
-
return false;
|
|
95
|
-
if (typeof dynSeg === "string") {
|
|
96
|
-
if (staticSeg !== dynSeg)
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
97
|
+
async function parseJayParams(jayHtmlPath) {
|
|
98
|
+
let content;
|
|
99
|
+
try {
|
|
100
|
+
content = await promises.readFile(jayHtmlPath, "utf-8");
|
|
101
|
+
} catch {
|
|
102
|
+
return { params: void 0, validations: [] };
|
|
99
103
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const inferenceLog = [];
|
|
104
|
-
const dynamicRoutes = routes.filter(hasDynamicSegments);
|
|
105
|
-
const enrichedRoutes = routes.map((route) => {
|
|
106
|
-
if (!isFullyStaticRoute(route))
|
|
107
|
-
return route;
|
|
108
|
-
if (route.segments.length === 0)
|
|
109
|
-
return route;
|
|
110
|
-
const dynamicSibling = dynamicRoutes.find((dyn) => dynamicRouteCouldMatch(route, dyn));
|
|
111
|
-
if (!dynamicSibling)
|
|
112
|
-
return route;
|
|
113
|
-
const inferredParams = {};
|
|
114
|
-
for (let i = 0; i < route.segments.length; i++) {
|
|
115
|
-
const staticSeg = route.segments[i];
|
|
116
|
-
const dynSeg = dynamicSibling.segments[i];
|
|
117
|
-
if (typeof staticSeg === "string" && typeof dynSeg !== "string") {
|
|
118
|
-
inferredParams[dynSeg.name] = staticSeg;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
if (Object.keys(inferredParams).length === 0)
|
|
122
|
-
return route;
|
|
123
|
-
inferenceLog.push({
|
|
124
|
-
staticRoute: route.rawRoute,
|
|
125
|
-
dynamicRoute: dynamicSibling.rawRoute,
|
|
126
|
-
inferredParams
|
|
127
|
-
});
|
|
128
|
-
return { ...route, inferredParams };
|
|
104
|
+
const root = parse(content, {
|
|
105
|
+
comment: true,
|
|
106
|
+
blockTextElements: { script: true, style: true }
|
|
129
107
|
});
|
|
130
|
-
|
|
108
|
+
const head = root.querySelector("head");
|
|
109
|
+
const paramScripts = (head ?? root).querySelectorAll('script[type="application/jay-params"]');
|
|
110
|
+
if (paramScripts.length === 0)
|
|
111
|
+
return { params: void 0, validations: [] };
|
|
112
|
+
if (paramScripts.length > 1) {
|
|
113
|
+
return {
|
|
114
|
+
params: void 0,
|
|
115
|
+
validations: [
|
|
116
|
+
'Multiple <script type="application/jay-params"> tags found — expected at most one'
|
|
117
|
+
]
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const body = dedentYaml(paramScripts[0].textContent ?? "");
|
|
121
|
+
if (!body)
|
|
122
|
+
return { params: void 0, validations: [] };
|
|
123
|
+
try {
|
|
124
|
+
return { params: YAML.parse(body), validations: [] };
|
|
125
|
+
} catch (e) {
|
|
126
|
+
return {
|
|
127
|
+
params: void 0,
|
|
128
|
+
validations: [`Failed to parse jay-params YAML: ${e.message}`]
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
131
|
}
|
|
132
132
|
async function scanRoutes(baseDir, options) {
|
|
133
133
|
const BASE_DIR = path.resolve(baseDir);
|
|
134
134
|
const routes = await scanDirectory(BASE_DIR, BASE_DIR, options);
|
|
135
135
|
const sortedRoutes = sortRoutesByPriority(routes);
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
getLogger().info("[route-scanner]
|
|
139
|
-
for (const
|
|
140
|
-
getLogger().info(
|
|
141
|
-
` ${entry.staticRoute} → params from ${entry.dynamicRoute}: ${JSON.stringify(entry.inferredParams)}`
|
|
142
|
-
);
|
|
136
|
+
const routesWithParams = sortedRoutes.filter((r) => r.inferredParams);
|
|
137
|
+
if (routesWithParams.length > 0) {
|
|
138
|
+
getLogger().info("[route-scanner] Routes with explicit params (jay-params):");
|
|
139
|
+
for (const route of routesWithParams) {
|
|
140
|
+
getLogger().info(` ${route.rawRoute} → ${JSON.stringify(route.inferredParams)}`);
|
|
143
141
|
}
|
|
144
142
|
}
|
|
145
|
-
return
|
|
143
|
+
return sortedRoutes;
|
|
146
144
|
}
|
|
147
145
|
function routeToExpressRoute(route) {
|
|
148
146
|
return "/" + route.segments.map((segment) => {
|
|
@@ -160,7 +158,6 @@ function routeToExpressRoute(route) {
|
|
|
160
158
|
}
|
|
161
159
|
export {
|
|
162
160
|
JayRouteParamType,
|
|
163
|
-
inferParamsForStaticRoutes,
|
|
164
161
|
routeToExpressRoute,
|
|
165
162
|
scanRoutes,
|
|
166
163
|
sortRoutesByPriority
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/stack-route-scanner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"types": "dist/index.d.mts",
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
"readme.md"
|
|
10
10
|
],
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@jay-framework/logger": "^0.
|
|
12
|
+
"@jay-framework/logger": "^0.15.0",
|
|
13
|
+
"node-html-parser": "^6.1.12",
|
|
14
|
+
"yaml": "^2.3.4"
|
|
13
15
|
},
|
|
14
16
|
"scripts": {
|
|
15
17
|
"build": "npm run build:js && npm run build:types",
|
|
@@ -23,7 +25,7 @@
|
|
|
23
25
|
"test:watch": "vitest"
|
|
24
26
|
},
|
|
25
27
|
"devDependencies": {
|
|
26
|
-
"@jay-framework/dev-environment": "^0.
|
|
28
|
+
"@jay-framework/dev-environment": "^0.15.0",
|
|
27
29
|
"@types/node": "^20.11.5",
|
|
28
30
|
"nodemon": "^3.0.3",
|
|
29
31
|
"replace-in-file": "^7.1.0",
|