@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.
- package/README.md +0 -3
- package/dist/lib/bootstrap-project.d.ts.map +1 -1
- package/dist/lib/bootstrap-project.js +32 -9
- package/dist/lib/cli.d.ts +29 -0
- package/dist/lib/cli.d.ts.map +1 -1
- package/dist/lib/cli.js +33 -14
- package/dist/lib/commands/completion.d.ts.map +1 -1
- package/dist/lib/commands/completion.js +5 -1
- package/dist/lib/commands/doctor.js +18 -18
- package/dist/lib/commands/help.d.ts.map +1 -1
- package/dist/lib/commands/help.js +9 -33
- package/dist/lib/commands/routes.js +3 -3
- package/dist/lib/commands/test.d.ts +1 -1
- package/dist/lib/commands/test.d.ts.map +1 -1
- package/dist/lib/commands/test.js +8 -4
- package/dist/lib/completion.d.ts.map +1 -1
- package/dist/lib/completion.js +4 -106
- package/dist/lib/controller-files.d.ts +0 -1
- package/dist/lib/controller-files.d.ts.map +1 -1
- package/dist/lib/controller-files.js +3 -5
- package/dist/lib/controller-ownership.d.ts +9 -9
- package/dist/lib/controller-ownership.d.ts.map +1 -1
- package/dist/lib/controller-ownership.js +56 -91
- package/dist/lib/doctor/controller-findings.d.ts +1 -1
- package/dist/lib/doctor/controller-findings.d.ts.map +1 -1
- package/dist/lib/doctor/controller-findings.js +15 -87
- package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -1
- package/dist/lib/doctor/controller-fix-plans.js +13 -24
- package/dist/lib/doctor/controller-placeholders.d.ts +2 -3
- package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -1
- package/dist/lib/doctor/controller-placeholders.js +18 -149
- package/dist/lib/doctor/controllers.js +1 -1
- package/dist/lib/doctor/project.js +60 -52
- package/dist/lib/doctor/types.d.ts +2 -2
- package/dist/lib/doctor/types.d.ts.map +1 -1
- package/dist/lib/errors.d.ts +0 -6
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +0 -11
- package/dist/lib/load-route-map-worker.js +17 -9
- package/dist/lib/route-map.d.ts +1 -1
- package/dist/lib/route-map.d.ts.map +1 -1
- package/dist/lib/route-map.js +29 -17
- package/package.json +4 -5
- package/src/lib/bootstrap-project.ts +39 -13
- package/src/lib/cli.ts +46 -15
- package/src/lib/commands/completion.ts +6 -1
- package/src/lib/commands/doctor.ts +18 -21
- package/src/lib/commands/help.ts +9 -43
- package/src/lib/commands/routes.ts +3 -3
- package/src/lib/commands/test.ts +10 -4
- package/src/lib/completion.ts +4 -151
- package/src/lib/controller-files.ts +4 -8
- package/src/lib/controller-ownership.ts +78 -141
- package/src/lib/doctor/controller-findings.ts +20 -97
- package/src/lib/doctor/controller-fix-plans.ts +13 -29
- package/src/lib/doctor/controller-placeholders.ts +17 -189
- package/src/lib/doctor/controllers.ts +1 -1
- package/src/lib/doctor/project.ts +60 -52
- package/src/lib/doctor/types.ts +1 -5
- package/src/lib/errors.ts +0 -12
- package/src/lib/load-route-map-worker.ts +19 -10
- package/src/lib/route-map.ts +61 -16
- package/template/.agents/skills/remix/SKILL.md +588 -0
- package/template/.agents/skills/remix/references/animate-elements.md +195 -0
- package/template/.agents/skills/remix/references/assets-and-browser-modules.md +130 -0
- package/template/.agents/skills/remix/references/auth-and-sessions.md +443 -0
- package/template/.agents/skills/remix/references/component-model.md +282 -0
- package/template/.agents/skills/remix/references/create-mixins.md +158 -0
- package/template/.agents/skills/remix/references/data-and-validation.md +379 -0
- package/template/.agents/skills/remix/references/hydration-frames-navigation.md +297 -0
- package/template/.agents/skills/remix/references/middleware-and-server.md +233 -0
- package/template/.agents/skills/remix/references/mixins-styling-events.md +213 -0
- package/template/.agents/skills/remix/references/routing-and-controllers.md +391 -0
- package/template/.agents/skills/remix/references/testing-patterns.md +172 -0
- package/{bootstrap → template}/AGENTS.md +13 -8
- package/template/README.md +29 -0
- package/template/app/actions/controller.tsx +18 -0
- package/template/app/assets/entry.ts +8 -0
- package/template/app/assets/prompt-button.tsx +163 -0
- package/template/app/assets.ts +20 -0
- package/template/app/middleware/render.tsx +43 -0
- package/template/app/router.ts +20 -0
- package/template/app/routes.ts +6 -0
- package/template/app/ui/document.tsx +33 -0
- package/template/app/ui/scaffold-home-page.tsx +527 -0
- package/template/gitignore +4 -0
- package/{bootstrap → template}/package.json +5 -6
- package/template/public/favicon.svg +11 -0
- package/{bootstrap → template}/server.ts +4 -5
- package/{bootstrap → template}/tsconfig.json +3 -3
- package/bootstrap/README.md +0 -27
- package/bootstrap/app/controllers/auth.tsx +0 -21
- package/bootstrap/app/controllers/home.tsx +0 -26
- package/bootstrap/app/router.ts +0 -10
- package/bootstrap/app/routes.ts +0 -6
- package/bootstrap/app/ui/document.tsx +0 -21
- package/bootstrap/app/ui/layout.tsx +0 -22
- package/bootstrap/app/utils/render.tsx +0 -7
- package/dist/lib/commands/skills.d.ts +0 -6
- package/dist/lib/commands/skills.d.ts.map +0 -1
- package/dist/lib/commands/skills.js +0 -222
- package/dist/lib/skills-cache.d.ts +0 -19
- package/dist/lib/skills-cache.d.ts.map +0 -1
- package/dist/lib/skills-cache.js +0 -89
- package/dist/lib/skills.d.ts +0 -30
- package/dist/lib/skills.d.ts.map +0 -1
- package/dist/lib/skills.js +0 -441
- package/src/lib/commands/skills.ts +0 -306
- package/src/lib/skills-cache.ts +0 -140
- package/src/lib/skills.ts +0 -706
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import
|
|
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: `
|
|
17
|
+
message: `Action controller ${actualPath} does not match any route map.`,
|
|
21
18
|
severity: 'warn' as const,
|
|
22
|
-
suite: '
|
|
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
|
|
24
|
+
message: `Directory ${actualPath} does not match any route-map key path.`,
|
|
28
25
|
severity: 'warn' as const,
|
|
29
|
-
suite: '
|
|
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
|
|
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:
|
|
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: '
|
|
48
|
+
suite: 'actions',
|
|
101
49
|
})
|
|
102
50
|
continue
|
|
103
51
|
}
|
|
104
52
|
|
|
105
|
-
if (subtree.
|
|
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
|
-
?
|
|
119
|
-
:
|
|
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: '
|
|
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 {
|
|
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: '
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
116
|
-
lines.push(`import
|
|
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('',
|
|
29
|
+
lines.push('', `export default createController(${routeExpression}, {`, ' actions: {')
|
|
123
30
|
lines.push(...actionEntries)
|
|
124
|
-
lines.push(' },',
|
|
31
|
+
lines.push(' },', '})', '')
|
|
125
32
|
|
|
126
33
|
return lines.join('\n')
|
|
127
34
|
}
|
|
128
35
|
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
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
|
}
|