@remix-run/cli 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +0 -3
  2. package/dist/lib/bootstrap-project.d.ts.map +1 -1
  3. package/dist/lib/bootstrap-project.js +32 -9
  4. package/dist/lib/cli.d.ts +29 -0
  5. package/dist/lib/cli.d.ts.map +1 -1
  6. package/dist/lib/cli.js +33 -14
  7. package/dist/lib/commands/completion.d.ts.map +1 -1
  8. package/dist/lib/commands/completion.js +5 -1
  9. package/dist/lib/commands/doctor.js +18 -18
  10. package/dist/lib/commands/help.d.ts.map +1 -1
  11. package/dist/lib/commands/help.js +9 -33
  12. package/dist/lib/commands/routes.js +3 -3
  13. package/dist/lib/commands/test.d.ts +1 -1
  14. package/dist/lib/commands/test.d.ts.map +1 -1
  15. package/dist/lib/commands/test.js +8 -4
  16. package/dist/lib/completion.d.ts.map +1 -1
  17. package/dist/lib/completion.js +4 -106
  18. package/dist/lib/controller-files.d.ts +0 -1
  19. package/dist/lib/controller-files.d.ts.map +1 -1
  20. package/dist/lib/controller-files.js +3 -5
  21. package/dist/lib/controller-ownership.d.ts +9 -9
  22. package/dist/lib/controller-ownership.d.ts.map +1 -1
  23. package/dist/lib/controller-ownership.js +56 -91
  24. package/dist/lib/doctor/controller-findings.d.ts +1 -1
  25. package/dist/lib/doctor/controller-findings.d.ts.map +1 -1
  26. package/dist/lib/doctor/controller-findings.js +15 -87
  27. package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -1
  28. package/dist/lib/doctor/controller-fix-plans.js +13 -24
  29. package/dist/lib/doctor/controller-placeholders.d.ts +2 -3
  30. package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -1
  31. package/dist/lib/doctor/controller-placeholders.js +18 -149
  32. package/dist/lib/doctor/controllers.js +1 -1
  33. package/dist/lib/doctor/project.js +60 -52
  34. package/dist/lib/doctor/types.d.ts +2 -2
  35. package/dist/lib/doctor/types.d.ts.map +1 -1
  36. package/dist/lib/errors.d.ts +0 -6
  37. package/dist/lib/errors.d.ts.map +1 -1
  38. package/dist/lib/errors.js +0 -11
  39. package/dist/lib/load-route-map-worker.js +17 -9
  40. package/dist/lib/route-map.d.ts +1 -1
  41. package/dist/lib/route-map.d.ts.map +1 -1
  42. package/dist/lib/route-map.js +29 -17
  43. package/package.json +4 -5
  44. package/src/lib/bootstrap-project.ts +39 -13
  45. package/src/lib/cli.ts +46 -15
  46. package/src/lib/commands/completion.ts +6 -1
  47. package/src/lib/commands/doctor.ts +18 -21
  48. package/src/lib/commands/help.ts +9 -43
  49. package/src/lib/commands/routes.ts +3 -3
  50. package/src/lib/commands/test.ts +10 -4
  51. package/src/lib/completion.ts +4 -151
  52. package/src/lib/controller-files.ts +4 -8
  53. package/src/lib/controller-ownership.ts +78 -141
  54. package/src/lib/doctor/controller-findings.ts +20 -97
  55. package/src/lib/doctor/controller-fix-plans.ts +13 -29
  56. package/src/lib/doctor/controller-placeholders.ts +17 -189
  57. package/src/lib/doctor/controllers.ts +1 -1
  58. package/src/lib/doctor/project.ts +60 -52
  59. package/src/lib/doctor/types.ts +1 -5
  60. package/src/lib/errors.ts +0 -12
  61. package/src/lib/load-route-map-worker.ts +19 -10
  62. package/src/lib/route-map.ts +61 -16
  63. package/template/.agents/skills/remix/SKILL.md +588 -0
  64. package/template/.agents/skills/remix/references/animate-elements.md +195 -0
  65. package/template/.agents/skills/remix/references/assets-and-browser-modules.md +130 -0
  66. package/template/.agents/skills/remix/references/auth-and-sessions.md +443 -0
  67. package/template/.agents/skills/remix/references/component-model.md +282 -0
  68. package/template/.agents/skills/remix/references/create-mixins.md +158 -0
  69. package/template/.agents/skills/remix/references/data-and-validation.md +379 -0
  70. package/template/.agents/skills/remix/references/hydration-frames-navigation.md +297 -0
  71. package/template/.agents/skills/remix/references/middleware-and-server.md +233 -0
  72. package/template/.agents/skills/remix/references/mixins-styling-events.md +213 -0
  73. package/template/.agents/skills/remix/references/routing-and-controllers.md +391 -0
  74. package/template/.agents/skills/remix/references/testing-patterns.md +172 -0
  75. package/{bootstrap → template}/AGENTS.md +13 -8
  76. package/template/README.md +29 -0
  77. package/template/app/actions/controller.tsx +18 -0
  78. package/template/app/assets/entry.ts +8 -0
  79. package/template/app/assets/prompt-button.tsx +163 -0
  80. package/template/app/assets.ts +20 -0
  81. package/template/app/middleware/render.tsx +43 -0
  82. package/template/app/router.ts +20 -0
  83. package/template/app/routes.ts +6 -0
  84. package/template/app/ui/document.tsx +33 -0
  85. package/template/app/ui/scaffold-home-page.tsx +527 -0
  86. package/template/gitignore +4 -0
  87. package/{bootstrap → template}/package.json +5 -6
  88. package/template/public/favicon.svg +11 -0
  89. package/{bootstrap → template}/server.ts +4 -5
  90. package/{bootstrap → template}/tsconfig.json +3 -3
  91. package/bootstrap/README.md +0 -27
  92. package/bootstrap/app/controllers/auth.tsx +0 -21
  93. package/bootstrap/app/controllers/home.tsx +0 -26
  94. package/bootstrap/app/router.ts +0 -10
  95. package/bootstrap/app/routes.ts +0 -6
  96. package/bootstrap/app/ui/document.tsx +0 -21
  97. package/bootstrap/app/ui/layout.tsx +0 -22
  98. package/bootstrap/app/utils/render.tsx +0 -7
  99. package/dist/lib/commands/skills.d.ts +0 -6
  100. package/dist/lib/commands/skills.d.ts.map +0 -1
  101. package/dist/lib/commands/skills.js +0 -222
  102. package/dist/lib/skills-cache.d.ts +0 -19
  103. package/dist/lib/skills-cache.d.ts.map +0 -1
  104. package/dist/lib/skills-cache.js +0 -89
  105. package/dist/lib/skills.d.ts +0 -30
  106. package/dist/lib/skills.d.ts.map +0 -1
  107. package/dist/lib/skills.js +0 -441
  108. package/src/lib/commands/skills.ts +0 -306
  109. package/src/lib/skills-cache.ts +0 -140
  110. package/src/lib/skills.ts +0 -706
@@ -1,25 +1,6 @@
1
1
  const COMPLETION_SHELLS = ['bash', 'zsh'];
2
- const HELP_COMMANDS = [
3
- 'completion',
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 [topic, ...rest] = filteredTokens;
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;AACrE,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAIrE;AAED,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
+ {"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
- export function getActionOwnerCandidates(segments) {
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('app', 'controllers', ...segments, `controller${extension}`)));
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('app', 'controllers', ...segments));
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 { RouteOwnerKind, RouteTreeNodeKind } from './route-map.ts';
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
- rootDirectoryPaths: Set<string>;
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":"AAaA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAEvE,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,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC7B,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACjC,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/B,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,mBAAmB,EAAE,MAAM,EAAE,CAAA;IAC7B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,gBAAgB,EAAE,MAAM,CAAA;IACxB,IAAI,EAAE,cAAc,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,YAAa,SAAQ,gBAAgB;IACpD,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,oBAAoB,EAAE,MAAM,EAAE,CAAA;IAC9B,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,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,yBAAyB,EAAE,MAAM,EAAE,CAAA;IACnC,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,CAY9B;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,kBAAkB,EAAE,EAC1B,cAAc,GAAE,MAAM,EAAO,EAC7B,QAAQ,GAAE,gBAAgB,EAAO,GAChC,gBAAgB,EAAE,CAwCpB"}
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 { getActionOwnerCandidates, getControllerOwnerCandidates, getOwnerModuleBaseName, getPreferredOwnerDisplayPath, getRouteSubtreePath, isActionFileName, isControllerEntryFileName, toDiskSegment, } from "./controller-files.js";
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(subtreePlans, scan),
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
- let segments = [...parentSegments, toDiskSegment(node.key)];
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
- if (parentSegments.length > 0) {
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 alternateCandidates = getControllerOwnerCandidates(segments);
38
- let entryCandidates = getActionOwnerCandidates(segments);
39
- subtrees.push({
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 subtrees;
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 controllersDir = path.join(appRoot, 'app', 'controllers');
64
+ let actionsDir = path.join(appRoot, 'app', 'actions');
53
65
  let controllerEntryPaths = new Set();
54
- let actionEntryPaths = new Set();
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
- if (isRoot) {
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 (isRoot && isActionCandidate(entry.name)) {
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(controllersDir, true);
100
+ await walk(actionsDir, true);
96
101
  return {
97
- actionEntryPaths,
98
102
  controllerEntryPaths,
99
- rootDirectoryPaths,
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 actualAlternatePaths = findOwnerPaths(scan, invertOwnerKind(subtree.kind), subtree.alternateCandidates);
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(subtreePlans, scan) {
171
- let expectedRootDirectories = new Set(subtreePlans.map((subtree) => getTopLevelSubtreePath(subtree.subtreePath)));
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.rootDirectoryPaths]
174
- .filter((directoryPath) => !expectedRootDirectories.has(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 findOwnerPath(scan, kind, candidatePaths) {
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 (hasOwnerPath(scan, kind, candidatePath)) {
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('app/controllers/') &&
210
- filePath.slice('app/controllers/'.length).includes('/'));
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 { ControllerOwnership } from '../controller-ownership.ts';
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,EAAE,mBAAmB,EAAgB,MAAM,4BAA4B,CAAA;AACnF,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,CAyBjB"}
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: `Controller ${actualPath} does not match any route group.`,
8
+ message: `Action controller ${actualPath} does not match any route map.`,
15
9
  severity: 'warn',
16
- suite: 'controllers',
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 subtree.`,
15
+ message: `Directory ${actualPath} does not match any route-map key path.`,
22
16
  severity: 'warn',
23
- suite: 'controllers',
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 actualEntryPath = subtree.actualEntryPath ?? undefined;
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: `Route "${subtree.routeName}" has multiple ${subtree.kind} files: ${subtree.actualEntryPaths.join(', ')}. Keep only one ${subtree.kind} owner file.`,
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: 'controllers',
34
+ suite: 'actions',
84
35
  });
85
36
  continue;
86
37
  }
87
- if (subtree.kind === 'controller' && subtree.actualEntryPath == null) {
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
- ? `Route "${subtree.routeName}" has files under ${subtree.subtreePath}, but is missing controller ${subtree.entryDisplayPath}.`
98
- : `Route "${subtree.routeName}" is missing controller ${subtree.entryDisplayPath}.`,
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: 'controllers',
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;AAEnC,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,CAqD1B"}
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"}