@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/src/lib/completion.ts
CHANGED
|
@@ -4,27 +4,8 @@ export interface CompletionResult {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
const COMPLETION_SHELLS = ['bash', 'zsh'] as const
|
|
7
|
-
const HELP_COMMANDS = [
|
|
8
|
-
|
|
9
|
-
'doctor',
|
|
10
|
-
'help',
|
|
11
|
-
'new',
|
|
12
|
-
'routes',
|
|
13
|
-
'skills',
|
|
14
|
-
'test',
|
|
15
|
-
'version',
|
|
16
|
-
] as const
|
|
17
|
-
const ROOT_COMMANDS = [
|
|
18
|
-
'completion',
|
|
19
|
-
'doctor',
|
|
20
|
-
'help',
|
|
21
|
-
'new',
|
|
22
|
-
'routes',
|
|
23
|
-
'skills',
|
|
24
|
-
'test',
|
|
25
|
-
'version',
|
|
26
|
-
] as const
|
|
27
|
-
const SKILLS_COMMANDS = ['install', 'list'] as const
|
|
7
|
+
const HELP_COMMANDS = ['completion', 'doctor', 'help', 'new', 'routes', 'test', 'version'] as const
|
|
8
|
+
const ROOT_COMMANDS = ['completion', 'doctor', 'help', 'new', 'routes', 'test', 'version'] as const
|
|
28
9
|
|
|
29
10
|
export type CompletionShell = (typeof COMPLETION_SHELLS)[number]
|
|
30
11
|
|
|
@@ -183,20 +164,12 @@ function completeCommand(
|
|
|
183
164
|
return completeRoutes(tokens, currentWord, usedGlobalFlags)
|
|
184
165
|
}
|
|
185
166
|
|
|
186
|
-
if (command === 'skills') {
|
|
187
|
-
return completeSkills(tokens, currentWord, usedGlobalFlags)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
167
|
if (command === 'version') {
|
|
191
168
|
return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, [])
|
|
192
169
|
}
|
|
193
170
|
|
|
194
171
|
if (command === 'test') {
|
|
195
|
-
return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, [
|
|
196
|
-
'--coverage',
|
|
197
|
-
'--watch',
|
|
198
|
-
'--help',
|
|
199
|
-
])
|
|
172
|
+
return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, ['--coverage', '--watch'])
|
|
200
173
|
}
|
|
201
174
|
|
|
202
175
|
if (command === 'completion') {
|
|
@@ -220,12 +193,8 @@ function completeHelp(
|
|
|
220
193
|
return completeValues(withHelpFlags([...HELP_COMMANDS], usedGlobalFlags), currentWord)
|
|
221
194
|
}
|
|
222
195
|
|
|
223
|
-
let [
|
|
196
|
+
let [, ...rest] = filteredTokens
|
|
224
197
|
if (rest.length === 0) {
|
|
225
|
-
if (topic === 'skills') {
|
|
226
|
-
return completeValues(withHelpFlags([...SKILLS_COMMANDS], usedGlobalFlags), currentWord)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
198
|
return completeValues([], currentWord)
|
|
230
199
|
}
|
|
231
200
|
|
|
@@ -344,122 +313,6 @@ function completeRoutes(
|
|
|
344
313
|
return completeValues(flags, currentWord)
|
|
345
314
|
}
|
|
346
315
|
|
|
347
|
-
function completeSkills(
|
|
348
|
-
tokens: string[],
|
|
349
|
-
currentWord: string,
|
|
350
|
-
usedGlobalFlags: Set<string>,
|
|
351
|
-
): CompletionResult {
|
|
352
|
-
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags)
|
|
353
|
-
if (filteredTokens == null) {
|
|
354
|
-
return completeValues([], currentWord)
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (filteredTokens.length === 0) {
|
|
358
|
-
return completeValues(withHelpFlags([...SKILLS_COMMANDS], usedGlobalFlags), currentWord)
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
let [subcommand, ...rest] = filteredTokens
|
|
362
|
-
if (subcommand === 'install') {
|
|
363
|
-
return completeSkillsInstall(rest, currentWord, usedGlobalFlags)
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (subcommand === 'list') {
|
|
367
|
-
return completeSkillsList(rest, currentWord, usedGlobalFlags)
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
return completeValues([], currentWord)
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function completeSkillsInstall(
|
|
374
|
-
tokens: string[],
|
|
375
|
-
currentWord: string,
|
|
376
|
-
usedGlobalFlags: Set<string>,
|
|
377
|
-
): CompletionResult {
|
|
378
|
-
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags)
|
|
379
|
-
if (filteredTokens == null) {
|
|
380
|
-
return completeValues([], currentWord)
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
let hasDir = false
|
|
384
|
-
let expectsDir = false
|
|
385
|
-
|
|
386
|
-
for (let token of filteredTokens) {
|
|
387
|
-
if (expectsDir) {
|
|
388
|
-
expectsDir = false
|
|
389
|
-
continue
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (token === '--dir') {
|
|
393
|
-
hasDir = true
|
|
394
|
-
expectsDir = true
|
|
395
|
-
continue
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
return completeValues([], currentWord)
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if (expectsDir) {
|
|
402
|
-
return { mode: 'files' }
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
return completeValues(withHelpFlags(!hasDir ? ['--dir'] : [], usedGlobalFlags), currentWord)
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function completeSkillsList(
|
|
409
|
-
tokens: string[],
|
|
410
|
-
currentWord: string,
|
|
411
|
-
usedGlobalFlags: Set<string>,
|
|
412
|
-
): CompletionResult {
|
|
413
|
-
return completeSkillsDirectoryCommand(tokens, currentWord, usedGlobalFlags)
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function completeSkillsDirectoryCommand(
|
|
417
|
-
tokens: string[],
|
|
418
|
-
currentWord: string,
|
|
419
|
-
usedGlobalFlags: Set<string>,
|
|
420
|
-
): CompletionResult {
|
|
421
|
-
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags)
|
|
422
|
-
if (filteredTokens == null) {
|
|
423
|
-
return completeValues([], currentWord)
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
let hasDir = false
|
|
427
|
-
let hasJson = false
|
|
428
|
-
let expectsDir = false
|
|
429
|
-
|
|
430
|
-
for (let token of filteredTokens) {
|
|
431
|
-
if (expectsDir) {
|
|
432
|
-
expectsDir = false
|
|
433
|
-
continue
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (token === '--dir') {
|
|
437
|
-
hasDir = true
|
|
438
|
-
expectsDir = true
|
|
439
|
-
continue
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (token === '--json') {
|
|
443
|
-
hasJson = true
|
|
444
|
-
continue
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
return completeValues([], currentWord)
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (expectsDir) {
|
|
451
|
-
return { mode: 'files' }
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
return completeValues(
|
|
455
|
-
withHelpFlags(
|
|
456
|
-
[...(!hasDir ? ['--dir'] : []), ...(!hasJson ? ['--json'] : [])],
|
|
457
|
-
usedGlobalFlags,
|
|
458
|
-
),
|
|
459
|
-
currentWord,
|
|
460
|
-
)
|
|
461
|
-
}
|
|
462
|
-
|
|
463
316
|
function completeCompletionCommand(
|
|
464
317
|
tokens: string[],
|
|
465
318
|
currentWord: string,
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
import * as path from 'node:path'
|
|
2
2
|
|
|
3
3
|
const OWNER_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'] as const
|
|
4
|
-
|
|
4
|
+
const ACTIONS_DIRECTORY = path.join('app', 'actions')
|
|
5
5
|
|
|
6
|
-
export
|
|
7
|
-
return OWNER_FILE_EXTENSIONS.map((extension) =>
|
|
8
|
-
normalizeRelativePath(path.join('app', 'controllers', `${path.join(...segments)}${extension}`)),
|
|
9
|
-
)
|
|
10
|
-
}
|
|
6
|
+
export type OwnerFileExtension = (typeof OWNER_FILE_EXTENSIONS)[number]
|
|
11
7
|
|
|
12
8
|
export function getControllerOwnerCandidates(segments: string[]): string[] {
|
|
13
9
|
return OWNER_FILE_EXTENSIONS.map((extension) =>
|
|
14
|
-
normalizeRelativePath(path.join(
|
|
10
|
+
normalizeRelativePath(path.join(ACTIONS_DIRECTORY, ...segments, `controller${extension}`)),
|
|
15
11
|
)
|
|
16
12
|
}
|
|
17
13
|
|
|
18
14
|
export function getRouteSubtreePath(segments: string[]): string {
|
|
19
|
-
return normalizeRelativePath(path.join(
|
|
15
|
+
return normalizeRelativePath(path.join(ACTIONS_DIRECTORY, ...segments))
|
|
20
16
|
}
|
|
21
17
|
|
|
22
18
|
export function getPreferredOwnerDisplayPath(candidates: string[]): string {
|
|
@@ -2,16 +2,18 @@ import * as fs from 'node:fs/promises'
|
|
|
2
2
|
import * as path from 'node:path'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
getActionOwnerCandidates,
|
|
6
5
|
getControllerOwnerCandidates,
|
|
7
6
|
getOwnerModuleBaseName,
|
|
8
7
|
getPreferredOwnerDisplayPath,
|
|
9
8
|
getRouteSubtreePath,
|
|
10
|
-
isActionFileName,
|
|
11
9
|
isControllerEntryFileName,
|
|
12
10
|
toDiskSegment,
|
|
13
11
|
} from './controller-files.ts'
|
|
14
|
-
import type {
|
|
12
|
+
import type { RouteTreeNodeKind } from './route-map.ts'
|
|
13
|
+
|
|
14
|
+
const ACTIONS_PATH_PREFIX = 'app/actions/'
|
|
15
|
+
|
|
16
|
+
export const ROOT_ROUTE_NAME = '<root>'
|
|
15
17
|
|
|
16
18
|
export interface OwnershipRouteNode {
|
|
17
19
|
children: OwnershipRouteNode[]
|
|
@@ -22,25 +24,24 @@ export interface OwnershipRouteNode {
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export interface ControllerDirectoryScan {
|
|
25
|
-
actionEntryPaths: Set<string>
|
|
26
27
|
controllerEntryPaths: Set<string>
|
|
27
|
-
|
|
28
|
+
routeDirectoryPaths: Set<string>
|
|
28
29
|
routeLocalFilePaths: Set<string>
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
export interface RouteDirectoryPlan {
|
|
33
|
+
directoryPath: string
|
|
34
|
+
routeName: string
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
export interface OwnedSubtreePlan {
|
|
32
|
-
alternateCandidates: string[]
|
|
33
|
-
alternateDisplayPath: string
|
|
34
38
|
entryCandidates: string[]
|
|
35
39
|
entryDisplayPath: string
|
|
36
|
-
kind: RouteOwnerKind
|
|
37
40
|
routeName: string
|
|
38
41
|
subtreePath: string
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
export interface OwnedSubtree extends OwnedSubtreePlan {
|
|
42
|
-
actualAlternatePath: string | null
|
|
43
|
-
actualAlternatePaths: string[]
|
|
44
45
|
actualEntryPath: string | null
|
|
45
46
|
actualEntryPaths: string[]
|
|
46
47
|
claimedFilePaths: string[]
|
|
@@ -48,9 +49,9 @@ export interface OwnedSubtree extends OwnedSubtreePlan {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
export interface ControllerOwnership {
|
|
51
|
-
orphanActionPaths: string[]
|
|
52
52
|
orphanControllerPaths: string[]
|
|
53
53
|
orphanRouteDirectoryPaths: string[]
|
|
54
|
+
routeDirectories: RouteDirectoryPlan[]
|
|
54
55
|
scan: ControllerDirectoryScan
|
|
55
56
|
subtrees: OwnedSubtree[]
|
|
56
57
|
}
|
|
@@ -60,13 +61,14 @@ export async function inspectControllerOwnership(
|
|
|
60
61
|
tree: OwnershipRouteNode[],
|
|
61
62
|
): Promise<ControllerOwnership> {
|
|
62
63
|
let subtreePlans = buildOwnedSubtrees(tree)
|
|
64
|
+
let routeDirectories = buildRouteDirectories(tree)
|
|
63
65
|
let scan = await scanControllersDirectory(appRoot)
|
|
64
66
|
let subtrees = applyScanToSubtrees(subtreePlans, scan)
|
|
65
67
|
|
|
66
68
|
return {
|
|
67
|
-
orphanActionPaths: getOrphanActionPaths(subtreePlans, scan),
|
|
68
69
|
orphanControllerPaths: getOrphanControllerPaths(subtreePlans, scan),
|
|
69
|
-
orphanRouteDirectoryPaths: getOrphanRouteDirectoryPaths(
|
|
70
|
+
orphanRouteDirectoryPaths: getOrphanRouteDirectoryPaths(routeDirectories, scan),
|
|
71
|
+
routeDirectories,
|
|
70
72
|
scan,
|
|
71
73
|
subtrees,
|
|
72
74
|
}
|
|
@@ -77,52 +79,69 @@ export function buildOwnedSubtrees(
|
|
|
77
79
|
parentSegments: string[] = [],
|
|
78
80
|
subtrees: OwnedSubtreePlan[] = [],
|
|
79
81
|
): OwnedSubtreePlan[] {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
if (
|
|
83
|
+
parentSegments.length === 0 &&
|
|
84
|
+
subtrees.length === 0 &&
|
|
85
|
+
tree.some((node) => node.kind === 'route')
|
|
86
|
+
) {
|
|
87
|
+
addSubtreePlan(ROOT_ROUTE_NAME, [], subtrees)
|
|
88
|
+
}
|
|
82
89
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let entryCandidates = getControllerOwnerCandidates(segments)
|
|
86
|
-
|
|
87
|
-
subtrees.push({
|
|
88
|
-
alternateCandidates,
|
|
89
|
-
alternateDisplayPath: getPreferredOwnerDisplayPath(alternateCandidates),
|
|
90
|
-
entryCandidates,
|
|
91
|
-
entryDisplayPath: getPreferredOwnerDisplayPath(entryCandidates),
|
|
92
|
-
kind: 'controller',
|
|
93
|
-
routeName: node.name,
|
|
94
|
-
subtreePath: getRouteSubtreePath(segments),
|
|
95
|
-
})
|
|
96
|
-
buildOwnedSubtrees(node.children, segments, subtrees)
|
|
90
|
+
for (let node of tree) {
|
|
91
|
+
if (node.kind !== 'group') {
|
|
97
92
|
continue
|
|
98
93
|
}
|
|
99
94
|
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
let segments = [...parentSegments, toDiskSegment(node.key)]
|
|
96
|
+
if (hasDirectRouteLeaf(node.children)) {
|
|
97
|
+
addSubtreePlan(node.name, segments, subtrees)
|
|
102
98
|
}
|
|
99
|
+
buildOwnedSubtrees(node.children, segments, subtrees)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return subtrees
|
|
103
|
+
}
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
export function buildRouteDirectories(
|
|
106
|
+
tree: OwnershipRouteNode[],
|
|
107
|
+
parentSegments: string[] = [],
|
|
108
|
+
directories: RouteDirectoryPlan[] = [],
|
|
109
|
+
): RouteDirectoryPlan[] {
|
|
110
|
+
for (let node of tree) {
|
|
111
|
+
if (node.kind !== 'group') {
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
106
114
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
entryCandidates,
|
|
111
|
-
entryDisplayPath: getPreferredOwnerDisplayPath(entryCandidates),
|
|
112
|
-
kind: 'action',
|
|
115
|
+
let segments = [...parentSegments, toDiskSegment(node.key)]
|
|
116
|
+
directories.push({
|
|
117
|
+
directoryPath: getRouteSubtreePath(segments),
|
|
113
118
|
routeName: node.name,
|
|
114
|
-
subtreePath: getRouteSubtreePath(segments),
|
|
115
119
|
})
|
|
120
|
+
buildRouteDirectories(node.children, segments, directories)
|
|
116
121
|
}
|
|
117
122
|
|
|
118
|
-
return
|
|
123
|
+
return directories
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function hasDirectRouteLeaf(tree: OwnershipRouteNode[]): boolean {
|
|
127
|
+
return tree.some((node) => node.kind === 'route')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function addSubtreePlan(routeName: string, segments: string[], subtrees: OwnedSubtreePlan[]): void {
|
|
131
|
+
let entryCandidates = getControllerOwnerCandidates(segments)
|
|
132
|
+
|
|
133
|
+
subtrees.push({
|
|
134
|
+
entryCandidates,
|
|
135
|
+
entryDisplayPath: getPreferredOwnerDisplayPath(entryCandidates),
|
|
136
|
+
routeName,
|
|
137
|
+
subtreePath: getRouteSubtreePath(segments),
|
|
138
|
+
})
|
|
119
139
|
}
|
|
120
140
|
|
|
121
141
|
async function scanControllersDirectory(appRoot: string): Promise<ControllerDirectoryScan> {
|
|
122
|
-
let
|
|
142
|
+
let actionsDir = path.join(appRoot, 'app', 'actions')
|
|
123
143
|
let controllerEntryPaths = new Set<string>()
|
|
124
|
-
let
|
|
125
|
-
let rootDirectoryPaths = new Set<string>()
|
|
144
|
+
let routeDirectoryPaths = new Set<string>()
|
|
126
145
|
let routeLocalFilePaths = new Set<string>()
|
|
127
146
|
|
|
128
147
|
async function walk(currentDir: string, isRoot: boolean): Promise<void> {
|
|
@@ -143,10 +162,7 @@ async function scanControllersDirectory(appRoot: string): Promise<ControllerDire
|
|
|
143
162
|
let relativePath = normalizeRelativePath(path.relative(appRoot, entryPath))
|
|
144
163
|
|
|
145
164
|
if (entry.isDirectory()) {
|
|
146
|
-
|
|
147
|
-
rootDirectoryPaths.add(relativePath)
|
|
148
|
-
}
|
|
149
|
-
|
|
165
|
+
routeDirectoryPaths.add(relativePath)
|
|
150
166
|
await walk(entryPath, false)
|
|
151
167
|
continue
|
|
152
168
|
}
|
|
@@ -160,23 +176,17 @@ async function scanControllersDirectory(appRoot: string): Promise<ControllerDire
|
|
|
160
176
|
continue
|
|
161
177
|
}
|
|
162
178
|
|
|
163
|
-
if (
|
|
164
|
-
actionEntryPaths.add(relativePath)
|
|
165
|
-
continue
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (!isRoot && isRouteLocalFileName(entry.name)) {
|
|
179
|
+
if (isRouteLocalFileName(entry.name)) {
|
|
169
180
|
routeLocalFilePaths.add(relativePath)
|
|
170
181
|
}
|
|
171
182
|
}
|
|
172
183
|
}
|
|
173
184
|
|
|
174
|
-
await walk(
|
|
185
|
+
await walk(actionsDir, true)
|
|
175
186
|
|
|
176
187
|
return {
|
|
177
|
-
actionEntryPaths,
|
|
178
188
|
controllerEntryPaths,
|
|
179
|
-
|
|
189
|
+
routeDirectoryPaths,
|
|
180
190
|
routeLocalFilePaths,
|
|
181
191
|
}
|
|
182
192
|
}
|
|
@@ -192,17 +202,10 @@ function applyScanToSubtrees(
|
|
|
192
202
|
let claimedContentPaths = claimFilesToDeepestSubtree(getNestedContentPaths(scan), subtreePlans)
|
|
193
203
|
|
|
194
204
|
return subtreePlans.map((subtree) => {
|
|
195
|
-
let
|
|
196
|
-
scan,
|
|
197
|
-
invertOwnerKind(subtree.kind),
|
|
198
|
-
subtree.alternateCandidates,
|
|
199
|
-
)
|
|
200
|
-
let actualEntryPaths = findOwnerPaths(scan, subtree.kind, subtree.entryCandidates)
|
|
205
|
+
let actualEntryPaths = findOwnerPaths(scan, subtree.entryCandidates)
|
|
201
206
|
|
|
202
207
|
return {
|
|
203
208
|
...subtree,
|
|
204
|
-
actualAlternatePath: actualAlternatePaths[0] ?? null,
|
|
205
|
-
actualAlternatePaths,
|
|
206
209
|
actualEntryPath: actualEntryPaths[0] ?? null,
|
|
207
210
|
actualEntryPaths,
|
|
208
211
|
claimedFilePaths: claimedContentPaths.get(subtree.routeName) ?? [],
|
|
@@ -253,61 +256,30 @@ function claimFilesToDeepestSubtree(
|
|
|
253
256
|
return claims
|
|
254
257
|
}
|
|
255
258
|
|
|
256
|
-
function getOrphanActionPaths(
|
|
257
|
-
subtreePlans: OwnedSubtreePlan[],
|
|
258
|
-
scan: ControllerDirectoryScan,
|
|
259
|
-
): string[] {
|
|
260
|
-
let expectedActionPaths = new Set(
|
|
261
|
-
subtreePlans
|
|
262
|
-
.filter((subtree) => subtree.kind === 'action')
|
|
263
|
-
.flatMap((subtree) => subtree.entryCandidates),
|
|
264
|
-
)
|
|
265
|
-
let alternateActionPaths = new Set(
|
|
266
|
-
subtreePlans
|
|
267
|
-
.filter((subtree) => subtree.kind === 'controller')
|
|
268
|
-
.flatMap((subtree) => subtree.alternateCandidates),
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
return [...scan.actionEntryPaths]
|
|
272
|
-
.filter((filePath) => !expectedActionPaths.has(filePath))
|
|
273
|
-
.filter((filePath) => !alternateActionPaths.has(filePath))
|
|
274
|
-
.sort()
|
|
275
|
-
}
|
|
276
|
-
|
|
277
259
|
function getOrphanControllerPaths(
|
|
278
260
|
subtreePlans: OwnedSubtreePlan[],
|
|
279
261
|
scan: ControllerDirectoryScan,
|
|
280
262
|
): string[] {
|
|
281
|
-
let expectedControllerPaths = new Set(
|
|
282
|
-
subtreePlans
|
|
283
|
-
.filter((subtree) => subtree.kind === 'controller')
|
|
284
|
-
.flatMap((subtree) => subtree.entryCandidates),
|
|
285
|
-
)
|
|
286
|
-
let alternateControllerPaths = new Set(
|
|
287
|
-
subtreePlans
|
|
288
|
-
.filter((subtree) => subtree.kind === 'action')
|
|
289
|
-
.flatMap((subtree) => subtree.alternateCandidates),
|
|
290
|
-
)
|
|
263
|
+
let expectedControllerPaths = new Set(subtreePlans.flatMap((subtree) => subtree.entryCandidates))
|
|
291
264
|
|
|
292
265
|
return [...scan.controllerEntryPaths]
|
|
293
266
|
.filter((filePath) => !expectedControllerPaths.has(filePath))
|
|
294
|
-
.filter((filePath) => !alternateControllerPaths.has(filePath))
|
|
295
267
|
.sort()
|
|
296
268
|
}
|
|
297
269
|
|
|
298
270
|
function getOrphanRouteDirectoryPaths(
|
|
299
|
-
|
|
271
|
+
routeDirectories: RouteDirectoryPlan[],
|
|
300
272
|
scan: ControllerDirectoryScan,
|
|
301
273
|
): string[] {
|
|
302
|
-
let
|
|
303
|
-
|
|
274
|
+
let expectedRouteDirectories = new Set(
|
|
275
|
+
routeDirectories.map((routeDirectory) => routeDirectory.directoryPath),
|
|
304
276
|
)
|
|
305
277
|
let actualControllerDirectories = [...scan.controllerEntryPaths].map((controllerPath) =>
|
|
306
278
|
normalizeRelativePath(path.dirname(controllerPath)),
|
|
307
279
|
)
|
|
308
280
|
|
|
309
|
-
return [...scan.
|
|
310
|
-
.filter((directoryPath) => !
|
|
281
|
+
return [...scan.routeDirectoryPaths]
|
|
282
|
+
.filter((directoryPath) => !expectedRouteDirectories.has(directoryPath))
|
|
311
283
|
.filter(
|
|
312
284
|
(directoryPath) =>
|
|
313
285
|
!actualControllerDirectories.some((controllerPath) =>
|
|
@@ -317,24 +289,6 @@ function getOrphanRouteDirectoryPaths(
|
|
|
317
289
|
.sort()
|
|
318
290
|
}
|
|
319
291
|
|
|
320
|
-
function hasOwnerPath(
|
|
321
|
-
scan: ControllerDirectoryScan,
|
|
322
|
-
kind: RouteOwnerKind,
|
|
323
|
-
filePath: string,
|
|
324
|
-
): boolean {
|
|
325
|
-
return kind === 'action'
|
|
326
|
-
? scan.actionEntryPaths.has(filePath)
|
|
327
|
-
: scan.controllerEntryPaths.has(filePath)
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function invertOwnerKind(kind: RouteOwnerKind): RouteOwnerKind {
|
|
331
|
-
return kind === 'action' ? 'controller' : 'action'
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function isActionCandidate(fileName: string): boolean {
|
|
335
|
-
return isActionFileName(fileName)
|
|
336
|
-
}
|
|
337
|
-
|
|
338
292
|
function isRouteLocalFileName(fileName: string): boolean {
|
|
339
293
|
let baseName = getOwnerModuleBaseName(fileName)
|
|
340
294
|
|
|
@@ -346,23 +300,11 @@ function isRouteLocalFileName(fileName: string): boolean {
|
|
|
346
300
|
)
|
|
347
301
|
}
|
|
348
302
|
|
|
349
|
-
function
|
|
350
|
-
scan: ControllerDirectoryScan,
|
|
351
|
-
kind: RouteOwnerKind,
|
|
352
|
-
candidatePaths: string[],
|
|
353
|
-
): string | null {
|
|
354
|
-
return findOwnerPaths(scan, kind, candidatePaths)[0] ?? null
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function findOwnerPaths(
|
|
358
|
-
scan: ControllerDirectoryScan,
|
|
359
|
-
kind: RouteOwnerKind,
|
|
360
|
-
candidatePaths: string[],
|
|
361
|
-
): string[] {
|
|
303
|
+
function findOwnerPaths(scan: ControllerDirectoryScan, candidatePaths: string[]): string[] {
|
|
362
304
|
let existingPaths: string[] = []
|
|
363
305
|
|
|
364
306
|
for (let candidatePath of candidatePaths) {
|
|
365
|
-
if (
|
|
307
|
+
if (scan.controllerEntryPaths.has(candidatePath)) {
|
|
366
308
|
existingPaths.push(candidatePath)
|
|
367
309
|
}
|
|
368
310
|
}
|
|
@@ -372,8 +314,8 @@ function findOwnerPaths(
|
|
|
372
314
|
|
|
373
315
|
function isNestedControllerPath(filePath: string): boolean {
|
|
374
316
|
return (
|
|
375
|
-
filePath.startsWith(
|
|
376
|
-
filePath.slice(
|
|
317
|
+
filePath.startsWith(ACTIONS_PATH_PREFIX) &&
|
|
318
|
+
filePath.slice(ACTIONS_PATH_PREFIX.length).includes('/')
|
|
377
319
|
)
|
|
378
320
|
}
|
|
379
321
|
|
|
@@ -387,11 +329,6 @@ function isDirectoryWithinDirectory(directoryPath: string, parentDirectoryPath:
|
|
|
387
329
|
)
|
|
388
330
|
}
|
|
389
331
|
|
|
390
|
-
function getTopLevelSubtreePath(subtreePath: string): string {
|
|
391
|
-
let subtreeSegments = subtreePath.slice('app/controllers/'.length).split('/')
|
|
392
|
-
return getRouteSubtreePath([subtreeSegments[0]])
|
|
393
|
-
}
|
|
394
|
-
|
|
395
332
|
function normalizeRelativePath(filePath: string): string {
|
|
396
333
|
return filePath.split(path.sep).join('/')
|
|
397
334
|
}
|