roto-rooter 0.8.0 → 0.8.2
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/README.md +94 -98
- package/dist/cli.js +96 -96
- package/dist/index.js +88 -88
- package/dist/parsers/component-parser.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,133 +1,129 @@
|
|
|
1
1
|
# roto-rooter
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A 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.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
```bash
|
|
5
|
+
```
|
|
8
6
|
npm install -g roto-rooter
|
|
9
7
|
```
|
|
10
8
|
|
|
11
|
-
##
|
|
9
|
+
## Running Checks
|
|
12
10
|
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
```
|
|
12
|
+
rr [OPTIONS] [FILES...]
|
|
13
|
+
```
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
rr app/routes/employees.tsx
|
|
15
|
+
Point `rr` at your project and it scans your route files for issues. With no arguments it runs the **default checks** (links, loader, params, interactivity) against all files in the current directory.
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
| Option | Description |
|
|
18
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| `-c, --check <checks>` | 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` |
|
|
20
|
+
| `-f, --format <format>` | Output format: `text` (default) or `json` |
|
|
21
|
+
| `-r, --root <path>` | Project root containing the `app/` folder (default: cwd) |
|
|
22
|
+
| `--fix` | Auto-fix issues where possible |
|
|
23
|
+
| `--dry-run` | Preview fixes without writing files |
|
|
24
|
+
| `--drizzle-schema <path>` | Path to Drizzle schema file (auto-discovered by default) |
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
rr --check all
|
|
26
|
+
**Checks at a glance:**
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
- **links** (default) -- validates `<Link>`, `redirect()`, `navigate()`, and `href` props on any component exist as routes
|
|
29
|
+
- **loader** (default) -- ensures `useLoaderData()` is backed by a loader; catches `clientLoader` importing server-only modules
|
|
30
|
+
- **params** (default) -- ensures `useParams()` only accesses params defined in the route path
|
|
31
|
+
- **interactivity** (default) -- catches "Save" buttons that don't save, "Delete" buttons that don't delete, and empty click handlers
|
|
32
|
+
- **forms** (opt-in) -- validates `<Form>` targets have actions and that field names match `formData.get()` calls
|
|
33
|
+
- **hydration** (opt-in) -- detects SSR/client mismatches from `new Date()`, `Math.random()`, `window` access in render
|
|
34
|
+
- **drizzle** (opt-in) -- validates Drizzle ORM operations against your schema (missing columns, type mismatches, etc.)
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
rr --format json
|
|
36
|
+
**Example output:**
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
rr --root
|
|
38
|
+
```
|
|
39
|
+
$ rr --root my-app
|
|
40
|
+
|
|
41
|
+
rr found 5 issues:
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
[error] dashboard.tsx:12:7
|
|
44
|
+
href="/employeees"
|
|
45
|
+
x No matching route
|
|
46
|
+
-> Did you mean: /employees?
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
[error] tasks.tsx:6:16
|
|
49
|
+
useLoaderData()
|
|
50
|
+
x useLoaderData() called but route has no loader
|
|
51
|
+
-> Add a loader function or remove the hook
|
|
40
52
|
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
[error] employees.$id.edit.tsx:7:32
|
|
54
|
+
useParams().invalidParam
|
|
55
|
+
x useParams() accesses "invalidParam" but route has no :invalidParam parameter
|
|
56
|
+
-> Available params: :id
|
|
43
57
|
|
|
44
|
-
|
|
45
|
-
|
|
58
|
+
[error] disconnected-dialog.tsx:27:11
|
|
59
|
+
<Button onClick={...}>Save Changes</Button>
|
|
60
|
+
x "Save Changes" button in Dialog only closes dialog without saving data
|
|
61
|
+
-> Wrap inputs in a <Form> component or use useFetcher.submit() to persist data
|
|
46
62
|
|
|
47
|
-
|
|
48
|
-
|
|
63
|
+
[warning] disconnected-dialog.tsx:78:11
|
|
64
|
+
<Button onClick={...}>Add Item</Button>
|
|
65
|
+
x "Add Item" button has an empty or stub onClick handler
|
|
66
|
+
-> Implement the handler or remove the button if not needed
|
|
49
67
|
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
Summary: 4 errors, 1 warning
|
|
69
|
+
Run with --help for options.
|
|
70
|
+
```
|
|
52
71
|
|
|
53
|
-
|
|
54
|
-
rr sql --drizzle app/routes/users.tsx
|
|
72
|
+
## Extracting SQL
|
|
55
73
|
|
|
56
|
-
# SQL output as JSON
|
|
57
|
-
rr sql --drizzle --format json
|
|
58
74
|
```
|
|
75
|
+
rr sql --drizzle [OPTIONS] [FILES...]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Reads 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.
|
|
79
|
+
|
|
80
|
+
| Option | Description |
|
|
81
|
+
| ------------------------- | -------------------------------------------------------- |
|
|
82
|
+
| `--drizzle` | Required. Specifies the ORM to analyze. |
|
|
83
|
+
| `-f, --format <format>` | Output format: `text` (default) or `json` |
|
|
84
|
+
| `-r, --root <path>` | Project root directory (default: cwd) |
|
|
85
|
+
| `--drizzle-schema <path>` | Path to Drizzle schema file (auto-discovered by default) |
|
|
86
|
+
|
|
87
|
+
**Example output:**
|
|
59
88
|
|
|
60
|
-
## Checks
|
|
61
|
-
|
|
62
|
-
**Default checks** (run automatically):
|
|
63
|
-
|
|
64
|
-
- **links**: Validates `<Link>`, `redirect()`, and `navigate()` targets exist as defined routes. Suggests closest matching route when a typo is detected. Auto-fixable when a close match exists.
|
|
65
|
-
- **loader**: Validates `useLoaderData()` is only used in routes that export a loader function. Detects `clientLoader`/`clientAction` that import server-only modules (database drivers, `fs`, etc.) which will fail in the browser. Auto-fixable by renaming to `loader`/`action`.
|
|
66
|
-
- **params**: Validates `useParams()` accesses only params defined in the route path (e.g., `:id` in `/users/:id`).
|
|
67
|
-
- **interactivity**: Detects disconnected interactive elements:
|
|
68
|
-
- Dialog/modal forms where "Save" button only closes the dialog without persisting data
|
|
69
|
-
- "Delete" confirmation buttons that only close without performing the action
|
|
70
|
-
- Buttons with empty or stub onClick handlers (console.log only)
|
|
71
|
-
- Validates dialogs use React Router `<Form>` or `useFetcher.submit()` for data operations
|
|
72
|
-
|
|
73
|
-
**Optional checks** (opt-in via `--check`):
|
|
74
|
-
|
|
75
|
-
- **forms**: Validates `<Form>` components submit to routes with action exports, and that form fields match what the action reads via `formData.get()`. Supports intent-based dispatch patterns. Auto-fixable when targeting a mistyped route.
|
|
76
|
-
- **hydration**: Detects SSR hydration mismatch risks:
|
|
77
|
-
- Date/time operations without consistent timezone handling
|
|
78
|
-
- Locale-dependent formatting (e.g., `toLocaleString()`) without explicit `timeZone` option
|
|
79
|
-
- Random value generation during render (`Math.random()`, `uuid()`, `nanoid()`)
|
|
80
|
-
- Browser-only API access outside `useEffect` (`window`, `document`, `localStorage`)
|
|
81
|
-
|
|
82
|
-
Some hydration issues are auto-fixable (e.g., adding `{ timeZone: "UTC" }` to locale methods, replacing `uuid()` with `useId()`).
|
|
83
|
-
|
|
84
|
-
- **drizzle** (persistence): Validates database operations against Drizzle ORM schema. Auto-discovers schema from common locations (`db/schema.ts`, `src/db/schema.ts`, etc.) or use `--drizzle-schema` for custom paths.
|
|
85
|
-
- Unknown table or column references in `db.insert()`, `db.update()`, `db.delete()`
|
|
86
|
-
- Missing required columns on `db.insert()` calls
|
|
87
|
-
- Null literal assigned to `notNull` column (insert or update)
|
|
88
|
-
- Invalid enum literal values (checked against schema-defined allowed values)
|
|
89
|
-
- Type mismatches: string from `formData.get()` to integer, boolean, timestamp, or json column
|
|
90
|
-
- Writing to auto-generated columns (e.g., serial, auto-increment) on insert
|
|
91
|
-
- `DELETE` or `UPDATE` without `.where()` clause (affects all rows)
|
|
92
|
-
- Enum columns receiving unvalidated external input
|
|
93
|
-
|
|
94
|
-
## SQL Query Extraction
|
|
95
|
-
|
|
96
|
-
The `rr sql` command extracts database queries from ORM code and generates equivalent SQL statements.
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
rr sql --drizzle # extract all SQL queries
|
|
100
|
-
rr sql --drizzle app/routes/users.tsx # extract from specific file
|
|
101
|
-
rr sql --drizzle --format json # JSON output
|
|
102
|
-
rr sql --drizzle --drizzle-schema db/schema.ts # explicit schema path
|
|
103
89
|
```
|
|
90
|
+
$ rr sql --drizzle --root my-app
|
|
91
|
+
|
|
92
|
+
Found 6 SQL queries:
|
|
104
93
|
|
|
105
|
-
|
|
94
|
+
File: app/routes/users.tsx:13:26
|
|
95
|
+
SELECT * FROM users
|
|
106
96
|
|
|
107
|
-
|
|
97
|
+
File: app/routes/users.tsx:16:9
|
|
98
|
+
SELECT id, name, email FROM users
|
|
108
99
|
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
File: app/routes/users.tsx:24:29
|
|
101
|
+
SELECT * FROM users WHERE status = 'active'
|
|
111
102
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
});
|
|
103
|
+
File: app/routes/users.tsx:36:9
|
|
104
|
+
INSERT INTO users (name, email, status) VALUES ($1, $2, $3)
|
|
105
|
+
Parameters:
|
|
106
|
+
$1: name (text)
|
|
107
|
+
$2: email (text)
|
|
108
|
+
$3: status (enum)
|
|
119
109
|
|
|
120
|
-
|
|
110
|
+
File: app/routes/orders.tsx:16:9
|
|
111
|
+
INSERT INTO orders (status, user_id, total) VALUES ($1, $2, $3)
|
|
112
|
+
Parameters:
|
|
113
|
+
$1: status (enum)
|
|
114
|
+
$2: userId (integer)
|
|
115
|
+
$3: total (integer)
|
|
121
116
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
117
|
+
File: app/routes/users.tsx:42:9
|
|
118
|
+
DELETE FROM users WHERE id = $1
|
|
119
|
+
Parameters:
|
|
120
|
+
$1: Number(params.id) (serial)
|
|
125
121
|
```
|
|
126
122
|
|
|
127
123
|
## Development
|
|
128
124
|
|
|
129
|
-
```
|
|
130
|
-
npm install #
|
|
131
|
-
npm test #
|
|
132
|
-
npm run build #
|
|
125
|
+
```
|
|
126
|
+
npm install # install dependencies
|
|
127
|
+
npm test # run tests
|
|
128
|
+
npm run build # build for distribution
|
|
133
129
|
```
|