@react-router/fs-routes 0.0.0-experimental-a26b992a1

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/LICENSE.md ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) React Training LLC 2015-2019
4
+ Copyright (c) Remix Software Inc. 2020-2021
5
+ Copyright (c) Shopify Inc. 2022-2023
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @react-router/fs-routes
2
+
3
+ File system routing conventions for [React Router.](https://github.com/remix-run/react-router)
4
+
5
+ ```sh
6
+ npm i @react-router/fs-routes
7
+ ```
@@ -0,0 +1,14 @@
1
+ import type { RouteManifest } from "./manifest";
2
+ export declare const routeModuleExts: string[];
3
+ export declare let paramPrefixChar: "$";
4
+ export declare let escapeStart: "[";
5
+ export declare let escapeEnd: "]";
6
+ export declare let optionalStart: "(";
7
+ export declare let optionalEnd: ")";
8
+ export declare function flatRoutes(appDirectory: string, ignoredFilePatterns?: string[], prefix?: string): RouteManifest;
9
+ export declare function flatRoutesUniversal(appDirectory: string, routes: string[], prefix?: string): RouteManifest;
10
+ export declare function getRouteSegments(routeId: string): [string[], string[]];
11
+ export declare function createRoutePath(routeSegments: string[], rawRouteSegments: string[], isIndex?: boolean): string | undefined;
12
+ export declare function getRoutePathConflictErrorMessage(pathname: string, routes: string[]): string;
13
+ export declare function getRouteIdConflictErrorMessage(routeId: string, files: string[]): string;
14
+ export declare function isSegmentSeparator(checkChar: string | undefined): boolean;
@@ -0,0 +1,425 @@
1
+ /**
2
+ * @react-router/fs-routes v0.0.0-experimental-a26b992a1
3
+ *
4
+ * Copyright (c) Remix Software Inc.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ 'use strict';
12
+
13
+ Object.defineProperty(exports, '__esModule', { value: true });
14
+
15
+ var fs = require('node:fs');
16
+ var path = require('node:path');
17
+ var minimatch = require('minimatch');
18
+ var normalizeSlashes = require('./normalizeSlashes.js');
19
+
20
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
21
+
22
+ var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
23
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
24
+
25
+ const routeModuleExts = [".js", ".jsx", ".ts", ".tsx", ".md", ".mdx"];
26
+ let paramPrefixChar = "$";
27
+ let escapeStart = "[";
28
+ let escapeEnd = "]";
29
+ let optionalStart = "(";
30
+ let optionalEnd = ")";
31
+ const PrefixLookupTrieEndSymbol = Symbol("PrefixLookupTrieEndSymbol");
32
+ class PrefixLookupTrie {
33
+ root = {
34
+ [PrefixLookupTrieEndSymbol]: false
35
+ };
36
+ add(value) {
37
+ if (!value) throw new Error("Cannot add empty string to PrefixLookupTrie");
38
+ let node = this.root;
39
+ for (let char of value) {
40
+ if (!node[char]) {
41
+ node[char] = {
42
+ [PrefixLookupTrieEndSymbol]: false
43
+ };
44
+ }
45
+ node = node[char];
46
+ }
47
+ node[PrefixLookupTrieEndSymbol] = true;
48
+ }
49
+ findAndRemove(prefix, filter) {
50
+ let node = this.root;
51
+ for (let char of prefix) {
52
+ if (!node[char]) return [];
53
+ node = node[char];
54
+ }
55
+ return this.#findAndRemoveRecursive([], node, prefix, filter);
56
+ }
57
+ #findAndRemoveRecursive(values, node, prefix, filter) {
58
+ for (let char of Object.keys(node)) {
59
+ this.#findAndRemoveRecursive(values, node[char], prefix + char, filter);
60
+ }
61
+ if (node[PrefixLookupTrieEndSymbol] && filter(prefix)) {
62
+ node[PrefixLookupTrieEndSymbol] = false;
63
+ values.push(prefix);
64
+ }
65
+ return values;
66
+ }
67
+ }
68
+ function flatRoutes(appDirectory, ignoredFilePatterns = [], prefix = "routes") {
69
+ let ignoredFileRegex = Array.from(new Set(["**/.*", ...ignoredFilePatterns])).map(re => minimatch.makeRe(re)).filter(re => !!re);
70
+ let routesDir = path__default["default"].join(appDirectory, prefix);
71
+ let rootRoute = findFile(appDirectory, "root", routeModuleExts);
72
+ if (!rootRoute) {
73
+ throw new Error(`Could not find a root route module in the app directory: ${appDirectory}`);
74
+ }
75
+ if (!fs__default["default"].existsSync(rootRoute)) {
76
+ throw new Error(`Could not find the routes directory: ${routesDir}. Did you forget to create it?`);
77
+ }
78
+ // Only read the routes directory
79
+ let entries = fs__default["default"].readdirSync(routesDir, {
80
+ withFileTypes: true,
81
+ encoding: "utf-8"
82
+ });
83
+ let routes = [];
84
+ for (let entry of entries) {
85
+ let filepath = normalizeSlashes.normalizeSlashes(path__default["default"].join(routesDir, entry.name));
86
+ let route = null;
87
+ // If it's a directory, don't recurse into it, instead just look for a route module
88
+ if (entry.isDirectory()) {
89
+ route = findRouteModuleForFolder(appDirectory, filepath, ignoredFileRegex);
90
+ } else if (entry.isFile()) {
91
+ route = findRouteModuleForFile(appDirectory, filepath, ignoredFileRegex);
92
+ }
93
+ if (route) routes.push(route);
94
+ }
95
+ let routeManifest = flatRoutesUniversal(appDirectory, routes, prefix);
96
+ return routeManifest;
97
+ }
98
+ function flatRoutesUniversal(appDirectory, routes, prefix = "routes") {
99
+ let urlConflicts = new Map();
100
+ let routeManifest = {};
101
+ let prefixLookup = new PrefixLookupTrie();
102
+ let uniqueRoutes = new Map();
103
+ let routeIdConflicts = new Map();
104
+ // id -> file
105
+ let routeIds = new Map();
106
+ for (let file of routes) {
107
+ let normalizedFile = normalizeSlashes.normalizeSlashes(file);
108
+ let routeExt = path__default["default"].extname(normalizedFile);
109
+ let routeDir = path__default["default"].dirname(normalizedFile);
110
+ let normalizedApp = normalizeSlashes.normalizeSlashes(appDirectory);
111
+ let routeId = routeDir === path__default["default"].posix.join(normalizedApp, prefix) ? path__default["default"].posix.relative(normalizedApp, normalizedFile).slice(0, -routeExt.length) : path__default["default"].posix.relative(normalizedApp, routeDir);
112
+ let conflict = routeIds.get(routeId);
113
+ if (conflict) {
114
+ let currentConflicts = routeIdConflicts.get(routeId);
115
+ if (!currentConflicts) {
116
+ currentConflicts = [path__default["default"].posix.relative(normalizedApp, conflict)];
117
+ }
118
+ currentConflicts.push(path__default["default"].posix.relative(normalizedApp, normalizedFile));
119
+ routeIdConflicts.set(routeId, currentConflicts);
120
+ continue;
121
+ }
122
+ routeIds.set(routeId, normalizedFile);
123
+ }
124
+ let sortedRouteIds = Array.from(routeIds).sort(([a], [b]) => b.length - a.length);
125
+ for (let [routeId, file] of sortedRouteIds) {
126
+ let index = routeId.endsWith("_index");
127
+ let [segments, raw] = getRouteSegments(routeId.slice(prefix.length + 1));
128
+ let pathname = createRoutePath(segments, raw, index);
129
+ routeManifest[routeId] = {
130
+ file: file.slice(appDirectory.length + 1),
131
+ id: routeId,
132
+ path: pathname
133
+ };
134
+ if (index) routeManifest[routeId].index = true;
135
+ let childRouteIds = prefixLookup.findAndRemove(routeId, value => {
136
+ return [".", "/"].includes(value.slice(routeId.length).charAt(0));
137
+ });
138
+ prefixLookup.add(routeId);
139
+ if (childRouteIds.length > 0) {
140
+ for (let childRouteId of childRouteIds) {
141
+ routeManifest[childRouteId].parentId = routeId;
142
+ }
143
+ }
144
+ }
145
+ // path creation
146
+ let parentChildrenMap = new Map();
147
+ for (let [routeId] of sortedRouteIds) {
148
+ let config = routeManifest[routeId];
149
+ if (!config.parentId) continue;
150
+ let existingChildren = parentChildrenMap.get(config.parentId) || [];
151
+ existingChildren.push(config);
152
+ parentChildrenMap.set(config.parentId, existingChildren);
153
+ }
154
+ for (let [routeId] of sortedRouteIds) {
155
+ let config = routeManifest[routeId];
156
+ let originalPathname = config.path || "";
157
+ let pathname = config.path;
158
+ let parentConfig = config.parentId ? routeManifest[config.parentId] : null;
159
+ if (parentConfig?.path && pathname) {
160
+ pathname = pathname.slice(parentConfig.path.length).replace(/^\//, "").replace(/\/$/, "");
161
+ }
162
+ if (!config.parentId) config.parentId = "root";
163
+ config.path = pathname || undefined;
164
+ /**
165
+ * We do not try to detect path collisions for pathless layout route
166
+ * files because, by definition, they create the potential for route
167
+ * collisions _at that level in the tree_.
168
+ *
169
+ * Consider example where a user may want multiple pathless layout routes
170
+ * for different subfolders
171
+ *
172
+ * routes/
173
+ * account.tsx
174
+ * account._private.tsx
175
+ * account._private.orders.tsx
176
+ * account._private.profile.tsx
177
+ * account._public.tsx
178
+ * account._public.login.tsx
179
+ * account._public.perks.tsx
180
+ *
181
+ * In order to support both a public and private layout for `/account/*`
182
+ * URLs, we are creating a mutually exclusive set of URLs beneath 2
183
+ * separate pathless layout routes. In this case, the route paths for
184
+ * both account._public.tsx and account._private.tsx is the same
185
+ * (/account), but we're again not expecting to match at that level.
186
+ *
187
+ * By only ignoring this check when the final portion of the filename is
188
+ * pathless, we will still detect path collisions such as:
189
+ *
190
+ * routes/parent._pathless.foo.tsx
191
+ * routes/parent._pathless2.foo.tsx
192
+ *
193
+ * and
194
+ *
195
+ * routes/parent._pathless/index.tsx
196
+ * routes/parent._pathless2/index.tsx
197
+ */
198
+ let lastRouteSegment = config.id.replace(new RegExp(`^${prefix}/`), "").split(".").pop();
199
+ let isPathlessLayoutRoute = lastRouteSegment && lastRouteSegment.startsWith("_") && lastRouteSegment !== "_index";
200
+ if (isPathlessLayoutRoute) {
201
+ continue;
202
+ }
203
+ let conflictRouteId = originalPathname + (config.index ? "?index" : "");
204
+ let conflict = uniqueRoutes.get(conflictRouteId);
205
+ uniqueRoutes.set(conflictRouteId, config);
206
+ if (conflict && (originalPathname || config.index)) {
207
+ let currentConflicts = urlConflicts.get(originalPathname);
208
+ if (!currentConflicts) currentConflicts = [conflict];
209
+ currentConflicts.push(config);
210
+ urlConflicts.set(originalPathname, currentConflicts);
211
+ continue;
212
+ }
213
+ }
214
+ if (routeIdConflicts.size > 0) {
215
+ for (let [routeId, files] of routeIdConflicts.entries()) {
216
+ console.error(getRouteIdConflictErrorMessage(routeId, files));
217
+ }
218
+ }
219
+ // report conflicts
220
+ if (urlConflicts.size > 0) {
221
+ for (let [path, routes] of urlConflicts.entries()) {
222
+ // delete all but the first route from the manifest
223
+ for (let i = 1; i < routes.length; i++) {
224
+ delete routeManifest[routes[i].id];
225
+ }
226
+ let files = routes.map(r => r.file);
227
+ console.error(getRoutePathConflictErrorMessage(path, files));
228
+ }
229
+ }
230
+ return routeManifest;
231
+ }
232
+ function findRouteModuleForFile(appDirectory, filepath, ignoredFileRegex) {
233
+ let relativePath = normalizeSlashes.normalizeSlashes(path__default["default"].relative(appDirectory, filepath));
234
+ let isIgnored = ignoredFileRegex.some(regex => regex.test(relativePath));
235
+ if (isIgnored) return null;
236
+ return filepath;
237
+ }
238
+ function findRouteModuleForFolder(appDirectory, filepath, ignoredFileRegex) {
239
+ let relativePath = path__default["default"].relative(appDirectory, filepath);
240
+ let isIgnored = ignoredFileRegex.some(regex => regex.test(relativePath));
241
+ if (isIgnored) return null;
242
+ let routeRouteModule = findFile(filepath, "route", routeModuleExts);
243
+ let routeIndexModule = findFile(filepath, "index", routeModuleExts);
244
+ // if both a route and index module exist, throw a conflict error
245
+ // preferring the route module over the index module
246
+ if (routeRouteModule && routeIndexModule) {
247
+ let [segments, raw] = getRouteSegments(path__default["default"].relative(appDirectory, filepath));
248
+ let routePath = createRoutePath(segments, raw, false);
249
+ console.error(getRoutePathConflictErrorMessage(routePath || "/", [routeRouteModule, routeIndexModule]));
250
+ }
251
+ return routeRouteModule || routeIndexModule || null;
252
+ }
253
+ function getRouteSegments(routeId) {
254
+ let routeSegments = [];
255
+ let rawRouteSegments = [];
256
+ let index = 0;
257
+ let routeSegment = "";
258
+ let rawRouteSegment = "";
259
+ let state = "NORMAL";
260
+ let pushRouteSegment = (segment, rawSegment) => {
261
+ if (!segment) return;
262
+ let notSupportedInRR = (segment, char) => {
263
+ throw new Error(`Route segment "${segment}" for "${routeId}" cannot contain "${char}".\n` + `If this is something you need, upvote this proposal for React Router https://github.com/remix-run/react-router/discussions/9822.`);
264
+ };
265
+ if (rawSegment.includes("*")) {
266
+ return notSupportedInRR(rawSegment, "*");
267
+ }
268
+ if (rawSegment.includes(":")) {
269
+ return notSupportedInRR(rawSegment, ":");
270
+ }
271
+ if (rawSegment.includes("/")) {
272
+ return notSupportedInRR(segment, "/");
273
+ }
274
+ routeSegments.push(segment);
275
+ rawRouteSegments.push(rawSegment);
276
+ };
277
+ while (index < routeId.length) {
278
+ let char = routeId[index];
279
+ index++; //advance to next char
280
+ switch (state) {
281
+ case "NORMAL":
282
+ {
283
+ if (isSegmentSeparator(char)) {
284
+ pushRouteSegment(routeSegment, rawRouteSegment);
285
+ routeSegment = "";
286
+ rawRouteSegment = "";
287
+ state = "NORMAL";
288
+ break;
289
+ }
290
+ if (char === escapeStart) {
291
+ state = "ESCAPE";
292
+ rawRouteSegment += char;
293
+ break;
294
+ }
295
+ if (char === optionalStart) {
296
+ state = "OPTIONAL";
297
+ rawRouteSegment += char;
298
+ break;
299
+ }
300
+ if (!routeSegment && char === paramPrefixChar) {
301
+ if (index === routeId.length) {
302
+ routeSegment += "*";
303
+ rawRouteSegment += char;
304
+ } else {
305
+ routeSegment += ":";
306
+ rawRouteSegment += char;
307
+ }
308
+ break;
309
+ }
310
+ routeSegment += char;
311
+ rawRouteSegment += char;
312
+ break;
313
+ }
314
+ case "ESCAPE":
315
+ {
316
+ if (char === escapeEnd) {
317
+ state = "NORMAL";
318
+ rawRouteSegment += char;
319
+ break;
320
+ }
321
+ routeSegment += char;
322
+ rawRouteSegment += char;
323
+ break;
324
+ }
325
+ case "OPTIONAL":
326
+ {
327
+ if (char === optionalEnd) {
328
+ routeSegment += "?";
329
+ rawRouteSegment += char;
330
+ state = "NORMAL";
331
+ break;
332
+ }
333
+ if (char === escapeStart) {
334
+ state = "OPTIONAL_ESCAPE";
335
+ rawRouteSegment += char;
336
+ break;
337
+ }
338
+ if (!routeSegment && char === paramPrefixChar) {
339
+ if (index === routeId.length) {
340
+ routeSegment += "*";
341
+ rawRouteSegment += char;
342
+ } else {
343
+ routeSegment += ":";
344
+ rawRouteSegment += char;
345
+ }
346
+ break;
347
+ }
348
+ routeSegment += char;
349
+ rawRouteSegment += char;
350
+ break;
351
+ }
352
+ case "OPTIONAL_ESCAPE":
353
+ {
354
+ if (char === escapeEnd) {
355
+ state = "OPTIONAL";
356
+ rawRouteSegment += char;
357
+ break;
358
+ }
359
+ routeSegment += char;
360
+ rawRouteSegment += char;
361
+ break;
362
+ }
363
+ }
364
+ }
365
+ // process remaining segment
366
+ pushRouteSegment(routeSegment, rawRouteSegment);
367
+ return [routeSegments, rawRouteSegments];
368
+ }
369
+ function createRoutePath(routeSegments, rawRouteSegments, isIndex) {
370
+ let result = [];
371
+ if (isIndex) {
372
+ routeSegments = routeSegments.slice(0, -1);
373
+ }
374
+ for (let index = 0; index < routeSegments.length; index++) {
375
+ let segment = routeSegments[index];
376
+ let rawSegment = rawRouteSegments[index];
377
+ // skip pathless layout segments
378
+ if (segment.startsWith("_") && rawSegment.startsWith("_")) {
379
+ continue;
380
+ }
381
+ // remove trailing slash
382
+ if (segment.endsWith("_") && rawSegment.endsWith("_")) {
383
+ segment = segment.slice(0, -1);
384
+ }
385
+ result.push(segment);
386
+ }
387
+ return result.length ? result.join("/") : undefined;
388
+ }
389
+ function getRoutePathConflictErrorMessage(pathname, routes) {
390
+ let [taken, ...others] = routes;
391
+ if (!pathname.startsWith("/")) {
392
+ pathname = "/" + pathname;
393
+ }
394
+ return `⚠️ Route Path Collision: "${pathname}"\n\n` + `The 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";
395
+ }
396
+ function getRouteIdConflictErrorMessage(routeId, files) {
397
+ let [taken, ...others] = files;
398
+ return `⚠️ Route ID Collision: "${routeId}"\n\n` + `The 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";
399
+ }
400
+ function isSegmentSeparator(checkChar) {
401
+ if (!checkChar) return false;
402
+ return ["/", ".", path__default["default"].win32.sep].includes(checkChar);
403
+ }
404
+ function findFile(dir, basename, extensions) {
405
+ for (let ext of extensions) {
406
+ let name = basename + ext;
407
+ let file = path__default["default"].join(dir, name);
408
+ if (fs__default["default"].existsSync(file)) return file;
409
+ }
410
+ return undefined;
411
+ }
412
+
413
+ exports.createRoutePath = createRoutePath;
414
+ exports.escapeEnd = escapeEnd;
415
+ exports.escapeStart = escapeStart;
416
+ exports.flatRoutes = flatRoutes;
417
+ exports.flatRoutesUniversal = flatRoutesUniversal;
418
+ exports.getRouteIdConflictErrorMessage = getRouteIdConflictErrorMessage;
419
+ exports.getRoutePathConflictErrorMessage = getRoutePathConflictErrorMessage;
420
+ exports.getRouteSegments = getRouteSegments;
421
+ exports.isSegmentSeparator = isSegmentSeparator;
422
+ exports.optionalEnd = optionalEnd;
423
+ exports.optionalStart = optionalStart;
424
+ exports.paramPrefixChar = paramPrefixChar;
425
+ exports.routeModuleExts = routeModuleExts;
@@ -0,0 +1,19 @@
1
+ import { type RouteConfigEntry } from "@react-router/dev/routes";
2
+ /**
3
+ * Creates route config from the file system using a convention that matches
4
+ * [Remix v2's route file
5
+ * naming](https://remix.run/docs/en/v2/file-conventions/routes-files), for use
6
+ * within `routes.ts`.
7
+ */
8
+ export declare function flatRoutes(options?: {
9
+ /**
10
+ * An array of [minimatch](https://www.npmjs.com/package/minimatch) globs that match files to ignore.
11
+ * Defaults to `[]`.
12
+ */
13
+ ignoredRouteFiles?: string[];
14
+ /**
15
+ * The directory containing file system routes, relative to the app directory.
16
+ * Defaults to `"./routes"`.
17
+ */
18
+ rootDirectory?: string;
19
+ }): Promise<RouteConfigEntry[]>;
package/dist/index.js ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @react-router/fs-routes v0.0.0-experimental-a26b992a1
3
+ *
4
+ * Copyright (c) Remix Software Inc.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ 'use strict';
12
+
13
+ Object.defineProperty(exports, '__esModule', { value: true });
14
+
15
+ var fs = require('node:fs');
16
+ var path = require('node:path');
17
+ var routes = require('@react-router/dev/routes');
18
+ var manifest = require('./manifest.js');
19
+ var flatRoutes$1 = require('./flatRoutes.js');
20
+ var normalizeSlashes = require('./normalizeSlashes.js');
21
+
22
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
23
+
24
+ var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
25
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
26
+
27
+ /**
28
+ * Creates route config from the file system using a convention that matches
29
+ * [Remix v2's route file
30
+ * naming](https://remix.run/docs/en/v2/file-conventions/routes-files), for use
31
+ * within `routes.ts`.
32
+ */
33
+ async function flatRoutes(options = {}) {
34
+ let {
35
+ ignoredRouteFiles = [],
36
+ rootDirectory: userRootDirectory = "routes"
37
+ } = options;
38
+ let appDirectory = routes.getAppDirectory();
39
+ let rootDirectory = path__default["default"].resolve(appDirectory, userRootDirectory);
40
+ let relativeRootDirectory = path__default["default"].relative(appDirectory, rootDirectory);
41
+ let prefix = normalizeSlashes.normalizeSlashes(relativeRootDirectory);
42
+ let routes$1 = fs__default["default"].existsSync(rootDirectory) ? flatRoutes$1.flatRoutes(appDirectory, ignoredRouteFiles, prefix) : {};
43
+ return manifest.routeManifestToRouteConfig(routes$1);
44
+ }
45
+
46
+ exports.flatRoutes = flatRoutes;
@@ -0,0 +1,13 @@
1
+ import type { RouteConfigEntry } from "@react-router/dev/routes";
2
+ export interface RouteManifestEntry {
3
+ path?: string;
4
+ index?: boolean;
5
+ caseSensitive?: boolean;
6
+ id: string;
7
+ parentId?: string;
8
+ file: string;
9
+ }
10
+ export interface RouteManifest {
11
+ [routeId: string]: RouteManifestEntry;
12
+ }
13
+ export declare function routeManifestToRouteConfig(routeManifest: RouteManifest, rootId?: string): RouteConfigEntry[];
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @react-router/fs-routes v0.0.0-experimental-a26b992a1
3
+ *
4
+ * Copyright (c) Remix Software Inc.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ 'use strict';
12
+
13
+ Object.defineProperty(exports, '__esModule', { value: true });
14
+
15
+ function routeManifestToRouteConfig(routeManifest, rootId = "root") {
16
+ let routeConfigById = {};
17
+ for (let id in routeManifest) {
18
+ let route = routeManifest[id];
19
+ routeConfigById[id] = {
20
+ id: route.id,
21
+ file: route.file,
22
+ path: route.path,
23
+ index: route.index,
24
+ caseSensitive: route.caseSensitive
25
+ };
26
+ }
27
+ let routeConfig = [];
28
+ for (let id in routeConfigById) {
29
+ let route = routeConfigById[id];
30
+ let parentId = routeManifest[route.id].parentId;
31
+ if (parentId === rootId) {
32
+ 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;
42
+ }
43
+
44
+ exports.routeManifestToRouteConfig = routeManifestToRouteConfig;
@@ -0,0 +1 @@
1
+ export declare function normalizeSlashes(file: string): string;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @react-router/fs-routes v0.0.0-experimental-a26b992a1
3
+ *
4
+ * Copyright (c) Remix Software Inc.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ 'use strict';
12
+
13
+ Object.defineProperty(exports, '__esModule', { value: true });
14
+
15
+ var path = require('node:path');
16
+
17
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
18
+
19
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
20
+
21
+ function normalizeSlashes(file) {
22
+ return file.split(path__default["default"].win32.sep).join("/");
23
+ }
24
+
25
+ exports.normalizeSlashes = normalizeSlashes;
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@react-router/fs-routes",
3
+ "version": "0.0.0-experimental-a26b992a1",
4
+ "description": "File system routing conventions for React Router",
5
+ "bugs": {
6
+ "url": "https://github.com/remix-run/react-router/issues"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/remix-run/react-router",
11
+ "directory": "packages/react-router-fs-routes"
12
+ },
13
+ "license": "MIT",
14
+ "main": "dist/index.js",
15
+ "typings": "dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "dependencies": {
24
+ "minimatch": "^9.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "typescript": "^5.1.6",
28
+ "@react-router/dev": "0.0.0-experimental-a26b992a1"
29
+ },
30
+ "peerDependencies": {
31
+ "typescript": "^5.1.0",
32
+ "@react-router/dev": "^0.0.0-experimental-a26b992a1"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "typescript": {
36
+ "optional": true
37
+ }
38
+ },
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "files": [
43
+ "dist/",
44
+ "CHANGELOG.md",
45
+ "LICENSE.md",
46
+ "README.md"
47
+ ],
48
+ "scripts": {
49
+ "tsc": "tsc"
50
+ }
51
+ }