@rainfw/core 0.2.0 → 0.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainfw/core",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A TypeScript web framework for Cloudflare Workers",
5
5
  "bin": {
6
6
  "rainjs": "./cli/index.js"
@@ -21,7 +21,8 @@
21
21
  "test:generate": "node --test tests/generate.test.js",
22
22
  "bench": "node scripts/bench.js",
23
23
  "build:pkg": "tsc -p tsconfig.build.json && node -e \"require('fs').writeFileSync('dist/package.json',JSON.stringify({type:'module'}))\"",
24
- "prepublishOnly": "npm run build:pkg"
24
+ "prepublishOnly": "npm run build:pkg",
25
+ "release": "npm run ci && npm publish --access public"
25
26
  },
26
27
  "keywords": [
27
28
  "cloudflare-workers",
@@ -271,6 +271,13 @@ function bundleClientFilesSync(clientFiles, srcDir) {
271
271
  return scripts;
272
272
  }
273
273
 
274
+ function stripRouteGroupSegments(filePath) {
275
+ return filePath
276
+ .split("/")
277
+ .filter((segment) => !/^\(.+\)$/.test(segment))
278
+ .join("/");
279
+ }
280
+
274
281
  function routePathToDir(filePath) {
275
282
  return filePath
276
283
  .replace(/\\/g, "/")
@@ -285,6 +292,7 @@ function middlewareImportName(filePath) {
285
292
  .replace(/\//g, "_")
286
293
  .replace(/\[/g, "$")
287
294
  .replace(/\]/g, "")
295
+ .replace(/[()]/g, "")
288
296
  .replace(/-/g, "_")
289
297
  .replace(/_+$/, "");
290
298
  return `mw_${base || "root"}`;
@@ -307,6 +315,7 @@ function layoutPathToDir(filePath) {
307
315
  function pageFilePathToUrlPath(filePath) {
308
316
  let urlPath = filePath.replace(/\.tsx?$/, "");
309
317
  urlPath = urlPath.replace(/\\/g, "/");
318
+ urlPath = stripRouteGroupSegments(urlPath);
310
319
  urlPath = urlPath.replace(/\[([^\]]+)\]/g, ":$1");
311
320
  urlPath = urlPath.replace(/\/page$/, "");
312
321
  if (urlPath === "page") urlPath = "";
@@ -322,6 +331,7 @@ function pageFilePathToImportName(filePath) {
322
331
  .replace(/\//g, "_")
323
332
  .replace(/\[/g, "$")
324
333
  .replace(/\]/g, "")
334
+ .replace(/[()]/g, "")
325
335
  .replace(/-/g, "_")
326
336
  );
327
337
  }
@@ -333,6 +343,7 @@ function layoutImportName(filePath) {
333
343
  .replace(/\//g, "_")
334
344
  .replace(/\[/g, "$")
335
345
  .replace(/\]/g, "")
346
+ .replace(/[()]/g, "")
336
347
  .replace(/-/g, "_")
337
348
  .replace(/_+$/, "");
338
349
  return `layout_${base || "root"}`;
@@ -402,6 +413,63 @@ function validateNoPageRouteColocation(routeFiles, pageFiles) {
402
413
  }
403
414
  }
404
415
 
416
+ const routeUrlMap = new Map();
417
+ for (const f of routeFiles) {
418
+ routeUrlMap.set(filePathToUrlPath(f), f);
419
+ }
420
+ for (const pageFile of pageFiles) {
421
+ const url = pageFilePathToUrlPath(pageFile);
422
+ const conflicting = routeUrlMap.get(url);
423
+ if (conflicting) {
424
+ const pageDir = pageFilePathToDir(pageFile);
425
+ if (!routeDirs.has(pageDir)) {
426
+ errors.push(
427
+ `[Rain] Error: page "${pageFile}" and route "${conflicting}" resolve to the same URL path "${url}":\n` +
428
+ " → This conflict occurs because route group folders are stripped from URLs.\n" +
429
+ " → Move one of them to a different URL path.",
430
+ );
431
+ }
432
+ }
433
+ }
434
+
435
+ return errors;
436
+ }
437
+
438
+ function validateNoDuplicateUrls(routeFiles, pageFiles) {
439
+ const errors = [];
440
+
441
+ const routeUrlMap = new Map();
442
+ for (const f of routeFiles) {
443
+ const url = filePathToUrlPath(f);
444
+ if (routeUrlMap.has(url)) {
445
+ errors.push(
446
+ `[Rain] Error: multiple route files resolve to the same URL path "${url}":\n` +
447
+ ` → ${routeUrlMap.get(url)}\n` +
448
+ ` → ${f}\n` +
449
+ " → Route group folders are stripped from URLs.\n" +
450
+ " → Rename one of the routes to avoid the conflict.",
451
+ );
452
+ } else {
453
+ routeUrlMap.set(url, f);
454
+ }
455
+ }
456
+
457
+ const pageUrlMap = new Map();
458
+ for (const f of pageFiles) {
459
+ const url = pageFilePathToUrlPath(f);
460
+ if (pageUrlMap.has(url)) {
461
+ errors.push(
462
+ `[Rain] Error: multiple page files resolve to the same URL path "${url}":\n` +
463
+ ` → ${pageUrlMap.get(url)}\n` +
464
+ ` → ${f}\n` +
465
+ " → Route group folders are stripped from URLs.\n" +
466
+ " → Rename one of the pages to avoid the conflict.",
467
+ );
468
+ } else {
469
+ pageUrlMap.set(url, f);
470
+ }
471
+ }
472
+
405
473
  return errors;
406
474
  }
407
475
 
@@ -429,6 +497,7 @@ function getMiddlewaresForRoute(routeFile, middlewareFiles) {
429
497
  function filePathToUrlPath(filePath) {
430
498
  let urlPath = filePath.replace(/\.tsx?$/, "");
431
499
  urlPath = urlPath.replace(/\\/g, "/");
500
+ urlPath = stripRouteGroupSegments(urlPath);
432
501
  urlPath = urlPath.replace(/\[([^\]]+)\]/g, ":$1");
433
502
  urlPath = urlPath.replace(/\/route$/, "");
434
503
  if (urlPath === "route") urlPath = "";
@@ -444,6 +513,7 @@ function filePathToImportName(filePath) {
444
513
  .replace(/\//g, "_")
445
514
  .replace(/\[/g, "$")
446
515
  .replace(/\]/g, "")
516
+ .replace(/[()]/g, "")
447
517
  .replace(/-/g, "_")
448
518
  );
449
519
  }
@@ -750,6 +820,12 @@ function generate() {
750
820
  process.exit(1);
751
821
  }
752
822
 
823
+ const duplicateErrors = validateNoDuplicateUrls(files, pageFiles);
824
+ for (const err of duplicateErrors) {
825
+ console.error(err);
826
+ process.exit(1);
827
+ }
828
+
753
829
  const hasRootLayout = layoutFiles.some((f) => layoutPathToDir(f) === "");
754
830
 
755
831
  processMiddlewares(middlewareFiles, imports);
@@ -832,6 +908,8 @@ module.exports = {
832
908
  detectUseClientDirective,
833
909
  bundleClientFilesSync,
834
910
  validateNoPageRouteColocation,
911
+ validateNoDuplicateUrls,
912
+ stripRouteGroupSegments,
835
913
  ROUTES_DIR,
836
914
  ENTRY_FILE,
837
915
  HTTP_METHODS,