@remix-run/cli 0.2.0 → 0.3.1
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/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 +26 -4
- 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/routes.js +3 -3
- package/dist/lib/completion.js +1 -5
- 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/help-text.js +1 -1
- 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 +5 -5
- package/src/lib/bootstrap-project.ts +39 -13
- package/src/lib/cli.ts +39 -4
- package/src/lib/commands/completion.ts +6 -1
- package/src/lib/commands/doctor.ts +18 -21
- package/src/lib/commands/routes.ts +3 -3
- package/src/lib/completion.ts +1 -5
- 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/help-text.ts +3 -2
- package/src/lib/load-route-map-worker.ts +19 -10
- package/src/lib/route-map.ts +61 -16
- package/{bootstrap → template}/.agents/skills/remix/SKILL.md +162 -77
- package/{bootstrap → template}/.agents/skills/remix/references/assets-and-browser-modules.md +22 -14
- package/{bootstrap → template}/.agents/skills/remix/references/auth-and-sessions.md +41 -18
- package/{bootstrap → template}/.agents/skills/remix/references/component-model.md +4 -1
- package/{bootstrap → template}/.agents/skills/remix/references/data-and-validation.md +21 -5
- package/{bootstrap → template}/.agents/skills/remix/references/middleware-and-server.md +42 -53
- package/{bootstrap → template}/.agents/skills/remix/references/routing-and-controllers.md +111 -44
- package/{bootstrap → template}/.agents/skills/remix/references/testing-patterns.md +17 -1
- package/{bootstrap → template}/AGENTS.md +10 -9
- package/template/README.md +29 -0
- package/template/app/actions/controller.tsx +18 -0
- package/template/app/assets/entry.ts +8 -0
- package/{bootstrap/app/ui → template/app/assets}/prompt-button.tsx +2 -1
- package/{bootstrap → template}/app/assets.ts +5 -3
- package/template/app/middleware/render.tsx +44 -0
- package/template/app/router.ts +20 -0
- package/{bootstrap → template}/app/routes.ts +1 -2
- package/{bootstrap → template}/app/ui/document.tsx +11 -4
- package/{bootstrap → template}/app/ui/scaffold-home-page.tsx +39 -38
- package/template/gitignore +4 -0
- package/{bootstrap → template}/package.json +5 -6
- package/template/public/favicon.svg +11 -0
- package/template/server.ts +38 -0
- package/{bootstrap → template}/tsconfig.json +3 -2
- package/bootstrap/README.md +0 -27
- package/bootstrap/app/assets/entry.ts +0 -19
- package/bootstrap/app/controllers/auth.tsx +0 -21
- package/bootstrap/app/controllers/home.tsx +0 -11
- package/bootstrap/app/router.ts +0 -16
- package/bootstrap/app/ui/layout.tsx +0 -22
- package/bootstrap/app/utils/render.tsx +0 -26
- package/bootstrap/server.ts +0 -37
- /package/{bootstrap → template}/.agents/skills/remix/references/animate-elements.md +0 -0
- /package/{bootstrap → template}/.agents/skills/remix/references/create-mixins.md +0 -0
- /package/{bootstrap → template}/.agents/skills/remix/references/hydration-frames-navigation.md +0 -0
- /package/{bootstrap → template}/.agents/skills/remix/references/mixins-styling-events.md +0 -0
|
@@ -1,92 +1,13 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
2
|
import { getOwnerFileExtension } from "../controller-files.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
'break',
|
|
6
|
-
'case',
|
|
7
|
-
'catch',
|
|
8
|
-
'class',
|
|
9
|
-
'const',
|
|
10
|
-
'continue',
|
|
11
|
-
'debugger',
|
|
12
|
-
'default',
|
|
13
|
-
'delete',
|
|
14
|
-
'do',
|
|
15
|
-
'else',
|
|
16
|
-
'enum',
|
|
17
|
-
'export',
|
|
18
|
-
'extends',
|
|
19
|
-
'false',
|
|
20
|
-
'finally',
|
|
21
|
-
'for',
|
|
22
|
-
'function',
|
|
23
|
-
'if',
|
|
24
|
-
'implements',
|
|
25
|
-
'import',
|
|
26
|
-
'in',
|
|
27
|
-
'instanceof',
|
|
28
|
-
'interface',
|
|
29
|
-
'let',
|
|
30
|
-
'new',
|
|
31
|
-
'null',
|
|
32
|
-
'package',
|
|
33
|
-
'private',
|
|
34
|
-
'protected',
|
|
35
|
-
'public',
|
|
36
|
-
'return',
|
|
37
|
-
'static',
|
|
38
|
-
'super',
|
|
39
|
-
'switch',
|
|
40
|
-
'this',
|
|
41
|
-
'throw',
|
|
42
|
-
'true',
|
|
43
|
-
'try',
|
|
44
|
-
'typeof',
|
|
45
|
-
'var',
|
|
46
|
-
'void',
|
|
47
|
-
'while',
|
|
48
|
-
'with',
|
|
49
|
-
'yield',
|
|
50
|
-
]);
|
|
51
|
-
export function renderActionPlaceholder(routeNode, entryPath) {
|
|
52
|
-
let routeMessage = `TODO: implement routes.${routeNode.name}`;
|
|
53
|
-
let extension = getOwnerFileExtension(entryPath);
|
|
54
|
-
let exportName = getActionExportName(routeNode.key);
|
|
55
|
-
if (extension === '.js' || extension === '.jsx') {
|
|
56
|
-
return [
|
|
57
|
-
`export const ${exportName} = {`,
|
|
58
|
-
` handler() {`,
|
|
59
|
-
` return new Response(${JSON.stringify(routeMessage)})`,
|
|
60
|
-
` },`,
|
|
61
|
-
`}`,
|
|
62
|
-
'',
|
|
63
|
-
].join('\n');
|
|
64
|
-
}
|
|
65
|
-
let routeExpression = getRouteAccessExpression(routeNode.name);
|
|
66
|
-
let routesImportPath = getRelativeImportPath(entryPath, 'app/routes.ts');
|
|
67
|
-
let method = routeNode.method ?? 'ANY';
|
|
68
|
-
return [
|
|
69
|
-
`import type { BuildAction } from 'remix/fetch-router'`,
|
|
70
|
-
'',
|
|
71
|
-
`import type { routes } from '${routesImportPath}'`,
|
|
72
|
-
'',
|
|
73
|
-
`export const ${exportName}: BuildAction<'${method}', typeof ${routeExpression}> = {`,
|
|
74
|
-
` handler() {`,
|
|
75
|
-
` return new Response(${JSON.stringify(routeMessage)})`,
|
|
76
|
-
` },`,
|
|
77
|
-
`}`,
|
|
78
|
-
'',
|
|
79
|
-
].join('\n');
|
|
80
|
-
}
|
|
81
|
-
export function renderControllerPlaceholder(routeNode, entryPath, resolvedEntryPathByRouteName) {
|
|
3
|
+
import { ROOT_ROUTE_NAME } from "../controller-ownership.js";
|
|
4
|
+
export function renderControllerPlaceholder(routeNode, entryPath) {
|
|
82
5
|
let extension = getOwnerFileExtension(entryPath);
|
|
83
6
|
let lines = [];
|
|
84
|
-
let
|
|
85
|
-
|
|
7
|
+
let actionEntries = routeNode.children
|
|
8
|
+
.filter((childNode) => childNode.kind === 'route')
|
|
9
|
+
.map((childNode) => renderControllerActionEntry(childNode));
|
|
86
10
|
if (extension === '.js' || extension === '.jsx') {
|
|
87
|
-
if (childImports.length > 0) {
|
|
88
|
-
lines.push(...childImports, '');
|
|
89
|
-
}
|
|
90
11
|
lines.push('export default {', ' actions: {');
|
|
91
12
|
lines.push(...actionEntries);
|
|
92
13
|
lines.push(' },', '}', '');
|
|
@@ -94,57 +15,24 @@ export function renderControllerPlaceholder(routeNode, entryPath, resolvedEntryP
|
|
|
94
15
|
}
|
|
95
16
|
let routeExpression = getRouteAccessExpression(routeNode.name);
|
|
96
17
|
let routesImportPath = getRelativeImportPath(entryPath, 'app/routes.ts');
|
|
97
|
-
lines.push(`import
|
|
98
|
-
lines.push(`import
|
|
99
|
-
|
|
100
|
-
lines.push('', ...childImports);
|
|
101
|
-
}
|
|
102
|
-
lines.push('', 'export default {', ' actions: {');
|
|
18
|
+
lines.push(`import { createController } from 'remix/router'`, '');
|
|
19
|
+
lines.push(`import { routes } from '${routesImportPath}'`);
|
|
20
|
+
lines.push('', `export default createController(${routeExpression}, {`, ' actions: {');
|
|
103
21
|
lines.push(...actionEntries);
|
|
104
|
-
lines.push(' },',
|
|
22
|
+
lines.push(' },', '})', '');
|
|
105
23
|
return lines.join('\n');
|
|
106
24
|
}
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
let childEntryPath = resolvedEntryPathByRouteName.get(childNode.name);
|
|
114
|
-
if (childEntryPath == null) {
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
imports.push(`import ${getControllerImportName(childNode.key)} from '${getRelativeImportPath(entryPath, childEntryPath)}'`);
|
|
118
|
-
}
|
|
119
|
-
return imports;
|
|
120
|
-
}
|
|
121
|
-
function renderControllerActionEntry(childNode, entryPath, resolvedEntryPathByRouteName) {
|
|
122
|
-
if (childNode.kind === 'route') {
|
|
123
|
-
return [
|
|
124
|
-
` ${formatObjectKey(childNode.key)}() {`,
|
|
125
|
-
` return new Response(${JSON.stringify(`TODO: implement routes.${childNode.name}`)})`,
|
|
126
|
-
` },`,
|
|
127
|
-
].join('\n');
|
|
128
|
-
}
|
|
129
|
-
let childEntryPath = resolvedEntryPathByRouteName.get(childNode.name);
|
|
130
|
-
if (childEntryPath != null) {
|
|
131
|
-
return ` ${formatObjectKey(childNode.key)}: ${getControllerImportName(childNode.key)},`;
|
|
132
|
-
}
|
|
133
|
-
return ` ${formatObjectKey(childNode.key)}: ${renderInlineControllerPlaceholder(childNode)},`;
|
|
134
|
-
}
|
|
135
|
-
function renderInlineControllerPlaceholder(routeNode) {
|
|
136
|
-
let childEntries = routeNode.children.map((childNode) => childNode.kind === 'route'
|
|
137
|
-
? `${formatObjectKey(childNode.key)}() { return new Response(${JSON.stringify(`TODO: implement routes.${childNode.name}`)}) }`
|
|
138
|
-
: `${formatObjectKey(childNode.key)}: ${renderInlineControllerPlaceholder(childNode)}`);
|
|
139
|
-
return `{ actions: { ${childEntries.join(', ')} } }`;
|
|
140
|
-
}
|
|
141
|
-
function getControllerImportName(key) {
|
|
142
|
-
return `${toIdentifier(key)}Controller`;
|
|
143
|
-
}
|
|
144
|
-
function getActionExportName(key) {
|
|
145
|
-
return toIdentifier(key);
|
|
25
|
+
function renderControllerActionEntry(childNode) {
|
|
26
|
+
return [
|
|
27
|
+
` ${formatObjectKey(childNode.key)}() {`,
|
|
28
|
+
` return new Response(${JSON.stringify(`TODO: implement routes.${childNode.name}`)})`,
|
|
29
|
+
` },`,
|
|
30
|
+
].join('\n');
|
|
146
31
|
}
|
|
147
32
|
function getRouteAccessExpression(routeName) {
|
|
33
|
+
if (routeName === ROOT_ROUTE_NAME) {
|
|
34
|
+
return 'routes';
|
|
35
|
+
}
|
|
148
36
|
let segments = routeName.split('.');
|
|
149
37
|
let expression = 'routes';
|
|
150
38
|
for (let segment of segments) {
|
|
@@ -159,25 +47,6 @@ function getRelativeImportPath(fromFilePath, toFilePath) {
|
|
|
159
47
|
function formatObjectKey(key) {
|
|
160
48
|
return isIdentifier(key) ? key : JSON.stringify(key);
|
|
161
49
|
}
|
|
162
|
-
function toIdentifier(value) {
|
|
163
|
-
let parts = value.split(/[^A-Za-z0-9_$]+/).filter(Boolean);
|
|
164
|
-
if (parts.length === 0) {
|
|
165
|
-
return 'route';
|
|
166
|
-
}
|
|
167
|
-
let identifier = parts
|
|
168
|
-
.map((part, index) => index === 0 ? part.replace(/^./, (char) => char.toLowerCase()) : capitalize(part))
|
|
169
|
-
.join('');
|
|
170
|
-
if (!/^[A-Za-z_$]/.test(identifier)) {
|
|
171
|
-
return `route${capitalize(identifier)}`;
|
|
172
|
-
}
|
|
173
|
-
if (RESERVED_BINDING_IDENTIFIERS.has(identifier)) {
|
|
174
|
-
return `route${capitalize(identifier)}`;
|
|
175
|
-
}
|
|
176
|
-
return identifier;
|
|
177
|
-
}
|
|
178
|
-
function capitalize(value) {
|
|
179
|
-
return value.length === 0 ? value : `${value[0].toUpperCase()}${value.slice(1)}`;
|
|
180
|
-
}
|
|
181
50
|
function isIdentifier(value) {
|
|
182
51
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
|
|
183
52
|
}
|
|
@@ -39,7 +39,7 @@ export async function checkProject(projectRoot) {
|
|
|
39
39
|
}
|
|
40
40
|
export async function getProjectFixPlans(projectRoot) {
|
|
41
41
|
let routesFile = path.join(projectRoot, 'app', 'routes.ts');
|
|
42
|
-
let homeActionPath = normalizeRelativePath(path.join('app', '
|
|
42
|
+
let homeActionPath = normalizeRelativePath(path.join('app', 'actions', `controller${await inferHomeOwnerExtension(projectRoot)}`));
|
|
43
43
|
if (await pathExists(routesFile)) {
|
|
44
44
|
let routesSource = await fs.readFile(routesFile, 'utf8');
|
|
45
45
|
if (!hasOnlyWhitespaceAndComments(routesSource)) {
|
|
@@ -181,8 +181,8 @@ function hasOnlyWhitespaceAndComments(source) {
|
|
|
181
181
|
return true;
|
|
182
182
|
}
|
|
183
183
|
async function inferHomeOwnerExtension(projectRoot) {
|
|
184
|
-
let
|
|
185
|
-
let extensions = await collectOwnerExtensions(
|
|
184
|
+
let actionsDir = path.join(projectRoot, 'app', 'actions');
|
|
185
|
+
let extensions = await collectOwnerExtensions(actionsDir);
|
|
186
186
|
if (extensions.length > 0) {
|
|
187
187
|
return getMostCommonExtension(extensions);
|
|
188
188
|
}
|
|
@@ -239,7 +239,7 @@ function getMostCommonExtension(extensions) {
|
|
|
239
239
|
}
|
|
240
240
|
function renderDefaultRoutesFile() {
|
|
241
241
|
return [
|
|
242
|
-
`import { route } from 'remix/
|
|
242
|
+
`import { route } from 'remix/routes'`,
|
|
243
243
|
'',
|
|
244
244
|
'export const routes = route({',
|
|
245
245
|
` home: '/',`,
|
|
@@ -257,22 +257,24 @@ function renderDefaultHomeAction(entryPath) {
|
|
|
257
257
|
`import { html } from 'remix/html-template'`,
|
|
258
258
|
`import { createHtmlResponse } from 'remix/response/html'`,
|
|
259
259
|
'',
|
|
260
|
-
'export
|
|
261
|
-
'
|
|
262
|
-
'
|
|
263
|
-
'
|
|
264
|
-
' <
|
|
265
|
-
' <
|
|
266
|
-
'
|
|
267
|
-
'
|
|
268
|
-
'
|
|
269
|
-
'
|
|
270
|
-
' <
|
|
271
|
-
'
|
|
272
|
-
'
|
|
273
|
-
'
|
|
274
|
-
'
|
|
275
|
-
'
|
|
260
|
+
'export default {',
|
|
261
|
+
' actions: {',
|
|
262
|
+
' home() {',
|
|
263
|
+
' let page = html`',
|
|
264
|
+
' <html lang="en">',
|
|
265
|
+
' <head>',
|
|
266
|
+
' <meta charset="utf-8" />',
|
|
267
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
268
|
+
' <title>Home</title>',
|
|
269
|
+
' </head>',
|
|
270
|
+
' <body>',
|
|
271
|
+
' <h1>Home</h1>',
|
|
272
|
+
' <p>Update app/routes.ts and app/actions/controller to keep building your app.</p>',
|
|
273
|
+
' </body>',
|
|
274
|
+
' </html>',
|
|
275
|
+
' `',
|
|
276
|
+
' return createHtmlResponse(page)',
|
|
277
|
+
' },',
|
|
276
278
|
' },',
|
|
277
279
|
'}',
|
|
278
280
|
'',
|
|
@@ -280,30 +282,32 @@ function renderDefaultHomeAction(entryPath) {
|
|
|
280
282
|
}
|
|
281
283
|
if (extension === '.ts') {
|
|
282
284
|
return [
|
|
283
|
-
`import
|
|
285
|
+
`import { createController } from 'remix/router'`,
|
|
284
286
|
`import { html } from 'remix/html-template'`,
|
|
285
287
|
`import { createHtmlResponse } from 'remix/response/html'`,
|
|
286
288
|
'',
|
|
287
|
-
`import
|
|
289
|
+
`import { routes } from '../routes.ts'`,
|
|
288
290
|
'',
|
|
289
|
-
`export
|
|
290
|
-
'
|
|
291
|
-
'
|
|
292
|
-
'
|
|
293
|
-
' <
|
|
294
|
-
' <
|
|
295
|
-
'
|
|
296
|
-
'
|
|
297
|
-
'
|
|
298
|
-
'
|
|
299
|
-
' <
|
|
300
|
-
'
|
|
301
|
-
'
|
|
302
|
-
'
|
|
303
|
-
'
|
|
304
|
-
'
|
|
291
|
+
`export default createController(routes, {`,
|
|
292
|
+
' actions: {',
|
|
293
|
+
' home() {',
|
|
294
|
+
' let page = html`',
|
|
295
|
+
' <html lang="en">',
|
|
296
|
+
' <head>',
|
|
297
|
+
' <meta charset="utf-8" />',
|
|
298
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
299
|
+
' <title>Home</title>',
|
|
300
|
+
' </head>',
|
|
301
|
+
' <body>',
|
|
302
|
+
' <h1>Home</h1>',
|
|
303
|
+
' <p>Update app/routes.ts and app/actions/controller to keep building your app.</p>',
|
|
304
|
+
' </body>',
|
|
305
|
+
' </html>',
|
|
306
|
+
' `',
|
|
307
|
+
' return createHtmlResponse(page)',
|
|
308
|
+
' },',
|
|
305
309
|
' },',
|
|
306
|
-
|
|
310
|
+
`})`,
|
|
307
311
|
'',
|
|
308
312
|
].join('\n');
|
|
309
313
|
}
|
|
@@ -312,10 +316,12 @@ function renderDefaultHomeAction(entryPath) {
|
|
|
312
316
|
`import { renderToStream } from 'remix/ui/server'`,
|
|
313
317
|
`import { createHtmlResponse } from 'remix/response/html'`,
|
|
314
318
|
'',
|
|
315
|
-
'export
|
|
316
|
-
'
|
|
317
|
-
'
|
|
318
|
-
'
|
|
319
|
+
'export default {',
|
|
320
|
+
' actions: {',
|
|
321
|
+
' home() {',
|
|
322
|
+
' let page = <HomePage />',
|
|
323
|
+
' return createHtmlResponse(renderToStream(page))',
|
|
324
|
+
' },',
|
|
319
325
|
' },',
|
|
320
326
|
'}',
|
|
321
327
|
'',
|
|
@@ -329,7 +335,7 @@ function renderDefaultHomeAction(entryPath) {
|
|
|
329
335
|
' </head>',
|
|
330
336
|
' <body>',
|
|
331
337
|
' <h1>Home</h1>',
|
|
332
|
-
' <p>Update app/routes.ts and app/
|
|
338
|
+
' <p>Update app/routes.ts and app/actions/controller to keep building your app.</p>',
|
|
333
339
|
' </body>',
|
|
334
340
|
' </html>',
|
|
335
341
|
' )',
|
|
@@ -338,18 +344,20 @@ function renderDefaultHomeAction(entryPath) {
|
|
|
338
344
|
].join('\n');
|
|
339
345
|
}
|
|
340
346
|
return [
|
|
341
|
-
`import
|
|
347
|
+
`import { createController } from 'remix/router'`,
|
|
342
348
|
`import { renderToStream } from 'remix/ui/server'`,
|
|
343
349
|
`import { createHtmlResponse } from 'remix/response/html'`,
|
|
344
350
|
'',
|
|
345
|
-
`import
|
|
351
|
+
`import { routes } from '../routes.ts'`,
|
|
346
352
|
'',
|
|
347
|
-
`export
|
|
348
|
-
'
|
|
349
|
-
'
|
|
350
|
-
'
|
|
353
|
+
`export default createController(routes, {`,
|
|
354
|
+
' actions: {',
|
|
355
|
+
' home() {',
|
|
356
|
+
' let page = <HomePage />',
|
|
357
|
+
' return createHtmlResponse(renderToStream(page))',
|
|
358
|
+
' },',
|
|
351
359
|
' },',
|
|
352
|
-
|
|
360
|
+
`})`,
|
|
353
361
|
'',
|
|
354
362
|
'function HomePage() {',
|
|
355
363
|
' return () => (',
|
|
@@ -361,7 +369,7 @@ function renderDefaultHomeAction(entryPath) {
|
|
|
361
369
|
' </head>',
|
|
362
370
|
' <body>',
|
|
363
371
|
' <h1>Home</h1>',
|
|
364
|
-
' <p>Update app/routes.ts and app/
|
|
372
|
+
' <p>Update app/routes.ts and app/actions/controller to keep building your app.</p>',
|
|
365
373
|
' </body>',
|
|
366
374
|
' </html>',
|
|
367
375
|
' )',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export type DoctorSuiteName = 'environment' | 'project' | '
|
|
1
|
+
export type DoctorSuiteName = 'environment' | 'project' | 'actions';
|
|
2
2
|
export type DoctorFindingSeverity = 'warn' | 'advice';
|
|
3
3
|
export type DoctorSuiteStatus = 'ok' | 'issues' | 'skipped';
|
|
4
|
-
export type DoctorFindingCode = 'project-root-not-found' | 'package-json-read-failed' | 'package-json-invalid' | 'node-engine-missing' | 'node-engine-unparseable' | 'node-version-unsupported' | 'remix-dependency-missing' | 'remix-install-missing' | 'routes-file-missing' | 'routes-export-missing' | 'route-map-invalid' | 'route-module-import-failed' | 'route-map-invalid-json' | 'route-map-loader-signal' | 'missing-owner' | '
|
|
4
|
+
export type DoctorFindingCode = 'project-root-not-found' | 'package-json-read-failed' | 'package-json-invalid' | 'node-engine-missing' | 'node-engine-unparseable' | 'node-version-unsupported' | 'remix-dependency-missing' | 'remix-install-missing' | 'routes-file-missing' | 'routes-export-missing' | 'route-map-invalid' | 'route-module-import-failed' | 'route-map-invalid-json' | 'route-map-loader-signal' | 'missing-owner' | 'duplicate-owner-file' | 'incomplete-controller' | 'orphan-controller' | 'orphan-route-directory';
|
|
5
5
|
export type DoctorFixKind = 'create-directory' | 'create-file' | 'update-file';
|
|
6
6
|
export interface DoctorFinding {
|
|
7
7
|
actualPath?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/doctor/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,SAAS,GAAG,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/doctor/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,SAAS,GAAG,SAAS,CAAA;AACnE,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,QAAQ,CAAA;AACrD,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,QAAQ,GAAG,SAAS,CAAA;AAE3D,MAAM,MAAM,iBAAiB,GACzB,wBAAwB,GACxB,0BAA0B,GAC1B,sBAAsB,GACtB,qBAAqB,GACrB,yBAAyB,GACzB,0BAA0B,GAC1B,0BAA0B,GAC1B,uBAAuB,GACvB,qBAAqB,GACrB,uBAAuB,GACvB,mBAAmB,GACnB,4BAA4B,GAC5B,wBAAwB,GACxB,yBAAyB,GACzB,eAAe,GACf,sBAAsB,GACtB,uBAAuB,GACvB,mBAAmB,GACnB,wBAAwB,CAAA;AAE5B,MAAM,MAAM,aAAa,GAAG,kBAAkB,GAAG,aAAa,GAAG,aAAa,CAAA;AAE9E,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,iBAAiB,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,KAAK,EAAE,eAAe,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,iBAAiB,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,eAAe,CAAA;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,iBAAiB,CAAA;IACvB,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,eAAe,CAAA;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,EAAE,gBAAgB,EAAE,CAAA;IACjC,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,IAAI,EAAE,eAAe,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,iBAAiB,CAAA;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,gBAAgB,EAAE,CAAA;IACjC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,iBAAiB,CAAC,EAAE,aAAa,EAAE,CAAA;IACnC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,iBAAiB,EAAE,CAAA;CAC5B;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,eAAe,EACrB,QAAQ,EAAE,aAAa,EAAE,GACxB,iBAAiB,CAMnB;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAOjG"}
|
package/dist/lib/help-text.js
CHANGED
|
@@ -39,5 +39,5 @@ function formatHeading(title, target) {
|
|
|
39
39
|
return boldLightBlue(title, target);
|
|
40
40
|
}
|
|
41
41
|
function highlightSyntax(text, target) {
|
|
42
|
-
return text.replace(/(--[A-Za-z0-9-]+|-[A-Za-z]|\[[A-Za-z][A-Za-z0-9-]*\]|<[^>\n]
|
|
42
|
+
return text.replace(/(?<![^\s[,|])(--[A-Za-z0-9-]+|-[A-Za-z])(?=$|[\s,\]|])|\[[A-Za-z][A-Za-z0-9-]*\]|<[^>\n]+>/g, (token) => lightYellow(token, target));
|
|
43
43
|
}
|
|
@@ -30,7 +30,7 @@ async function loadRawRouteTree(routesFile) {
|
|
|
30
30
|
if (!('routes' in routeModule)) {
|
|
31
31
|
throw new Error(`Route module ${routesFile} must export a named "routes" value.`);
|
|
32
32
|
}
|
|
33
|
-
return normalizeRouteGroup(
|
|
33
|
+
return normalizeRouteGroup(routeModule.routes, [], new Map());
|
|
34
34
|
}
|
|
35
35
|
function normalizeRouteGroup(value, parentSegments, seen) {
|
|
36
36
|
if (!isPlainObject(value)) {
|
|
@@ -47,14 +47,15 @@ function normalizeRouteGroup(value, parentSegments, seen) {
|
|
|
47
47
|
return Object.entries(value).map(([key, entryValue]) => {
|
|
48
48
|
let nameSegments = [...parentSegments, key];
|
|
49
49
|
let name = nameSegments.join('.');
|
|
50
|
-
|
|
50
|
+
let routeLeaf = readRouteLeaf(entryValue);
|
|
51
|
+
if (routeLeaf !== undefined) {
|
|
51
52
|
return {
|
|
52
53
|
children: [],
|
|
53
54
|
key,
|
|
54
55
|
kind: 'route',
|
|
55
|
-
method:
|
|
56
|
+
method: routeLeaf.method,
|
|
56
57
|
name,
|
|
57
|
-
pattern:
|
|
58
|
+
pattern: routeLeaf.pattern,
|
|
58
59
|
};
|
|
59
60
|
}
|
|
60
61
|
if (isPlainObject(entryValue)) {
|
|
@@ -72,13 +73,20 @@ function normalizeRouteGroup(value, parentSegments, seen) {
|
|
|
72
73
|
seen.delete(value);
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
|
-
function
|
|
76
|
+
function readRouteLeaf(value) {
|
|
77
|
+
if (typeof value !== 'object' || value == null)
|
|
78
|
+
return undefined;
|
|
79
|
+
let method = Reflect.get(value, 'method');
|
|
80
|
+
let pattern = Reflect.get(value, 'pattern');
|
|
81
|
+
if (typeof method !== 'string' || !hasToString(pattern))
|
|
82
|
+
return undefined;
|
|
83
|
+
return { method, pattern: pattern.toString() };
|
|
84
|
+
}
|
|
85
|
+
function hasToString(value) {
|
|
76
86
|
return (typeof value === 'object' &&
|
|
77
87
|
value != null &&
|
|
78
|
-
|
|
79
|
-
typeof
|
|
80
|
-
Reflect.get(value, 'pattern') != null &&
|
|
81
|
-
typeof Reflect.get(Reflect.get(value, 'pattern'), 'source') === 'string');
|
|
88
|
+
'toString' in value &&
|
|
89
|
+
typeof value.toString === 'function');
|
|
82
90
|
}
|
|
83
91
|
function isPlainObject(value) {
|
|
84
92
|
if (typeof value !== 'object' || value == null) {
|
package/dist/lib/route-map.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-map.d.ts","sourceRoot":"","sources":["../../src/lib/route-map.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"route-map.d.ts","sourceRoot":"","sources":["../../src/lib/route-map.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,WAAW,CAAA;AACvD,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,OAAO,CAAA;AAEjD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAA;IACf,IAAI,EAAE,cAAc,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,iBAAiB,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,cAAc,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,aAAa,EAAE,CAAA;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,gBAAgB,EAAE,CAAA;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,gBAAgB,EAAE,CAAA;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,iBAAiB,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,wBAAsB,YAAY,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,cAAc,CAAC,CAUvF;AAED,wBAAsB,iBAAiB,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAGjG;AAED,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAShG"}
|
package/dist/lib/route-map.js
CHANGED
|
@@ -3,12 +3,12 @@ import * as path from 'node:path';
|
|
|
3
3
|
import * as process from 'node:process';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { inspectControllerOwnership } from "./controller-ownership.js";
|
|
6
|
+
import { ROOT_ROUTE_NAME, inspectControllerOwnership, } from "./controller-ownership.js";
|
|
7
7
|
import { routeMapLoaderFailed, routeMapLoaderInvalidJson, routeMapLoaderSignal, routeOwnerPlanUnresolved, routesFileNotFound, } from "./errors.js";
|
|
8
8
|
export async function loadRouteMap(cwd = process.cwd()) {
|
|
9
9
|
let manifest = await loadRouteManifest(cwd);
|
|
10
10
|
let ownership = await inspectControllerOwnership(manifest.appRoot, manifest.tree);
|
|
11
|
-
let tree = decorateRouteTree(manifest.tree, ownership
|
|
11
|
+
let tree = decorateRouteTree(manifest.tree, ownership);
|
|
12
12
|
return {
|
|
13
13
|
appRoot: manifest.appRoot,
|
|
14
14
|
routesFile: manifest.routesFile,
|
|
@@ -70,17 +70,18 @@ async function loadRawRouteMap(appRoot, routesFile) {
|
|
|
70
70
|
}
|
|
71
71
|
return assertRawRouteTree(parsed);
|
|
72
72
|
}
|
|
73
|
-
function decorateRouteTree(rawTree,
|
|
74
|
-
let subtreesByRouteName = new Map(subtrees.map((subtree) => [subtree.routeName, subtree]));
|
|
75
|
-
|
|
73
|
+
function decorateRouteTree(rawTree, ownership) {
|
|
74
|
+
let subtreesByRouteName = new Map(ownership.subtrees.map((subtree) => [subtree.routeName, subtree]));
|
|
75
|
+
let directoriesByRouteName = new Map(ownership.routeDirectories.map((directory) => [directory.routeName, directory]));
|
|
76
|
+
return decorateRouteTreeWithLookup(rawTree, subtreesByRouteName, directoriesByRouteName, ownership.scan.routeDirectoryPaths);
|
|
76
77
|
}
|
|
77
|
-
function decorateRouteTreeWithLookup(rawTree, subtreesByRouteName, parentSegments = []) {
|
|
78
|
+
function decorateRouteTreeWithLookup(rawTree, subtreesByRouteName, directoriesByRouteName, actualRouteDirectories, parentSegments = []) {
|
|
78
79
|
return rawTree.map((rawNode) => {
|
|
79
|
-
let owner = getRouteOwner(rawNode, parentSegments, subtreesByRouteName);
|
|
80
|
+
let owner = getRouteOwner(rawNode, parentSegments, subtreesByRouteName, directoriesByRouteName, actualRouteDirectories);
|
|
80
81
|
let nextParentSegments = rawNode.kind === 'group' ? [...parentSegments, rawNode.key] : parentSegments;
|
|
81
82
|
return {
|
|
82
83
|
children: rawNode.kind === 'group'
|
|
83
|
-
? decorateRouteTreeWithLookup(rawNode.children, subtreesByRouteName, nextParentSegments)
|
|
84
|
+
? decorateRouteTreeWithLookup(rawNode.children, subtreesByRouteName, directoriesByRouteName, actualRouteDirectories, nextParentSegments)
|
|
84
85
|
: [],
|
|
85
86
|
key: rawNode.key,
|
|
86
87
|
kind: rawNode.kind,
|
|
@@ -91,21 +92,32 @@ function decorateRouteTreeWithLookup(rawTree, subtreesByRouteName, parentSegment
|
|
|
91
92
|
};
|
|
92
93
|
});
|
|
93
94
|
}
|
|
94
|
-
function getRouteOwner(rawNode, parentSegments, subtreesByRouteName) {
|
|
95
|
+
function getRouteOwner(rawNode, parentSegments, subtreesByRouteName, directoriesByRouteName, actualRouteDirectories) {
|
|
95
96
|
let ownerRouteName = rawNode.kind === 'group'
|
|
96
97
|
? rawNode.name
|
|
97
98
|
: parentSegments.length === 0
|
|
98
|
-
?
|
|
99
|
+
? ROOT_ROUTE_NAME
|
|
99
100
|
: parentSegments.join('.');
|
|
100
101
|
let subtree = subtreesByRouteName.get(ownerRouteName);
|
|
101
|
-
if (subtree
|
|
102
|
-
|
|
102
|
+
if (subtree != null) {
|
|
103
|
+
return {
|
|
104
|
+
exists: subtree.actualEntryPath != null,
|
|
105
|
+
kind: 'controller',
|
|
106
|
+
path: subtree.actualEntryPath ?? subtree.entryDisplayPath,
|
|
107
|
+
};
|
|
103
108
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
if (rawNode.kind === 'group') {
|
|
110
|
+
let directory = directoriesByRouteName.get(rawNode.name);
|
|
111
|
+
if (directory == null) {
|
|
112
|
+
throw routeOwnerPlanUnresolved(rawNode.name);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
exists: actualRouteDirectories.has(directory.directoryPath),
|
|
116
|
+
kind: 'directory',
|
|
117
|
+
path: directory.directoryPath,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
throw routeOwnerPlanUnresolved(rawNode.name);
|
|
109
121
|
}
|
|
110
122
|
function assertRawRouteTree(value) {
|
|
111
123
|
if (!Array.isArray(value)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remix-run/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Command-line interface for Remix",
|
|
5
5
|
"author": "Michael Jackson <mjijackson@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
},
|
|
12
12
|
"homepage": "https://github.com/remix-run/remix/tree/main/packages/cli#readme",
|
|
13
13
|
"files": [
|
|
14
|
-
"bootstrap",
|
|
15
14
|
"LICENSE",
|
|
16
15
|
"README.md",
|
|
17
16
|
"dist",
|
|
18
17
|
"src",
|
|
18
|
+
"template",
|
|
19
19
|
"!src/**/*.test.ts"
|
|
20
20
|
],
|
|
21
21
|
"type": "module",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"semver": "^7.7.4",
|
|
34
|
-
"@remix-run/
|
|
35
|
-
"@remix-run/
|
|
34
|
+
"@remix-run/terminal": "^0.1.1",
|
|
35
|
+
"@remix-run/test": "^0.4.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^24.6.0",
|
|
39
39
|
"@types/semver": "^7.5.8",
|
|
40
40
|
"@typescript/native-preview": "7.0.0-dev.20251125.1",
|
|
41
|
-
"@remix-run/assert": "^0.2.
|
|
41
|
+
"@remix-run/assert": "^0.2.1"
|
|
42
42
|
},
|
|
43
43
|
"keywords": [
|
|
44
44
|
"remix",
|