@remix-run/cli 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -3
- package/dist/lib/bootstrap-project.d.ts.map +1 -1
- package/dist/lib/bootstrap-project.js +32 -9
- package/dist/lib/cli.d.ts +29 -0
- package/dist/lib/cli.d.ts.map +1 -1
- package/dist/lib/cli.js +33 -14
- package/dist/lib/commands/completion.d.ts.map +1 -1
- package/dist/lib/commands/completion.js +5 -1
- package/dist/lib/commands/doctor.js +18 -18
- package/dist/lib/commands/help.d.ts.map +1 -1
- package/dist/lib/commands/help.js +9 -33
- package/dist/lib/commands/routes.js +3 -3
- package/dist/lib/commands/test.d.ts +1 -1
- package/dist/lib/commands/test.d.ts.map +1 -1
- package/dist/lib/commands/test.js +8 -4
- package/dist/lib/completion.d.ts.map +1 -1
- package/dist/lib/completion.js +4 -106
- package/dist/lib/controller-files.d.ts +0 -1
- package/dist/lib/controller-files.d.ts.map +1 -1
- package/dist/lib/controller-files.js +3 -5
- package/dist/lib/controller-ownership.d.ts +9 -9
- package/dist/lib/controller-ownership.d.ts.map +1 -1
- package/dist/lib/controller-ownership.js +56 -91
- package/dist/lib/doctor/controller-findings.d.ts +1 -1
- package/dist/lib/doctor/controller-findings.d.ts.map +1 -1
- package/dist/lib/doctor/controller-findings.js +15 -87
- package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -1
- package/dist/lib/doctor/controller-fix-plans.js +13 -24
- package/dist/lib/doctor/controller-placeholders.d.ts +2 -3
- package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -1
- package/dist/lib/doctor/controller-placeholders.js +18 -149
- package/dist/lib/doctor/controllers.js +1 -1
- package/dist/lib/doctor/project.js +60 -52
- package/dist/lib/doctor/types.d.ts +2 -2
- package/dist/lib/doctor/types.d.ts.map +1 -1
- package/dist/lib/errors.d.ts +0 -6
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +0 -11
- package/dist/lib/load-route-map-worker.js +17 -9
- package/dist/lib/route-map.d.ts +1 -1
- package/dist/lib/route-map.d.ts.map +1 -1
- package/dist/lib/route-map.js +29 -17
- package/package.json +4 -5
- package/src/lib/bootstrap-project.ts +39 -13
- package/src/lib/cli.ts +46 -15
- package/src/lib/commands/completion.ts +6 -1
- package/src/lib/commands/doctor.ts +18 -21
- package/src/lib/commands/help.ts +9 -43
- package/src/lib/commands/routes.ts +3 -3
- package/src/lib/commands/test.ts +10 -4
- package/src/lib/completion.ts +4 -151
- package/src/lib/controller-files.ts +4 -8
- package/src/lib/controller-ownership.ts +78 -141
- package/src/lib/doctor/controller-findings.ts +20 -97
- package/src/lib/doctor/controller-fix-plans.ts +13 -29
- package/src/lib/doctor/controller-placeholders.ts +17 -189
- package/src/lib/doctor/controllers.ts +1 -1
- package/src/lib/doctor/project.ts +60 -52
- package/src/lib/doctor/types.ts +1 -5
- package/src/lib/errors.ts +0 -12
- package/src/lib/load-route-map-worker.ts +19 -10
- package/src/lib/route-map.ts +61 -16
- package/template/.agents/skills/remix/SKILL.md +588 -0
- package/template/.agents/skills/remix/references/animate-elements.md +195 -0
- package/template/.agents/skills/remix/references/assets-and-browser-modules.md +130 -0
- package/template/.agents/skills/remix/references/auth-and-sessions.md +443 -0
- package/template/.agents/skills/remix/references/component-model.md +282 -0
- package/template/.agents/skills/remix/references/create-mixins.md +158 -0
- package/template/.agents/skills/remix/references/data-and-validation.md +379 -0
- package/template/.agents/skills/remix/references/hydration-frames-navigation.md +297 -0
- package/template/.agents/skills/remix/references/middleware-and-server.md +233 -0
- package/template/.agents/skills/remix/references/mixins-styling-events.md +213 -0
- package/template/.agents/skills/remix/references/routing-and-controllers.md +391 -0
- package/template/.agents/skills/remix/references/testing-patterns.md +172 -0
- package/{bootstrap → template}/AGENTS.md +13 -8
- package/template/README.md +29 -0
- package/template/app/actions/controller.tsx +18 -0
- package/template/app/assets/entry.ts +8 -0
- package/template/app/assets/prompt-button.tsx +163 -0
- package/template/app/assets.ts +20 -0
- package/template/app/middleware/render.tsx +43 -0
- package/template/app/router.ts +20 -0
- package/template/app/routes.ts +6 -0
- package/template/app/ui/document.tsx +33 -0
- package/template/app/ui/scaffold-home-page.tsx +527 -0
- package/template/gitignore +4 -0
- package/{bootstrap → template}/package.json +5 -6
- package/template/public/favicon.svg +11 -0
- package/{bootstrap → template}/server.ts +4 -5
- package/{bootstrap → template}/tsconfig.json +3 -3
- package/bootstrap/README.md +0 -27
- package/bootstrap/app/controllers/auth.tsx +0 -21
- package/bootstrap/app/controllers/home.tsx +0 -26
- package/bootstrap/app/router.ts +0 -10
- package/bootstrap/app/routes.ts +0 -6
- package/bootstrap/app/ui/document.tsx +0 -21
- package/bootstrap/app/ui/layout.tsx +0 -22
- package/bootstrap/app/utils/render.tsx +0 -7
- package/dist/lib/commands/skills.d.ts +0 -6
- package/dist/lib/commands/skills.d.ts.map +0 -1
- package/dist/lib/commands/skills.js +0 -222
- package/dist/lib/skills-cache.d.ts +0 -19
- package/dist/lib/skills-cache.d.ts.map +0 -1
- package/dist/lib/skills-cache.js +0 -89
- package/dist/lib/skills.d.ts +0 -30
- package/dist/lib/skills.d.ts.map +0 -1
- package/dist/lib/skills.js +0 -441
- package/src/lib/commands/skills.ts +0 -306
- package/src/lib/skills-cache.ts +0 -140
- package/src/lib/skills.ts +0 -706
package/dist/lib/route-map.js
CHANGED
|
@@ -3,12 +3,12 @@ import * as path from 'node:path';
|
|
|
3
3
|
import * as process from 'node:process';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { inspectControllerOwnership } from "./controller-ownership.js";
|
|
6
|
+
import { ROOT_ROUTE_NAME, inspectControllerOwnership, } from "./controller-ownership.js";
|
|
7
7
|
import { routeMapLoaderFailed, routeMapLoaderInvalidJson, routeMapLoaderSignal, routeOwnerPlanUnresolved, routesFileNotFound, } from "./errors.js";
|
|
8
8
|
export async function loadRouteMap(cwd = process.cwd()) {
|
|
9
9
|
let manifest = await loadRouteManifest(cwd);
|
|
10
10
|
let ownership = await inspectControllerOwnership(manifest.appRoot, manifest.tree);
|
|
11
|
-
let tree = decorateRouteTree(manifest.tree, ownership
|
|
11
|
+
let tree = decorateRouteTree(manifest.tree, ownership);
|
|
12
12
|
return {
|
|
13
13
|
appRoot: manifest.appRoot,
|
|
14
14
|
routesFile: manifest.routesFile,
|
|
@@ -70,17 +70,18 @@ async function loadRawRouteMap(appRoot, routesFile) {
|
|
|
70
70
|
}
|
|
71
71
|
return assertRawRouteTree(parsed);
|
|
72
72
|
}
|
|
73
|
-
function decorateRouteTree(rawTree,
|
|
74
|
-
let subtreesByRouteName = new Map(subtrees.map((subtree) => [subtree.routeName, subtree]));
|
|
75
|
-
|
|
73
|
+
function decorateRouteTree(rawTree, ownership) {
|
|
74
|
+
let subtreesByRouteName = new Map(ownership.subtrees.map((subtree) => [subtree.routeName, subtree]));
|
|
75
|
+
let directoriesByRouteName = new Map(ownership.routeDirectories.map((directory) => [directory.routeName, directory]));
|
|
76
|
+
return decorateRouteTreeWithLookup(rawTree, subtreesByRouteName, directoriesByRouteName, ownership.scan.routeDirectoryPaths);
|
|
76
77
|
}
|
|
77
|
-
function decorateRouteTreeWithLookup(rawTree, subtreesByRouteName, parentSegments = []) {
|
|
78
|
+
function decorateRouteTreeWithLookup(rawTree, subtreesByRouteName, directoriesByRouteName, actualRouteDirectories, parentSegments = []) {
|
|
78
79
|
return rawTree.map((rawNode) => {
|
|
79
|
-
let owner = getRouteOwner(rawNode, parentSegments, subtreesByRouteName);
|
|
80
|
+
let owner = getRouteOwner(rawNode, parentSegments, subtreesByRouteName, directoriesByRouteName, actualRouteDirectories);
|
|
80
81
|
let nextParentSegments = rawNode.kind === 'group' ? [...parentSegments, rawNode.key] : parentSegments;
|
|
81
82
|
return {
|
|
82
83
|
children: rawNode.kind === 'group'
|
|
83
|
-
? decorateRouteTreeWithLookup(rawNode.children, subtreesByRouteName, nextParentSegments)
|
|
84
|
+
? decorateRouteTreeWithLookup(rawNode.children, subtreesByRouteName, directoriesByRouteName, actualRouteDirectories, nextParentSegments)
|
|
84
85
|
: [],
|
|
85
86
|
key: rawNode.key,
|
|
86
87
|
kind: rawNode.kind,
|
|
@@ -91,21 +92,32 @@ function decorateRouteTreeWithLookup(rawTree, subtreesByRouteName, parentSegment
|
|
|
91
92
|
};
|
|
92
93
|
});
|
|
93
94
|
}
|
|
94
|
-
function getRouteOwner(rawNode, parentSegments, subtreesByRouteName) {
|
|
95
|
+
function getRouteOwner(rawNode, parentSegments, subtreesByRouteName, directoriesByRouteName, actualRouteDirectories) {
|
|
95
96
|
let ownerRouteName = rawNode.kind === 'group'
|
|
96
97
|
? rawNode.name
|
|
97
98
|
: parentSegments.length === 0
|
|
98
|
-
?
|
|
99
|
+
? ROOT_ROUTE_NAME
|
|
99
100
|
: parentSegments.join('.');
|
|
100
101
|
let subtree = subtreesByRouteName.get(ownerRouteName);
|
|
101
|
-
if (subtree
|
|
102
|
-
|
|
102
|
+
if (subtree != null) {
|
|
103
|
+
return {
|
|
104
|
+
exists: subtree.actualEntryPath != null,
|
|
105
|
+
kind: 'controller',
|
|
106
|
+
path: subtree.actualEntryPath ?? subtree.entryDisplayPath,
|
|
107
|
+
};
|
|
103
108
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
if (rawNode.kind === 'group') {
|
|
110
|
+
let directory = directoriesByRouteName.get(rawNode.name);
|
|
111
|
+
if (directory == null) {
|
|
112
|
+
throw routeOwnerPlanUnresolved(rawNode.name);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
exists: actualRouteDirectories.has(directory.directoryPath),
|
|
116
|
+
kind: 'directory',
|
|
117
|
+
path: directory.directoryPath,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
throw routeOwnerPlanUnresolved(rawNode.name);
|
|
109
121
|
}
|
|
110
122
|
function assertRawRouteTree(value) {
|
|
111
123
|
if (!Array.isArray(value)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remix-run/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Command-line interface for Remix",
|
|
5
5
|
"author": "Michael Jackson <mjijackson@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
},
|
|
12
12
|
"homepage": "https://github.com/remix-run/remix/tree/main/packages/cli#readme",
|
|
13
13
|
"files": [
|
|
14
|
-
"bootstrap",
|
|
15
14
|
"LICENSE",
|
|
16
15
|
"README.md",
|
|
17
16
|
"dist",
|
|
18
17
|
"src",
|
|
18
|
+
"template",
|
|
19
19
|
"!src/**/*.test.ts"
|
|
20
20
|
],
|
|
21
21
|
"type": "module",
|
|
@@ -31,15 +31,14 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"semver": "^7.7.4",
|
|
34
|
-
"@remix-run/test": "^0.
|
|
35
|
-
"@remix-run/tar-parser": "^0.7.1",
|
|
34
|
+
"@remix-run/test": "^0.4.0",
|
|
36
35
|
"@remix-run/terminal": "^0.1.0"
|
|
37
36
|
},
|
|
38
37
|
"devDependencies": {
|
|
39
38
|
"@types/node": "^24.6.0",
|
|
40
39
|
"@types/semver": "^7.5.8",
|
|
41
40
|
"@typescript/native-preview": "7.0.0-dev.20251125.1",
|
|
42
|
-
"@remix-run/assert": "^0.
|
|
41
|
+
"@remix-run/assert": "^0.2.0"
|
|
43
42
|
},
|
|
44
43
|
"keywords": [
|
|
45
44
|
"remix",
|
|
@@ -12,11 +12,10 @@ import {
|
|
|
12
12
|
} from './errors.ts'
|
|
13
13
|
import { runProgressStep, type StepProgressReporter } from './reporter.ts'
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
)
|
|
19
|
-
const BOOTSTRAP_EXCLUDED_NAMES = new Set(['.gitkeep', 'node_modules'])
|
|
15
|
+
const MODULE_DIRECTORY = path.dirname(fileURLToPath(import.meta.url))
|
|
16
|
+
const PACKAGE_TEMPLATE_DIRECTORY = path.resolve(MODULE_DIRECTORY, '../../template')
|
|
17
|
+
const REPO_TEMPLATE_DIRECTORY = path.resolve(MODULE_DIRECTORY, '../../../../template')
|
|
18
|
+
const TEMPLATE_EXCLUDED_NAMES = new Set(['.gitkeep', 'node_modules'])
|
|
20
19
|
export const MINIMUM_SUPPORTED_NODE_VERSION = '24.3.0'
|
|
21
20
|
|
|
22
21
|
export interface BootstrapProjectOptions {
|
|
@@ -67,9 +66,9 @@ export async function bootstrapProject(
|
|
|
67
66
|
await runProgressStep(progress, 'prepare-target-directory', () =>
|
|
68
67
|
ensureTargetDirectory(targetDir, options.force),
|
|
69
68
|
)
|
|
70
|
-
await runProgressStep(progress, 'generate-scaffold-files', () =>
|
|
71
|
-
|
|
72
|
-
sourceDir:
|
|
69
|
+
await runProgressStep(progress, 'generate-scaffold-files', async () =>
|
|
70
|
+
copyTemplateDirectory({
|
|
71
|
+
sourceDir: await resolveTemplateDirectory(),
|
|
73
72
|
targetDir,
|
|
74
73
|
templateValues: createTemplateValues(config),
|
|
75
74
|
}),
|
|
@@ -105,6 +104,34 @@ function createTemplateValues(config: BootstrapConfig): TemplateValues {
|
|
|
105
104
|
}
|
|
106
105
|
}
|
|
107
106
|
|
|
107
|
+
async function resolveTemplateDirectory(): Promise<string> {
|
|
108
|
+
if (await isDirectory(PACKAGE_TEMPLATE_DIRECTORY)) {
|
|
109
|
+
return PACKAGE_TEMPLATE_DIRECTORY
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (await isDirectory(REPO_TEMPLATE_DIRECTORY)) {
|
|
113
|
+
return REPO_TEMPLATE_DIRECTORY
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Could not find the Remix app template at ${PACKAGE_TEMPLATE_DIRECTORY} or ${REPO_TEMPLATE_DIRECTORY}.`,
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function isDirectory(directory: string): Promise<boolean> {
|
|
122
|
+
try {
|
|
123
|
+
let stats = await fs.stat(directory)
|
|
124
|
+
return stats.isDirectory()
|
|
125
|
+
} catch (error) {
|
|
126
|
+
let nodeError = error as NodeJS.ErrnoException
|
|
127
|
+
if (nodeError.code === 'ENOENT') {
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
throw error
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
108
135
|
async function writeScaffoldPackageJson(targetDir: string, config: BootstrapConfig): Promise<void> {
|
|
109
136
|
let packageJsonPath = path.join(targetDir, 'package.json')
|
|
110
137
|
let packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')) as {
|
|
@@ -118,7 +145,6 @@ async function writeScaffoldPackageJson(targetDir: string, config: BootstrapConf
|
|
|
118
145
|
packageJson.dependencies = {
|
|
119
146
|
...packageJson.dependencies,
|
|
120
147
|
remix: config.remixVersion,
|
|
121
|
-
tsx: 'latest',
|
|
122
148
|
}
|
|
123
149
|
packageJson.devDependencies = {
|
|
124
150
|
...packageJson.devDependencies,
|
|
@@ -154,7 +180,7 @@ async function ensureTargetDirectory(targetDir: string, force: boolean): Promise
|
|
|
154
180
|
await fs.mkdir(targetDir, { recursive: true })
|
|
155
181
|
}
|
|
156
182
|
|
|
157
|
-
async function
|
|
183
|
+
async function copyTemplateDirectory({
|
|
158
184
|
sourceDir,
|
|
159
185
|
targetDir,
|
|
160
186
|
templateValues,
|
|
@@ -167,15 +193,15 @@ async function copyBootstrapDirectory({
|
|
|
167
193
|
|
|
168
194
|
for (let entry of entries) {
|
|
169
195
|
let sourcePath = path.join(sourceDir, entry.name)
|
|
170
|
-
let targetPath = path.join(targetDir, entry.name)
|
|
196
|
+
let targetPath = path.join(targetDir, entry.name === 'gitignore' ? '.gitignore' : entry.name)
|
|
171
197
|
|
|
172
|
-
if (
|
|
198
|
+
if (TEMPLATE_EXCLUDED_NAMES.has(entry.name)) {
|
|
173
199
|
continue
|
|
174
200
|
}
|
|
175
201
|
|
|
176
202
|
if (entry.isDirectory()) {
|
|
177
203
|
await fs.mkdir(targetPath, { recursive: true })
|
|
178
|
-
await
|
|
204
|
+
await copyTemplateDirectory({ sourceDir: sourcePath, targetDir: targetPath, templateValues })
|
|
179
205
|
continue
|
|
180
206
|
}
|
|
181
207
|
|
package/src/lib/cli.ts
CHANGED
|
@@ -1,22 +1,44 @@
|
|
|
1
1
|
import * as process from 'node:process'
|
|
2
2
|
|
|
3
|
-
import { runCompletionCommand } from './commands/completion.ts'
|
|
4
|
-
import { runDoctorCommand } from './commands/doctor.ts'
|
|
5
3
|
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
4
|
import { resolveCliContext, type CliContext } from './cli-context.ts'
|
|
12
5
|
import { renderCliError, unknownCommand } from './errors.ts'
|
|
13
6
|
import { configureColors, restoreTerminalFormatting } from './terminal.ts'
|
|
14
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Options accepted by {@link runRemix}.
|
|
10
|
+
*/
|
|
15
11
|
export interface RunRemixOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Directory used as the working root when resolving project files (default
|
|
14
|
+
* `process.cwd()`).
|
|
15
|
+
*/
|
|
16
16
|
cwd?: string
|
|
17
|
+
/**
|
|
18
|
+
* Override for the Remix version reported by `remix --version`. Defaults to
|
|
19
|
+
* the version of the installed `@remix-run/cli` package.
|
|
20
|
+
*/
|
|
17
21
|
remixVersion?: string
|
|
18
22
|
}
|
|
19
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Entry point for the `remix` CLI. Parses `argv`, dispatches to the matching
|
|
26
|
+
* subcommand (`new`, `doctor`, `routes`, `test`, `version`, `completion`,
|
|
27
|
+
* `help`), and resolves with the exit code the process should use.
|
|
28
|
+
*
|
|
29
|
+
* @param argv Argument vector to parse, excluding the node and script paths
|
|
30
|
+
* (default `process.argv.slice(2)`).
|
|
31
|
+
* @param options Overrides for CLI context resolution.
|
|
32
|
+
* @returns The numeric exit code (`0` on success, non-zero on failure).
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { runRemix } from '@remix-run/cli'
|
|
37
|
+
*
|
|
38
|
+
* let exitCode = await runRemix(process.argv.slice(2))
|
|
39
|
+
* process.exit(exitCode)
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
20
42
|
export async function runRemix(
|
|
21
43
|
argv: string[] = process.argv.slice(2),
|
|
22
44
|
options: RunRemixOptions = {},
|
|
@@ -24,12 +46,10 @@ export async function runRemix(
|
|
|
24
46
|
let context = await resolveCliContext(options)
|
|
25
47
|
|
|
26
48
|
try {
|
|
27
|
-
|
|
28
|
-
argv = argv.slice(1)
|
|
29
|
-
}
|
|
49
|
+
argv = stripLeadingSeparators(argv)
|
|
30
50
|
|
|
31
51
|
let globalOptions = extractGlobalOptions(argv)
|
|
32
|
-
argv = globalOptions.argv
|
|
52
|
+
argv = stripLeadingSeparators(globalOptions.argv)
|
|
33
53
|
configureColors({ disabled: globalOptions.noColor })
|
|
34
54
|
|
|
35
55
|
if (argv.length === 0) {
|
|
@@ -50,40 +70,51 @@ export async function runRemix(
|
|
|
50
70
|
}
|
|
51
71
|
}
|
|
52
72
|
|
|
73
|
+
function stripLeadingSeparators(argv: string[]): string[] {
|
|
74
|
+
while (argv[0] === '--') {
|
|
75
|
+
argv = argv.slice(1)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return argv
|
|
79
|
+
}
|
|
80
|
+
|
|
53
81
|
async function runCommand(command: string, argv: string[], context: CliContext): Promise<number> {
|
|
54
82
|
if (command === 'help') {
|
|
55
83
|
return runHelpCommand(argv)
|
|
56
84
|
}
|
|
57
85
|
|
|
58
86
|
if (command === '-v' || command === '--version') {
|
|
87
|
+
let { runVersionCommand } = await import('./commands/version.ts')
|
|
59
88
|
return runVersionCommand([], context)
|
|
60
89
|
}
|
|
61
90
|
|
|
62
91
|
if (command === 'new') {
|
|
92
|
+
let { runNewCommand } = await import('./commands/new.ts')
|
|
63
93
|
return runNewCommand(argv, context)
|
|
64
94
|
}
|
|
65
95
|
|
|
66
96
|
if (command === 'completion') {
|
|
97
|
+
let { runCompletionCommand } = await import('./commands/completion.ts')
|
|
67
98
|
return runCompletionCommand(argv)
|
|
68
99
|
}
|
|
69
100
|
|
|
70
101
|
if (command === 'doctor') {
|
|
102
|
+
let { runDoctorCommand } = await import('./commands/doctor.ts')
|
|
71
103
|
return runDoctorCommand(argv, context)
|
|
72
104
|
}
|
|
73
105
|
|
|
74
|
-
if (command === 'skills') {
|
|
75
|
-
return runSkillsCommand(argv, context)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
106
|
if (command === 'routes') {
|
|
107
|
+
let { runRoutesCommand } = await import('./commands/routes.ts')
|
|
79
108
|
return runRoutesCommand(argv, context)
|
|
80
109
|
}
|
|
81
110
|
|
|
82
111
|
if (command === 'test') {
|
|
112
|
+
let { runTestCommand } = await import('./commands/test.ts')
|
|
83
113
|
return runTestCommand(argv, context)
|
|
84
114
|
}
|
|
85
115
|
|
|
86
116
|
if (command === 'version') {
|
|
117
|
+
let { runVersionCommand } = await import('./commands/version.ts')
|
|
87
118
|
return runVersionCommand(argv, context)
|
|
88
119
|
}
|
|
89
120
|
|
|
@@ -16,7 +16,7 @@ import { formatHelpText } from '../help-text.ts'
|
|
|
16
16
|
import { parseArgs } from '../parse-args.ts'
|
|
17
17
|
|
|
18
18
|
export async function runCompletionCommand(argv: string[]): Promise<number> {
|
|
19
|
-
if (argv.length === 0
|
|
19
|
+
if (argv.length === 0) {
|
|
20
20
|
process.stdout.write(getCompletionCommandHelpText())
|
|
21
21
|
return 0
|
|
22
22
|
}
|
|
@@ -25,6 +25,11 @@ export async function runCompletionCommand(argv: string[]): Promise<number> {
|
|
|
25
25
|
return runCompletionPlumbing(argv.slice(1))
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
if (argv.includes('-h') || argv.includes('--help')) {
|
|
29
|
+
process.stdout.write(getCompletionCommandHelpText())
|
|
30
|
+
return 0
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
let [shell, ...rest] = argv
|
|
29
34
|
|
|
30
35
|
try {
|
|
@@ -26,9 +26,9 @@ import {
|
|
|
26
26
|
} from '../reporter.ts'
|
|
27
27
|
|
|
28
28
|
const DOCTOR_SUITE_LABELS = {
|
|
29
|
-
|
|
30
|
-
complete: '
|
|
31
|
-
running: 'Checking
|
|
29
|
+
actions: {
|
|
30
|
+
complete: 'actions',
|
|
31
|
+
running: 'Checking actions',
|
|
32
32
|
},
|
|
33
33
|
environment: {
|
|
34
34
|
complete: 'environment',
|
|
@@ -109,7 +109,7 @@ export function getDoctorCommandHelpText(target: NodeJS.WriteStream = process.st
|
|
|
109
109
|
description: 'Exit with status 1 when warning-level findings are present',
|
|
110
110
|
label: '--strict',
|
|
111
111
|
},
|
|
112
|
-
{ description: 'Apply low-risk project and
|
|
112
|
+
{ description: 'Apply low-risk project and action fixes', label: '--fix' },
|
|
113
113
|
],
|
|
114
114
|
usage: ['remix doctor [--json] [--strict] [--fix] [--no-color]'],
|
|
115
115
|
},
|
|
@@ -182,16 +182,13 @@ async function collectDoctorReport(
|
|
|
182
182
|
|
|
183
183
|
if (hasWarningFindings(environment.suite.findings)) {
|
|
184
184
|
let projectSuite = createSkippedDoctorSuite('project', 'Blocked by environment warnings.')
|
|
185
|
-
let
|
|
186
|
-
|
|
187
|
-
'Blocked by environment warnings.',
|
|
188
|
-
)
|
|
189
|
-
suites.push(projectSuite, controllersSuite)
|
|
185
|
+
let actionsSuite = createSkippedDoctorSuite('actions', 'Blocked by environment warnings.')
|
|
186
|
+
suites.push(projectSuite, actionsSuite)
|
|
190
187
|
progress?.skip(projectSuite.name, projectSuite.reason)
|
|
191
188
|
writeDoctorSuiteDetails(reporter, projectSuite)
|
|
192
189
|
progress?.writeSummaryGap()
|
|
193
|
-
progress?.skip(
|
|
194
|
-
writeDoctorSuiteDetails(reporter,
|
|
190
|
+
progress?.skip(actionsSuite.name, actionsSuite.reason)
|
|
191
|
+
writeDoctorSuiteDetails(reporter, actionsSuite)
|
|
195
192
|
progress?.writeSummaryGap()
|
|
196
193
|
|
|
197
194
|
return {
|
|
@@ -240,10 +237,10 @@ async function collectDoctorReport(
|
|
|
240
237
|
progress?.writeSummaryGap()
|
|
241
238
|
|
|
242
239
|
if (hasWarningFindings(project.suite.findings)) {
|
|
243
|
-
let
|
|
244
|
-
suites.push(
|
|
245
|
-
progress?.skip(
|
|
246
|
-
writeDoctorSuiteDetails(reporter,
|
|
240
|
+
let actionsSuite = createSkippedDoctorSuite('actions', 'Blocked by project warnings.')
|
|
241
|
+
suites.push(actionsSuite)
|
|
242
|
+
progress?.skip(actionsSuite.name, actionsSuite.reason)
|
|
243
|
+
writeDoctorSuiteDetails(reporter, actionsSuite)
|
|
247
244
|
progress?.writeSummaryGap()
|
|
248
245
|
|
|
249
246
|
return {
|
|
@@ -256,7 +253,7 @@ async function collectDoctorReport(
|
|
|
256
253
|
}
|
|
257
254
|
}
|
|
258
255
|
|
|
259
|
-
let
|
|
256
|
+
let actions = await runDoctorSuite(progress, 'actions', async () => {
|
|
260
257
|
let controllerResult = await checkControllerConventions(
|
|
261
258
|
project.routeManifest!.appRoot,
|
|
262
259
|
project.routeManifest!.tree,
|
|
@@ -276,7 +273,7 @@ async function collectDoctorReport(
|
|
|
276
273
|
remainingFindings = finalControllerResult.suite.findings
|
|
277
274
|
}
|
|
278
275
|
|
|
279
|
-
let suite = createDoctorSuite('
|
|
276
|
+
let suite = createDoctorSuite('actions', remainingFindings)
|
|
280
277
|
if (suiteAppliedFixes.length > 0) {
|
|
281
278
|
suite.appliedFixes = suiteAppliedFixes
|
|
282
279
|
}
|
|
@@ -284,10 +281,10 @@ async function collectDoctorReport(
|
|
|
284
281
|
return { appliedFixes: suiteAppliedFixes, suite }
|
|
285
282
|
})
|
|
286
283
|
|
|
287
|
-
findings.push(...
|
|
288
|
-
appliedFixes.push(...(
|
|
289
|
-
suites.push(
|
|
290
|
-
writeDoctorSuiteDetails(reporter,
|
|
284
|
+
findings.push(...actions.suite.findings)
|
|
285
|
+
appliedFixes.push(...(actions.appliedFixes ?? []))
|
|
286
|
+
suites.push(actions.suite)
|
|
287
|
+
writeDoctorSuiteDetails(reporter, actions.suite)
|
|
291
288
|
progress?.writeSummaryGap()
|
|
292
289
|
|
|
293
290
|
let report: DoctorReport = {
|
package/src/lib/commands/help.ts
CHANGED
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import * as process from 'node:process'
|
|
2
2
|
|
|
3
|
-
import { getCompletionCommandHelpText } from './completion.ts'
|
|
4
3
|
import { renderCliError, toCliError, unknownHelpTopic } from '../errors.ts'
|
|
5
4
|
import { formatHelpText } from '../help-text.ts'
|
|
6
|
-
import { getDoctorCommandHelpText } from './doctor.ts'
|
|
7
|
-
import { getNewCommandHelpText } from './new.ts'
|
|
8
|
-
import { getRoutesCommandHelpText } from './routes.ts'
|
|
9
|
-
import {
|
|
10
|
-
getSkillsCommandHelpText,
|
|
11
|
-
getSkillsInstallCommandHelpText,
|
|
12
|
-
getSkillsListCommandHelpText,
|
|
13
|
-
} from './skills.ts'
|
|
14
|
-
import { getTestCommandHelpText } from './test.ts'
|
|
15
|
-
import { getVersionCommandHelpText } from './version.ts'
|
|
16
5
|
|
|
17
6
|
export async function runHelpCommand(argv: string[]): Promise<number> {
|
|
18
7
|
if (argv.includes('-h') || argv.includes('--help')) {
|
|
@@ -21,7 +10,7 @@ export async function runHelpCommand(argv: string[]): Promise<number> {
|
|
|
21
10
|
}
|
|
22
11
|
|
|
23
12
|
try {
|
|
24
|
-
process.stdout.write(getCommandHelpText(argv))
|
|
13
|
+
process.stdout.write(await getCommandHelpText(argv))
|
|
25
14
|
return 0
|
|
26
15
|
} catch (error) {
|
|
27
16
|
process.stderr.write(
|
|
@@ -40,7 +29,6 @@ export function getCliHelpText(target: NodeJS.WriteStream = process.stdout): str
|
|
|
40
29
|
{ description: 'Create a new Remix project', label: 'new <name>' },
|
|
41
30
|
{ description: 'Check project health for the current project', label: 'doctor' },
|
|
42
31
|
{ description: 'Show the route tree for the current project', label: 'routes' },
|
|
43
|
-
{ description: 'Manage Remix skills for the current project', label: 'skills' },
|
|
44
32
|
{ description: 'Run tests for the current project', label: 'test [glob]' },
|
|
45
33
|
{ description: 'Show the current Remix version', label: 'version' },
|
|
46
34
|
],
|
|
@@ -49,12 +37,10 @@ export function getCliHelpText(target: NodeJS.WriteStream = process.stdout): str
|
|
|
49
37
|
'remix help',
|
|
50
38
|
'remix help completion',
|
|
51
39
|
'remix help doctor',
|
|
52
|
-
'remix help skills install',
|
|
53
40
|
'remix doctor',
|
|
54
41
|
'remix new my-remix-app',
|
|
55
42
|
'remix new my-remix-app --app-name "My Remix App"',
|
|
56
43
|
'remix routes',
|
|
57
|
-
'remix skills install',
|
|
58
44
|
'remix test',
|
|
59
45
|
'remix version',
|
|
60
46
|
],
|
|
@@ -79,7 +65,6 @@ export function getHelpCommandHelpText(target: NodeJS.WriteStream = process.stdo
|
|
|
79
65
|
'remix help doctor',
|
|
80
66
|
'remix help new',
|
|
81
67
|
'remix help routes',
|
|
82
|
-
'remix help skills install',
|
|
83
68
|
'remix help test',
|
|
84
69
|
'remix help version',
|
|
85
70
|
],
|
|
@@ -89,7 +74,7 @@ export function getHelpCommandHelpText(target: NodeJS.WriteStream = process.stdo
|
|
|
89
74
|
)
|
|
90
75
|
}
|
|
91
76
|
|
|
92
|
-
function getCommandHelpText(argv: string[]): string {
|
|
77
|
+
async function getCommandHelpText(argv: string[]): Promise<string> {
|
|
93
78
|
if (argv.length === 0) {
|
|
94
79
|
return getCliHelpText()
|
|
95
80
|
}
|
|
@@ -101,30 +86,32 @@ function getCommandHelpText(argv: string[]): string {
|
|
|
101
86
|
}
|
|
102
87
|
|
|
103
88
|
if (command === 'new' && rest.length === 0) {
|
|
89
|
+
let { getNewCommandHelpText } = await import('./new.ts')
|
|
104
90
|
return getNewCommandHelpText()
|
|
105
91
|
}
|
|
106
92
|
|
|
107
93
|
if (command === 'completion' && rest.length === 0) {
|
|
94
|
+
let { getCompletionCommandHelpText } = await import('./completion.ts')
|
|
108
95
|
return getCompletionCommandHelpText()
|
|
109
96
|
}
|
|
110
97
|
|
|
111
98
|
if (command === 'doctor' && rest.length === 0) {
|
|
99
|
+
let { getDoctorCommandHelpText } = await import('./doctor.ts')
|
|
112
100
|
return getDoctorCommandHelpText()
|
|
113
101
|
}
|
|
114
102
|
|
|
115
103
|
if (command === 'routes' && rest.length === 0) {
|
|
104
|
+
let { getRoutesCommandHelpText } = await import('./routes.ts')
|
|
116
105
|
return getRoutesCommandHelpText()
|
|
117
106
|
}
|
|
118
107
|
|
|
119
|
-
if (command === 'skills') {
|
|
120
|
-
return getSkillsHelpText(rest)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
108
|
if (command === 'test' && rest.length === 0) {
|
|
124
|
-
|
|
109
|
+
let { getTestCommandHelpText } = await import('./test.ts')
|
|
110
|
+
return await getTestCommandHelpText()
|
|
125
111
|
}
|
|
126
112
|
|
|
127
113
|
if (command === 'version' && rest.length === 0) {
|
|
114
|
+
let { getVersionCommandHelpText } = await import('./version.ts')
|
|
128
115
|
return getVersionCommandHelpText()
|
|
129
116
|
}
|
|
130
117
|
|
|
@@ -134,24 +121,3 @@ function getCommandHelpText(argv: string[]): string {
|
|
|
134
121
|
function getNestedHelpText(command: string, argv: string[]): string {
|
|
135
122
|
throw unknownHelpTopic(`${command} ${argv.join(' ')}`)
|
|
136
123
|
}
|
|
137
|
-
|
|
138
|
-
function getSkillsHelpText(argv: string[]): string {
|
|
139
|
-
if (argv.length === 0) {
|
|
140
|
-
return getSkillsCommandHelpText()
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
let [subcommand, ...rest] = argv
|
|
144
|
-
if (rest.length > 0) {
|
|
145
|
-
throw unknownHelpTopic(`skills ${argv.join(' ')}`)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (subcommand === 'install') {
|
|
149
|
-
return getSkillsInstallCommandHelpText()
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (subcommand === 'list') {
|
|
153
|
-
return getSkillsListCommandHelpText()
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
throw unknownHelpTopic(`skills ${argv.join(' ')}`)
|
|
157
|
-
}
|
|
@@ -8,7 +8,7 @@ import { createCommandReporter, type TextChannel } from '../reporter.ts'
|
|
|
8
8
|
import { loadRouteMap, type LoadedRouteMap, type RouteTreeNode } from '../route-map.ts'
|
|
9
9
|
import { lightRed } from '../terminal.ts'
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const ACTIONS_PATH_PREFIX = 'app/actions/'
|
|
12
12
|
|
|
13
13
|
export async function runRoutesCommand(argv: string[], context: CliContext): Promise<number> {
|
|
14
14
|
if (argv.includes('-h') || argv.includes('--help')) {
|
|
@@ -243,8 +243,8 @@ function colorRouteLine(line: string, node: RouteTreeNode): string {
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
function getCompactOwnerPath(ownerPath: string): string {
|
|
246
|
-
if (ownerPath.startsWith(
|
|
247
|
-
return ownerPath.slice(
|
|
246
|
+
if (ownerPath.startsWith(ACTIONS_PATH_PREFIX)) {
|
|
247
|
+
return ownerPath.slice(ACTIONS_PATH_PREFIX.length)
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
return ownerPath
|
package/src/lib/commands/test.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getRemixTestHelpText, runRemixTest } from '@remix-run/test/cli'
|
|
2
1
|
import * as process from 'node:process'
|
|
3
2
|
|
|
4
3
|
import type { CliContext } from '../cli-context.ts'
|
|
@@ -6,20 +5,27 @@ import { renderCliError, toCliError } from '../errors.ts'
|
|
|
6
5
|
|
|
7
6
|
export async function runTestCommand(argv: string[], context: CliContext): Promise<number> {
|
|
8
7
|
if (argv.includes('-h') || argv.includes('--help')) {
|
|
9
|
-
process.stdout.write(`${getTestCommandHelpText()}\n`)
|
|
8
|
+
process.stdout.write(`${await getTestCommandHelpText()}\n`)
|
|
10
9
|
return 0
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
try {
|
|
13
|
+
let { runRemixTest } = await import('@remix-run/test/cli')
|
|
14
14
|
return await runRemixTest({ argv, cwd: context.cwd })
|
|
15
15
|
} catch (error) {
|
|
16
|
+
let helpText = await getTestCommandHelpText(process.stderr)
|
|
16
17
|
process.stderr.write(
|
|
17
|
-
renderCliError(toCliError(error), {
|
|
18
|
+
renderCliError(toCliError(error), {
|
|
19
|
+
helpText,
|
|
20
|
+
}),
|
|
18
21
|
)
|
|
19
22
|
return 1
|
|
20
23
|
}
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
export function getTestCommandHelpText(
|
|
26
|
+
export async function getTestCommandHelpText(
|
|
27
|
+
target: NodeJS.WriteStream = process.stdout,
|
|
28
|
+
): Promise<string> {
|
|
29
|
+
let { getRemixTestHelpText } = await import('@remix-run/test/cli')
|
|
24
30
|
return getRemixTestHelpText(target)
|
|
25
31
|
}
|