@react-router/fs-routes 7.16.0 → 8.0.0-pre.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 (3) hide show
  1. package/dist/index.d.ts +15 -13
  2. package/dist/index.js +353 -440
  3. package/package.json +11 -9
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { RouteConfigEntry } from '@react-router/dev/routes';
2
1
 
2
+ import { RouteConfigEntry } from "@react-router/dev/routes";
3
+
4
+ //#region index.d.ts
3
5
  /**
4
6
  * Creates route config from the file system using a convention that matches
5
7
  * [Remix v2's route file
@@ -7,16 +9,16 @@ import { RouteConfigEntry } from '@react-router/dev/routes';
7
9
  * within `routes.ts`.
8
10
  */
9
11
  declare function flatRoutes(options?: {
10
- /**
11
- * An array of [minimatch](https://www.npmjs.com/package/minimatch) globs that match files to ignore.
12
- * Defaults to `[]`.
13
- */
14
- ignoredRouteFiles?: string[];
15
- /**
16
- * The directory containing file system routes, relative to the app directory.
17
- * Defaults to `"./routes"`.
18
- */
19
- rootDirectory?: string;
12
+ /**
13
+ * An array of [minimatch](https://www.npmjs.com/package/minimatch) globs that match files to ignore.
14
+ * Defaults to `[]`.
15
+ */
16
+ ignoredRouteFiles?: string[];
17
+ /**
18
+ * The directory containing file system routes, relative to the app directory.
19
+ * Defaults to `"./routes"`.
20
+ */
21
+ rootDirectory?: string;
20
22
  }): Promise<RouteConfigEntry[]>;
21
-
22
- export { flatRoutes };
23
+ //#endregion
24
+ export { flatRoutes };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/fs-routes v7.16.0
2
+ * @react-router/fs-routes v8.0.0-pre.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,468 +8,381 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- "use strict";
12
- var __create = Object.create;
13
- var __defProp = Object.defineProperty;
14
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
15
- var __getOwnPropNames = Object.getOwnPropertyNames;
16
- var __getProtoOf = Object.getPrototypeOf;
17
- var __hasOwnProp = Object.prototype.hasOwnProperty;
18
- var __export = (target, all) => {
19
- for (var name in all)
20
- __defProp(target, name, { get: all[name], enumerable: true });
21
- };
22
- var __copyProps = (to, from, except, desc) => {
23
- if (from && typeof from === "object" || typeof from === "function") {
24
- for (let key of __getOwnPropNames(from))
25
- if (!__hasOwnProp.call(to, key) && key !== except)
26
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
27
- }
28
- return to;
29
- };
30
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
31
- // If the importer is in node compatibility mode or this is not an ESM
32
- // file that has been converted to a CommonJS file using a Babel-
33
- // compatible transform (i.e. "__esModule" has not been set), then set
34
- // "default" to the CommonJS "module.exports" for node compatibility.
35
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
36
- mod
37
- ));
38
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
39
-
40
- // index.ts
41
- var index_exports = {};
42
- __export(index_exports, {
43
- flatRoutes: () => flatRoutes2
44
- });
45
- module.exports = __toCommonJS(index_exports);
46
- var import_node_fs2 = __toESM(require("fs"));
47
- var import_node_path3 = __toESM(require("path"));
48
- var import_routes = require("@react-router/dev/routes");
49
-
50
- // manifest.ts
11
+ import fs from "node:fs";
12
+ import path from "node:path";
13
+ import { getAppDirectory } from "@react-router/dev/routes";
14
+ import { makeRe } from "minimatch";
15
+ //#region manifest.ts
51
16
  function routeManifestToRouteConfig(routeManifest, rootId = "root") {
52
- let routeConfigById = {};
53
- for (let id in routeManifest) {
54
- let route = routeManifest[id];
55
- routeConfigById[id] = {
56
- id: route.id,
57
- file: route.file,
58
- path: route.path,
59
- index: route.index,
60
- caseSensitive: route.caseSensitive
61
- };
62
- }
63
- let routeConfig = [];
64
- for (let id in routeConfigById) {
65
- let route = routeConfigById[id];
66
- let parentId = routeManifest[route.id].parentId;
67
- if (parentId === rootId) {
68
- routeConfig.push(route);
69
- } else {
70
- let parentRoute = parentId && routeConfigById[parentId];
71
- if (parentRoute) {
72
- parentRoute.children = parentRoute.children || [];
73
- parentRoute.children.push(route);
74
- }
75
- }
76
- }
77
- return routeConfig;
17
+ let routeConfigById = {};
18
+ for (let id in routeManifest) {
19
+ let route = routeManifest[id];
20
+ routeConfigById[id] = {
21
+ id: route.id,
22
+ file: route.file,
23
+ path: route.path,
24
+ index: route.index,
25
+ caseSensitive: route.caseSensitive
26
+ };
27
+ }
28
+ let routeConfig = [];
29
+ for (let id in routeConfigById) {
30
+ let route = routeConfigById[id];
31
+ let parentId = routeManifest[route.id].parentId;
32
+ if (parentId === rootId) routeConfig.push(route);
33
+ else {
34
+ let parentRoute = parentId && routeConfigById[parentId];
35
+ if (parentRoute) {
36
+ parentRoute.children = parentRoute.children || [];
37
+ parentRoute.children.push(route);
38
+ }
39
+ }
40
+ }
41
+ return routeConfig;
78
42
  }
79
-
80
- // flatRoutes.ts
81
- var import_node_fs = __toESM(require("fs"));
82
- var import_node_path2 = __toESM(require("path"));
83
- var import_minimatch = require("minimatch");
84
-
85
- // normalizeSlashes.ts
86
- var import_node_path = __toESM(require("path"));
43
+ //#endregion
44
+ //#region normalizeSlashes.ts
87
45
  function normalizeSlashes(file) {
88
- return file.replaceAll(import_node_path.default.win32.sep, "/");
46
+ return file.replaceAll(path.win32.sep, "/");
89
47
  }
90
-
91
- // flatRoutes.ts
92
- var routeModuleExts = [".js", ".jsx", ".ts", ".tsx", ".md", ".mdx"];
93
- var paramPrefixChar = "$";
94
- var escapeStart = "[";
95
- var escapeEnd = "]";
96
- var optionalStart = "(";
97
- var optionalEnd = ")";
98
- var PrefixLookupTrieEndSymbol = Symbol("PrefixLookupTrieEndSymbol");
48
+ //#endregion
49
+ //#region flatRoutes.ts
50
+ const routeModuleExts = [
51
+ ".js",
52
+ ".jsx",
53
+ ".ts",
54
+ ".tsx",
55
+ ".md",
56
+ ".mdx"
57
+ ];
58
+ const PrefixLookupTrieEndSymbol = Symbol("PrefixLookupTrieEndSymbol");
99
59
  var PrefixLookupTrie = class {
100
- root = {
101
- [PrefixLookupTrieEndSymbol]: false
102
- };
103
- add(value) {
104
- if (!value) throw new Error("Cannot add empty string to PrefixLookupTrie");
105
- let node = this.root;
106
- for (let char of value) {
107
- if (!node[char]) {
108
- node[char] = {
109
- [PrefixLookupTrieEndSymbol]: false
110
- };
111
- }
112
- node = node[char];
113
- }
114
- node[PrefixLookupTrieEndSymbol] = true;
115
- }
116
- findAndRemove(prefix, filter) {
117
- let node = this.root;
118
- for (let char of prefix) {
119
- if (!node[char]) return [];
120
- node = node[char];
121
- }
122
- return this.#findAndRemoveRecursive([], node, prefix, filter);
123
- }
124
- #findAndRemoveRecursive(values, node, prefix, filter) {
125
- for (let char of Object.keys(node)) {
126
- this.#findAndRemoveRecursive(values, node[char], prefix + char, filter);
127
- }
128
- if (node[PrefixLookupTrieEndSymbol] && filter(prefix)) {
129
- node[PrefixLookupTrieEndSymbol] = false;
130
- values.push(prefix);
131
- }
132
- return values;
133
- }
60
+ root = { [PrefixLookupTrieEndSymbol]: false };
61
+ add(value) {
62
+ if (!value) throw new Error("Cannot add empty string to PrefixLookupTrie");
63
+ let node = this.root;
64
+ for (let char of value) {
65
+ if (!node[char]) node[char] = { [PrefixLookupTrieEndSymbol]: false };
66
+ node = node[char];
67
+ }
68
+ node[PrefixLookupTrieEndSymbol] = true;
69
+ }
70
+ findAndRemove(prefix, filter) {
71
+ let node = this.root;
72
+ for (let char of prefix) {
73
+ if (!node[char]) return [];
74
+ node = node[char];
75
+ }
76
+ return this.#findAndRemoveRecursive([], node, prefix, filter);
77
+ }
78
+ #findAndRemoveRecursive(values, node, prefix, filter) {
79
+ for (let char of Object.keys(node)) this.#findAndRemoveRecursive(values, node[char], prefix + char, filter);
80
+ if (node[PrefixLookupTrieEndSymbol] && filter(prefix)) {
81
+ node[PrefixLookupTrieEndSymbol] = false;
82
+ values.push(prefix);
83
+ }
84
+ return values;
85
+ }
134
86
  };
135
- function flatRoutes(appDirectory, ignoredFilePatterns = [], prefix = "routes") {
136
- let ignoredFileRegex = Array.from(/* @__PURE__ */ new Set(["**/.*", ...ignoredFilePatterns])).map((re) => (0, import_minimatch.makeRe)(re)).filter((re) => !!re);
137
- let routesDir = import_node_path2.default.join(appDirectory, prefix);
138
- let rootRoute = findFile(appDirectory, "root", routeModuleExts);
139
- if (!rootRoute) {
140
- throw new Error(
141
- `Could not find a root route module in the app directory: ${appDirectory}`
142
- );
143
- }
144
- if (!import_node_fs.default.existsSync(routesDir)) {
145
- throw new Error(
146
- `Could not find the routes directory: ${routesDir}. Did you forget to create it?`
147
- );
148
- }
149
- let entries = import_node_fs.default.readdirSync(routesDir, {
150
- withFileTypes: true,
151
- encoding: "utf-8"
152
- });
153
- let routes = [];
154
- for (let entry of entries) {
155
- let filepath = normalizeSlashes(import_node_path2.default.join(routesDir, entry.name));
156
- let route = null;
157
- if (entry.isDirectory()) {
158
- route = findRouteModuleForFolder(
159
- appDirectory,
160
- filepath,
161
- ignoredFileRegex
162
- );
163
- } else if (entry.isFile()) {
164
- route = findRouteModuleForFile(appDirectory, filepath, ignoredFileRegex);
165
- }
166
- if (route) routes.push(route);
167
- }
168
- let routeManifest = flatRoutesUniversal(appDirectory, routes, prefix);
169
- return routeManifest;
87
+ function flatRoutes$1(appDirectory, ignoredFilePatterns = [], prefix = "routes") {
88
+ let ignoredFileRegex = Array.from(new Set(["**/.*", ...ignoredFilePatterns])).map((re) => makeRe(re)).filter((re) => !!re);
89
+ let routesDir = path.join(appDirectory, prefix);
90
+ if (!findFile(appDirectory, "root", routeModuleExts)) throw new Error(`Could not find a root route module in the app directory: ${appDirectory}`);
91
+ if (!fs.existsSync(routesDir)) throw new Error(`Could not find the routes directory: ${routesDir}. Did you forget to create it?`);
92
+ let entries = fs.readdirSync(routesDir, {
93
+ withFileTypes: true,
94
+ encoding: "utf-8"
95
+ });
96
+ let routes = [];
97
+ for (let entry of entries) {
98
+ let filepath = normalizeSlashes(path.join(routesDir, entry.name));
99
+ let route = null;
100
+ if (entry.isDirectory()) route = findRouteModuleForFolder(appDirectory, filepath, ignoredFileRegex);
101
+ else if (entry.isFile()) route = findRouteModuleForFile(appDirectory, filepath, ignoredFileRegex);
102
+ if (route) routes.push(route);
103
+ }
104
+ return flatRoutesUniversal(appDirectory, routes, prefix);
170
105
  }
171
106
  function flatRoutesUniversal(appDirectory, routes, prefix = "routes") {
172
- let urlConflicts = /* @__PURE__ */ new Map();
173
- let routeManifest = {};
174
- let prefixLookup = new PrefixLookupTrie();
175
- let uniqueRoutes = /* @__PURE__ */ new Map();
176
- let routeIdConflicts = /* @__PURE__ */ new Map();
177
- let normalizedApp = normalizeSlashes(appDirectory);
178
- let appWithPrefix = import_node_path2.default.posix.join(normalizedApp, prefix);
179
- let routeIds = /* @__PURE__ */ new Map();
180
- for (let file of routes) {
181
- let normalizedFile = normalizeSlashes(file);
182
- let routeExt = import_node_path2.default.extname(normalizedFile);
183
- let routeDir = import_node_path2.default.dirname(normalizedFile);
184
- let routeId = routeDir === appWithPrefix ? import_node_path2.default.posix.relative(normalizedApp, normalizedFile).slice(0, -routeExt.length) : import_node_path2.default.posix.relative(normalizedApp, routeDir);
185
- let conflict = routeIds.get(routeId);
186
- if (conflict) {
187
- let currentConflicts = routeIdConflicts.get(routeId);
188
- if (!currentConflicts) {
189
- currentConflicts = [import_node_path2.default.posix.relative(normalizedApp, conflict)];
190
- }
191
- currentConflicts.push(import_node_path2.default.posix.relative(normalizedApp, normalizedFile));
192
- routeIdConflicts.set(routeId, currentConflicts);
193
- continue;
194
- }
195
- routeIds.set(routeId, normalizedFile);
196
- }
197
- let sortedRouteIds = Array.from(routeIds).sort(
198
- ([a], [b]) => b.length - a.length
199
- );
200
- for (let [routeId, file] of sortedRouteIds) {
201
- let index = routeId.endsWith("_index");
202
- let [segments, raw] = getRouteSegments(routeId.slice(prefix.length + 1));
203
- let pathname = createRoutePath(segments, raw, index);
204
- routeManifest[routeId] = {
205
- file: import_node_path2.default.posix.relative(normalizedApp, file),
206
- id: routeId,
207
- path: pathname
208
- };
209
- if (index) routeManifest[routeId].index = true;
210
- let childRouteIds = prefixLookup.findAndRemove(routeId, (value) => {
211
- return [".", "/"].includes(value.slice(routeId.length).charAt(0));
212
- });
213
- prefixLookup.add(routeId);
214
- if (childRouteIds.length > 0) {
215
- for (let childRouteId of childRouteIds) {
216
- routeManifest[childRouteId].parentId = routeId;
217
- }
218
- }
219
- }
220
- let parentChildrenMap = /* @__PURE__ */ new Map();
221
- for (let [routeId] of sortedRouteIds) {
222
- let config = routeManifest[routeId];
223
- if (!config.parentId) continue;
224
- let existingChildren = parentChildrenMap.get(config.parentId) || [];
225
- existingChildren.push(config);
226
- parentChildrenMap.set(config.parentId, existingChildren);
227
- }
228
- for (let [routeId] of sortedRouteIds) {
229
- let config = routeManifest[routeId];
230
- let originalPathname = config.path || "";
231
- let pathname = config.path;
232
- let parentConfig = config.parentId ? routeManifest[config.parentId] : null;
233
- if (parentConfig?.path && pathname) {
234
- pathname = pathname.slice(parentConfig.path.length).replace(/^\//, "").replace(/\/$/, "");
235
- }
236
- if (!config.parentId) config.parentId = "root";
237
- config.path = pathname || void 0;
238
- let lastRouteSegment = config.id.replace(new RegExp(`^${prefix}/`), "").split(".").pop();
239
- let isPathlessLayoutRoute = lastRouteSegment && lastRouteSegment.startsWith("_") && lastRouteSegment !== "_index";
240
- if (isPathlessLayoutRoute) {
241
- continue;
242
- }
243
- let conflictRouteId = originalPathname + (config.index ? "?index" : "");
244
- let conflict = uniqueRoutes.get(conflictRouteId);
245
- uniqueRoutes.set(conflictRouteId, config);
246
- if (conflict && (originalPathname || config.index)) {
247
- let currentConflicts = urlConflicts.get(originalPathname);
248
- if (!currentConflicts) currentConflicts = [conflict];
249
- currentConflicts.push(config);
250
- urlConflicts.set(originalPathname, currentConflicts);
251
- continue;
252
- }
253
- }
254
- if (routeIdConflicts.size > 0) {
255
- for (let [routeId, files] of routeIdConflicts.entries()) {
256
- console.error(getRouteIdConflictErrorMessage(routeId, files));
257
- }
258
- }
259
- if (urlConflicts.size > 0) {
260
- for (let [path4, routes2] of urlConflicts.entries()) {
261
- for (let i = 1; i < routes2.length; i++) {
262
- delete routeManifest[routes2[i].id];
263
- }
264
- let files = routes2.map((r) => r.file);
265
- console.error(getRoutePathConflictErrorMessage(path4, files));
266
- }
267
- }
268
- return routeManifest;
107
+ let urlConflicts = /* @__PURE__ */ new Map();
108
+ let routeManifest = {};
109
+ let prefixLookup = new PrefixLookupTrie();
110
+ let uniqueRoutes = /* @__PURE__ */ new Map();
111
+ let routeIdConflicts = /* @__PURE__ */ new Map();
112
+ let normalizedApp = normalizeSlashes(appDirectory);
113
+ let appWithPrefix = path.posix.join(normalizedApp, prefix);
114
+ let routeIds = /* @__PURE__ */ new Map();
115
+ for (let file of routes) {
116
+ let normalizedFile = normalizeSlashes(file);
117
+ let routeExt = path.extname(normalizedFile);
118
+ let routeDir = path.dirname(normalizedFile);
119
+ let routeId = routeDir === appWithPrefix ? path.posix.relative(normalizedApp, normalizedFile).slice(0, -routeExt.length) : path.posix.relative(normalizedApp, routeDir);
120
+ let conflict = routeIds.get(routeId);
121
+ if (conflict) {
122
+ let currentConflicts = routeIdConflicts.get(routeId);
123
+ if (!currentConflicts) currentConflicts = [path.posix.relative(normalizedApp, conflict)];
124
+ currentConflicts.push(path.posix.relative(normalizedApp, normalizedFile));
125
+ routeIdConflicts.set(routeId, currentConflicts);
126
+ continue;
127
+ }
128
+ routeIds.set(routeId, normalizedFile);
129
+ }
130
+ let sortedRouteIds = Array.from(routeIds).sort(([a], [b]) => b.length - a.length);
131
+ for (let [routeId, file] of sortedRouteIds) {
132
+ let index = routeId.endsWith("_index");
133
+ let [segments, raw] = getRouteSegments(routeId.slice(prefix.length + 1));
134
+ let pathname = createRoutePath(segments, raw, index);
135
+ routeManifest[routeId] = {
136
+ file: path.posix.relative(normalizedApp, file),
137
+ id: routeId,
138
+ path: pathname
139
+ };
140
+ if (index) routeManifest[routeId].index = true;
141
+ let childRouteIds = prefixLookup.findAndRemove(routeId, (value) => {
142
+ return [".", "/"].includes(value.slice(routeId.length).charAt(0));
143
+ });
144
+ prefixLookup.add(routeId);
145
+ if (childRouteIds.length > 0) for (let childRouteId of childRouteIds) routeManifest[childRouteId].parentId = routeId;
146
+ }
147
+ let parentChildrenMap = /* @__PURE__ */ new Map();
148
+ for (let [routeId] of sortedRouteIds) {
149
+ let config = routeManifest[routeId];
150
+ if (!config.parentId) continue;
151
+ let existingChildren = parentChildrenMap.get(config.parentId) || [];
152
+ existingChildren.push(config);
153
+ parentChildrenMap.set(config.parentId, existingChildren);
154
+ }
155
+ for (let [routeId] of sortedRouteIds) {
156
+ let config = routeManifest[routeId];
157
+ let originalPathname = config.path || "";
158
+ let pathname = config.path;
159
+ let parentConfig = config.parentId ? routeManifest[config.parentId] : null;
160
+ if (parentConfig?.path && pathname) pathname = pathname.slice(parentConfig.path.length).replace(/^\//, "").replace(/\/$/, "");
161
+ if (!config.parentId) config.parentId = "root";
162
+ config.path = pathname || void 0;
163
+ /**
164
+ * We do not try to detect path collisions for pathless layout route
165
+ * files because, by definition, they create the potential for route
166
+ * collisions _at that level in the tree_.
167
+ *
168
+ * Consider example where a user may want multiple pathless layout routes
169
+ * for different subfolders
170
+ *
171
+ * routes/
172
+ * account.tsx
173
+ * account._private.tsx
174
+ * account._private.orders.tsx
175
+ * account._private.profile.tsx
176
+ * account._public.tsx
177
+ * account._public.login.tsx
178
+ * account._public.perks.tsx
179
+ *
180
+ * In order to support both a public and private layout for `/account/*`
181
+ * URLs, we are creating a mutually exclusive set of URLs beneath 2
182
+ * separate pathless layout routes. In this case, the route paths for
183
+ * both account._public.tsx and account._private.tsx is the same
184
+ * (/account), but we're again not expecting to match at that level.
185
+ *
186
+ * By only ignoring this check when the final portion of the filename is
187
+ * pathless, we will still detect path collisions such as:
188
+ *
189
+ * routes/parent._pathless.foo.tsx
190
+ * routes/parent._pathless2.foo.tsx
191
+ *
192
+ * and
193
+ *
194
+ * routes/parent._pathless/index.tsx
195
+ * routes/parent._pathless2/index.tsx
196
+ */
197
+ let lastRouteSegment = config.id.replace(new RegExp(`^${prefix}/`), "").split(".").pop();
198
+ if (lastRouteSegment && lastRouteSegment.startsWith("_") && lastRouteSegment !== "_index") continue;
199
+ let conflictRouteId = originalPathname + (config.index ? "?index" : "");
200
+ let conflict = uniqueRoutes.get(conflictRouteId);
201
+ uniqueRoutes.set(conflictRouteId, config);
202
+ if (conflict && (originalPathname || config.index)) {
203
+ let currentConflicts = urlConflicts.get(originalPathname);
204
+ if (!currentConflicts) currentConflicts = [conflict];
205
+ currentConflicts.push(config);
206
+ urlConflicts.set(originalPathname, currentConflicts);
207
+ continue;
208
+ }
209
+ }
210
+ if (routeIdConflicts.size > 0) for (let [routeId, files] of routeIdConflicts.entries()) console.error(getRouteIdConflictErrorMessage(routeId, files));
211
+ if (urlConflicts.size > 0) for (let [path, routes] of urlConflicts.entries()) {
212
+ for (let i = 1; i < routes.length; i++) delete routeManifest[routes[i].id];
213
+ let files = routes.map((r) => r.file);
214
+ console.error(getRoutePathConflictErrorMessage(path, files));
215
+ }
216
+ return routeManifest;
269
217
  }
270
218
  function findRouteModuleForFile(appDirectory, filepath, ignoredFileRegex) {
271
- let relativePath = normalizeSlashes(import_node_path2.default.relative(appDirectory, filepath));
272
- let isIgnored = ignoredFileRegex.some((regex) => regex.test(relativePath));
273
- if (isIgnored) return null;
274
- return filepath;
219
+ let relativePath = normalizeSlashes(path.relative(appDirectory, filepath));
220
+ if (ignoredFileRegex.some((regex) => regex.test(relativePath))) return null;
221
+ return filepath;
275
222
  }
276
223
  function findRouteModuleForFolder(appDirectory, filepath, ignoredFileRegex) {
277
- let relativePath = import_node_path2.default.relative(appDirectory, filepath);
278
- let isIgnored = ignoredFileRegex.some((regex) => regex.test(relativePath));
279
- if (isIgnored) return null;
280
- let routeRouteModule = findFile(filepath, "route", routeModuleExts);
281
- let routeIndexModule = findFile(filepath, "index", routeModuleExts);
282
- if (routeRouteModule && routeIndexModule) {
283
- let [segments, raw] = getRouteSegments(
284
- import_node_path2.default.relative(appDirectory, filepath)
285
- );
286
- let routePath = createRoutePath(segments, raw, false);
287
- console.error(
288
- getRoutePathConflictErrorMessage(routePath || "/", [
289
- routeRouteModule,
290
- routeIndexModule
291
- ])
292
- );
293
- }
294
- return routeRouteModule || routeIndexModule || null;
224
+ let relativePath = path.relative(appDirectory, filepath);
225
+ if (ignoredFileRegex.some((regex) => regex.test(relativePath))) return null;
226
+ let routeRouteModule = findFile(filepath, "route", routeModuleExts);
227
+ let routeIndexModule = findFile(filepath, "index", routeModuleExts);
228
+ if (routeRouteModule && routeIndexModule) {
229
+ let [segments, raw] = getRouteSegments(path.relative(appDirectory, filepath));
230
+ let routePath = createRoutePath(segments, raw, false);
231
+ console.error(getRoutePathConflictErrorMessage(routePath || "/", [routeRouteModule, routeIndexModule]));
232
+ }
233
+ return routeRouteModule || routeIndexModule || null;
295
234
  }
296
235
  function getRouteSegments(routeId) {
297
- let routeSegments = [];
298
- let rawRouteSegments = [];
299
- let index = 0;
300
- let routeSegment = "";
301
- let rawRouteSegment = "";
302
- let state = "NORMAL";
303
- let pushRouteSegment = (segment, rawSegment) => {
304
- if (!segment) return;
305
- let notSupportedInRR = (segment2, char) => {
306
- throw new Error(
307
- `Route segment "${segment2}" for "${routeId}" cannot contain "${char}".
308
- If this is something you need, upvote this proposal for React Router https://github.com/remix-run/react-router/discussions/9822.`
309
- );
310
- };
311
- if (rawSegment.includes("*")) {
312
- return notSupportedInRR(rawSegment, "*");
313
- }
314
- if (rawSegment.includes(":")) {
315
- return notSupportedInRR(rawSegment, ":");
316
- }
317
- if (rawSegment.includes("/")) {
318
- return notSupportedInRR(segment, "/");
319
- }
320
- routeSegments.push(segment);
321
- rawRouteSegments.push(rawSegment);
322
- };
323
- while (index < routeId.length) {
324
- let char = routeId[index];
325
- index++;
326
- switch (state) {
327
- case "NORMAL": {
328
- if (isSegmentSeparator(char)) {
329
- pushRouteSegment(routeSegment, rawRouteSegment);
330
- routeSegment = "";
331
- rawRouteSegment = "";
332
- state = "NORMAL";
333
- break;
334
- }
335
- if (char === escapeStart) {
336
- state = "ESCAPE";
337
- rawRouteSegment += char;
338
- break;
339
- }
340
- if (char === optionalStart) {
341
- state = "OPTIONAL";
342
- rawRouteSegment += char;
343
- break;
344
- }
345
- if (!routeSegment && char === paramPrefixChar) {
346
- if (index === routeId.length) {
347
- routeSegment += "*";
348
- rawRouteSegment += char;
349
- } else {
350
- routeSegment += ":";
351
- rawRouteSegment += char;
352
- }
353
- break;
354
- }
355
- routeSegment += char;
356
- rawRouteSegment += char;
357
- break;
358
- }
359
- case "ESCAPE": {
360
- if (char === escapeEnd) {
361
- state = "NORMAL";
362
- rawRouteSegment += char;
363
- break;
364
- }
365
- routeSegment += char;
366
- rawRouteSegment += char;
367
- break;
368
- }
369
- case "OPTIONAL": {
370
- if (char === optionalEnd) {
371
- routeSegment += "?";
372
- rawRouteSegment += char;
373
- state = "NORMAL";
374
- break;
375
- }
376
- if (char === escapeStart) {
377
- state = "OPTIONAL_ESCAPE";
378
- rawRouteSegment += char;
379
- break;
380
- }
381
- if (!routeSegment && char === paramPrefixChar) {
382
- if (index === routeId.length) {
383
- routeSegment += "*";
384
- rawRouteSegment += char;
385
- } else {
386
- routeSegment += ":";
387
- rawRouteSegment += char;
388
- }
389
- break;
390
- }
391
- routeSegment += char;
392
- rawRouteSegment += char;
393
- break;
394
- }
395
- case "OPTIONAL_ESCAPE": {
396
- if (char === escapeEnd) {
397
- state = "OPTIONAL";
398
- rawRouteSegment += char;
399
- break;
400
- }
401
- routeSegment += char;
402
- rawRouteSegment += char;
403
- break;
404
- }
405
- }
406
- }
407
- pushRouteSegment(routeSegment, rawRouteSegment);
408
- return [routeSegments, rawRouteSegments];
236
+ let routeSegments = [];
237
+ let rawRouteSegments = [];
238
+ let index = 0;
239
+ let routeSegment = "";
240
+ let rawRouteSegment = "";
241
+ let state = "NORMAL";
242
+ let pushRouteSegment = (segment, rawSegment) => {
243
+ if (!segment) return;
244
+ let notSupportedInRR = (segment, char) => {
245
+ throw new Error(`Route segment "${segment}" for "${routeId}" cannot contain "${char}".\nIf this is something you need, upvote this proposal for React Router https://github.com/remix-run/react-router/discussions/9822.`);
246
+ };
247
+ if (rawSegment.includes("*")) return notSupportedInRR(rawSegment, "*");
248
+ if (rawSegment.includes(":")) return notSupportedInRR(rawSegment, ":");
249
+ if (rawSegment.includes("/")) return notSupportedInRR(segment, "/");
250
+ routeSegments.push(segment);
251
+ rawRouteSegments.push(rawSegment);
252
+ };
253
+ while (index < routeId.length) {
254
+ let char = routeId[index];
255
+ index++;
256
+ switch (state) {
257
+ case "NORMAL":
258
+ if (isSegmentSeparator(char)) {
259
+ pushRouteSegment(routeSegment, rawRouteSegment);
260
+ routeSegment = "";
261
+ rawRouteSegment = "";
262
+ state = "NORMAL";
263
+ break;
264
+ }
265
+ if (char === "[") {
266
+ state = "ESCAPE";
267
+ rawRouteSegment += char;
268
+ break;
269
+ }
270
+ if (char === "(") {
271
+ state = "OPTIONAL";
272
+ rawRouteSegment += char;
273
+ break;
274
+ }
275
+ if (!routeSegment && char === "$") {
276
+ if (index === routeId.length) {
277
+ routeSegment += "*";
278
+ rawRouteSegment += char;
279
+ } else {
280
+ routeSegment += ":";
281
+ rawRouteSegment += char;
282
+ }
283
+ break;
284
+ }
285
+ routeSegment += char;
286
+ rawRouteSegment += char;
287
+ break;
288
+ case "ESCAPE":
289
+ if (char === "]") {
290
+ state = "NORMAL";
291
+ rawRouteSegment += char;
292
+ break;
293
+ }
294
+ routeSegment += char;
295
+ rawRouteSegment += char;
296
+ break;
297
+ case "OPTIONAL":
298
+ if (char === ")") {
299
+ routeSegment += "?";
300
+ rawRouteSegment += char;
301
+ state = "NORMAL";
302
+ break;
303
+ }
304
+ if (char === "[") {
305
+ state = "OPTIONAL_ESCAPE";
306
+ rawRouteSegment += char;
307
+ break;
308
+ }
309
+ if (!routeSegment && char === "$") {
310
+ if (index === routeId.length) {
311
+ routeSegment += "*";
312
+ rawRouteSegment += char;
313
+ } else {
314
+ routeSegment += ":";
315
+ rawRouteSegment += char;
316
+ }
317
+ break;
318
+ }
319
+ routeSegment += char;
320
+ rawRouteSegment += char;
321
+ break;
322
+ case "OPTIONAL_ESCAPE":
323
+ if (char === "]") {
324
+ state = "OPTIONAL";
325
+ rawRouteSegment += char;
326
+ break;
327
+ }
328
+ routeSegment += char;
329
+ rawRouteSegment += char;
330
+ break;
331
+ }
332
+ }
333
+ pushRouteSegment(routeSegment, rawRouteSegment);
334
+ return [routeSegments, rawRouteSegments];
409
335
  }
410
336
  function createRoutePath(routeSegments, rawRouteSegments, isIndex) {
411
- let result = [];
412
- if (isIndex) {
413
- routeSegments = routeSegments.slice(0, -1);
414
- }
415
- for (let index = 0; index < routeSegments.length; index++) {
416
- let segment = routeSegments[index];
417
- let rawSegment = rawRouteSegments[index];
418
- if (segment.startsWith("_") && rawSegment.startsWith("_")) {
419
- continue;
420
- }
421
- if (segment.endsWith("_") && rawSegment.endsWith("_")) {
422
- segment = segment.slice(0, -1);
423
- }
424
- result.push(segment);
425
- }
426
- return result.length ? result.join("/") : void 0;
337
+ let result = [];
338
+ if (isIndex) routeSegments = routeSegments.slice(0, -1);
339
+ for (let index = 0; index < routeSegments.length; index++) {
340
+ let segment = routeSegments[index];
341
+ let rawSegment = rawRouteSegments[index];
342
+ if (segment.startsWith("_") && rawSegment.startsWith("_")) continue;
343
+ if (segment.endsWith("_") && rawSegment.endsWith("_")) segment = segment.slice(0, -1);
344
+ result.push(segment);
345
+ }
346
+ return result.length ? result.join("/") : void 0;
427
347
  }
428
348
  function getRoutePathConflictErrorMessage(pathname, routes) {
429
- let [taken, ...others] = routes;
430
- if (!pathname.startsWith("/")) {
431
- pathname = "/" + pathname;
432
- }
433
- return `\u26A0\uFE0F Route Path Collision: "${pathname}"
434
-
435
- The following routes all define the same URL, only the first one will be used
436
-
437
- \u{1F7E2} ${taken}
438
- ` + others.map((route) => `\u2B55\uFE0F\uFE0F ${route}`).join("\n") + "\n";
349
+ let [taken, ...others] = routes;
350
+ if (!pathname.startsWith("/")) pathname = "/" + pathname;
351
+ return `⚠️ Route Path Collision: "${pathname}"\n\nThe following routes all define the same URL, only the first one will be used\n\n🟢 ${taken}\n` + others.map((route) => `⭕️️ ${route}`).join("\n") + "\n";
439
352
  }
440
353
  function getRouteIdConflictErrorMessage(routeId, files) {
441
- let [taken, ...others] = files;
442
- return `\u26A0\uFE0F Route ID Collision: "${routeId}"
443
-
444
- The following routes all define the same Route ID, only the first one will be used
445
-
446
- \u{1F7E2} ${taken}
447
- ` + others.map((route) => `\u2B55\uFE0F\uFE0F ${route}`).join("\n") + "\n";
354
+ let [taken, ...others] = files;
355
+ return `⚠️ Route ID Collision: "${routeId}"\n\nThe following routes all define the same Route ID, only the first one will be used\n\n🟢 ${taken}\n` + others.map((route) => `⭕️️ ${route}`).join("\n") + "\n";
448
356
  }
449
357
  function isSegmentSeparator(checkChar) {
450
- if (!checkChar) return false;
451
- return ["/", ".", import_node_path2.default.win32.sep].includes(checkChar);
358
+ if (!checkChar) return false;
359
+ return [
360
+ "/",
361
+ ".",
362
+ path.win32.sep
363
+ ].includes(checkChar);
452
364
  }
453
365
  function findFile(dir, basename, extensions) {
454
- for (let ext of extensions) {
455
- let name = basename + ext;
456
- let file = import_node_path2.default.join(dir, name);
457
- if (import_node_fs.default.existsSync(file)) return file;
458
- }
459
- return void 0;
366
+ for (let ext of extensions) {
367
+ let name = basename + ext;
368
+ let file = path.join(dir, name);
369
+ if (fs.existsSync(file)) return file;
370
+ }
460
371
  }
461
-
462
- // index.ts
463
- async function flatRoutes2(options = {}) {
464
- let { ignoredRouteFiles = [], rootDirectory: userRootDirectory = "routes" } = options;
465
- let appDirectory = (0, import_routes.getAppDirectory)();
466
- let rootDirectory = import_node_path3.default.resolve(appDirectory, userRootDirectory);
467
- let relativeRootDirectory = import_node_path3.default.relative(appDirectory, rootDirectory);
468
- let prefix = normalizeSlashes(relativeRootDirectory);
469
- let routes = import_node_fs2.default.existsSync(rootDirectory) ? flatRoutes(appDirectory, ignoredRouteFiles, prefix) : {};
470
- return routeManifestToRouteConfig(routes);
372
+ //#endregion
373
+ //#region index.ts
374
+ /**
375
+ * Creates route config from the file system using a convention that matches
376
+ * [Remix v2's route file
377
+ * naming](https://v2.remix.run/docs/file-conventions/routes), for use
378
+ * within `routes.ts`.
379
+ */
380
+ async function flatRoutes(options = {}) {
381
+ let { ignoredRouteFiles = [], rootDirectory: userRootDirectory = "routes" } = options;
382
+ let appDirectory = getAppDirectory();
383
+ let rootDirectory = path.resolve(appDirectory, userRootDirectory);
384
+ let prefix = normalizeSlashes(path.relative(appDirectory, rootDirectory));
385
+ return routeManifestToRouteConfig(fs.existsSync(rootDirectory) ? flatRoutes$1(appDirectory, ignoredRouteFiles, prefix) : {});
471
386
  }
472
- // Annotate the CommonJS export names for ESM import in node:
473
- 0 && (module.exports = {
474
- flatRoutes
475
- });
387
+ //#endregion
388
+ export { flatRoutes };
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@react-router/fs-routes",
3
- "version": "7.16.0",
3
+ "type": "module",
4
+ "version": "8.0.0-pre.0",
4
5
  "description": "File system routing conventions for React Router, for use within routes.ts",
5
6
  "bugs": {
6
7
  "url": "https://github.com/remix-run/react-router/issues"
@@ -22,7 +23,7 @@
22
23
  },
23
24
  "wireit": {
24
25
  "build": {
25
- "command": "tsup",
26
+ "command": "tsdown",
26
27
  "files": [
27
28
  "../../pnpm-workspace.yaml",
28
29
  "*.ts",
@@ -35,17 +36,18 @@
35
36
  }
36
37
  },
37
38
  "dependencies": {
38
- "minimatch": "^9.0.0"
39
+ "minimatch": "^10.2.5"
39
40
  },
40
41
  "devDependencies": {
41
- "tsup": "^8.3.0",
42
- "typescript": "^5.4.5",
43
- "wireit": "0.14.9",
44
- "@react-router/dev": "7.16.0"
42
+ "@types/node": "^22.19.19",
43
+ "tsdown": "^0.22.0",
44
+ "typescript": "^6.0.3",
45
+ "wireit": "0.14.12",
46
+ "@react-router/dev": "8.0.0-pre.0"
45
47
  },
46
48
  "peerDependencies": {
47
49
  "typescript": "^5.1.0 || ^6.0.0",
48
- "@react-router/dev": "^7.16.0"
50
+ "@react-router/dev": "^8.0.0-pre.0"
49
51
  },
50
52
  "peerDependenciesMeta": {
51
53
  "typescript": {
@@ -53,7 +55,7 @@
53
55
  }
54
56
  },
55
57
  "engines": {
56
- "node": ">=20.0.0"
58
+ "node": ">=22.12.0"
57
59
  },
58
60
  "files": [
59
61
  "dist/",