@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
package/dist/lib/completion.js
CHANGED
|
@@ -1,25 +1,6 @@
|
|
|
1
1
|
const COMPLETION_SHELLS = ['bash', 'zsh'];
|
|
2
|
-
const HELP_COMMANDS = [
|
|
3
|
-
|
|
4
|
-
'doctor',
|
|
5
|
-
'help',
|
|
6
|
-
'new',
|
|
7
|
-
'routes',
|
|
8
|
-
'skills',
|
|
9
|
-
'test',
|
|
10
|
-
'version',
|
|
11
|
-
];
|
|
12
|
-
const ROOT_COMMANDS = [
|
|
13
|
-
'completion',
|
|
14
|
-
'doctor',
|
|
15
|
-
'help',
|
|
16
|
-
'new',
|
|
17
|
-
'routes',
|
|
18
|
-
'skills',
|
|
19
|
-
'test',
|
|
20
|
-
'version',
|
|
21
|
-
];
|
|
22
|
-
const SKILLS_COMMANDS = ['install', 'list'];
|
|
2
|
+
const HELP_COMMANDS = ['completion', 'doctor', 'help', 'new', 'routes', 'test', 'version'];
|
|
3
|
+
const ROOT_COMMANDS = ['completion', 'doctor', 'help', 'new', 'routes', 'test', 'version'];
|
|
23
4
|
export function isCompletionShell(value) {
|
|
24
5
|
return COMPLETION_SHELLS.includes(value);
|
|
25
6
|
}
|
|
@@ -152,18 +133,11 @@ function completeCommand(command, tokens, currentWord, usedGlobalFlags) {
|
|
|
152
133
|
if (command === 'routes') {
|
|
153
134
|
return completeRoutes(tokens, currentWord, usedGlobalFlags);
|
|
154
135
|
}
|
|
155
|
-
if (command === 'skills') {
|
|
156
|
-
return completeSkills(tokens, currentWord, usedGlobalFlags);
|
|
157
|
-
}
|
|
158
136
|
if (command === 'version') {
|
|
159
137
|
return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, []);
|
|
160
138
|
}
|
|
161
139
|
if (command === 'test') {
|
|
162
|
-
return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, [
|
|
163
|
-
'--coverage',
|
|
164
|
-
'--watch',
|
|
165
|
-
'--help',
|
|
166
|
-
]);
|
|
140
|
+
return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, ['--coverage', '--watch']);
|
|
167
141
|
}
|
|
168
142
|
if (command === 'completion') {
|
|
169
143
|
return completeCompletionCommand(tokens, currentWord, usedGlobalFlags);
|
|
@@ -178,11 +152,8 @@ function completeHelp(tokens, currentWord, usedGlobalFlags) {
|
|
|
178
152
|
if (filteredTokens.length === 0) {
|
|
179
153
|
return completeValues(withHelpFlags([...HELP_COMMANDS], usedGlobalFlags), currentWord);
|
|
180
154
|
}
|
|
181
|
-
let [
|
|
155
|
+
let [, ...rest] = filteredTokens;
|
|
182
156
|
if (rest.length === 0) {
|
|
183
|
-
if (topic === 'skills') {
|
|
184
|
-
return completeValues(withHelpFlags([...SKILLS_COMMANDS], usedGlobalFlags), currentWord);
|
|
185
|
-
}
|
|
186
157
|
return completeValues([], currentWord);
|
|
187
158
|
}
|
|
188
159
|
return completeValues([], currentWord);
|
|
@@ -264,79 +235,6 @@ function completeRoutes(tokens, currentWord, usedGlobalFlags) {
|
|
|
264
235
|
], usedGlobalFlags);
|
|
265
236
|
return completeValues(flags, currentWord);
|
|
266
237
|
}
|
|
267
|
-
function completeSkills(tokens, currentWord, usedGlobalFlags) {
|
|
268
|
-
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags);
|
|
269
|
-
if (filteredTokens == null) {
|
|
270
|
-
return completeValues([], currentWord);
|
|
271
|
-
}
|
|
272
|
-
if (filteredTokens.length === 0) {
|
|
273
|
-
return completeValues(withHelpFlags([...SKILLS_COMMANDS], usedGlobalFlags), currentWord);
|
|
274
|
-
}
|
|
275
|
-
let [subcommand, ...rest] = filteredTokens;
|
|
276
|
-
if (subcommand === 'install') {
|
|
277
|
-
return completeSkillsInstall(rest, currentWord, usedGlobalFlags);
|
|
278
|
-
}
|
|
279
|
-
if (subcommand === 'list') {
|
|
280
|
-
return completeSkillsList(rest, currentWord, usedGlobalFlags);
|
|
281
|
-
}
|
|
282
|
-
return completeValues([], currentWord);
|
|
283
|
-
}
|
|
284
|
-
function completeSkillsInstall(tokens, currentWord, usedGlobalFlags) {
|
|
285
|
-
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags);
|
|
286
|
-
if (filteredTokens == null) {
|
|
287
|
-
return completeValues([], currentWord);
|
|
288
|
-
}
|
|
289
|
-
let hasDir = false;
|
|
290
|
-
let expectsDir = false;
|
|
291
|
-
for (let token of filteredTokens) {
|
|
292
|
-
if (expectsDir) {
|
|
293
|
-
expectsDir = false;
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
if (token === '--dir') {
|
|
297
|
-
hasDir = true;
|
|
298
|
-
expectsDir = true;
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
return completeValues([], currentWord);
|
|
302
|
-
}
|
|
303
|
-
if (expectsDir) {
|
|
304
|
-
return { mode: 'files' };
|
|
305
|
-
}
|
|
306
|
-
return completeValues(withHelpFlags(!hasDir ? ['--dir'] : [], usedGlobalFlags), currentWord);
|
|
307
|
-
}
|
|
308
|
-
function completeSkillsList(tokens, currentWord, usedGlobalFlags) {
|
|
309
|
-
return completeSkillsDirectoryCommand(tokens, currentWord, usedGlobalFlags);
|
|
310
|
-
}
|
|
311
|
-
function completeSkillsDirectoryCommand(tokens, currentWord, usedGlobalFlags) {
|
|
312
|
-
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags);
|
|
313
|
-
if (filteredTokens == null) {
|
|
314
|
-
return completeValues([], currentWord);
|
|
315
|
-
}
|
|
316
|
-
let hasDir = false;
|
|
317
|
-
let hasJson = false;
|
|
318
|
-
let expectsDir = false;
|
|
319
|
-
for (let token of filteredTokens) {
|
|
320
|
-
if (expectsDir) {
|
|
321
|
-
expectsDir = false;
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
if (token === '--dir') {
|
|
325
|
-
hasDir = true;
|
|
326
|
-
expectsDir = true;
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
if (token === '--json') {
|
|
330
|
-
hasJson = true;
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
return completeValues([], currentWord);
|
|
334
|
-
}
|
|
335
|
-
if (expectsDir) {
|
|
336
|
-
return { mode: 'files' };
|
|
337
|
-
}
|
|
338
|
-
return completeValues(withHelpFlags([...(!hasDir ? ['--dir'] : []), ...(!hasJson ? ['--json'] : [])], usedGlobalFlags), currentWord);
|
|
339
|
-
}
|
|
340
238
|
function completeCompletionCommand(tokens, currentWord, usedGlobalFlags) {
|
|
341
239
|
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags);
|
|
342
240
|
if (filteredTokens == null) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
declare const OWNER_FILE_EXTENSIONS: readonly [".ts", ".tsx", ".js", ".jsx"];
|
|
2
2
|
export type OwnerFileExtension = (typeof OWNER_FILE_EXTENSIONS)[number];
|
|
3
|
-
export declare function getActionOwnerCandidates(segments: string[]): string[];
|
|
4
3
|
export declare function getControllerOwnerCandidates(segments: string[]): string[];
|
|
5
4
|
export declare function getRouteSubtreePath(segments: string[]): string;
|
|
6
5
|
export declare function getPreferredOwnerDisplayPath(candidates: string[]): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller-files.d.ts","sourceRoot":"","sources":["../../src/lib/controller-files.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,qBAAqB,yCAA0C,CAAA;
|
|
1
|
+
{"version":3,"file":"controller-files.d.ts","sourceRoot":"","sources":["../../src/lib/controller-files.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,qBAAqB,yCAA0C,CAAA;AAGrE,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAIzE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAE9D;AAED,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAGzE;AAED,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAAE,EACpB,SAAS,EAAE,kBAAkB,GAC5B,MAAM,GAAG,IAAI,CAEf;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAEjF;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAYrD;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQtE;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAS1D"}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
2
|
const OWNER_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
3
|
-
|
|
4
|
-
return OWNER_FILE_EXTENSIONS.map((extension) => normalizeRelativePath(path.join('app', 'controllers', `${path.join(...segments)}${extension}`)));
|
|
5
|
-
}
|
|
3
|
+
const ACTIONS_DIRECTORY = path.join('app', 'actions');
|
|
6
4
|
export function getControllerOwnerCandidates(segments) {
|
|
7
|
-
return OWNER_FILE_EXTENSIONS.map((extension) => normalizeRelativePath(path.join(
|
|
5
|
+
return OWNER_FILE_EXTENSIONS.map((extension) => normalizeRelativePath(path.join(ACTIONS_DIRECTORY, ...segments, `controller${extension}`)));
|
|
8
6
|
}
|
|
9
7
|
export function getRouteSubtreePath(segments) {
|
|
10
|
-
return normalizeRelativePath(path.join(
|
|
8
|
+
return normalizeRelativePath(path.join(ACTIONS_DIRECTORY, ...segments));
|
|
11
9
|
}
|
|
12
10
|
export function getPreferredOwnerDisplayPath(candidates) {
|
|
13
11
|
let tsxCandidate = candidates.find((candidate) => candidate.endsWith('.tsx'));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RouteTreeNodeKind } from './route-map.ts';
|
|
2
|
+
export declare const ROOT_ROUTE_NAME = "<root>";
|
|
2
3
|
export interface OwnershipRouteNode {
|
|
3
4
|
children: OwnershipRouteNode[];
|
|
4
5
|
key: string;
|
|
@@ -7,35 +8,34 @@ export interface OwnershipRouteNode {
|
|
|
7
8
|
name: string;
|
|
8
9
|
}
|
|
9
10
|
export interface ControllerDirectoryScan {
|
|
10
|
-
actionEntryPaths: Set<string>;
|
|
11
11
|
controllerEntryPaths: Set<string>;
|
|
12
|
-
|
|
12
|
+
routeDirectoryPaths: Set<string>;
|
|
13
13
|
routeLocalFilePaths: Set<string>;
|
|
14
14
|
}
|
|
15
|
+
export interface RouteDirectoryPlan {
|
|
16
|
+
directoryPath: string;
|
|
17
|
+
routeName: string;
|
|
18
|
+
}
|
|
15
19
|
export interface OwnedSubtreePlan {
|
|
16
|
-
alternateCandidates: string[];
|
|
17
|
-
alternateDisplayPath: string;
|
|
18
20
|
entryCandidates: string[];
|
|
19
21
|
entryDisplayPath: string;
|
|
20
|
-
kind: RouteOwnerKind;
|
|
21
22
|
routeName: string;
|
|
22
23
|
subtreePath: string;
|
|
23
24
|
}
|
|
24
25
|
export interface OwnedSubtree extends OwnedSubtreePlan {
|
|
25
|
-
actualAlternatePath: string | null;
|
|
26
|
-
actualAlternatePaths: string[];
|
|
27
26
|
actualEntryPath: string | null;
|
|
28
27
|
actualEntryPaths: string[];
|
|
29
28
|
claimedFilePaths: string[];
|
|
30
29
|
claimedRouteLocalFilePaths: string[];
|
|
31
30
|
}
|
|
32
31
|
export interface ControllerOwnership {
|
|
33
|
-
orphanActionPaths: string[];
|
|
34
32
|
orphanControllerPaths: string[];
|
|
35
33
|
orphanRouteDirectoryPaths: string[];
|
|
34
|
+
routeDirectories: RouteDirectoryPlan[];
|
|
36
35
|
scan: ControllerDirectoryScan;
|
|
37
36
|
subtrees: OwnedSubtree[];
|
|
38
37
|
}
|
|
39
38
|
export declare function inspectControllerOwnership(appRoot: string, tree: OwnershipRouteNode[]): Promise<ControllerOwnership>;
|
|
40
39
|
export declare function buildOwnedSubtrees(tree: OwnershipRouteNode[], parentSegments?: string[], subtrees?: OwnedSubtreePlan[]): OwnedSubtreePlan[];
|
|
40
|
+
export declare function buildRouteDirectories(tree: OwnershipRouteNode[], parentSegments?: string[], directories?: RouteDirectoryPlan[]): RouteDirectoryPlan[];
|
|
41
41
|
//# sourceMappingURL=controller-ownership.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller-ownership.d.ts","sourceRoot":"","sources":["../../src/lib/controller-ownership.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"controller-ownership.d.ts","sourceRoot":"","sources":["../../src/lib/controller-ownership.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAIvD,eAAO,MAAM,eAAe,WAAW,CAAA;AAEvC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,kBAAkB,EAAE,CAAA;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,iBAAiB,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,uBAAuB;IACtC,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACjC,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAChC,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,YAAa,SAAQ,gBAAgB;IACpD,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,0BAA0B,EAAE,MAAM,EAAE,CAAA;CACrC;AAED,MAAM,WAAW,mBAAmB;IAClC,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,yBAAyB,EAAE,MAAM,EAAE,CAAA;IACnC,gBAAgB,EAAE,kBAAkB,EAAE,CAAA;IACtC,IAAI,EAAE,uBAAuB,CAAA;IAC7B,QAAQ,EAAE,YAAY,EAAE,CAAA;CACzB;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,kBAAkB,EAAE,GACzB,OAAO,CAAC,mBAAmB,CAAC,CAa9B;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,kBAAkB,EAAE,EAC1B,cAAc,GAAE,MAAM,EAAO,EAC7B,QAAQ,GAAE,gBAAgB,EAAO,GAChC,gBAAgB,EAAE,CAsBpB;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,kBAAkB,EAAE,EAC1B,cAAc,GAAE,MAAM,EAAO,EAC7B,WAAW,GAAE,kBAAkB,EAAO,GACrC,kBAAkB,EAAE,CAetB"}
|
|
@@ -1,58 +1,69 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { getControllerOwnerCandidates, getOwnerModuleBaseName, getPreferredOwnerDisplayPath, getRouteSubtreePath, isControllerEntryFileName, toDiskSegment, } from "./controller-files.js";
|
|
4
|
+
const ACTIONS_PATH_PREFIX = 'app/actions/';
|
|
5
|
+
export const ROOT_ROUTE_NAME = '<root>';
|
|
4
6
|
export async function inspectControllerOwnership(appRoot, tree) {
|
|
5
7
|
let subtreePlans = buildOwnedSubtrees(tree);
|
|
8
|
+
let routeDirectories = buildRouteDirectories(tree);
|
|
6
9
|
let scan = await scanControllersDirectory(appRoot);
|
|
7
10
|
let subtrees = applyScanToSubtrees(subtreePlans, scan);
|
|
8
11
|
return {
|
|
9
|
-
orphanActionPaths: getOrphanActionPaths(subtreePlans, scan),
|
|
10
12
|
orphanControllerPaths: getOrphanControllerPaths(subtreePlans, scan),
|
|
11
|
-
orphanRouteDirectoryPaths: getOrphanRouteDirectoryPaths(
|
|
13
|
+
orphanRouteDirectoryPaths: getOrphanRouteDirectoryPaths(routeDirectories, scan),
|
|
14
|
+
routeDirectories,
|
|
12
15
|
scan,
|
|
13
16
|
subtrees,
|
|
14
17
|
};
|
|
15
18
|
}
|
|
16
19
|
export function buildOwnedSubtrees(tree, parentSegments = [], subtrees = []) {
|
|
20
|
+
if (parentSegments.length === 0 &&
|
|
21
|
+
subtrees.length === 0 &&
|
|
22
|
+
tree.some((node) => node.kind === 'route')) {
|
|
23
|
+
addSubtreePlan(ROOT_ROUTE_NAME, [], subtrees);
|
|
24
|
+
}
|
|
17
25
|
for (let node of tree) {
|
|
18
|
-
|
|
19
|
-
if (node.kind === 'group') {
|
|
20
|
-
let alternateCandidates = getActionOwnerCandidates(segments);
|
|
21
|
-
let entryCandidates = getControllerOwnerCandidates(segments);
|
|
22
|
-
subtrees.push({
|
|
23
|
-
alternateCandidates,
|
|
24
|
-
alternateDisplayPath: getPreferredOwnerDisplayPath(alternateCandidates),
|
|
25
|
-
entryCandidates,
|
|
26
|
-
entryDisplayPath: getPreferredOwnerDisplayPath(entryCandidates),
|
|
27
|
-
kind: 'controller',
|
|
28
|
-
routeName: node.name,
|
|
29
|
-
subtreePath: getRouteSubtreePath(segments),
|
|
30
|
-
});
|
|
31
|
-
buildOwnedSubtrees(node.children, segments, subtrees);
|
|
26
|
+
if (node.kind !== 'group') {
|
|
32
27
|
continue;
|
|
33
28
|
}
|
|
34
|
-
|
|
29
|
+
let segments = [...parentSegments, toDiskSegment(node.key)];
|
|
30
|
+
if (hasDirectRouteLeaf(node.children)) {
|
|
31
|
+
addSubtreePlan(node.name, segments, subtrees);
|
|
32
|
+
}
|
|
33
|
+
buildOwnedSubtrees(node.children, segments, subtrees);
|
|
34
|
+
}
|
|
35
|
+
return subtrees;
|
|
36
|
+
}
|
|
37
|
+
export function buildRouteDirectories(tree, parentSegments = [], directories = []) {
|
|
38
|
+
for (let node of tree) {
|
|
39
|
+
if (node.kind !== 'group') {
|
|
35
40
|
continue;
|
|
36
41
|
}
|
|
37
|
-
let
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
alternateCandidates,
|
|
41
|
-
alternateDisplayPath: getPreferredOwnerDisplayPath(alternateCandidates),
|
|
42
|
-
entryCandidates,
|
|
43
|
-
entryDisplayPath: getPreferredOwnerDisplayPath(entryCandidates),
|
|
44
|
-
kind: 'action',
|
|
42
|
+
let segments = [...parentSegments, toDiskSegment(node.key)];
|
|
43
|
+
directories.push({
|
|
44
|
+
directoryPath: getRouteSubtreePath(segments),
|
|
45
45
|
routeName: node.name,
|
|
46
|
-
subtreePath: getRouteSubtreePath(segments),
|
|
47
46
|
});
|
|
47
|
+
buildRouteDirectories(node.children, segments, directories);
|
|
48
48
|
}
|
|
49
|
-
return
|
|
49
|
+
return directories;
|
|
50
|
+
}
|
|
51
|
+
function hasDirectRouteLeaf(tree) {
|
|
52
|
+
return tree.some((node) => node.kind === 'route');
|
|
53
|
+
}
|
|
54
|
+
function addSubtreePlan(routeName, segments, subtrees) {
|
|
55
|
+
let entryCandidates = getControllerOwnerCandidates(segments);
|
|
56
|
+
subtrees.push({
|
|
57
|
+
entryCandidates,
|
|
58
|
+
entryDisplayPath: getPreferredOwnerDisplayPath(entryCandidates),
|
|
59
|
+
routeName,
|
|
60
|
+
subtreePath: getRouteSubtreePath(segments),
|
|
61
|
+
});
|
|
50
62
|
}
|
|
51
63
|
async function scanControllersDirectory(appRoot) {
|
|
52
|
-
let
|
|
64
|
+
let actionsDir = path.join(appRoot, 'app', 'actions');
|
|
53
65
|
let controllerEntryPaths = new Set();
|
|
54
|
-
let
|
|
55
|
-
let rootDirectoryPaths = new Set();
|
|
66
|
+
let routeDirectoryPaths = new Set();
|
|
56
67
|
let routeLocalFilePaths = new Set();
|
|
57
68
|
async function walk(currentDir, isRoot) {
|
|
58
69
|
let entries;
|
|
@@ -70,9 +81,7 @@ async function scanControllersDirectory(appRoot) {
|
|
|
70
81
|
let entryPath = path.join(currentDir, entry.name);
|
|
71
82
|
let relativePath = normalizeRelativePath(path.relative(appRoot, entryPath));
|
|
72
83
|
if (entry.isDirectory()) {
|
|
73
|
-
|
|
74
|
-
rootDirectoryPaths.add(relativePath);
|
|
75
|
-
}
|
|
84
|
+
routeDirectoryPaths.add(relativePath);
|
|
76
85
|
await walk(entryPath, false);
|
|
77
86
|
continue;
|
|
78
87
|
}
|
|
@@ -83,20 +92,15 @@ async function scanControllersDirectory(appRoot) {
|
|
|
83
92
|
controllerEntryPaths.add(relativePath);
|
|
84
93
|
continue;
|
|
85
94
|
}
|
|
86
|
-
if (
|
|
87
|
-
actionEntryPaths.add(relativePath);
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (!isRoot && isRouteLocalFileName(entry.name)) {
|
|
95
|
+
if (isRouteLocalFileName(entry.name)) {
|
|
91
96
|
routeLocalFilePaths.add(relativePath);
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
|
-
await walk(
|
|
100
|
+
await walk(actionsDir, true);
|
|
96
101
|
return {
|
|
97
|
-
actionEntryPaths,
|
|
98
102
|
controllerEntryPaths,
|
|
99
|
-
|
|
103
|
+
routeDirectoryPaths,
|
|
100
104
|
routeLocalFilePaths,
|
|
101
105
|
};
|
|
102
106
|
}
|
|
@@ -104,12 +108,9 @@ function applyScanToSubtrees(subtreePlans, scan) {
|
|
|
104
108
|
let claimedRouteLocalPaths = claimFilesToDeepestSubtree([...scan.routeLocalFilePaths], subtreePlans);
|
|
105
109
|
let claimedContentPaths = claimFilesToDeepestSubtree(getNestedContentPaths(scan), subtreePlans);
|
|
106
110
|
return subtreePlans.map((subtree) => {
|
|
107
|
-
let
|
|
108
|
-
let actualEntryPaths = findOwnerPaths(scan, subtree.kind, subtree.entryCandidates);
|
|
111
|
+
let actualEntryPaths = findOwnerPaths(scan, subtree.entryCandidates);
|
|
109
112
|
return {
|
|
110
113
|
...subtree,
|
|
111
|
-
actualAlternatePath: actualAlternatePaths[0] ?? null,
|
|
112
|
-
actualAlternatePaths,
|
|
113
114
|
actualEntryPath: actualEntryPaths[0] ?? null,
|
|
114
115
|
actualEntryPaths,
|
|
115
116
|
claimedFilePaths: claimedContentPaths.get(subtree.routeName) ?? [],
|
|
@@ -143,49 +144,20 @@ function claimFilesToDeepestSubtree(filePaths, subtreePlans) {
|
|
|
143
144
|
}
|
|
144
145
|
return claims;
|
|
145
146
|
}
|
|
146
|
-
function getOrphanActionPaths(subtreePlans, scan) {
|
|
147
|
-
let expectedActionPaths = new Set(subtreePlans
|
|
148
|
-
.filter((subtree) => subtree.kind === 'action')
|
|
149
|
-
.flatMap((subtree) => subtree.entryCandidates));
|
|
150
|
-
let alternateActionPaths = new Set(subtreePlans
|
|
151
|
-
.filter((subtree) => subtree.kind === 'controller')
|
|
152
|
-
.flatMap((subtree) => subtree.alternateCandidates));
|
|
153
|
-
return [...scan.actionEntryPaths]
|
|
154
|
-
.filter((filePath) => !expectedActionPaths.has(filePath))
|
|
155
|
-
.filter((filePath) => !alternateActionPaths.has(filePath))
|
|
156
|
-
.sort();
|
|
157
|
-
}
|
|
158
147
|
function getOrphanControllerPaths(subtreePlans, scan) {
|
|
159
|
-
let expectedControllerPaths = new Set(subtreePlans
|
|
160
|
-
.filter((subtree) => subtree.kind === 'controller')
|
|
161
|
-
.flatMap((subtree) => subtree.entryCandidates));
|
|
162
|
-
let alternateControllerPaths = new Set(subtreePlans
|
|
163
|
-
.filter((subtree) => subtree.kind === 'action')
|
|
164
|
-
.flatMap((subtree) => subtree.alternateCandidates));
|
|
148
|
+
let expectedControllerPaths = new Set(subtreePlans.flatMap((subtree) => subtree.entryCandidates));
|
|
165
149
|
return [...scan.controllerEntryPaths]
|
|
166
150
|
.filter((filePath) => !expectedControllerPaths.has(filePath))
|
|
167
|
-
.filter((filePath) => !alternateControllerPaths.has(filePath))
|
|
168
151
|
.sort();
|
|
169
152
|
}
|
|
170
|
-
function getOrphanRouteDirectoryPaths(
|
|
171
|
-
let
|
|
153
|
+
function getOrphanRouteDirectoryPaths(routeDirectories, scan) {
|
|
154
|
+
let expectedRouteDirectories = new Set(routeDirectories.map((routeDirectory) => routeDirectory.directoryPath));
|
|
172
155
|
let actualControllerDirectories = [...scan.controllerEntryPaths].map((controllerPath) => normalizeRelativePath(path.dirname(controllerPath)));
|
|
173
|
-
return [...scan.
|
|
174
|
-
.filter((directoryPath) => !
|
|
156
|
+
return [...scan.routeDirectoryPaths]
|
|
157
|
+
.filter((directoryPath) => !expectedRouteDirectories.has(directoryPath))
|
|
175
158
|
.filter((directoryPath) => !actualControllerDirectories.some((controllerPath) => isDirectoryWithinDirectory(controllerPath, directoryPath)))
|
|
176
159
|
.sort();
|
|
177
160
|
}
|
|
178
|
-
function hasOwnerPath(scan, kind, filePath) {
|
|
179
|
-
return kind === 'action'
|
|
180
|
-
? scan.actionEntryPaths.has(filePath)
|
|
181
|
-
: scan.controllerEntryPaths.has(filePath);
|
|
182
|
-
}
|
|
183
|
-
function invertOwnerKind(kind) {
|
|
184
|
-
return kind === 'action' ? 'controller' : 'action';
|
|
185
|
-
}
|
|
186
|
-
function isActionCandidate(fileName) {
|
|
187
|
-
return isActionFileName(fileName);
|
|
188
|
-
}
|
|
189
161
|
function isRouteLocalFileName(fileName) {
|
|
190
162
|
let baseName = getOwnerModuleBaseName(fileName);
|
|
191
163
|
return (baseName != null &&
|
|
@@ -193,21 +165,18 @@ function isRouteLocalFileName(fileName) {
|
|
|
193
165
|
!baseName.endsWith('.test') &&
|
|
194
166
|
!baseName.endsWith('.spec'));
|
|
195
167
|
}
|
|
196
|
-
function
|
|
197
|
-
return findOwnerPaths(scan, kind, candidatePaths)[0] ?? null;
|
|
198
|
-
}
|
|
199
|
-
function findOwnerPaths(scan, kind, candidatePaths) {
|
|
168
|
+
function findOwnerPaths(scan, candidatePaths) {
|
|
200
169
|
let existingPaths = [];
|
|
201
170
|
for (let candidatePath of candidatePaths) {
|
|
202
|
-
if (
|
|
171
|
+
if (scan.controllerEntryPaths.has(candidatePath)) {
|
|
203
172
|
existingPaths.push(candidatePath);
|
|
204
173
|
}
|
|
205
174
|
}
|
|
206
175
|
return existingPaths;
|
|
207
176
|
}
|
|
208
177
|
function isNestedControllerPath(filePath) {
|
|
209
|
-
return (filePath.startsWith(
|
|
210
|
-
filePath.slice(
|
|
178
|
+
return (filePath.startsWith(ACTIONS_PATH_PREFIX) &&
|
|
179
|
+
filePath.slice(ACTIONS_PATH_PREFIX.length).includes('/'));
|
|
211
180
|
}
|
|
212
181
|
function isWithinDirectory(filePath, directoryPath) {
|
|
213
182
|
return filePath.startsWith(`${directoryPath}/`);
|
|
@@ -215,10 +184,6 @@ function isWithinDirectory(filePath, directoryPath) {
|
|
|
215
184
|
function isDirectoryWithinDirectory(directoryPath, parentDirectoryPath) {
|
|
216
185
|
return (directoryPath === parentDirectoryPath || isWithinDirectory(directoryPath, parentDirectoryPath));
|
|
217
186
|
}
|
|
218
|
-
function getTopLevelSubtreePath(subtreePath) {
|
|
219
|
-
let subtreeSegments = subtreePath.slice('app/controllers/'.length).split('/');
|
|
220
|
-
return getRouteSubtreePath([subtreeSegments[0]]);
|
|
221
|
-
}
|
|
222
187
|
function normalizeRelativePath(filePath) {
|
|
223
188
|
return filePath.split(path.sep).join('/');
|
|
224
189
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ControllerOwnership } from '../controller-ownership.ts';
|
|
2
2
|
import type { DoctorFinding, DoctorFixPlan } from './types.ts';
|
|
3
3
|
export declare function getControllerFindings(ownership: ControllerOwnership, fixPlans: DoctorFixPlan[]): DoctorFinding[];
|
|
4
4
|
//# sourceMappingURL=controller-findings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller-findings.d.ts","sourceRoot":"","sources":["../../../src/lib/doctor/controller-findings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"controller-findings.d.ts","sourceRoot":"","sources":["../../../src/lib/doctor/controller-findings.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,4BAA4B,CAAA;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE9D,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,mBAAmB,EAC9B,QAAQ,EAAE,aAAa,EAAE,GACxB,aAAa,EAAE,CAkBjB"}
|
|
@@ -1,26 +1,20 @@
|
|
|
1
|
+
import { ROOT_ROUTE_NAME, } from "../controller-ownership.js";
|
|
1
2
|
export function getControllerFindings(ownership, fixPlans) {
|
|
2
3
|
return [
|
|
3
4
|
...getSubtreeFindings(ownership.subtrees, fixPlans),
|
|
4
|
-
...ownership.orphanActionPaths.map((actualPath) => ({
|
|
5
|
-
actualPath,
|
|
6
|
-
code: 'orphan-action',
|
|
7
|
-
message: `Standalone action ${actualPath} does not match any top-level route.`,
|
|
8
|
-
severity: 'warn',
|
|
9
|
-
suite: 'controllers',
|
|
10
|
-
})),
|
|
11
5
|
...ownership.orphanControllerPaths.map((actualPath) => ({
|
|
12
6
|
actualPath,
|
|
13
7
|
code: 'orphan-controller',
|
|
14
|
-
message: `
|
|
8
|
+
message: `Action controller ${actualPath} does not match any route map.`,
|
|
15
9
|
severity: 'warn',
|
|
16
|
-
suite: '
|
|
10
|
+
suite: 'actions',
|
|
17
11
|
})),
|
|
18
12
|
...ownership.orphanRouteDirectoryPaths.map((actualPath) => ({
|
|
19
13
|
actualPath,
|
|
20
14
|
code: 'orphan-route-directory',
|
|
21
|
-
message: `Directory ${actualPath} does not match any route
|
|
15
|
+
message: `Directory ${actualPath} does not match any route-map key path.`,
|
|
22
16
|
severity: 'warn',
|
|
23
|
-
suite: '
|
|
17
|
+
suite: 'actions',
|
|
24
18
|
})),
|
|
25
19
|
];
|
|
26
20
|
}
|
|
@@ -28,63 +22,20 @@ function getSubtreeFindings(subtrees, fixPlans) {
|
|
|
28
22
|
let findings = [];
|
|
29
23
|
let fixableFindingKeys = new Set(fixPlans.map((fixPlan) => `${fixPlan.code}:${fixPlan.routeName ?? ''}`));
|
|
30
24
|
for (let subtree of subtrees) {
|
|
31
|
-
let
|
|
32
|
-
let actualAlternatePath = subtree.actualAlternatePath ?? undefined;
|
|
25
|
+
let routeMapName = formatRouteMapName(subtree.routeName);
|
|
33
26
|
if (subtree.actualEntryPaths.length > 1) {
|
|
34
27
|
findings.push({
|
|
35
28
|
actualPath: subtree.actualEntryPaths[0],
|
|
36
29
|
code: 'duplicate-owner-file',
|
|
37
30
|
expectedPath: subtree.entryDisplayPath,
|
|
38
|
-
message:
|
|
39
|
-
routeName: subtree.routeName,
|
|
40
|
-
severity: 'warn',
|
|
41
|
-
suite: 'controllers',
|
|
42
|
-
});
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
if (subtree.actualAlternatePaths.length > 1) {
|
|
46
|
-
let alternateKind = subtree.kind === 'action' ? 'controller' : 'action';
|
|
47
|
-
findings.push({
|
|
48
|
-
actualPath: subtree.actualAlternatePaths[0],
|
|
49
|
-
code: 'duplicate-owner-file',
|
|
50
|
-
expectedPath: subtree.entryDisplayPath,
|
|
51
|
-
message: `Route "${subtree.routeName}" has multiple ${alternateKind} files: ${subtree.actualAlternatePaths.join(', ')}. Keep only one ${alternateKind} owner file.`,
|
|
52
|
-
routeName: subtree.routeName,
|
|
53
|
-
severity: 'warn',
|
|
54
|
-
suite: 'controllers',
|
|
55
|
-
});
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
if (actualEntryPath != null && actualAlternatePath != null) {
|
|
59
|
-
findings.push({
|
|
60
|
-
actualPath: actualAlternatePath,
|
|
61
|
-
code: 'ambiguous-owner',
|
|
62
|
-
expectedPath: actualEntryPath,
|
|
63
|
-
message: subtree.kind === 'action'
|
|
64
|
-
? `Route "${subtree.routeName}" has both action ${actualEntryPath} and controller ${actualAlternatePath}.`
|
|
65
|
-
: `Route "${subtree.routeName}" has both controller ${actualEntryPath} and standalone action ${actualAlternatePath}.`,
|
|
66
|
-
routeName: subtree.routeName,
|
|
67
|
-
severity: 'warn',
|
|
68
|
-
suite: 'controllers',
|
|
69
|
-
});
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if (actualAlternatePath != null) {
|
|
73
|
-
findings.push({
|
|
74
|
-
actualPath: actualAlternatePath,
|
|
75
|
-
code: 'wrong-owner-kind',
|
|
76
|
-
expectedPath: subtree.entryDisplayPath,
|
|
77
|
-
message: subtree.kind === 'action'
|
|
78
|
-
? `Route "${subtree.routeName}" expects action ${subtree.entryDisplayPath}, but found controller ${actualAlternatePath}.`
|
|
79
|
-
: `Route "${subtree.routeName}" expects controller ${subtree.entryDisplayPath}, but found standalone action ${actualAlternatePath}.`,
|
|
80
|
-
fixable: fixableFindingKeys.has(`wrong-owner-kind:${subtree.routeName}`),
|
|
31
|
+
message: `${routeMapName} has multiple action controller files: ${subtree.actualEntryPaths.join(', ')}. Keep only one controller owner file.`,
|
|
81
32
|
routeName: subtree.routeName,
|
|
82
33
|
severity: 'warn',
|
|
83
|
-
suite: '
|
|
34
|
+
suite: 'actions',
|
|
84
35
|
});
|
|
85
36
|
continue;
|
|
86
37
|
}
|
|
87
|
-
if (subtree.
|
|
38
|
+
if (subtree.actualEntryPath == null) {
|
|
88
39
|
let code = subtree.claimedFilePaths.length > 0
|
|
89
40
|
? 'incomplete-controller'
|
|
90
41
|
: 'missing-owner';
|
|
@@ -94,39 +45,16 @@ function getSubtreeFindings(subtrees, fixPlans) {
|
|
|
94
45
|
expectedPath: subtree.entryDisplayPath,
|
|
95
46
|
fixable: fixableFindingKeys.has(`${code}:${subtree.routeName}`),
|
|
96
47
|
message: subtree.claimedFilePaths.length > 0
|
|
97
|
-
?
|
|
98
|
-
:
|
|
99
|
-
routeName: subtree.routeName,
|
|
100
|
-
severity: 'warn',
|
|
101
|
-
suite: 'controllers',
|
|
102
|
-
});
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (subtree.kind === 'action' && subtree.claimedFilePaths.length > 0) {
|
|
106
|
-
findings.push({
|
|
107
|
-
actualPath: subtree.subtreePath,
|
|
108
|
-
code: 'promotion-drift',
|
|
109
|
-
expectedPath: actualEntryPath ?? subtree.entryDisplayPath,
|
|
110
|
-
message: actualEntryPath == null
|
|
111
|
-
? `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}.`
|
|
112
|
-
: `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}.`,
|
|
113
|
-
routeName: subtree.routeName,
|
|
114
|
-
severity: 'warn',
|
|
115
|
-
suite: 'controllers',
|
|
116
|
-
});
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (subtree.actualEntryPath == null) {
|
|
120
|
-
findings.push({
|
|
121
|
-
code: 'missing-owner',
|
|
122
|
-
expectedPath: subtree.entryDisplayPath,
|
|
123
|
-
fixable: fixableFindingKeys.has(`missing-owner:${subtree.routeName}`),
|
|
124
|
-
message: `Route "${subtree.routeName}" is missing action ${subtree.entryDisplayPath}.`,
|
|
48
|
+
? `${routeMapName} has files under ${subtree.subtreePath}, but is missing action controller ${subtree.entryDisplayPath}.`
|
|
49
|
+
: `${routeMapName} is missing action controller ${subtree.entryDisplayPath}.`,
|
|
125
50
|
routeName: subtree.routeName,
|
|
126
51
|
severity: 'warn',
|
|
127
|
-
suite: '
|
|
52
|
+
suite: 'actions',
|
|
128
53
|
});
|
|
129
54
|
}
|
|
130
55
|
}
|
|
131
56
|
return findings;
|
|
132
57
|
}
|
|
58
|
+
function formatRouteMapName(routeName) {
|
|
59
|
+
return routeName === ROOT_ROUTE_NAME ? 'Root route map' : `Route map "${routeName}"`;
|
|
60
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller-fix-plans.d.ts","sourceRoot":"","sources":["../../../src/lib/doctor/controller-fix-plans.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,mBAAmB,EAEnB,kBAAkB,EACnB,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"controller-fix-plans.d.ts","sourceRoot":"","sources":["../../../src/lib/doctor/controller-fix-plans.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,mBAAmB,EAEnB,kBAAkB,EACnB,MAAM,4BAA4B,CAAA;AAGnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAI/C,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,kBAAkB,EAAE,EAC1B,SAAS,EAAE,mBAAmB,GAC7B,OAAO,CAAC,aAAa,EAAE,CAAC,CAkD1B"}
|