react-doctor-cli-dev 1.0.2 → 1.0.3
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/backend/data/reports.db +0 -0
- package/cli/dist/commands/dashboard.js +152 -0
- package/cli/dist/index.js +24 -50
- package/cli/src/commands/dashboard.ts +179 -0
- package/cli/src/index.ts +33 -60
- package/package.json +1 -1
|
Binary file
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─────────────────────────────────────────────────────────────
|
|
3
|
+
// cli/src/commands/dashboard.ts
|
|
4
|
+
//
|
|
5
|
+
// react-doctor dashboard
|
|
6
|
+
//
|
|
7
|
+
// Opens the React Doctor dashboard in the browser.
|
|
8
|
+
//
|
|
9
|
+
// WHAT IT DOES:
|
|
10
|
+
// 1. Checks if the backend is already running on the port
|
|
11
|
+
// 2. If not — starts it automatically (same logic as --upload)
|
|
12
|
+
// 3. Opens http://localhost:PORT in the default browser
|
|
13
|
+
//
|
|
14
|
+
// This command is the natural companion to --upload.
|
|
15
|
+
// Workflow:
|
|
16
|
+
// react-doctor full ./my-app --upload ← runs analysis + saves report
|
|
17
|
+
// react-doctor dashboard ← opens the dashboard to view it
|
|
18
|
+
//
|
|
19
|
+
// Or in one shot:
|
|
20
|
+
// react-doctor full ./my-app --upload && react-doctor dashboard
|
|
21
|
+
// ─────────────────────────────────────────────────────────────
|
|
22
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.registerDashboardCommand = registerDashboardCommand;
|
|
27
|
+
const path_1 = __importDefault(require("path"));
|
|
28
|
+
const fs_1 = __importDefault(require("fs"));
|
|
29
|
+
const axios_1 = __importDefault(require("axios"));
|
|
30
|
+
const child_process_1 = require("child_process");
|
|
31
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
32
|
+
const ui_1 = require("../ui");
|
|
33
|
+
function registerDashboardCommand(program) {
|
|
34
|
+
program
|
|
35
|
+
.command("dashboard")
|
|
36
|
+
.description("Open the React Doctor dashboard (auto-starts backend if needed)")
|
|
37
|
+
.option("--port <port>", "Port the backend runs on", "3000")
|
|
38
|
+
.option("--api-key <key>", "API key for the backend", process.env.REACT_DOCTOR_API_KEY || "react-doctor-secret-key-change-this")
|
|
39
|
+
.option("--no-banner", "Skip the banner")
|
|
40
|
+
.action(async (options) => {
|
|
41
|
+
if (!options.noBanner)
|
|
42
|
+
(0, ui_1.printBanner)();
|
|
43
|
+
const port = options.port;
|
|
44
|
+
const apiUrl = `http://localhost:${port}`;
|
|
45
|
+
(0, ui_1.printSection)("Dashboard");
|
|
46
|
+
(0, ui_1.printInfo)("Backend URL", apiUrl);
|
|
47
|
+
console.log();
|
|
48
|
+
const spin = (0, ui_1.spinner)("Checking backend status...");
|
|
49
|
+
try {
|
|
50
|
+
// ── 1. Check if backend is already up ─────────────────
|
|
51
|
+
let backendRunning = false;
|
|
52
|
+
try {
|
|
53
|
+
await axios_1.default.get(`${apiUrl}/health`, { timeout: 2000 });
|
|
54
|
+
backendRunning = true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
backendRunning = false;
|
|
58
|
+
}
|
|
59
|
+
// ── 2. Start backend if not running ───────────────────
|
|
60
|
+
if (!backendRunning) {
|
|
61
|
+
spin.text = " Backend not running — starting automatically...";
|
|
62
|
+
// Locate backend folder (sibling of cli/)
|
|
63
|
+
const projectRoot = path_1.default.resolve(__dirname, "..", "..", "..");
|
|
64
|
+
const backendRoot = path_1.default.resolve(projectRoot, "backend");
|
|
65
|
+
const backendDist = path_1.default.join(backendRoot, "dist", "index.js");
|
|
66
|
+
const backendSrc = path_1.default.join(backendRoot, "src", "index.ts");
|
|
67
|
+
let command;
|
|
68
|
+
let args;
|
|
69
|
+
if (fs_1.default.existsSync(backendDist)) {
|
|
70
|
+
command = "node";
|
|
71
|
+
args = [backendDist];
|
|
72
|
+
}
|
|
73
|
+
else if (fs_1.default.existsSync(backendSrc)) {
|
|
74
|
+
command = "npx";
|
|
75
|
+
args = ["ts-node", backendSrc];
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
spin.fail(chalk_1.default.red("Backend not found"));
|
|
79
|
+
(0, ui_1.printFail)(`Could not find backend at: ${backendRoot}\n\n` +
|
|
80
|
+
` Make sure the 'backend/' folder exists next to 'cli/'.`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
// Create data dir inside npm global cache for the backend DB
|
|
84
|
+
const dataDir = path_1.default.join(backendRoot, "data");
|
|
85
|
+
fs_1.default.mkdirSync(dataDir, { recursive: true });
|
|
86
|
+
(0, child_process_1.spawn)(command, args, {
|
|
87
|
+
stdio: "ignore",
|
|
88
|
+
detached: true,
|
|
89
|
+
env: {
|
|
90
|
+
...process.env,
|
|
91
|
+
API_KEY: options.apiKey,
|
|
92
|
+
PORT: port,
|
|
93
|
+
DB_PATH: path_1.default.join(dataDir, "reports.db"),
|
|
94
|
+
},
|
|
95
|
+
cwd: backendRoot,
|
|
96
|
+
}).unref(); // let CLI exit without killing the server
|
|
97
|
+
// Wait for backend to be ready (up to 15 seconds)
|
|
98
|
+
let ready = false;
|
|
99
|
+
let retries = 0;
|
|
100
|
+
while (!ready && retries < 15) {
|
|
101
|
+
try {
|
|
102
|
+
await axios_1.default.get(`${apiUrl}/health`, { timeout: 1000 });
|
|
103
|
+
ready = true;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
107
|
+
retries++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!ready) {
|
|
111
|
+
spin.fail(chalk_1.default.red("Backend failed to start"));
|
|
112
|
+
(0, ui_1.printFail)("Backend did not respond after 15 seconds.");
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
spin.succeed(chalk_1.default.green("Backend started successfully"));
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
spin.succeed(chalk_1.default.green("Backend already running"));
|
|
119
|
+
}
|
|
120
|
+
// ── 3. Open dashboard in default browser ──────────────
|
|
121
|
+
const dashboardUrl = apiUrl;
|
|
122
|
+
console.log();
|
|
123
|
+
(0, ui_1.printInfo)("Opening", dashboardUrl);
|
|
124
|
+
console.log();
|
|
125
|
+
// Cross-platform browser open
|
|
126
|
+
const openCmd = process.platform === "win32" ? ["cmd", ["/c", "start", dashboardUrl]] :
|
|
127
|
+
process.platform === "darwin" ? ["open", [dashboardUrl]] :
|
|
128
|
+
["xdg-open", [dashboardUrl]];
|
|
129
|
+
(0, child_process_1.spawn)(openCmd[0], openCmd[1], {
|
|
130
|
+
stdio: "ignore",
|
|
131
|
+
detached: true,
|
|
132
|
+
}).unref();
|
|
133
|
+
(0, ui_1.printDone)(`Dashboard opened at ${chalk_1.default.cyan(dashboardUrl)}`);
|
|
134
|
+
// ── 4. Show quick API reference ───────────────────────
|
|
135
|
+
console.log(chalk_1.default.gray(" Available endpoints:"));
|
|
136
|
+
console.log(chalk_1.default.cyan(` GET ${apiUrl}/health`));
|
|
137
|
+
console.log(chalk_1.default.cyan(` GET ${apiUrl}/api/reports`));
|
|
138
|
+
console.log(chalk_1.default.cyan(` GET ${apiUrl}/api/reports/:id`));
|
|
139
|
+
console.log(chalk_1.default.cyan(` GET ${apiUrl}/api/reports/project/:name`));
|
|
140
|
+
console.log(chalk_1.default.cyan(` POST ${apiUrl}/api/reports/upload`));
|
|
141
|
+
console.log();
|
|
142
|
+
console.log(chalk_1.default.gray(" Tip: run ") +
|
|
143
|
+
chalk_1.default.cyan("react-doctor full ./ --upload") +
|
|
144
|
+
chalk_1.default.gray(" to add a new report.\n"));
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
spin.fail(chalk_1.default.red("Dashboard failed to open"));
|
|
148
|
+
console.log(chalk_1.default.red(`\n ${err.message}\n`));
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
package/cli/dist/index.js
CHANGED
|
@@ -1,77 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
// ─────────────────────────────────────────────────────────────
|
|
4
|
-
// cli/src/index.ts
|
|
5
|
-
//
|
|
6
|
-
// The CLI entry point. This is the file that runs when the
|
|
7
|
-
// user types "react-doctor" in their terminal.
|
|
8
|
-
//
|
|
9
|
-
// HOW IT WORKS:
|
|
10
|
-
// 1. Commander.js parses the command and flags from argv
|
|
11
|
-
// 2. The matching command handler is called
|
|
12
|
-
// 3. The handler imports core modules and runs the pipeline
|
|
13
|
-
//
|
|
14
|
-
// HOW THE BINARY REGISTRATION WORKS:
|
|
15
|
-
// package.json has a "bin" field:
|
|
16
|
-
// "bin": { "react-doctor": "./dist/index.js" }
|
|
17
|
-
//
|
|
18
|
-
// After "npm link" (dev) or "npm install" (production),
|
|
19
|
-
// npm creates a symlink from the system's bin directory
|
|
20
|
-
// to this file. That's what makes "react-doctor" a real
|
|
21
|
-
// terminal command available anywhere.
|
|
22
|
-
//
|
|
23
|
-
// THE SHEBANG (#!/usr/bin/env node) on line 1:
|
|
24
|
-
// This tells the OS to run this file with Node.js when
|
|
25
|
-
// called directly as a script. Without it, the OS doesn't
|
|
26
|
-
// know which interpreter to use.
|
|
4
|
+
// cli/src/index.ts — CLI entry point
|
|
27
5
|
// ─────────────────────────────────────────────────────────────
|
|
28
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
7
|
const commander_1 = require("commander");
|
|
30
8
|
const analyze_1 = require("./commands/analyze");
|
|
31
9
|
const profile_1 = require("./commands/profile");
|
|
32
10
|
const full_1 = require("./commands/full");
|
|
33
|
-
const
|
|
11
|
+
const dashboard_1 = require("./commands/dashboard");
|
|
34
12
|
const program = new commander_1.Command();
|
|
35
13
|
// ── Program metadata ──────────────────────────────────────────
|
|
36
14
|
program
|
|
37
15
|
.name("react-doctor")
|
|
38
16
|
.description("React performance analyzer — static analysis + runtime profiling + smart suggestions")
|
|
39
|
-
.version("1.0.
|
|
40
|
-
// ── Register
|
|
41
|
-
// Each function adds one command to the program.
|
|
42
|
-
// The order here is the order they appear in --help output.
|
|
17
|
+
.version("1.0.2");
|
|
18
|
+
// ── Register commands ─────────────────────────────────────────
|
|
43
19
|
(0, full_1.registerFullCommand)(program); // react-doctor full
|
|
44
20
|
(0, analyze_1.registerAnalyzeCommand)(program); // react-doctor analyze
|
|
45
21
|
(0, profile_1.registerProfileCommand)(program); // react-doctor profile
|
|
46
|
-
(0,
|
|
47
|
-
// ── Usage examples
|
|
22
|
+
(0, dashboard_1.registerDashboardCommand)(program); // react-doctor dashboard
|
|
23
|
+
// ── Usage examples ────────────────────────────────────────────
|
|
48
24
|
program.addHelpText("after", `
|
|
49
25
|
Examples:
|
|
50
|
-
$ react-doctor full ./my-app
|
|
51
|
-
$ react-doctor full ./my-app --mobile
|
|
52
|
-
$ react-doctor full ./my-app --desktop --mobile
|
|
53
|
-
$ react-doctor full ./my-app --cpu 4
|
|
54
|
-
$ react-doctor full ./my-app --throttle slow4g
|
|
55
|
-
$ react-doctor full ./my-app --throttle 3g
|
|
56
|
-
$ react-doctor full ./my-app --cpu 4 --throttle 3g
|
|
57
|
-
$ react-doctor full ./my-app --upload
|
|
26
|
+
$ react-doctor full ./my-app Run full diagnostic (desktop)
|
|
27
|
+
$ react-doctor full ./my-app --mobile Include mobile viewport
|
|
28
|
+
$ react-doctor full ./my-app --desktop --mobile Both desktop and mobile
|
|
29
|
+
$ react-doctor full ./my-app --cpu 4 Simulate slow Android device
|
|
30
|
+
$ react-doctor full ./my-app --throttle slow4g Simulate slow 4G network
|
|
31
|
+
$ react-doctor full ./my-app --throttle 3g Simulate 3G network
|
|
32
|
+
$ react-doctor full ./my-app --cpu 4 --throttle 3g Slow device + slow network
|
|
33
|
+
$ react-doctor full ./my-app --upload Run + save report to dashboard
|
|
58
34
|
|
|
59
|
-
$ react-doctor analyze ./my-app
|
|
60
|
-
$ react-doctor analyze ./my-app --full
|
|
35
|
+
$ react-doctor analyze ./my-app Static code analysis only
|
|
36
|
+
$ react-doctor analyze ./my-app --full Static + runtime + rules
|
|
61
37
|
|
|
62
|
-
$ react-doctor profile ./my-app
|
|
63
|
-
$ react-doctor profile ./my-app --mobile
|
|
64
|
-
$ react-doctor profile ./my-app --desktop --mobile
|
|
65
|
-
$ react-doctor profile ./my-app --cpu 4
|
|
66
|
-
$ react-doctor profile ./my-app --throttle slow4g
|
|
67
|
-
$ react-doctor profile ./my-app --throttle 3g
|
|
38
|
+
$ react-doctor profile ./my-app Runtime profiling only (desktop)
|
|
39
|
+
$ react-doctor profile ./my-app --mobile Mobile viewport
|
|
40
|
+
$ react-doctor profile ./my-app --desktop --mobile Both devices
|
|
41
|
+
$ react-doctor profile ./my-app --cpu 4 4x CPU slowdown simulation
|
|
42
|
+
$ react-doctor profile ./my-app --throttle slow4g Simulate slow 4G network
|
|
43
|
+
$ react-doctor profile ./my-app --throttle 3g Simulate 3G network
|
|
68
44
|
|
|
69
|
-
$ react-doctor
|
|
70
|
-
$ react-doctor
|
|
45
|
+
$ react-doctor dashboard Open dashboard (auto-starts backend)
|
|
46
|
+
$ react-doctor dashboard --port 4000 Use custom port
|
|
71
47
|
`);
|
|
72
48
|
// ── Show help if called with no arguments ─────────────────────
|
|
73
|
-
// Without this, calling "react-doctor" with no command just
|
|
74
|
-
// exits silently, which is confusing. This prints help instead.
|
|
75
49
|
if (process.argv.length < 3) {
|
|
76
50
|
program.help();
|
|
77
51
|
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// cli/src/commands/dashboard.ts
|
|
3
|
+
//
|
|
4
|
+
// react-doctor dashboard
|
|
5
|
+
//
|
|
6
|
+
// Opens the React Doctor dashboard in the browser.
|
|
7
|
+
//
|
|
8
|
+
// WHAT IT DOES:
|
|
9
|
+
// 1. Checks if the backend is already running on the port
|
|
10
|
+
// 2. If not — starts it automatically (same logic as --upload)
|
|
11
|
+
// 3. Opens http://localhost:PORT in the default browser
|
|
12
|
+
//
|
|
13
|
+
// This command is the natural companion to --upload.
|
|
14
|
+
// Workflow:
|
|
15
|
+
// react-doctor full ./my-app --upload ← runs analysis + saves report
|
|
16
|
+
// react-doctor dashboard ← opens the dashboard to view it
|
|
17
|
+
//
|
|
18
|
+
// Or in one shot:
|
|
19
|
+
// react-doctor full ./my-app --upload && react-doctor dashboard
|
|
20
|
+
// ─────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
import { Command } from "commander";
|
|
23
|
+
import path from "path";
|
|
24
|
+
import fs from "fs";
|
|
25
|
+
import axios from "axios";
|
|
26
|
+
import { spawn } from "child_process";
|
|
27
|
+
import chalk from "chalk";
|
|
28
|
+
import {
|
|
29
|
+
printBanner, printSection,
|
|
30
|
+
printDone, printFail, printInfo, spinner,
|
|
31
|
+
} from "../ui";
|
|
32
|
+
|
|
33
|
+
export function registerDashboardCommand(program: Command): void {
|
|
34
|
+
program
|
|
35
|
+
.command("dashboard")
|
|
36
|
+
.description("Open the React Doctor dashboard (auto-starts backend if needed)")
|
|
37
|
+
.option(
|
|
38
|
+
"--port <port>",
|
|
39
|
+
"Port the backend runs on",
|
|
40
|
+
"3000",
|
|
41
|
+
)
|
|
42
|
+
.option(
|
|
43
|
+
"--api-key <key>",
|
|
44
|
+
"API key for the backend",
|
|
45
|
+
process.env.REACT_DOCTOR_API_KEY || "react-doctor-secret-key-change-this",
|
|
46
|
+
)
|
|
47
|
+
.option("--no-banner", "Skip the banner")
|
|
48
|
+
.action(async (options) => {
|
|
49
|
+
|
|
50
|
+
if (!options.noBanner) printBanner();
|
|
51
|
+
|
|
52
|
+
const port = options.port;
|
|
53
|
+
const apiUrl = `http://localhost:${port}`;
|
|
54
|
+
|
|
55
|
+
printSection("Dashboard");
|
|
56
|
+
printInfo("Backend URL", apiUrl);
|
|
57
|
+
console.log();
|
|
58
|
+
|
|
59
|
+
const spin = spinner("Checking backend status...");
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// ── 1. Check if backend is already up ─────────────────
|
|
63
|
+
let backendRunning = false;
|
|
64
|
+
try {
|
|
65
|
+
await axios.get(`${apiUrl}/health`, { timeout: 2000 });
|
|
66
|
+
backendRunning = true;
|
|
67
|
+
} catch {
|
|
68
|
+
backendRunning = false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── 2. Start backend if not running ───────────────────
|
|
72
|
+
if (!backendRunning) {
|
|
73
|
+
spin.text = " Backend not running — starting automatically...";
|
|
74
|
+
|
|
75
|
+
// Locate backend folder (sibling of cli/)
|
|
76
|
+
const projectRoot = path.resolve(__dirname, "..", "..", "..");
|
|
77
|
+
const backendRoot = path.resolve(projectRoot, "backend");
|
|
78
|
+
const backendDist = path.join(backendRoot, "dist", "index.js");
|
|
79
|
+
const backendSrc = path.join(backendRoot, "src", "index.ts");
|
|
80
|
+
|
|
81
|
+
let command: string;
|
|
82
|
+
let args: string[];
|
|
83
|
+
|
|
84
|
+
if (fs.existsSync(backendDist)) {
|
|
85
|
+
command = "node";
|
|
86
|
+
args = [backendDist];
|
|
87
|
+
} else if (fs.existsSync(backendSrc)) {
|
|
88
|
+
command = "npx";
|
|
89
|
+
args = ["ts-node", backendSrc];
|
|
90
|
+
} else {
|
|
91
|
+
spin.fail(chalk.red("Backend not found"));
|
|
92
|
+
printFail(
|
|
93
|
+
`Could not find backend at: ${backendRoot}\n\n` +
|
|
94
|
+
` Make sure the 'backend/' folder exists next to 'cli/'.`,
|
|
95
|
+
);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Create data dir inside npm global cache for the backend DB
|
|
100
|
+
const dataDir = path.join(backendRoot, "data");
|
|
101
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
102
|
+
|
|
103
|
+
spawn(command, args, {
|
|
104
|
+
stdio: "ignore",
|
|
105
|
+
detached: true,
|
|
106
|
+
env: {
|
|
107
|
+
...process.env,
|
|
108
|
+
API_KEY: options.apiKey,
|
|
109
|
+
PORT: port,
|
|
110
|
+
DB_PATH: path.join(dataDir, "reports.db"),
|
|
111
|
+
},
|
|
112
|
+
cwd: backendRoot,
|
|
113
|
+
}).unref(); // let CLI exit without killing the server
|
|
114
|
+
|
|
115
|
+
// Wait for backend to be ready (up to 15 seconds)
|
|
116
|
+
let ready = false;
|
|
117
|
+
let retries = 0;
|
|
118
|
+
while (!ready && retries < 15) {
|
|
119
|
+
try {
|
|
120
|
+
await axios.get(`${apiUrl}/health`, { timeout: 1000 });
|
|
121
|
+
ready = true;
|
|
122
|
+
} catch {
|
|
123
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
124
|
+
retries++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!ready) {
|
|
129
|
+
spin.fail(chalk.red("Backend failed to start"));
|
|
130
|
+
printFail("Backend did not respond after 15 seconds.");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
spin.succeed(chalk.green("Backend started successfully"));
|
|
135
|
+
} else {
|
|
136
|
+
spin.succeed(chalk.green("Backend already running"));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── 3. Open dashboard in default browser ──────────────
|
|
140
|
+
const dashboardUrl = apiUrl;
|
|
141
|
+
|
|
142
|
+
console.log();
|
|
143
|
+
printInfo("Opening", dashboardUrl);
|
|
144
|
+
console.log();
|
|
145
|
+
|
|
146
|
+
// Cross-platform browser open
|
|
147
|
+
const openCmd =
|
|
148
|
+
process.platform === "win32" ? ["cmd", ["/c", "start", dashboardUrl]] :
|
|
149
|
+
process.platform === "darwin" ? ["open", [dashboardUrl]] :
|
|
150
|
+
["xdg-open", [dashboardUrl]];
|
|
151
|
+
|
|
152
|
+
spawn(openCmd[0] as string, openCmd[1] as string[], {
|
|
153
|
+
stdio: "ignore",
|
|
154
|
+
detached: true,
|
|
155
|
+
}).unref();
|
|
156
|
+
|
|
157
|
+
printDone(`Dashboard opened at ${chalk.cyan(dashboardUrl)}`);
|
|
158
|
+
|
|
159
|
+
// ── 4. Show quick API reference ───────────────────────
|
|
160
|
+
console.log(chalk.gray(" Available endpoints:"));
|
|
161
|
+
console.log(chalk.cyan(` GET ${apiUrl}/health`));
|
|
162
|
+
console.log(chalk.cyan(` GET ${apiUrl}/api/reports`));
|
|
163
|
+
console.log(chalk.cyan(` GET ${apiUrl}/api/reports/:id`));
|
|
164
|
+
console.log(chalk.cyan(` GET ${apiUrl}/api/reports/project/:name`));
|
|
165
|
+
console.log(chalk.cyan(` POST ${apiUrl}/api/reports/upload`));
|
|
166
|
+
console.log();
|
|
167
|
+
console.log(
|
|
168
|
+
chalk.gray(" Tip: run ") +
|
|
169
|
+
chalk.cyan("react-doctor full ./ --upload") +
|
|
170
|
+
chalk.gray(" to add a new report.\n"),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
} catch (err: any) {
|
|
174
|
+
spin.fail(chalk.red("Dashboard failed to open"));
|
|
175
|
+
console.log(chalk.red(`\n ${err.message}\n`));
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
package/cli/src/index.ts
CHANGED
|
@@ -1,35 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// ─────────────────────────────────────────────────────────────
|
|
3
|
-
// cli/src/index.ts
|
|
4
|
-
//
|
|
5
|
-
// The CLI entry point. This is the file that runs when the
|
|
6
|
-
// user types "react-doctor" in their terminal.
|
|
7
|
-
//
|
|
8
|
-
// HOW IT WORKS:
|
|
9
|
-
// 1. Commander.js parses the command and flags from argv
|
|
10
|
-
// 2. The matching command handler is called
|
|
11
|
-
// 3. The handler imports core modules and runs the pipeline
|
|
12
|
-
//
|
|
13
|
-
// HOW THE BINARY REGISTRATION WORKS:
|
|
14
|
-
// package.json has a "bin" field:
|
|
15
|
-
// "bin": { "react-doctor": "./dist/index.js" }
|
|
16
|
-
//
|
|
17
|
-
// After "npm link" (dev) or "npm install" (production),
|
|
18
|
-
// npm creates a symlink from the system's bin directory
|
|
19
|
-
// to this file. That's what makes "react-doctor" a real
|
|
20
|
-
// terminal command available anywhere.
|
|
21
|
-
//
|
|
22
|
-
// THE SHEBANG (#!/usr/bin/env node) on line 1:
|
|
23
|
-
// This tells the OS to run this file with Node.js when
|
|
24
|
-
// called directly as a script. Without it, the OS doesn't
|
|
25
|
-
// know which interpreter to use.
|
|
3
|
+
// cli/src/index.ts — CLI entry point
|
|
26
4
|
// ─────────────────────────────────────────────────────────────
|
|
27
5
|
|
|
28
6
|
import { Command } from "commander";
|
|
29
|
-
import { registerAnalyzeCommand }
|
|
30
|
-
import { registerProfileCommand }
|
|
31
|
-
import { registerFullCommand }
|
|
32
|
-
import {
|
|
7
|
+
import { registerAnalyzeCommand } from "./commands/analyze";
|
|
8
|
+
import { registerProfileCommand } from "./commands/profile";
|
|
9
|
+
import { registerFullCommand } from "./commands/full";
|
|
10
|
+
import { registerDashboardCommand } from "./commands/dashboard";
|
|
33
11
|
|
|
34
12
|
const program = new Command();
|
|
35
13
|
|
|
@@ -37,46 +15,41 @@ const program = new Command();
|
|
|
37
15
|
program
|
|
38
16
|
.name("react-doctor")
|
|
39
17
|
.description("React performance analyzer — static analysis + runtime profiling + smart suggestions")
|
|
40
|
-
.version("1.0.
|
|
18
|
+
.version("1.0.2");
|
|
41
19
|
|
|
42
|
-
// ── Register
|
|
43
|
-
//
|
|
44
|
-
//
|
|
20
|
+
// ── Register commands ─────────────────────────────────────────
|
|
21
|
+
registerFullCommand(program); // react-doctor full
|
|
22
|
+
registerAnalyzeCommand(program); // react-doctor analyze
|
|
23
|
+
registerProfileCommand(program); // react-doctor profile
|
|
24
|
+
registerDashboardCommand(program); // react-doctor dashboard
|
|
45
25
|
|
|
46
|
-
|
|
47
|
-
registerAnalyzeCommand(program); // react-doctor analyze
|
|
48
|
-
registerProfileCommand(program); // react-doctor profile
|
|
49
|
-
registerInstallCommand(program); // react-doctor install
|
|
50
|
-
|
|
51
|
-
// ── Usage examples shown at bottom of --help ─────────────────
|
|
26
|
+
// ── Usage examples ────────────────────────────────────────────
|
|
52
27
|
program.addHelpText("after", `
|
|
53
28
|
Examples:
|
|
54
|
-
$ react-doctor full ./my-app
|
|
55
|
-
$ react-doctor full ./my-app --mobile
|
|
56
|
-
$ react-doctor full ./my-app --desktop --mobile
|
|
57
|
-
$ react-doctor full ./my-app --cpu 4
|
|
58
|
-
$ react-doctor full ./my-app --throttle slow4g
|
|
59
|
-
$ react-doctor full ./my-app --throttle 3g
|
|
60
|
-
$ react-doctor full ./my-app --cpu 4 --throttle 3g
|
|
61
|
-
$ react-doctor full ./my-app --upload
|
|
62
|
-
|
|
63
|
-
$ react-doctor analyze ./my-app
|
|
64
|
-
$ react-doctor analyze ./my-app --full
|
|
65
|
-
|
|
66
|
-
$ react-doctor profile ./my-app
|
|
67
|
-
$ react-doctor profile ./my-app --mobile
|
|
68
|
-
$ react-doctor profile ./my-app --desktop --mobile
|
|
69
|
-
$ react-doctor profile ./my-app --cpu 4
|
|
70
|
-
$ react-doctor profile ./my-app --throttle slow4g
|
|
71
|
-
$ react-doctor profile ./my-app --throttle 3g
|
|
72
|
-
|
|
73
|
-
$ react-doctor
|
|
74
|
-
$ react-doctor
|
|
29
|
+
$ react-doctor full ./my-app Run full diagnostic (desktop)
|
|
30
|
+
$ react-doctor full ./my-app --mobile Include mobile viewport
|
|
31
|
+
$ react-doctor full ./my-app --desktop --mobile Both desktop and mobile
|
|
32
|
+
$ react-doctor full ./my-app --cpu 4 Simulate slow Android device
|
|
33
|
+
$ react-doctor full ./my-app --throttle slow4g Simulate slow 4G network
|
|
34
|
+
$ react-doctor full ./my-app --throttle 3g Simulate 3G network
|
|
35
|
+
$ react-doctor full ./my-app --cpu 4 --throttle 3g Slow device + slow network
|
|
36
|
+
$ react-doctor full ./my-app --upload Run + save report to dashboard
|
|
37
|
+
|
|
38
|
+
$ react-doctor analyze ./my-app Static code analysis only
|
|
39
|
+
$ react-doctor analyze ./my-app --full Static + runtime + rules
|
|
40
|
+
|
|
41
|
+
$ react-doctor profile ./my-app Runtime profiling only (desktop)
|
|
42
|
+
$ react-doctor profile ./my-app --mobile Mobile viewport
|
|
43
|
+
$ react-doctor profile ./my-app --desktop --mobile Both devices
|
|
44
|
+
$ react-doctor profile ./my-app --cpu 4 4x CPU slowdown simulation
|
|
45
|
+
$ react-doctor profile ./my-app --throttle slow4g Simulate slow 4G network
|
|
46
|
+
$ react-doctor profile ./my-app --throttle 3g Simulate 3G network
|
|
47
|
+
|
|
48
|
+
$ react-doctor dashboard Open dashboard (auto-starts backend)
|
|
49
|
+
$ react-doctor dashboard --port 4000 Use custom port
|
|
75
50
|
`);
|
|
76
51
|
|
|
77
52
|
// ── Show help if called with no arguments ─────────────────────
|
|
78
|
-
// Without this, calling "react-doctor" with no command just
|
|
79
|
-
// exits silently, which is confusing. This prints help instead.
|
|
80
53
|
if (process.argv.length < 3) {
|
|
81
54
|
program.help();
|
|
82
55
|
}
|
package/package.json
CHANGED