@remix-run/cli 0.3.4 → 0.4.0

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.
Files changed (118) hide show
  1. package/README.md +105 -1
  2. package/dist/index.js +1 -1
  3. package/dist/lib/app-root.d.ts +11 -0
  4. package/dist/lib/app-root.d.ts.map +1 -0
  5. package/dist/lib/app-root.js +38 -0
  6. package/dist/lib/bootstrap-project.js +2 -2
  7. package/dist/lib/cli-context.d.ts +6 -0
  8. package/dist/lib/cli-context.d.ts.map +1 -1
  9. package/dist/lib/cli-context.js +12 -1
  10. package/dist/lib/cli.d.ts +3 -2
  11. package/dist/lib/cli.d.ts.map +1 -1
  12. package/dist/lib/cli.js +42 -14
  13. package/dist/lib/commands/completion.js +4 -4
  14. package/dist/lib/commands/db.d.ts +4 -0
  15. package/dist/lib/commands/db.d.ts.map +1 -0
  16. package/dist/lib/commands/db.js +283 -0
  17. package/dist/lib/commands/doctor.d.ts +5 -0
  18. package/dist/lib/commands/doctor.d.ts.map +1 -1
  19. package/dist/lib/commands/doctor.js +26 -251
  20. package/dist/lib/commands/help.d.ts.map +1 -1
  21. package/dist/lib/commands/help.js +17 -9
  22. package/dist/lib/commands/new.js +6 -6
  23. package/dist/lib/commands/routes.js +6 -6
  24. package/dist/lib/commands/test.d.ts +20 -1
  25. package/dist/lib/commands/test.d.ts.map +1 -1
  26. package/dist/lib/commands/test.js +353 -10
  27. package/dist/lib/commands/version.js +3 -3
  28. package/dist/lib/completion.d.ts +1 -1
  29. package/dist/lib/completion.d.ts.map +1 -1
  30. package/dist/lib/completion.js +229 -6
  31. package/dist/lib/controller-files.d.ts +0 -8
  32. package/dist/lib/controller-files.d.ts.map +1 -1
  33. package/dist/lib/controller-files.js +0 -23
  34. package/dist/lib/controller-ownership.d.ts +4 -12
  35. package/dist/lib/controller-ownership.d.ts.map +1 -1
  36. package/dist/lib/controller-ownership.js +31 -127
  37. package/dist/lib/database-command.d.ts +20 -0
  38. package/dist/lib/database-command.d.ts.map +1 -0
  39. package/dist/lib/database-command.js +7 -0
  40. package/dist/lib/doctor/controller-findings.d.ts +2 -2
  41. package/dist/lib/doctor/controller-findings.d.ts.map +1 -1
  42. package/dist/lib/doctor/controller-findings.js +23 -39
  43. package/dist/lib/doctor/controllers.d.ts +1 -2
  44. package/dist/lib/doctor/controllers.d.ts.map +1 -1
  45. package/dist/lib/doctor/controllers.js +4 -7
  46. package/dist/lib/doctor/environment.js +2 -2
  47. package/dist/lib/doctor/fixes.js +1 -1
  48. package/dist/lib/doctor/project.d.ts.map +1 -1
  49. package/dist/lib/doctor/project.js +3 -212
  50. package/dist/lib/doctor/run.d.ts +9 -0
  51. package/dist/lib/doctor/run.d.ts.map +1 -0
  52. package/dist/lib/doctor/run.js +237 -0
  53. package/dist/lib/doctor/types.d.ts +2 -1
  54. package/dist/lib/doctor/types.d.ts.map +1 -1
  55. package/dist/lib/doctor/types.js +4 -1
  56. package/dist/lib/errors.d.ts +30 -0
  57. package/dist/lib/errors.d.ts.map +1 -1
  58. package/dist/lib/errors.js +55 -0
  59. package/dist/lib/help-text.js +1 -1
  60. package/dist/lib/parse-args.d.ts.map +1 -1
  61. package/dist/lib/parse-args.js +17 -4
  62. package/dist/lib/remix-config.d.ts +75 -0
  63. package/dist/lib/remix-config.d.ts.map +1 -0
  64. package/dist/lib/remix-config.js +411 -0
  65. package/dist/lib/reporter.js +1 -1
  66. package/dist/lib/route-map.js +13 -33
  67. package/package.json +13 -7
  68. package/schema/remix.json +242 -0
  69. package/src/lib/app-root.ts +46 -0
  70. package/src/lib/cli-context.ts +17 -0
  71. package/src/lib/cli.ts +41 -6
  72. package/src/lib/commands/db.ts +377 -0
  73. package/src/lib/commands/doctor.ts +27 -329
  74. package/src/lib/commands/help.ts +10 -1
  75. package/src/lib/commands/test.ts +453 -12
  76. package/src/lib/completion.ts +321 -6
  77. package/src/lib/controller-files.ts +0 -38
  78. package/src/lib/controller-ownership.ts +50 -203
  79. package/src/lib/database-command.ts +31 -0
  80. package/src/lib/doctor/controller-findings.ts +30 -47
  81. package/src/lib/doctor/controllers.ts +2 -6
  82. package/src/lib/doctor/project.ts +0 -236
  83. package/src/lib/doctor/run.ts +311 -0
  84. package/src/lib/doctor/types.ts +6 -5
  85. package/src/lib/errors.ts +65 -0
  86. package/src/lib/parse-args.ts +19 -3
  87. package/src/lib/remix-config.ts +639 -0
  88. package/src/lib/route-map.ts +11 -48
  89. package/template/.agents/skills/remix/SKILL.md +38 -27
  90. package/template/.agents/skills/remix/references/assets-and-browser-modules.md +53 -10
  91. package/template/.agents/skills/remix/references/auth-and-sessions.md +5 -5
  92. package/template/.agents/skills/remix/references/data-and-validation.md +15 -15
  93. package/template/.agents/skills/remix/references/hydration-frames-navigation.md +54 -8
  94. package/template/.agents/skills/remix/references/middleware-and-server.md +68 -6
  95. package/template/.agents/skills/remix/references/mixins-styling-events.md +2 -0
  96. package/template/.agents/skills/remix/references/routing-and-controllers.md +8 -8
  97. package/template/.agents/skills/remix/references/testing-patterns.md +19 -18
  98. package/template/AGENTS.md +6 -3
  99. package/template/README.md +6 -3
  100. package/template/app/actions/controller.tsx +1 -1
  101. package/template/app/{ui → actions}/document.tsx +7 -5
  102. package/template/app/{ui/scaffold-home-page.tsx → actions/home-page.tsx} +1 -2
  103. package/template/app/actions/public/entry.ts +48 -0
  104. package/template/app/{assets → actions/public}/prompt-button.tsx +29 -25
  105. package/template/app/assets.ts +15 -2
  106. package/template/hmr.ts +46 -0
  107. package/template/package.json +1 -0
  108. package/template/server.ts +8 -1
  109. package/template/tsconfig.json +1 -1
  110. package/dist/lib/doctor/controller-fix-plans.d.ts +0 -4
  111. package/dist/lib/doctor/controller-fix-plans.d.ts.map +0 -1
  112. package/dist/lib/doctor/controller-fix-plans.js +0 -119
  113. package/dist/lib/doctor/controller-placeholders.d.ts +0 -3
  114. package/dist/lib/doctor/controller-placeholders.d.ts.map +0 -1
  115. package/dist/lib/doctor/controller-placeholders.js +0 -52
  116. package/src/lib/doctor/controller-fix-plans.ts +0 -168
  117. package/src/lib/doctor/controller-placeholders.ts +0 -70
  118. package/template/app/assets/entry.ts +0 -17
@@ -3,16 +3,12 @@ import * as path from 'node:path'
3
3
 
4
4
  import {
5
5
  getControllerOwnerCandidates,
6
- getOwnerModuleBaseName,
7
6
  getPreferredOwnerDisplayPath,
8
7
  getRouteSubtreePath,
9
- isControllerEntryFileName,
10
8
  toDiskSegment,
11
9
  } from './controller-files.ts'
12
10
  import type { RouteTreeNodeKind } from './route-map.ts'
13
11
 
14
- const ACTIONS_PATH_PREFIX = 'app/actions/'
15
-
16
12
  export const ROOT_ROUTE_NAME = '<root>'
17
13
 
18
14
  export interface OwnershipRouteNode {
@@ -23,17 +19,15 @@ export interface OwnershipRouteNode {
23
19
  name: string
24
20
  }
25
21
 
26
- export interface ControllerDirectoryScan {
27
- controllerEntryPaths: Set<string>
28
- routeDirectoryPaths: Set<string>
29
- routeLocalFilePaths: Set<string>
30
- }
31
-
32
22
  export interface RouteDirectoryPlan {
33
23
  directoryPath: string
34
24
  routeName: string
35
25
  }
36
26
 
27
+ export interface RouteDirectory extends RouteDirectoryPlan {
28
+ exists: boolean
29
+ }
30
+
37
31
  export interface OwnedSubtreePlan {
38
32
  entryCandidates: string[]
39
33
  entryDisplayPath: string
@@ -43,16 +37,10 @@ export interface OwnedSubtreePlan {
43
37
 
44
38
  export interface OwnedSubtree extends OwnedSubtreePlan {
45
39
  actualEntryPath: string | null
46
- actualEntryPaths: string[]
47
- claimedFilePaths: string[]
48
- claimedRouteLocalFilePaths: string[]
49
40
  }
50
41
 
51
42
  export interface ControllerOwnership {
52
- orphanControllerPaths: string[]
53
- orphanRouteDirectoryPaths: string[]
54
- routeDirectories: RouteDirectoryPlan[]
55
- scan: ControllerDirectoryScan
43
+ routeDirectories: RouteDirectory[]
56
44
  subtrees: OwnedSubtree[]
57
45
  }
58
46
 
@@ -61,15 +49,14 @@ export async function inspectControllerOwnership(
61
49
  tree: OwnershipRouteNode[],
62
50
  ): Promise<ControllerOwnership> {
63
51
  let subtreePlans = buildOwnedSubtrees(tree)
64
- let routeDirectories = buildRouteDirectories(tree)
65
- let scan = await scanControllersDirectory(appRoot)
66
- let subtrees = applyScanToSubtrees(subtreePlans, scan)
52
+ let routeDirectoryPlans = buildRouteDirectories(tree)
53
+ let [routeDirectories, subtrees] = await Promise.all([
54
+ inspectRouteDirectories(appRoot, routeDirectoryPlans),
55
+ inspectOwnedSubtrees(appRoot, subtreePlans),
56
+ ])
67
57
 
68
58
  return {
69
- orphanControllerPaths: getOrphanControllerPaths(subtreePlans, scan),
70
- orphanRouteDirectoryPaths: getOrphanRouteDirectoryPaths(routeDirectories, scan),
71
59
  routeDirectories,
72
- scan,
73
60
  subtrees,
74
61
  }
75
62
  }
@@ -138,197 +125,57 @@ function addSubtreePlan(routeName: string, segments: string[], subtrees: OwnedSu
138
125
  })
139
126
  }
140
127
 
141
- async function scanControllersDirectory(appRoot: string): Promise<ControllerDirectoryScan> {
142
- let actionsDir = path.join(appRoot, 'app', 'actions')
143
- let controllerEntryPaths = new Set<string>()
144
- let routeDirectoryPaths = new Set<string>()
145
- let routeLocalFilePaths = new Set<string>()
146
-
147
- async function walk(currentDir: string, isRoot: boolean): Promise<void> {
148
- let entries
149
- try {
150
- entries = await fs.readdir(currentDir, { withFileTypes: true })
151
- } catch (error) {
152
- let nodeError = error as NodeJS.ErrnoException
153
- if (nodeError.code === 'ENOENT' && isRoot) {
154
- return
155
- }
156
-
157
- throw error
158
- }
159
-
160
- for (let entry of entries) {
161
- let entryPath = path.join(currentDir, entry.name)
162
- let relativePath = normalizeRelativePath(path.relative(appRoot, entryPath))
163
-
164
- if (entry.isDirectory()) {
165
- routeDirectoryPaths.add(relativePath)
166
- await walk(entryPath, false)
167
- continue
168
- }
169
-
170
- if (!entry.isFile()) {
171
- continue
172
- }
173
-
174
- if (isControllerEntryFileName(entry.name)) {
175
- controllerEntryPaths.add(relativePath)
176
- continue
177
- }
178
-
179
- if (isRouteLocalFileName(entry.name)) {
180
- routeLocalFilePaths.add(relativePath)
181
- }
182
- }
183
- }
184
-
185
- await walk(actionsDir, true)
186
-
187
- return {
188
- controllerEntryPaths,
189
- routeDirectoryPaths,
190
- routeLocalFilePaths,
191
- }
192
- }
193
-
194
- function applyScanToSubtrees(
195
- subtreePlans: OwnedSubtreePlan[],
196
- scan: ControllerDirectoryScan,
197
- ): OwnedSubtree[] {
198
- let claimedRouteLocalPaths = claimFilesToDeepestSubtree(
199
- [...scan.routeLocalFilePaths],
200
- subtreePlans,
201
- )
202
- let claimedContentPaths = claimFilesToDeepestSubtree(getNestedContentPaths(scan), subtreePlans)
203
-
204
- return subtreePlans.map((subtree) => {
205
- let actualEntryPaths = findOwnerPaths(scan, subtree.entryCandidates)
206
-
207
- return {
208
- ...subtree,
209
- actualEntryPath: actualEntryPaths[0] ?? null,
210
- actualEntryPaths,
211
- claimedFilePaths: claimedContentPaths.get(subtree.routeName) ?? [],
212
- claimedRouteLocalFilePaths: claimedRouteLocalPaths.get(subtree.routeName) ?? [],
213
- }
214
- })
215
- }
216
-
217
- function getNestedContentPaths(scan: ControllerDirectoryScan): string[] {
218
- let nestedControllerPaths = [...scan.controllerEntryPaths].filter((filePath) =>
219
- isNestedControllerPath(filePath),
220
- )
221
-
222
- return [...new Set([...nestedControllerPaths, ...scan.routeLocalFilePaths])].sort()
223
- }
224
-
225
- function claimFilesToDeepestSubtree(
226
- filePaths: string[],
227
- subtreePlans: OwnedSubtreePlan[],
228
- ): Map<string, string[]> {
229
- let subtreesByDepth = [...subtreePlans].sort((left, right) => {
230
- if (right.subtreePath.length !== left.subtreePath.length) {
231
- return right.subtreePath.length - left.subtreePath.length
232
- }
233
-
234
- return left.routeName.localeCompare(right.routeName)
235
- })
236
- let claims = new Map<string, string[]>()
237
-
238
- for (let filePath of filePaths.sort()) {
239
- let matchingSubtree = subtreesByDepth.find((subtree) =>
240
- isWithinDirectory(filePath, subtree.subtreePath),
241
- )
242
-
243
- if (matchingSubtree == null) {
244
- continue
245
- }
246
-
247
- let claimedPaths = claims.get(matchingSubtree.routeName)
248
- if (claimedPaths == null) {
249
- claims.set(matchingSubtree.routeName, [filePath])
250
- continue
251
- }
252
-
253
- claimedPaths.push(filePath)
254
- }
255
-
256
- return claims
257
- }
258
-
259
- function getOrphanControllerPaths(
260
- subtreePlans: OwnedSubtreePlan[],
261
- scan: ControllerDirectoryScan,
262
- ): string[] {
263
- let expectedControllerPaths = new Set(subtreePlans.flatMap((subtree) => subtree.entryCandidates))
264
-
265
- return [...scan.controllerEntryPaths]
266
- .filter((filePath) => !expectedControllerPaths.has(filePath))
267
- .sort()
268
- }
269
-
270
- function getOrphanRouteDirectoryPaths(
271
- routeDirectories: RouteDirectoryPlan[],
272
- scan: ControllerDirectoryScan,
273
- ): string[] {
274
- let expectedRouteDirectories = new Set(
275
- routeDirectories.map((routeDirectory) => routeDirectory.directoryPath),
276
- )
277
- let actualControllerDirectories = [...scan.controllerEntryPaths].map((controllerPath) =>
278
- normalizeRelativePath(path.dirname(controllerPath)),
128
+ async function inspectRouteDirectories(
129
+ appRoot: string,
130
+ plans: RouteDirectoryPlan[],
131
+ ): Promise<RouteDirectory[]> {
132
+ return Promise.all(
133
+ plans.map(async (plan) => ({
134
+ ...plan,
135
+ exists: await pathHasType(appRoot, plan.directoryPath, 'directory'),
136
+ })),
279
137
  )
280
-
281
- return [...scan.routeDirectoryPaths]
282
- .filter((directoryPath) => !expectedRouteDirectories.has(directoryPath))
283
- .filter(
284
- (directoryPath) =>
285
- !actualControllerDirectories.some((controllerPath) =>
286
- isDirectoryWithinDirectory(controllerPath, directoryPath),
287
- ),
288
- )
289
- .sort()
290
138
  }
291
139
 
292
- function isRouteLocalFileName(fileName: string): boolean {
293
- let baseName = getOwnerModuleBaseName(fileName)
294
-
295
- return (
296
- baseName != null &&
297
- baseName !== 'controller' &&
298
- !baseName.endsWith('.test') &&
299
- !baseName.endsWith('.spec')
140
+ async function inspectOwnedSubtrees(
141
+ appRoot: string,
142
+ plans: OwnedSubtreePlan[],
143
+ ): Promise<OwnedSubtree[]> {
144
+ return Promise.all(
145
+ plans.map(async (plan) => ({
146
+ ...plan,
147
+ actualEntryPath: await findControllerEntryPath(appRoot, plan.entryCandidates),
148
+ })),
300
149
  )
301
150
  }
302
151
 
303
- function findOwnerPaths(scan: ControllerDirectoryScan, candidatePaths: string[]): string[] {
304
- let existingPaths: string[] = []
305
-
152
+ async function findControllerEntryPath(
153
+ appRoot: string,
154
+ candidatePaths: string[],
155
+ ): Promise<string | null> {
306
156
  for (let candidatePath of candidatePaths) {
307
- if (scan.controllerEntryPaths.has(candidatePath)) {
308
- existingPaths.push(candidatePath)
157
+ if (await pathHasType(appRoot, candidatePath, 'file')) {
158
+ return candidatePath
309
159
  }
310
160
  }
311
161
 
312
- return existingPaths
313
- }
314
-
315
- function isNestedControllerPath(filePath: string): boolean {
316
- return (
317
- filePath.startsWith(ACTIONS_PATH_PREFIX) &&
318
- filePath.slice(ACTIONS_PATH_PREFIX.length).includes('/')
319
- )
320
- }
321
-
322
- function isWithinDirectory(filePath: string, directoryPath: string): boolean {
323
- return filePath.startsWith(`${directoryPath}/`)
162
+ return null
324
163
  }
325
164
 
326
- function isDirectoryWithinDirectory(directoryPath: string, parentDirectoryPath: string): boolean {
327
- return (
328
- directoryPath === parentDirectoryPath || isWithinDirectory(directoryPath, parentDirectoryPath)
329
- )
330
- }
165
+ async function pathHasType(
166
+ appRoot: string,
167
+ relativePath: string,
168
+ type: 'directory' | 'file',
169
+ ): Promise<boolean> {
170
+ try {
171
+ let stats = await fs.stat(path.join(appRoot, relativePath))
172
+ return type === 'directory' ? stats.isDirectory() : stats.isFile()
173
+ } catch (error) {
174
+ let nodeError = error as NodeJS.ErrnoException
175
+ if (nodeError.code === 'ENOENT' || nodeError.code === 'ENOTDIR') {
176
+ return false
177
+ }
331
178
 
332
- function normalizeRelativePath(filePath: string): string {
333
- return filePath.split(path.sep).join('/')
179
+ throw error
180
+ }
334
181
  }
@@ -0,0 +1,31 @@
1
+ import type { RemixDbAdapterConfig } from './remix-config.ts'
2
+
3
+ export type DatabaseCommand = 'migrate' | 'reset' | 'seed' | 'status' | 'wipe'
4
+
5
+ export interface DatabaseCommandInvocation {
6
+ command: DatabaseCommand
7
+ connectionEnv?: string
8
+ journalTable?: string
9
+ migrations?: string
10
+ seed?: string
11
+ to?: string
12
+ }
13
+
14
+ export interface DatabaseCommandPlan {
15
+ adapter: RemixDbAdapterConfig
16
+ command: DatabaseCommand
17
+ journalTable?: string
18
+ migrations?: string
19
+ seed?: string
20
+ to?: string
21
+ }
22
+
23
+ export function isDatabaseCommand(value: unknown): value is DatabaseCommand {
24
+ return (
25
+ value === 'migrate' ||
26
+ value === 'reset' ||
27
+ value === 'seed' ||
28
+ value === 'status' ||
29
+ value === 'wipe'
30
+ )
31
+ }
@@ -2,69 +2,52 @@ import {
2
2
  ROOT_ROUTE_NAME,
3
3
  type ControllerOwnership,
4
4
  type OwnedSubtree,
5
+ type RouteDirectory,
5
6
  } from '../controller-ownership.ts'
6
- import type { DoctorFinding, DoctorFixPlan } from './types.ts'
7
+ import type { DoctorFinding } from './types.ts'
8
+
9
+ export function getControllerFindings(ownership: ControllerOwnership): DoctorFinding[] {
10
+ let missingRouteNames = new Set(
11
+ ownership.routeDirectories
12
+ .filter((directory) => !directory.exists)
13
+ .map((directory) => directory.routeName),
14
+ )
7
15
 
8
- export function getControllerFindings(
9
- ownership: ControllerOwnership,
10
- fixPlans: DoctorFixPlan[],
11
- ): DoctorFinding[] {
12
16
  return [
13
- ...getSubtreeFindings(ownership.subtrees, fixPlans),
14
- ...ownership.orphanControllerPaths.map((actualPath) => ({
15
- actualPath,
16
- code: 'orphan-controller' as const,
17
- message: `Action controller ${actualPath} does not match any route map.`,
18
- severity: 'warn' as const,
19
- suite: 'actions' as const,
20
- })),
21
- ...ownership.orphanRouteDirectoryPaths.map((actualPath) => ({
22
- actualPath,
23
- code: 'orphan-route-directory' as const,
24
- message: `Directory ${actualPath} does not match any route-map key path.`,
17
+ ...getSubtreeFindings(ownership.subtrees, missingRouteNames),
18
+ ...getRouteDirectoryFindings(ownership.routeDirectories),
19
+ ]
20
+ }
21
+
22
+ function getRouteDirectoryFindings(directories: RouteDirectory[]): DoctorFinding[] {
23
+ return directories
24
+ .filter((directory) => !directory.exists)
25
+ .map((directory) => ({
26
+ code: 'missing-route-directory' as const,
27
+ expectedPath: directory.directoryPath,
28
+ message: `Route map "${directory.routeName}" is missing action directory ${directory.directoryPath}.`,
29
+ routeName: directory.routeName,
25
30
  severity: 'warn' as const,
26
31
  suite: 'actions' as const,
27
- })),
28
- ]
32
+ }))
29
33
  }
30
34
 
31
- function getSubtreeFindings(subtrees: OwnedSubtree[], fixPlans: DoctorFixPlan[]): DoctorFinding[] {
35
+ function getSubtreeFindings(
36
+ subtrees: OwnedSubtree[],
37
+ missingRouteNames: Set<string>,
38
+ ): DoctorFinding[] {
32
39
  let findings: DoctorFinding[] = []
33
- let fixableFindingKeys = new Set(
34
- fixPlans.map((fixPlan) => `${fixPlan.code}:${fixPlan.routeName ?? ''}`),
35
- )
36
40
 
37
41
  for (let subtree of subtrees) {
38
- let routeMapName = formatRouteMapName(subtree.routeName)
39
-
40
- if (subtree.actualEntryPaths.length > 1) {
41
- findings.push({
42
- actualPath: subtree.actualEntryPaths[0],
43
- code: 'duplicate-owner-file',
44
- expectedPath: subtree.entryDisplayPath,
45
- message: `${routeMapName} has multiple action controller files: ${subtree.actualEntryPaths.join(', ')}. Keep only one controller owner file.`,
46
- routeName: subtree.routeName,
47
- severity: 'warn',
48
- suite: 'actions',
49
- })
42
+ if (missingRouteNames.has(subtree.routeName)) {
50
43
  continue
51
44
  }
52
45
 
53
46
  if (subtree.actualEntryPath == null) {
54
- let code =
55
- subtree.claimedFilePaths.length > 0
56
- ? ('incomplete-controller' as const)
57
- : ('missing-owner' as const)
58
-
59
47
  findings.push({
60
- actualPath: subtree.claimedFilePaths.length > 0 ? subtree.subtreePath : undefined,
61
- code,
48
+ code: 'missing-owner',
62
49
  expectedPath: subtree.entryDisplayPath,
63
- fixable: fixableFindingKeys.has(`${code}:${subtree.routeName}`),
64
- message:
65
- subtree.claimedFilePaths.length > 0
66
- ? `${routeMapName} has files under ${subtree.subtreePath}, but is missing action controller ${subtree.entryDisplayPath}.`
67
- : `${routeMapName} is missing action controller ${subtree.entryDisplayPath}.`,
50
+ message: `${formatRouteMapName(subtree.routeName)} is missing action controller ${subtree.entryDisplayPath}.`,
68
51
  routeName: subtree.routeName,
69
52
  severity: 'warn',
70
53
  suite: 'actions',
@@ -1,10 +1,8 @@
1
1
  import { inspectControllerOwnership, type OwnershipRouteNode } from '../controller-ownership.ts'
2
2
  import { getControllerFindings } from './controller-findings.ts'
3
- import { getControllerFixPlans } from './controller-fix-plans.ts'
4
- import { createDoctorSuite, type DoctorFixPlan, type DoctorSuiteResult } from './types.ts'
3
+ import { createDoctorSuite, type DoctorSuiteResult } from './types.ts'
5
4
 
6
5
  export interface ControllerDoctorResult {
7
- fixPlans: DoctorFixPlan[]
8
6
  suite: DoctorSuiteResult
9
7
  }
10
8
 
@@ -13,11 +11,9 @@ export async function checkControllerConventions(
13
11
  tree: OwnershipRouteNode[],
14
12
  ): Promise<ControllerDoctorResult> {
15
13
  let ownership = await inspectControllerOwnership(appRoot, tree)
16
- let fixPlans = await getControllerFixPlans(appRoot, tree, ownership)
17
- let findings = getControllerFindings(ownership, fixPlans)
14
+ let findings = getControllerFindings(ownership)
18
15
 
19
16
  return {
20
- fixPlans,
21
17
  suite: createDoctorSuite('actions', findings),
22
18
  }
23
19
  }