Skip to content

Commit 35a7ddb

Browse files
authored
feat(cli): create rsdoctor/cli package (#187)
1 parent df0e55e commit 35a7ddb

File tree

15 files changed

+448
-10
lines changed

15 files changed

+448
-10
lines changed

.changeset/fresh-paws-check.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@rsdoctor/cli': patch
3+
---
4+
5+
feat(cli): create rsdoctor/cli package

packages/cli/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# @rsdoctor/cli
2+
3+
## 0.1.1

packages/cli/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023-present Bytedance, Inc. and its affiliates.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/cli/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Rsdoctor Cli
2+
3+
This is the cli package of Rsdoctor, you can use the capabilities of this package to open the analysis page without building.
4+
5+
```
6+
7+
npx @rsdoctor/cli analyze --profile [.rsdoctor/manifest.json filepath]
8+
9+
```
10+
11+
## features
12+
13+
- Rsdoctor is a one-stop tool for diagnosing and analyzing the build process and build artifacts.
14+
- Rsdoctor is a tool that supports Webpack and Rspack build analysis.
15+
- Rsdoctor is an analysis tool that can display the time-consuming and behavioral details of the compilation.
16+
- Rsdoctor is a tool that provides bundle Diff and other anti-degradation capabilities simultaneously.
17+
18+
## Documentation
19+
20+
https://rsdoctor.dev/
21+
22+
## Contributing
23+
24+
Please read the [Contributing Guide](https://github.com/web-infra-dev/rsdoctor/blob/main/CONTRIBUTING.md).
25+
26+
## License
27+
28+
Rsdoctor is [MIT licensed](https://github.com/web-infra-dev/rsdoctor/blob/main/LICENSE).

packages/cli/bin/rsdoctor

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env node
2+
3+
const { execute } = require('../dist/index.js');
4+
5+
execute();

packages/cli/modern.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { baseBuildConfig } from '../../scripts/modern.base.config';
2+
3+
export default baseBuildConfig;

packages/cli/package.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"name": "@rsdoctor/cli",
3+
"version": "0.1.1",
4+
"repository": {
5+
"type": "git",
6+
"url": "https://github.com/web-infra-dev/rsdoctor",
7+
"directory": "packages/cli"
8+
},
9+
"bin": {
10+
"rsdoctor": "./bin/rsdoctor"
11+
},
12+
"files": [
13+
"bin",
14+
"dist"
15+
],
16+
"license": "MIT",
17+
"main": "dist/index.js",
18+
"module": "dist/index.js",
19+
"types": "dist/index.d.ts",
20+
"scripts": {
21+
"dev": "npm run start",
22+
"build": "modern build",
23+
"start": "modern build -w",
24+
"test": "vitest run"
25+
},
26+
"devDependencies": {
27+
"@types/yargs": "17.0.13",
28+
"typescript": "^5.2.2"
29+
},
30+
"dependencies": {
31+
"@rsdoctor/sdk": "workspace:*",
32+
"@rsdoctor/types": "workspace:*",
33+
"@rsdoctor/utils": "workspace:*",
34+
"axios": "^1.6.1",
35+
"chalk": "^4.1.2",
36+
"ora": "^5.0.1",
37+
"tslib": "2.4.1",
38+
"yargs": "17.6.2"
39+
},
40+
"publishConfig": {
41+
"access": "public",
42+
"provenance": true,
43+
"registry": "https://registry.npmjs.org/"
44+
}
45+
}

packages/cli/src/commands/analyze.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { Manifest } from '@rsdoctor/utils/common';
2+
import { Constants, Manifest as ManifestType, SDK } from '@rsdoctor/types';
3+
import { RsdoctorWebpackSDK } from '@rsdoctor/sdk';
4+
import ora from 'ora';
5+
import { cyan, red } from 'chalk';
6+
import { Command } from '../types';
7+
import {
8+
enhanceCommand,
9+
loadJSON,
10+
loadShardingFileWithSpinner,
11+
} from '../utils';
12+
import { Commands } from '../constants';
13+
14+
interface Options {
15+
profile: string;
16+
open?: boolean;
17+
port?: number;
18+
type?: SDK.ToDataType;
19+
}
20+
21+
export const analyze: Command<Commands.Analyze, Options, RsdoctorWebpackSDK> =
22+
enhanceCommand(({ cwd, name, bin }) => ({
23+
command: Commands.Analyze,
24+
description: `
25+
use ${name} to open "${Constants.RsdoctorOutputManifestPath}" in browser for analysis.
26+
27+
example: ${bin} ${Commands.Analyze} --profile "${Constants.RsdoctorOutputManifestPath}"
28+
29+
`.trim(),
30+
options(yargs) {
31+
yargs
32+
.option('profile', {
33+
type: 'string',
34+
description: 'profile for Rsdoctor server',
35+
demandOption: true,
36+
})
37+
.option('open', {
38+
type: 'boolean',
39+
description: 'turn off it if you need not open browser automatically',
40+
default: true,
41+
})
42+
.option('port', {
43+
type: 'number',
44+
description: 'port for Rsdoctor Server',
45+
})
46+
.option('type', {
47+
type: 'boolean',
48+
description: 'if need lite bundle mode',
49+
});
50+
},
51+
async action({ profile, open = true, port, type = SDK.ToDataType.Normal }) {
52+
const spinner = ora({ prefixText: cyan(`[${name}]`) }).start(
53+
`start to loading "${profile}"`,
54+
);
55+
56+
const json =
57+
await loadJSON<ManifestType.RsdoctorManifestWithShardingFiles>(
58+
profile,
59+
cwd,
60+
);
61+
62+
spinner.text = `start to loading data...`;
63+
64+
let dataValue: ManifestType.RsdoctorManifestData;
65+
66+
try {
67+
dataValue = await Manifest.fetchShardingFiles(
68+
json.data,
69+
(url: string) => loadShardingFileWithSpinner(url, cwd, spinner),
70+
);
71+
} catch (error) {
72+
spinner.fail(red((error as Error).message));
73+
throw error;
74+
}
75+
76+
spinner.text = `start server`;
77+
78+
const sdk = new RsdoctorWebpackSDK({ name, root: cwd, port, type });
79+
80+
await sdk.bootstrap();
81+
82+
sdk.getStoreData = () => dataValue;
83+
sdk.getManifestData = () => json;
84+
85+
const manifestBuffer = Buffer.from(
86+
JSON.stringify({ ...json, __LOCAL__SERVER__: true }),
87+
);
88+
89+
sdk.server.proxy(SDK.ServerAPI.API.Manifest, 'GET', () => manifestBuffer);
90+
91+
if (open) {
92+
spinner.text = `open browser automatically`;
93+
94+
await sdk.server.openClientPage('homepage');
95+
}
96+
97+
spinner.succeed(
98+
`the local url: ${cyan(sdk.server.getClientUrl('homepage'))}`,
99+
);
100+
101+
return sdk;
102+
},
103+
}));

packages/cli/src/commands/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './analyze';

packages/cli/src/constants.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export enum Commands {
2+
Analyze = 'analyze',
3+
}
4+
5+
export const pkg: {
6+
name: string;
7+
version: string;
8+
bin: Record<string, string>;
9+
} = require('../package.json');
10+
11+
export const bin = Object.keys(pkg.bin)[0];

packages/cli/src/index.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import yargs from 'yargs/yargs';
2+
import { hideBin } from 'yargs/helpers';
3+
import chalk from 'chalk';
4+
import { Common } from '@rsdoctor/types';
5+
import { analyze } from './commands';
6+
import { Command, CommandContext, GetCommandArgumentsType } from './types';
7+
import { Commands, pkg, bin } from './constants';
8+
9+
export async function execute<
10+
T extends GetCommandArgumentsType<typeof analyze>,
11+
>(
12+
command: Commands.Analyze | `${Commands.Analyze}`,
13+
options: T['options'],
14+
): Promise<T['result']>;
15+
export async function execute(): Promise<void>;
16+
export async function execute(
17+
command?: `${Commands}` | Commands,
18+
options?: Common.PlainObject,
19+
): Promise<unknown> {
20+
const cwd = process.cwd();
21+
const { name, version } = pkg;
22+
23+
const ctx: CommandContext = { bin, cwd, name };
24+
25+
if (command === Commands.Analyze) {
26+
const { action } = analyze(ctx);
27+
28+
return action(
29+
options as GetCommandArgumentsType<typeof analyze>['options'],
30+
);
31+
}
32+
33+
const argv = hideBin(process.argv);
34+
const args = yargs(argv).usage(`${bin} <command> [options]`);
35+
36+
args.version(version);
37+
38+
const commands: Command<string>[] = [analyze];
39+
40+
commands.forEach((cmd) => {
41+
const { command, description, options, action } = cmd(ctx);
42+
43+
args.command(
44+
command,
45+
description,
46+
(yargs) => {
47+
return options(yargs.usage(`${bin} ${command} [options]`));
48+
},
49+
async (args) => {
50+
try {
51+
await action(args);
52+
} catch (error) {
53+
const { message, stack } = error as Error;
54+
console.log('');
55+
console.error(chalk.red(stack || message));
56+
process.exit(1);
57+
}
58+
},
59+
);
60+
});
61+
62+
if (!argv.length) {
63+
args.showHelp();
64+
}
65+
66+
await args.parse();
67+
}

packages/cli/src/types.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Common } from '@rsdoctor/types';
2+
import { Argv } from 'yargs';
3+
4+
export interface Command<CMD, Options = Common.PlainObject, Result = unknown> {
5+
(ctx: CommandContext): CommandOutput<CMD, Options, Result>;
6+
}
7+
8+
export interface CommandContext {
9+
name: string;
10+
bin: string;
11+
cwd: string;
12+
}
13+
14+
export interface CommandOutput<CMD, Options, Result> {
15+
command: CMD;
16+
description: string;
17+
options(yargs: Argv<Options>): void;
18+
action(args: Options): Result | Promise<Result>;
19+
}
20+
21+
export type GetCommandArgumentsType<T> = T extends Command<
22+
infer C,
23+
infer Options,
24+
infer Result
25+
>
26+
? {
27+
command: C;
28+
options: Options;
29+
result: Result;
30+
}
31+
: unknown;

0 commit comments

Comments
 (0)