@remix-run/cli 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +77 -3
- package/bootstrap/.agents/skills/remix/SKILL.md +501 -0
- package/bootstrap/.agents/skills/remix/references/animate-elements.md +195 -0
- package/bootstrap/.agents/skills/remix/references/assets-and-browser-modules.md +122 -0
- package/bootstrap/.agents/skills/remix/references/auth-and-sessions.md +420 -0
- package/bootstrap/.agents/skills/remix/references/component-model.md +282 -0
- package/bootstrap/.agents/skills/remix/references/create-mixins.md +158 -0
- package/bootstrap/.agents/skills/remix/references/data-and-validation.md +363 -0
- package/bootstrap/.agents/skills/remix/references/hydration-frames-navigation.md +297 -0
- package/bootstrap/.agents/skills/remix/references/middleware-and-server.md +243 -0
- package/bootstrap/.agents/skills/remix/references/mixins-styling-events.md +213 -0
- package/bootstrap/.agents/skills/remix/references/routing-and-controllers.md +324 -0
- package/bootstrap/.agents/skills/remix/references/testing-patterns.md +156 -0
- package/bootstrap/AGENTS.md +39 -0
- package/bootstrap/README.md +27 -0
- package/bootstrap/app/assets/entry.ts +19 -0
- package/bootstrap/app/assets.ts +18 -0
- package/bootstrap/app/controllers/auth.tsx +21 -0
- package/bootstrap/app/controllers/home.tsx +11 -0
- package/bootstrap/app/router.ts +16 -0
- package/bootstrap/app/routes.ts +7 -0
- package/bootstrap/app/ui/document.tsx +26 -0
- package/bootstrap/app/ui/layout.tsx +22 -0
- package/bootstrap/app/ui/prompt-button.tsx +162 -0
- package/bootstrap/app/ui/scaffold-home-page.tsx +526 -0
- package/bootstrap/app/utils/render.tsx +26 -0
- package/bootstrap/package.json +22 -0
- package/bootstrap/server.ts +37 -0
- package/bootstrap/tsconfig.json +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/bootstrap-project.d.ts +17 -0
- package/dist/lib/bootstrap-project.d.ts.map +1 -0
- package/dist/lib/bootstrap-project.js +137 -0
- package/dist/lib/cli-context.d.ts +11 -0
- package/dist/lib/cli-context.d.ts.map +1 -0
- package/dist/lib/cli-context.js +19 -0
- package/dist/lib/cli.d.ts +6 -0
- package/dist/lib/cli.d.ts.map +1 -0
- package/dist/lib/cli.js +84 -0
- package/dist/lib/commands/completion.d.ts +3 -0
- package/dist/lib/commands/completion.d.ts.map +1 -0
- package/dist/lib/commands/completion.js +51 -0
- package/dist/lib/commands/doctor.d.ts +4 -0
- package/dist/lib/commands/doctor.d.ts.map +1 -0
- package/dist/lib/commands/doctor.js +293 -0
- package/dist/lib/commands/help.d.ts +4 -0
- package/dist/lib/commands/help.d.ts.map +1 -0
- package/dist/lib/commands/help.js +100 -0
- package/dist/lib/commands/new.d.ts +4 -0
- package/dist/lib/commands/new.d.ts.map +1 -0
- package/dist/lib/commands/new.js +70 -0
- package/dist/lib/commands/routes.d.ts +4 -0
- package/dist/lib/commands/routes.d.ts.map +1 -0
- package/dist/lib/commands/routes.js +161 -0
- package/dist/lib/commands/test.d.ts +4 -0
- package/dist/lib/commands/test.d.ts.map +1 -0
- package/dist/lib/commands/test.js +23 -0
- package/dist/lib/commands/version.d.ts +4 -0
- package/dist/lib/commands/version.d.ts.map +1 -0
- package/dist/lib/commands/version.js +29 -0
- package/dist/lib/completion.d.ts +12 -0
- package/dist/lib/completion.d.ts.map +1 -0
- package/dist/lib/completion.js +310 -0
- package/dist/lib/contained-path.d.ts +2 -0
- package/dist/lib/contained-path.d.ts.map +1 -0
- package/dist/lib/contained-path.js +10 -0
- package/dist/lib/controller-files.d.ts +14 -0
- package/dist/lib/controller-files.d.ts.map +1 -0
- package/dist/lib/controller-files.js +53 -0
- package/dist/lib/controller-ownership.d.ts +41 -0
- package/dist/lib/controller-ownership.d.ts.map +1 -0
- package/dist/lib/controller-ownership.js +224 -0
- package/dist/lib/display-path.d.ts +2 -0
- package/dist/lib/display-path.d.ts.map +1 -0
- package/dist/lib/display-path.js +12 -0
- package/dist/lib/doctor/controller-findings.d.ts +4 -0
- package/dist/lib/doctor/controller-findings.d.ts.map +1 -0
- package/dist/lib/doctor/controller-findings.js +132 -0
- package/dist/lib/doctor/controller-fix-plans.d.ts +4 -0
- package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -0
- package/dist/lib/doctor/controller-fix-plans.js +130 -0
- package/dist/lib/doctor/controller-placeholders.d.ts +4 -0
- package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -0
- package/dist/lib/doctor/controller-placeholders.js +183 -0
- package/dist/lib/doctor/controllers.d.ts +8 -0
- package/dist/lib/doctor/controllers.d.ts.map +1 -0
- package/dist/lib/doctor/controllers.js +13 -0
- package/dist/lib/doctor/environment.d.ts +17 -0
- package/dist/lib/doctor/environment.d.ts.map +1 -0
- package/dist/lib/doctor/environment.js +226 -0
- package/dist/lib/doctor/fixes.d.ts +3 -0
- package/dist/lib/doctor/fixes.d.ts.map +1 -0
- package/dist/lib/doctor/fixes.js +65 -0
- package/dist/lib/doctor/project.d.ts +10 -0
- package/dist/lib/doctor/project.d.ts.map +1 -0
- package/dist/lib/doctor/project.js +371 -0
- package/dist/lib/doctor/types.d.ts +48 -0
- package/dist/lib/doctor/types.d.ts.map +1 -0
- package/dist/lib/doctor/types.js +15 -0
- package/dist/lib/errors.d.ts +167 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +307 -0
- package/dist/lib/help-text.d.ts +13 -0
- package/dist/lib/help-text.d.ts.map +1 -0
- package/dist/lib/help-text.js +43 -0
- package/dist/lib/load-route-map-worker.d.ts +2 -0
- package/dist/lib/load-route-map-worker.d.ts.map +1 -0
- package/dist/lib/load-route-map-worker.js +92 -0
- package/dist/lib/parse-args.d.ts +22 -0
- package/dist/lib/parse-args.d.ts.map +1 -0
- package/dist/lib/parse-args.js +35 -0
- package/dist/lib/remix-version.d.ts +2 -0
- package/dist/lib/remix-version.d.ts.map +1 -0
- package/dist/lib/remix-version.js +58 -0
- package/dist/lib/reporter.d.ts +56 -0
- package/dist/lib/reporter.d.ts.map +1 -0
- package/dist/lib/reporter.js +357 -0
- package/dist/lib/route-map.d.ts +38 -0
- package/dist/lib/route-map.d.ts.map +1 -0
- package/dist/lib/route-map.js +195 -0
- package/dist/lib/terminal.d.ts +17 -0
- package/dist/lib/terminal.d.ts.map +1 -0
- package/dist/lib/terminal.js +66 -0
- package/package.json +47 -5
- package/src/index.ts +1 -0
- package/src/lib/bootstrap-project.ts +222 -0
- package/src/lib/cli-context.ts +38 -0
- package/src/lib/cli.ts +116 -0
- package/src/lib/commands/completion.ts +74 -0
- package/src/lib/commands/doctor.ts +393 -0
- package/src/lib/commands/help.ts +123 -0
- package/src/lib/commands/new.ts +104 -0
- package/src/lib/commands/routes.ts +251 -0
- package/src/lib/commands/test.ts +31 -0
- package/src/lib/commands/version.ts +39 -0
- package/src/lib/completion.ts +418 -0
- package/src/lib/contained-path.ts +13 -0
- package/src/lib/controller-files.ts +79 -0
- package/src/lib/controller-ownership.ts +397 -0
- package/src/lib/display-path.ts +16 -0
- package/src/lib/doctor/controller-findings.ts +157 -0
- package/src/lib/doctor/controller-fix-plans.ts +184 -0
- package/src/lib/doctor/controller-placeholders.ts +242 -0
- package/src/lib/doctor/controllers.ts +23 -0
- package/src/lib/doctor/environment.ts +289 -0
- package/src/lib/doctor/fixes.ts +84 -0
- package/src/lib/doctor/project.ts +435 -0
- package/src/lib/doctor/types.ts +95 -0
- package/src/lib/errors.ts +367 -0
- package/src/lib/help-text.ts +88 -0
- package/src/lib/load-route-map-worker.ts +110 -0
- package/src/lib/parse-args.ts +82 -0
- package/src/lib/remix-version.ts +80 -0
- package/src/lib/reporter.ts +535 -0
- package/src/lib/route-map.ts +303 -0
- package/src/lib/terminal.ts +86 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as process from 'node:process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import semver from 'semver';
|
|
6
|
+
import { appNameUnavailable, invalidPackageName, targetDirectoryNotEmpty, targetPathNotDirectory, } from "./errors.js";
|
|
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']);
|
|
10
|
+
export const MINIMUM_SUPPORTED_NODE_VERSION = '24.3.0';
|
|
11
|
+
export async function bootstrapProject(options, progress) {
|
|
12
|
+
let cwd = options.cwd ?? process.cwd();
|
|
13
|
+
let targetDir = path.resolve(cwd, options.targetDir);
|
|
14
|
+
let rawAppName = options.appName ?? path.basename(targetDir);
|
|
15
|
+
if (rawAppName.length === 0) {
|
|
16
|
+
throw appNameUnavailable(targetDir);
|
|
17
|
+
}
|
|
18
|
+
let config = {
|
|
19
|
+
appDisplayName: options.appName ?? humanizeName(rawAppName),
|
|
20
|
+
packageName: toPackageName(rawAppName),
|
|
21
|
+
remixVersion: readDefaultRemixVersion(options.remixVersion),
|
|
22
|
+
};
|
|
23
|
+
await runProgressStep(progress, 'prepare-target-directory', () => ensureTargetDirectory(targetDir, options.force));
|
|
24
|
+
await runProgressStep(progress, 'generate-scaffold-files', () => copyBootstrapDirectory({
|
|
25
|
+
sourceDir: BOOTSTRAP_DIRECTORY,
|
|
26
|
+
targetDir,
|
|
27
|
+
templateValues: createTemplateValues(config),
|
|
28
|
+
}));
|
|
29
|
+
await runProgressStep(progress, 'finalize-package-json', () => writeScaffoldPackageJson(targetDir, config));
|
|
30
|
+
return {
|
|
31
|
+
appDisplayName: config.appDisplayName,
|
|
32
|
+
targetDir,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function readDefaultRemixVersion(runtimeVersion) {
|
|
36
|
+
let overriddenVersion = process.env.REMIX_VERSION?.trim();
|
|
37
|
+
if (overriddenVersion) {
|
|
38
|
+
return overriddenVersion;
|
|
39
|
+
}
|
|
40
|
+
if (runtimeVersion == null) {
|
|
41
|
+
return 'latest';
|
|
42
|
+
}
|
|
43
|
+
let validRuntimeVersion = semver.valid(runtimeVersion);
|
|
44
|
+
return validRuntimeVersion == null ? runtimeVersion : `^${validRuntimeVersion}`;
|
|
45
|
+
}
|
|
46
|
+
function createTemplateValues(config) {
|
|
47
|
+
return {
|
|
48
|
+
'%%RMX_APP_DISPLAY_NAME%%': config.appDisplayName,
|
|
49
|
+
'%%RMX_APP_DISPLAY_NAME_URI_COMPONENT%%': encodeURIComponent(config.appDisplayName),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function writeScaffoldPackageJson(targetDir, config) {
|
|
53
|
+
let packageJsonPath = path.join(targetDir, 'package.json');
|
|
54
|
+
let packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
55
|
+
packageJson.name = config.packageName;
|
|
56
|
+
packageJson.dependencies = {
|
|
57
|
+
...packageJson.dependencies,
|
|
58
|
+
remix: config.remixVersion,
|
|
59
|
+
tsx: 'latest',
|
|
60
|
+
};
|
|
61
|
+
packageJson.devDependencies = {
|
|
62
|
+
...packageJson.devDependencies,
|
|
63
|
+
'@types/node': 'latest',
|
|
64
|
+
typescript: 'latest',
|
|
65
|
+
};
|
|
66
|
+
packageJson.engines = {
|
|
67
|
+
...packageJson.engines,
|
|
68
|
+
node: `>=${MINIMUM_SUPPORTED_NODE_VERSION}`,
|
|
69
|
+
};
|
|
70
|
+
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
|
|
71
|
+
}
|
|
72
|
+
async function ensureTargetDirectory(targetDir, force) {
|
|
73
|
+
try {
|
|
74
|
+
let stats = await fs.stat(targetDir);
|
|
75
|
+
if (!stats.isDirectory()) {
|
|
76
|
+
throw targetPathNotDirectory(targetDir);
|
|
77
|
+
}
|
|
78
|
+
let entries = await fs.readdir(targetDir);
|
|
79
|
+
if (entries.length > 0 && !force) {
|
|
80
|
+
throw targetDirectoryNotEmpty(targetDir);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
let nodeError = error;
|
|
85
|
+
if (nodeError.code !== 'ENOENT') {
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
async function copyBootstrapDirectory({ sourceDir, targetDir, templateValues, }) {
|
|
92
|
+
let entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
93
|
+
for (let entry of entries) {
|
|
94
|
+
let sourcePath = path.join(sourceDir, entry.name);
|
|
95
|
+
let targetPath = path.join(targetDir, entry.name);
|
|
96
|
+
if (BOOTSTRAP_EXCLUDED_NAMES.has(entry.name)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (entry.isDirectory()) {
|
|
100
|
+
await fs.mkdir(targetPath, { recursive: true });
|
|
101
|
+
await copyBootstrapDirectory({ sourceDir: sourcePath, targetDir: targetPath, templateValues });
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
let content = await fs.readFile(sourcePath, 'utf8');
|
|
105
|
+
await fs.writeFile(targetPath, replaceTemplateValues(content, templateValues), 'utf8');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function replaceTemplateValues(content, templateValues) {
|
|
109
|
+
for (let [token, value] of Object.entries(templateValues)) {
|
|
110
|
+
content = content.split(token).join(value);
|
|
111
|
+
}
|
|
112
|
+
return content;
|
|
113
|
+
}
|
|
114
|
+
function humanizeName(value) {
|
|
115
|
+
let parts = value.split(/[-_\s]+/).filter(Boolean);
|
|
116
|
+
if (parts.length === 0) {
|
|
117
|
+
return 'Remix App';
|
|
118
|
+
}
|
|
119
|
+
return parts
|
|
120
|
+
.map((part) => {
|
|
121
|
+
let head = part.slice(0, 1).toUpperCase();
|
|
122
|
+
let tail = part.slice(1);
|
|
123
|
+
return `${head}${tail}`;
|
|
124
|
+
})
|
|
125
|
+
.join(' ');
|
|
126
|
+
}
|
|
127
|
+
function toPackageName(value) {
|
|
128
|
+
let packageName = value
|
|
129
|
+
.trim()
|
|
130
|
+
.toLowerCase()
|
|
131
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
132
|
+
.replace(/^-+|-+$/g, '');
|
|
133
|
+
if (packageName.length === 0) {
|
|
134
|
+
throw invalidPackageName(value);
|
|
135
|
+
}
|
|
136
|
+
return packageName;
|
|
137
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface ResolveCliContextOptions {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
remixVersion?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface CliContext {
|
|
6
|
+
cwd: string;
|
|
7
|
+
remixVersion?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function resolveCliContext(options?: ResolveCliContextOptions): Promise<CliContext>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=cli-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-context.d.ts","sourceRoot":"","sources":["../../src/lib/cli-context.ts"],"names":[],"mappings":"AAKA,UAAU,wBAAwB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,UAAU,CAAC,CAQrB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import * as process from 'node:process';
|
|
3
|
+
import { resolveDefaultRemixVersion } from "./remix-version.js";
|
|
4
|
+
export async function resolveCliContext(options = {}) {
|
|
5
|
+
let cwd = resolveCwd(options.cwd);
|
|
6
|
+
let remixVersion = normalizeRemixVersion(options.remixVersion);
|
|
7
|
+
return {
|
|
8
|
+
cwd,
|
|
9
|
+
remixVersion: remixVersion ?? (await resolveDefaultRemixVersion(cwd)),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function resolveCwd(cwd) {
|
|
13
|
+
let normalizedCwd = cwd?.trim();
|
|
14
|
+
return path.resolve(normalizedCwd == null || normalizedCwd.length === 0 ? process.cwd() : normalizedCwd);
|
|
15
|
+
}
|
|
16
|
+
function normalizeRemixVersion(remixVersion) {
|
|
17
|
+
let normalizedVersion = remixVersion?.trim();
|
|
18
|
+
return normalizedVersion == null || normalizedVersion.length === 0 ? undefined : normalizedVersion;
|
|
19
|
+
}
|
|
@@ -0,0 +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"}
|
package/dist/lib/cli.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as process from 'node:process';
|
|
2
|
+
import { getCliHelpText, runHelpCommand } from "./commands/help.js";
|
|
3
|
+
import { resolveCliContext } from "./cli-context.js";
|
|
4
|
+
import { renderCliError, unknownCommand } from "./errors.js";
|
|
5
|
+
import { configureColors, restoreTerminalFormatting } from "./terminal.js";
|
|
6
|
+
export async function runRemix(argv = process.argv.slice(2), options = {}) {
|
|
7
|
+
let context = await resolveCliContext(options);
|
|
8
|
+
try {
|
|
9
|
+
while (argv[0] === '--') {
|
|
10
|
+
argv = argv.slice(1);
|
|
11
|
+
}
|
|
12
|
+
let globalOptions = extractGlobalOptions(argv);
|
|
13
|
+
argv = globalOptions.argv;
|
|
14
|
+
configureColors({ disabled: globalOptions.noColor });
|
|
15
|
+
if (argv.length === 0) {
|
|
16
|
+
process.stdout.write(getCliHelpText());
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
let [command, ...rest] = argv;
|
|
20
|
+
if (command === '-h' || command === '--help') {
|
|
21
|
+
process.stdout.write(getCliHelpText());
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
return await runCommand(command, rest, context);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
restoreTerminalFormatting();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function runCommand(command, argv, context) {
|
|
31
|
+
if (command === 'help') {
|
|
32
|
+
return runHelpCommand(argv);
|
|
33
|
+
}
|
|
34
|
+
if (command === '-v' || command === '--version') {
|
|
35
|
+
let { runVersionCommand } = await import("./commands/version.js");
|
|
36
|
+
return runVersionCommand([], context);
|
|
37
|
+
}
|
|
38
|
+
if (command === 'new') {
|
|
39
|
+
let { runNewCommand } = await import("./commands/new.js");
|
|
40
|
+
return runNewCommand(argv, context);
|
|
41
|
+
}
|
|
42
|
+
if (command === 'completion') {
|
|
43
|
+
let { runCompletionCommand } = await import("./commands/completion.js");
|
|
44
|
+
return runCompletionCommand(argv);
|
|
45
|
+
}
|
|
46
|
+
if (command === 'doctor') {
|
|
47
|
+
let { runDoctorCommand } = await import("./commands/doctor.js");
|
|
48
|
+
return runDoctorCommand(argv, context);
|
|
49
|
+
}
|
|
50
|
+
if (command === 'routes') {
|
|
51
|
+
let { runRoutesCommand } = await import("./commands/routes.js");
|
|
52
|
+
return runRoutesCommand(argv, context);
|
|
53
|
+
}
|
|
54
|
+
if (command === 'test') {
|
|
55
|
+
let { runTestCommand } = await import("./commands/test.js");
|
|
56
|
+
return runTestCommand(argv, context);
|
|
57
|
+
}
|
|
58
|
+
if (command === 'version') {
|
|
59
|
+
let { runVersionCommand } = await import("./commands/version.js");
|
|
60
|
+
return runVersionCommand(argv, context);
|
|
61
|
+
}
|
|
62
|
+
process.stderr.write(renderCliError(unknownCommand(command), { helpText: getCliHelpText(process.stderr) }));
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
function extractGlobalOptions(argv) {
|
|
66
|
+
let filteredArgv = [];
|
|
67
|
+
let noColor = false;
|
|
68
|
+
for (let index = 0; index < argv.length; index++) {
|
|
69
|
+
let arg = argv[index];
|
|
70
|
+
if (arg === '--') {
|
|
71
|
+
filteredArgv.push(...argv.slice(index));
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
if (arg === '--no-color') {
|
|
75
|
+
noColor = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
filteredArgv.push(arg);
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
argv: filteredArgv,
|
|
82
|
+
noColor,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as process from 'node:process';
|
|
2
|
+
import { getCompletionResult, getCompletionScript, isCompletionShell, renderCompletionResult, } from "../completion.js";
|
|
3
|
+
import { invalidCompletionRequest, renderCliError, toCliError, unknownCompletionShell, } from "../errors.js";
|
|
4
|
+
import { formatHelpText } from "../help-text.js";
|
|
5
|
+
import { parseArgs } from "../parse-args.js";
|
|
6
|
+
export async function runCompletionCommand(argv) {
|
|
7
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
|
|
8
|
+
process.stdout.write(getCompletionCommandHelpText());
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
if (argv[0] === '--') {
|
|
12
|
+
return runCompletionPlumbing(argv.slice(1));
|
|
13
|
+
}
|
|
14
|
+
let [shell, ...rest] = argv;
|
|
15
|
+
try {
|
|
16
|
+
parseArgs(rest, {}, { maxPositionals: 0 });
|
|
17
|
+
if (!isCompletionShell(shell)) {
|
|
18
|
+
throw unknownCompletionShell(shell);
|
|
19
|
+
}
|
|
20
|
+
process.stdout.write(getCompletionScript());
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
process.stderr.write(renderCliError(toCliError(error), {
|
|
25
|
+
helpText: getCompletionCommandHelpText(process.stderr),
|
|
26
|
+
}));
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function getCompletionCommandHelpText(target = process.stdout) {
|
|
31
|
+
return formatHelpText({
|
|
32
|
+
description: 'Print a shell completion script for Remix.',
|
|
33
|
+
examples: ['remix completion bash >> ~/.bashrc', 'remix completion zsh >> ~/.zshrc'],
|
|
34
|
+
usage: ['remix completion <bash|zsh>'],
|
|
35
|
+
}, target);
|
|
36
|
+
}
|
|
37
|
+
function runCompletionPlumbing(argv) {
|
|
38
|
+
try {
|
|
39
|
+
let [currentIndexArg, ...words] = argv;
|
|
40
|
+
if (currentIndexArg == null || !/^\d+$/.test(currentIndexArg)) {
|
|
41
|
+
throw invalidCompletionRequest();
|
|
42
|
+
}
|
|
43
|
+
let currentIndex = Number(currentIndexArg);
|
|
44
|
+
process.stdout.write(renderCompletionResult(getCompletionResult(words, currentIndex)));
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
process.stderr.write(renderCliError(toCliError(error)));
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CliContext } from '../cli-context.ts';
|
|
2
|
+
export declare function runDoctorCommand(argv: string[], context: CliContext): Promise<number>;
|
|
3
|
+
export declare function getDoctorCommandHelpText(target?: NodeJS.WriteStream): string;
|
|
4
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/doctor.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AA4CnD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA6C3F;AAED,wBAAgB,wBAAwB,CAAC,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAsB5F"}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import * as process from 'node:process';
|
|
3
|
+
import { checkControllerConventions } from "../doctor/controllers.js";
|
|
4
|
+
import { checkEnvironment, getEnvironmentFixPlans } from "../doctor/environment.js";
|
|
5
|
+
import { applyDoctorFixPlans } from "../doctor/fixes.js";
|
|
6
|
+
import { checkProject, getProjectFixPlans } from "../doctor/project.js";
|
|
7
|
+
import { createDoctorSuite, createSkippedDoctorSuite, } from "../doctor/types.js";
|
|
8
|
+
import { renderCliError, toCliError } from "../errors.js";
|
|
9
|
+
import { formatHelpText } from "../help-text.js";
|
|
10
|
+
import { parseArgs } from "../parse-args.js";
|
|
11
|
+
import { createCommandReporter, createStepProgressReporter, } from "../reporter.js";
|
|
12
|
+
const DOCTOR_SUITE_LABELS = {
|
|
13
|
+
controllers: {
|
|
14
|
+
complete: 'controllers',
|
|
15
|
+
running: 'Checking controllers',
|
|
16
|
+
},
|
|
17
|
+
environment: {
|
|
18
|
+
complete: 'environment',
|
|
19
|
+
running: 'Checking environment',
|
|
20
|
+
},
|
|
21
|
+
project: {
|
|
22
|
+
complete: 'project',
|
|
23
|
+
running: 'Checking project',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
export async function runDoctorCommand(argv, context) {
|
|
27
|
+
if (argv.includes('-h') || argv.includes('--help')) {
|
|
28
|
+
process.stdout.write(getDoctorCommandHelpText());
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
let reporter = null;
|
|
32
|
+
let progress = null;
|
|
33
|
+
try {
|
|
34
|
+
let options = parseDoctorCommandArgs(argv);
|
|
35
|
+
reporter = options.json
|
|
36
|
+
? null
|
|
37
|
+
: createCommandReporter({
|
|
38
|
+
remixVersion: context.remixVersion,
|
|
39
|
+
stderr: process.stdout,
|
|
40
|
+
stdout: process.stdout,
|
|
41
|
+
});
|
|
42
|
+
progress = reporter == null ? null : createDoctorProgressReporter(reporter);
|
|
43
|
+
if (reporter != null) {
|
|
44
|
+
await reporter.status.commandHeader('doctor');
|
|
45
|
+
}
|
|
46
|
+
let report = await collectDoctorReport(progress, options, reporter, context);
|
|
47
|
+
if (options.json) {
|
|
48
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
49
|
+
}
|
|
50
|
+
else if (reporter != null) {
|
|
51
|
+
writeDoctorReport(reporter, report);
|
|
52
|
+
reporter.finish();
|
|
53
|
+
}
|
|
54
|
+
if ((options.fix || options.strict) && hasWarningFindings(report.findings)) {
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
progress?.writeSummaryGap();
|
|
61
|
+
process.stderr.write(renderCliError(toCliError(error), { helpText: getDoctorCommandHelpText(process.stderr) }));
|
|
62
|
+
reporter?.finish();
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function getDoctorCommandHelpText(target = process.stdout) {
|
|
67
|
+
return formatHelpText({
|
|
68
|
+
description: 'Check project environment and Remix app conventions for the current project.',
|
|
69
|
+
examples: [
|
|
70
|
+
'remix doctor',
|
|
71
|
+
'remix doctor --json',
|
|
72
|
+
'remix doctor --strict',
|
|
73
|
+
'remix doctor --fix',
|
|
74
|
+
],
|
|
75
|
+
options: [
|
|
76
|
+
{ description: 'Print doctor findings as JSON', label: '--json' },
|
|
77
|
+
{
|
|
78
|
+
description: 'Exit with status 1 when warning-level findings are present',
|
|
79
|
+
label: '--strict',
|
|
80
|
+
},
|
|
81
|
+
{ description: 'Apply low-risk project and controller fixes', label: '--fix' },
|
|
82
|
+
],
|
|
83
|
+
usage: ['remix doctor [--json] [--strict] [--fix] [--no-color]'],
|
|
84
|
+
}, target);
|
|
85
|
+
}
|
|
86
|
+
function parseDoctorCommandArgs(argv) {
|
|
87
|
+
let parsed = parseArgs(argv, {
|
|
88
|
+
fix: { flag: '--fix', type: 'boolean' },
|
|
89
|
+
json: { flag: '--json', type: 'boolean' },
|
|
90
|
+
strict: { flag: '--strict', type: 'boolean' },
|
|
91
|
+
}, { maxPositionals: 0 });
|
|
92
|
+
return {
|
|
93
|
+
fix: parsed.options.fix,
|
|
94
|
+
json: parsed.options.json,
|
|
95
|
+
strict: parsed.options.strict,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async function collectDoctorReport(progress, options, reporter, context) {
|
|
99
|
+
let cwd = context.cwd;
|
|
100
|
+
let appliedFixes = [];
|
|
101
|
+
let environment = await runDoctorSuite(progress, 'environment', async () => {
|
|
102
|
+
let result = await checkEnvironment(cwd);
|
|
103
|
+
let suiteAppliedFixes = [];
|
|
104
|
+
let finalResult = result;
|
|
105
|
+
if (options.fix && result.projectRoot != null) {
|
|
106
|
+
let fixPlans = getEnvironmentFixPlans(result, context.remixVersion);
|
|
107
|
+
if (fixPlans.length > 0) {
|
|
108
|
+
suiteAppliedFixes = await applyDoctorFixPlans(result.projectRoot, fixPlans);
|
|
109
|
+
finalResult = await checkEnvironment(result.projectRoot);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
let suite = finalResult.suite;
|
|
113
|
+
if (suiteAppliedFixes.length > 0) {
|
|
114
|
+
suite = {
|
|
115
|
+
...suite,
|
|
116
|
+
appliedFixes: suiteAppliedFixes,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
appliedFixes: suiteAppliedFixes,
|
|
121
|
+
projectRoot: finalResult.projectRoot ?? result.projectRoot,
|
|
122
|
+
suite,
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
let findings = [...environment.suite.findings];
|
|
126
|
+
appliedFixes.push(...(environment.appliedFixes ?? []));
|
|
127
|
+
let suites = [environment.suite];
|
|
128
|
+
let routesFile = environment.projectRoot == null
|
|
129
|
+
? undefined
|
|
130
|
+
: path.join(environment.projectRoot, 'app', 'routes.ts');
|
|
131
|
+
writeDoctorSuiteDetails(reporter, environment.suite);
|
|
132
|
+
progress?.writeSummaryGap();
|
|
133
|
+
if (hasWarningFindings(environment.suite.findings)) {
|
|
134
|
+
let projectSuite = createSkippedDoctorSuite('project', 'Blocked by environment warnings.');
|
|
135
|
+
let controllersSuite = createSkippedDoctorSuite('controllers', 'Blocked by environment warnings.');
|
|
136
|
+
suites.push(projectSuite, controllersSuite);
|
|
137
|
+
progress?.skip(projectSuite.name, projectSuite.reason);
|
|
138
|
+
writeDoctorSuiteDetails(reporter, projectSuite);
|
|
139
|
+
progress?.writeSummaryGap();
|
|
140
|
+
progress?.skip(controllersSuite.name, controllersSuite.reason);
|
|
141
|
+
writeDoctorSuiteDetails(reporter, controllersSuite);
|
|
142
|
+
progress?.writeSummaryGap();
|
|
143
|
+
return {
|
|
144
|
+
appRoot: environment.projectRoot,
|
|
145
|
+
appliedFixes,
|
|
146
|
+
findings,
|
|
147
|
+
remainingFindings: findings,
|
|
148
|
+
routesFile,
|
|
149
|
+
suites,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
let project = await runDoctorSuite(progress, 'project', async () => {
|
|
153
|
+
let result = await checkProject(environment.projectRoot);
|
|
154
|
+
let suiteAppliedFixes = [];
|
|
155
|
+
let finalResult = result;
|
|
156
|
+
if (options.fix) {
|
|
157
|
+
let fixPlans = await getProjectFixPlans(environment.projectRoot);
|
|
158
|
+
if (fixPlans.length > 0) {
|
|
159
|
+
suiteAppliedFixes = await applyDoctorFixPlans(environment.projectRoot, fixPlans);
|
|
160
|
+
finalResult = await checkProject(environment.projectRoot);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
let suite = finalResult.suite;
|
|
164
|
+
if (suiteAppliedFixes.length > 0) {
|
|
165
|
+
suite = {
|
|
166
|
+
...suite,
|
|
167
|
+
appliedFixes: suiteAppliedFixes,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
appliedFixes: suiteAppliedFixes,
|
|
172
|
+
routeManifest: finalResult.routeManifest,
|
|
173
|
+
routesFile: finalResult.routesFile,
|
|
174
|
+
suite,
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
findings.push(...project.suite.findings);
|
|
178
|
+
appliedFixes.push(...(project.appliedFixes ?? []));
|
|
179
|
+
routesFile = project.routesFile;
|
|
180
|
+
suites.push(project.suite);
|
|
181
|
+
writeDoctorSuiteDetails(reporter, project.suite);
|
|
182
|
+
progress?.writeSummaryGap();
|
|
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);
|
|
188
|
+
progress?.writeSummaryGap();
|
|
189
|
+
return {
|
|
190
|
+
appRoot: environment.projectRoot,
|
|
191
|
+
appliedFixes,
|
|
192
|
+
findings,
|
|
193
|
+
remainingFindings: findings,
|
|
194
|
+
routesFile,
|
|
195
|
+
suites,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
let controllers = await runDoctorSuite(progress, 'controllers', async () => {
|
|
199
|
+
let controllerResult = await checkControllerConventions(project.routeManifest.appRoot, project.routeManifest.tree);
|
|
200
|
+
let remainingFindings = controllerResult.suite.findings;
|
|
201
|
+
let suiteAppliedFixes = [];
|
|
202
|
+
if (options.fix && controllerResult.fixPlans.length > 0) {
|
|
203
|
+
suiteAppliedFixes = await applyDoctorFixPlans(project.routeManifest.appRoot, controllerResult.fixPlans);
|
|
204
|
+
let finalControllerResult = await checkControllerConventions(project.routeManifest.appRoot, project.routeManifest.tree);
|
|
205
|
+
remainingFindings = finalControllerResult.suite.findings;
|
|
206
|
+
}
|
|
207
|
+
let suite = createDoctorSuite('controllers', remainingFindings);
|
|
208
|
+
if (suiteAppliedFixes.length > 0) {
|
|
209
|
+
suite.appliedFixes = suiteAppliedFixes;
|
|
210
|
+
}
|
|
211
|
+
return { appliedFixes: suiteAppliedFixes, suite };
|
|
212
|
+
});
|
|
213
|
+
findings.push(...controllers.suite.findings);
|
|
214
|
+
appliedFixes.push(...(controllers.appliedFixes ?? []));
|
|
215
|
+
suites.push(controllers.suite);
|
|
216
|
+
writeDoctorSuiteDetails(reporter, controllers.suite);
|
|
217
|
+
progress?.writeSummaryGap();
|
|
218
|
+
let report = {
|
|
219
|
+
appRoot: environment.projectRoot,
|
|
220
|
+
findings,
|
|
221
|
+
routesFile,
|
|
222
|
+
suites,
|
|
223
|
+
};
|
|
224
|
+
if (options.fix) {
|
|
225
|
+
report.appliedFixes = appliedFixes;
|
|
226
|
+
report.remainingFindings = findings;
|
|
227
|
+
}
|
|
228
|
+
return report;
|
|
229
|
+
}
|
|
230
|
+
function hasWarningFindings(findings) {
|
|
231
|
+
return findings.some((finding) => finding.severity === 'warn');
|
|
232
|
+
}
|
|
233
|
+
function formatAppliedFix(appliedFix) {
|
|
234
|
+
if (appliedFix.kind === 'update-file') {
|
|
235
|
+
return `Updated ${appliedFix.path}`;
|
|
236
|
+
}
|
|
237
|
+
return `Created ${appliedFix.path}`;
|
|
238
|
+
}
|
|
239
|
+
async function runDoctorSuite(progress, label, callback) {
|
|
240
|
+
progress?.start(label);
|
|
241
|
+
try {
|
|
242
|
+
let result = await callback();
|
|
243
|
+
if (result.suite.status === 'ok') {
|
|
244
|
+
progress?.succeed(label);
|
|
245
|
+
}
|
|
246
|
+
else if (result.suite.status === 'issues') {
|
|
247
|
+
progress?.fail(label);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
progress?.skip(label, result.suite.reason);
|
|
251
|
+
}
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
progress?.fail(label);
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function createDoctorProgressReporter(reporter) {
|
|
260
|
+
return createStepProgressReporter(reporter.status, DOCTOR_SUITE_LABELS);
|
|
261
|
+
}
|
|
262
|
+
function writeDoctorReport(reporter, report) {
|
|
263
|
+
let warningCount = report.findings.filter((finding) => finding.severity === 'warn').length;
|
|
264
|
+
let adviceCount = report.findings.length - warningCount;
|
|
265
|
+
if ((report.appliedFixes?.length ?? 0) > 0) {
|
|
266
|
+
let fixCount = report.appliedFixes.length;
|
|
267
|
+
reporter.out.line(`Applied ${fixCount} ${fixCount === 1 ? 'fix' : 'fixes'}.`);
|
|
268
|
+
}
|
|
269
|
+
if (report.findings.length === 0) {
|
|
270
|
+
reporter.out.line('Doctor found no issues.');
|
|
271
|
+
}
|
|
272
|
+
reporter.out.line(`Summary: ${warningCount} warnings, ${adviceCount} advice.`);
|
|
273
|
+
}
|
|
274
|
+
function writeDoctorSuiteDetails(reporter, suite) {
|
|
275
|
+
if (reporter == null) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (suite.findings.length === 0 && (suite.appliedFixes?.length ?? 0) === 0) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
reporter.out.withIndent(() => {
|
|
282
|
+
for (let finding of suite.findings) {
|
|
283
|
+
reporter.out.bullet(reporter.out.label(finding.severity.toUpperCase(), finding.message, {
|
|
284
|
+
tone: finding.severity === 'warn' ? 'warn' : undefined,
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
if ((suite.appliedFixes?.length ?? 0) > 0) {
|
|
288
|
+
reporter.out.section('Applied fixes:', () => {
|
|
289
|
+
reporter.out.bullets((suite.appliedFixes ?? []).map(formatAppliedFix));
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function runHelpCommand(argv: string[]): Promise<number>;
|
|
2
|
+
export declare function getCliHelpText(target?: NodeJS.WriteStream): string;
|
|
3
|
+
export declare function getHelpCommandHelpText(target?: NodeJS.WriteStream): string;
|
|
4
|
+
//# sourceMappingURL=help.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../../src/lib/commands/help.ts"],"names":[],"mappings":"AAKA,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAepE;AAED,wBAAgB,cAAc,CAAC,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAiClF;AAED,wBAAgB,sBAAsB,CAAC,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,CAiB1F"}
|