@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 CHANGED
@@ -14,9 +14,9 @@ type JayRoute = {
14
14
  jayHtmlPath: string;
15
15
  compPath: string;
16
16
  /**
17
- * For static override routes, inferred params from sibling dynamic routes.
18
- * e.g., /products/ceramic-flower-vase has inferredParams: { slug: 'ceramic-flower-vase' }
19
- * when /products/[slug] exists as a sibling.
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 ParamInferenceResult, type ScanFilesOptions, inferParamsForStaticRoutes, routeToExpressRoute, scanRoutes, sortRoutesByPriority };
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 hasDynamicSegments(route) {
80
- return route.segments.some((seg) => typeof seg !== "string");
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 isFullyStaticRoute(route) {
83
- return route.segments.every((seg) => typeof seg === "string");
84
- }
85
- function dynamicRouteCouldMatch(staticRoute, dynamicRoute) {
86
- if (staticRoute.segments.length !== dynamicRoute.segments.length)
87
- return false;
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
- return true;
101
- }
102
- function inferParamsForStaticRoutes(routes) {
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
- return { routes: enrichedRoutes, inferenceLog };
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 { routes: enrichedRoutes, inferenceLog } = inferParamsForStaticRoutes(sortedRoutes);
137
- if (inferenceLog.length > 0) {
138
- getLogger().info("[route-scanner] Inferred params for static override routes:");
139
- for (const entry of inferenceLog) {
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 enrichedRoutes;
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.14.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.14.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.14.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",