@remix-run/cli 0.1.0 → 0.3.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 (110) hide show
  1. package/README.md +0 -3
  2. package/dist/lib/bootstrap-project.d.ts.map +1 -1
  3. package/dist/lib/bootstrap-project.js +32 -9
  4. package/dist/lib/cli.d.ts +29 -0
  5. package/dist/lib/cli.d.ts.map +1 -1
  6. package/dist/lib/cli.js +33 -14
  7. package/dist/lib/commands/completion.d.ts.map +1 -1
  8. package/dist/lib/commands/completion.js +5 -1
  9. package/dist/lib/commands/doctor.js +18 -18
  10. package/dist/lib/commands/help.d.ts.map +1 -1
  11. package/dist/lib/commands/help.js +9 -33
  12. package/dist/lib/commands/routes.js +3 -3
  13. package/dist/lib/commands/test.d.ts +1 -1
  14. package/dist/lib/commands/test.d.ts.map +1 -1
  15. package/dist/lib/commands/test.js +8 -4
  16. package/dist/lib/completion.d.ts.map +1 -1
  17. package/dist/lib/completion.js +4 -106
  18. package/dist/lib/controller-files.d.ts +0 -1
  19. package/dist/lib/controller-files.d.ts.map +1 -1
  20. package/dist/lib/controller-files.js +3 -5
  21. package/dist/lib/controller-ownership.d.ts +9 -9
  22. package/dist/lib/controller-ownership.d.ts.map +1 -1
  23. package/dist/lib/controller-ownership.js +56 -91
  24. package/dist/lib/doctor/controller-findings.d.ts +1 -1
  25. package/dist/lib/doctor/controller-findings.d.ts.map +1 -1
  26. package/dist/lib/doctor/controller-findings.js +15 -87
  27. package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -1
  28. package/dist/lib/doctor/controller-fix-plans.js +13 -24
  29. package/dist/lib/doctor/controller-placeholders.d.ts +2 -3
  30. package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -1
  31. package/dist/lib/doctor/controller-placeholders.js +18 -149
  32. package/dist/lib/doctor/controllers.js +1 -1
  33. package/dist/lib/doctor/project.js +60 -52
  34. package/dist/lib/doctor/types.d.ts +2 -2
  35. package/dist/lib/doctor/types.d.ts.map +1 -1
  36. package/dist/lib/errors.d.ts +0 -6
  37. package/dist/lib/errors.d.ts.map +1 -1
  38. package/dist/lib/errors.js +0 -11
  39. package/dist/lib/load-route-map-worker.js +17 -9
  40. package/dist/lib/route-map.d.ts +1 -1
  41. package/dist/lib/route-map.d.ts.map +1 -1
  42. package/dist/lib/route-map.js +29 -17
  43. package/package.json +4 -5
  44. package/src/lib/bootstrap-project.ts +39 -13
  45. package/src/lib/cli.ts +46 -15
  46. package/src/lib/commands/completion.ts +6 -1
  47. package/src/lib/commands/doctor.ts +18 -21
  48. package/src/lib/commands/help.ts +9 -43
  49. package/src/lib/commands/routes.ts +3 -3
  50. package/src/lib/commands/test.ts +10 -4
  51. package/src/lib/completion.ts +4 -151
  52. package/src/lib/controller-files.ts +4 -8
  53. package/src/lib/controller-ownership.ts +78 -141
  54. package/src/lib/doctor/controller-findings.ts +20 -97
  55. package/src/lib/doctor/controller-fix-plans.ts +13 -29
  56. package/src/lib/doctor/controller-placeholders.ts +17 -189
  57. package/src/lib/doctor/controllers.ts +1 -1
  58. package/src/lib/doctor/project.ts +60 -52
  59. package/src/lib/doctor/types.ts +1 -5
  60. package/src/lib/errors.ts +0 -12
  61. package/src/lib/load-route-map-worker.ts +19 -10
  62. package/src/lib/route-map.ts +61 -16
  63. package/template/.agents/skills/remix/SKILL.md +588 -0
  64. package/template/.agents/skills/remix/references/animate-elements.md +195 -0
  65. package/template/.agents/skills/remix/references/assets-and-browser-modules.md +130 -0
  66. package/template/.agents/skills/remix/references/auth-and-sessions.md +443 -0
  67. package/template/.agents/skills/remix/references/component-model.md +282 -0
  68. package/template/.agents/skills/remix/references/create-mixins.md +158 -0
  69. package/template/.agents/skills/remix/references/data-and-validation.md +379 -0
  70. package/template/.agents/skills/remix/references/hydration-frames-navigation.md +297 -0
  71. package/template/.agents/skills/remix/references/middleware-and-server.md +233 -0
  72. package/template/.agents/skills/remix/references/mixins-styling-events.md +213 -0
  73. package/template/.agents/skills/remix/references/routing-and-controllers.md +391 -0
  74. package/template/.agents/skills/remix/references/testing-patterns.md +172 -0
  75. package/{bootstrap → template}/AGENTS.md +13 -8
  76. package/template/README.md +29 -0
  77. package/template/app/actions/controller.tsx +18 -0
  78. package/template/app/assets/entry.ts +8 -0
  79. package/template/app/assets/prompt-button.tsx +163 -0
  80. package/template/app/assets.ts +20 -0
  81. package/template/app/middleware/render.tsx +43 -0
  82. package/template/app/router.ts +20 -0
  83. package/template/app/routes.ts +6 -0
  84. package/template/app/ui/document.tsx +33 -0
  85. package/template/app/ui/scaffold-home-page.tsx +527 -0
  86. package/template/gitignore +4 -0
  87. package/{bootstrap → template}/package.json +5 -6
  88. package/template/public/favicon.svg +11 -0
  89. package/{bootstrap → template}/server.ts +4 -5
  90. package/{bootstrap → template}/tsconfig.json +3 -3
  91. package/bootstrap/README.md +0 -27
  92. package/bootstrap/app/controllers/auth.tsx +0 -21
  93. package/bootstrap/app/controllers/home.tsx +0 -26
  94. package/bootstrap/app/router.ts +0 -10
  95. package/bootstrap/app/routes.ts +0 -6
  96. package/bootstrap/app/ui/document.tsx +0 -21
  97. package/bootstrap/app/ui/layout.tsx +0 -22
  98. package/bootstrap/app/utils/render.tsx +0 -7
  99. package/dist/lib/commands/skills.d.ts +0 -6
  100. package/dist/lib/commands/skills.d.ts.map +0 -1
  101. package/dist/lib/commands/skills.js +0 -222
  102. package/dist/lib/skills-cache.d.ts +0 -19
  103. package/dist/lib/skills-cache.d.ts.map +0 -1
  104. package/dist/lib/skills-cache.js +0 -89
  105. package/dist/lib/skills.d.ts +0 -30
  106. package/dist/lib/skills.d.ts.map +0 -1
  107. package/dist/lib/skills.js +0 -441
  108. package/src/lib/commands/skills.ts +0 -306
  109. package/src/lib/skills-cache.ts +0 -140
  110. package/src/lib/skills.ts +0 -706
@@ -4,27 +4,8 @@ export interface CompletionResult {
4
4
  }
5
5
 
6
6
  const COMPLETION_SHELLS = ['bash', 'zsh'] as const
7
- const HELP_COMMANDS = [
8
- 'completion',
9
- 'doctor',
10
- 'help',
11
- 'new',
12
- 'routes',
13
- 'skills',
14
- 'test',
15
- 'version',
16
- ] as const
17
- const ROOT_COMMANDS = [
18
- 'completion',
19
- 'doctor',
20
- 'help',
21
- 'new',
22
- 'routes',
23
- 'skills',
24
- 'test',
25
- 'version',
26
- ] as const
27
- const SKILLS_COMMANDS = ['install', 'list'] as const
7
+ const HELP_COMMANDS = ['completion', 'doctor', 'help', 'new', 'routes', 'test', 'version'] as const
8
+ const ROOT_COMMANDS = ['completion', 'doctor', 'help', 'new', 'routes', 'test', 'version'] as const
28
9
 
29
10
  export type CompletionShell = (typeof COMPLETION_SHELLS)[number]
30
11
 
@@ -183,20 +164,12 @@ function completeCommand(
183
164
  return completeRoutes(tokens, currentWord, usedGlobalFlags)
184
165
  }
185
166
 
186
- if (command === 'skills') {
187
- return completeSkills(tokens, currentWord, usedGlobalFlags)
188
- }
189
-
190
167
  if (command === 'version') {
191
168
  return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, [])
192
169
  }
193
170
 
194
171
  if (command === 'test') {
195
- return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, [
196
- '--coverage',
197
- '--watch',
198
- '--help',
199
- ])
172
+ return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, ['--coverage', '--watch'])
200
173
  }
201
174
 
202
175
  if (command === 'completion') {
@@ -220,12 +193,8 @@ function completeHelp(
220
193
  return completeValues(withHelpFlags([...HELP_COMMANDS], usedGlobalFlags), currentWord)
221
194
  }
222
195
 
223
- let [topic, ...rest] = filteredTokens
196
+ let [, ...rest] = filteredTokens
224
197
  if (rest.length === 0) {
225
- if (topic === 'skills') {
226
- return completeValues(withHelpFlags([...SKILLS_COMMANDS], usedGlobalFlags), currentWord)
227
- }
228
-
229
198
  return completeValues([], currentWord)
230
199
  }
231
200
 
@@ -344,122 +313,6 @@ function completeRoutes(
344
313
  return completeValues(flags, currentWord)
345
314
  }
346
315
 
347
- function completeSkills(
348
- tokens: string[],
349
- currentWord: string,
350
- usedGlobalFlags: Set<string>,
351
- ): CompletionResult {
352
- let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags)
353
- if (filteredTokens == null) {
354
- return completeValues([], currentWord)
355
- }
356
-
357
- if (filteredTokens.length === 0) {
358
- return completeValues(withHelpFlags([...SKILLS_COMMANDS], usedGlobalFlags), currentWord)
359
- }
360
-
361
- let [subcommand, ...rest] = filteredTokens
362
- if (subcommand === 'install') {
363
- return completeSkillsInstall(rest, currentWord, usedGlobalFlags)
364
- }
365
-
366
- if (subcommand === 'list') {
367
- return completeSkillsList(rest, currentWord, usedGlobalFlags)
368
- }
369
-
370
- return completeValues([], currentWord)
371
- }
372
-
373
- function completeSkillsInstall(
374
- tokens: string[],
375
- currentWord: string,
376
- usedGlobalFlags: Set<string>,
377
- ): CompletionResult {
378
- let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags)
379
- if (filteredTokens == null) {
380
- return completeValues([], currentWord)
381
- }
382
-
383
- let hasDir = false
384
- let expectsDir = false
385
-
386
- for (let token of filteredTokens) {
387
- if (expectsDir) {
388
- expectsDir = false
389
- continue
390
- }
391
-
392
- if (token === '--dir') {
393
- hasDir = true
394
- expectsDir = true
395
- continue
396
- }
397
-
398
- return completeValues([], currentWord)
399
- }
400
-
401
- if (expectsDir) {
402
- return { mode: 'files' }
403
- }
404
-
405
- return completeValues(withHelpFlags(!hasDir ? ['--dir'] : [], usedGlobalFlags), currentWord)
406
- }
407
-
408
- function completeSkillsList(
409
- tokens: string[],
410
- currentWord: string,
411
- usedGlobalFlags: Set<string>,
412
- ): CompletionResult {
413
- return completeSkillsDirectoryCommand(tokens, currentWord, usedGlobalFlags)
414
- }
415
-
416
- function completeSkillsDirectoryCommand(
417
- tokens: string[],
418
- currentWord: string,
419
- usedGlobalFlags: Set<string>,
420
- ): CompletionResult {
421
- let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags)
422
- if (filteredTokens == null) {
423
- return completeValues([], currentWord)
424
- }
425
-
426
- let hasDir = false
427
- let hasJson = false
428
- let expectsDir = false
429
-
430
- for (let token of filteredTokens) {
431
- if (expectsDir) {
432
- expectsDir = false
433
- continue
434
- }
435
-
436
- if (token === '--dir') {
437
- hasDir = true
438
- expectsDir = true
439
- continue
440
- }
441
-
442
- if (token === '--json') {
443
- hasJson = true
444
- continue
445
- }
446
-
447
- return completeValues([], currentWord)
448
- }
449
-
450
- if (expectsDir) {
451
- return { mode: 'files' }
452
- }
453
-
454
- return completeValues(
455
- withHelpFlags(
456
- [...(!hasDir ? ['--dir'] : []), ...(!hasJson ? ['--json'] : [])],
457
- usedGlobalFlags,
458
- ),
459
- currentWord,
460
- )
461
- }
462
-
463
316
  function completeCompletionCommand(
464
317
  tokens: string[],
465
318
  currentWord: string,
@@ -1,22 +1,18 @@
1
1
  import * as path from 'node:path'
2
2
 
3
3
  const OWNER_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'] as const
4
- export type OwnerFileExtension = (typeof OWNER_FILE_EXTENSIONS)[number]
4
+ const ACTIONS_DIRECTORY = path.join('app', 'actions')
5
5
 
6
- export function getActionOwnerCandidates(segments: string[]): string[] {
7
- return OWNER_FILE_EXTENSIONS.map((extension) =>
8
- normalizeRelativePath(path.join('app', 'controllers', `${path.join(...segments)}${extension}`)),
9
- )
10
- }
6
+ export type OwnerFileExtension = (typeof OWNER_FILE_EXTENSIONS)[number]
11
7
 
12
8
  export function getControllerOwnerCandidates(segments: string[]): string[] {
13
9
  return OWNER_FILE_EXTENSIONS.map((extension) =>
14
- normalizeRelativePath(path.join('app', 'controllers', ...segments, `controller${extension}`)),
10
+ normalizeRelativePath(path.join(ACTIONS_DIRECTORY, ...segments, `controller${extension}`)),
15
11
  )
16
12
  }
17
13
 
18
14
  export function getRouteSubtreePath(segments: string[]): string {
19
- return normalizeRelativePath(path.join('app', 'controllers', ...segments))
15
+ return normalizeRelativePath(path.join(ACTIONS_DIRECTORY, ...segments))
20
16
  }
21
17
 
22
18
  export function getPreferredOwnerDisplayPath(candidates: string[]): string {
@@ -2,16 +2,18 @@ import * as fs from 'node:fs/promises'
2
2
  import * as path from 'node:path'
3
3
 
4
4
  import {
5
- getActionOwnerCandidates,
6
5
  getControllerOwnerCandidates,
7
6
  getOwnerModuleBaseName,
8
7
  getPreferredOwnerDisplayPath,
9
8
  getRouteSubtreePath,
10
- isActionFileName,
11
9
  isControllerEntryFileName,
12
10
  toDiskSegment,
13
11
  } from './controller-files.ts'
14
- import type { RouteOwnerKind, RouteTreeNodeKind } from './route-map.ts'
12
+ import type { RouteTreeNodeKind } from './route-map.ts'
13
+
14
+ const ACTIONS_PATH_PREFIX = 'app/actions/'
15
+
16
+ export const ROOT_ROUTE_NAME = '<root>'
15
17
 
16
18
  export interface OwnershipRouteNode {
17
19
  children: OwnershipRouteNode[]
@@ -22,25 +24,24 @@ export interface OwnershipRouteNode {
22
24
  }
23
25
 
24
26
  export interface ControllerDirectoryScan {
25
- actionEntryPaths: Set<string>
26
27
  controllerEntryPaths: Set<string>
27
- rootDirectoryPaths: Set<string>
28
+ routeDirectoryPaths: Set<string>
28
29
  routeLocalFilePaths: Set<string>
29
30
  }
30
31
 
32
+ export interface RouteDirectoryPlan {
33
+ directoryPath: string
34
+ routeName: string
35
+ }
36
+
31
37
  export interface OwnedSubtreePlan {
32
- alternateCandidates: string[]
33
- alternateDisplayPath: string
34
38
  entryCandidates: string[]
35
39
  entryDisplayPath: string
36
- kind: RouteOwnerKind
37
40
  routeName: string
38
41
  subtreePath: string
39
42
  }
40
43
 
41
44
  export interface OwnedSubtree extends OwnedSubtreePlan {
42
- actualAlternatePath: string | null
43
- actualAlternatePaths: string[]
44
45
  actualEntryPath: string | null
45
46
  actualEntryPaths: string[]
46
47
  claimedFilePaths: string[]
@@ -48,9 +49,9 @@ export interface OwnedSubtree extends OwnedSubtreePlan {
48
49
  }
49
50
 
50
51
  export interface ControllerOwnership {
51
- orphanActionPaths: string[]
52
52
  orphanControllerPaths: string[]
53
53
  orphanRouteDirectoryPaths: string[]
54
+ routeDirectories: RouteDirectoryPlan[]
54
55
  scan: ControllerDirectoryScan
55
56
  subtrees: OwnedSubtree[]
56
57
  }
@@ -60,13 +61,14 @@ export async function inspectControllerOwnership(
60
61
  tree: OwnershipRouteNode[],
61
62
  ): Promise<ControllerOwnership> {
62
63
  let subtreePlans = buildOwnedSubtrees(tree)
64
+ let routeDirectories = buildRouteDirectories(tree)
63
65
  let scan = await scanControllersDirectory(appRoot)
64
66
  let subtrees = applyScanToSubtrees(subtreePlans, scan)
65
67
 
66
68
  return {
67
- orphanActionPaths: getOrphanActionPaths(subtreePlans, scan),
68
69
  orphanControllerPaths: getOrphanControllerPaths(subtreePlans, scan),
69
- orphanRouteDirectoryPaths: getOrphanRouteDirectoryPaths(subtreePlans, scan),
70
+ orphanRouteDirectoryPaths: getOrphanRouteDirectoryPaths(routeDirectories, scan),
71
+ routeDirectories,
70
72
  scan,
71
73
  subtrees,
72
74
  }
@@ -77,52 +79,69 @@ export function buildOwnedSubtrees(
77
79
  parentSegments: string[] = [],
78
80
  subtrees: OwnedSubtreePlan[] = [],
79
81
  ): OwnedSubtreePlan[] {
80
- for (let node of tree) {
81
- let segments = [...parentSegments, toDiskSegment(node.key)]
82
+ if (
83
+ parentSegments.length === 0 &&
84
+ subtrees.length === 0 &&
85
+ tree.some((node) => node.kind === 'route')
86
+ ) {
87
+ addSubtreePlan(ROOT_ROUTE_NAME, [], subtrees)
88
+ }
82
89
 
83
- if (node.kind === 'group') {
84
- let alternateCandidates = getActionOwnerCandidates(segments)
85
- let entryCandidates = getControllerOwnerCandidates(segments)
86
-
87
- subtrees.push({
88
- alternateCandidates,
89
- alternateDisplayPath: getPreferredOwnerDisplayPath(alternateCandidates),
90
- entryCandidates,
91
- entryDisplayPath: getPreferredOwnerDisplayPath(entryCandidates),
92
- kind: 'controller',
93
- routeName: node.name,
94
- subtreePath: getRouteSubtreePath(segments),
95
- })
96
- buildOwnedSubtrees(node.children, segments, subtrees)
90
+ for (let node of tree) {
91
+ if (node.kind !== 'group') {
97
92
  continue
98
93
  }
99
94
 
100
- if (parentSegments.length > 0) {
101
- continue
95
+ let segments = [...parentSegments, toDiskSegment(node.key)]
96
+ if (hasDirectRouteLeaf(node.children)) {
97
+ addSubtreePlan(node.name, segments, subtrees)
102
98
  }
99
+ buildOwnedSubtrees(node.children, segments, subtrees)
100
+ }
101
+
102
+ return subtrees
103
+ }
103
104
 
104
- let alternateCandidates = getControllerOwnerCandidates(segments)
105
- let entryCandidates = getActionOwnerCandidates(segments)
105
+ export function buildRouteDirectories(
106
+ tree: OwnershipRouteNode[],
107
+ parentSegments: string[] = [],
108
+ directories: RouteDirectoryPlan[] = [],
109
+ ): RouteDirectoryPlan[] {
110
+ for (let node of tree) {
111
+ if (node.kind !== 'group') {
112
+ continue
113
+ }
106
114
 
107
- subtrees.push({
108
- alternateCandidates,
109
- alternateDisplayPath: getPreferredOwnerDisplayPath(alternateCandidates),
110
- entryCandidates,
111
- entryDisplayPath: getPreferredOwnerDisplayPath(entryCandidates),
112
- kind: 'action',
115
+ let segments = [...parentSegments, toDiskSegment(node.key)]
116
+ directories.push({
117
+ directoryPath: getRouteSubtreePath(segments),
113
118
  routeName: node.name,
114
- subtreePath: getRouteSubtreePath(segments),
115
119
  })
120
+ buildRouteDirectories(node.children, segments, directories)
116
121
  }
117
122
 
118
- return subtrees
123
+ return directories
124
+ }
125
+
126
+ function hasDirectRouteLeaf(tree: OwnershipRouteNode[]): boolean {
127
+ return tree.some((node) => node.kind === 'route')
128
+ }
129
+
130
+ function addSubtreePlan(routeName: string, segments: string[], subtrees: OwnedSubtreePlan[]): void {
131
+ let entryCandidates = getControllerOwnerCandidates(segments)
132
+
133
+ subtrees.push({
134
+ entryCandidates,
135
+ entryDisplayPath: getPreferredOwnerDisplayPath(entryCandidates),
136
+ routeName,
137
+ subtreePath: getRouteSubtreePath(segments),
138
+ })
119
139
  }
120
140
 
121
141
  async function scanControllersDirectory(appRoot: string): Promise<ControllerDirectoryScan> {
122
- let controllersDir = path.join(appRoot, 'app', 'controllers')
142
+ let actionsDir = path.join(appRoot, 'app', 'actions')
123
143
  let controllerEntryPaths = new Set<string>()
124
- let actionEntryPaths = new Set<string>()
125
- let rootDirectoryPaths = new Set<string>()
144
+ let routeDirectoryPaths = new Set<string>()
126
145
  let routeLocalFilePaths = new Set<string>()
127
146
 
128
147
  async function walk(currentDir: string, isRoot: boolean): Promise<void> {
@@ -143,10 +162,7 @@ async function scanControllersDirectory(appRoot: string): Promise<ControllerDire
143
162
  let relativePath = normalizeRelativePath(path.relative(appRoot, entryPath))
144
163
 
145
164
  if (entry.isDirectory()) {
146
- if (isRoot) {
147
- rootDirectoryPaths.add(relativePath)
148
- }
149
-
165
+ routeDirectoryPaths.add(relativePath)
150
166
  await walk(entryPath, false)
151
167
  continue
152
168
  }
@@ -160,23 +176,17 @@ async function scanControllersDirectory(appRoot: string): Promise<ControllerDire
160
176
  continue
161
177
  }
162
178
 
163
- if (isRoot && isActionCandidate(entry.name)) {
164
- actionEntryPaths.add(relativePath)
165
- continue
166
- }
167
-
168
- if (!isRoot && isRouteLocalFileName(entry.name)) {
179
+ if (isRouteLocalFileName(entry.name)) {
169
180
  routeLocalFilePaths.add(relativePath)
170
181
  }
171
182
  }
172
183
  }
173
184
 
174
- await walk(controllersDir, true)
185
+ await walk(actionsDir, true)
175
186
 
176
187
  return {
177
- actionEntryPaths,
178
188
  controllerEntryPaths,
179
- rootDirectoryPaths,
189
+ routeDirectoryPaths,
180
190
  routeLocalFilePaths,
181
191
  }
182
192
  }
@@ -192,17 +202,10 @@ function applyScanToSubtrees(
192
202
  let claimedContentPaths = claimFilesToDeepestSubtree(getNestedContentPaths(scan), subtreePlans)
193
203
 
194
204
  return subtreePlans.map((subtree) => {
195
- let actualAlternatePaths = findOwnerPaths(
196
- scan,
197
- invertOwnerKind(subtree.kind),
198
- subtree.alternateCandidates,
199
- )
200
- let actualEntryPaths = findOwnerPaths(scan, subtree.kind, subtree.entryCandidates)
205
+ let actualEntryPaths = findOwnerPaths(scan, subtree.entryCandidates)
201
206
 
202
207
  return {
203
208
  ...subtree,
204
- actualAlternatePath: actualAlternatePaths[0] ?? null,
205
- actualAlternatePaths,
206
209
  actualEntryPath: actualEntryPaths[0] ?? null,
207
210
  actualEntryPaths,
208
211
  claimedFilePaths: claimedContentPaths.get(subtree.routeName) ?? [],
@@ -253,61 +256,30 @@ function claimFilesToDeepestSubtree(
253
256
  return claims
254
257
  }
255
258
 
256
- function getOrphanActionPaths(
257
- subtreePlans: OwnedSubtreePlan[],
258
- scan: ControllerDirectoryScan,
259
- ): string[] {
260
- let expectedActionPaths = new Set(
261
- subtreePlans
262
- .filter((subtree) => subtree.kind === 'action')
263
- .flatMap((subtree) => subtree.entryCandidates),
264
- )
265
- let alternateActionPaths = new Set(
266
- subtreePlans
267
- .filter((subtree) => subtree.kind === 'controller')
268
- .flatMap((subtree) => subtree.alternateCandidates),
269
- )
270
-
271
- return [...scan.actionEntryPaths]
272
- .filter((filePath) => !expectedActionPaths.has(filePath))
273
- .filter((filePath) => !alternateActionPaths.has(filePath))
274
- .sort()
275
- }
276
-
277
259
  function getOrphanControllerPaths(
278
260
  subtreePlans: OwnedSubtreePlan[],
279
261
  scan: ControllerDirectoryScan,
280
262
  ): string[] {
281
- let expectedControllerPaths = new Set(
282
- subtreePlans
283
- .filter((subtree) => subtree.kind === 'controller')
284
- .flatMap((subtree) => subtree.entryCandidates),
285
- )
286
- let alternateControllerPaths = new Set(
287
- subtreePlans
288
- .filter((subtree) => subtree.kind === 'action')
289
- .flatMap((subtree) => subtree.alternateCandidates),
290
- )
263
+ let expectedControllerPaths = new Set(subtreePlans.flatMap((subtree) => subtree.entryCandidates))
291
264
 
292
265
  return [...scan.controllerEntryPaths]
293
266
  .filter((filePath) => !expectedControllerPaths.has(filePath))
294
- .filter((filePath) => !alternateControllerPaths.has(filePath))
295
267
  .sort()
296
268
  }
297
269
 
298
270
  function getOrphanRouteDirectoryPaths(
299
- subtreePlans: OwnedSubtreePlan[],
271
+ routeDirectories: RouteDirectoryPlan[],
300
272
  scan: ControllerDirectoryScan,
301
273
  ): string[] {
302
- let expectedRootDirectories = new Set(
303
- subtreePlans.map((subtree) => getTopLevelSubtreePath(subtree.subtreePath)),
274
+ let expectedRouteDirectories = new Set(
275
+ routeDirectories.map((routeDirectory) => routeDirectory.directoryPath),
304
276
  )
305
277
  let actualControllerDirectories = [...scan.controllerEntryPaths].map((controllerPath) =>
306
278
  normalizeRelativePath(path.dirname(controllerPath)),
307
279
  )
308
280
 
309
- return [...scan.rootDirectoryPaths]
310
- .filter((directoryPath) => !expectedRootDirectories.has(directoryPath))
281
+ return [...scan.routeDirectoryPaths]
282
+ .filter((directoryPath) => !expectedRouteDirectories.has(directoryPath))
311
283
  .filter(
312
284
  (directoryPath) =>
313
285
  !actualControllerDirectories.some((controllerPath) =>
@@ -317,24 +289,6 @@ function getOrphanRouteDirectoryPaths(
317
289
  .sort()
318
290
  }
319
291
 
320
- function hasOwnerPath(
321
- scan: ControllerDirectoryScan,
322
- kind: RouteOwnerKind,
323
- filePath: string,
324
- ): boolean {
325
- return kind === 'action'
326
- ? scan.actionEntryPaths.has(filePath)
327
- : scan.controllerEntryPaths.has(filePath)
328
- }
329
-
330
- function invertOwnerKind(kind: RouteOwnerKind): RouteOwnerKind {
331
- return kind === 'action' ? 'controller' : 'action'
332
- }
333
-
334
- function isActionCandidate(fileName: string): boolean {
335
- return isActionFileName(fileName)
336
- }
337
-
338
292
  function isRouteLocalFileName(fileName: string): boolean {
339
293
  let baseName = getOwnerModuleBaseName(fileName)
340
294
 
@@ -346,23 +300,11 @@ function isRouteLocalFileName(fileName: string): boolean {
346
300
  )
347
301
  }
348
302
 
349
- function findOwnerPath(
350
- scan: ControllerDirectoryScan,
351
- kind: RouteOwnerKind,
352
- candidatePaths: string[],
353
- ): string | null {
354
- return findOwnerPaths(scan, kind, candidatePaths)[0] ?? null
355
- }
356
-
357
- function findOwnerPaths(
358
- scan: ControllerDirectoryScan,
359
- kind: RouteOwnerKind,
360
- candidatePaths: string[],
361
- ): string[] {
303
+ function findOwnerPaths(scan: ControllerDirectoryScan, candidatePaths: string[]): string[] {
362
304
  let existingPaths: string[] = []
363
305
 
364
306
  for (let candidatePath of candidatePaths) {
365
- if (hasOwnerPath(scan, kind, candidatePath)) {
307
+ if (scan.controllerEntryPaths.has(candidatePath)) {
366
308
  existingPaths.push(candidatePath)
367
309
  }
368
310
  }
@@ -372,8 +314,8 @@ function findOwnerPaths(
372
314
 
373
315
  function isNestedControllerPath(filePath: string): boolean {
374
316
  return (
375
- filePath.startsWith('app/controllers/') &&
376
- filePath.slice('app/controllers/'.length).includes('/')
317
+ filePath.startsWith(ACTIONS_PATH_PREFIX) &&
318
+ filePath.slice(ACTIONS_PATH_PREFIX.length).includes('/')
377
319
  )
378
320
  }
379
321
 
@@ -387,11 +329,6 @@ function isDirectoryWithinDirectory(directoryPath: string, parentDirectoryPath:
387
329
  )
388
330
  }
389
331
 
390
- function getTopLevelSubtreePath(subtreePath: string): string {
391
- let subtreeSegments = subtreePath.slice('app/controllers/'.length).split('/')
392
- return getRouteSubtreePath([subtreeSegments[0]])
393
- }
394
-
395
332
  function normalizeRelativePath(filePath: string): string {
396
333
  return filePath.split(path.sep).join('/')
397
334
  }