{"id":40205865,"url":"https://github.com/glideapps/roto-rooter","last_synced_at":"2026-02-25T00:07:47.567Z","repository":{"id":333029853,"uuid":"1135941170","full_name":"glideapps/roto-rooter","owner":"glideapps","description":"Static analysis and functional verifier tool for React Router applications","archived":false,"fork":false,"pushed_at":"2026-02-12T20:16:16.000Z","size":311,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-13T04:31:40.713Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/glideapps.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-16T20:12:57.000Z","updated_at":"2026-02-12T20:15:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"269ed044-517a-41e5-90ca-cfaebaa38d8d","html_url":"https://github.com/glideapps/roto-rooter","commit_stats":null,"previous_names":["glideapps/roto-rooter"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/glideapps/roto-rooter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glideapps%2Froto-rooter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glideapps%2Froto-rooter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glideapps%2Froto-rooter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glideapps%2Froto-rooter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/glideapps","download_url":"https://codeload.github.com/glideapps/roto-rooter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glideapps%2Froto-rooter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29806145,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-24T22:43:48.403Z","status":"ssl_error","status_checked_at":"2026-02-24T22:43:18.536Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-19T21:00:23.345Z","updated_at":"2026-02-25T00:07:47.556Z","avatar_url":"https://github.com/glideapps.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# roto-rooter\n\nA static analysis tool for [React Router](https://reactrouter.com/) apps. It catches common bugs -- broken links, missing loaders, hydration mismatches, disconnected UI elements, and incorrect database operations -- by reading your route definitions and cross-referencing them against your components.\n\n```\nnpm install -g roto-rooter\n```\n\n## Running Checks\n\n```\nrr [OPTIONS] [FILES...]\n```\n\nPoint `rr` at your project and it scans your route files for issues. With no arguments it runs the **default checks** (links, forms, loader, params, interactivity, hydration) against all files in the current directory.\n\n| Option                    | Description                                                                                                                                                                                       |\n| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `-c, --check \u003cchecks\u003e`    | Comma-separated checks to run. Use `defaults` for the default set, `all` for everything, or pick individual checks: `links`, `loader`, `params`, `interactivity`, `forms`, `hydration`, `drizzle` |\n| `-f, --format \u003cformat\u003e`   | Output format: `text` (default) or `json`                                                                                                                                                         |\n| `-r, --root \u003cpath\u003e`       | Project root containing the `app/` folder (default: cwd)                                                                                                                                          |\n| `--fix`                   | Auto-fix issues where possible                                                                                                                                                                    |\n| `--dry-run`               | Preview fixes without writing files                                                                                                                                                               |\n| `--drizzle-schema \u003cpath\u003e` | Path to Drizzle schema file (auto-discovered by default)                                                                                                                                          |\n\n**Checks at a glance:**\n\n- **links** (default) -- validates `\u003cLink\u003e`, `redirect()`, `navigate()`, and `href` props on any component exist as routes\n- **loader** (default) -- ensures `useLoaderData()` is backed by a loader; catches `clientLoader` importing server-only modules\n- **params** (default) -- ensures `useParams()` only accesses params defined in the route path\n- **interactivity** (default) -- catches \"Save\" buttons that don't save, \"Delete\" buttons that don't delete, and empty click handlers\n- **hydration** (default) -- detects SSR/client mismatches from `new Date()`, `Math.random()`, locale-dependent formatting, `window` access in render\n- **forms** (default) -- validates `\u003cForm\u003e` targets have actions and that field names match `formData.get()` calls\n- **drizzle** (opt-in) -- validates Drizzle ORM operations against your schema (missing columns, type mismatches, etc.)\n\n**Example output:**\n\n```\n$ rr --root my-app\n\nrr found 5 issues:\n\n[error] dashboard.tsx:12:7\n  href=\"/employeees\"\n  x No matching route\n  -\u003e Did you mean: /employees?\n\n[error] tasks.tsx:6:16\n  useLoaderData()\n  x useLoaderData() called but route has no loader\n  -\u003e Add a loader function or remove the hook\n\n[error] employees.$id.edit.tsx:7:32\n  useParams().invalidParam\n  x useParams() accesses \"invalidParam\" but route has no :invalidParam parameter\n  -\u003e Available params: :id\n\n[error] disconnected-dialog.tsx:27:11\n  \u003cButton onClick={...}\u003eSave Changes\u003c/Button\u003e\n  x \"Save Changes\" button in Dialog only closes dialog without saving data\n  -\u003e Wrap inputs in a \u003cForm\u003e component or use useFetcher.submit() to persist data\n\n[warning] disconnected-dialog.tsx:78:11\n  \u003cButton onClick={...}\u003eAdd Item\u003c/Button\u003e\n  x \"Add Item\" button has an empty or stub onClick handler\n  -\u003e Implement the handler or remove the button if not needed\n\nSummary: 4 errors, 1 warning\nRun with --help for options.\n```\n\n## Extracting SQL\n\n```\nrr sql --drizzle [OPTIONS] [FILES...]\n```\n\nReads your Drizzle ORM code and prints the equivalent SQL for every query it finds. Useful for reviewing what your app actually sends to the database.\n\n| Option                    | Description                                              |\n| ------------------------- | -------------------------------------------------------- |\n| `--drizzle`               | Required. Specifies the ORM to analyze.                  |\n| `-f, --format \u003cformat\u003e`   | Output format: `text` (default) or `json`                |\n| `-r, --root \u003cpath\u003e`       | Project root directory (default: cwd)                    |\n| `--drizzle-schema \u003cpath\u003e` | Path to Drizzle schema file (auto-discovered by default) |\n\n**Example output:**\n\n```\n$ rr sql --drizzle --root my-app\n\nFound 6 SQL queries:\n\nFile: app/routes/users.tsx:13:26\n  SELECT * FROM users\n\nFile: app/routes/users.tsx:16:9\n  SELECT id, name, email FROM users\n\nFile: app/routes/users.tsx:24:29\n  SELECT * FROM users WHERE status = 'active'\n\nFile: app/routes/users.tsx:36:9\n  INSERT INTO users (name, email, status) VALUES ($1, $2, $3)\n  Parameters:\n    $1: name (text)\n    $2: email (text)\n    $3: status (enum)\n\nFile: app/routes/orders.tsx:16:9\n  INSERT INTO orders (status, user_id, total) VALUES ($1, $2, $3)\n  Parameters:\n    $1: status (enum)\n    $2: userId (integer)\n    $3: total (integer)\n\nFile: app/routes/users.tsx:42:9\n  DELETE FROM users WHERE id = $1\n  Parameters:\n    $1: Number(params.id) (serial)\n```\n\n## Development\n\n```\nnpm install      # install dependencies\nnpm test         # run tests\nnpm run build    # build for distribution\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglideapps%2Froto-rooter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglideapps%2Froto-rooter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglideapps%2Froto-rooter/lists"}