@remix-run/cli 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +80 -3
- package/bootstrap/AGENTS.md +35 -0
- package/bootstrap/README.md +27 -0
- package/bootstrap/app/controllers/auth.tsx +21 -0
- package/bootstrap/app/controllers/home.tsx +26 -0
- package/bootstrap/app/router.ts +10 -0
- package/bootstrap/app/routes.ts +6 -0
- package/bootstrap/app/ui/document.tsx +21 -0
- package/bootstrap/app/ui/layout.tsx +22 -0
- package/bootstrap/app/utils/render.tsx +7 -0
- package/bootstrap/package.json +22 -0
- package/bootstrap/server.ts +37 -0
- package/bootstrap/tsconfig.json +19 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/bootstrap-project.d.ts +17 -0
- package/dist/lib/bootstrap-project.d.ts.map +1 -0
- package/dist/lib/bootstrap-project.js +137 -0
- package/dist/lib/cli-context.d.ts +11 -0
- package/dist/lib/cli-context.d.ts.map +1 -0
- package/dist/lib/cli-context.js +19 -0
- package/dist/lib/cli.d.ts +6 -0
- package/dist/lib/cli.d.ts.map +1 -0
- package/dist/lib/cli.js +87 -0
- package/dist/lib/commands/completion.d.ts +3 -0
- package/dist/lib/commands/completion.d.ts.map +1 -0
- package/dist/lib/commands/completion.js +51 -0
- package/dist/lib/commands/doctor.d.ts +4 -0
- package/dist/lib/commands/doctor.d.ts.map +1 -0
- package/dist/lib/commands/doctor.js +293 -0
- package/dist/lib/commands/help.d.ts +4 -0
- package/dist/lib/commands/help.d.ts.map +1 -0
- package/dist/lib/commands/help.js +124 -0
- package/dist/lib/commands/new.d.ts +4 -0
- package/dist/lib/commands/new.d.ts.map +1 -0
- package/dist/lib/commands/new.js +70 -0
- package/dist/lib/commands/routes.d.ts +4 -0
- package/dist/lib/commands/routes.d.ts.map +1 -0
- package/dist/lib/commands/routes.js +161 -0
- package/dist/lib/commands/skills.d.ts +6 -0
- package/dist/lib/commands/skills.d.ts.map +1 -0
- package/dist/lib/commands/skills.js +222 -0
- package/dist/lib/commands/test.d.ts +4 -0
- package/dist/lib/commands/test.d.ts.map +1 -0
- package/dist/lib/commands/test.js +19 -0
- package/dist/lib/commands/version.d.ts +4 -0
- package/dist/lib/commands/version.d.ts.map +1 -0
- package/dist/lib/commands/version.js +29 -0
- package/dist/lib/completion.d.ts +12 -0
- package/dist/lib/completion.d.ts.map +1 -0
- package/dist/lib/completion.js +408 -0
- package/dist/lib/contained-path.d.ts +2 -0
- package/dist/lib/contained-path.d.ts.map +1 -0
- package/dist/lib/contained-path.js +10 -0
- package/dist/lib/controller-files.d.ts +14 -0
- package/dist/lib/controller-files.d.ts.map +1 -0
- package/dist/lib/controller-files.js +53 -0
- package/dist/lib/controller-ownership.d.ts +41 -0
- package/dist/lib/controller-ownership.d.ts.map +1 -0
- package/dist/lib/controller-ownership.js +224 -0
- package/dist/lib/display-path.d.ts +2 -0
- package/dist/lib/display-path.d.ts.map +1 -0
- package/dist/lib/display-path.js +12 -0
- package/dist/lib/doctor/controller-findings.d.ts +4 -0
- package/dist/lib/doctor/controller-findings.d.ts.map +1 -0
- package/dist/lib/doctor/controller-findings.js +132 -0
- package/dist/lib/doctor/controller-fix-plans.d.ts +4 -0
- package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -0
- package/dist/lib/doctor/controller-fix-plans.js +130 -0
- package/dist/lib/doctor/controller-placeholders.d.ts +4 -0
- package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -0
- package/dist/lib/doctor/controller-placeholders.js +183 -0
- package/dist/lib/doctor/controllers.d.ts +8 -0
- package/dist/lib/doctor/controllers.d.ts.map +1 -0
- package/dist/lib/doctor/controllers.js +13 -0
- package/dist/lib/doctor/environment.d.ts +17 -0
- package/dist/lib/doctor/environment.d.ts.map +1 -0
- package/dist/lib/doctor/environment.js +226 -0
- package/dist/lib/doctor/fixes.d.ts +3 -0
- package/dist/lib/doctor/fixes.d.ts.map +1 -0
- package/dist/lib/doctor/fixes.js +65 -0
- package/dist/lib/doctor/project.d.ts +10 -0
- package/dist/lib/doctor/project.d.ts.map +1 -0
- package/dist/lib/doctor/project.js +371 -0
- package/dist/lib/doctor/types.d.ts +48 -0
- package/dist/lib/doctor/types.d.ts.map +1 -0
- package/dist/lib/doctor/types.js +15 -0
- package/dist/lib/errors.d.ts +173 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +318 -0
- package/dist/lib/help-text.d.ts +13 -0
- package/dist/lib/help-text.d.ts.map +1 -0
- package/dist/lib/help-text.js +43 -0
- package/dist/lib/load-route-map-worker.d.ts +2 -0
- package/dist/lib/load-route-map-worker.d.ts.map +1 -0
- package/dist/lib/load-route-map-worker.js +92 -0
- package/dist/lib/parse-args.d.ts +22 -0
- package/dist/lib/parse-args.d.ts.map +1 -0
- package/dist/lib/parse-args.js +35 -0
- package/dist/lib/remix-version.d.ts +2 -0
- package/dist/lib/remix-version.d.ts.map +1 -0
- package/dist/lib/remix-version.js +58 -0
- package/dist/lib/reporter.d.ts +56 -0
- package/dist/lib/reporter.d.ts.map +1 -0
- package/dist/lib/reporter.js +357 -0
- package/dist/lib/route-map.d.ts +38 -0
- package/dist/lib/route-map.d.ts.map +1 -0
- package/dist/lib/route-map.js +195 -0
- package/dist/lib/skills-cache.d.ts +19 -0
- package/dist/lib/skills-cache.d.ts.map +1 -0
- package/dist/lib/skills-cache.js +89 -0
- package/dist/lib/skills.d.ts +30 -0
- package/dist/lib/skills.d.ts.map +1 -0
- package/dist/lib/skills.js +441 -0
- package/dist/lib/terminal.d.ts +17 -0
- package/dist/lib/terminal.d.ts.map +1 -0
- package/dist/lib/terminal.js +66 -0
- package/package.json +48 -5
- package/src/index.ts +1 -0
- package/src/lib/bootstrap-project.ts +222 -0
- package/src/lib/cli-context.ts +38 -0
- package/src/lib/cli.ts +120 -0
- package/src/lib/commands/completion.ts +74 -0
- package/src/lib/commands/doctor.ts +393 -0
- package/src/lib/commands/help.ts +157 -0
- package/src/lib/commands/new.ts +104 -0
- package/src/lib/commands/routes.ts +251 -0
- package/src/lib/commands/skills.ts +306 -0
- package/src/lib/commands/test.ts +25 -0
- package/src/lib/commands/version.ts +39 -0
- package/src/lib/completion.ts +561 -0
- package/src/lib/contained-path.ts +13 -0
- package/src/lib/controller-files.ts +79 -0
- package/src/lib/controller-ownership.ts +397 -0
- package/src/lib/display-path.ts +16 -0
- package/src/lib/doctor/controller-findings.ts +157 -0
- package/src/lib/doctor/controller-fix-plans.ts +184 -0
- package/src/lib/doctor/controller-placeholders.ts +242 -0
- package/src/lib/doctor/controllers.ts +23 -0
- package/src/lib/doctor/environment.ts +289 -0
- package/src/lib/doctor/fixes.ts +84 -0
- package/src/lib/doctor/project.ts +435 -0
- package/src/lib/doctor/types.ts +95 -0
- package/src/lib/errors.ts +379 -0
- package/src/lib/help-text.ts +88 -0
- package/src/lib/load-route-map-worker.ts +110 -0
- package/src/lib/parse-args.ts +82 -0
- package/src/lib/remix-version.ts +80 -0
- package/src/lib/reporter.ts +535 -0
- package/src/lib/route-map.ts +303 -0
- package/src/lib/skills-cache.ts +140 -0
- package/src/lib/skills.ts +706 -0
- package/src/lib/terminal.ts +86 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { getOwnerFileExtension, isActionFileName, isControllerEntryFileName, } from "../controller-files.js";
|
|
4
|
+
import { CliError } from "../errors.js";
|
|
5
|
+
import { loadRouteManifestFromAppRoot } from "../route-map.js";
|
|
6
|
+
import { createDoctorSuite, } from "./types.js";
|
|
7
|
+
export async function checkProject(projectRoot) {
|
|
8
|
+
let routesFile = path.join(projectRoot, 'app', 'routes.ts');
|
|
9
|
+
if (!(await pathExists(routesFile))) {
|
|
10
|
+
return {
|
|
11
|
+
routesFile,
|
|
12
|
+
suite: createDoctorSuite('project', [
|
|
13
|
+
{
|
|
14
|
+
code: 'routes-file-missing',
|
|
15
|
+
expectedPath: 'app/routes.ts',
|
|
16
|
+
fixable: true,
|
|
17
|
+
message: 'Project is missing app/routes.ts.',
|
|
18
|
+
severity: 'warn',
|
|
19
|
+
suite: 'project',
|
|
20
|
+
},
|
|
21
|
+
]),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
let routeManifest = await loadRouteManifestFromAppRoot(projectRoot);
|
|
26
|
+
return {
|
|
27
|
+
routeManifest,
|
|
28
|
+
routesFile,
|
|
29
|
+
suite: createDoctorSuite('project', []),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
let finding = toProjectFinding(error);
|
|
34
|
+
return {
|
|
35
|
+
routesFile,
|
|
36
|
+
suite: createDoctorSuite('project', [finding]),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function getProjectFixPlans(projectRoot) {
|
|
41
|
+
let routesFile = path.join(projectRoot, 'app', 'routes.ts');
|
|
42
|
+
let homeActionPath = normalizeRelativePath(path.join('app', 'controllers', `home${await inferHomeOwnerExtension(projectRoot)}`));
|
|
43
|
+
if (await pathExists(routesFile)) {
|
|
44
|
+
let routesSource = await fs.readFile(routesFile, 'utf8');
|
|
45
|
+
if (!hasOnlyWhitespaceAndComments(routesSource)) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
return [
|
|
49
|
+
{
|
|
50
|
+
code: 'routes-export-missing',
|
|
51
|
+
contents: renderDefaultRoutesFile(),
|
|
52
|
+
kind: 'update-file',
|
|
53
|
+
path: 'app/routes.ts',
|
|
54
|
+
suite: 'project',
|
|
55
|
+
},
|
|
56
|
+
...((await pathExists(path.join(projectRoot, homeActionPath)))
|
|
57
|
+
? []
|
|
58
|
+
: [
|
|
59
|
+
{
|
|
60
|
+
code: 'missing-owner',
|
|
61
|
+
contents: renderDefaultHomeAction(homeActionPath),
|
|
62
|
+
kind: 'create-file',
|
|
63
|
+
path: homeActionPath,
|
|
64
|
+
routeName: 'home',
|
|
65
|
+
suite: 'project',
|
|
66
|
+
},
|
|
67
|
+
]),
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
code: 'routes-file-missing',
|
|
73
|
+
contents: renderDefaultRoutesFile(),
|
|
74
|
+
kind: 'create-file',
|
|
75
|
+
path: 'app/routes.ts',
|
|
76
|
+
suite: 'project',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
code: 'missing-owner',
|
|
80
|
+
contents: renderDefaultHomeAction(homeActionPath),
|
|
81
|
+
kind: 'create-file',
|
|
82
|
+
path: homeActionPath,
|
|
83
|
+
routeName: 'home',
|
|
84
|
+
suite: 'project',
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
function toProjectFinding(error) {
|
|
89
|
+
if (error instanceof CliError) {
|
|
90
|
+
if (error.code === 'RMX_ROUTE_MAP_LOADER_INVALID_JSON') {
|
|
91
|
+
return {
|
|
92
|
+
code: 'route-map-invalid-json',
|
|
93
|
+
message: 'Route-map loader returned invalid JSON while loading app/routes.ts.',
|
|
94
|
+
severity: 'warn',
|
|
95
|
+
suite: 'project',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (error.code === 'RMX_ROUTE_MAP_LOADER_SIGNAL') {
|
|
99
|
+
return {
|
|
100
|
+
code: 'route-map-loader-signal',
|
|
101
|
+
message: error.message,
|
|
102
|
+
severity: 'warn',
|
|
103
|
+
suite: 'project',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (error.code === 'RMX_ROUTE_MAP_LOADER_FAILED') {
|
|
107
|
+
return classifyRouteMapLoaderFailure(error.message);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
111
|
+
return {
|
|
112
|
+
code: 'route-module-import-failed',
|
|
113
|
+
message: `Failed to load app/routes.ts: ${message}`,
|
|
114
|
+
severity: 'warn',
|
|
115
|
+
suite: 'project',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function classifyRouteMapLoaderFailure(message) {
|
|
119
|
+
if (message.includes('must export a named "routes" value')) {
|
|
120
|
+
return {
|
|
121
|
+
code: 'routes-export-missing',
|
|
122
|
+
message: 'app/routes.ts must export a named "routes" value.',
|
|
123
|
+
severity: 'warn',
|
|
124
|
+
suite: 'project',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (message.startsWith('Invalid route map value at "') ||
|
|
128
|
+
message.startsWith('Detected a route map cycle at "')) {
|
|
129
|
+
return {
|
|
130
|
+
code: 'route-map-invalid',
|
|
131
|
+
message,
|
|
132
|
+
severity: 'warn',
|
|
133
|
+
suite: 'project',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
code: 'route-module-import-failed',
|
|
138
|
+
message: `Failed to load app/routes.ts: ${message}`,
|
|
139
|
+
severity: 'warn',
|
|
140
|
+
suite: 'project',
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async function pathExists(filePath) {
|
|
144
|
+
try {
|
|
145
|
+
await fs.access(filePath);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
let nodeError = error;
|
|
150
|
+
if (nodeError.code === 'ENOENT') {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function hasOnlyWhitespaceAndComments(source) {
|
|
157
|
+
for (let index = 0; index < source.length;) {
|
|
158
|
+
let char = source[index];
|
|
159
|
+
let next = source[index + 1];
|
|
160
|
+
if (char == null || /\s/.test(char)) {
|
|
161
|
+
index += 1;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (char === '/' && next === '/') {
|
|
165
|
+
index += 2;
|
|
166
|
+
while (index < source.length && source[index] !== '\n') {
|
|
167
|
+
index += 1;
|
|
168
|
+
}
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (char === '/' && next === '*') {
|
|
172
|
+
index += 2;
|
|
173
|
+
while (index < source.length && !(source[index] === '*' && source[index + 1] === '/')) {
|
|
174
|
+
index += 1;
|
|
175
|
+
}
|
|
176
|
+
index = Math.min(index + 2, source.length);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
async function inferHomeOwnerExtension(projectRoot) {
|
|
184
|
+
let controllersDir = path.join(projectRoot, 'app', 'controllers');
|
|
185
|
+
let extensions = await collectOwnerExtensions(controllersDir);
|
|
186
|
+
if (extensions.length > 0) {
|
|
187
|
+
return getMostCommonExtension(extensions);
|
|
188
|
+
}
|
|
189
|
+
if (await pathExists(path.join(projectRoot, 'tsconfig.json'))) {
|
|
190
|
+
return '.tsx';
|
|
191
|
+
}
|
|
192
|
+
return '.js';
|
|
193
|
+
}
|
|
194
|
+
async function collectOwnerExtensions(directory) {
|
|
195
|
+
try {
|
|
196
|
+
let entries = await fs.readdir(directory, { withFileTypes: true });
|
|
197
|
+
let extensions = [];
|
|
198
|
+
for (let entry of entries) {
|
|
199
|
+
let absolutePath = path.join(directory, entry.name);
|
|
200
|
+
if (entry.isDirectory()) {
|
|
201
|
+
extensions.push(...(await collectOwnerExtensions(absolutePath)));
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
let fileName = path.basename(absolutePath);
|
|
205
|
+
if (!isActionFileName(fileName) && !isControllerEntryFileName(fileName)) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
let extension = getOwnerFileExtension(fileName);
|
|
209
|
+
if (extension != null) {
|
|
210
|
+
extensions.push(extension);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return extensions;
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
let nodeError = error;
|
|
217
|
+
if (nodeError.code === 'ENOENT') {
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function getMostCommonExtension(extensions) {
|
|
224
|
+
let priority = ['.tsx', '.ts', '.jsx', '.js'];
|
|
225
|
+
let counts = new Map();
|
|
226
|
+
for (let extension of extensions) {
|
|
227
|
+
counts.set(extension, (counts.get(extension) ?? 0) + 1);
|
|
228
|
+
}
|
|
229
|
+
let bestExtension = priority[0];
|
|
230
|
+
let bestCount = -1;
|
|
231
|
+
for (let extension of priority) {
|
|
232
|
+
let count = counts.get(extension) ?? 0;
|
|
233
|
+
if (count > bestCount) {
|
|
234
|
+
bestCount = count;
|
|
235
|
+
bestExtension = extension;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return bestExtension;
|
|
239
|
+
}
|
|
240
|
+
function renderDefaultRoutesFile() {
|
|
241
|
+
return [
|
|
242
|
+
`import { route } from 'remix/fetch-router/routes'`,
|
|
243
|
+
'',
|
|
244
|
+
'export const routes = route({',
|
|
245
|
+
` home: '/',`,
|
|
246
|
+
'})',
|
|
247
|
+
'',
|
|
248
|
+
].join('\n');
|
|
249
|
+
}
|
|
250
|
+
function normalizeRelativePath(filePath) {
|
|
251
|
+
return filePath.split(path.sep).join('/');
|
|
252
|
+
}
|
|
253
|
+
function renderDefaultHomeAction(entryPath) {
|
|
254
|
+
let extension = getOwnerFileExtension(entryPath);
|
|
255
|
+
if (extension === '.js') {
|
|
256
|
+
return [
|
|
257
|
+
`import { html } from 'remix/html-template'`,
|
|
258
|
+
`import { createHtmlResponse } from 'remix/response/html'`,
|
|
259
|
+
'',
|
|
260
|
+
'export const home = {',
|
|
261
|
+
' handler() {',
|
|
262
|
+
' let page = html`',
|
|
263
|
+
' <html lang="en">',
|
|
264
|
+
' <head>',
|
|
265
|
+
' <meta charset="utf-8" />',
|
|
266
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
267
|
+
' <title>Home</title>',
|
|
268
|
+
' </head>',
|
|
269
|
+
' <body>',
|
|
270
|
+
' <h1>Home</h1>',
|
|
271
|
+
' <p>Update app/routes.ts and app/controllers/home to keep building your app.</p>',
|
|
272
|
+
' </body>',
|
|
273
|
+
' </html>',
|
|
274
|
+
' `',
|
|
275
|
+
' return createHtmlResponse(page)',
|
|
276
|
+
' },',
|
|
277
|
+
'}',
|
|
278
|
+
'',
|
|
279
|
+
].join('\n');
|
|
280
|
+
}
|
|
281
|
+
if (extension === '.ts') {
|
|
282
|
+
return [
|
|
283
|
+
`import type { BuildAction } from 'remix/fetch-router'`,
|
|
284
|
+
`import { html } from 'remix/html-template'`,
|
|
285
|
+
`import { createHtmlResponse } from 'remix/response/html'`,
|
|
286
|
+
'',
|
|
287
|
+
`import type { routes } from '../routes.ts'`,
|
|
288
|
+
'',
|
|
289
|
+
`export const home: BuildAction<'ANY', typeof routes.home> = {`,
|
|
290
|
+
' handler() {',
|
|
291
|
+
' let page = html`',
|
|
292
|
+
' <html lang="en">',
|
|
293
|
+
' <head>',
|
|
294
|
+
' <meta charset="utf-8" />',
|
|
295
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
296
|
+
' <title>Home</title>',
|
|
297
|
+
' </head>',
|
|
298
|
+
' <body>',
|
|
299
|
+
' <h1>Home</h1>',
|
|
300
|
+
' <p>Update app/routes.ts and app/controllers/home to keep building your app.</p>',
|
|
301
|
+
' </body>',
|
|
302
|
+
' </html>',
|
|
303
|
+
' `',
|
|
304
|
+
' return createHtmlResponse(page)',
|
|
305
|
+
' },',
|
|
306
|
+
'}',
|
|
307
|
+
'',
|
|
308
|
+
].join('\n');
|
|
309
|
+
}
|
|
310
|
+
if (extension === '.jsx') {
|
|
311
|
+
return [
|
|
312
|
+
`import { renderToStream } from 'remix/ui/server'`,
|
|
313
|
+
`import { createHtmlResponse } from 'remix/response/html'`,
|
|
314
|
+
'',
|
|
315
|
+
'export const home = {',
|
|
316
|
+
' handler() {',
|
|
317
|
+
' let page = <HomePage />',
|
|
318
|
+
' return createHtmlResponse(renderToStream(page))',
|
|
319
|
+
' },',
|
|
320
|
+
'}',
|
|
321
|
+
'',
|
|
322
|
+
'function HomePage() {',
|
|
323
|
+
' return () => (',
|
|
324
|
+
' <html lang="en">',
|
|
325
|
+
' <head>',
|
|
326
|
+
' <meta charSet="utf-8" />',
|
|
327
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
328
|
+
' <title>Home</title>',
|
|
329
|
+
' </head>',
|
|
330
|
+
' <body>',
|
|
331
|
+
' <h1>Home</h1>',
|
|
332
|
+
' <p>Update app/routes.ts and app/controllers/home to keep building your app.</p>',
|
|
333
|
+
' </body>',
|
|
334
|
+
' </html>',
|
|
335
|
+
' )',
|
|
336
|
+
'}',
|
|
337
|
+
'',
|
|
338
|
+
].join('\n');
|
|
339
|
+
}
|
|
340
|
+
return [
|
|
341
|
+
`import type { BuildAction } from 'remix/fetch-router'`,
|
|
342
|
+
`import { renderToStream } from 'remix/ui/server'`,
|
|
343
|
+
`import { createHtmlResponse } from 'remix/response/html'`,
|
|
344
|
+
'',
|
|
345
|
+
`import type { routes } from '../routes.ts'`,
|
|
346
|
+
'',
|
|
347
|
+
`export const home: BuildAction<'ANY', typeof routes.home> = {`,
|
|
348
|
+
' handler() {',
|
|
349
|
+
' let page = <HomePage />',
|
|
350
|
+
' return createHtmlResponse(renderToStream(page))',
|
|
351
|
+
' },',
|
|
352
|
+
'}',
|
|
353
|
+
'',
|
|
354
|
+
'function HomePage() {',
|
|
355
|
+
' return () => (',
|
|
356
|
+
' <html lang="en">',
|
|
357
|
+
' <head>',
|
|
358
|
+
' <meta charSet="utf-8" />',
|
|
359
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
360
|
+
' <title>Home</title>',
|
|
361
|
+
' </head>',
|
|
362
|
+
' <body>',
|
|
363
|
+
' <h1>Home</h1>',
|
|
364
|
+
' <p>Update app/routes.ts and app/controllers/home to keep building your app.</p>',
|
|
365
|
+
' </body>',
|
|
366
|
+
' </html>',
|
|
367
|
+
' )',
|
|
368
|
+
'}',
|
|
369
|
+
'',
|
|
370
|
+
].join('\n');
|
|
371
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type DoctorSuiteName = 'environment' | 'project' | 'controllers';
|
|
2
|
+
export type DoctorFindingSeverity = 'warn' | 'advice';
|
|
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' | 'wrong-owner-kind' | 'ambiguous-owner' | 'duplicate-owner-file' | 'incomplete-controller' | 'promotion-drift' | 'orphan-action' | 'orphan-controller' | 'orphan-route-directory';
|
|
5
|
+
export type DoctorFixKind = 'create-directory' | 'create-file' | 'update-file';
|
|
6
|
+
export interface DoctorFinding {
|
|
7
|
+
actualPath?: string;
|
|
8
|
+
code: DoctorFindingCode;
|
|
9
|
+
expectedPath?: string;
|
|
10
|
+
fixable?: boolean;
|
|
11
|
+
message: string;
|
|
12
|
+
routeName?: string;
|
|
13
|
+
severity: DoctorFindingSeverity;
|
|
14
|
+
suite: DoctorSuiteName;
|
|
15
|
+
}
|
|
16
|
+
export interface DoctorFixPlan {
|
|
17
|
+
code: DoctorFindingCode;
|
|
18
|
+
contents?: string;
|
|
19
|
+
kind: DoctorFixKind;
|
|
20
|
+
path: string;
|
|
21
|
+
routeName?: string;
|
|
22
|
+
suite: DoctorSuiteName;
|
|
23
|
+
}
|
|
24
|
+
export interface DoctorAppliedFix {
|
|
25
|
+
code: DoctorFindingCode;
|
|
26
|
+
kind: DoctorFixKind;
|
|
27
|
+
path: string;
|
|
28
|
+
routeName?: string;
|
|
29
|
+
suite: DoctorSuiteName;
|
|
30
|
+
}
|
|
31
|
+
export interface DoctorSuiteResult {
|
|
32
|
+
appliedFixes?: DoctorAppliedFix[];
|
|
33
|
+
findings: DoctorFinding[];
|
|
34
|
+
name: DoctorSuiteName;
|
|
35
|
+
reason?: string;
|
|
36
|
+
status: DoctorSuiteStatus;
|
|
37
|
+
}
|
|
38
|
+
export interface DoctorReport {
|
|
39
|
+
appliedFixes?: DoctorAppliedFix[];
|
|
40
|
+
appRoot?: string;
|
|
41
|
+
findings: DoctorFinding[];
|
|
42
|
+
remainingFindings?: DoctorFinding[];
|
|
43
|
+
routesFile?: string;
|
|
44
|
+
suites: DoctorSuiteResult[];
|
|
45
|
+
}
|
|
46
|
+
export declare function createDoctorSuite(name: DoctorSuiteName, findings: DoctorFinding[]): DoctorSuiteResult;
|
|
47
|
+
export declare function createSkippedDoctorSuite(name: DoctorSuiteName, reason: string): DoctorSuiteResult;
|
|
48
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +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,aAAa,CAAA;AACvE,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,kBAAkB,GAClB,iBAAiB,GACjB,sBAAsB,GACtB,uBAAuB,GACvB,iBAAiB,GACjB,eAAe,GACf,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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function createDoctorSuite(name, findings) {
|
|
2
|
+
return {
|
|
3
|
+
findings,
|
|
4
|
+
name,
|
|
5
|
+
status: findings.some((finding) => finding.severity === 'warn') ? 'issues' : 'ok',
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function createSkippedDoctorSuite(name, reason) {
|
|
9
|
+
return {
|
|
10
|
+
findings: [],
|
|
11
|
+
name,
|
|
12
|
+
reason,
|
|
13
|
+
status: 'skipped',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
export interface CliErrorDefinition {
|
|
2
|
+
code: string;
|
|
3
|
+
docsUrl?: string;
|
|
4
|
+
fix?: string;
|
|
5
|
+
title: string;
|
|
6
|
+
}
|
|
7
|
+
export type CliErrorContext = Record<string, boolean | number | string | null | undefined>;
|
|
8
|
+
export interface CliErrorOptions {
|
|
9
|
+
code: string;
|
|
10
|
+
context?: CliErrorContext;
|
|
11
|
+
docsUrl?: string;
|
|
12
|
+
fix?: string;
|
|
13
|
+
message: string;
|
|
14
|
+
showHelp?: boolean;
|
|
15
|
+
title: string;
|
|
16
|
+
}
|
|
17
|
+
export declare const CLI_ERROR_DEFINITIONS: {
|
|
18
|
+
appNameUnavailable: {
|
|
19
|
+
code: string;
|
|
20
|
+
title: string;
|
|
21
|
+
fix: string;
|
|
22
|
+
};
|
|
23
|
+
remixVersionUnavailable: {
|
|
24
|
+
code: string;
|
|
25
|
+
title: string;
|
|
26
|
+
};
|
|
27
|
+
invalidCompletionRequest: {
|
|
28
|
+
code: string;
|
|
29
|
+
title: string;
|
|
30
|
+
};
|
|
31
|
+
fetchUnavailable: {
|
|
32
|
+
code: string;
|
|
33
|
+
title: string;
|
|
34
|
+
fix: string;
|
|
35
|
+
};
|
|
36
|
+
internalError: {
|
|
37
|
+
code: string;
|
|
38
|
+
title: string;
|
|
39
|
+
};
|
|
40
|
+
invalidFlagCombination: {
|
|
41
|
+
code: string;
|
|
42
|
+
title: string;
|
|
43
|
+
fix: string;
|
|
44
|
+
};
|
|
45
|
+
invalidPackageName: {
|
|
46
|
+
code: string;
|
|
47
|
+
title: string;
|
|
48
|
+
fix: string;
|
|
49
|
+
};
|
|
50
|
+
missingOptionValue: {
|
|
51
|
+
code: string;
|
|
52
|
+
title: string;
|
|
53
|
+
fix: string;
|
|
54
|
+
};
|
|
55
|
+
missingTargetDirectory: {
|
|
56
|
+
code: string;
|
|
57
|
+
title: string;
|
|
58
|
+
fix: string;
|
|
59
|
+
};
|
|
60
|
+
projectRootNotFound: {
|
|
61
|
+
code: string;
|
|
62
|
+
title: string;
|
|
63
|
+
fix: string;
|
|
64
|
+
};
|
|
65
|
+
remoteSkillDataMissing: {
|
|
66
|
+
code: string;
|
|
67
|
+
title: string;
|
|
68
|
+
};
|
|
69
|
+
routeMapLoaderFailed: {
|
|
70
|
+
code: string;
|
|
71
|
+
title: string;
|
|
72
|
+
fix: string;
|
|
73
|
+
};
|
|
74
|
+
routeMapLoaderInvalidJson: {
|
|
75
|
+
code: string;
|
|
76
|
+
title: string;
|
|
77
|
+
fix: string;
|
|
78
|
+
};
|
|
79
|
+
routeMapLoaderSignal: {
|
|
80
|
+
code: string;
|
|
81
|
+
title: string;
|
|
82
|
+
fix: string;
|
|
83
|
+
};
|
|
84
|
+
routeOwnerPlanUnresolved: {
|
|
85
|
+
code: string;
|
|
86
|
+
title: string;
|
|
87
|
+
};
|
|
88
|
+
routesFileNotFound: {
|
|
89
|
+
code: string;
|
|
90
|
+
title: string;
|
|
91
|
+
fix: string;
|
|
92
|
+
};
|
|
93
|
+
targetDirectoryNotEmpty: {
|
|
94
|
+
code: string;
|
|
95
|
+
title: string;
|
|
96
|
+
fix: string;
|
|
97
|
+
};
|
|
98
|
+
targetPathNotDirectory: {
|
|
99
|
+
code: string;
|
|
100
|
+
title: string;
|
|
101
|
+
fix: string;
|
|
102
|
+
};
|
|
103
|
+
unexpectedExtraArgument: {
|
|
104
|
+
code: string;
|
|
105
|
+
title: string;
|
|
106
|
+
fix: string;
|
|
107
|
+
};
|
|
108
|
+
unknownArgument: {
|
|
109
|
+
code: string;
|
|
110
|
+
title: string;
|
|
111
|
+
fix: string;
|
|
112
|
+
};
|
|
113
|
+
unknownCommand: {
|
|
114
|
+
code: string;
|
|
115
|
+
title: string;
|
|
116
|
+
fix: string;
|
|
117
|
+
};
|
|
118
|
+
unknownCompletionShell: {
|
|
119
|
+
code: string;
|
|
120
|
+
title: string;
|
|
121
|
+
fix: string;
|
|
122
|
+
};
|
|
123
|
+
unknownHelpTopic: {
|
|
124
|
+
code: string;
|
|
125
|
+
title: string;
|
|
126
|
+
fix: string;
|
|
127
|
+
};
|
|
128
|
+
unknownSkillsCommand: {
|
|
129
|
+
code: string;
|
|
130
|
+
title: string;
|
|
131
|
+
fix: string;
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
export declare class CliError extends Error {
|
|
135
|
+
code: string;
|
|
136
|
+
context?: CliErrorContext;
|
|
137
|
+
docsUrl?: string;
|
|
138
|
+
fix?: string;
|
|
139
|
+
showHelp: boolean;
|
|
140
|
+
title: string;
|
|
141
|
+
constructor(options: CliErrorOptions);
|
|
142
|
+
}
|
|
143
|
+
export declare class UsageError extends CliError {
|
|
144
|
+
constructor(options: CliErrorOptions);
|
|
145
|
+
}
|
|
146
|
+
export declare function appNameUnavailable(targetDir?: string): UsageError;
|
|
147
|
+
export declare function remixVersionUnavailable(): CliError;
|
|
148
|
+
export declare function invalidCompletionRequest(): CliError;
|
|
149
|
+
export declare function fetchUnavailable(): CliError;
|
|
150
|
+
export declare function invalidFlagCombination(details: string): UsageError;
|
|
151
|
+
export declare function invalidPackageName(input: string): UsageError;
|
|
152
|
+
export declare function missingOptionValue(option: string): UsageError;
|
|
153
|
+
export declare function missingTargetDirectory(): UsageError;
|
|
154
|
+
export declare function projectRootNotFound(startDir?: string): CliError;
|
|
155
|
+
export declare function remoteSkillDataMissing(name: string): CliError;
|
|
156
|
+
export declare function routeMapLoaderFailed(details: string): CliError;
|
|
157
|
+
export declare function routeMapLoaderInvalidJson(): CliError;
|
|
158
|
+
export declare function routeMapLoaderSignal(signal: NodeJS.Signals): CliError;
|
|
159
|
+
export declare function routeOwnerPlanUnresolved(routeName: string): CliError;
|
|
160
|
+
export declare function routesFileNotFound(startDir?: string): CliError;
|
|
161
|
+
export declare function targetDirectoryNotEmpty(targetDir: string): UsageError;
|
|
162
|
+
export declare function targetPathNotDirectory(targetDir: string): UsageError;
|
|
163
|
+
export declare function toCliError(error: unknown): CliError;
|
|
164
|
+
export declare function unknownArgument(argument: string): UsageError;
|
|
165
|
+
export declare function unknownCommand(command: string): UsageError;
|
|
166
|
+
export declare function unknownCompletionShell(shell: string): UsageError;
|
|
167
|
+
export declare function unknownHelpTopic(topic: string): UsageError;
|
|
168
|
+
export declare function unknownSkillsCommand(command: string): UsageError;
|
|
169
|
+
export declare function unexpectedExtraArgument(argument: string): UsageError;
|
|
170
|
+
export declare function renderCliError(error: unknown, options?: {
|
|
171
|
+
helpText?: string;
|
|
172
|
+
}): string;
|
|
173
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"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"}
|