@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
package/package.json
CHANGED
|
@@ -1,14 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remix-run/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Command-line interface for Remix",
|
|
5
|
+
"author": "Michael Jackson <mjijackson@gmail.com>",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
8
9
|
"url": "git+https://github.com/remix-run/remix.git",
|
|
9
10
|
"directory": "packages/cli"
|
|
10
11
|
},
|
|
11
|
-
"
|
|
12
|
-
|
|
12
|
+
"homepage": "https://github.com/remix-run/remix/tree/main/packages/cli#readme",
|
|
13
|
+
"files": [
|
|
14
|
+
"bootstrap",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"README.md",
|
|
17
|
+
"dist",
|
|
18
|
+
"src",
|
|
19
|
+
"!src/**/*.test.ts"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=24.3.0"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"semver": "^7.7.4",
|
|
34
|
+
"@remix-run/test": "^0.2.0",
|
|
35
|
+
"@remix-run/tar-parser": "^0.7.1",
|
|
36
|
+
"@remix-run/terminal": "^0.1.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^24.6.0",
|
|
40
|
+
"@types/semver": "^7.5.8",
|
|
41
|
+
"@typescript/native-preview": "7.0.0-dev.20251125.1",
|
|
42
|
+
"@remix-run/assert": "^0.1.0"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"remix",
|
|
46
|
+
"cli",
|
|
47
|
+
"scaffold",
|
|
48
|
+
"project",
|
|
49
|
+
"command-line"
|
|
50
|
+
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsgo -p tsconfig.build.json",
|
|
53
|
+
"clean": "git clean -fdX",
|
|
54
|
+
"test": "remix-test --concurrency 1",
|
|
55
|
+
"typecheck": "tsgo --noEmit"
|
|
13
56
|
}
|
|
14
|
-
}
|
|
57
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { runRemix, type RunRemixOptions } from './lib/cli.ts'
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises'
|
|
2
|
+
import * as path from 'node:path'
|
|
3
|
+
import * as process from 'node:process'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import semver from 'semver'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
appNameUnavailable,
|
|
9
|
+
invalidPackageName,
|
|
10
|
+
targetDirectoryNotEmpty,
|
|
11
|
+
targetPathNotDirectory,
|
|
12
|
+
} from './errors.ts'
|
|
13
|
+
import { runProgressStep, type StepProgressReporter } from './reporter.ts'
|
|
14
|
+
|
|
15
|
+
const BOOTSTRAP_DIRECTORY = path.resolve(
|
|
16
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
17
|
+
'../../bootstrap',
|
|
18
|
+
)
|
|
19
|
+
const BOOTSTRAP_EXCLUDED_NAMES = new Set(['.gitkeep', 'node_modules'])
|
|
20
|
+
export const MINIMUM_SUPPORTED_NODE_VERSION = '24.3.0'
|
|
21
|
+
|
|
22
|
+
export interface BootstrapProjectOptions {
|
|
23
|
+
appName: string | null
|
|
24
|
+
cwd?: string
|
|
25
|
+
force: boolean
|
|
26
|
+
remixVersion?: string
|
|
27
|
+
targetDir: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type BootstrapProjectPhase =
|
|
31
|
+
| 'prepare-target-directory'
|
|
32
|
+
| 'generate-scaffold-files'
|
|
33
|
+
| 'finalize-package-json'
|
|
34
|
+
|
|
35
|
+
export interface BootstrappedProject {
|
|
36
|
+
appDisplayName: string
|
|
37
|
+
targetDir: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type BootstrapProgressReporter = StepProgressReporter<BootstrapProjectPhase>
|
|
41
|
+
|
|
42
|
+
interface BootstrapConfig {
|
|
43
|
+
appDisplayName: string
|
|
44
|
+
packageName: string
|
|
45
|
+
remixVersion: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type TemplateValues = Record<string, string>
|
|
49
|
+
|
|
50
|
+
export async function bootstrapProject(
|
|
51
|
+
options: BootstrapProjectOptions,
|
|
52
|
+
progress?: BootstrapProgressReporter,
|
|
53
|
+
): Promise<BootstrappedProject> {
|
|
54
|
+
let cwd = options.cwd ?? process.cwd()
|
|
55
|
+
let targetDir = path.resolve(cwd, options.targetDir)
|
|
56
|
+
let rawAppName = options.appName ?? path.basename(targetDir)
|
|
57
|
+
if (rawAppName.length === 0) {
|
|
58
|
+
throw appNameUnavailable(targetDir)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let config = {
|
|
62
|
+
appDisplayName: options.appName ?? humanizeName(rawAppName),
|
|
63
|
+
packageName: toPackageName(rawAppName),
|
|
64
|
+
remixVersion: readDefaultRemixVersion(options.remixVersion),
|
|
65
|
+
} satisfies BootstrapConfig
|
|
66
|
+
|
|
67
|
+
await runProgressStep(progress, 'prepare-target-directory', () =>
|
|
68
|
+
ensureTargetDirectory(targetDir, options.force),
|
|
69
|
+
)
|
|
70
|
+
await runProgressStep(progress, 'generate-scaffold-files', () =>
|
|
71
|
+
copyBootstrapDirectory({
|
|
72
|
+
sourceDir: BOOTSTRAP_DIRECTORY,
|
|
73
|
+
targetDir,
|
|
74
|
+
templateValues: createTemplateValues(config),
|
|
75
|
+
}),
|
|
76
|
+
)
|
|
77
|
+
await runProgressStep(progress, 'finalize-package-json', () =>
|
|
78
|
+
writeScaffoldPackageJson(targetDir, config),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
appDisplayName: config.appDisplayName,
|
|
83
|
+
targetDir,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function readDefaultRemixVersion(runtimeVersion: string | undefined): string {
|
|
88
|
+
let overriddenVersion = process.env.REMIX_VERSION?.trim()
|
|
89
|
+
if (overriddenVersion) {
|
|
90
|
+
return overriddenVersion
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (runtimeVersion == null) {
|
|
94
|
+
return 'latest'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let validRuntimeVersion = semver.valid(runtimeVersion)
|
|
98
|
+
return validRuntimeVersion == null ? runtimeVersion : `^${validRuntimeVersion}`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function createTemplateValues(config: BootstrapConfig): TemplateValues {
|
|
102
|
+
return {
|
|
103
|
+
'%%RMX_APP_DISPLAY_NAME%%': config.appDisplayName,
|
|
104
|
+
'%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%': encodeURIComponent(config.appDisplayName),
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function writeScaffoldPackageJson(targetDir: string, config: BootstrapConfig): Promise<void> {
|
|
109
|
+
let packageJsonPath = path.join(targetDir, 'package.json')
|
|
110
|
+
let packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')) as {
|
|
111
|
+
dependencies?: Record<string, string>
|
|
112
|
+
devDependencies?: Record<string, string>
|
|
113
|
+
engines?: Record<string, string>
|
|
114
|
+
name?: string
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
packageJson.name = config.packageName
|
|
118
|
+
packageJson.dependencies = {
|
|
119
|
+
...packageJson.dependencies,
|
|
120
|
+
remix: config.remixVersion,
|
|
121
|
+
tsx: 'latest',
|
|
122
|
+
}
|
|
123
|
+
packageJson.devDependencies = {
|
|
124
|
+
...packageJson.devDependencies,
|
|
125
|
+
'@types/node': 'latest',
|
|
126
|
+
typescript: 'latest',
|
|
127
|
+
}
|
|
128
|
+
packageJson.engines = {
|
|
129
|
+
...packageJson.engines,
|
|
130
|
+
node: `>=${MINIMUM_SUPPORTED_NODE_VERSION}`,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8')
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function ensureTargetDirectory(targetDir: string, force: boolean): Promise<void> {
|
|
137
|
+
try {
|
|
138
|
+
let stats = await fs.stat(targetDir)
|
|
139
|
+
if (!stats.isDirectory()) {
|
|
140
|
+
throw targetPathNotDirectory(targetDir)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let entries = await fs.readdir(targetDir)
|
|
144
|
+
if (entries.length > 0 && !force) {
|
|
145
|
+
throw targetDirectoryNotEmpty(targetDir)
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
let nodeError = error as NodeJS.ErrnoException
|
|
149
|
+
if (nodeError.code !== 'ENOENT') {
|
|
150
|
+
throw error
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await fs.mkdir(targetDir, { recursive: true })
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function copyBootstrapDirectory({
|
|
158
|
+
sourceDir,
|
|
159
|
+
targetDir,
|
|
160
|
+
templateValues,
|
|
161
|
+
}: {
|
|
162
|
+
sourceDir: string
|
|
163
|
+
targetDir: string
|
|
164
|
+
templateValues: TemplateValues
|
|
165
|
+
}): Promise<void> {
|
|
166
|
+
let entries = await fs.readdir(sourceDir, { withFileTypes: true })
|
|
167
|
+
|
|
168
|
+
for (let entry of entries) {
|
|
169
|
+
let sourcePath = path.join(sourceDir, entry.name)
|
|
170
|
+
let targetPath = path.join(targetDir, entry.name)
|
|
171
|
+
|
|
172
|
+
if (BOOTSTRAP_EXCLUDED_NAMES.has(entry.name)) {
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (entry.isDirectory()) {
|
|
177
|
+
await fs.mkdir(targetPath, { recursive: true })
|
|
178
|
+
await copyBootstrapDirectory({ sourceDir: sourcePath, targetDir: targetPath, templateValues })
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let content = await fs.readFile(sourcePath, 'utf8')
|
|
183
|
+
await fs.writeFile(targetPath, replaceTemplateValues(content, templateValues), 'utf8')
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function replaceTemplateValues(content: string, templateValues: TemplateValues): string {
|
|
188
|
+
for (let [token, value] of Object.entries(templateValues)) {
|
|
189
|
+
content = content.split(token).join(value)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return content
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function humanizeName(value: string): string {
|
|
196
|
+
let parts = value.split(/[-_\s]+/).filter(Boolean)
|
|
197
|
+
if (parts.length === 0) {
|
|
198
|
+
return 'Remix App'
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return parts
|
|
202
|
+
.map((part) => {
|
|
203
|
+
let head = part.slice(0, 1).toUpperCase()
|
|
204
|
+
let tail = part.slice(1)
|
|
205
|
+
return `${head}${tail}`
|
|
206
|
+
})
|
|
207
|
+
.join(' ')
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function toPackageName(value: string): string {
|
|
211
|
+
let packageName = value
|
|
212
|
+
.trim()
|
|
213
|
+
.toLowerCase()
|
|
214
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
215
|
+
.replace(/^-+|-+$/g, '')
|
|
216
|
+
|
|
217
|
+
if (packageName.length === 0) {
|
|
218
|
+
throw invalidPackageName(value)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return packageName
|
|
222
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as path from 'node:path'
|
|
2
|
+
import * as process from 'node:process'
|
|
3
|
+
|
|
4
|
+
import { resolveDefaultRemixVersion } from './remix-version.ts'
|
|
5
|
+
|
|
6
|
+
interface ResolveCliContextOptions {
|
|
7
|
+
cwd?: string
|
|
8
|
+
remixVersion?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CliContext {
|
|
12
|
+
cwd: string
|
|
13
|
+
remixVersion?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function resolveCliContext(
|
|
17
|
+
options: ResolveCliContextOptions = {},
|
|
18
|
+
): Promise<CliContext> {
|
|
19
|
+
let cwd = resolveCwd(options.cwd)
|
|
20
|
+
let remixVersion = normalizeRemixVersion(options.remixVersion)
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
cwd,
|
|
24
|
+
remixVersion: remixVersion ?? (await resolveDefaultRemixVersion(cwd)),
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveCwd(cwd: string | undefined): string {
|
|
29
|
+
let normalizedCwd = cwd?.trim()
|
|
30
|
+
return path.resolve(
|
|
31
|
+
normalizedCwd == null || normalizedCwd.length === 0 ? process.cwd() : normalizedCwd,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeRemixVersion(remixVersion: string | undefined): string | undefined {
|
|
36
|
+
let normalizedVersion = remixVersion?.trim()
|
|
37
|
+
return normalizedVersion == null || normalizedVersion.length === 0 ? undefined : normalizedVersion
|
|
38
|
+
}
|
package/src/lib/cli.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as process from 'node:process'
|
|
2
|
+
|
|
3
|
+
import { runCompletionCommand } from './commands/completion.ts'
|
|
4
|
+
import { runDoctorCommand } from './commands/doctor.ts'
|
|
5
|
+
import { getCliHelpText, runHelpCommand } from './commands/help.ts'
|
|
6
|
+
import { runNewCommand } from './commands/new.ts'
|
|
7
|
+
import { runRoutesCommand } from './commands/routes.ts'
|
|
8
|
+
import { runSkillsCommand } from './commands/skills.ts'
|
|
9
|
+
import { runTestCommand } from './commands/test.ts'
|
|
10
|
+
import { runVersionCommand } from './commands/version.ts'
|
|
11
|
+
import { resolveCliContext, type CliContext } from './cli-context.ts'
|
|
12
|
+
import { renderCliError, unknownCommand } from './errors.ts'
|
|
13
|
+
import { configureColors, restoreTerminalFormatting } from './terminal.ts'
|
|
14
|
+
|
|
15
|
+
export interface RunRemixOptions {
|
|
16
|
+
cwd?: string
|
|
17
|
+
remixVersion?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function runRemix(
|
|
21
|
+
argv: string[] = process.argv.slice(2),
|
|
22
|
+
options: RunRemixOptions = {},
|
|
23
|
+
): Promise<number> {
|
|
24
|
+
let context = await resolveCliContext(options)
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
while (argv[0] === '--') {
|
|
28
|
+
argv = argv.slice(1)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let globalOptions = extractGlobalOptions(argv)
|
|
32
|
+
argv = globalOptions.argv
|
|
33
|
+
configureColors({ disabled: globalOptions.noColor })
|
|
34
|
+
|
|
35
|
+
if (argv.length === 0) {
|
|
36
|
+
process.stdout.write(getCliHelpText())
|
|
37
|
+
return 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let [command, ...rest] = argv
|
|
41
|
+
|
|
42
|
+
if (command === '-h' || command === '--help') {
|
|
43
|
+
process.stdout.write(getCliHelpText())
|
|
44
|
+
return 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return await runCommand(command, rest, context)
|
|
48
|
+
} finally {
|
|
49
|
+
restoreTerminalFormatting()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function runCommand(command: string, argv: string[], context: CliContext): Promise<number> {
|
|
54
|
+
if (command === 'help') {
|
|
55
|
+
return runHelpCommand(argv)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (command === '-v' || command === '--version') {
|
|
59
|
+
return runVersionCommand([], context)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (command === 'new') {
|
|
63
|
+
return runNewCommand(argv, context)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (command === 'completion') {
|
|
67
|
+
return runCompletionCommand(argv)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (command === 'doctor') {
|
|
71
|
+
return runDoctorCommand(argv, context)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (command === 'skills') {
|
|
75
|
+
return runSkillsCommand(argv, context)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (command === 'routes') {
|
|
79
|
+
return runRoutesCommand(argv, context)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (command === 'test') {
|
|
83
|
+
return runTestCommand(argv, context)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (command === 'version') {
|
|
87
|
+
return runVersionCommand(argv, context)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
process.stderr.write(
|
|
91
|
+
renderCliError(unknownCommand(command), { helpText: getCliHelpText(process.stderr) }),
|
|
92
|
+
)
|
|
93
|
+
return 1
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function extractGlobalOptions(argv: string[]): { argv: string[]; noColor: boolean } {
|
|
97
|
+
let filteredArgv: string[] = []
|
|
98
|
+
let noColor = false
|
|
99
|
+
|
|
100
|
+
for (let index = 0; index < argv.length; index++) {
|
|
101
|
+
let arg = argv[index]!
|
|
102
|
+
|
|
103
|
+
if (arg === '--') {
|
|
104
|
+
filteredArgv.push(...argv.slice(index))
|
|
105
|
+
break
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (arg === '--no-color') {
|
|
109
|
+
noColor = true
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
filteredArgv.push(arg)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
argv: filteredArgv,
|
|
118
|
+
noColor,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as process from 'node:process'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getCompletionResult,
|
|
5
|
+
getCompletionScript,
|
|
6
|
+
isCompletionShell,
|
|
7
|
+
renderCompletionResult,
|
|
8
|
+
} from '../completion.ts'
|
|
9
|
+
import {
|
|
10
|
+
invalidCompletionRequest,
|
|
11
|
+
renderCliError,
|
|
12
|
+
toCliError,
|
|
13
|
+
unknownCompletionShell,
|
|
14
|
+
} from '../errors.ts'
|
|
15
|
+
import { formatHelpText } from '../help-text.ts'
|
|
16
|
+
import { parseArgs } from '../parse-args.ts'
|
|
17
|
+
|
|
18
|
+
export async function runCompletionCommand(argv: string[]): Promise<number> {
|
|
19
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
20
|
+
process.stdout.write(getCompletionCommandHelpText())
|
|
21
|
+
return 0
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (argv[0] === '--') {
|
|
25
|
+
return runCompletionPlumbing(argv.slice(1))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let [shell, ...rest] = argv
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
parseArgs(rest, {}, { maxPositionals: 0 })
|
|
32
|
+
|
|
33
|
+
if (!isCompletionShell(shell)) {
|
|
34
|
+
throw unknownCompletionShell(shell)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
process.stdout.write(getCompletionScript())
|
|
38
|
+
return 0
|
|
39
|
+
} catch (error) {
|
|
40
|
+
process.stderr.write(
|
|
41
|
+
renderCliError(toCliError(error), {
|
|
42
|
+
helpText: getCompletionCommandHelpText(process.stderr),
|
|
43
|
+
}),
|
|
44
|
+
)
|
|
45
|
+
return 1
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getCompletionCommandHelpText(target: NodeJS.WriteStream = process.stdout): string {
|
|
50
|
+
return formatHelpText(
|
|
51
|
+
{
|
|
52
|
+
description: 'Print a shell completion script for Remix.',
|
|
53
|
+
examples: ['remix completion bash >> ~/.bashrc', 'remix completion zsh >> ~/.zshrc'],
|
|
54
|
+
usage: ['remix completion <bash|zsh>'],
|
|
55
|
+
},
|
|
56
|
+
target,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function runCompletionPlumbing(argv: string[]): number {
|
|
61
|
+
try {
|
|
62
|
+
let [currentIndexArg, ...words] = argv
|
|
63
|
+
if (currentIndexArg == null || !/^\d+$/.test(currentIndexArg)) {
|
|
64
|
+
throw invalidCompletionRequest()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let currentIndex = Number(currentIndexArg)
|
|
68
|
+
process.stdout.write(renderCompletionResult(getCompletionResult(words, currentIndex)))
|
|
69
|
+
return 0
|
|
70
|
+
} catch (error) {
|
|
71
|
+
process.stderr.write(renderCliError(toCliError(error)))
|
|
72
|
+
return 1
|
|
73
|
+
}
|
|
74
|
+
}
|