@edgeone/vite-core 1.0.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.
Files changed (73) hide show
  1. package/dist/bundler.d.ts +13 -0
  2. package/dist/bundler.d.ts.map +1 -0
  3. package/dist/bundler.js +101 -0
  4. package/dist/bundler.js.map +1 -0
  5. package/dist/core.d.ts +11 -0
  6. package/dist/core.d.ts.map +1 -0
  7. package/dist/core.js +330 -0
  8. package/dist/core.js.map +1 -0
  9. package/dist/factory/detectors.d.ts +13 -0
  10. package/dist/factory/detectors.d.ts.map +1 -0
  11. package/dist/factory/detectors.js +46 -0
  12. package/dist/factory/detectors.js.map +1 -0
  13. package/dist/factory/hooks.d.ts +29 -0
  14. package/dist/factory/hooks.d.ts.map +1 -0
  15. package/dist/factory/hooks.js +158 -0
  16. package/dist/factory/hooks.js.map +1 -0
  17. package/dist/factory/index.d.ts +24 -0
  18. package/dist/factory/index.d.ts.map +1 -0
  19. package/dist/factory/index.js +47 -0
  20. package/dist/factory/index.js.map +1 -0
  21. package/dist/factory/presets.d.ts +27 -0
  22. package/dist/factory/presets.d.ts.map +1 -0
  23. package/dist/factory/presets.js +186 -0
  24. package/dist/factory/presets.js.map +1 -0
  25. package/dist/factory.d.ts +183 -0
  26. package/dist/factory.d.ts.map +1 -0
  27. package/dist/factory.js +482 -0
  28. package/dist/factory.js.map +1 -0
  29. package/dist/helpers.d.ts +53 -0
  30. package/dist/helpers.d.ts.map +1 -0
  31. package/dist/helpers.js +177 -0
  32. package/dist/helpers.js.map +1 -0
  33. package/dist/index.d.ts +16 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +12 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/route/index.d.ts +7 -0
  38. package/dist/route/index.d.ts.map +1 -0
  39. package/dist/route/index.js +6 -0
  40. package/dist/route/index.js.map +1 -0
  41. package/dist/route/parser.d.ts +18 -0
  42. package/dist/route/parser.d.ts.map +1 -0
  43. package/dist/route/parser.js +187 -0
  44. package/dist/route/parser.js.map +1 -0
  45. package/dist/route/regex.d.ts +31 -0
  46. package/dist/route/regex.d.ts.map +1 -0
  47. package/dist/route/regex.js +140 -0
  48. package/dist/route/regex.js.map +1 -0
  49. package/dist/route/regex.test.d.ts +7 -0
  50. package/dist/route/regex.test.d.ts.map +1 -0
  51. package/dist/route/regex.test.js +662 -0
  52. package/dist/route/regex.test.js.map +1 -0
  53. package/dist/route/types.d.ts +58 -0
  54. package/dist/route/types.d.ts.map +1 -0
  55. package/dist/route/types.js +5 -0
  56. package/dist/route/types.js.map +1 -0
  57. package/dist/route-parser.d.ts +8 -0
  58. package/dist/route-parser.d.ts.map +1 -0
  59. package/dist/route-parser.js +8 -0
  60. package/dist/route-parser.js.map +1 -0
  61. package/dist/types.d.ts +160 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/dist/types.js +5 -0
  64. package/dist/types.js.map +1 -0
  65. package/dist/utils.d.ts +40 -0
  66. package/dist/utils.d.ts.map +1 -0
  67. package/dist/utils.js +242 -0
  68. package/dist/utils.js.map +1 -0
  69. package/dist/vite-config-parser.d.ts +62 -0
  70. package/dist/vite-config-parser.d.ts.map +1 -0
  71. package/dist/vite-config-parser.js +229 -0
  72. package/dist/vite-config-parser.js.map +1 -0
  73. package/package.json +51 -0
@@ -0,0 +1,662 @@
1
+ /**
2
+ * Route Regex Tests
3
+ *
4
+ * Comprehensive test suite for route regex conversion and matching
5
+ */
6
+ import { isDynamicRoute, isCatchAllRoute, routeToRegex, addRegexToRoutes, convertRoutesToMetaFormat, sortRoutesByPriority, } from "./regex.js";
7
+ // ============================================================================
8
+ // Test Utilities
9
+ // ============================================================================
10
+ function testMatch(regex, path) {
11
+ return new RegExp(regex).test(path);
12
+ }
13
+ function assertMatch(regex, path, expected, message) {
14
+ const result = testMatch(regex, path);
15
+ const status = result === expected ? "✓" : "✗";
16
+ const msg = message || `${path} should ${expected ? "" : "not "}match ${regex}`;
17
+ console.log(` ${status} ${msg}`);
18
+ if (result !== expected) {
19
+ throw new Error(`Assertion failed: ${msg}`);
20
+ }
21
+ }
22
+ function runTestGroup(name, fn) {
23
+ console.log(`\n${name}`);
24
+ console.log("─".repeat(60));
25
+ fn();
26
+ }
27
+ // ============================================================================
28
+ // isDynamicRoute Tests
29
+ // ============================================================================
30
+ function testIsDynamicRoute() {
31
+ runTestGroup("isDynamicRoute - Colon Syntax", () => {
32
+ console.log(" ✓ Static routes:");
33
+ console.assert(!isDynamicRoute("/"), "/ should be static");
34
+ console.assert(!isDynamicRoute("/about"), "/about should be static");
35
+ console.assert(!isDynamicRoute("/api/users"), "/api/users should be static");
36
+ console.log(" / , /about, /api/users → false");
37
+ console.log(" ✓ Dynamic routes:");
38
+ console.assert(isDynamicRoute("/:id"), "/:id should be dynamic");
39
+ console.assert(isDynamicRoute("/blog/:slug"), "/blog/:slug should be dynamic");
40
+ console.assert(isDynamicRoute("/user/:id/posts"), "/user/:id/posts should be dynamic");
41
+ console.log(" /:id, /blog/:slug, /user/:id/posts → true");
42
+ console.log(" ✓ Optional parameters:");
43
+ console.assert(isDynamicRoute("/:id?"), "/:id? should be dynamic");
44
+ console.assert(isDynamicRoute("/blog/:slug?"), "/blog/:slug? should be dynamic");
45
+ console.log(" /:id?, /blog/:slug? → true");
46
+ console.log(" ✓ Wildcard:");
47
+ console.assert(isDynamicRoute("/*"), "/* should be dynamic");
48
+ console.assert(isDynamicRoute("/files/*"), "/files/* should be dynamic");
49
+ console.log(" /*, /files/* → true");
50
+ });
51
+ runTestGroup("isDynamicRoute - Dollar Syntax (TanStack)", () => {
52
+ console.log(" ✓ Dynamic routes:");
53
+ console.assert(isDynamicRoute("/$id"), "/$id should be dynamic");
54
+ console.assert(isDynamicRoute("/blog/$slug"), "/blog/$slug should be dynamic");
55
+ console.log(" /$id, /blog/$slug → true");
56
+ console.log(" ✓ Splat routes:");
57
+ console.assert(isDynamicRoute("/$"), "/$ should be dynamic");
58
+ console.assert(isDynamicRoute("/files/$"), "/files/$ should be dynamic");
59
+ console.log(" /$, /files/$ → true");
60
+ });
61
+ runTestGroup("isDynamicRoute - At Syntax (Vike)", () => {
62
+ console.log(" ✓ Dynamic routes:");
63
+ console.assert(isDynamicRoute("/@id"), "/@id should be dynamic");
64
+ console.assert(isDynamicRoute("/user/@id"), "/user/@id should be dynamic");
65
+ console.assert(isDynamicRoute("/blog/@slug"), "/blog/@slug should be dynamic");
66
+ console.assert(isDynamicRoute("/api/@version/@endpoint"), "/api/@version/@endpoint should be dynamic");
67
+ console.log(" /@id, /user/@id, /blog/@slug, /api/@version/@endpoint → true");
68
+ console.log(" ✓ Mixed with static segments:");
69
+ console.assert(isDynamicRoute("/product/@category/@id"), "/product/@category/@id should be dynamic");
70
+ console.assert(isDynamicRoute("/shop/@shopId/item/@itemId"), "/shop/@shopId/item/@itemId should be dynamic");
71
+ console.log(" /product/@category/@id, /shop/@shopId/item/@itemId → true");
72
+ });
73
+ runTestGroup("isDynamicRoute - Bracket Syntax (Next.js/Nuxt)", () => {
74
+ console.log(" ✓ Dynamic routes:");
75
+ console.assert(isDynamicRoute("/[id]"), "/[id] should be dynamic");
76
+ console.assert(isDynamicRoute("/blog/[slug]"), "/blog/[slug] should be dynamic");
77
+ console.log(" /[id], /blog/[slug] → true");
78
+ console.log(" ✓ Optional routes:");
79
+ console.assert(isDynamicRoute("/[[id]]"), "/[[id]] should be dynamic");
80
+ console.assert(isDynamicRoute("/blog/[[slug]]"), "/blog/[[slug]] should be dynamic");
81
+ console.log(" /[[id]], /blog/[[slug]] → true");
82
+ console.log(" ✓ Catch-all routes:");
83
+ console.assert(isDynamicRoute("/[...path]"), "/[...path] should be dynamic");
84
+ console.assert(isDynamicRoute("/docs/[...slug]"), "/docs/[...slug] should be dynamic");
85
+ console.log(" /[...path], /docs/[...slug] → true");
86
+ console.log(" ✓ Optional catch-all routes:");
87
+ console.assert(isDynamicRoute("/[[...path]]"), "/[[...path]] should be dynamic");
88
+ console.log(" /[[...path]] → true");
89
+ });
90
+ }
91
+ // ============================================================================
92
+ // isCatchAllRoute Tests
93
+ // ============================================================================
94
+ function testIsCatchAllRoute() {
95
+ runTestGroup("isCatchAllRoute", () => {
96
+ console.log(" ✓ Not catch-all:");
97
+ console.assert(!isCatchAllRoute("/"), "/ should not be catch-all");
98
+ console.assert(!isCatchAllRoute("/about"), "/about should not be catch-all");
99
+ console.assert(!isCatchAllRoute("/:id"), "/:id should not be catch-all");
100
+ console.assert(!isCatchAllRoute("/[id]"), "/[id] should not be catch-all");
101
+ console.assert(!isCatchAllRoute("/$id"), "/$id should not be catch-all");
102
+ console.log(" /, /about, /:id, /[id], /$id → false");
103
+ console.log(" ✓ Catch-all (colon syntax):");
104
+ console.assert(isCatchAllRoute("/*"), "/* should be catch-all");
105
+ console.assert(isCatchAllRoute("/files/*"), "/files/* should be catch-all");
106
+ console.log(" /*, /files/* → true");
107
+ console.log(" ✓ Catch-all (dollar syntax):");
108
+ console.assert(isCatchAllRoute("/$"), "/$ should be catch-all");
109
+ console.assert(isCatchAllRoute("/files/$"), "/files/$ should be catch-all");
110
+ console.log(" /$, /files/$ → true");
111
+ console.log(" ✓ Catch-all (bracket syntax):");
112
+ console.assert(isCatchAllRoute("/[...path]"), "/[...path] should be catch-all");
113
+ console.assert(isCatchAllRoute("/[[...path]]"), "/[[...path]] should be catch-all");
114
+ console.log(" /[...path], /[[...path]] → true");
115
+ });
116
+ }
117
+ // ============================================================================
118
+ // routeToRegex Tests - Colon Syntax
119
+ // ============================================================================
120
+ function testColonSyntax() {
121
+ runTestGroup("routeToRegex - Colon Syntax (React Router/Remix/Express)", () => {
122
+ // Root route
123
+ const rootRegex = routeToRegex("/");
124
+ console.log(` "/" → ${rootRegex}`);
125
+ assertMatch(rootRegex, "/", true, "Root should match /");
126
+ assertMatch(rootRegex, "/about", false, "Root should not match /about");
127
+ // Static route
128
+ const aboutRegex = routeToRegex("/about");
129
+ console.log(` "/about" → ${aboutRegex}`);
130
+ assertMatch(aboutRegex, "/about", true);
131
+ assertMatch(aboutRegex, "/about/", true, "Should match with trailing slash");
132
+ assertMatch(aboutRegex, "/about/more", false);
133
+ // Single dynamic parameter
134
+ const idRegex = routeToRegex("/user/:id");
135
+ console.log(` "/user/:id" → ${idRegex}`);
136
+ assertMatch(idRegex, "/user/123", true);
137
+ assertMatch(idRegex, "/user/abc", true);
138
+ assertMatch(idRegex, "/user/", false, "Should not match empty param");
139
+ assertMatch(idRegex, "/user/123/extra", false);
140
+ // Multiple dynamic parameters
141
+ const multiRegex = routeToRegex("/user/:org/:repo");
142
+ console.log(` "/user/:org/:repo" → ${multiRegex}`);
143
+ assertMatch(multiRegex, "/user/facebook/react", true);
144
+ assertMatch(multiRegex, "/user/google/angular", true);
145
+ assertMatch(multiRegex, "/user/only-one", false);
146
+ // Mixed static and dynamic
147
+ const mixedRegex = routeToRegex("/api/v1/:id/details");
148
+ console.log(` "/api/v1/:id/details" → ${mixedRegex}`);
149
+ assertMatch(mixedRegex, "/api/v1/123/details", true);
150
+ assertMatch(mixedRegex, "/api/v1/abc/details", true);
151
+ assertMatch(mixedRegex, "/api/v1/details", false);
152
+ // Optional parameter
153
+ const optionalRegex = routeToRegex("/blog/:slug?");
154
+ console.log(` "/blog/:slug?" → ${optionalRegex}`);
155
+ assertMatch(optionalRegex, "/blog/my-post", true);
156
+ assertMatch(optionalRegex, "/blog", true, "Optional param can be absent");
157
+ // Note: /blog/ with trailing slash may or may not match depending on implementation
158
+ // The key is that /blog works without the parameter
159
+ // Wildcard/splat
160
+ const wildcardRegex = routeToRegex("/files/*");
161
+ console.log(` "/files/*" → ${wildcardRegex}`);
162
+ assertMatch(wildcardRegex, "/files/", true);
163
+ assertMatch(wildcardRegex, "/files/doc.txt", true);
164
+ assertMatch(wildcardRegex, "/files/nested/path/file.txt", true);
165
+ assertMatch(wildcardRegex, "/files", true);
166
+ });
167
+ }
168
+ // ============================================================================
169
+ // routeToRegex Tests - Dollar Syntax
170
+ // ============================================================================
171
+ function testDollarSyntax() {
172
+ runTestGroup("routeToRegex - Dollar Syntax (TanStack Start)", () => {
173
+ // Single dynamic parameter
174
+ const idRegex = routeToRegex("/user/$id");
175
+ console.log(` "/user/$id" → ${idRegex}`);
176
+ assertMatch(idRegex, "/user/123", true);
177
+ assertMatch(idRegex, "/user/abc", true);
178
+ assertMatch(idRegex, "/user/", false);
179
+ // Multiple dynamic parameters
180
+ const multiRegex = routeToRegex("/user/$org/$repo");
181
+ console.log(` "/user/$org/$repo" → ${multiRegex}`);
182
+ assertMatch(multiRegex, "/user/facebook/react", true);
183
+ assertMatch(multiRegex, "/user/only-one", false);
184
+ // Splat route
185
+ const splatRegex = routeToRegex("/files/$");
186
+ console.log(` "/files/$" → ${splatRegex}`);
187
+ assertMatch(splatRegex, "/files/", true);
188
+ assertMatch(splatRegex, "/files/doc.txt", true);
189
+ assertMatch(splatRegex, "/files/nested/path/file.txt", true);
190
+ assertMatch(splatRegex, "/files", true);
191
+ // Mixed static and dynamic
192
+ const mixedRegex = routeToRegex("/api/v1/$id/details");
193
+ console.log(` "/api/v1/$id/details" → ${mixedRegex}`);
194
+ assertMatch(mixedRegex, "/api/v1/123/details", true);
195
+ assertMatch(mixedRegex, "/api/v1/details", false);
196
+ });
197
+ }
198
+ // ============================================================================
199
+ // routeToRegex Tests - At Syntax (Vike)
200
+ // ============================================================================
201
+ function testAtSyntax() {
202
+ runTestGroup("routeToRegex - At Syntax (Vike)", () => {
203
+ // Single dynamic parameter
204
+ const idRegex = routeToRegex("/user/@id");
205
+ console.log(` "/user/@id" → ${idRegex}`);
206
+ assertMatch(idRegex, "/user/123", true);
207
+ assertMatch(idRegex, "/user/abc", true);
208
+ assertMatch(idRegex, "/user/john-doe", true);
209
+ assertMatch(idRegex, "/user/", false, "Should not match empty param");
210
+ assertMatch(idRegex, "/user/123/extra", false);
211
+ // Multiple dynamic parameters
212
+ const multiRegex = routeToRegex("/api/@version/@endpoint");
213
+ console.log(` "/api/@version/@endpoint" → ${multiRegex}`);
214
+ assertMatch(multiRegex, "/api/v1/users", true);
215
+ assertMatch(multiRegex, "/api/v2/posts", true);
216
+ assertMatch(multiRegex, "/api/only-one", false);
217
+ // Blog slug
218
+ const blogRegex = routeToRegex("/blog/@slug");
219
+ console.log(` "/blog/@slug" → ${blogRegex}`);
220
+ assertMatch(blogRegex, "/blog/my-first-post", true);
221
+ assertMatch(blogRegex, "/blog/hello-world", true);
222
+ assertMatch(blogRegex, "/blog/", false);
223
+ // Product category and id
224
+ const productRegex = routeToRegex("/product/@category/@id");
225
+ console.log(` "/product/@category/@id" → ${productRegex}`);
226
+ assertMatch(productRegex, "/product/electronics/iphone-15", true);
227
+ assertMatch(productRegex, "/product/books/978-0-13-468599-1", true);
228
+ assertMatch(productRegex, "/product/electronics", false);
229
+ // Deep nested with multiple params
230
+ const shopRegex = routeToRegex("/shop/@shopId/item/@itemId");
231
+ console.log(` "/shop/@shopId/item/@itemId" → ${shopRegex}`);
232
+ assertMatch(shopRegex, "/shop/123/item/456", true);
233
+ assertMatch(shopRegex, "/shop/amazon/item/B00XYZ", true);
234
+ assertMatch(shopRegex, "/shop/123/item", false);
235
+ // Localized routes
236
+ const langRegex = routeToRegex("/lang/@locale/page/@pageNum");
237
+ console.log(` "/lang/@locale/page/@pageNum" → ${langRegex}`);
238
+ assertMatch(langRegex, "/lang/en/page/1", true);
239
+ assertMatch(langRegex, "/lang/zh-CN/page/10", true);
240
+ assertMatch(langRegex, "/lang/en/page", false);
241
+ // Search keyword
242
+ const searchRegex = routeToRegex("/search/@keyword");
243
+ console.log(` "/search/@keyword" → ${searchRegex}`);
244
+ assertMatch(searchRegex, "/search/hello", true);
245
+ assertMatch(searchRegex, "/search/hello%20world", true);
246
+ // Docs path (single param, not catch-all)
247
+ const docsRegex = routeToRegex("/docs/@path");
248
+ console.log(` "/docs/@path" → ${docsRegex}`);
249
+ assertMatch(docsRegex, "/docs/getting-started", true);
250
+ assertMatch(docsRegex, "/docs/api-reference", true);
251
+ assertMatch(docsRegex, "/docs/nested/path", false, "Single @param doesn't match nested paths");
252
+ });
253
+ }
254
+ // ============================================================================
255
+ // routeToRegex Tests - Bracket Syntax
256
+ // ============================================================================
257
+ function testBracketSyntax() {
258
+ runTestGroup("routeToRegex - Bracket Syntax (Next.js/Nuxt/SvelteKit)", () => {
259
+ // Single dynamic parameter
260
+ const idRegex = routeToRegex("/user/[id]");
261
+ console.log(` "/user/[id]" → ${idRegex}`);
262
+ assertMatch(idRegex, "/user/123", true);
263
+ assertMatch(idRegex, "/user/abc", true);
264
+ assertMatch(idRegex, "/user/", false);
265
+ // Multiple dynamic parameters
266
+ const multiRegex = routeToRegex("/user/[org]/[repo]");
267
+ console.log(` "/user/[org]/[repo]" → ${multiRegex}`);
268
+ assertMatch(multiRegex, "/user/facebook/react", true);
269
+ assertMatch(multiRegex, "/user/only-one", false);
270
+ // Optional parameter [[param]]
271
+ const optionalRegex = routeToRegex("/blog/[[slug]]");
272
+ console.log(` "/blog/[[slug]]" → ${optionalRegex}`);
273
+ assertMatch(optionalRegex, "/blog/my-post", true);
274
+ assertMatch(optionalRegex, "/blog", true, "Should match without optional param");
275
+ // Note: /blog/ may not match because (?:/([^/]+))? expects content after /
276
+ // Catch-all [...param]
277
+ const catchAllRegex = routeToRegex("/docs/[...path]");
278
+ console.log(` "/docs/[...path]" → ${catchAllRegex}`);
279
+ assertMatch(catchAllRegex, "/docs/", true);
280
+ assertMatch(catchAllRegex, "/docs/intro", true);
281
+ assertMatch(catchAllRegex, "/docs/guide/getting-started", true);
282
+ assertMatch(catchAllRegex, "/docs", true);
283
+ // Optional catch-all [[...param]]
284
+ const optCatchAllRegex = routeToRegex("/[[...path]]");
285
+ console.log(` "/[[...path]]" → ${optCatchAllRegex}`);
286
+ assertMatch(optCatchAllRegex, "/", true);
287
+ assertMatch(optCatchAllRegex, "/anything", true);
288
+ assertMatch(optCatchAllRegex, "/nested/path/here", true);
289
+ });
290
+ }
291
+ // ============================================================================
292
+ // routeToRegex Options Tests
293
+ // ============================================================================
294
+ function testOptions() {
295
+ runTestGroup("routeToRegex - Options", () => {
296
+ // trailingSlashOptional: false
297
+ const strictRegex = routeToRegex("/about", { trailingSlashOptional: false });
298
+ console.log(` trailingSlashOptional: false → ${strictRegex}`);
299
+ assertMatch(strictRegex, "/about", true);
300
+ assertMatch(strictRegex, "/about/", false, "Should not match trailing slash");
301
+ // anchored: false
302
+ const unanchoredRegex = routeToRegex("/api", { anchored: false });
303
+ console.log(` anchored: false → ${unanchoredRegex}`);
304
+ assertMatch(unanchoredRegex, "/api", true);
305
+ assertMatch(unanchoredRegex, "/v1/api", true, "Should match anywhere");
306
+ assertMatch(unanchoredRegex, "/api/users", true);
307
+ // explicit syntax
308
+ const colonRegex = routeToRegex("/user/:id", { syntax: "colon" });
309
+ console.log(` syntax: "colon" → ${colonRegex}`);
310
+ assertMatch(colonRegex, "/user/123", true);
311
+ });
312
+ }
313
+ // ============================================================================
314
+ // Edge Cases Tests
315
+ // ============================================================================
316
+ function testEdgeCases() {
317
+ runTestGroup("Edge Cases - Basic", () => {
318
+ // Root with trailing slash
319
+ const rootRegex = routeToRegex("/");
320
+ console.log(` "/" root route`);
321
+ assertMatch(rootRegex, "/", true);
322
+ // Route with dots (file extensions)
323
+ const fileRegex = routeToRegex("/files/:name.json");
324
+ console.log(` "/files/:name.json" → ${fileRegex}`);
325
+ assertMatch(fileRegex, "/files/config.json", true);
326
+ assertMatch(fileRegex, "/files/data.json", true);
327
+ // Route with multiple consecutive params
328
+ const consecutiveRegex = routeToRegex("/:a/:b/:c");
329
+ console.log(` "/:a/:b/:c" → ${consecutiveRegex}`);
330
+ assertMatch(consecutiveRegex, "/x/y/z", true);
331
+ assertMatch(consecutiveRegex, "/1/2/3", true);
332
+ assertMatch(consecutiveRegex, "/x/y", false);
333
+ // Numeric paths
334
+ const numericRegex = routeToRegex("/api/v1/users/:id");
335
+ console.log(` "/api/v1/users/:id" → ${numericRegex}`);
336
+ assertMatch(numericRegex, "/api/v1/users/123", true);
337
+ assertMatch(numericRegex, "/api/v2/users/123", false);
338
+ // Special characters in path
339
+ const specialRegex = routeToRegex("/path+with+plus");
340
+ console.log(` "/path+with+plus" → ${specialRegex}`);
341
+ assertMatch(specialRegex, "/path+with+plus", true);
342
+ });
343
+ runTestGroup("Edge Cases - Parameter Names", () => {
344
+ // Parameter with numbers
345
+ const numParamRegex = routeToRegex("/user/:id123");
346
+ console.log(` "/user/:id123" → ${numParamRegex}`);
347
+ assertMatch(numParamRegex, "/user/abc", true);
348
+ assertMatch(numParamRegex, "/user/456", true);
349
+ // Parameter with underscore
350
+ const underscoreRegex = routeToRegex("/post/:post_id");
351
+ console.log(` "/post/:post_id" → ${underscoreRegex}`);
352
+ assertMatch(underscoreRegex, "/post/123", true);
353
+ assertMatch(underscoreRegex, "/post/my-post", true);
354
+ // Multiple underscores
355
+ const multiUnderscoreRegex = routeToRegex("/api/:resource_type/:resource_id");
356
+ console.log(` "/api/:resource_type/:resource_id" → ${multiUnderscoreRegex}`);
357
+ assertMatch(multiUnderscoreRegex, "/api/users/123", true);
358
+ // Bracket syntax with underscore
359
+ const bracketUnderscoreRegex = routeToRegex("/item/[item_id]");
360
+ console.log(` "/item/[item_id]" → ${bracketUnderscoreRegex}`);
361
+ assertMatch(bracketUnderscoreRegex, "/item/abc123", true);
362
+ // Dollar syntax with underscore
363
+ const dollarUnderscoreRegex = routeToRegex("/product/$product_id");
364
+ console.log(` "/product/$product_id" → ${dollarUnderscoreRegex}`);
365
+ assertMatch(dollarUnderscoreRegex, "/product/xyz", true);
366
+ });
367
+ runTestGroup("Edge Cases - Path Variations", () => {
368
+ // Hyphenated static path
369
+ const hyphenRegex = routeToRegex("/my-page/:slug");
370
+ console.log(` "/my-page/:slug" → ${hyphenRegex}`);
371
+ assertMatch(hyphenRegex, "/my-page/hello-world", true);
372
+ assertMatch(hyphenRegex, "/my-page/test", true);
373
+ // Deep nested routes
374
+ const deepRegex = routeToRegex("/a/b/c/d/e/:id/f/g");
375
+ console.log(` "/a/b/c/d/e/:id/f/g" → ${deepRegex}`);
376
+ assertMatch(deepRegex, "/a/b/c/d/e/123/f/g", true);
377
+ assertMatch(deepRegex, "/a/b/c/d/e/123/f", false);
378
+ // Multiple dynamic segments scattered
379
+ const scatteredRegex = routeToRegex("/org/:orgId/team/:teamId/member/:memberId");
380
+ console.log(` "/org/:orgId/team/:teamId/member/:memberId" → ${scatteredRegex}`);
381
+ assertMatch(scatteredRegex, "/org/acme/team/eng/member/john", true);
382
+ assertMatch(scatteredRegex, "/org/acme/team/eng", false);
383
+ // Parentheses in static path (TanStack route groups)
384
+ const groupRegex = routeToRegex("/(auth)/login");
385
+ console.log(` "/(auth)/login" → ${groupRegex}`);
386
+ assertMatch(groupRegex, "/(auth)/login", true);
387
+ });
388
+ runTestGroup("Edge Cases - Special Matching", () => {
389
+ // URL with encoded characters
390
+ const encodedRegex = routeToRegex("/search/:query");
391
+ console.log(` "/search/:query" URL encoded`);
392
+ assertMatch(encodedRegex, "/search/hello%20world", true);
393
+ assertMatch(encodedRegex, "/search/test%2Fpath", true);
394
+ // Route ending with specific extension
395
+ const extRegex = routeToRegex("/download/:file.pdf");
396
+ console.log(` "/download/:file.pdf" → ${extRegex}`);
397
+ assertMatch(extRegex, "/download/report.pdf", true);
398
+ assertMatch(extRegex, "/download/document.pdf", true);
399
+ // Numbers in static path
400
+ const num404Regex = routeToRegex("/error/404");
401
+ console.log(` "/error/404" → ${num404Regex}`);
402
+ assertMatch(num404Regex, "/error/404", true);
403
+ assertMatch(num404Regex, "/error/500", false);
404
+ // Very long parameter value
405
+ const longValueRegex = routeToRegex("/article/:slug");
406
+ const longSlug = "a".repeat(200);
407
+ console.log(` Long parameter value (200 chars)`);
408
+ assertMatch(longValueRegex, `/article/${longSlug}`, true);
409
+ });
410
+ runTestGroup("Edge Cases - Wildcards and Catch-alls", () => {
411
+ // Root catch-all
412
+ const rootCatchAllRegex = routeToRegex("/*");
413
+ console.log(` "/*" root catch-all`);
414
+ assertMatch(rootCatchAllRegex, "/", true);
415
+ assertMatch(rootCatchAllRegex, "/anything", true);
416
+ assertMatch(rootCatchAllRegex, "/a/b/c/d", true);
417
+ // Nested catch-all
418
+ const nestedCatchAllRegex = routeToRegex("/api/v1/*");
419
+ console.log(` "/api/v1/*" → ${nestedCatchAllRegex}`);
420
+ assertMatch(nestedCatchAllRegex, "/api/v1", true);
421
+ assertMatch(nestedCatchAllRegex, "/api/v1/", true);
422
+ assertMatch(nestedCatchAllRegex, "/api/v1/users/123/posts", true);
423
+ // Dollar splat at different depths
424
+ const deepDollarSplatRegex = routeToRegex("/files/public/$");
425
+ console.log(` "/files/public/$" → ${deepDollarSplatRegex}`);
426
+ assertMatch(deepDollarSplatRegex, "/files/public", true);
427
+ assertMatch(deepDollarSplatRegex, "/files/public/images/photo.jpg", true);
428
+ // Bracket catch-all variations
429
+ const bracketDeepCatchAllRegex = routeToRegex("/shop/[...categories]");
430
+ console.log(` "/shop/[...categories]" → ${bracketDeepCatchAllRegex}`);
431
+ assertMatch(bracketDeepCatchAllRegex, "/shop", true);
432
+ assertMatch(bracketDeepCatchAllRegex, "/shop/electronics", true);
433
+ assertMatch(bracketDeepCatchAllRegex, "/shop/electronics/phones/iphone", true);
434
+ });
435
+ runTestGroup("Edge Cases - Multiple Optional Params", () => {
436
+ // Two optional colon params (Note: typically not valid but test conversion)
437
+ const twoOptionalRegex = routeToRegex("/archive/:year?/:month?");
438
+ console.log(` "/archive/:year?/:month?" → ${twoOptionalRegex}`);
439
+ assertMatch(twoOptionalRegex, "/archive", true);
440
+ assertMatch(twoOptionalRegex, "/archive/2024", true);
441
+ // Note: /archive/2024/12 may not match due to how optional groups work
442
+ // Bracket optional at root - this is a special case
443
+ // /[[lang]] means: optional language prefix, the root "/" is implicit
444
+ const rootOptionalRegex = routeToRegex("/[[lang]]");
445
+ console.log(` "/[[lang]]" → ${rootOptionalRegex}`);
446
+ // Note: ^(?:/([^/]+))?$ won't match "/" because it expects either nothing or /content
447
+ // This is expected behavior - "/" matches when the pattern is just "/" not "/[[lang]]"
448
+ assertMatch(rootOptionalRegex, "/en", true);
449
+ assertMatch(rootOptionalRegex, "/zh-CN", true);
450
+ assertMatch(rootOptionalRegex, "", true, "Empty string matches optional");
451
+ });
452
+ runTestGroup("Edge Cases - Real World Patterns", () => {
453
+ // GitHub-style routes
454
+ const githubRegex = routeToRegex("/:owner/:repo/tree/:branch/*");
455
+ console.log(` "/:owner/:repo/tree/:branch/*" GitHub pattern`);
456
+ assertMatch(githubRegex, "/facebook/react/tree/main", true);
457
+ assertMatch(githubRegex, "/facebook/react/tree/main/packages/react", true);
458
+ // Blog with date pattern
459
+ const blogDateRegex = routeToRegex("/blog/:year/:month/:day/:slug");
460
+ console.log(` "/blog/:year/:month/:day/:slug" → ${blogDateRegex}`);
461
+ assertMatch(blogDateRegex, "/blog/2024/01/15/hello-world", true);
462
+ // E-commerce product
463
+ const productRegex = routeToRegex("/category/[category]/product/[productId]");
464
+ console.log(` "/category/[category]/product/[productId]" → ${productRegex}`);
465
+ assertMatch(productRegex, "/category/electronics/product/iphone-15", true);
466
+ // API versioning with dynamic resource
467
+ const apiRegex = routeToRegex("/api/v:version/:resource/:id");
468
+ console.log(` "/api/v:version/:resource/:id" → ${apiRegex}`);
469
+ assertMatch(apiRegex, "/api/v1/users/123", true);
470
+ assertMatch(apiRegex, "/api/v2/posts/456", true);
471
+ // Localized routes
472
+ const i18nRegex = routeToRegex("/[locale]/about");
473
+ console.log(` "/[locale]/about" → ${i18nRegex}`);
474
+ assertMatch(i18nRegex, "/en/about", true);
475
+ assertMatch(i18nRegex, "/zh-CN/about", true);
476
+ assertMatch(i18nRegex, "/about", false);
477
+ // Vike-style routes
478
+ const vikeUserRegex = routeToRegex("/user/@id");
479
+ console.log(` "/user/@id" Vike pattern`);
480
+ assertMatch(vikeUserRegex, "/user/123", true);
481
+ assertMatch(vikeUserRegex, "/user/john", true);
482
+ const vikeApiRegex = routeToRegex("/api/@version/@endpoint");
483
+ console.log(` "/api/@version/@endpoint" Vike pattern`);
484
+ assertMatch(vikeApiRegex, "/api/v1/users", true);
485
+ const vikeShopRegex = routeToRegex("/shop/@shopId/item/@itemId");
486
+ console.log(` "/shop/@shopId/item/@itemId" Vike pattern`);
487
+ assertMatch(vikeShopRegex, "/shop/store1/item/product123", true);
488
+ });
489
+ }
490
+ // ============================================================================
491
+ // sortRoutesByPriority Tests
492
+ // ============================================================================
493
+ function testSortRoutes() {
494
+ runTestGroup("sortRoutesByPriority", () => {
495
+ const routes = [
496
+ { path: "/*", isStatic: false },
497
+ { path: "/api/:resource", isStatic: false },
498
+ { path: "/api/users/:id", isStatic: false },
499
+ { path: "/", isStatic: true },
500
+ { path: "/about", isStatic: true },
501
+ { path: "/api/users", isStatic: false },
502
+ ];
503
+ const sorted = sortRoutesByPriority(routes);
504
+ console.log(" Input order: /*, /api/:resource, /api/users/:id, /, /about, /api/users");
505
+ console.log(" Sorted order:", sorted.map(r => r.path).join(", "));
506
+ // Static routes should come first
507
+ console.assert(sorted[0].path === "/" || sorted[0].path === "/about", "Static routes should be first");
508
+ // Catch-all should be last
509
+ console.assert(sorted[sorted.length - 1].path === "/*", "Catch-all should be last");
510
+ // More specific dynamic routes should come before less specific
511
+ const apiUsersIdIndex = sorted.findIndex(r => r.path === "/api/users/:id");
512
+ const apiResourceIndex = sorted.findIndex(r => r.path === "/api/:resource");
513
+ console.assert(apiUsersIdIndex < apiResourceIndex, "/api/users/:id should come before /api/:resource");
514
+ console.log(" ✓ Static routes first, catch-all last, specific before general");
515
+ });
516
+ runTestGroup("sortRoutesByPriority - Mixed Syntaxes", () => {
517
+ const routes = [
518
+ { path: "/files/$", isStatic: false }, // TanStack splat
519
+ { path: "/blog/[slug]", isStatic: false }, // Next.js dynamic
520
+ { path: "/user/:id", isStatic: false }, // React Router dynamic
521
+ { path: "/about", isStatic: true },
522
+ { path: "/docs/[...path]", isStatic: false }, // Next.js catch-all
523
+ { path: "/product/@id", isStatic: false }, // Vike dynamic
524
+ ];
525
+ const sorted = sortRoutesByPriority(routes);
526
+ console.log(" Mixed syntax routes sorted:", sorted.map(r => r.path).join(", "));
527
+ // Static should be first
528
+ console.assert(sorted[0].path === "/about", "Static route should be first");
529
+ // Catch-all routes should be last
530
+ const catchAllPaths = ["/files/$", "/docs/[...path]"];
531
+ const lastTwo = sorted.slice(-2).map(r => r.path);
532
+ console.assert(catchAllPaths.every(p => lastTwo.includes(p)), "Catch-all routes should be last");
533
+ console.log(" ✓ Correctly handles mixed syntaxes including Vike @param");
534
+ });
535
+ }
536
+ // ============================================================================
537
+ // addRegexToRoutes Tests
538
+ // ============================================================================
539
+ function testAddRegexToRoutes() {
540
+ runTestGroup("addRegexToRoutes", () => {
541
+ const routes = [
542
+ { path: "/", isStatic: true },
543
+ { path: "/about", isStatic: true },
544
+ { path: "/blog/:slug", isStatic: false },
545
+ { path: "/user/:id/posts", isStatic: false },
546
+ ];
547
+ const result = addRegexToRoutes(routes);
548
+ console.log(" Static routes should not have regex:");
549
+ console.assert(!result[0].regex, "/ should not have regex");
550
+ console.assert(!result[1].regex, "/about should not have regex");
551
+ console.log(" ✓ / and /about have no regex");
552
+ console.log(" Dynamic routes should have regex:");
553
+ console.assert(result[2].regex !== undefined, "/blog/:slug should have regex");
554
+ console.assert(result[3].regex !== undefined, "/user/:id/posts should have regex");
555
+ console.log(` ✓ /blog/:slug → ${result[2].regex}`);
556
+ console.log(` ✓ /user/:id/posts → ${result[3].regex}`);
557
+ // Verify regex actually works
558
+ if (result[2].regex) {
559
+ assertMatch(result[2].regex, "/blog/my-post", true);
560
+ }
561
+ });
562
+ runTestGroup("addRegexToRoutes - with srcRoute", () => {
563
+ const routes = [
564
+ { path: "/blog/my-post", isStatic: true, srcRoute: "/blog/:slug" },
565
+ ];
566
+ const result = addRegexToRoutes(routes);
567
+ console.assert(result[0].regex !== undefined, "Should use srcRoute for regex");
568
+ console.log(` ✓ Uses srcRoute "/blog/:slug" → ${result[0].regex}`);
569
+ });
570
+ }
571
+ // ============================================================================
572
+ // convertRoutesToMetaFormat Tests
573
+ // ============================================================================
574
+ function testConvertRoutesToMetaFormat() {
575
+ runTestGroup("convertRoutesToMetaFormat", () => {
576
+ const routes = [
577
+ { path: "/", isStatic: true },
578
+ { path: "/about", isStatic: true },
579
+ { path: "/blog/:slug", isStatic: false },
580
+ { path: "/files/*", isStatic: false },
581
+ ];
582
+ const result = convertRoutesToMetaFormat(routes);
583
+ console.log(" Static routes keep original path:");
584
+ console.assert(result[0].regexPath === "/", "/ should stay /");
585
+ console.assert(result[1].regexPath === "/about", "/about should stay /about");
586
+ console.log(` ✓ "/" → "${result[0].regexPath}"`);
587
+ console.log(` ✓ "/about" → "${result[1].regexPath}"`);
588
+ console.log(" Dynamic routes converted to regex:");
589
+ console.assert(result[2].regexPath.includes("([^/]+)"), "/blog/:slug should be regex");
590
+ console.assert(result[3].regexPath.includes("(.*)"), "/files/* should be regex");
591
+ console.log(` ✓ "/blog/:slug" → "${result[2].regexPath}"`);
592
+ console.log(` ✓ "/files/*" → "${result[3].regexPath}"`);
593
+ console.log(" isStatic preserved:");
594
+ console.assert(result[0].isStatic === true, "/ isStatic should be true");
595
+ console.assert(result[2].isStatic === false, "/blog/:slug isStatic should be false");
596
+ console.log(" ✓ isStatic values preserved");
597
+ });
598
+ }
599
+ // ============================================================================
600
+ // Regression Tests
601
+ // ============================================================================
602
+ function testRegressions() {
603
+ runTestGroup("Regression Tests", () => {
604
+ // Issue: [[param]] was producing ([^/]+)? instead of (?:/([^/]+))?
605
+ console.log(" Issue: Optional bracket params with preceding slash");
606
+ const optBracket = routeToRegex("/blog/[[slug]]");
607
+ assertMatch(optBracket, "/blog", true, "Should match without param");
608
+ assertMatch(optBracket, "/blog/my-post", true, "Should match with param");
609
+ // Note: /blog/ with trailing slash but no param may not match - this is correct behavior
610
+ // Issue: Dollar splat $ not matching correctly
611
+ console.log(" Issue: Dollar splat handling");
612
+ const dollarSplat = routeToRegex("/api/$");
613
+ assertMatch(dollarSplat, "/api", true);
614
+ assertMatch(dollarSplat, "/api/", true);
615
+ assertMatch(dollarSplat, "/api/anything", true);
616
+ assertMatch(dollarSplat, "/api/nested/path", true);
617
+ // Issue: Bracket catch-all [...] behavior
618
+ console.log(" Issue: Bracket catch-all consistency");
619
+ const bracketCatchAll = routeToRegex("/docs/[...path]");
620
+ const bracketOptCatchAll = routeToRegex("/docs/[[...path]]");
621
+ // Both should have similar structure
622
+ console.log(` [...path] → ${bracketCatchAll}`);
623
+ console.log(` [[...path]] → ${bracketOptCatchAll}`);
624
+ assertMatch(bracketCatchAll, "/docs", true);
625
+ assertMatch(bracketOptCatchAll, "/docs", true);
626
+ });
627
+ }
628
+ // ============================================================================
629
+ // Run All Tests
630
+ // ============================================================================
631
+ export function runAllTests() {
632
+ console.log("╔════════════════════════════════════════════════════════════╗");
633
+ console.log("║ Route Regex Test Suite ║");
634
+ console.log("╚════════════════════════════════════════════════════════════╝");
635
+ try {
636
+ testIsDynamicRoute();
637
+ testIsCatchAllRoute();
638
+ testColonSyntax();
639
+ testDollarSyntax();
640
+ testAtSyntax();
641
+ testBracketSyntax();
642
+ testOptions();
643
+ testEdgeCases();
644
+ testSortRoutes();
645
+ testAddRegexToRoutes();
646
+ testConvertRoutesToMetaFormat();
647
+ testRegressions();
648
+ console.log("\n╔════════════════════════════════════════════════════════════╗");
649
+ console.log("║ ✓ All tests passed! ║");
650
+ console.log("╚════════════════════════════════════════════════════════════╝\n");
651
+ }
652
+ catch (error) {
653
+ console.error("\n╔════════════════════════════════════════════════════════════╗");
654
+ console.error("║ ✗ Tests failed! ║");
655
+ console.error("╚════════════════════════════════════════════════════════════╝");
656
+ console.error(error);
657
+ process.exit(1);
658
+ }
659
+ }
660
+ // Run tests if executed directly
661
+ runAllTests();
662
+ //# sourceMappingURL=regex.test.js.map