Download lib/parser.js from Shaurya2020/laravel-to-node: direct link, hf CLI and curl.
- Browser
- Download file 8.47 kB
-
https://huggingface.co/Shaurya2020/laravel-to-node/resolve/main/lib/parser.js
- Command line
-
hf download hf://Shaurya2020/laravel-to-node/lib/parser.js
-
curl -L -o parser.js https://huggingface.co/Shaurya2020/laravel-to-node/resolve/main/lib/parser.js
8.47 kB
| ; | |
| // Laravel route parser. Regex-based — pragmatic, not a full PHP AST. | |
| // Captures every Route::* declaration in routes/web.php and routes/api.php, | |
| // including groups with middleware/prefix/name/namespace and resource/apiResource. | |
| const path = require('path'); | |
| const fs = require('fs'); | |
| const php = require('./php-parse'); | |
| const { tryRead, listPhp, parseController, parseModel, parseMigration, parseMiddleware, listBlade } = php; | |
| // Extract use statements: `use App\Http\Controllers\UserController;` | |
| function parseUses(src) { | |
| const out = {}; | |
| const re = /use\s+([\\A-Za-z0-9_]+)(?:\s+as\s+([A-Za-z0-9_]+))?\s*;/g; | |
| let m; | |
| while ((m = re.exec(src))) { | |
| const fq = m[1].replace(/^\\/, ''); | |
| const alias = m[2] || fq.split('\\').pop(); | |
| out[alias] = fq; | |
| } | |
| return out; | |
| } | |
| // Tokenize a Route:: call arguments. Honors quotes, brackets, commas at top level. | |
| function tokenizeArgs(s) { | |
| const tokens = []; | |
| let depth = 0, cur = '', inStr = false, q = ''; | |
| for (let i = 0; i < s.length; i++) { | |
| const c = s[i]; | |
| if (inStr) { | |
| cur += c; | |
| if (c === '\\') { cur += s[++i] || ''; continue; } | |
| if (c === q) inStr = false; | |
| continue; | |
| } | |
| if (c === '"' || c === "'") { inStr = true; q = c; cur += c; continue; } | |
| if (c === '[' || c === '{' || c === '(') depth++; | |
| if (c === ']' || c === '}' || c === ')') depth--; | |
| if (c === ',' && depth === 0) { | |
| tokens.push(cur.trim()); | |
| cur = ''; | |
| continue; | |
| } | |
| cur += c; | |
| } | |
| if (cur.trim()) tokens.push(cur.trim()); | |
| return tokens; | |
| } | |
| // Parse `[Controller::class, 'action']` or `'Controller@action'` or Closure. | |
| function parseAction(arg, uses) { | |
| if (!arg) return null; | |
| arg = arg.trim(); | |
| if (/^function\s*\(/.test(arg) || /^fn\s*\(/.test(arg)) return { closure: true }; | |
| if (arg.startsWith('[') && arg.endsWith(']')) { | |
| const parts = tokenizeArgs(arg.slice(1, -1)); | |
| if (parts.length < 2) return null; | |
| const cls = parts[0].replace(/::class$/, '').trim(); | |
| const act = parts[1].replace(/^['"]|['"]$/g, '').trim(); | |
| const fq = uses[cls] || cls; | |
| return { controller: fq.split('\\').pop(), action: act, fqcn: fq }; | |
| } | |
| if (arg.includes('@')) { | |
| const [c, a] = arg.replace(/^['"]|['"]$/g, '').split('@'); | |
| const fq = uses[c] || c; | |
| return { controller: fq.split('\\').pop(), action: a, fqcn: fq }; | |
| } | |
| const fq = uses[arg.replace(/::class$/, '').trim()] || arg.replace(/::class$/, '').trim(); | |
| return { controller: fq.split('\\').pop(), action: null, fqcn: fq }; | |
| } | |
| // Find matching closing brace/paren starting at openIdx. | |
| function matchClose(src, openIdx, open, close) { | |
| let depth = 0, inStr = false, q = ''; | |
| for (let i = openIdx; i < src.length; i++) { | |
| const c = src[i]; | |
| if (inStr) { | |
| if (c === '\\') { i++; continue; } | |
| if (c === q) inStr = false; | |
| continue; | |
| } | |
| if (c === '"' || c === "'") { inStr = true; q = c; continue; } | |
| if (c === open) depth++; | |
| else if (c === close) { depth--; if (depth === 0) return i; } | |
| } | |
| return -1; | |
| } | |
| // Parse Route:: chains. Returns array of route objects. | |
| function parseRoutes(src, uses, ctx) { | |
| ctx = ctx || { middleware: [], prefix: '', name: '' }; | |
| const routes = []; | |
| const re = /Route::([a-zA-Z]+)\s*\(/g; | |
| let m; | |
| while ((m = re.exec(src))) { | |
| const method = m[1]; | |
| const argStart = m.index + m[0].length - 1; | |
| const argEnd = matchClose(src, argStart, '(', ')'); | |
| if (argEnd < 0) break; | |
| const argStr = src.slice(argStart + 1, argEnd); | |
| let after = argEnd + 1; | |
| const chain = { middleware: [], prefix: '', name: '', namespace: '' }; | |
| let isGroup = false; | |
| while (after < src.length) { | |
| const rest = src.slice(after); | |
| const cm = rest.match(/^->([a-zA-Z]+)\s*\(/); | |
| if (!cm) break; | |
| const cmStart = after + cm[0].length - 1; | |
| const cmEnd = matchClose(src, cmStart, '(', ')'); | |
| if (cmEnd < 0) break; | |
| const cmArg = src.slice(cmStart + 1, cmEnd); | |
| const fn = cm[1]; | |
| if (fn === 'middleware') { | |
| chain.middleware.push(...cmArg.replace(/^['"]|['"]$/g, '').split('|').filter(Boolean)); | |
| } else if (fn === 'prefix') { | |
| chain.prefix = cmArg.replace(/^['"]|['"]$/g, ''); | |
| } else if (fn === 'name') { | |
| chain.name = cmArg.replace(/^['"]|['"]$/g, ''); | |
| } else if (fn === 'namespace') { | |
| chain.namespace = cmArg.replace(/^['"]|['"]$/g, ''); | |
| } else if (fn === 'group') { | |
| isGroup = true; | |
| const bodyStart = cmArg.indexOf('{'); | |
| if (bodyStart >= 0) { | |
| const bodyEnd = matchClose(cmArg, bodyStart, '{', '}'); | |
| const inner = cmArg.slice(bodyStart + 1, bodyEnd >= 0 ? bodyEnd : cmArg.length); | |
| const childCtx = { | |
| middleware: [...ctx.middleware, ...chain.middleware], | |
| prefix: (ctx.prefix + '/' + chain.prefix).replace(/\/+/g, '/').replace(/\/$/, ''), | |
| name: (ctx.name + chain.name), | |
| }; | |
| routes.push(...parseRoutes(inner, uses, childCtx)); | |
| } | |
| after = cmEnd + 1; | |
| break; | |
| } | |
| after = cmEnd + 1; | |
| } | |
| // Advance regex past the consumed chain so the outer loop does not | |
| // re-scan the closure body of a group (which would duplicate inner routes). | |
| re.lastIndex = after; | |
| if (isGroup) continue; | |
| const args = tokenizeArgs(argStr); | |
| if (method === 'resource' || method === 'apiResource') { | |
| const resPath = (args[0] || '').replace(/^['"]|['"]$/g, ''); | |
| const act = parseAction(args[1], uses); | |
| const ctrl = act ? act.controller : 'Unknown'; | |
| const crud = method === 'resource' | |
| ? [ | |
| ['get', '', 'index'], | |
| ['get', '/create', 'create'], | |
| ['post', '', 'store'], | |
| ['get', '/{id}', 'show'], | |
| ['get', '/{id}/edit', 'edit'], | |
| ['put', '/{id}', 'update'], | |
| ['delete','/{id}', 'destroy'], | |
| ] | |
| : [ | |
| ['get', '', 'index'], | |
| ['post', '', 'store'], | |
| ['get', '/{id}', 'show'], | |
| ['put', '/{id}', 'update'], | |
| ['delete','/{id}', 'destroy'], | |
| ]; | |
| for (const [verb, suffix, action] of crud) { | |
| let p = resPath + suffix; | |
| p = (ctx.prefix + '/' + p).replace(/\/+/g, '/').replace(/\/$/, '') || '/'; | |
| routes.push({ | |
| method: verb.toUpperCase(), path: p, controller: ctrl, action, | |
| middleware: [...ctx.middleware, ...chain.middleware], | |
| name: ctx.name + resPath.replace('/', '.') + '.' + action, | |
| }); | |
| } | |
| continue; | |
| } | |
| if (!args[0]) continue; | |
| const rpath = (ctx.prefix + '/' + args[0].replace(/^['"]|['"]$/g, '')) | |
| .replace(/\/+/g, '/').replace(/\/$/, '') || '/'; | |
| const act = parseAction(args[1], uses); | |
| routes.push({ | |
| method: method.toUpperCase(), path: rpath, | |
| controller: act && act.controller ? act.controller : 'Closure', | |
| action: act && act.action ? act.action : null, | |
| closure: act && act.closure, | |
| middleware: [...ctx.middleware, ...chain.middleware], | |
| name: ctx.name + (args.find(a => a.startsWith('name:')) || '').replace(/name:\s*['"]|['"]$/g, ''), | |
| }); | |
| } | |
| return routes; | |
| } | |
| function discover(laravelPath) { | |
| const root = path.resolve(laravelPath); | |
| const routesDir = path.join(root, 'routes'); | |
| const ctrlDir = path.join(root, 'app', 'Http', 'Controllers'); | |
| const modelDir = path.join(root, 'app', 'Models'); | |
| const mwDir = path.join(root, 'app', 'Http', 'Middleware'); | |
| const migDir = path.join(root, 'database', 'migrations'); | |
| const viewDir = path.join(root, 'resources', 'views'); | |
| const webSrc = tryRead(path.join(routesDir, 'web.php')) || ''; | |
| const apiSrc = tryRead(path.join(routesDir, 'api.php')) || ''; | |
| const webUses = parseUses(webSrc); | |
| const apiUses = parseUses(apiSrc); | |
| return { | |
| root, | |
| routes: { | |
| web: parseRoutes(webSrc, webUses), | |
| api: parseRoutes(apiSrc, apiUses), | |
| }, | |
| controllers: listPhp(ctrlDir).map(parseController), | |
| models: listPhp(modelDir).map(parseModel), | |
| middleware: listPhp(mwDir).map(parseMiddleware), | |
| migrations: listPhp(migDir).map(parseMigration).filter(Boolean), | |
| views: listBlade(viewDir), | |
| }; | |
| } | |
| module.exports = { | |
| discover, parseRoutes, parseUses, parseAction, tokenizeArgs, matchClose, | |
| parseController, parseModel, parseMigration, parseMiddleware, listBlade, tryRead, | |
| }; | |