remix-flat-routes
Advanced tools
| { | ||
| "version": 3, | ||
| "sources": ["../src/cli.ts"], | ||
| "sourcesContent": ["#!/usr/bin/env node\n\nimport * as fs from 'fs'\nimport { migrate, MigrateOptions } from './migrate'\n\nmain()\n\nfunction main() {\n const argv = process.argv.slice(2)\n if (argv.length < 2) {\n usage()\n process.exit(1)\n }\n const sourceDir = argv[0]\n const targetDir = argv[1]\n\n if (sourceDir === targetDir) {\n console.error('source and target directories must be different')\n process.exit(1)\n }\n\n if (!fs.existsSync(sourceDir)) {\n console.error(`source directory '${sourceDir}' does not exist`)\n process.exit(1)\n }\n\n let options: MigrateOptions = { convention: 'flat-files' }\n\n for (let option of argv.slice(2)) {\n if (option.startsWith('--convention=')) {\n let convention = option.substring('--convention='.length)\n if (convention === 'flat-files' || convention === 'flat-folders') {\n options.convention = convention\n } else {\n usage()\n }\n } else {\n usage()\n }\n }\n\n migrate(sourceDir, targetDir, options)\n}\n\nfunction usage() {\n console.log(\n `Usage: migrate <sourceDir> <targetDir> [options]\n\nOptions:\n --convention=<convention>\n The convention to use when migrating.\n flat-files - Migrates all files to a flat directory structure.\n flat-folders - Migrates all files to a flat directory structure, but\n creates folders for each route.\n`,\n )\n}\n"], | ||
| "mappings": ";;;;;;;;;;;;;;;;AAEA,SAAoB;AACpB,qBAAwC;AAExC,KAAK;AAEL,gBAAgB;AACd,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,MAAI,KAAK,SAAS,GAAG;AACnB,UAAM;AACN,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,QAAM,YAAY,KAAK;AACvB,QAAM,YAAY,KAAK;AAEvB,MAAI,cAAc,WAAW;AAC3B,YAAQ,MAAM,iDAAiD;AAC/D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC7B,YAAQ,MAAM,qBAAqB,2BAA2B;AAC9D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,UAA0B,EAAE,YAAY,aAAa;AAEzD,WAAS,UAAU,KAAK,MAAM,CAAC,GAAG;AAChC,QAAI,OAAO,WAAW,eAAe,GAAG;AACtC,UAAI,aAAa,OAAO,UAAU,gBAAgB,MAAM;AACxD,UAAI,eAAe,gBAAgB,eAAe,gBAAgB;AAChE,gBAAQ,aAAa;AAAA,MACvB,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF,OAAO;AACL,YAAM;AAAA,IACR;AAAA,EACF;AAEA,8BAAQ,WAAW,WAAW,OAAO;AACvC;AAEA,iBAAiB;AACf,UAAQ,IACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CASF;AACF;", | ||
| "names": [] | ||
| } |
| { | ||
| "version": 3, | ||
| "sources": ["../src/index.ts"], | ||
| "sourcesContent": ["import * as path from 'path'\nimport { visitFiles } from './util'\n\ntype RouteInfo = {\n path: string\n file: string\n name: string\n parent: string\n isIndex: boolean\n}\n\ntype DefineRouteOptions = {\n caseSensitive?: boolean\n index?: boolean\n}\n\ntype DefineRouteChildren = {\n (): void\n}\n\ntype DefineRouteFunction = (\n path: string | undefined,\n file: string,\n optionsOrChildren?: DefineRouteOptions | DefineRouteChildren,\n children?: DefineRouteChildren,\n) => void\n\nexport type VisitFilesFunction = (\n dir: string,\n visitor: (file: string) => void,\n baseDir?: string,\n) => void\n\ntype FlatRoutesOptions = {\n basePath?: string\n}\n\ntype ParentMapEntry = {\n routeInfo: RouteInfo\n children: RouteInfo[]\n}\n\nexport type DefineRoutesFunction = (\n callback: (route: DefineRouteFunction) => void,\n) => any\n\nexport default function flatRoutes(\n baseDir: string,\n defineRoutes: DefineRoutesFunction,\n options: FlatRoutesOptions = {},\n) {\n const routeMap = new Map<string, RouteInfo>()\n const parentMap = new Map<string, ParentMapEntry>()\n\n // initialize root route\n routeMap.set('root', {\n path: '',\n file: 'root.tsx',\n name: 'root',\n parent: '',\n isIndex: false,\n })\n var routes = defineRoutes(route => {\n visitFiles(`app/${baseDir}`, routeFile => {\n const routeInfo = getRouteInfo(baseDir, routeFile, options.basePath)\n if (!routeInfo) return\n routeMap.set(routeInfo.name, routeInfo)\n })\n // setup parent map\n for (let [_name, route] of routeMap) {\n let parentRoute = route.parent\n if (parentRoute) {\n let parent = parentMap.get(parentRoute)\n if (!parent) {\n parent = {\n routeInfo: routeMap.get(parentRoute)!,\n children: [],\n }\n parentMap.set(parentRoute, parent)\n }\n parent.children.push(route)\n }\n }\n // start with root\n getRoutes(parentMap, 'root', route)\n })\n // don't return root since remix already provides it\n delete routes.root\n return routes\n}\n\nfunction getRoutes(\n parentMap: Map<string, ParentMapEntry>,\n parent: string,\n route: DefineRouteFunction,\n) {\n let parentRoute = parentMap.get(parent)\n if (parentRoute && parentRoute.children) {\n const routeOptions: DefineRouteOptions = {\n caseSensitive: false,\n index: parentRoute!.routeInfo.isIndex,\n }\n const routeChildren: DefineRouteChildren = () => {\n for (let child of parentRoute!.children) {\n getRoutes(parentMap, child.name, route)\n const path = child.path.substring(\n parentRoute!.routeInfo.path.length + 1,\n )\n route(path, child.file, { index: child.isIndex })\n }\n }\n route(\n parentRoute.routeInfo.path,\n parentRoute.routeInfo.file,\n routeOptions,\n routeChildren,\n )\n }\n}\n\nexport function getRouteInfo(\n baseDir: string,\n routeFile: string,\n basePath?: string,\n): RouteInfo | null {\n let state = 'START'\n let subState = 'NORMAL'\n let parentState = 'APPEND'\n let url = basePath ?? ''\n let parent = ''\n let isIndex = false\n // get extension\n let ext = path.extname(routeFile)\n // only process valid route files\n if (!['.js', '.jsx', '.ts', '.tsx'].includes(ext)) {\n return null\n }\n // remove extension from name\n let name = routeFile.substring(0, routeFile.length - ext.length)\n // route module so only process index routes\n if (routeFile.includes('/')) {\n if (\n !name.endsWith('/index') &&\n !name.endsWith('/_layout') &&\n !name.endsWith('/_route')\n ) {\n return null\n }\n name = path.dirname(routeFile)\n isIndex = name.endsWith('/index') || name.endsWith('.index')\n }\n let index = 0\n let pathSegment = ''\n let routeSegment = ''\n while (index < name.length) {\n let char = name[index]\n switch (state) {\n case 'START':\n // process existing segment\n url = appendPathSegment(url, pathSegment)\n\n if (routeSegment.endsWith('_')) {\n parentState = 'IGNORE'\n }\n if (parentState === 'APPEND') {\n if (parent) {\n parent += '.'\n }\n parent += routeSegment\n }\n if (routeSegment === 'index') isIndex = true\n pathSegment = '' // reset segment\n routeSegment = ''\n state = 'PATH'\n continue // restart without advancing index\n case 'PATH':\n if (isPathSeparator(char) && subState === 'NORMAL') {\n state = 'START'\n break\n } else if (char === '$') {\n pathSegment += ':'\n } else if (char === '[') {\n subState = 'ESCAPE'\n } else if (char === ']') {\n subState = 'NORMAL'\n } else {\n pathSegment += char\n }\n routeSegment += char\n\n break\n }\n index++ // advance to next character\n }\n if (routeSegment === 'index') isIndex = true\n url = appendPathSegment(url, pathSegment)\n return {\n path: url,\n file: `${baseDir}/${routeFile}`,\n name,\n parent: parent || 'root',\n isIndex,\n }\n}\n\nfunction appendPathSegment(url: string, segment: string) {\n if (segment) {\n if (segment.startsWith('_')) {\n return url\n }\n if (segment === 'index') {\n if (!url.endsWith('/')) {\n url += '/'\n }\n } else if (segment === ':' || segment === ':_') {\n url += '/*'\n } else if (segment !== 'route') {\n // strip trailing underscore\n if (segment.endsWith('_')) {\n segment = segment.slice(0, -1)\n }\n url += '/' + segment\n }\n }\n return url\n}\n\nfunction isPathSeparator(char: string) {\n return char === '/' || char === path.win32.sep || char === '.'\n}\n\nexport { flatRoutes }\nexport type {\n DefineRouteFunction,\n DefineRouteOptions,\n DefineRouteChildren,\n RouteInfo,\n}\n"], | ||
| "mappings": "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", | ||
| "names": [] | ||
| } |
| { | ||
| "version": 3, | ||
| "sources": ["../src/migrate.ts"], | ||
| "sourcesContent": ["import * as fs from 'fs'\nimport * as path from 'path'\nimport { visitFiles } from './util'\n\nexport type RoutingConvention = 'flat-files' | 'flat-folders'\nexport type MigrateOptions = {\n convention: RoutingConvention\n}\nexport function migrate(\n sourceDir: string,\n targetDir: string,\n options: MigrateOptions = { convention: 'flat-files' },\n) {\n var visitor = options.convention === 'flat-files' ? flatFiles : flatFolders\n\n visitFiles(sourceDir, visitor(sourceDir, targetDir))\n}\n\nexport function flatFiles(sourceDir: string, targetDir: string) {\n return (file: string) => {\n console.log(file)\n let extension = path.extname(file)\n let name = file.substring(0, file.length - extension.length)\n const route = convertToRoute(name)\n const targetFile = `${targetDir}/${route}${extension}`\n fs.cpSync(`${sourceDir}/${file}`, targetFile, { force: true })\n }\n}\n\nconst routeExtensions = ['.js', '.jsx', '.ts', '.tsx']\nexport function flatFolders(sourceDir: string, targetDir: string) {\n return (file: string) => {\n console.log(file)\n const extension = path.extname(file)\n const name = file.substring(0, file.length - extension.length)\n const route = convertToRoute(name)\n const targetFolder = `${targetDir}/${route}`\n if (!routeExtensions.includes(extension)) {\n return\n }\n fs.mkdirSync(targetFolder, { recursive: true })\n fs.cpSync(`${sourceDir}/${file}`, `${targetFolder}/index${extension}`, {\n force: true,\n })\n }\n}\n\nexport function convertToRoute(file: string) {\n const pathSegments = file.split('/')\n const parent =\n pathSegments.length == 1 ? '' : pathSegments.slice(0, -1).join('/')\n const route = pathSegments.slice(-1)[0]\n const routeSegments = route.split('.')\n\n if (routeSegments.length > 1) {\n routeSegments[0] = `${routeSegments[0]}_`\n }\n\n return `${getFlatRoute(parent.split('/'))}${\n parent === '' ? '' : '.'\n }${getFlatRoute(routeSegments)}`\n}\n\nfunction getFlatRoute(segments: string[]) {\n return segments\n .map(segment => (segment.startsWith('__') ? segment.substring(1) : segment))\n .join('.')\n}\n"], | ||
| "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAoB;AACpB,WAAsB;AACtB,kBAA2B;AAMpB,iBACL,WACA,WACA,UAA0B,EAAE,YAAY,aAAa,GACrD;AACA,MAAI,UAAU,QAAQ,eAAe,eAAe,YAAY;AAEhE,8BAAW,WAAW,QAAQ,WAAW,SAAS,CAAC;AACrD;AAEO,mBAAmB,WAAmB,WAAmB;AAC9D,SAAO,CAAC,SAAiB;AACvB,YAAQ,IAAI,IAAI;AAChB,QAAI,YAAY,KAAK,QAAQ,IAAI;AACjC,QAAI,OAAO,KAAK,UAAU,GAAG,KAAK,SAAS,UAAU,MAAM;AAC3D,UAAM,QAAQ,eAAe,IAAI;AACjC,UAAM,aAAa,GAAG,aAAa,QAAQ;AAC3C,OAAG,OAAO,GAAG,aAAa,QAAQ,YAAY,EAAE,OAAO,KAAK,CAAC;AAAA,EAC/D;AACF;AAEA,MAAM,kBAAkB,CAAC,OAAO,QAAQ,OAAO,MAAM;AAC9C,qBAAqB,WAAmB,WAAmB;AAChE,SAAO,CAAC,SAAiB;AACvB,YAAQ,IAAI,IAAI;AAChB,UAAM,YAAY,KAAK,QAAQ,IAAI;AACnC,UAAM,OAAO,KAAK,UAAU,GAAG,KAAK,SAAS,UAAU,MAAM;AAC7D,UAAM,QAAQ,eAAe,IAAI;AACjC,UAAM,eAAe,GAAG,aAAa;AACrC,QAAI,CAAC,gBAAgB,SAAS,SAAS,GAAG;AACxC;AAAA,IACF;AACA,OAAG,UAAU,cAAc,EAAE,WAAW,KAAK,CAAC;AAC9C,OAAG,OAAO,GAAG,aAAa,QAAQ,GAAG,qBAAqB,aAAa;AAAA,MACrE,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;AAEO,wBAAwB,MAAc;AAC3C,QAAM,eAAe,KAAK,MAAM,GAAG;AACnC,QAAM,SACJ,aAAa,UAAU,IAAI,KAAK,aAAa,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG;AACpE,QAAM,QAAQ,aAAa,MAAM,EAAE,EAAE;AACrC,QAAM,gBAAgB,MAAM,MAAM,GAAG;AAErC,MAAI,cAAc,SAAS,GAAG;AAC5B,kBAAc,KAAK,GAAG,cAAc;AAAA,EACtC;AAEA,SAAO,GAAG,aAAa,OAAO,MAAM,GAAG,CAAC,IACtC,WAAW,KAAK,KAAK,MACpB,aAAa,aAAa;AAC/B;AAEA,sBAAsB,UAAoB;AACxC,SAAO,SACJ,IAAI,aAAY,QAAQ,WAAW,IAAI,IAAI,QAAQ,UAAU,CAAC,IAAI,OAAQ,EAC1E,KAAK,GAAG;AACb;", | ||
| "names": [] | ||
| } |
| { | ||
| "version": 3, | ||
| "sources": ["../src/util.ts"], | ||
| "sourcesContent": ["import * as fs from 'fs'\nimport * as path from 'path'\nimport type { VisitFilesFunction } from './index'\n\nexport const visitFiles: VisitFilesFunction = (\n dir: string,\n visitor: (file: string) => void,\n baseDir = dir,\n) => {\n for (let filename of fs.readdirSync(dir)) {\n let file = path.resolve(dir, filename)\n let stat = fs.lstatSync(file)\n\n if (stat.isDirectory()) {\n visitFiles(file, visitor, baseDir)\n } else if (stat.isFile()) {\n visitor(path.relative(baseDir, file))\n }\n }\n}\n"], | ||
| "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAoB;AACpB,WAAsB;AAGf,MAAM,aAAiC,CAC5C,KACA,SACA,UAAU,QACP;AACH,WAAS,YAAY,GAAG,YAAY,GAAG,GAAG;AACxC,QAAI,OAAO,KAAK,QAAQ,KAAK,QAAQ;AACrC,QAAI,OAAO,GAAG,UAAU,IAAI;AAE5B,QAAI,KAAK,YAAY,GAAG;AACtB,iBAAW,MAAM,SAAS,OAAO;AAAA,IACnC,WAAW,KAAK,OAAO,GAAG;AACxB,cAAQ,KAAK,SAAS,SAAS,IAAI,CAAC;AAAA,IACtC;AAAA,EACF;AACF;", | ||
| "names": [] | ||
| } |
+1
-0
@@ -62,1 +62,2 @@ #!/usr/bin/env node | ||
| } | ||
| //# sourceMappingURL=cli.js.map |
+22
-12
@@ -25,3 +25,3 @@ var __create = Object.create; | ||
| flatRoutes: () => flatRoutes, | ||
| parseRouteFile: () => parseRouteFile | ||
| getRouteInfo: () => getRouteInfo | ||
| }); | ||
@@ -31,3 +31,3 @@ module.exports = __toCommonJS(src_exports); | ||
| var import_util = require("./util"); | ||
| function flatRoutes(baseDir, defineRoutes) { | ||
| function flatRoutes(baseDir, defineRoutes, options = {}) { | ||
| const routeMap = /* @__PURE__ */ new Map(); | ||
@@ -37,10 +37,13 @@ const parentMap = /* @__PURE__ */ new Map(); | ||
| path: "", | ||
| file: "root.tsx" | ||
| file: "root.tsx", | ||
| name: "root", | ||
| parent: "", | ||
| isIndex: false | ||
| }); | ||
| var routes = defineRoutes((route) => { | ||
| (0, import_util.visitFiles)(`app/${baseDir}`, (routeFile) => { | ||
| const parsed = parseRouteFile(baseDir, routeFile); | ||
| if (!parsed) | ||
| const routeInfo = getRouteInfo(baseDir, routeFile, options.basePath); | ||
| if (!routeInfo) | ||
| return; | ||
| routeMap.set(parsed.name, parsed); | ||
| routeMap.set(routeInfo.name, routeInfo); | ||
| }); | ||
@@ -53,3 +56,3 @@ for (let [_name, route2] of routeMap) { | ||
| parent = { | ||
| parsed: routeMap.get(parentRoute), | ||
| routeInfo: routeMap.get(parentRoute), | ||
| children: [] | ||
@@ -70,15 +73,21 @@ }; | ||
| if (parentRoute && parentRoute.children) { | ||
| route(parentRoute.parsed.path, parentRoute.parsed.file, () => { | ||
| const routeOptions = { | ||
| caseSensitive: false, | ||
| index: parentRoute.routeInfo.isIndex | ||
| }; | ||
| const routeChildren = () => { | ||
| for (let child of parentRoute.children) { | ||
| getRoutes(parentMap, child.name, route); | ||
| route(child.path.substring(parentRoute.parsed.path.length + 1), child.file, { index: child.isIndex }); | ||
| const path2 = child.path.substring(parentRoute.routeInfo.path.length + 1); | ||
| route(path2, child.file, { index: child.isIndex }); | ||
| } | ||
| }, { index: parentRoute.parsed.isIndex }); | ||
| }; | ||
| route(parentRoute.routeInfo.path, parentRoute.routeInfo.file, routeOptions, routeChildren); | ||
| } | ||
| } | ||
| function parseRouteFile(baseDir, routeFile) { | ||
| function getRouteInfo(baseDir, routeFile, basePath) { | ||
| let state = "START"; | ||
| let subState = "NORMAL"; | ||
| let parentState = "APPEND"; | ||
| let url = ""; | ||
| let url = basePath != null ? basePath : ""; | ||
| let parent = ""; | ||
@@ -173,1 +182,2 @@ let isIndex = false; | ||
| } | ||
| //# sourceMappingURL=index.js.map |
+1
-0
@@ -76,1 +76,2 @@ var __create = Object.create; | ||
| } | ||
| //# sourceMappingURL=migrate.js.map |
+3
-2
@@ -28,3 +28,3 @@ var __create = Object.create; | ||
| var path = __toESM(require("path")); | ||
| function visitFiles(dir, visitor, baseDir = dir) { | ||
| const visitFiles = (dir, visitor, baseDir = dir) => { | ||
| for (let filename of fs.readdirSync(dir)) { | ||
@@ -39,2 +39,3 @@ let file = path.resolve(dir, filename); | ||
| } | ||
| } | ||
| }; | ||
| //# sourceMappingURL=util.js.map |
+2
-2
| { | ||
| "name": "remix-flat-routes", | ||
| "version": "0.2.1", | ||
| "version": "0.3.0", | ||
| "description": "Package for generating routes using flat convention", | ||
@@ -17,3 +17,3 @@ "main": "dist/index.js", | ||
| "clean": "rimraf dist", | ||
| "build": "npm run clean && esbuild --format=cjs --outdir=./dist ./src/*.ts", | ||
| "build": "npm run clean && esbuild --format=cjs --sourcemap --outdir=./dist ./src/*.ts", | ||
| "test": "jest", | ||
@@ -20,0 +20,0 @@ "contributors:add": "all-contributors add", |
+1
-2
@@ -43,3 +43,2 @@ # Remix Flat Routes | ||
| Example: | ||
@@ -49,3 +48,3 @@ npx migrate-flat-routes ./app/routes ./app/flatroutes --convention=flat-folders | ||
| NOTE: | ||
| sourceDir and targetDir are relative to project roo | ||
| sourceDir and targetDir are relative to project root | ||
@@ -52,0 +51,0 @@ Options: |
+82
-33
| import * as path from 'path' | ||
| import { visitFiles } from './util' | ||
| type RouteInfo = { | ||
| path: string | ||
| file: string | ||
| name: string | ||
| parent: string | ||
| isIndex: boolean | ||
| } | ||
| type DefineRouteOptions = { | ||
| caseSensitive?: boolean | ||
| index?: boolean | ||
| } | ||
| type DefineRouteChildren = { | ||
| (): void | ||
| } | ||
| type DefineRouteFunction = ( | ||
| path: string | undefined, | ||
| file: string, | ||
| optionsOrChildren?: DefineRouteOptions | DefineRouteChildren, | ||
| children?: DefineRouteChildren, | ||
| ) => void | ||
| export type VisitFilesFunction = ( | ||
| dir: string, | ||
| visitor: (file: string) => void, | ||
| baseDir?: string, | ||
| ) => void | ||
| type FlatRoutesOptions = { | ||
| basePath?: string | ||
| } | ||
| type ParentMapEntry = { | ||
| routeInfo: RouteInfo | ||
| children: RouteInfo[] | ||
| } | ||
| export type DefineRoutesFunction = ( | ||
| callback: (route: DefineRouteFunction) => void, | ||
| ) => any | ||
| export default function flatRoutes( | ||
| baseDir: string, | ||
| defineRoutes: (route: (...args: any) => void) => any, | ||
| defineRoutes: DefineRoutesFunction, | ||
| options: FlatRoutesOptions = {}, | ||
| ) { | ||
| const routeMap = new Map() | ||
| const parentMap = new Map() | ||
| const routeMap = new Map<string, RouteInfo>() | ||
| const parentMap = new Map<string, ParentMapEntry>() | ||
@@ -15,8 +59,11 @@ // initialize root route | ||
| file: 'root.tsx', | ||
| name: 'root', | ||
| parent: '', | ||
| isIndex: false, | ||
| }) | ||
| var routes = defineRoutes(route => { | ||
| visitFiles(`app/${baseDir}`, routeFile => { | ||
| const parsed = parseRouteFile(baseDir, routeFile) | ||
| if (!parsed) return | ||
| routeMap.set(parsed.name, parsed) | ||
| const routeInfo = getRouteInfo(baseDir, routeFile, options.basePath) | ||
| if (!routeInfo) return | ||
| routeMap.set(routeInfo.name, routeInfo) | ||
| }) | ||
@@ -30,3 +77,3 @@ // setup parent map | ||
| parent = { | ||
| parsed: routeMap.get(parentRoute), | ||
| routeInfo: routeMap.get(parentRoute)!, | ||
| children: [], | ||
@@ -48,22 +95,26 @@ } | ||
| function getRoutes( | ||
| parentMap: Map<string, any>, | ||
| parentMap: Map<string, ParentMapEntry>, | ||
| parent: string, | ||
| route: (...args: any) => void, | ||
| route: DefineRouteFunction, | ||
| ) { | ||
| let parentRoute = parentMap.get(parent) | ||
| if (parentRoute && parentRoute.children) { | ||
| const routeOptions: DefineRouteOptions = { | ||
| caseSensitive: false, | ||
| index: parentRoute!.routeInfo.isIndex, | ||
| } | ||
| const routeChildren: DefineRouteChildren = () => { | ||
| for (let child of parentRoute!.children) { | ||
| getRoutes(parentMap, child.name, route) | ||
| const path = child.path.substring( | ||
| parentRoute!.routeInfo.path.length + 1, | ||
| ) | ||
| route(path, child.file, { index: child.isIndex }) | ||
| } | ||
| } | ||
| route( | ||
| parentRoute.parsed.path, | ||
| parentRoute.parsed.file, | ||
| () => { | ||
| for (let child of parentRoute.children) { | ||
| getRoutes(parentMap, child.name, route) | ||
| route( | ||
| child.path.substring(parentRoute.parsed.path.length + 1), | ||
| child.file, | ||
| { index: child.isIndex }, | ||
| ) | ||
| } | ||
| }, | ||
| { index: parentRoute.parsed.isIndex }, | ||
| parentRoute.routeInfo.path, | ||
| parentRoute.routeInfo.file, | ||
| routeOptions, | ||
| routeChildren, | ||
| ) | ||
@@ -73,13 +124,6 @@ } | ||
| export type RouteInfo = { | ||
| path: string | ||
| file: string | ||
| name: string | ||
| parent: string | ||
| isIndex: boolean | ||
| } | ||
| export function parseRouteFile( | ||
| export function getRouteInfo( | ||
| baseDir: string, | ||
| routeFile: string, | ||
| basePath?: string, | ||
| ): RouteInfo | null { | ||
@@ -89,3 +133,3 @@ let state = 'START' | ||
| let parentState = 'APPEND' | ||
| let url = '' | ||
| let url = basePath ?? '' | ||
| let parent = '' | ||
@@ -118,3 +162,2 @@ let isIndex = false | ||
| let char = name[index] | ||
| //console.log({ char, state, subState, segment, url }) | ||
| switch (state) { | ||
@@ -196,1 +239,7 @@ case 'START': | ||
| export { flatRoutes } | ||
| export type { | ||
| DefineRouteFunction, | ||
| DefineRouteOptions, | ||
| DefineRouteChildren, | ||
| RouteInfo, | ||
| } |
+3
-2
| import * as fs from 'fs' | ||
| import * as path from 'path' | ||
| import type { VisitFilesFunction } from './index' | ||
| export function visitFiles( | ||
| export const visitFiles: VisitFilesFunction = ( | ||
| dir: string, | ||
| visitor: (file: string) => void, | ||
| baseDir = dir, | ||
| ) { | ||
| ) => { | ||
| for (let filename of fs.readdirSync(dir)) { | ||
@@ -10,0 +11,0 @@ let file = path.resolve(dir, filename) |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
51123
51.82%15
36.36%699
8.71%232
-0.43%