@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.
Files changed (110) hide show
  1. package/README.md +0 -3
  2. package/dist/lib/bootstrap-project.d.ts.map +1 -1
  3. package/dist/lib/bootstrap-project.js +32 -9
  4. package/dist/lib/cli.d.ts +29 -0
  5. package/dist/lib/cli.d.ts.map +1 -1
  6. package/dist/lib/cli.js +33 -14
  7. package/dist/lib/commands/completion.d.ts.map +1 -1
  8. package/dist/lib/commands/completion.js +5 -1
  9. package/dist/lib/commands/doctor.js +18 -18
  10. package/dist/lib/commands/help.d.ts.map +1 -1
  11. package/dist/lib/commands/help.js +9 -33
  12. package/dist/lib/commands/routes.js +3 -3
  13. package/dist/lib/commands/test.d.ts +1 -1
  14. package/dist/lib/commands/test.d.ts.map +1 -1
  15. package/dist/lib/commands/test.js +8 -4
  16. package/dist/lib/completion.d.ts.map +1 -1
  17. package/dist/lib/completion.js +4 -106
  18. package/dist/lib/controller-files.d.ts +0 -1
  19. package/dist/lib/controller-files.d.ts.map +1 -1
  20. package/dist/lib/controller-files.js +3 -5
  21. package/dist/lib/controller-ownership.d.ts +9 -9
  22. package/dist/lib/controller-ownership.d.ts.map +1 -1
  23. package/dist/lib/controller-ownership.js +56 -91
  24. package/dist/lib/doctor/controller-findings.d.ts +1 -1
  25. package/dist/lib/doctor/controller-findings.d.ts.map +1 -1
  26. package/dist/lib/doctor/controller-findings.js +15 -87
  27. package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -1
  28. package/dist/lib/doctor/controller-fix-plans.js +13 -24
  29. package/dist/lib/doctor/controller-placeholders.d.ts +2 -3
  30. package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -1
  31. package/dist/lib/doctor/controller-placeholders.js +18 -149
  32. package/dist/lib/doctor/controllers.js +1 -1
  33. package/dist/lib/doctor/project.js +60 -52
  34. package/dist/lib/doctor/types.d.ts +2 -2
  35. package/dist/lib/doctor/types.d.ts.map +1 -1
  36. package/dist/lib/errors.d.ts +0 -6
  37. package/dist/lib/errors.d.ts.map +1 -1
  38. package/dist/lib/errors.js +0 -11
  39. package/dist/lib/load-route-map-worker.js +17 -9
  40. package/dist/lib/route-map.d.ts +1 -1
  41. package/dist/lib/route-map.d.ts.map +1 -1
  42. package/dist/lib/route-map.js +29 -17
  43. package/package.json +4 -5
  44. package/src/lib/bootstrap-project.ts +39 -13
  45. package/src/lib/cli.ts +46 -15
  46. package/src/lib/commands/completion.ts +6 -1
  47. package/src/lib/commands/doctor.ts +18 -21
  48. package/src/lib/commands/help.ts +9 -43
  49. package/src/lib/commands/routes.ts +3 -3
  50. package/src/lib/commands/test.ts +10 -4
  51. package/src/lib/completion.ts +4 -151
  52. package/src/lib/controller-files.ts +4 -8
  53. package/src/lib/controller-ownership.ts +78 -141
  54. package/src/lib/doctor/controller-findings.ts +20 -97
  55. package/src/lib/doctor/controller-fix-plans.ts +13 -29
  56. package/src/lib/doctor/controller-placeholders.ts +17 -189
  57. package/src/lib/doctor/controllers.ts +1 -1
  58. package/src/lib/doctor/project.ts +60 -52
  59. package/src/lib/doctor/types.ts +1 -5
  60. package/src/lib/errors.ts +0 -12
  61. package/src/lib/load-route-map-worker.ts +19 -10
  62. package/src/lib/route-map.ts +61 -16
  63. package/template/.agents/skills/remix/SKILL.md +588 -0
  64. package/template/.agents/skills/remix/references/animate-elements.md +195 -0
  65. package/template/.agents/skills/remix/references/assets-and-browser-modules.md +130 -0
  66. package/template/.agents/skills/remix/references/auth-and-sessions.md +443 -0
  67. package/template/.agents/skills/remix/references/component-model.md +282 -0
  68. package/template/.agents/skills/remix/references/create-mixins.md +158 -0
  69. package/template/.agents/skills/remix/references/data-and-validation.md +379 -0
  70. package/template/.agents/skills/remix/references/hydration-frames-navigation.md +297 -0
  71. package/template/.agents/skills/remix/references/middleware-and-server.md +233 -0
  72. package/template/.agents/skills/remix/references/mixins-styling-events.md +213 -0
  73. package/template/.agents/skills/remix/references/routing-and-controllers.md +391 -0
  74. package/template/.agents/skills/remix/references/testing-patterns.md +172 -0
  75. package/{bootstrap → template}/AGENTS.md +13 -8
  76. package/template/README.md +29 -0
  77. package/template/app/actions/controller.tsx +18 -0
  78. package/template/app/assets/entry.ts +8 -0
  79. package/template/app/assets/prompt-button.tsx +163 -0
  80. package/template/app/assets.ts +20 -0
  81. package/template/app/middleware/render.tsx +43 -0
  82. package/template/app/router.ts +20 -0
  83. package/template/app/routes.ts +6 -0
  84. package/template/app/ui/document.tsx +33 -0
  85. package/template/app/ui/scaffold-home-page.tsx +527 -0
  86. package/template/gitignore +4 -0
  87. package/{bootstrap → template}/package.json +5 -6
  88. package/template/public/favicon.svg +11 -0
  89. package/{bootstrap → template}/server.ts +4 -5
  90. package/{bootstrap → template}/tsconfig.json +3 -3
  91. package/bootstrap/README.md +0 -27
  92. package/bootstrap/app/controllers/auth.tsx +0 -21
  93. package/bootstrap/app/controllers/home.tsx +0 -26
  94. package/bootstrap/app/router.ts +0 -10
  95. package/bootstrap/app/routes.ts +0 -6
  96. package/bootstrap/app/ui/document.tsx +0 -21
  97. package/bootstrap/app/ui/layout.tsx +0 -22
  98. package/bootstrap/app/utils/render.tsx +0 -7
  99. package/dist/lib/commands/skills.d.ts +0 -6
  100. package/dist/lib/commands/skills.d.ts.map +0 -1
  101. package/dist/lib/commands/skills.js +0 -222
  102. package/dist/lib/skills-cache.d.ts +0 -19
  103. package/dist/lib/skills-cache.d.ts.map +0 -1
  104. package/dist/lib/skills-cache.js +0 -89
  105. package/dist/lib/skills.d.ts +0 -30
  106. package/dist/lib/skills.d.ts.map +0 -1
  107. package/dist/lib/skills.js +0 -441
  108. package/src/lib/commands/skills.ts +0 -306
  109. package/src/lib/skills-cache.ts +0 -140
  110. package/src/lib/skills.ts +0 -706
@@ -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.subtrees);
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, subtrees, parentSegments = []) {
74
- let subtreesByRouteName = new Map(subtrees.map((subtree) => [subtree.routeName, subtree]));
75
- return decorateRouteTreeWithLookup(rawTree, subtreesByRouteName, parentSegments);
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
- ? rawNode.name
99
+ ? ROOT_ROUTE_NAME
99
100
  : parentSegments.join('.');
100
101
  let subtree = subtreesByRouteName.get(ownerRouteName);
101
- if (subtree == null) {
102
- throw routeOwnerPlanUnresolved(rawNode.name);
102
+ if (subtree != null) {
103
+ return {
104
+ exists: subtree.actualEntryPath != null,
105
+ kind: 'controller',
106
+ path: subtree.actualEntryPath ?? subtree.entryDisplayPath,
107
+ };
103
108
  }
104
- return {
105
- exists: subtree.actualEntryPath != null,
106
- kind: subtree.kind,
107
- path: subtree.actualEntryPath ?? subtree.entryDisplayPath,
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.1.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.2.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.1.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 BOOTSTRAP_DIRECTORY = path.resolve(
16
- path.dirname(fileURLToPath(import.meta.url)),
17
- '../../bootstrap',
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
- copyBootstrapDirectory({
72
- sourceDir: BOOTSTRAP_DIRECTORY,
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 copyBootstrapDirectory({
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 (BOOTSTRAP_EXCLUDED_NAMES.has(entry.name)) {
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 copyBootstrapDirectory({ sourceDir: sourcePath, targetDir: targetPath, templateValues })
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
- while (argv[0] === '--') {
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 || argv[0] === '-h' || argv[0] === '--help') {
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
- controllers: {
30
- complete: 'controllers',
31
- running: 'Checking controllers',
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 controller fixes', label: '--fix' },
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 controllersSuite = createSkippedDoctorSuite(
186
- 'controllers',
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(controllersSuite.name, controllersSuite.reason)
194
- writeDoctorSuiteDetails(reporter, controllersSuite)
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 controllersSuite = createSkippedDoctorSuite('controllers', 'Blocked by project warnings.')
244
- suites.push(controllersSuite)
245
- progress?.skip(controllersSuite.name, controllersSuite.reason)
246
- writeDoctorSuiteDetails(reporter, controllersSuite)
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 controllers = await runDoctorSuite(progress, 'controllers', async () => {
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('controllers', remainingFindings)
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(...controllers.suite.findings)
288
- appliedFixes.push(...(controllers.appliedFixes ?? []))
289
- suites.push(controllers.suite)
290
- writeDoctorSuiteDetails(reporter, controllers.suite)
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 = {
@@ -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
- return getTestCommandHelpText()
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 CONTROLLERS_PATH_PREFIX = 'app/controllers/'
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(CONTROLLERS_PATH_PREFIX)) {
247
- return ownerPath.slice(CONTROLLERS_PATH_PREFIX.length)
246
+ if (ownerPath.startsWith(ACTIONS_PATH_PREFIX)) {
247
+ return ownerPath.slice(ACTIONS_PATH_PREFIX.length)
248
248
  }
249
249
 
250
250
  return ownerPath
@@ -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), { helpText: getTestCommandHelpText(process.stderr) }),
18
+ renderCliError(toCliError(error), {
19
+ helpText,
20
+ }),
18
21
  )
19
22
  return 1
20
23
  }
21
24
  }
22
25
 
23
- export function getTestCommandHelpText(target: NodeJS.WriteStream = process.stdout): string {
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
  }