@remix-run/cli 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +80 -3
- package/bootstrap/AGENTS.md +35 -0
- package/bootstrap/README.md +27 -0
- package/bootstrap/app/controllers/auth.tsx +21 -0
- package/bootstrap/app/controllers/home.tsx +26 -0
- package/bootstrap/app/router.ts +10 -0
- package/bootstrap/app/routes.ts +6 -0
- package/bootstrap/app/ui/document.tsx +21 -0
- package/bootstrap/app/ui/layout.tsx +22 -0
- package/bootstrap/app/utils/render.tsx +7 -0
- package/bootstrap/package.json +22 -0
- package/bootstrap/server.ts +37 -0
- package/bootstrap/tsconfig.json +19 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/bootstrap-project.d.ts +17 -0
- package/dist/lib/bootstrap-project.d.ts.map +1 -0
- package/dist/lib/bootstrap-project.js +137 -0
- package/dist/lib/cli-context.d.ts +11 -0
- package/dist/lib/cli-context.d.ts.map +1 -0
- package/dist/lib/cli-context.js +19 -0
- package/dist/lib/cli.d.ts +6 -0
- package/dist/lib/cli.d.ts.map +1 -0
- package/dist/lib/cli.js +87 -0
- package/dist/lib/commands/completion.d.ts +3 -0
- package/dist/lib/commands/completion.d.ts.map +1 -0
- package/dist/lib/commands/completion.js +51 -0
- package/dist/lib/commands/doctor.d.ts +4 -0
- package/dist/lib/commands/doctor.d.ts.map +1 -0
- package/dist/lib/commands/doctor.js +293 -0
- package/dist/lib/commands/help.d.ts +4 -0
- package/dist/lib/commands/help.d.ts.map +1 -0
- package/dist/lib/commands/help.js +124 -0
- package/dist/lib/commands/new.d.ts +4 -0
- package/dist/lib/commands/new.d.ts.map +1 -0
- package/dist/lib/commands/new.js +70 -0
- package/dist/lib/commands/routes.d.ts +4 -0
- package/dist/lib/commands/routes.d.ts.map +1 -0
- package/dist/lib/commands/routes.js +161 -0
- package/dist/lib/commands/skills.d.ts +6 -0
- package/dist/lib/commands/skills.d.ts.map +1 -0
- package/dist/lib/commands/skills.js +222 -0
- package/dist/lib/commands/test.d.ts +4 -0
- package/dist/lib/commands/test.d.ts.map +1 -0
- package/dist/lib/commands/test.js +19 -0
- package/dist/lib/commands/version.d.ts +4 -0
- package/dist/lib/commands/version.d.ts.map +1 -0
- package/dist/lib/commands/version.js +29 -0
- package/dist/lib/completion.d.ts +12 -0
- package/dist/lib/completion.d.ts.map +1 -0
- package/dist/lib/completion.js +408 -0
- package/dist/lib/contained-path.d.ts +2 -0
- package/dist/lib/contained-path.d.ts.map +1 -0
- package/dist/lib/contained-path.js +10 -0
- package/dist/lib/controller-files.d.ts +14 -0
- package/dist/lib/controller-files.d.ts.map +1 -0
- package/dist/lib/controller-files.js +53 -0
- package/dist/lib/controller-ownership.d.ts +41 -0
- package/dist/lib/controller-ownership.d.ts.map +1 -0
- package/dist/lib/controller-ownership.js +224 -0
- package/dist/lib/display-path.d.ts +2 -0
- package/dist/lib/display-path.d.ts.map +1 -0
- package/dist/lib/display-path.js +12 -0
- package/dist/lib/doctor/controller-findings.d.ts +4 -0
- package/dist/lib/doctor/controller-findings.d.ts.map +1 -0
- package/dist/lib/doctor/controller-findings.js +132 -0
- package/dist/lib/doctor/controller-fix-plans.d.ts +4 -0
- package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -0
- package/dist/lib/doctor/controller-fix-plans.js +130 -0
- package/dist/lib/doctor/controller-placeholders.d.ts +4 -0
- package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -0
- package/dist/lib/doctor/controller-placeholders.js +183 -0
- package/dist/lib/doctor/controllers.d.ts +8 -0
- package/dist/lib/doctor/controllers.d.ts.map +1 -0
- package/dist/lib/doctor/controllers.js +13 -0
- package/dist/lib/doctor/environment.d.ts +17 -0
- package/dist/lib/doctor/environment.d.ts.map +1 -0
- package/dist/lib/doctor/environment.js +226 -0
- package/dist/lib/doctor/fixes.d.ts +3 -0
- package/dist/lib/doctor/fixes.d.ts.map +1 -0
- package/dist/lib/doctor/fixes.js +65 -0
- package/dist/lib/doctor/project.d.ts +10 -0
- package/dist/lib/doctor/project.d.ts.map +1 -0
- package/dist/lib/doctor/project.js +371 -0
- package/dist/lib/doctor/types.d.ts +48 -0
- package/dist/lib/doctor/types.d.ts.map +1 -0
- package/dist/lib/doctor/types.js +15 -0
- package/dist/lib/errors.d.ts +173 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +318 -0
- package/dist/lib/help-text.d.ts +13 -0
- package/dist/lib/help-text.d.ts.map +1 -0
- package/dist/lib/help-text.js +43 -0
- package/dist/lib/load-route-map-worker.d.ts +2 -0
- package/dist/lib/load-route-map-worker.d.ts.map +1 -0
- package/dist/lib/load-route-map-worker.js +92 -0
- package/dist/lib/parse-args.d.ts +22 -0
- package/dist/lib/parse-args.d.ts.map +1 -0
- package/dist/lib/parse-args.js +35 -0
- package/dist/lib/remix-version.d.ts +2 -0
- package/dist/lib/remix-version.d.ts.map +1 -0
- package/dist/lib/remix-version.js +58 -0
- package/dist/lib/reporter.d.ts +56 -0
- package/dist/lib/reporter.d.ts.map +1 -0
- package/dist/lib/reporter.js +357 -0
- package/dist/lib/route-map.d.ts +38 -0
- package/dist/lib/route-map.d.ts.map +1 -0
- package/dist/lib/route-map.js +195 -0
- package/dist/lib/skills-cache.d.ts +19 -0
- package/dist/lib/skills-cache.d.ts.map +1 -0
- package/dist/lib/skills-cache.js +89 -0
- package/dist/lib/skills.d.ts +30 -0
- package/dist/lib/skills.d.ts.map +1 -0
- package/dist/lib/skills.js +441 -0
- package/dist/lib/terminal.d.ts +17 -0
- package/dist/lib/terminal.d.ts.map +1 -0
- package/dist/lib/terminal.js +66 -0
- package/package.json +48 -5
- package/src/index.ts +1 -0
- package/src/lib/bootstrap-project.ts +222 -0
- package/src/lib/cli-context.ts +38 -0
- package/src/lib/cli.ts +120 -0
- package/src/lib/commands/completion.ts +74 -0
- package/src/lib/commands/doctor.ts +393 -0
- package/src/lib/commands/help.ts +157 -0
- package/src/lib/commands/new.ts +104 -0
- package/src/lib/commands/routes.ts +251 -0
- package/src/lib/commands/skills.ts +306 -0
- package/src/lib/commands/test.ts +25 -0
- package/src/lib/commands/version.ts +39 -0
- package/src/lib/completion.ts +561 -0
- package/src/lib/contained-path.ts +13 -0
- package/src/lib/controller-files.ts +79 -0
- package/src/lib/controller-ownership.ts +397 -0
- package/src/lib/display-path.ts +16 -0
- package/src/lib/doctor/controller-findings.ts +157 -0
- package/src/lib/doctor/controller-fix-plans.ts +184 -0
- package/src/lib/doctor/controller-placeholders.ts +242 -0
- package/src/lib/doctor/controllers.ts +23 -0
- package/src/lib/doctor/environment.ts +289 -0
- package/src/lib/doctor/fixes.ts +84 -0
- package/src/lib/doctor/project.ts +435 -0
- package/src/lib/doctor/types.ts +95 -0
- package/src/lib/errors.ts +379 -0
- package/src/lib/help-text.ts +88 -0
- package/src/lib/load-route-map-worker.ts +110 -0
- package/src/lib/parse-args.ts +82 -0
- package/src/lib/remix-version.ts +80 -0
- package/src/lib/reporter.ts +535 -0
- package/src/lib/route-map.ts +303 -0
- package/src/lib/skills-cache.ts +140 -0
- package/src/lib/skills.ts +706 -0
- package/src/lib/terminal.ts +86 -0
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
export interface CompletionResult {
|
|
2
|
+
mode: 'files' | 'none' | 'values'
|
|
3
|
+
values?: string[]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const COMPLETION_SHELLS = ['bash', 'zsh'] as const
|
|
7
|
+
const HELP_COMMANDS = [
|
|
8
|
+
'completion',
|
|
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
|
|
28
|
+
|
|
29
|
+
export type CompletionShell = (typeof COMPLETION_SHELLS)[number]
|
|
30
|
+
|
|
31
|
+
export function isCompletionShell(value: string): value is CompletionShell {
|
|
32
|
+
return COMPLETION_SHELLS.includes(value as CompletionShell)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getCompletionResult(words: string[], currentIndex: number): CompletionResult {
|
|
36
|
+
let resolvedIndex = Math.max(0, currentIndex)
|
|
37
|
+
let currentWord = words[resolvedIndex] ?? ''
|
|
38
|
+
let tokens = getTokensBeforeCursor(words, resolvedIndex)
|
|
39
|
+
|
|
40
|
+
return completeTopLevel(tokens, currentWord)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function renderCompletionResult(result: CompletionResult): string {
|
|
44
|
+
let lines = [`mode:${result.mode}`]
|
|
45
|
+
|
|
46
|
+
if (result.mode === 'values') {
|
|
47
|
+
lines.push(...(result.values ?? []))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return `${lines.join('\n')}\n`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function getCompletionScript(): string {
|
|
54
|
+
return `###-begin-remix-completion-###
|
|
55
|
+
#
|
|
56
|
+
# Remix command completion script
|
|
57
|
+
#
|
|
58
|
+
# Installation:
|
|
59
|
+
# remix completion bash >> ~/.bashrc
|
|
60
|
+
# remix completion zsh >> ~/.zshrc
|
|
61
|
+
#
|
|
62
|
+
|
|
63
|
+
if type complete &>/dev/null; then
|
|
64
|
+
_remix_completion() {
|
|
65
|
+
local words cword current output mode si
|
|
66
|
+
local lines
|
|
67
|
+
|
|
68
|
+
if type _get_comp_words_by_ref &>/dev/null; then
|
|
69
|
+
_get_comp_words_by_ref -w words -i cword
|
|
70
|
+
else
|
|
71
|
+
cword="$COMP_CWORD"
|
|
72
|
+
words=("\${COMP_WORDS[@]}")
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
current="\${words[cword]}"
|
|
76
|
+
output=$(remix completion -- "$cword" "\${words[@]}" 2>/dev/null) || return 0
|
|
77
|
+
si="$IFS"
|
|
78
|
+
IFS=$'\\n' lines=($output)
|
|
79
|
+
IFS="$si"
|
|
80
|
+
mode="\${lines[0]}"
|
|
81
|
+
|
|
82
|
+
if [[ "$mode" == "mode:values" ]]; then
|
|
83
|
+
COMPREPLY=("\${lines[@]:1}")
|
|
84
|
+
return 0
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
if [[ "$mode" == "mode:files" ]]; then
|
|
88
|
+
COMPREPLY=($(compgen -f -- "$current"))
|
|
89
|
+
return 0
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
COMPREPLY=()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
complete -o default -F _remix_completion remix
|
|
96
|
+
elif type compdef &>/dev/null; then
|
|
97
|
+
_remix_completion() {
|
|
98
|
+
local output mode
|
|
99
|
+
local -a lines values
|
|
100
|
+
|
|
101
|
+
output=$(remix completion -- "$((CURRENT - 1))" "\${words[@]}" 2>/dev/null) || return 0
|
|
102
|
+
lines=("\${(@f)output}")
|
|
103
|
+
mode="\${lines[1]}"
|
|
104
|
+
|
|
105
|
+
if [[ "$mode" == "mode:values" ]]; then
|
|
106
|
+
values=("\${(@)lines[2,-1]}")
|
|
107
|
+
if (( \${#values[@]} > 0 )); then
|
|
108
|
+
compadd -- "\${values[@]}"
|
|
109
|
+
fi
|
|
110
|
+
return 0
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
if [[ "$mode" == "mode:files" ]]; then
|
|
114
|
+
if autoload -U +X _files 2>/dev/null; then
|
|
115
|
+
_files
|
|
116
|
+
else
|
|
117
|
+
compadd -f
|
|
118
|
+
fi
|
|
119
|
+
return 0
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
return 0
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
compdef _remix_completion remix
|
|
126
|
+
fi
|
|
127
|
+
###-end-remix-completion-###
|
|
128
|
+
`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function completeTopLevel(tokens: string[], currentWord: string): CompletionResult {
|
|
132
|
+
let usedFlags = new Set<string>()
|
|
133
|
+
let index = 0
|
|
134
|
+
|
|
135
|
+
while (index < tokens.length) {
|
|
136
|
+
let token = tokens[index]
|
|
137
|
+
|
|
138
|
+
if (token === '--no-color') {
|
|
139
|
+
usedFlags.add('--no-color')
|
|
140
|
+
index++
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (token === '-h' || token === '--help') {
|
|
145
|
+
usedFlags.add('-h')
|
|
146
|
+
return completeValues([], currentWord)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (token === '-v' || token === '--version') {
|
|
150
|
+
usedFlags.add('-v')
|
|
151
|
+
return completeValues([], currentWord)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return completeCommand(token, tokens.slice(index + 1), currentWord, usedFlags)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return completeValues(getTopLevelSuggestions(currentWord, usedFlags), currentWord)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function completeCommand(
|
|
161
|
+
command: string,
|
|
162
|
+
tokens: string[],
|
|
163
|
+
currentWord: string,
|
|
164
|
+
usedGlobalFlags: Set<string>,
|
|
165
|
+
): CompletionResult {
|
|
166
|
+
if (command === 'help') {
|
|
167
|
+
return completeHelp(tokens, currentWord, usedGlobalFlags)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (command === 'new') {
|
|
171
|
+
return completeNew(tokens, currentWord, usedGlobalFlags)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (command === 'doctor') {
|
|
175
|
+
return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, [
|
|
176
|
+
'--fix',
|
|
177
|
+
'--json',
|
|
178
|
+
'--strict',
|
|
179
|
+
])
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (command === 'routes') {
|
|
183
|
+
return completeRoutes(tokens, currentWord, usedGlobalFlags)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (command === 'skills') {
|
|
187
|
+
return completeSkills(tokens, currentWord, usedGlobalFlags)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (command === 'version') {
|
|
191
|
+
return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, [])
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (command === 'test') {
|
|
195
|
+
return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, [
|
|
196
|
+
'--coverage',
|
|
197
|
+
'--watch',
|
|
198
|
+
'--help',
|
|
199
|
+
])
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (command === 'completion') {
|
|
203
|
+
return completeCompletionCommand(tokens, currentWord, usedGlobalFlags)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return completeValues([], currentWord)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function completeHelp(
|
|
210
|
+
tokens: string[],
|
|
211
|
+
currentWord: string,
|
|
212
|
+
usedGlobalFlags: Set<string>,
|
|
213
|
+
): CompletionResult {
|
|
214
|
+
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags)
|
|
215
|
+
if (filteredTokens == null) {
|
|
216
|
+
return completeValues([], currentWord)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (filteredTokens.length === 0) {
|
|
220
|
+
return completeValues(withHelpFlags([...HELP_COMMANDS], usedGlobalFlags), currentWord)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let [topic, ...rest] = filteredTokens
|
|
224
|
+
if (rest.length === 0) {
|
|
225
|
+
if (topic === 'skills') {
|
|
226
|
+
return completeValues(withHelpFlags([...SKILLS_COMMANDS], usedGlobalFlags), currentWord)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return completeValues([], currentWord)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return completeValues([], currentWord)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function completeNew(
|
|
236
|
+
tokens: string[],
|
|
237
|
+
currentWord: string,
|
|
238
|
+
usedGlobalFlags: Set<string>,
|
|
239
|
+
): CompletionResult {
|
|
240
|
+
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags)
|
|
241
|
+
if (filteredTokens == null) {
|
|
242
|
+
return completeValues([], currentWord)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let hasAppName = false
|
|
246
|
+
let hasForce = false
|
|
247
|
+
let hasTargetDir = false
|
|
248
|
+
let expectsAppName = false
|
|
249
|
+
|
|
250
|
+
for (let token of filteredTokens) {
|
|
251
|
+
if (expectsAppName) {
|
|
252
|
+
expectsAppName = false
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (token === '--app-name') {
|
|
257
|
+
hasAppName = true
|
|
258
|
+
expectsAppName = true
|
|
259
|
+
continue
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (token === '--force') {
|
|
263
|
+
hasForce = true
|
|
264
|
+
continue
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (token.startsWith('-')) {
|
|
268
|
+
return completeValues([], currentWord)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!hasTargetDir) {
|
|
272
|
+
hasTargetDir = true
|
|
273
|
+
continue
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return completeValues([], currentWord)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (expectsAppName) {
|
|
280
|
+
return { mode: 'none' }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let flags = withHelpFlags(
|
|
284
|
+
[...(!hasAppName ? ['--app-name'] : []), ...(!hasForce ? ['--force'] : [])],
|
|
285
|
+
usedGlobalFlags,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
if (!hasTargetDir && !currentWord.startsWith('-')) {
|
|
289
|
+
return { mode: 'files' }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return completeValues(flags, currentWord)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function completeRoutes(
|
|
296
|
+
tokens: string[],
|
|
297
|
+
currentWord: string,
|
|
298
|
+
usedGlobalFlags: Set<string>,
|
|
299
|
+
): CompletionResult {
|
|
300
|
+
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags)
|
|
301
|
+
if (filteredTokens == null) {
|
|
302
|
+
return completeValues([], currentWord)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
let hasJson = false
|
|
306
|
+
let hasNoHeaders = false
|
|
307
|
+
let hasTable = false
|
|
308
|
+
let hasVerbose = false
|
|
309
|
+
|
|
310
|
+
for (let token of filteredTokens) {
|
|
311
|
+
if (token === '--json') {
|
|
312
|
+
hasJson = true
|
|
313
|
+
continue
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (token === '--table') {
|
|
317
|
+
hasTable = true
|
|
318
|
+
continue
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (token === '--no-headers') {
|
|
322
|
+
hasNoHeaders = true
|
|
323
|
+
continue
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (token === '--verbose') {
|
|
327
|
+
hasVerbose = true
|
|
328
|
+
continue
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return completeValues([], currentWord)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let flags = withHelpFlags(
|
|
335
|
+
[
|
|
336
|
+
...(!hasJson && !hasTable && !hasVerbose ? ['--json'] : []),
|
|
337
|
+
...(!hasTable && !hasJson ? ['--table'] : []),
|
|
338
|
+
...(!hasNoHeaders && hasTable ? ['--no-headers'] : []),
|
|
339
|
+
...(!hasVerbose && !hasJson ? ['--verbose'] : []),
|
|
340
|
+
],
|
|
341
|
+
usedGlobalFlags,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
return completeValues(flags, currentWord)
|
|
345
|
+
}
|
|
346
|
+
|
|
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
|
+
function completeCompletionCommand(
|
|
464
|
+
tokens: string[],
|
|
465
|
+
currentWord: string,
|
|
466
|
+
usedGlobalFlags: Set<string>,
|
|
467
|
+
): CompletionResult {
|
|
468
|
+
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags)
|
|
469
|
+
if (filteredTokens == null) {
|
|
470
|
+
return completeValues([], currentWord)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (filteredTokens.length === 0) {
|
|
474
|
+
return completeValues(withHelpFlags([...COMPLETION_SHELLS], usedGlobalFlags), currentWord)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return completeValues([], currentWord)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function completeSimpleFlags(
|
|
481
|
+
tokens: string[],
|
|
482
|
+
currentWord: string,
|
|
483
|
+
usedGlobalFlags: Set<string>,
|
|
484
|
+
commandFlags: string[],
|
|
485
|
+
): CompletionResult {
|
|
486
|
+
let filteredTokens = filterGlobalCommandTokens(tokens, usedGlobalFlags)
|
|
487
|
+
if (filteredTokens == null) {
|
|
488
|
+
return completeValues([], currentWord)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
let usedFlags = new Set<string>()
|
|
492
|
+
for (let token of filteredTokens) {
|
|
493
|
+
if (!commandFlags.includes(token)) {
|
|
494
|
+
return completeValues([], currentWord)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
usedFlags.add(token)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
let availableFlags = commandFlags.filter((flag) => !usedFlags.has(flag))
|
|
501
|
+
return completeValues(withHelpFlags(availableFlags, usedGlobalFlags), currentWord)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function getTopLevelSuggestions(currentWord: string, usedFlags: Set<string>): string[] {
|
|
505
|
+
let flags = [
|
|
506
|
+
...(!usedFlags.has('-h') ? ['-h', '--help'] : []),
|
|
507
|
+
...(!usedFlags.has('--no-color') ? ['--no-color'] : []),
|
|
508
|
+
...(!usedFlags.has('-v') ? ['-v', '--version'] : []),
|
|
509
|
+
]
|
|
510
|
+
|
|
511
|
+
if (currentWord.startsWith('-')) {
|
|
512
|
+
return flags
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return [...ROOT_COMMANDS, ...flags]
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function withHelpFlags(values: string[], usedGlobalFlags: Set<string>): string[] {
|
|
519
|
+
return [
|
|
520
|
+
...values,
|
|
521
|
+
...(!usedGlobalFlags.has('-h') ? ['-h', '--help'] : []),
|
|
522
|
+
...(!usedGlobalFlags.has('--no-color') ? ['--no-color'] : []),
|
|
523
|
+
]
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function filterGlobalCommandTokens(
|
|
527
|
+
tokens: string[],
|
|
528
|
+
usedGlobalFlags: Set<string>,
|
|
529
|
+
): string[] | null {
|
|
530
|
+
let filtered: string[] = []
|
|
531
|
+
|
|
532
|
+
for (let token of tokens) {
|
|
533
|
+
if (token === '--no-color') {
|
|
534
|
+
usedGlobalFlags.add('--no-color')
|
|
535
|
+
continue
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (token === '-h' || token === '--help') {
|
|
539
|
+
usedGlobalFlags.add('-h')
|
|
540
|
+
return null
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
filtered.push(token)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return filtered
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function completeValues(values: string[], currentWord: string): CompletionResult {
|
|
550
|
+
return {
|
|
551
|
+
mode: 'values',
|
|
552
|
+
values: values.filter((value) => value.startsWith(currentWord)),
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function getTokensBeforeCursor(words: string[], currentIndex: number): string[] {
|
|
557
|
+
let startIndex = words[0] === 'remix' ? 1 : 0
|
|
558
|
+
let endIndex = Math.max(startIndex, Math.min(currentIndex, words.length))
|
|
559
|
+
|
|
560
|
+
return words.slice(startIndex, endIndex)
|
|
561
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as path from 'node:path'
|
|
2
|
+
|
|
3
|
+
export function resolveContainedPath(rootDir: string, relativePath: string): string {
|
|
4
|
+
let resolvedRootDir = path.resolve(rootDir)
|
|
5
|
+
let resolvedPath = path.resolve(resolvedRootDir, relativePath)
|
|
6
|
+
let pathFromRoot = path.relative(resolvedRootDir, resolvedPath)
|
|
7
|
+
|
|
8
|
+
if (pathFromRoot === '' || (!pathFromRoot.startsWith('..') && !path.isAbsolute(pathFromRoot))) {
|
|
9
|
+
return resolvedPath
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
throw new Error(`Resolved path escapes the allowed root: ${relativePath}`)
|
|
13
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as path from 'node:path'
|
|
2
|
+
|
|
3
|
+
const OWNER_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'] as const
|
|
4
|
+
export type OwnerFileExtension = (typeof OWNER_FILE_EXTENSIONS)[number]
|
|
5
|
+
|
|
6
|
+
export function getActionOwnerCandidates(segments: string[]): string[] {
|
|
7
|
+
return OWNER_FILE_EXTENSIONS.map((extension) =>
|
|
8
|
+
normalizeRelativePath(path.join('app', 'controllers', `${path.join(...segments)}${extension}`)),
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getControllerOwnerCandidates(segments: string[]): string[] {
|
|
13
|
+
return OWNER_FILE_EXTENSIONS.map((extension) =>
|
|
14
|
+
normalizeRelativePath(path.join('app', 'controllers', ...segments, `controller${extension}`)),
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getRouteSubtreePath(segments: string[]): string {
|
|
19
|
+
return normalizeRelativePath(path.join('app', 'controllers', ...segments))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getPreferredOwnerDisplayPath(candidates: string[]): string {
|
|
23
|
+
let tsxCandidate = candidates.find((candidate) => candidate.endsWith('.tsx'))
|
|
24
|
+
return tsxCandidate ?? candidates[0] ?? ''
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getOwnerCandidateForExtension(
|
|
28
|
+
candidates: string[],
|
|
29
|
+
extension: OwnerFileExtension,
|
|
30
|
+
): string | null {
|
|
31
|
+
return candidates.find((candidate) => candidate.endsWith(extension)) ?? null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getOwnerFileExtension(filePath: string): OwnerFileExtension | null {
|
|
35
|
+
return OWNER_FILE_EXTENSIONS.find((extension) => filePath.endsWith(extension)) ?? null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function toDiskSegment(segment: string): string {
|
|
39
|
+
let diskSegment = segment
|
|
40
|
+
.replace(/[\\/]+/g, '-')
|
|
41
|
+
.replace(/\.+/g, '-')
|
|
42
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
43
|
+
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
|
|
44
|
+
.toLowerCase()
|
|
45
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
46
|
+
.replace(/-+/g, '-')
|
|
47
|
+
.replace(/^-+|-+$/g, '')
|
|
48
|
+
|
|
49
|
+
return diskSegment.length === 0 ? 'route' : diskSegment
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isControllerEntryFileName(fileName: string): boolean {
|
|
53
|
+
return OWNER_FILE_EXTENSIONS.some((extension) => fileName === `controller${extension}`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getOwnerModuleBaseName(fileName: string): string | null {
|
|
57
|
+
let extension = OWNER_FILE_EXTENSIONS.find((ownerExtension) => fileName.endsWith(ownerExtension))
|
|
58
|
+
|
|
59
|
+
if (extension == null) {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return fileName.slice(0, -extension.length)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isActionFileName(fileName: string): boolean {
|
|
67
|
+
let baseName = getOwnerModuleBaseName(fileName)
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
baseName != null &&
|
|
71
|
+
baseName !== 'controller' &&
|
|
72
|
+
!baseName.endsWith('.test') &&
|
|
73
|
+
!baseName.endsWith('.spec')
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeRelativePath(filePath: string): string {
|
|
78
|
+
return filePath.split(path.sep).join('/')
|
|
79
|
+
}
|