Skip to content

feat: added file based router#6

Open
izniburak wants to merge 4 commits intodashersw:mainfrom
izniburak:feat/file-based-router
Open

feat: added file based router#6
izniburak wants to merge 4 commits intodashersw:mainfrom
izniburak:feat/file-based-router

Conversation

@izniburak
Copy link
Copy Markdown

@izniburak izniburak commented Mar 24, 2026

File-Based Router

Adds a file-based routing (like Next.js App Router) convention to Gea, allowing routes to be defined by the filesystem structure instead of manually calling router.setRoutes(...).

How it works

Calling router.setPath('./pages') in your app is all that's needed. The @geajs/vite-plugin transforms this call at build time into import.meta.glob calls — pages are lazy-loaded, layouts are eagerly loaded.

// main.ts      
router.setPath('./pages')

// ↓ compiled to:
router.setRoutes(
  buildFileRoutes(
    import.meta.glob('./pages/**/page.{tsx,ts,jsx,js}'),
    import.meta.glob('./pages/**/layout.{tsx,ts,jsx,js}', { eager: true }),
    './pages'
  )
)

File conventions

File path Route
pages/page.tsx /
pages/about/page.tsx /about
pages/blog/[slug]/page.tsx /blog/:slug
pages/users/[id]/page.tsx /users/:id
pages/[...all]/page.tsx * (catch-all)
pages/layout.tsx Root layout wrapping all routes

Layouts (layout.tsx) are matched to routes by directory prefix and wrap their subtree automatically via <Outlet />.

Changes

@geajs/core

  • New buildFileRoutes(pages, layouts, basePath) runtime helper that maps glob results to a RouteMap, resolving layout nesting by directory prefix
  • buildFileRoutes is now exported from the public package entry

@geajs/vite-plugin

  • New transformFileRoutes transform that rewrites router.setPath(dir) into the expanded router.setRoutes(buildFileRoutes(...)) form at compile time

examples/router-file-based

  • Full example app demonstrating all conventions: static routes, dynamic params, nested layouts, and a catch-all 404 page
  • To run example project: npm run examples/router-file-based

Tests

  • Unit tests for buildFileRoutes (runtime logic) and transformFileRoutes (compiler transform)
  • E2E Playwright tests covering navigation, dynamic segments, layouts, and catch-all fallback

Summary by CodeRabbit

  • New Features

    • File-based routing is now publicly supported; router.setPath(...) is transformed at build time into file-generated routes with dynamic and catch-all segments.
  • Documentation

    • Added a complete example project and README demonstrating setup, directory conventions, and dev commands for the file-based router.
  • Tests

    • Added unit and E2E tests covering route generation, nested layouts, dynamic/catch-all segments, and runtime navigation.
  • Chores

    • Added a workspace script to run the file-based router example.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 59fd286e-3136-4be8-96f9-279ae7692032

📥 Commits

Reviewing files that changed from the base of the PR and between f25cc9f and 8a02476.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • packages/gea-tools/package.json
✅ Files skipped from review due to trivial changes (1)
  • packages/gea-tools/package.json

📝 Walkthrough

Walkthrough

Exports a new build-time helper buildFileRoutes from core, adds Router.setPath() as a build-time hook, implements a Vite transform that rewrites router.setPath('./pages') into router.setRoutes(buildFileRoutes(import.meta.glob(...), import.meta.glob(...), basePath)), and adds example app + unit and E2E tests.

Changes

Cohort / File(s) Summary
Changeset & Root package
\.changeset/export-build-file-routes.md, package.json
Patch-level changeset documenting export; added example:router-file-based npm script.
Core — Router implementation
packages/gea/src/lib/router/file-routes.ts, packages/gea/src/lib/router/router.ts
New buildFileRoutes(pageGlob, layoutGlob, basePath): RouteMap implementation and new Router.setPath(_path: string) build-time hook with JSDoc.
Core — Public entry
packages/gea/src/index.ts, packages/gea/src/lib/router/index.ts
Re-exported buildFileRoutes from router into core public entry.
Vite plugin — Transform integration
packages/vite-plugin-gea/src/transform-file-routes.ts, packages/vite-plugin-gea/src/index.ts
Added transformFileRoutes(code) that replaces .setPath(...) with .setRoutes(__geaBuildFileRoutes(import.meta.glob(...), import.meta.glob(...), basePath)), injects import, and integrated result into plugin transform flow.
Example — Project scaffold & config
examples/router-file-based/index.html, examples/router-file-based/package.json, examples/router-file-based/vite.config.ts, examples/router-file-based/tsconfig.json, examples/router-file-based/README.md
New runnable example project demonstrating file-based routing with local package aliases and docs.
Example — App, pages, layouts & styles
examples/router-file-based/src/...
Added app entry main.ts, root App.tsx, styles.css, layout.tsx, pages for home, about, blog (list + [slug]), users (list + [id]), and catch-all [...all].
Tests — Unit
packages/gea/tests/router-file-routes.test.ts, packages/vite-plugin-gea/tests/transform-file-routes.test.ts
Unit tests for buildFileRoutes (segment conversion, layout nesting, edge cases) and transformer (replacement correctness, idempotency, import injection).
Tests — E2E
tests/e2e/playwright.config.ts, tests/e2e/router-file-based.spec.ts
Playwright project and spec to validate runtime routing, dynamic segments, layout persistence, active-state tracking, and 404 behavior.
Misc (ordering)
packages/gea-tools/package.json
Reordered dependency entries (no version changes).

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer
    participant Build as Vite Build
    participant Plugin as Vite Plugin
    participant Glob as import.meta.glob
    participant Core as `@geajs/core`
    participant Runtime as App Runtime

    Dev->>Build: run dev/build
    Build->>Plugin: load & transform source (detect `router.setPath(...)`)
    Plugin->>Plugin: transformFileRoutes(code) -> replaced code + injected import
    Plugin->>Glob: emit page & layout glob patterns
    Glob-->>Plugin: module loader maps (page: lazy, layout: eager)
    Plugin->>Core: call __geaBuildFileRoutes(pageGlob, layoutGlob, basePath)
    Core-->>Plugin: RouteMap (constructed mapping/layout groups)
    Plugin->>Build: emit transformed module (uses setRoutes(RouteMap))
    Runtime->>Runtime: route matching/rendering
    Runtime->>Glob: load layout (eager) and page (lazy) modules
    Glob-->>Runtime: module instances
    Runtime->>Dev: render layout + page
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Runtime fixes #2 — Modifies router implementation (event wiring/guards); likely to overlap with the Router changes introduced here.

Poem

🐇
I nibble paths in folder rows,
Layout roots where wild thyme grows,
Slugs turn gentle, catch-alls spring,
Build-time hops make routes take wing,
Files become the map I know.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: added file based router' clearly and directly summarizes the main change: introducing file-based routing functionality to the Gea framework. It is concise, specific, and matches the primary purpose of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/gea/tests/router-file-routes.test.ts (1)

148-163: Test name and assertion may not match expected behavior.

The test is named "catch-all is placed in root layout children" but the comments on lines 159-161 suggest '*' is placed outside layouts. The assertion '*' in routes || '*' in (group?.children ?? {}) passes either way, which could mask a regression if the behavior changes.

Consider either:

  1. Renaming the test to reflect actual expected behavior
  2. Tightening the assertion to verify the specific location
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/gea/tests/router-file-routes.test.ts` around lines 148 - 163, The
test name and assertion are inconsistent: the spec says "catch-all is placed in
root layout children" but the comments say '*' belongs outside layouts and the
assertion routes || group.children is too permissive; update the test to either
rename it to reflect the actual expected behavior or tighten the assertion to
check the exact location. Concretely, in the test using buildFileRoutes, update
the it(...) title (the 'catch-all...' string) if you expect '*' to be outside
the root layout, or change the assertion to assert.strictEqual('*' in
(group?.children ?? {}), true) (or false) to explicitly verify whether
routes['/'] (variable group) contains the '*' child; ensure you reference the
same RootLayout, routes and group variables so the test fails if behavior
changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/export-build-file-routes.md:
- Around line 1-7: The changeset currently only versions `@geajs/core` but the PR
also changes published output for `@geajs/vite-plugin`; add a corresponding entry
for `@geajs/vite-plugin` (either in this .changeset/export-build-file-routes.md or
a new changeset in the same PR) and ensure the metadata lists both packages with
the same release type (patch/minor as appropriate), and include a short
description like "Include buildFileRoutes export / router.setPath integration
for `@geajs/vite-plugin`" so both packages are coordinated in the release.

In `@examples/router-file-based/package.json`:
- Around line 2-4: The package manifest includes a hardcoded "version" property
which violates the repository policy of letting changesets manage package
versions; remove the "version": "1.0.0" entry from this package.json (or if
there's a justified exception, add a short comment in the repo docs explaining
why this package is excluded from changesets) so that version bumping is handled
exclusively by changesets and not manually.

In `@packages/gea/src/lib/router/file-routes.ts`:
- Around line 112-208: The layout nesting is being computed from route-pattern
strings (with :param/*), causing dynamic layouts to incorrectly claim sibling
static pages; fix by preserving the original filesystem directory paths (e.g.
raw layout dir strings) and compute ancestry from that file-tree representation
before converting segments to route patterns. Update buildNestedRouteMap to pass
both the pattern prefixes (layouts keys) and their corresponding raw filesystem
prefixes into buildLayoutTree, then change buildLayoutTree, belongsToNode, and
buildGroupChildren to use the filesystem-prefix ancestry checks (instead of
isAncestor/isUnderPrefix against route patterns) when deciding parent/child
relationships and page ownership; keep isAncestor/isUnderPrefix only for final
route-matching after the tree is built. Ensure any places referencing
node.prefix still distinguish and carry both node.fsPrefix and
node.patternPrefix (or similar) so layout.tree logic uses fsPrefix and route
matching uses patternPrefix.

In `@packages/vite-plugin-gea/src/transform-file-routes.ts`:
- Around line 16-50: The current transformFileRoutes implementation uses
SET_PATH_RE to patch text and wrongly mutates template literals, strings, and
comments; replace the regex approach by parsing the source into an AST (e.g.,
`@babel/parser` or acorn), walk for CallExpression nodes whose callee is a
MemberExpression with property name "setPath" and whose first argument is a
StringLiteral (this ensures you only transform real calls like
router.setPath('./dir')), then replace that CallExpression AST node with a
CallExpression that calls the IMPORT_MARKER identifier (i.e., buildFileRoutes
wrapper) passing import.meta.glob expressions built from the string argument;
also detect existing import for buildFileRoutes by checking ImportDeclaration
nodes instead of searching the text and insert IMPORT_STMT as an
ImportDeclaration only if missing; update transformFileRoutes to print/generate
code from the modified AST and return it (with map null) so comments, template
literals and other strings remain untouched.

---

Nitpick comments:
In `@packages/gea/tests/router-file-routes.test.ts`:
- Around line 148-163: The test name and assertion are inconsistent: the spec
says "catch-all is placed in root layout children" but the comments say '*'
belongs outside layouts and the assertion routes || group.children is too
permissive; update the test to either rename it to reflect the actual expected
behavior or tighten the assertion to check the exact location. Concretely, in
the test using buildFileRoutes, update the it(...) title (the 'catch-all...'
string) if you expect '*' to be outside the root layout, or change the assertion
to assert.strictEqual('*' in (group?.children ?? {}), true) (or false) to
explicitly verify whether routes['/'] (variable group) contains the '*' child;
ensure you reference the same RootLayout, routes and group variables so the test
fails if behavior changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3a6dd8ef-ba46-4edc-ba13-0c57f936e610

📥 Commits

Reviewing files that changed from the base of the PR and between 7342d20 and 7209033.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (28)
  • .changeset/export-build-file-routes.md
  • examples/router-file-based/README.md
  • examples/router-file-based/index.html
  • examples/router-file-based/package.json
  • examples/router-file-based/src/App.tsx
  • examples/router-file-based/src/main.ts
  • examples/router-file-based/src/pages/[...all]/page.tsx
  • examples/router-file-based/src/pages/about/page.tsx
  • examples/router-file-based/src/pages/blog/[slug]/page.tsx
  • examples/router-file-based/src/pages/blog/page.tsx
  • examples/router-file-based/src/pages/layout.tsx
  • examples/router-file-based/src/pages/page.tsx
  • examples/router-file-based/src/pages/users/[id]/page.tsx
  • examples/router-file-based/src/pages/users/page.tsx
  • examples/router-file-based/src/styles.css
  • examples/router-file-based/tsconfig.json
  • examples/router-file-based/vite.config.ts
  • package.json
  • packages/gea/src/index.ts
  • packages/gea/src/lib/router/file-routes.ts
  • packages/gea/src/lib/router/index.ts
  • packages/gea/src/lib/router/router.ts
  • packages/gea/tests/router-file-routes.test.ts
  • packages/vite-plugin-gea/src/index.ts
  • packages/vite-plugin-gea/src/transform-file-routes.ts
  • packages/vite-plugin-gea/tests/transform-file-routes.test.ts
  • tests/e2e/playwright.config.ts
  • tests/e2e/router-file-based.spec.ts

Comment on lines +2 to +4
"name": "router-file-based-gea",
"version": "1.0.0",
"type": "module",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove manually managed version from this manifest (or document an exception).

Line 3 introduces a hardcoded package version, which conflicts with repository versioning policy.

Suggested adjustment
 {
   "name": "router-file-based-gea",
-  "version": "1.0.0",
+  "private": true,
   "type": "module",
As per coding guidelines: `**/package.json`: Never manually edit version numbers in `package.json` — let changesets own them.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"name": "router-file-based-gea",
"version": "1.0.0",
"type": "module",
"name": "router-file-based-gea",
"private": true,
"type": "module",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/router-file-based/package.json` around lines 2 - 4, The package
manifest includes a hardcoded "version" property which violates the repository
policy of letting changesets manage package versions; remove the "version":
"1.0.0" entry from this package.json (or if there's a justified exception, add a
short comment in the repo docs explaining why this package is excluded from
changesets) so that version bumping is handled exclusively by changesets and not
manually.

Comment on lines +112 to +208
function buildLayoutTree(prefixes: string[], layouts: Map<string, any>): LayoutNode[] {
const nodes: LayoutNode[] = prefixes.map((prefix) => ({
prefix,
layout: layouts.get(prefix)!,
children: [],
}))

const roots: LayoutNode[] = []

for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
let parent: LayoutNode | null = null

for (let j = 0; j < i; j++) {
const candidate = nodes[j]
if (isAncestor(candidate.prefix, node.prefix)) {
// Pick the deepest ancestor
if (!parent || candidate.prefix.length > parent.prefix.length) {
parent = candidate
}
}
}

if (parent) {
parent.children.push(node)
} else {
roots.push(node)
}
}

return roots
}

// ── RouteMap builder ─────────────────────────────────────────────

/** True if `route` belongs directly to `node` (not to a child layout). */
function belongsToNode(route: string, node: LayoutNode): boolean {
if (!isUnderPrefix(route, node.prefix)) return false
// Must not fall under any child layout
for (const child of node.children) {
if (isUnderPrefix(route, child.prefix)) return false
}
return true
}

function buildGroupChildren(
node: LayoutNode,
pages: Array<{ route: string; loader: LazyComponent }>,
): RouteMap {
const children: Record<string, any> = {}

// Pages owned directly by this node
for (const { route, loader } of pages) {
if (!belongsToNode(route, node)) continue
children[relativePath(route, node.prefix)] = loader
}

// Nested layout groups
for (const child of node.children) {
const key = relativePath(child.prefix, node.prefix)
const group: RouteGroupConfig = {
layout: child.layout,
children: buildGroupChildren(child, pages),
}
children[key] = group
}

return children as RouteMap
}

function buildNestedRouteMap(
pages: Array<{ route: string; loader: LazyComponent }>,
layouts: Map<string, any>,
): RouteMap {
const sortedPrefixes = [...layouts.keys()].sort((a, b) => {
const da = a === '/' ? 0 : a.split('/').filter(Boolean).length
const db = b === '/' ? 0 : b.split('/').filter(Boolean).length
return da - db
})

const roots = buildLayoutTree(sortedPrefixes, layouts)
const result: Record<string, any> = {}

// Pages not under any root layout node
for (const { route, loader } of pages) {
const underRoot = roots.some((r) => isUnderPrefix(route, r.prefix))
if (!underRoot) result[route] = loader
}

// Layout groups
for (const root of roots) {
const group: RouteGroupConfig = {
layout: root.layout,
children: buildGroupChildren(root, pages),
}
result[root.prefix] = group
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Build layout nesting from filesystem ancestry, not route-prefix matches.

isUnderPrefix() / isAncestor() are deciding ownership from converted route patterns, so dynamic layouts capture sibling static branches. With pages/[lang]/layout.tsx and sibling pages/about/page.tsx, isUnderPrefix('/about', '/:lang') is true, so Lines 196-198 pull /about under /:lang and Lines 164-166 re-emit it as child '/'. That makes /about render inside the [lang] layout even though it is not in that directory. Lines 121-138 have the same problem for sibling layouts like pages/[lang]/layout.tsx vs pages/en/layout.tsx. Please keep the original filesystem directories around and compute ancestry from the file tree before converting segments to :param / *.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/gea/src/lib/router/file-routes.ts` around lines 112 - 208, The
layout nesting is being computed from route-pattern strings (with :param/*),
causing dynamic layouts to incorrectly claim sibling static pages; fix by
preserving the original filesystem directory paths (e.g. raw layout dir strings)
and compute ancestry from that file-tree representation before converting
segments to route patterns. Update buildNestedRouteMap to pass both the pattern
prefixes (layouts keys) and their corresponding raw filesystem prefixes into
buildLayoutTree, then change buildLayoutTree, belongsToNode, and
buildGroupChildren to use the filesystem-prefix ancestry checks (instead of
isAncestor/isUnderPrefix against route patterns) when deciding parent/child
relationships and page ownership; keep isAncestor/isUnderPrefix only for final
route-matching after the tree is built. Ensure any places referencing
node.prefix still distinguish and carry both node.fsPrefix and
node.patternPrefix (or similar) so layout.tree logic uses fsPrefix and route
matching uses patternPrefix.

Comment on lines +16 to +50
const SET_PATH_RE = /\.setPath\(\s*(['"])((?:\.{1,2}\/)[^'"]*)\1\s*\)/g
const IMPORT_MARKER = '__geaBuildFileRoutes'
const IMPORT_STMT = `import { buildFileRoutes as ${IMPORT_MARKER} } from '@geajs/core';\n`

export function transformFileRoutes(code: string): { code: string; map: null } | null {
if (!code.includes('.setPath(')) return null

SET_PATH_RE.lastIndex = 0
if (!SET_PATH_RE.test(code)) return null

SET_PATH_RE.lastIndex = 0
const transformed = code.replace(SET_PATH_RE, (match, _quote, dirPath, offset) => {
// Skip if inside a block/JSDoc comment (line starts with optional whitespace then *)
const lineStart = code.lastIndexOf('\n', offset) + 1
const linePrefix = code.slice(lineStart, offset)
if (/^\s*\*/.test(linePrefix)) return match

const pageGlob = JSON.stringify(`${dirPath}/**/page.{tsx,ts,jsx,js}`)
const layoutGlob = JSON.stringify(`${dirPath}/**/layout.{tsx,ts,jsx,js}`)
return (
`.setRoutes(${IMPORT_MARKER}(` +
`import.meta.glob(${pageGlob}), ` +
`import.meta.glob(${layoutGlob}, { eager: true }), ` +
`${JSON.stringify(dirPath)}` +
`))`
)
})

if (transformed === code) return null

// Prepend the import only once (it may already be present from a previous
// HMR pass or if the user imports buildFileRoutes themselves).
const final = transformed.includes(IMPORT_MARKER) && code.includes(IMPORT_MARKER)
? transformed
: IMPORT_STMT + transformed
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import pathlib
import re

pattern = re.compile(r"\.setPath\(\s*(['\"])((?:\.{1,2}\/)[^'\"]*)\1\s*\)")

def repl(m: re.Match[str]) -> str:
    dir_path = m.group(2)
    page_glob = f'"{dir_path}/**/page.{{tsx,ts,jsx,js}}"'
    layout_glob = f'"{dir_path}/**/layout.{{tsx,ts,jsx,js}}"'
    return (
        '.setRoutes(__geaBuildFileRoutes('
        f'import.meta.glob({page_glob}), '
        f'import.meta.glob({layout_glob}, {{ eager: true }}), '
        f'"{dir_path}"'
        '))'
    )

blog_path = pathlib.Path("examples/router-file-based/src/pages/blog/[slug]/page.tsx")
blog_code = blog_path.read_text()

print("--- regex matches inside the blog page source ---")
for m in pattern.finditer(blog_code):
    line = blog_code.count("\n", 0, m.start()) + 1
    print(f"blog match at Line {line}: {m.group(0)}")

print("--- transformed blog line containing the helper marker ---")
for i, line in enumerate(pattern.sub(repl, blog_code).splitlines(), 1):
    if "__geaBuildFileRoutes" in line:
        print(f"Line {i}: {line}")

sample = 'const s = "router.setPath(\'./pages\')"'
print("--- transformed plain-string sample ---")
print(pattern.sub(repl, sample))
PY

Repository: dashersw/gea

Length of output: 682


Fix regex-based string matching to use AST-based call detection.

The regex matches .setPath() calls inside template literals and string literals, not just real method calls. The transformation at Line 28 in examples/router-file-based/src/pages/blog/[slug]/page.tsx injects the __geaBuildFileRoutes marker into the template literal content, corrupting displayed text. Additionally, transforming a plain string like const s = "router.setPath('./pages')" produces invalid syntax with unescaped " characters inside the quoted string. The comment guard (line 28-31) only skips JSDoc-style lines starting with *, leaving // and /* ... */ comments unprotected.

Switch to parsing the AST, transforming only actual CallExpression nodes for .setPath(), and detecting/deduplicating the helper import structurally instead of via text search.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vite-plugin-gea/src/transform-file-routes.ts` around lines 16 - 50,
The current transformFileRoutes implementation uses SET_PATH_RE to patch text
and wrongly mutates template literals, strings, and comments; replace the regex
approach by parsing the source into an AST (e.g., `@babel/parser` or acorn), walk
for CallExpression nodes whose callee is a MemberExpression with property name
"setPath" and whose first argument is a StringLiteral (this ensures you only
transform real calls like router.setPath('./dir')), then replace that
CallExpression AST node with a CallExpression that calls the IMPORT_MARKER
identifier (i.e., buildFileRoutes wrapper) passing import.meta.glob expressions
built from the string argument; also detect existing import for buildFileRoutes
by checking ImportDeclaration nodes instead of searching the text and insert
IMPORT_STMT as an ImportDeclaration only if missing; update transformFileRoutes
to print/generate code from the modified AST and return it (with map null) so
comments, template literals and other strings remain untouched.

@izniburak izniburak force-pushed the feat/file-based-router branch from 7209033 to f25cc9f Compare March 26, 2026 21:15
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/gea-tools/package.json (1)

39-42: ⚠️ Potential issue | 🟡 Minor

Missing test script per coding guidelines.

This package does not define a test script. As per coding guidelines, each package must define its own test script in package.json using tsx with the --test flag.

Proposed fix to add test script
   "scripts": {
     "compile": "tsc -p ./",
-    "watch": "tsc -w -p ./"
+    "watch": "tsc -w -p ./",
+    "test": "tsx --test ./tests/**/*.test.ts"
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/gea-tools/package.json` around lines 39 - 42, The package.json
scripts block is missing a "test" entry; add a "test" script that runs the
project's tests with tsx using the --test flag (e.g., add "test": "tsx --test"
alongside "compile" and "watch") so the package follows the guideline requiring
each package to define its own test script; update the scripts object in
package.json accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/gea-tools/package.json`:
- Around line 45-46: The package.json has mismatched major versions between
"vscode-languageclient" and "vscode-languageserver"; pick a matching major
version for both (either upgrade "vscode-languageclient" to ^8.1.0 to match
"vscode-languageserver" or downgrade "vscode-languageserver" to a 6.x range) and
update package.json accordingly; if you choose to upgrade the client to 7/8.x
also update imports from "vscode-languageclient" to "vscode-languageclient/node"
and adjust LanguageClient usage (async start/stop and any API changes) in files
that reference LanguageClient to match the chosen major version.
- Line 44: The package.json in packages/gea-tools contains an unused dependency
"quill": "2.0.2"; remove this dependency entry from
packages/gea-tools/package.json (or if you prefer to relocate it, add the same
dependency to packages/gea/package.json) and then run the package manager to
update lockfiles; ensure tests/files that use quill (e.g.,
tests/quill-editor.test.ts and examples/jira_clone/) resolve from the package
that now declares it (packages/gea) so imports continue to work.

---

Outside diff comments:
In `@packages/gea-tools/package.json`:
- Around line 39-42: The package.json scripts block is missing a "test" entry;
add a "test" script that runs the project's tests with tsx using the --test flag
(e.g., add "test": "tsx --test" alongside "compile" and "watch") so the package
follows the guideline requiring each package to define its own test script;
update the scripts object in package.json accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 80fabf7f-fbcb-44fe-bdd8-42d55849bd95

📥 Commits

Reviewing files that changed from the base of the PR and between 7209033 and f25cc9f.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (20)
  • .changeset/export-build-file-routes.md
  • examples/router-file-based/README.md
  • examples/router-file-based/index.html
  • examples/router-file-based/package.json
  • examples/router-file-based/src/App.tsx
  • examples/router-file-based/src/main.ts
  • examples/router-file-based/src/pages/[...all]/page.tsx
  • examples/router-file-based/src/pages/about/page.tsx
  • examples/router-file-based/src/pages/blog/[slug]/page.tsx
  • examples/router-file-based/src/pages/blog/page.tsx
  • examples/router-file-based/src/pages/layout.tsx
  • examples/router-file-based/src/pages/page.tsx
  • examples/router-file-based/src/pages/users/[id]/page.tsx
  • examples/router-file-based/src/pages/users/page.tsx
  • examples/router-file-based/src/styles.css
  • examples/router-file-based/tsconfig.json
  • examples/router-file-based/vite.config.ts
  • package.json
  • packages/gea-tools/package.json
  • packages/gea/src/index.ts
✅ Files skipped from review due to trivial changes (11)
  • package.json
  • examples/router-file-based/src/pages/[...all]/page.tsx
  • examples/router-file-based/index.html
  • examples/router-file-based/tsconfig.json
  • packages/gea/src/index.ts
  • examples/router-file-based/src/App.tsx
  • examples/router-file-based/src/pages/layout.tsx
  • examples/router-file-based/src/styles.css
  • examples/router-file-based/src/pages/users/page.tsx
  • examples/router-file-based/package.json
  • examples/router-file-based/README.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • .changeset/export-build-file-routes.md
  • examples/router-file-based/vite.config.ts
  • examples/router-file-based/src/pages/blog/page.tsx
  • examples/router-file-based/src/pages/page.tsx
  • examples/router-file-based/src/main.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant