@remix-run/cli 0.2.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 (84) hide show
  1. package/dist/lib/bootstrap-project.d.ts.map +1 -1
  2. package/dist/lib/bootstrap-project.js +32 -9
  3. package/dist/lib/cli.d.ts +29 -0
  4. package/dist/lib/cli.d.ts.map +1 -1
  5. package/dist/lib/cli.js +26 -4
  6. package/dist/lib/commands/completion.d.ts.map +1 -1
  7. package/dist/lib/commands/completion.js +5 -1
  8. package/dist/lib/commands/doctor.js +18 -18
  9. package/dist/lib/commands/routes.js +3 -3
  10. package/dist/lib/completion.js +1 -5
  11. package/dist/lib/controller-files.d.ts +0 -1
  12. package/dist/lib/controller-files.d.ts.map +1 -1
  13. package/dist/lib/controller-files.js +3 -5
  14. package/dist/lib/controller-ownership.d.ts +9 -9
  15. package/dist/lib/controller-ownership.d.ts.map +1 -1
  16. package/dist/lib/controller-ownership.js +56 -91
  17. package/dist/lib/doctor/controller-findings.d.ts +1 -1
  18. package/dist/lib/doctor/controller-findings.d.ts.map +1 -1
  19. package/dist/lib/doctor/controller-findings.js +15 -87
  20. package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -1
  21. package/dist/lib/doctor/controller-fix-plans.js +13 -24
  22. package/dist/lib/doctor/controller-placeholders.d.ts +2 -3
  23. package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -1
  24. package/dist/lib/doctor/controller-placeholders.js +18 -149
  25. package/dist/lib/doctor/controllers.js +1 -1
  26. package/dist/lib/doctor/project.js +60 -52
  27. package/dist/lib/doctor/types.d.ts +2 -2
  28. package/dist/lib/doctor/types.d.ts.map +1 -1
  29. package/dist/lib/load-route-map-worker.js +17 -9
  30. package/dist/lib/route-map.d.ts +1 -1
  31. package/dist/lib/route-map.d.ts.map +1 -1
  32. package/dist/lib/route-map.js +29 -17
  33. package/package.json +3 -3
  34. package/src/lib/bootstrap-project.ts +39 -13
  35. package/src/lib/cli.ts +39 -4
  36. package/src/lib/commands/completion.ts +6 -1
  37. package/src/lib/commands/doctor.ts +18 -21
  38. package/src/lib/commands/routes.ts +3 -3
  39. package/src/lib/completion.ts +1 -5
  40. package/src/lib/controller-files.ts +4 -8
  41. package/src/lib/controller-ownership.ts +78 -141
  42. package/src/lib/doctor/controller-findings.ts +20 -97
  43. package/src/lib/doctor/controller-fix-plans.ts +13 -29
  44. package/src/lib/doctor/controller-placeholders.ts +17 -189
  45. package/src/lib/doctor/controllers.ts +1 -1
  46. package/src/lib/doctor/project.ts +60 -52
  47. package/src/lib/doctor/types.ts +1 -5
  48. package/src/lib/load-route-map-worker.ts +19 -10
  49. package/src/lib/route-map.ts +61 -16
  50. package/{bootstrap → template}/.agents/skills/remix/SKILL.md +160 -73
  51. package/{bootstrap → template}/.agents/skills/remix/references/assets-and-browser-modules.md +22 -14
  52. package/{bootstrap → template}/.agents/skills/remix/references/auth-and-sessions.md +41 -18
  53. package/{bootstrap → template}/.agents/skills/remix/references/data-and-validation.md +21 -5
  54. package/{bootstrap → template}/.agents/skills/remix/references/middleware-and-server.md +43 -53
  55. package/{bootstrap → template}/.agents/skills/remix/references/routing-and-controllers.md +111 -44
  56. package/{bootstrap → template}/.agents/skills/remix/references/testing-patterns.md +17 -1
  57. package/{bootstrap → template}/AGENTS.md +10 -9
  58. package/template/README.md +29 -0
  59. package/template/app/actions/controller.tsx +18 -0
  60. package/template/app/assets/entry.ts +8 -0
  61. package/{bootstrap/app/ui → template/app/assets}/prompt-button.tsx +2 -1
  62. package/{bootstrap → template}/app/assets.ts +5 -3
  63. package/template/app/middleware/render.tsx +43 -0
  64. package/template/app/router.ts +20 -0
  65. package/{bootstrap → template}/app/routes.ts +1 -2
  66. package/{bootstrap → template}/app/ui/document.tsx +11 -4
  67. package/{bootstrap → template}/app/ui/scaffold-home-page.tsx +39 -38
  68. package/template/gitignore +4 -0
  69. package/{bootstrap → template}/package.json +5 -6
  70. package/template/public/favicon.svg +11 -0
  71. package/{bootstrap → template}/server.ts +10 -11
  72. package/{bootstrap → template}/tsconfig.json +3 -2
  73. package/bootstrap/README.md +0 -27
  74. package/bootstrap/app/assets/entry.ts +0 -19
  75. package/bootstrap/app/controllers/auth.tsx +0 -21
  76. package/bootstrap/app/controllers/home.tsx +0 -11
  77. package/bootstrap/app/router.ts +0 -16
  78. package/bootstrap/app/ui/layout.tsx +0 -22
  79. package/bootstrap/app/utils/render.tsx +0 -26
  80. /package/{bootstrap → template}/.agents/skills/remix/references/animate-elements.md +0 -0
  81. /package/{bootstrap → template}/.agents/skills/remix/references/component-model.md +0 -0
  82. /package/{bootstrap → template}/.agents/skills/remix/references/create-mixins.md +0 -0
  83. /package/{bootstrap → template}/.agents/skills/remix/references/hydration-frames-navigation.md +0 -0
  84. /package/{bootstrap → template}/.agents/skills/remix/references/mixins-styling-events.md +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap-project.d.ts","sourceRoot":"","sources":["../../src/lib/bootstrap-project.ts"],"names":[],"mappings":"AAYA,OAAO,EAAmB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAO1E,eAAO,MAAM,8BAA8B,WAAW,CAAA;AAEtD,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,qBAAqB,GAC7B,0BAA0B,GAC1B,yBAAyB,GACzB,uBAAuB,CAAA;AAE3B,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,CAAC,qBAAqB,CAAC,CAAA;AAUnF,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,uBAAuB,EAChC,QAAQ,CAAC,EAAE,yBAAyB,GACnC,OAAO,CAAC,mBAAmB,CAAC,CAgC9B"}
1
+ {"version":3,"file":"bootstrap-project.d.ts","sourceRoot":"","sources":["../../src/lib/bootstrap-project.ts"],"names":[],"mappings":"AAYA,OAAO,EAAmB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAM1E,eAAO,MAAM,8BAA8B,WAAW,CAAA;AAEtD,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,qBAAqB,GAC7B,0BAA0B,GAC1B,yBAAyB,GACzB,uBAAuB,CAAA;AAE3B,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,CAAC,qBAAqB,CAAC,CAAA;AAUnF,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,uBAAuB,EAChC,QAAQ,CAAC,EAAE,yBAAyB,GACnC,OAAO,CAAC,mBAAmB,CAAC,CAgC9B"}
@@ -5,8 +5,10 @@ import { fileURLToPath } from 'node:url';
5
5
  import semver from 'semver';
6
6
  import { appNameUnavailable, invalidPackageName, targetDirectoryNotEmpty, targetPathNotDirectory, } from "./errors.js";
7
7
  import { runProgressStep } from "./reporter.js";
8
- const BOOTSTRAP_DIRECTORY = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../bootstrap');
9
- const BOOTSTRAP_EXCLUDED_NAMES = new Set(['.gitkeep', 'node_modules']);
8
+ const MODULE_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
9
+ const PACKAGE_TEMPLATE_DIRECTORY = path.resolve(MODULE_DIRECTORY, '../../template');
10
+ const REPO_TEMPLATE_DIRECTORY = path.resolve(MODULE_DIRECTORY, '../../../../template');
11
+ const TEMPLATE_EXCLUDED_NAMES = new Set(['.gitkeep', 'node_modules']);
10
12
  export const MINIMUM_SUPPORTED_NODE_VERSION = '24.3.0';
11
13
  export async function bootstrapProject(options, progress) {
12
14
  let cwd = options.cwd ?? process.cwd();
@@ -21,8 +23,8 @@ export async function bootstrapProject(options, progress) {
21
23
  remixVersion: readDefaultRemixVersion(options.remixVersion),
22
24
  };
23
25
  await runProgressStep(progress, 'prepare-target-directory', () => ensureTargetDirectory(targetDir, options.force));
24
- await runProgressStep(progress, 'generate-scaffold-files', () => copyBootstrapDirectory({
25
- sourceDir: BOOTSTRAP_DIRECTORY,
26
+ await runProgressStep(progress, 'generate-scaffold-files', async () => copyTemplateDirectory({
27
+ sourceDir: await resolveTemplateDirectory(),
26
28
  targetDir,
27
29
  templateValues: createTemplateValues(config),
28
30
  }));
@@ -49,6 +51,28 @@ function createTemplateValues(config) {
49
51
  '%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%': encodeURIComponent(config.appDisplayName),
50
52
  };
51
53
  }
54
+ async function resolveTemplateDirectory() {
55
+ if (await isDirectory(PACKAGE_TEMPLATE_DIRECTORY)) {
56
+ return PACKAGE_TEMPLATE_DIRECTORY;
57
+ }
58
+ if (await isDirectory(REPO_TEMPLATE_DIRECTORY)) {
59
+ return REPO_TEMPLATE_DIRECTORY;
60
+ }
61
+ throw new Error(`Could not find the Remix app template at ${PACKAGE_TEMPLATE_DIRECTORY} or ${REPO_TEMPLATE_DIRECTORY}.`);
62
+ }
63
+ async function isDirectory(directory) {
64
+ try {
65
+ let stats = await fs.stat(directory);
66
+ return stats.isDirectory();
67
+ }
68
+ catch (error) {
69
+ let nodeError = error;
70
+ if (nodeError.code === 'ENOENT') {
71
+ return false;
72
+ }
73
+ throw error;
74
+ }
75
+ }
52
76
  async function writeScaffoldPackageJson(targetDir, config) {
53
77
  let packageJsonPath = path.join(targetDir, 'package.json');
54
78
  let packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
@@ -56,7 +80,6 @@ async function writeScaffoldPackageJson(targetDir, config) {
56
80
  packageJson.dependencies = {
57
81
  ...packageJson.dependencies,
58
82
  remix: config.remixVersion,
59
- tsx: 'latest',
60
83
  };
61
84
  packageJson.devDependencies = {
62
85
  ...packageJson.devDependencies,
@@ -88,17 +111,17 @@ async function ensureTargetDirectory(targetDir, force) {
88
111
  }
89
112
  await fs.mkdir(targetDir, { recursive: true });
90
113
  }
91
- async function copyBootstrapDirectory({ sourceDir, targetDir, templateValues, }) {
114
+ async function copyTemplateDirectory({ sourceDir, targetDir, templateValues, }) {
92
115
  let entries = await fs.readdir(sourceDir, { withFileTypes: true });
93
116
  for (let entry of entries) {
94
117
  let sourcePath = path.join(sourceDir, entry.name);
95
- let targetPath = path.join(targetDir, entry.name);
96
- if (BOOTSTRAP_EXCLUDED_NAMES.has(entry.name)) {
118
+ let targetPath = path.join(targetDir, entry.name === 'gitignore' ? '.gitignore' : entry.name);
119
+ if (TEMPLATE_EXCLUDED_NAMES.has(entry.name)) {
97
120
  continue;
98
121
  }
99
122
  if (entry.isDirectory()) {
100
123
  await fs.mkdir(targetPath, { recursive: true });
101
- await copyBootstrapDirectory({ sourceDir: sourcePath, targetDir: targetPath, templateValues });
124
+ await copyTemplateDirectory({ sourceDir: sourcePath, targetDir: targetPath, templateValues });
102
125
  continue;
103
126
  }
104
127
  let content = await fs.readFile(sourcePath, 'utf8');
package/dist/lib/cli.d.ts CHANGED
@@ -1,6 +1,35 @@
1
+ /**
2
+ * Options accepted by {@link runRemix}.
3
+ */
1
4
  export interface RunRemixOptions {
5
+ /**
6
+ * Directory used as the working root when resolving project files (default
7
+ * `process.cwd()`).
8
+ */
2
9
  cwd?: string;
10
+ /**
11
+ * Override for the Remix version reported by `remix --version`. Defaults to
12
+ * the version of the installed `@remix-run/cli` package.
13
+ */
3
14
  remixVersion?: string;
4
15
  }
16
+ /**
17
+ * Entry point for the `remix` CLI. Parses `argv`, dispatches to the matching
18
+ * subcommand (`new`, `doctor`, `routes`, `test`, `version`, `completion`,
19
+ * `help`), and resolves with the exit code the process should use.
20
+ *
21
+ * @param argv Argument vector to parse, excluding the node and script paths
22
+ * (default `process.argv.slice(2)`).
23
+ * @param options Overrides for CLI context resolution.
24
+ * @returns The numeric exit code (`0` on success, non-zero on failure).
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import { runRemix } from '@remix-run/cli'
29
+ *
30
+ * let exitCode = await runRemix(process.argv.slice(2))
31
+ * process.exit(exitCode)
32
+ * ```
33
+ */
5
34
  export declare function runRemix(argv?: string[], options?: RunRemixOptions): Promise<number>;
6
35
  //# sourceMappingURL=cli.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,wBAAsB,QAAQ,CAC5B,IAAI,GAAE,MAAM,EAA0B,EACtC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,MAAM,CAAC,CA4BjB"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,GAAE,MAAM,EAA0B,EACtC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,MAAM,CAAC,CA0BjB"}
package/dist/lib/cli.js CHANGED
@@ -3,14 +3,30 @@ import { getCliHelpText, runHelpCommand } from "./commands/help.js";
3
3
  import { resolveCliContext } from "./cli-context.js";
4
4
  import { renderCliError, unknownCommand } from "./errors.js";
5
5
  import { configureColors, restoreTerminalFormatting } from "./terminal.js";
6
+ /**
7
+ * Entry point for the `remix` CLI. Parses `argv`, dispatches to the matching
8
+ * subcommand (`new`, `doctor`, `routes`, `test`, `version`, `completion`,
9
+ * `help`), and resolves with the exit code the process should use.
10
+ *
11
+ * @param argv Argument vector to parse, excluding the node and script paths
12
+ * (default `process.argv.slice(2)`).
13
+ * @param options Overrides for CLI context resolution.
14
+ * @returns The numeric exit code (`0` on success, non-zero on failure).
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { runRemix } from '@remix-run/cli'
19
+ *
20
+ * let exitCode = await runRemix(process.argv.slice(2))
21
+ * process.exit(exitCode)
22
+ * ```
23
+ */
6
24
  export async function runRemix(argv = process.argv.slice(2), options = {}) {
7
25
  let context = await resolveCliContext(options);
8
26
  try {
9
- while (argv[0] === '--') {
10
- argv = argv.slice(1);
11
- }
27
+ argv = stripLeadingSeparators(argv);
12
28
  let globalOptions = extractGlobalOptions(argv);
13
- argv = globalOptions.argv;
29
+ argv = stripLeadingSeparators(globalOptions.argv);
14
30
  configureColors({ disabled: globalOptions.noColor });
15
31
  if (argv.length === 0) {
16
32
  process.stdout.write(getCliHelpText());
@@ -27,6 +43,12 @@ export async function runRemix(argv = process.argv.slice(2), options = {}) {
27
43
  restoreTerminalFormatting();
28
44
  }
29
45
  }
46
+ function stripLeadingSeparators(argv) {
47
+ while (argv[0] === '--') {
48
+ argv = argv.slice(1);
49
+ }
50
+ return argv;
51
+ }
30
52
  async function runCommand(command, argv, context) {
31
53
  if (command === 'help') {
32
54
  return runHelpCommand(argv);
@@ -1 +1 @@
1
- {"version":3,"file":"completion.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/completion.ts"],"names":[],"mappings":"AAiBA,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA6B1E;AAED,wBAAgB,4BAA4B,CAAC,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAShG"}
1
+ {"version":3,"file":"completion.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/completion.ts"],"names":[],"mappings":"AAiBA,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAkC1E;AAED,wBAAgB,4BAA4B,CAAC,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAShG"}
@@ -4,13 +4,17 @@ import { invalidCompletionRequest, renderCliError, toCliError, unknownCompletion
4
4
  import { formatHelpText } from "../help-text.js";
5
5
  import { parseArgs } from "../parse-args.js";
6
6
  export async function runCompletionCommand(argv) {
7
- if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
7
+ if (argv.length === 0) {
8
8
  process.stdout.write(getCompletionCommandHelpText());
9
9
  return 0;
10
10
  }
11
11
  if (argv[0] === '--') {
12
12
  return runCompletionPlumbing(argv.slice(1));
13
13
  }
14
+ if (argv.includes('-h') || argv.includes('--help')) {
15
+ process.stdout.write(getCompletionCommandHelpText());
16
+ return 0;
17
+ }
14
18
  let [shell, ...rest] = argv;
15
19
  try {
16
20
  parseArgs(rest, {}, { maxPositionals: 0 });
@@ -10,9 +10,9 @@ import { formatHelpText } from "../help-text.js";
10
10
  import { parseArgs } from "../parse-args.js";
11
11
  import { createCommandReporter, createStepProgressReporter, } from "../reporter.js";
12
12
  const DOCTOR_SUITE_LABELS = {
13
- controllers: {
14
- complete: 'controllers',
15
- running: 'Checking controllers',
13
+ actions: {
14
+ complete: 'actions',
15
+ running: 'Checking actions',
16
16
  },
17
17
  environment: {
18
18
  complete: 'environment',
@@ -78,7 +78,7 @@ export function getDoctorCommandHelpText(target = process.stdout) {
78
78
  description: 'Exit with status 1 when warning-level findings are present',
79
79
  label: '--strict',
80
80
  },
81
- { description: 'Apply low-risk project and controller fixes', label: '--fix' },
81
+ { description: 'Apply low-risk project and action fixes', label: '--fix' },
82
82
  ],
83
83
  usage: ['remix doctor [--json] [--strict] [--fix] [--no-color]'],
84
84
  }, target);
@@ -132,13 +132,13 @@ async function collectDoctorReport(progress, options, reporter, context) {
132
132
  progress?.writeSummaryGap();
133
133
  if (hasWarningFindings(environment.suite.findings)) {
134
134
  let projectSuite = createSkippedDoctorSuite('project', 'Blocked by environment warnings.');
135
- let controllersSuite = createSkippedDoctorSuite('controllers', 'Blocked by environment warnings.');
136
- suites.push(projectSuite, controllersSuite);
135
+ let actionsSuite = createSkippedDoctorSuite('actions', 'Blocked by environment warnings.');
136
+ suites.push(projectSuite, actionsSuite);
137
137
  progress?.skip(projectSuite.name, projectSuite.reason);
138
138
  writeDoctorSuiteDetails(reporter, projectSuite);
139
139
  progress?.writeSummaryGap();
140
- progress?.skip(controllersSuite.name, controllersSuite.reason);
141
- writeDoctorSuiteDetails(reporter, controllersSuite);
140
+ progress?.skip(actionsSuite.name, actionsSuite.reason);
141
+ writeDoctorSuiteDetails(reporter, actionsSuite);
142
142
  progress?.writeSummaryGap();
143
143
  return {
144
144
  appRoot: environment.projectRoot,
@@ -181,10 +181,10 @@ async function collectDoctorReport(progress, options, reporter, context) {
181
181
  writeDoctorSuiteDetails(reporter, project.suite);
182
182
  progress?.writeSummaryGap();
183
183
  if (hasWarningFindings(project.suite.findings)) {
184
- let controllersSuite = createSkippedDoctorSuite('controllers', 'Blocked by project warnings.');
185
- suites.push(controllersSuite);
186
- progress?.skip(controllersSuite.name, controllersSuite.reason);
187
- writeDoctorSuiteDetails(reporter, controllersSuite);
184
+ let actionsSuite = createSkippedDoctorSuite('actions', 'Blocked by project warnings.');
185
+ suites.push(actionsSuite);
186
+ progress?.skip(actionsSuite.name, actionsSuite.reason);
187
+ writeDoctorSuiteDetails(reporter, actionsSuite);
188
188
  progress?.writeSummaryGap();
189
189
  return {
190
190
  appRoot: environment.projectRoot,
@@ -195,7 +195,7 @@ async function collectDoctorReport(progress, options, reporter, context) {
195
195
  suites,
196
196
  };
197
197
  }
198
- let controllers = await runDoctorSuite(progress, 'controllers', async () => {
198
+ let actions = await runDoctorSuite(progress, 'actions', async () => {
199
199
  let controllerResult = await checkControllerConventions(project.routeManifest.appRoot, project.routeManifest.tree);
200
200
  let remainingFindings = controllerResult.suite.findings;
201
201
  let suiteAppliedFixes = [];
@@ -204,16 +204,16 @@ async function collectDoctorReport(progress, options, reporter, context) {
204
204
  let finalControllerResult = await checkControllerConventions(project.routeManifest.appRoot, project.routeManifest.tree);
205
205
  remainingFindings = finalControllerResult.suite.findings;
206
206
  }
207
- let suite = createDoctorSuite('controllers', remainingFindings);
207
+ let suite = createDoctorSuite('actions', remainingFindings);
208
208
  if (suiteAppliedFixes.length > 0) {
209
209
  suite.appliedFixes = suiteAppliedFixes;
210
210
  }
211
211
  return { appliedFixes: suiteAppliedFixes, suite };
212
212
  });
213
- findings.push(...controllers.suite.findings);
214
- appliedFixes.push(...(controllers.appliedFixes ?? []));
215
- suites.push(controllers.suite);
216
- writeDoctorSuiteDetails(reporter, controllers.suite);
213
+ findings.push(...actions.suite.findings);
214
+ appliedFixes.push(...(actions.appliedFixes ?? []));
215
+ suites.push(actions.suite);
216
+ writeDoctorSuiteDetails(reporter, actions.suite);
217
217
  progress?.writeSummaryGap();
218
218
  let report = {
219
219
  appRoot: environment.projectRoot,
@@ -5,7 +5,7 @@ import { parseArgs } from "../parse-args.js";
5
5
  import { createCommandReporter } from "../reporter.js";
6
6
  import { loadRouteMap } from "../route-map.js";
7
7
  import { lightRed } from "../terminal.js";
8
- const CONTROLLERS_PATH_PREFIX = 'app/controllers/';
8
+ const ACTIONS_PATH_PREFIX = 'app/actions/';
9
9
  export async function runRoutesCommand(argv, context) {
10
10
  if (argv.includes('-h') || argv.includes('--help')) {
11
11
  process.stdout.write(getRoutesCommandHelpText());
@@ -154,8 +154,8 @@ function colorRouteLine(line, node) {
154
154
  return node.owner.exists ? line : lightRed(line);
155
155
  }
156
156
  function getCompactOwnerPath(ownerPath) {
157
- if (ownerPath.startsWith(CONTROLLERS_PATH_PREFIX)) {
158
- return ownerPath.slice(CONTROLLERS_PATH_PREFIX.length);
157
+ if (ownerPath.startsWith(ACTIONS_PATH_PREFIX)) {
158
+ return ownerPath.slice(ACTIONS_PATH_PREFIX.length);
159
159
  }
160
160
  return ownerPath;
161
161
  }
@@ -137,11 +137,7 @@ function completeCommand(command, tokens, currentWord, usedGlobalFlags) {
137
137
  return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, []);
138
138
  }
139
139
  if (command === 'test') {
140
- return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, [
141
- '--coverage',
142
- '--watch',
143
- '--help',
144
- ]);
140
+ return completeSimpleFlags(tokens, currentWord, usedGlobalFlags, ['--coverage', '--watch']);
145
141
  }
146
142
  if (command === 'completion') {
147
143
  return completeCompletionCommand(tokens, currentWord, usedGlobalFlags);
@@ -1,6 +1,5 @@
1
1
  declare const OWNER_FILE_EXTENSIONS: readonly [".ts", ".tsx", ".js", ".jsx"];
2
2
  export type OwnerFileExtension = (typeof OWNER_FILE_EXTENSIONS)[number];
3
- export declare function getActionOwnerCandidates(segments: string[]): string[];
4
3
  export declare function getControllerOwnerCandidates(segments: string[]): string[];
5
4
  export declare function getRouteSubtreePath(segments: string[]): string;
6
5
  export declare function getPreferredOwnerDisplayPath(candidates: string[]): string;
@@ -1 +1 @@
1
- {"version":3,"file":"controller-files.d.ts","sourceRoot":"","sources":["../../src/lib/controller-files.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,qBAAqB,yCAA0C,CAAA;AACrE,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAIrE;AAED,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAIzE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAE9D;AAED,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAGzE;AAED,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAAE,EACpB,SAAS,EAAE,kBAAkB,GAC5B,MAAM,GAAG,IAAI,CAEf;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAEjF;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAYrD;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQtE;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAS1D"}
1
+ {"version":3,"file":"controller-files.d.ts","sourceRoot":"","sources":["../../src/lib/controller-files.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,qBAAqB,yCAA0C,CAAA;AAGrE,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAIzE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAE9D;AAED,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAGzE;AAED,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAAE,EACpB,SAAS,EAAE,kBAAkB,GAC5B,MAAM,GAAG,IAAI,CAEf;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAEjF;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAYrD;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQtE;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAS1D"}
@@ -1,13 +1,11 @@
1
1
  import * as path from 'node:path';
2
2
  const OWNER_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
3
- export function getActionOwnerCandidates(segments) {
4
- return OWNER_FILE_EXTENSIONS.map((extension) => normalizeRelativePath(path.join('app', 'controllers', `${path.join(...segments)}${extension}`)));
5
- }
3
+ const ACTIONS_DIRECTORY = path.join('app', 'actions');
6
4
  export function getControllerOwnerCandidates(segments) {
7
- return OWNER_FILE_EXTENSIONS.map((extension) => normalizeRelativePath(path.join('app', 'controllers', ...segments, `controller${extension}`)));
5
+ return OWNER_FILE_EXTENSIONS.map((extension) => normalizeRelativePath(path.join(ACTIONS_DIRECTORY, ...segments, `controller${extension}`)));
8
6
  }
9
7
  export function getRouteSubtreePath(segments) {
10
- return normalizeRelativePath(path.join('app', 'controllers', ...segments));
8
+ return normalizeRelativePath(path.join(ACTIONS_DIRECTORY, ...segments));
11
9
  }
12
10
  export function getPreferredOwnerDisplayPath(candidates) {
13
11
  let tsxCandidate = candidates.find((candidate) => candidate.endsWith('.tsx'));
@@ -1,4 +1,5 @@
1
- import type { RouteOwnerKind, RouteTreeNodeKind } from './route-map.ts';
1
+ import type { RouteTreeNodeKind } from './route-map.ts';
2
+ export declare const ROOT_ROUTE_NAME = "<root>";
2
3
  export interface OwnershipRouteNode {
3
4
  children: OwnershipRouteNode[];
4
5
  key: string;
@@ -7,35 +8,34 @@ export interface OwnershipRouteNode {
7
8
  name: string;
8
9
  }
9
10
  export interface ControllerDirectoryScan {
10
- actionEntryPaths: Set<string>;
11
11
  controllerEntryPaths: Set<string>;
12
- rootDirectoryPaths: Set<string>;
12
+ routeDirectoryPaths: Set<string>;
13
13
  routeLocalFilePaths: Set<string>;
14
14
  }
15
+ export interface RouteDirectoryPlan {
16
+ directoryPath: string;
17
+ routeName: string;
18
+ }
15
19
  export interface OwnedSubtreePlan {
16
- alternateCandidates: string[];
17
- alternateDisplayPath: string;
18
20
  entryCandidates: string[];
19
21
  entryDisplayPath: string;
20
- kind: RouteOwnerKind;
21
22
  routeName: string;
22
23
  subtreePath: string;
23
24
  }
24
25
  export interface OwnedSubtree extends OwnedSubtreePlan {
25
- actualAlternatePath: string | null;
26
- actualAlternatePaths: string[];
27
26
  actualEntryPath: string | null;
28
27
  actualEntryPaths: string[];
29
28
  claimedFilePaths: string[];
30
29
  claimedRouteLocalFilePaths: string[];
31
30
  }
32
31
  export interface ControllerOwnership {
33
- orphanActionPaths: string[];
34
32
  orphanControllerPaths: string[];
35
33
  orphanRouteDirectoryPaths: string[];
34
+ routeDirectories: RouteDirectoryPlan[];
36
35
  scan: ControllerDirectoryScan;
37
36
  subtrees: OwnedSubtree[];
38
37
  }
39
38
  export declare function inspectControllerOwnership(appRoot: string, tree: OwnershipRouteNode[]): Promise<ControllerOwnership>;
40
39
  export declare function buildOwnedSubtrees(tree: OwnershipRouteNode[], parentSegments?: string[], subtrees?: OwnedSubtreePlan[]): OwnedSubtreePlan[];
40
+ export declare function buildRouteDirectories(tree: OwnershipRouteNode[], parentSegments?: string[], directories?: RouteDirectoryPlan[]): RouteDirectoryPlan[];
41
41
  //# sourceMappingURL=controller-ownership.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"controller-ownership.d.ts","sourceRoot":"","sources":["../../src/lib/controller-ownership.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAEvE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,kBAAkB,EAAE,CAAA;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,iBAAiB,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,uBAAuB;IACtC,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC7B,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACjC,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/B,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,mBAAmB,EAAE,MAAM,EAAE,CAAA;IAC7B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,gBAAgB,EAAE,MAAM,CAAA;IACxB,IAAI,EAAE,cAAc,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,YAAa,SAAQ,gBAAgB;IACpD,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,oBAAoB,EAAE,MAAM,EAAE,CAAA;IAC9B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,0BAA0B,EAAE,MAAM,EAAE,CAAA;CACrC;AAED,MAAM,WAAW,mBAAmB;IAClC,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,yBAAyB,EAAE,MAAM,EAAE,CAAA;IACnC,IAAI,EAAE,uBAAuB,CAAA;IAC7B,QAAQ,EAAE,YAAY,EAAE,CAAA;CACzB;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,kBAAkB,EAAE,GACzB,OAAO,CAAC,mBAAmB,CAAC,CAY9B;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,kBAAkB,EAAE,EAC1B,cAAc,GAAE,MAAM,EAAO,EAC7B,QAAQ,GAAE,gBAAgB,EAAO,GAChC,gBAAgB,EAAE,CAwCpB"}
1
+ {"version":3,"file":"controller-ownership.d.ts","sourceRoot":"","sources":["../../src/lib/controller-ownership.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAIvD,eAAO,MAAM,eAAe,WAAW,CAAA;AAEvC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,kBAAkB,EAAE,CAAA;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,iBAAiB,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,uBAAuB;IACtC,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACjC,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAChC,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,YAAa,SAAQ,gBAAgB;IACpD,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,0BAA0B,EAAE,MAAM,EAAE,CAAA;CACrC;AAED,MAAM,WAAW,mBAAmB;IAClC,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,yBAAyB,EAAE,MAAM,EAAE,CAAA;IACnC,gBAAgB,EAAE,kBAAkB,EAAE,CAAA;IACtC,IAAI,EAAE,uBAAuB,CAAA;IAC7B,QAAQ,EAAE,YAAY,EAAE,CAAA;CACzB;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,kBAAkB,EAAE,GACzB,OAAO,CAAC,mBAAmB,CAAC,CAa9B;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,kBAAkB,EAAE,EAC1B,cAAc,GAAE,MAAM,EAAO,EAC7B,QAAQ,GAAE,gBAAgB,EAAO,GAChC,gBAAgB,EAAE,CAsBpB;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,kBAAkB,EAAE,EAC1B,cAAc,GAAE,MAAM,EAAO,EAC7B,WAAW,GAAE,kBAAkB,EAAO,GACrC,kBAAkB,EAAE,CAetB"}