@remix-run/cli 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +77 -3
- package/bootstrap/.agents/skills/remix/SKILL.md +501 -0
- package/bootstrap/.agents/skills/remix/references/animate-elements.md +195 -0
- package/bootstrap/.agents/skills/remix/references/assets-and-browser-modules.md +122 -0
- package/bootstrap/.agents/skills/remix/references/auth-and-sessions.md +420 -0
- package/bootstrap/.agents/skills/remix/references/component-model.md +282 -0
- package/bootstrap/.agents/skills/remix/references/create-mixins.md +158 -0
- package/bootstrap/.agents/skills/remix/references/data-and-validation.md +363 -0
- package/bootstrap/.agents/skills/remix/references/hydration-frames-navigation.md +297 -0
- package/bootstrap/.agents/skills/remix/references/middleware-and-server.md +243 -0
- package/bootstrap/.agents/skills/remix/references/mixins-styling-events.md +213 -0
- package/bootstrap/.agents/skills/remix/references/routing-and-controllers.md +324 -0
- package/bootstrap/.agents/skills/remix/references/testing-patterns.md +156 -0
- package/bootstrap/AGENTS.md +39 -0
- package/bootstrap/README.md +27 -0
- package/bootstrap/app/assets/entry.ts +19 -0
- package/bootstrap/app/assets.ts +18 -0
- package/bootstrap/app/controllers/auth.tsx +21 -0
- package/bootstrap/app/controllers/home.tsx +11 -0
- package/bootstrap/app/router.ts +16 -0
- package/bootstrap/app/routes.ts +7 -0
- package/bootstrap/app/ui/document.tsx +26 -0
- package/bootstrap/app/ui/layout.tsx +22 -0
- package/bootstrap/app/ui/prompt-button.tsx +162 -0
- package/bootstrap/app/ui/scaffold-home-page.tsx +526 -0
- package/bootstrap/app/utils/render.tsx +26 -0
- package/bootstrap/package.json +22 -0
- package/bootstrap/server.ts +37 -0
- package/bootstrap/tsconfig.json +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/bootstrap-project.d.ts +17 -0
- package/dist/lib/bootstrap-project.d.ts.map +1 -0
- package/dist/lib/bootstrap-project.js +137 -0
- package/dist/lib/cli-context.d.ts +11 -0
- package/dist/lib/cli-context.d.ts.map +1 -0
- package/dist/lib/cli-context.js +19 -0
- package/dist/lib/cli.d.ts +6 -0
- package/dist/lib/cli.d.ts.map +1 -0
- package/dist/lib/cli.js +84 -0
- package/dist/lib/commands/completion.d.ts +3 -0
- package/dist/lib/commands/completion.d.ts.map +1 -0
- package/dist/lib/commands/completion.js +51 -0
- package/dist/lib/commands/doctor.d.ts +4 -0
- package/dist/lib/commands/doctor.d.ts.map +1 -0
- package/dist/lib/commands/doctor.js +293 -0
- package/dist/lib/commands/help.d.ts +4 -0
- package/dist/lib/commands/help.d.ts.map +1 -0
- package/dist/lib/commands/help.js +100 -0
- package/dist/lib/commands/new.d.ts +4 -0
- package/dist/lib/commands/new.d.ts.map +1 -0
- package/dist/lib/commands/new.js +70 -0
- package/dist/lib/commands/routes.d.ts +4 -0
- package/dist/lib/commands/routes.d.ts.map +1 -0
- package/dist/lib/commands/routes.js +161 -0
- package/dist/lib/commands/test.d.ts +4 -0
- package/dist/lib/commands/test.d.ts.map +1 -0
- package/dist/lib/commands/test.js +23 -0
- package/dist/lib/commands/version.d.ts +4 -0
- package/dist/lib/commands/version.d.ts.map +1 -0
- package/dist/lib/commands/version.js +29 -0
- package/dist/lib/completion.d.ts +12 -0
- package/dist/lib/completion.d.ts.map +1 -0
- package/dist/lib/completion.js +310 -0
- package/dist/lib/contained-path.d.ts +2 -0
- package/dist/lib/contained-path.d.ts.map +1 -0
- package/dist/lib/contained-path.js +10 -0
- package/dist/lib/controller-files.d.ts +14 -0
- package/dist/lib/controller-files.d.ts.map +1 -0
- package/dist/lib/controller-files.js +53 -0
- package/dist/lib/controller-ownership.d.ts +41 -0
- package/dist/lib/controller-ownership.d.ts.map +1 -0
- package/dist/lib/controller-ownership.js +224 -0
- package/dist/lib/display-path.d.ts +2 -0
- package/dist/lib/display-path.d.ts.map +1 -0
- package/dist/lib/display-path.js +12 -0
- package/dist/lib/doctor/controller-findings.d.ts +4 -0
- package/dist/lib/doctor/controller-findings.d.ts.map +1 -0
- package/dist/lib/doctor/controller-findings.js +132 -0
- package/dist/lib/doctor/controller-fix-plans.d.ts +4 -0
- package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -0
- package/dist/lib/doctor/controller-fix-plans.js +130 -0
- package/dist/lib/doctor/controller-placeholders.d.ts +4 -0
- package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -0
- package/dist/lib/doctor/controller-placeholders.js +183 -0
- package/dist/lib/doctor/controllers.d.ts +8 -0
- package/dist/lib/doctor/controllers.d.ts.map +1 -0
- package/dist/lib/doctor/controllers.js +13 -0
- package/dist/lib/doctor/environment.d.ts +17 -0
- package/dist/lib/doctor/environment.d.ts.map +1 -0
- package/dist/lib/doctor/environment.js +226 -0
- package/dist/lib/doctor/fixes.d.ts +3 -0
- package/dist/lib/doctor/fixes.d.ts.map +1 -0
- package/dist/lib/doctor/fixes.js +65 -0
- package/dist/lib/doctor/project.d.ts +10 -0
- package/dist/lib/doctor/project.d.ts.map +1 -0
- package/dist/lib/doctor/project.js +371 -0
- package/dist/lib/doctor/types.d.ts +48 -0
- package/dist/lib/doctor/types.d.ts.map +1 -0
- package/dist/lib/doctor/types.js +15 -0
- package/dist/lib/errors.d.ts +167 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +307 -0
- package/dist/lib/help-text.d.ts +13 -0
- package/dist/lib/help-text.d.ts.map +1 -0
- package/dist/lib/help-text.js +43 -0
- package/dist/lib/load-route-map-worker.d.ts +2 -0
- package/dist/lib/load-route-map-worker.d.ts.map +1 -0
- package/dist/lib/load-route-map-worker.js +92 -0
- package/dist/lib/parse-args.d.ts +22 -0
- package/dist/lib/parse-args.d.ts.map +1 -0
- package/dist/lib/parse-args.js +35 -0
- package/dist/lib/remix-version.d.ts +2 -0
- package/dist/lib/remix-version.d.ts.map +1 -0
- package/dist/lib/remix-version.js +58 -0
- package/dist/lib/reporter.d.ts +56 -0
- package/dist/lib/reporter.d.ts.map +1 -0
- package/dist/lib/reporter.js +357 -0
- package/dist/lib/route-map.d.ts +38 -0
- package/dist/lib/route-map.d.ts.map +1 -0
- package/dist/lib/route-map.js +195 -0
- package/dist/lib/terminal.d.ts +17 -0
- package/dist/lib/terminal.d.ts.map +1 -0
- package/dist/lib/terminal.js +66 -0
- package/package.json +47 -5
- package/src/index.ts +1 -0
- package/src/lib/bootstrap-project.ts +222 -0
- package/src/lib/cli-context.ts +38 -0
- package/src/lib/cli.ts +116 -0
- package/src/lib/commands/completion.ts +74 -0
- package/src/lib/commands/doctor.ts +393 -0
- package/src/lib/commands/help.ts +123 -0
- package/src/lib/commands/new.ts +104 -0
- package/src/lib/commands/routes.ts +251 -0
- package/src/lib/commands/test.ts +31 -0
- package/src/lib/commands/version.ts +39 -0
- package/src/lib/completion.ts +418 -0
- package/src/lib/contained-path.ts +13 -0
- package/src/lib/controller-files.ts +79 -0
- package/src/lib/controller-ownership.ts +397 -0
- package/src/lib/display-path.ts +16 -0
- package/src/lib/doctor/controller-findings.ts +157 -0
- package/src/lib/doctor/controller-fix-plans.ts +184 -0
- package/src/lib/doctor/controller-placeholders.ts +242 -0
- package/src/lib/doctor/controllers.ts +23 -0
- package/src/lib/doctor/environment.ts +289 -0
- package/src/lib/doctor/fixes.ts +84 -0
- package/src/lib/doctor/project.ts +435 -0
- package/src/lib/doctor/types.ts +95 -0
- package/src/lib/errors.ts +367 -0
- package/src/lib/help-text.ts +88 -0
- package/src/lib/load-route-map-worker.ts +110 -0
- package/src/lib/parse-args.ts +82 -0
- package/src/lib/remix-version.ts +80 -0
- package/src/lib/reporter.ts +535 -0
- package/src/lib/route-map.ts +303 -0
- package/src/lib/terminal.ts +86 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
const COMPLETION_SHELLS = ['bash', 'zsh'];
|
|
2
|
+
const HELP_COMMANDS = ['completion', 'doctor', 'help', 'new', 'routes', 'test', 'version'];
|
|
3
|
+
const ROOT_COMMANDS = ['completion', 'doctor', 'help', 'new', 'routes', 'test', 'version'];
|
|
4
|
+
export function isCompletionShell(value) {
|
|
5
|
+
return COMPLETION_SHELLS.includes(value);
|
|
6
|
+
}
|
|
7
|
+
export function getCompletionResult(words, currentIndex) {
|
|
8
|
+
let resolvedIndex = Math.max(0, currentIndex);
|
|
9
|
+
let currentWord = words[resolvedIndex] ?? '';
|
|
10
|
+
let tokens = getTokensBeforeCursor(words, resolvedIndex);
|
|
11
|
+
return completeTopLevel(tokens, currentWord);
|
|
12
|
+
}
|
|
13
|
+
export function renderCompletionResult(result) {
|
|
14
|
+
let lines = [`mode:${result.mode}`];
|
|
15
|
+
if (result.mode === 'values') {
|
|
16
|
+
lines.push(...(result.values ?? []));
|
|
17
|
+
}
|
|
18
|
+
return `${lines.join('\n')}\n`;
|
|
19
|
+
}
|
|
20
|
+
export function getCompletionScript() {
|
|
21
|
+
return `###-begin-remix-completion-###
|
|
22
|
+
#
|
|
23
|
+
# Remix command completion script
|
|
24
|
+
#
|
|
25
|
+
# Installation:
|
|
26
|
+
# remix completion bash >> ~/.bashrc
|
|
27
|
+
# remix completion zsh >> ~/.zshrc
|
|
28
|
+
#
|
|
29
|
+
|
|
30
|
+
if type complete &>/dev/null; then
|
|
31
|
+
_remix_completion() {
|
|
32
|
+
local words cword current output mode si
|
|
33
|
+
local lines
|
|
34
|
+
|
|
35
|
+
if type _get_comp_words_by_ref &>/dev/null; then
|
|
36
|
+
_get_comp_words_by_ref -w words -i cword
|
|
37
|
+
else
|
|
38
|
+
cword="$COMP_CWORD"
|
|
39
|
+
words=("\${COMP_WORDS[@]}")
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
current="\${words[cword]}"
|
|
43
|
+
output=$(remix completion -- "$cword" "\${words[@]}" 2>/dev/null) || return 0
|
|
44
|
+
si="$IFS"
|
|
45
|
+
IFS=$'\\n' lines=($output)
|
|
46
|
+
IFS="$si"
|
|
47
|
+
mode="\${lines[0]}"
|
|
48
|
+
|
|
49
|
+
if [[ "$mode" == "mode:values" ]]; then
|
|
50
|
+
COMPREPLY=("\${lines[@]:1}")
|
|
51
|
+
return 0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
if [[ "$mode" == "mode:files" ]]; then
|
|
55
|
+
COMPREPLY=($(compgen -f -- "$current"))
|
|
56
|
+
return 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
COMPREPLY=()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
complete -o default -F _remix_completion remix
|
|
63
|
+
elif type compdef &>/dev/null; then
|
|
64
|
+
_remix_completion() {
|
|
65
|
+
local output mode
|
|
66
|
+
local -a lines values
|
|
67
|
+
|
|
68
|
+
output=$(remix completion -- "$((CURRENT - 1))" "\${words[@]}" 2>/dev/null) || return 0
|
|
69
|
+
lines=("\${(@f)output}")
|
|
70
|
+
mode="\${lines[1]}"
|
|
71
|
+
|
|
72
|
+
if [[ "$mode" == "mode:values" ]]; then
|
|
73
|
+
values=("\${(@)lines[2,-1]}")
|
|
74
|
+
if (( \${#values[@]} > 0 )); then
|
|
75
|
+
compadd -- "\${values[@]}"
|
|
76
|
+
fi
|
|
77
|
+
return 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
if [[ "$mode" == "mode:files" ]]; then
|
|
81
|
+
if autoload -U +X _files 2>/dev/null; then
|
|
82
|
+
_files
|
|
83
|
+
else
|
|
84
|
+
compadd -f
|
|
85
|
+
fi
|
|
86
|
+
return 0
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
return 0
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
compdef _remix_completion remix
|
|
93
|
+
fi
|
|
94
|
+
###-end-remix-completion-###
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
function completeTopLevel(tokens, currentWord) {
|
|
98
|
+
let usedFlags = new Set();
|
|
99
|
+
let index = 0;
|
|
100
|
+
while (index < tokens.length) {
|
|
101
|
+
let token = tokens[index];
|
|
102
|
+
if (token === '--no-color') {
|
|
103
|
+
usedFlags.add('--no-color');
|
|
104
|
+
index++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (token === '-h' || token === '--help') {
|
|
108
|
+
usedFlags.add('-h');
|
|
109
|
+
return completeValues([], currentWord);
|
|
110
|
+
}
|
|
111
|
+
if (token === '-v' || token === '--version') {
|
|
112
|
+
usedFlags.add('-v');
|
|
113
|
+
return completeValues([], currentWord);
|
|
114
|
+
}
|
|
115
|
+
return completeCommand(token, tokens.slice(index + 1), currentWord, usedFlags);
|
|
116
|
+
}
|
|
117
|
+
return completeValues(getTopLevelSuggestions(currentWord, usedFlags), currentWord);
|
|
118
|
+
}
|
|
119
|
+
function completeCommand(command, tokens, currentWord, usedGlobalFlags) {
|
|
120
|
+
if (command === 'help') {
|
|
121
|
+
return completeHelp(tokens, currentWord, usedGlobalFlags);
|
|
122
|
+
}
|
|
123
|
+
if (command === 'new') {
|
|
124
|
+
return completeNew(tokens, currentWord, usedGlobalFlags);
|
|
125
|
+
}
|
|
126
|
+
if (command === 'doctor') {
|
|
127
|
+
return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, [
|
|
128
|
+
'--fix',
|
|
129
|
+
'--json',
|
|
130
|
+
'--strict',
|
|
131
|
+
]);
|
|
132
|
+
}
|
|
133
|
+
if (command === 'routes') {
|
|
134
|
+
return completeRoutes(tokens, currentWord, usedGlobalFlags);
|
|
135
|
+
}
|
|
136
|
+
if (command === 'version') {
|
|
137
|
+
return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, []);
|
|
138
|
+
}
|
|
139
|
+
if (command === 'test') {
|
|
140
|
+
return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, [
|
|
141
|
+
'--coverage',
|
|
142
|
+
'--watch',
|
|
143
|
+
'--help',
|
|
144
|
+
]);
|
|
145
|
+
}
|
|
146
|
+
if (command === 'completion') {
|
|
147
|
+
return completeCompletionCommand(tokens, currentWord, usedGlobalFlags);
|
|
148
|
+
}
|
|
149
|
+
return completeValues([], currentWord);
|
|
150
|
+
}
|
|
151
|
+
function completeHelp(tokens, currentWord, usedGlobalFlags) {
|
|
152
|
+
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags);
|
|
153
|
+
if (filteredTokens == null) {
|
|
154
|
+
return completeValues([], currentWord);
|
|
155
|
+
}
|
|
156
|
+
if (filteredTokens.length === 0) {
|
|
157
|
+
return completeValues(withHelpFlags([...HELP_COMMANDS], usedGlobalFlags), currentWord);
|
|
158
|
+
}
|
|
159
|
+
let [, ...rest] = filteredTokens;
|
|
160
|
+
if (rest.length === 0) {
|
|
161
|
+
return completeValues([], currentWord);
|
|
162
|
+
}
|
|
163
|
+
return completeValues([], currentWord);
|
|
164
|
+
}
|
|
165
|
+
function completeNew(tokens, currentWord, usedGlobalFlags) {
|
|
166
|
+
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags);
|
|
167
|
+
if (filteredTokens == null) {
|
|
168
|
+
return completeValues([], currentWord);
|
|
169
|
+
}
|
|
170
|
+
let hasAppName = false;
|
|
171
|
+
let hasForce = false;
|
|
172
|
+
let hasTargetDir = false;
|
|
173
|
+
let expectsAppName = false;
|
|
174
|
+
for (let token of filteredTokens) {
|
|
175
|
+
if (expectsAppName) {
|
|
176
|
+
expectsAppName = false;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (token === '--app-name') {
|
|
180
|
+
hasAppName = true;
|
|
181
|
+
expectsAppName = true;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (token === '--force') {
|
|
185
|
+
hasForce = true;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (token.startsWith('-')) {
|
|
189
|
+
return completeValues([], currentWord);
|
|
190
|
+
}
|
|
191
|
+
if (!hasTargetDir) {
|
|
192
|
+
hasTargetDir = true;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
return completeValues([], currentWord);
|
|
196
|
+
}
|
|
197
|
+
if (expectsAppName) {
|
|
198
|
+
return { mode: 'none' };
|
|
199
|
+
}
|
|
200
|
+
let flags = withHelpFlags([...(!hasAppName ? ['--app-name'] : []), ...(!hasForce ? ['--force'] : [])], usedGlobalFlags);
|
|
201
|
+
if (!hasTargetDir && !currentWord.startsWith('-')) {
|
|
202
|
+
return { mode: 'files' };
|
|
203
|
+
}
|
|
204
|
+
return completeValues(flags, currentWord);
|
|
205
|
+
}
|
|
206
|
+
function completeRoutes(tokens, currentWord, usedGlobalFlags) {
|
|
207
|
+
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags);
|
|
208
|
+
if (filteredTokens == null) {
|
|
209
|
+
return completeValues([], currentWord);
|
|
210
|
+
}
|
|
211
|
+
let hasJson = false;
|
|
212
|
+
let hasNoHeaders = false;
|
|
213
|
+
let hasTable = false;
|
|
214
|
+
let hasVerbose = false;
|
|
215
|
+
for (let token of filteredTokens) {
|
|
216
|
+
if (token === '--json') {
|
|
217
|
+
hasJson = true;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (token === '--table') {
|
|
221
|
+
hasTable = true;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (token === '--no-headers') {
|
|
225
|
+
hasNoHeaders = true;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (token === '--verbose') {
|
|
229
|
+
hasVerbose = true;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
return completeValues([], currentWord);
|
|
233
|
+
}
|
|
234
|
+
let flags = withHelpFlags([
|
|
235
|
+
...(!hasJson && !hasTable && !hasVerbose ? ['--json'] : []),
|
|
236
|
+
...(!hasTable && !hasJson ? ['--table'] : []),
|
|
237
|
+
...(!hasNoHeaders && hasTable ? ['--no-headers'] : []),
|
|
238
|
+
...(!hasVerbose && !hasJson ? ['--verbose'] : []),
|
|
239
|
+
], usedGlobalFlags);
|
|
240
|
+
return completeValues(flags, currentWord);
|
|
241
|
+
}
|
|
242
|
+
function completeCompletionCommand(tokens, currentWord, usedGlobalFlags) {
|
|
243
|
+
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags);
|
|
244
|
+
if (filteredTokens == null) {
|
|
245
|
+
return completeValues([], currentWord);
|
|
246
|
+
}
|
|
247
|
+
if (filteredTokens.length === 0) {
|
|
248
|
+
return completeValues(withHelpFlags([...COMPLETION_SHELLS], usedGlobalFlags), currentWord);
|
|
249
|
+
}
|
|
250
|
+
return completeValues([], currentWord);
|
|
251
|
+
}
|
|
252
|
+
function completeSimpleFlags(tokens, currentWord, usedGlobalFlags, commandFlags) {
|
|
253
|
+
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags);
|
|
254
|
+
if (filteredTokens == null) {
|
|
255
|
+
return completeValues([], currentWord);
|
|
256
|
+
}
|
|
257
|
+
let usedFlags = new Set();
|
|
258
|
+
for (let token of filteredTokens) {
|
|
259
|
+
if (!commandFlags.includes(token)) {
|
|
260
|
+
return completeValues([], currentWord);
|
|
261
|
+
}
|
|
262
|
+
usedFlags.add(token);
|
|
263
|
+
}
|
|
264
|
+
let availableFlags = commandFlags.filter((flag) => !usedFlags.has(flag));
|
|
265
|
+
return completeValues(withHelpFlags(availableFlags, usedGlobalFlags), currentWord);
|
|
266
|
+
}
|
|
267
|
+
function getTopLevelSuggestions(currentWord, usedFlags) {
|
|
268
|
+
let flags = [
|
|
269
|
+
...(!usedFlags.has('-h') ? ['-h', '--help'] : []),
|
|
270
|
+
...(!usedFlags.has('--no-color') ? ['--no-color'] : []),
|
|
271
|
+
...(!usedFlags.has('-v') ? ['-v', '--version'] : []),
|
|
272
|
+
];
|
|
273
|
+
if (currentWord.startsWith('-')) {
|
|
274
|
+
return flags;
|
|
275
|
+
}
|
|
276
|
+
return [...ROOT_COMMANDS, ...flags];
|
|
277
|
+
}
|
|
278
|
+
function withHelpFlags(values, usedGlobalFlags) {
|
|
279
|
+
return [
|
|
280
|
+
...values,
|
|
281
|
+
...(!usedGlobalFlags.has('-h') ? ['-h', '--help'] : []),
|
|
282
|
+
...(!usedGlobalFlags.has('--no-color') ? ['--no-color'] : []),
|
|
283
|
+
];
|
|
284
|
+
}
|
|
285
|
+
function filterGlobalCommandTokens(tokens, usedGlobalFlags) {
|
|
286
|
+
let filtered = [];
|
|
287
|
+
for (let token of tokens) {
|
|
288
|
+
if (token === '--no-color') {
|
|
289
|
+
usedGlobalFlags.add('--no-color');
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (token === '-h' || token === '--help') {
|
|
293
|
+
usedGlobalFlags.add('-h');
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
filtered.push(token);
|
|
297
|
+
}
|
|
298
|
+
return filtered;
|
|
299
|
+
}
|
|
300
|
+
function completeValues(values, currentWord) {
|
|
301
|
+
return {
|
|
302
|
+
mode: 'values',
|
|
303
|
+
values: values.filter((value) => value.startsWith(currentWord)),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function getTokensBeforeCursor(words, currentIndex) {
|
|
307
|
+
let startIndex = words[0] === 'remix' ? 1 : 0;
|
|
308
|
+
let endIndex = Math.max(startIndex, Math.min(currentIndex, words.length));
|
|
309
|
+
return words.slice(startIndex, endIndex);
|
|
310
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contained-path.d.ts","sourceRoot":"","sources":["../../src/lib/contained-path.ts"],"names":[],"mappings":"AAEA,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAUlF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
export function resolveContainedPath(rootDir, relativePath) {
|
|
3
|
+
let resolvedRootDir = path.resolve(rootDir);
|
|
4
|
+
let resolvedPath = path.resolve(resolvedRootDir, relativePath);
|
|
5
|
+
let pathFromRoot = path.relative(resolvedRootDir, resolvedPath);
|
|
6
|
+
if (pathFromRoot === '' || (!pathFromRoot.startsWith('..') && !path.isAbsolute(pathFromRoot))) {
|
|
7
|
+
return resolvedPath;
|
|
8
|
+
}
|
|
9
|
+
throw new Error(`Resolved path escapes the allowed root: ${relativePath}`);
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare const OWNER_FILE_EXTENSIONS: readonly [".ts", ".tsx", ".js", ".jsx"];
|
|
2
|
+
export type OwnerFileExtension = (typeof OWNER_FILE_EXTENSIONS)[number];
|
|
3
|
+
export declare function getActionOwnerCandidates(segments: string[]): string[];
|
|
4
|
+
export declare function getControllerOwnerCandidates(segments: string[]): string[];
|
|
5
|
+
export declare function getRouteSubtreePath(segments: string[]): string;
|
|
6
|
+
export declare function getPreferredOwnerDisplayPath(candidates: string[]): string;
|
|
7
|
+
export declare function getOwnerCandidateForExtension(candidates: string[], extension: OwnerFileExtension): string | null;
|
|
8
|
+
export declare function getOwnerFileExtension(filePath: string): OwnerFileExtension | null;
|
|
9
|
+
export declare function toDiskSegment(segment: string): string;
|
|
10
|
+
export declare function isControllerEntryFileName(fileName: string): boolean;
|
|
11
|
+
export declare function getOwnerModuleBaseName(fileName: string): string | null;
|
|
12
|
+
export declare function isActionFileName(fileName: string): boolean;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=controller-files.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
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
|
+
}
|
|
6
|
+
export function getControllerOwnerCandidates(segments) {
|
|
7
|
+
return OWNER_FILE_EXTENSIONS.map((extension) => normalizeRelativePath(path.join('app', 'controllers', ...segments, `controller${extension}`)));
|
|
8
|
+
}
|
|
9
|
+
export function getRouteSubtreePath(segments) {
|
|
10
|
+
return normalizeRelativePath(path.join('app', 'controllers', ...segments));
|
|
11
|
+
}
|
|
12
|
+
export function getPreferredOwnerDisplayPath(candidates) {
|
|
13
|
+
let tsxCandidate = candidates.find((candidate) => candidate.endsWith('.tsx'));
|
|
14
|
+
return tsxCandidate ?? candidates[0] ?? '';
|
|
15
|
+
}
|
|
16
|
+
export function getOwnerCandidateForExtension(candidates, extension) {
|
|
17
|
+
return candidates.find((candidate) => candidate.endsWith(extension)) ?? null;
|
|
18
|
+
}
|
|
19
|
+
export function getOwnerFileExtension(filePath) {
|
|
20
|
+
return OWNER_FILE_EXTENSIONS.find((extension) => filePath.endsWith(extension)) ?? null;
|
|
21
|
+
}
|
|
22
|
+
export function toDiskSegment(segment) {
|
|
23
|
+
let diskSegment = segment
|
|
24
|
+
.replace(/[\\/]+/g, '-')
|
|
25
|
+
.replace(/\.+/g, '-')
|
|
26
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
27
|
+
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
30
|
+
.replace(/-+/g, '-')
|
|
31
|
+
.replace(/^-+|-+$/g, '');
|
|
32
|
+
return diskSegment.length === 0 ? 'route' : diskSegment;
|
|
33
|
+
}
|
|
34
|
+
export function isControllerEntryFileName(fileName) {
|
|
35
|
+
return OWNER_FILE_EXTENSIONS.some((extension) => fileName === `controller${extension}`);
|
|
36
|
+
}
|
|
37
|
+
export function getOwnerModuleBaseName(fileName) {
|
|
38
|
+
let extension = OWNER_FILE_EXTENSIONS.find((ownerExtension) => fileName.endsWith(ownerExtension));
|
|
39
|
+
if (extension == null) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return fileName.slice(0, -extension.length);
|
|
43
|
+
}
|
|
44
|
+
export function isActionFileName(fileName) {
|
|
45
|
+
let baseName = getOwnerModuleBaseName(fileName);
|
|
46
|
+
return (baseName != null &&
|
|
47
|
+
baseName !== 'controller' &&
|
|
48
|
+
!baseName.endsWith('.test') &&
|
|
49
|
+
!baseName.endsWith('.spec'));
|
|
50
|
+
}
|
|
51
|
+
function normalizeRelativePath(filePath) {
|
|
52
|
+
return filePath.split(path.sep).join('/');
|
|
53
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { RouteOwnerKind, RouteTreeNodeKind } from './route-map.ts';
|
|
2
|
+
export interface OwnershipRouteNode {
|
|
3
|
+
children: OwnershipRouteNode[];
|
|
4
|
+
key: string;
|
|
5
|
+
kind: RouteTreeNodeKind;
|
|
6
|
+
method?: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ControllerDirectoryScan {
|
|
10
|
+
actionEntryPaths: Set<string>;
|
|
11
|
+
controllerEntryPaths: Set<string>;
|
|
12
|
+
rootDirectoryPaths: Set<string>;
|
|
13
|
+
routeLocalFilePaths: Set<string>;
|
|
14
|
+
}
|
|
15
|
+
export interface OwnedSubtreePlan {
|
|
16
|
+
alternateCandidates: string[];
|
|
17
|
+
alternateDisplayPath: string;
|
|
18
|
+
entryCandidates: string[];
|
|
19
|
+
entryDisplayPath: string;
|
|
20
|
+
kind: RouteOwnerKind;
|
|
21
|
+
routeName: string;
|
|
22
|
+
subtreePath: string;
|
|
23
|
+
}
|
|
24
|
+
export interface OwnedSubtree extends OwnedSubtreePlan {
|
|
25
|
+
actualAlternatePath: string | null;
|
|
26
|
+
actualAlternatePaths: string[];
|
|
27
|
+
actualEntryPath: string | null;
|
|
28
|
+
actualEntryPaths: string[];
|
|
29
|
+
claimedFilePaths: string[];
|
|
30
|
+
claimedRouteLocalFilePaths: string[];
|
|
31
|
+
}
|
|
32
|
+
export interface ControllerOwnership {
|
|
33
|
+
orphanActionPaths: string[];
|
|
34
|
+
orphanControllerPaths: string[];
|
|
35
|
+
orphanRouteDirectoryPaths: string[];
|
|
36
|
+
scan: ControllerDirectoryScan;
|
|
37
|
+
subtrees: OwnedSubtree[];
|
|
38
|
+
}
|
|
39
|
+
export declare function inspectControllerOwnership(appRoot: string, tree: OwnershipRouteNode[]): Promise<ControllerOwnership>;
|
|
40
|
+
export declare function buildOwnedSubtrees(tree: OwnershipRouteNode[], parentSegments?: string[], subtrees?: OwnedSubtreePlan[]): OwnedSubtreePlan[];
|
|
41
|
+
//# sourceMappingURL=controller-ownership.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { getActionOwnerCandidates, getControllerOwnerCandidates, getOwnerModuleBaseName, getPreferredOwnerDisplayPath, getRouteSubtreePath, isActionFileName, isControllerEntryFileName, toDiskSegment, } from "./controller-files.js";
|
|
4
|
+
export async function inspectControllerOwnership(appRoot, tree) {
|
|
5
|
+
let subtreePlans = buildOwnedSubtrees(tree);
|
|
6
|
+
let scan = await scanControllersDirectory(appRoot);
|
|
7
|
+
let subtrees = applyScanToSubtrees(subtreePlans, scan);
|
|
8
|
+
return {
|
|
9
|
+
orphanActionPaths: getOrphanActionPaths(subtreePlans, scan),
|
|
10
|
+
orphanControllerPaths: getOrphanControllerPaths(subtreePlans, scan),
|
|
11
|
+
orphanRouteDirectoryPaths: getOrphanRouteDirectoryPaths(subtreePlans, scan),
|
|
12
|
+
scan,
|
|
13
|
+
subtrees,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function buildOwnedSubtrees(tree, parentSegments = [], subtrees = []) {
|
|
17
|
+
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);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (parentSegments.length > 0) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
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',
|
|
45
|
+
routeName: node.name,
|
|
46
|
+
subtreePath: getRouteSubtreePath(segments),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return subtrees;
|
|
50
|
+
}
|
|
51
|
+
async function scanControllersDirectory(appRoot) {
|
|
52
|
+
let controllersDir = path.join(appRoot, 'app', 'controllers');
|
|
53
|
+
let controllerEntryPaths = new Set();
|
|
54
|
+
let actionEntryPaths = new Set();
|
|
55
|
+
let rootDirectoryPaths = new Set();
|
|
56
|
+
let routeLocalFilePaths = new Set();
|
|
57
|
+
async function walk(currentDir, isRoot) {
|
|
58
|
+
let entries;
|
|
59
|
+
try {
|
|
60
|
+
entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
let nodeError = error;
|
|
64
|
+
if (nodeError.code === 'ENOENT' && isRoot) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
for (let entry of entries) {
|
|
70
|
+
let entryPath = path.join(currentDir, entry.name);
|
|
71
|
+
let relativePath = normalizeRelativePath(path.relative(appRoot, entryPath));
|
|
72
|
+
if (entry.isDirectory()) {
|
|
73
|
+
if (isRoot) {
|
|
74
|
+
rootDirectoryPaths.add(relativePath);
|
|
75
|
+
}
|
|
76
|
+
await walk(entryPath, false);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!entry.isFile()) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (isControllerEntryFileName(entry.name)) {
|
|
83
|
+
controllerEntryPaths.add(relativePath);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (isRoot && isActionCandidate(entry.name)) {
|
|
87
|
+
actionEntryPaths.add(relativePath);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!isRoot && isRouteLocalFileName(entry.name)) {
|
|
91
|
+
routeLocalFilePaths.add(relativePath);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
await walk(controllersDir, true);
|
|
96
|
+
return {
|
|
97
|
+
actionEntryPaths,
|
|
98
|
+
controllerEntryPaths,
|
|
99
|
+
rootDirectoryPaths,
|
|
100
|
+
routeLocalFilePaths,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function applyScanToSubtrees(subtreePlans, scan) {
|
|
104
|
+
let claimedRouteLocalPaths = claimFilesToDeepestSubtree([...scan.routeLocalFilePaths], subtreePlans);
|
|
105
|
+
let claimedContentPaths = claimFilesToDeepestSubtree(getNestedContentPaths(scan), subtreePlans);
|
|
106
|
+
return subtreePlans.map((subtree) => {
|
|
107
|
+
let actualAlternatePaths = findOwnerPaths(scan, invertOwnerKind(subtree.kind), subtree.alternateCandidates);
|
|
108
|
+
let actualEntryPaths = findOwnerPaths(scan, subtree.kind, subtree.entryCandidates);
|
|
109
|
+
return {
|
|
110
|
+
...subtree,
|
|
111
|
+
actualAlternatePath: actualAlternatePaths[0] ?? null,
|
|
112
|
+
actualAlternatePaths,
|
|
113
|
+
actualEntryPath: actualEntryPaths[0] ?? null,
|
|
114
|
+
actualEntryPaths,
|
|
115
|
+
claimedFilePaths: claimedContentPaths.get(subtree.routeName) ?? [],
|
|
116
|
+
claimedRouteLocalFilePaths: claimedRouteLocalPaths.get(subtree.routeName) ?? [],
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function getNestedContentPaths(scan) {
|
|
121
|
+
let nestedControllerPaths = [...scan.controllerEntryPaths].filter((filePath) => isNestedControllerPath(filePath));
|
|
122
|
+
return [...new Set([...nestedControllerPaths, ...scan.routeLocalFilePaths])].sort();
|
|
123
|
+
}
|
|
124
|
+
function claimFilesToDeepestSubtree(filePaths, subtreePlans) {
|
|
125
|
+
let subtreesByDepth = [...subtreePlans].sort((left, right) => {
|
|
126
|
+
if (right.subtreePath.length !== left.subtreePath.length) {
|
|
127
|
+
return right.subtreePath.length - left.subtreePath.length;
|
|
128
|
+
}
|
|
129
|
+
return left.routeName.localeCompare(right.routeName);
|
|
130
|
+
});
|
|
131
|
+
let claims = new Map();
|
|
132
|
+
for (let filePath of filePaths.sort()) {
|
|
133
|
+
let matchingSubtree = subtreesByDepth.find((subtree) => isWithinDirectory(filePath, subtree.subtreePath));
|
|
134
|
+
if (matchingSubtree == null) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
let claimedPaths = claims.get(matchingSubtree.routeName);
|
|
138
|
+
if (claimedPaths == null) {
|
|
139
|
+
claims.set(matchingSubtree.routeName, [filePath]);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
claimedPaths.push(filePath);
|
|
143
|
+
}
|
|
144
|
+
return claims;
|
|
145
|
+
}
|
|
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
|
+
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));
|
|
165
|
+
return [...scan.controllerEntryPaths]
|
|
166
|
+
.filter((filePath) => !expectedControllerPaths.has(filePath))
|
|
167
|
+
.filter((filePath) => !alternateControllerPaths.has(filePath))
|
|
168
|
+
.sort();
|
|
169
|
+
}
|
|
170
|
+
function getOrphanRouteDirectoryPaths(subtreePlans, scan) {
|
|
171
|
+
let expectedRootDirectories = new Set(subtreePlans.map((subtree) => getTopLevelSubtreePath(subtree.subtreePath)));
|
|
172
|
+
let actualControllerDirectories = [...scan.controllerEntryPaths].map((controllerPath) => normalizeRelativePath(path.dirname(controllerPath)));
|
|
173
|
+
return [...scan.rootDirectoryPaths]
|
|
174
|
+
.filter((directoryPath) => !expectedRootDirectories.has(directoryPath))
|
|
175
|
+
.filter((directoryPath) => !actualControllerDirectories.some((controllerPath) => isDirectoryWithinDirectory(controllerPath, directoryPath)))
|
|
176
|
+
.sort();
|
|
177
|
+
}
|
|
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
|
+
function isRouteLocalFileName(fileName) {
|
|
190
|
+
let baseName = getOwnerModuleBaseName(fileName);
|
|
191
|
+
return (baseName != null &&
|
|
192
|
+
baseName !== 'controller' &&
|
|
193
|
+
!baseName.endsWith('.test') &&
|
|
194
|
+
!baseName.endsWith('.spec'));
|
|
195
|
+
}
|
|
196
|
+
function findOwnerPath(scan, kind, candidatePaths) {
|
|
197
|
+
return findOwnerPaths(scan, kind, candidatePaths)[0] ?? null;
|
|
198
|
+
}
|
|
199
|
+
function findOwnerPaths(scan, kind, candidatePaths) {
|
|
200
|
+
let existingPaths = [];
|
|
201
|
+
for (let candidatePath of candidatePaths) {
|
|
202
|
+
if (hasOwnerPath(scan, kind, candidatePath)) {
|
|
203
|
+
existingPaths.push(candidatePath);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return existingPaths;
|
|
207
|
+
}
|
|
208
|
+
function isNestedControllerPath(filePath) {
|
|
209
|
+
return (filePath.startsWith('app/controllers/') &&
|
|
210
|
+
filePath.slice('app/controllers/'.length).includes('/'));
|
|
211
|
+
}
|
|
212
|
+
function isWithinDirectory(filePath, directoryPath) {
|
|
213
|
+
return filePath.startsWith(`${directoryPath}/`);
|
|
214
|
+
}
|
|
215
|
+
function isDirectoryWithinDirectory(directoryPath, parentDirectoryPath) {
|
|
216
|
+
return (directoryPath === parentDirectoryPath || isWithinDirectory(directoryPath, parentDirectoryPath));
|
|
217
|
+
}
|
|
218
|
+
function getTopLevelSubtreePath(subtreePath) {
|
|
219
|
+
let subtreeSegments = subtreePath.slice('app/controllers/'.length).split('/');
|
|
220
|
+
return getRouteSubtreePath([subtreeSegments[0]]);
|
|
221
|
+
}
|
|
222
|
+
function normalizeRelativePath(filePath) {
|
|
223
|
+
return filePath.split(path.sep).join('/');
|
|
224
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display-path.d.ts","sourceRoot":"","sources":["../../src/lib/display-path.ts"],"names":[],"mappings":"AAGA,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,MAAsB,GAAG,MAAM,CAYpF"}
|