@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
@@ -1,4 +1,8 @@
1
- import type { ControllerOwnership, OwnedSubtree } from '../controller-ownership.ts'
1
+ import {
2
+ ROOT_ROUTE_NAME,
3
+ type ControllerOwnership,
4
+ type OwnedSubtree,
5
+ } from '../controller-ownership.ts'
2
6
  import type { DoctorFinding, DoctorFixPlan } from './types.ts'
3
7
 
4
8
  export function getControllerFindings(
@@ -7,26 +11,19 @@ export function getControllerFindings(
7
11
  ): DoctorFinding[] {
8
12
  return [
9
13
  ...getSubtreeFindings(ownership.subtrees, fixPlans),
10
- ...ownership.orphanActionPaths.map((actualPath) => ({
11
- actualPath,
12
- code: 'orphan-action' as const,
13
- message: `Standalone action ${actualPath} does not match any top-level route.`,
14
- severity: 'warn' as const,
15
- suite: 'controllers' as const,
16
- })),
17
14
  ...ownership.orphanControllerPaths.map((actualPath) => ({
18
15
  actualPath,
19
16
  code: 'orphan-controller' as const,
20
- message: `Controller ${actualPath} does not match any route group.`,
17
+ message: `Action controller ${actualPath} does not match any route map.`,
21
18
  severity: 'warn' as const,
22
- suite: 'controllers' as const,
19
+ suite: 'actions' as const,
23
20
  })),
24
21
  ...ownership.orphanRouteDirectoryPaths.map((actualPath) => ({
25
22
  actualPath,
26
23
  code: 'orphan-route-directory' as const,
27
- message: `Directory ${actualPath} does not match any route subtree.`,
24
+ message: `Directory ${actualPath} does not match any route-map key path.`,
28
25
  severity: 'warn' as const,
29
- suite: 'controllers' as const,
26
+ suite: 'actions' as const,
30
27
  })),
31
28
  ]
32
29
  }
@@ -38,71 +35,22 @@ function getSubtreeFindings(subtrees: OwnedSubtree[], fixPlans: DoctorFixPlan[])
38
35
  )
39
36
 
40
37
  for (let subtree of subtrees) {
41
- let actualEntryPath = subtree.actualEntryPath ?? undefined
42
- let actualAlternatePath = subtree.actualAlternatePath ?? undefined
38
+ let routeMapName = formatRouteMapName(subtree.routeName)
43
39
 
44
40
  if (subtree.actualEntryPaths.length > 1) {
45
41
  findings.push({
46
42
  actualPath: subtree.actualEntryPaths[0],
47
43
  code: 'duplicate-owner-file',
48
44
  expectedPath: subtree.entryDisplayPath,
49
- message: `Route "${subtree.routeName}" has multiple ${subtree.kind} files: ${subtree.actualEntryPaths.join(', ')}. Keep only one ${subtree.kind} owner file.`,
50
- routeName: subtree.routeName,
51
- severity: 'warn',
52
- suite: 'controllers',
53
- })
54
- continue
55
- }
56
-
57
- if (subtree.actualAlternatePaths.length > 1) {
58
- let alternateKind = subtree.kind === 'action' ? 'controller' : 'action'
59
-
60
- findings.push({
61
- actualPath: subtree.actualAlternatePaths[0],
62
- code: 'duplicate-owner-file',
63
- expectedPath: subtree.entryDisplayPath,
64
- message: `Route "${subtree.routeName}" has multiple ${alternateKind} files: ${subtree.actualAlternatePaths.join(', ')}. Keep only one ${alternateKind} owner file.`,
65
- routeName: subtree.routeName,
66
- severity: 'warn',
67
- suite: 'controllers',
68
- })
69
- continue
70
- }
71
-
72
- if (actualEntryPath != null && actualAlternatePath != null) {
73
- findings.push({
74
- actualPath: actualAlternatePath,
75
- code: 'ambiguous-owner',
76
- expectedPath: actualEntryPath,
77
- message:
78
- subtree.kind === 'action'
79
- ? `Route "${subtree.routeName}" has both action ${actualEntryPath} and controller ${actualAlternatePath}.`
80
- : `Route "${subtree.routeName}" has both controller ${actualEntryPath} and standalone action ${actualAlternatePath}.`,
81
- routeName: subtree.routeName,
82
- severity: 'warn',
83
- suite: 'controllers',
84
- })
85
- continue
86
- }
87
-
88
- if (actualAlternatePath != null) {
89
- findings.push({
90
- actualPath: actualAlternatePath,
91
- code: 'wrong-owner-kind',
92
- expectedPath: subtree.entryDisplayPath,
93
- message:
94
- subtree.kind === 'action'
95
- ? `Route "${subtree.routeName}" expects action ${subtree.entryDisplayPath}, but found controller ${actualAlternatePath}.`
96
- : `Route "${subtree.routeName}" expects controller ${subtree.entryDisplayPath}, but found standalone action ${actualAlternatePath}.`,
97
- fixable: fixableFindingKeys.has(`wrong-owner-kind:${subtree.routeName}`),
45
+ message: `${routeMapName} has multiple action controller files: ${subtree.actualEntryPaths.join(', ')}. Keep only one controller owner file.`,
98
46
  routeName: subtree.routeName,
99
47
  severity: 'warn',
100
- suite: 'controllers',
48
+ suite: 'actions',
101
49
  })
102
50
  continue
103
51
  }
104
52
 
105
- if (subtree.kind === 'controller' && subtree.actualEntryPath == null) {
53
+ if (subtree.actualEntryPath == null) {
106
54
  let code =
107
55
  subtree.claimedFilePaths.length > 0
108
56
  ? ('incomplete-controller' as const)
@@ -115,43 +63,18 @@ function getSubtreeFindings(subtrees: OwnedSubtree[], fixPlans: DoctorFixPlan[])
115
63
  fixable: fixableFindingKeys.has(`${code}:${subtree.routeName}`),
116
64
  message:
117
65
  subtree.claimedFilePaths.length > 0
118
- ? `Route "${subtree.routeName}" has files under ${subtree.subtreePath}, but is missing controller ${subtree.entryDisplayPath}.`
119
- : `Route "${subtree.routeName}" is missing controller ${subtree.entryDisplayPath}.`,
66
+ ? `${routeMapName} has files under ${subtree.subtreePath}, but is missing action controller ${subtree.entryDisplayPath}.`
67
+ : `${routeMapName} is missing action controller ${subtree.entryDisplayPath}.`,
120
68
  routeName: subtree.routeName,
121
69
  severity: 'warn',
122
- suite: 'controllers',
123
- })
124
- continue
125
- }
126
-
127
- if (subtree.kind === 'action' && subtree.claimedFilePaths.length > 0) {
128
- findings.push({
129
- actualPath: subtree.subtreePath,
130
- code: 'promotion-drift',
131
- expectedPath: actualEntryPath ?? subtree.entryDisplayPath,
132
- message:
133
- actualEntryPath == null
134
- ? `Route "${subtree.routeName}" has files under ${subtree.subtreePath}, but is still expected to use action ${subtree.entryDisplayPath}. Promote it to controller ${subtree.alternateDisplayPath} or move the files back into ${subtree.entryDisplayPath}.`
135
- : `Route "${subtree.routeName}" uses action ${actualEntryPath}, but also has files under ${subtree.subtreePath}. Promote it to controller ${subtree.alternateDisplayPath} or keep the route in ${actualEntryPath}.`,
136
- routeName: subtree.routeName,
137
- severity: 'warn',
138
- suite: 'controllers',
139
- })
140
- continue
141
- }
142
-
143
- if (subtree.actualEntryPath == null) {
144
- findings.push({
145
- code: 'missing-owner',
146
- expectedPath: subtree.entryDisplayPath,
147
- fixable: fixableFindingKeys.has(`missing-owner:${subtree.routeName}`),
148
- message: `Route "${subtree.routeName}" is missing action ${subtree.entryDisplayPath}.`,
149
- routeName: subtree.routeName,
150
- severity: 'warn',
151
- suite: 'controllers',
70
+ suite: 'actions',
152
71
  })
153
72
  }
154
73
  }
155
74
 
156
75
  return findings
157
76
  }
77
+
78
+ function formatRouteMapName(routeName: string): string {
79
+ return routeName === ROOT_ROUTE_NAME ? 'Root route map' : `Route map "${routeName}"`
80
+ }
@@ -11,7 +11,8 @@ import type {
11
11
  OwnedSubtree,
12
12
  OwnershipRouteNode,
13
13
  } from '../controller-ownership.ts'
14
- import { renderActionPlaceholder, renderControllerPlaceholder } from './controller-placeholders.ts'
14
+ import { ROOT_ROUTE_NAME } from '../controller-ownership.ts'
15
+ import { renderControllerPlaceholder } from './controller-placeholders.ts'
15
16
  import type { DoctorFixPlan } from './types.ts'
16
17
 
17
18
  const OWNER_EXTENSION_PRIORITY: OwnerFileExtension[] = ['.tsx', '.ts', '.jsx', '.js']
@@ -57,10 +58,7 @@ export async function getControllerFixPlans(
57
58
  continue
58
59
  }
59
60
 
60
- let contents =
61
- subtree.kind === 'action'
62
- ? renderActionPlaceholder(routeNode, entryPath)
63
- : renderControllerPlaceholder(routeNode, entryPath, resolvedEntryPathByRouteName)
61
+ let contents = renderControllerPlaceholder(routeNode, entryPath)
64
62
 
65
63
  fixPlans.push({
66
64
  code,
@@ -68,7 +66,7 @@ export async function getControllerFixPlans(
68
66
  kind: 'create-file',
69
67
  path: entryPath,
70
68
  routeName: subtree.routeName,
71
- suite: 'controllers',
69
+ suite: 'actions',
72
70
  })
73
71
  }
74
72
 
@@ -76,7 +74,7 @@ export async function getControllerFixPlans(
76
74
  }
77
75
 
78
76
  function getFixCodeForSubtree(subtree: OwnedSubtree): DoctorFixPlan['code'] | null {
79
- if (subtree.actualEntryPaths.length > 1 || subtree.actualAlternatePaths.length > 1) {
77
+ if (subtree.actualEntryPaths.length > 1) {
80
78
  return null
81
79
  }
82
80
 
@@ -84,19 +82,7 @@ function getFixCodeForSubtree(subtree: OwnedSubtree): DoctorFixPlan['code'] | nu
84
82
  return null
85
83
  }
86
84
 
87
- if (subtree.actualAlternatePath != null) {
88
- return 'wrong-owner-kind'
89
- }
90
-
91
- if (subtree.kind === 'controller') {
92
- return subtree.claimedFilePaths.length > 0 ? 'incomplete-controller' : 'missing-owner'
93
- }
94
-
95
- if (subtree.claimedFilePaths.length > 0) {
96
- return null
97
- }
98
-
99
- return 'missing-owner'
85
+ return subtree.claimedFilePaths.length > 0 ? 'incomplete-controller' : 'missing-owner'
100
86
  }
101
87
 
102
88
  function inferOwnerExtension(
@@ -109,15 +95,7 @@ function inferOwnerExtension(
109
95
  return subtreeExtension
110
96
  }
111
97
 
112
- let alternateExtension = getMostCommonOwnerExtension(subtree.actualAlternatePaths)
113
- if (alternateExtension != null) {
114
- return alternateExtension
115
- }
116
-
117
- let projectExtension = getMostCommonOwnerExtension([
118
- ...ownership.scan.actionEntryPaths,
119
- ...ownership.scan.controllerEntryPaths,
120
- ])
98
+ let projectExtension = getMostCommonOwnerExtension([...ownership.scan.controllerEntryPaths])
121
99
  if (projectExtension != null) {
122
100
  return projectExtension
123
101
  }
@@ -157,6 +135,12 @@ function getMostCommonOwnerExtension(filePaths: string[]): OwnerFileExtension |
157
135
 
158
136
  function getRouteNodesByName(tree: OwnershipRouteNode[]): Map<string, OwnershipRouteNode> {
159
137
  let routeNodesByName = new Map<string, OwnershipRouteNode>()
138
+ routeNodesByName.set(ROOT_ROUTE_NAME, {
139
+ children: tree,
140
+ key: ROOT_ROUTE_NAME,
141
+ kind: 'group',
142
+ name: ROOT_ROUTE_NAME,
143
+ })
160
144
 
161
145
  function visit(nodes: OwnershipRouteNode[]): void {
162
146
  for (let node of nodes) {
@@ -1,108 +1,19 @@
1
1
  import * as path from 'node:path'
2
2
 
3
3
  import { getOwnerFileExtension } from '../controller-files.ts'
4
- import type { OwnershipRouteNode } from '../controller-ownership.ts'
5
-
6
- const RESERVED_BINDING_IDENTIFIERS = new Set([
7
- 'await',
8
- 'break',
9
- 'case',
10
- 'catch',
11
- 'class',
12
- 'const',
13
- 'continue',
14
- 'debugger',
15
- 'default',
16
- 'delete',
17
- 'do',
18
- 'else',
19
- 'enum',
20
- 'export',
21
- 'extends',
22
- 'false',
23
- 'finally',
24
- 'for',
25
- 'function',
26
- 'if',
27
- 'implements',
28
- 'import',
29
- 'in',
30
- 'instanceof',
31
- 'interface',
32
- 'let',
33
- 'new',
34
- 'null',
35
- 'package',
36
- 'private',
37
- 'protected',
38
- 'public',
39
- 'return',
40
- 'static',
41
- 'super',
42
- 'switch',
43
- 'this',
44
- 'throw',
45
- 'true',
46
- 'try',
47
- 'typeof',
48
- 'var',
49
- 'void',
50
- 'while',
51
- 'with',
52
- 'yield',
53
- ])
54
-
55
- export function renderActionPlaceholder(routeNode: OwnershipRouteNode, entryPath: string): string {
56
- let routeMessage = `TODO: implement routes.${routeNode.name}`
57
- let extension = getOwnerFileExtension(entryPath)
58
- let exportName = getActionExportName(routeNode.key)
59
-
60
- if (extension === '.js' || extension === '.jsx') {
61
- return [
62
- `export const ${exportName} = {`,
63
- ` handler() {`,
64
- ` return new Response(${JSON.stringify(routeMessage)})`,
65
- ` },`,
66
- `}`,
67
- '',
68
- ].join('\n')
69
- }
70
-
71
- let routeExpression = getRouteAccessExpression(routeNode.name)
72
- let routesImportPath = getRelativeImportPath(entryPath, 'app/routes.ts')
73
- let method = routeNode.method ?? 'ANY'
74
-
75
- return [
76
- `import type { BuildAction } from 'remix/fetch-router'`,
77
- '',
78
- `import type { routes } from '${routesImportPath}'`,
79
- '',
80
- `export const ${exportName}: BuildAction<'${method}', typeof ${routeExpression}> = {`,
81
- ` handler() {`,
82
- ` return new Response(${JSON.stringify(routeMessage)})`,
83
- ` },`,
84
- `}`,
85
- '',
86
- ].join('\n')
87
- }
4
+ import { ROOT_ROUTE_NAME, type OwnershipRouteNode } from '../controller-ownership.ts'
88
5
 
89
6
  export function renderControllerPlaceholder(
90
7
  routeNode: OwnershipRouteNode,
91
8
  entryPath: string,
92
- resolvedEntryPathByRouteName: Map<string, string>,
93
9
  ): string {
94
10
  let extension = getOwnerFileExtension(entryPath)
95
11
  let lines: string[] = []
96
- let childImports = getControllerChildImports(routeNode, entryPath, resolvedEntryPathByRouteName)
97
- let actionEntries = routeNode.children.map((childNode) =>
98
- renderControllerActionEntry(childNode, entryPath, resolvedEntryPathByRouteName),
99
- )
12
+ let actionEntries = routeNode.children
13
+ .filter((childNode) => childNode.kind === 'route')
14
+ .map((childNode) => renderControllerActionEntry(childNode))
100
15
 
101
16
  if (extension === '.js' || extension === '.jsx') {
102
- if (childImports.length > 0) {
103
- lines.push(...childImports, '')
104
- }
105
-
106
17
  lines.push('export default {', ' actions: {')
107
18
  lines.push(...actionEntries)
108
19
  lines.push(' },', '}', '')
@@ -112,85 +23,29 @@ export function renderControllerPlaceholder(
112
23
  let routeExpression = getRouteAccessExpression(routeNode.name)
113
24
  let routesImportPath = getRelativeImportPath(entryPath, 'app/routes.ts')
114
25
 
115
- lines.push(`import type { Controller } from 'remix/fetch-router'`, '')
116
- lines.push(`import type { routes } from '${routesImportPath}'`)
117
-
118
- if (childImports.length > 0) {
119
- lines.push('', ...childImports)
120
- }
26
+ lines.push(`import { createController } from 'remix/router'`, '')
27
+ lines.push(`import { routes } from '${routesImportPath}'`)
121
28
 
122
- lines.push('', 'export default {', ' actions: {')
29
+ lines.push('', `export default createController(${routeExpression}, {`, ' actions: {')
123
30
  lines.push(...actionEntries)
124
- lines.push(' },', `} satisfies Controller<typeof ${routeExpression}>`, '')
31
+ lines.push(' },', '})', '')
125
32
 
126
33
  return lines.join('\n')
127
34
  }
128
35
 
129
- function getControllerChildImports(
130
- routeNode: OwnershipRouteNode,
131
- entryPath: string,
132
- resolvedEntryPathByRouteName: Map<string, string>,
133
- ): string[] {
134
- let imports: string[] = []
135
-
136
- for (let childNode of routeNode.children) {
137
- if (childNode.kind !== 'group') {
138
- continue
139
- }
140
-
141
- let childEntryPath = resolvedEntryPathByRouteName.get(childNode.name)
142
- if (childEntryPath == null) {
143
- continue
144
- }
145
-
146
- imports.push(
147
- `import ${getControllerImportName(childNode.key)} from '${getRelativeImportPath(entryPath, childEntryPath)}'`,
148
- )
149
- }
150
-
151
- return imports
36
+ function renderControllerActionEntry(childNode: OwnershipRouteNode): string {
37
+ return [
38
+ ` ${formatObjectKey(childNode.key)}() {`,
39
+ ` return new Response(${JSON.stringify(`TODO: implement routes.${childNode.name}`)})`,
40
+ ` },`,
41
+ ].join('\n')
152
42
  }
153
43
 
154
- function renderControllerActionEntry(
155
- childNode: OwnershipRouteNode,
156
- entryPath: string,
157
- resolvedEntryPathByRouteName: Map<string, string>,
158
- ): string {
159
- if (childNode.kind === 'route') {
160
- return [
161
- ` ${formatObjectKey(childNode.key)}() {`,
162
- ` return new Response(${JSON.stringify(`TODO: implement routes.${childNode.name}`)})`,
163
- ` },`,
164
- ].join('\n')
165
- }
166
-
167
- let childEntryPath = resolvedEntryPathByRouteName.get(childNode.name)
168
- if (childEntryPath != null) {
169
- return ` ${formatObjectKey(childNode.key)}: ${getControllerImportName(childNode.key)},`
44
+ function getRouteAccessExpression(routeName: string): string {
45
+ if (routeName === ROOT_ROUTE_NAME) {
46
+ return 'routes'
170
47
  }
171
48
 
172
- return ` ${formatObjectKey(childNode.key)}: ${renderInlineControllerPlaceholder(childNode)},`
173
- }
174
-
175
- function renderInlineControllerPlaceholder(routeNode: OwnershipRouteNode): string {
176
- let childEntries = routeNode.children.map((childNode) =>
177
- childNode.kind === 'route'
178
- ? `${formatObjectKey(childNode.key)}() { return new Response(${JSON.stringify(`TODO: implement routes.${childNode.name}`)}) }`
179
- : `${formatObjectKey(childNode.key)}: ${renderInlineControllerPlaceholder(childNode)}`,
180
- )
181
-
182
- return `{ actions: { ${childEntries.join(', ')} } }`
183
- }
184
-
185
- function getControllerImportName(key: string): string {
186
- return `${toIdentifier(key)}Controller`
187
- }
188
-
189
- function getActionExportName(key: string): string {
190
- return toIdentifier(key)
191
- }
192
-
193
- function getRouteAccessExpression(routeName: string): string {
194
49
  let segments = routeName.split('.')
195
50
  let expression = 'routes'
196
51
 
@@ -210,33 +65,6 @@ function formatObjectKey(key: string): string {
210
65
  return isIdentifier(key) ? key : JSON.stringify(key)
211
66
  }
212
67
 
213
- function toIdentifier(value: string): string {
214
- let parts = value.split(/[^A-Za-z0-9_$]+/).filter(Boolean)
215
- if (parts.length === 0) {
216
- return 'route'
217
- }
218
-
219
- let identifier = parts
220
- .map((part, index) =>
221
- index === 0 ? part.replace(/^./, (char) => char.toLowerCase()) : capitalize(part),
222
- )
223
- .join('')
224
-
225
- if (!/^[A-Za-z_$]/.test(identifier)) {
226
- return `route${capitalize(identifier)}`
227
- }
228
-
229
- if (RESERVED_BINDING_IDENTIFIERS.has(identifier)) {
230
- return `route${capitalize(identifier)}`
231
- }
232
-
233
- return identifier
234
- }
235
-
236
- function capitalize(value: string): string {
237
- return value.length === 0 ? value : `${value[0].toUpperCase()}${value.slice(1)}`
238
- }
239
-
240
68
  function isIdentifier(value: string): boolean {
241
69
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value)
242
70
  }
@@ -18,6 +18,6 @@ export async function checkControllerConventions(
18
18
 
19
19
  return {
20
20
  fixPlans,
21
- suite: createDoctorSuite('controllers', findings),
21
+ suite: createDoctorSuite('actions', findings),
22
22
  }
23
23
  }