Skip to content

Commit 1d95c58

Browse files
committed
chore: update
1 parent 2a30bc7 commit 1d95c58

33 files changed

+1384
-1424
lines changed

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Migrate Rsbuild projects from v1 to v2. Use when a user asks to upgrade Rsbuild,
3636

3737
## Rsdoctor Skills
3838

39-
### rsdoctor-analytics
39+
### rsdoctor-analysis
4040

4141
```bash
4242
npx skills add rstackjs/agent-skills --skill rsdoctor-analytics
@@ -46,8 +46,6 @@ Analyze Rspack/Webpack bundles from local Rsdoctor build data. Zero-dependency J
4646

4747
Use when you need to analyze bundle composition, identify duplicate packages, detect similar packages, find large chunks, analyze side effects modules, or get comprehensive bundle optimization recommendations.
4848

49-
5049
## License
5150

5251
MIT licensed.
53-
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "rsdoctor-analysis",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"scripts": {
6+
"build": "rslib build",
7+
"dev": "rslib build --watch"
8+
},
9+
"devDependencies": {
10+
"@rslib/core": "^0.19.4",
11+
"@types/node": "^24.10.9",
12+
"commander": "^12.1.0",
13+
"typescript": "^5.9.3"
14+
}
15+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { basename, join } from 'node:path';
2+
import { defineConfig } from '@rslib/core';
3+
import { baseConfig } from '@rstackjs/config/rslib.config.ts';
4+
5+
const pkgName = basename(import.meta.dirname);
6+
7+
export default defineConfig({
8+
lib: [
9+
{
10+
...baseConfig,
11+
source: {
12+
entry: {
13+
rsdoctor: './src/index.ts',
14+
},
15+
},
16+
banner: {
17+
js: '#!/usr/bin/env node',
18+
},
19+
output: {
20+
distPath: join(import.meta.dirname, `../../skills/${pkgName}/scripts`),
21+
legalComments: 'none',
22+
cleanDistPath: true,
23+
},
24+
},
25+
],
26+
});
Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { Command } from 'commander';
2-
import { printResult } from './utils/cli-utils';
2+
import { registerAssetCommands } from './commands/assets';
3+
import { registerBuildCommands } from './commands/build';
34
import { registerChunkCommands } from './commands/chunks';
5+
import { registerErrorCommands } from './commands/errors';
6+
import { registerLoaderCommands } from './commands/loaders';
47
import { registerModuleCommands } from './commands/modules';
58
import { registerPackageCommands } from './commands/packages';
69
import { registerRuleCommands } from './commands/rules';
7-
import { registerAssetCommands } from './commands/assets';
8-
import { registerLoaderCommands } from './commands/loaders';
9-
import { registerBuildCommands } from './commands/build';
10-
import { registerErrorCommands } from './commands/errors';
1110
import { registerServerCommands } from './commands/server';
1211
import { closeAllSockets } from './socket';
12+
import { printResult } from './utils/cli-utils';
1313

1414
export const program = new Command();
1515
program
@@ -20,7 +20,9 @@ program
2020
.showHelpAfterError()
2121
.showSuggestionAfterError();
2222

23-
export const execute = async (handler: () => Promise<unknown>): Promise<void> => {
23+
export const execute = async (
24+
handler: () => Promise<unknown>,
25+
): Promise<void> => {
2426
// Parse compact option once at the beginning
2527
const opts = program.opts<{ compact?: boolean | string }>();
2628
const compact = opts.compact === true || opts.compact === 'true';
@@ -69,7 +71,7 @@ export async function run(): Promise<void> {
6971
program.help({ error: true });
7072
}
7173
await program.parseAsync(process.argv);
72-
74+
7375
// Cleanup
7476
closeAllSockets();
7577
}

skills/rsdoctor/infra/rsdoctor/commands/assets.ts renamed to packages/rsdoctor-analysis/src/commands/assets.ts

Lines changed: 126 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import type { Command } from 'commander';
21
import path from 'node:path';
3-
import { requireArg } from '../utils/cli-utils';
4-
import { getAssets, getAllChunks } from '../tools';
2+
import type { Command } from 'commander';
53
import { loadJsonData } from '../datasource';
4+
import { getAllChunks, getAssets } from '../tools';
5+
import { requireArg } from '../utils/cli-utils';
66

7-
interface CommandExecutor {
8-
(handler: () => Promise<unknown>): Promise<void>;
9-
}
7+
type CommandExecutor = (handler: () => Promise<unknown>) => Promise<void>;
108

119
interface Asset {
1210
path: string;
@@ -45,7 +43,7 @@ const Constants = {
4543

4644
const extname = (filename: string): string => {
4745
const baseName = filename.split('?')[0];
48-
const matches = baseName.match(/\.([0-9a-z]+)(?:[\?#]|$)/i);
46+
const matches = baseName.match(/\.([0-9a-z]+)(?:[?#]|$)/i);
4947
return matches ? `.${matches[1]}` : '';
5048
};
5149

@@ -55,31 +53,60 @@ const isAssetMatchExtension = (asset: Asset, ext: string): boolean =>
5553
const isAssetMatchExtensions = (asset: Asset, exts: string[]): boolean =>
5654
Array.isArray(exts) && exts.some((ext) => isAssetMatchExtension(asset, ext));
5755

58-
const filterAssetsByExtensions = (assets: Asset[], exts: string | string[]): Asset[] => {
59-
if (typeof exts === 'string') return assets.filter((e) => isAssetMatchExtension(e, exts));
60-
if (Array.isArray(exts)) return assets.filter((e) => isAssetMatchExtensions(e, exts));
56+
const filterAssetsByExtensions = (
57+
assets: Asset[],
58+
exts: string | string[],
59+
): Asset[] => {
60+
if (typeof exts === 'string')
61+
return assets.filter((e) => isAssetMatchExtension(e, exts));
62+
if (Array.isArray(exts))
63+
return assets.filter((e) => isAssetMatchExtensions(e, exts));
6164
return assets;
6265
};
6366

64-
const filterAssets = (assets: Asset[], filterOrExtensions?: string | string[] | ((asset: Asset) => boolean)): Asset[] => {
67+
const filterAssets = (
68+
assets: Asset[],
69+
filterOrExtensions?: string | string[] | ((asset: Asset) => boolean),
70+
): Asset[] => {
6571
if (!filterOrExtensions) return assets;
66-
if (typeof filterOrExtensions === 'function') return assets.filter(filterOrExtensions);
72+
if (typeof filterOrExtensions === 'function')
73+
return assets.filter(filterOrExtensions);
6774
return filterAssetsByExtensions(assets, filterOrExtensions);
6875
};
6976

7077
const isInitialAsset = (asset: Asset, chunks: Chunk[]): boolean => {
7178
const assetChunkIds = (asset.chunks || []) as Array<number | string>;
7279
if (!assetChunkIds.length) return false;
73-
const initialSet = new Set<number>((chunks || []).filter((c) => c.initial).map((c) => c.id));
80+
const initialSet = new Set<number>(
81+
(chunks || []).filter((c) => c.initial).map((c) => c.id),
82+
);
7483
return assetChunkIds.some((id) => initialSet.has(Number(id)));
7584
};
7685

7786
const getAssetsSizeInfo = (
7887
assets: Asset[],
7988
chunks: Chunk[],
80-
{ withFileContent = false, filterOrExtensions }: { withFileContent?: boolean; filterOrExtensions?: string | string[] | ((asset: Asset) => boolean) } = {}
81-
): { count: number; size: number; files: Array<{ path: string; size: number; gzipSize?: number; initial: boolean; content?: unknown }> } => {
82-
let filtered = assets.filter((e) => !isAssetMatchExtensions(e, Constants.MapExtensions));
89+
{
90+
withFileContent = false,
91+
filterOrExtensions,
92+
}: {
93+
withFileContent?: boolean;
94+
filterOrExtensions?: string | string[] | ((asset: Asset) => boolean);
95+
} = {},
96+
): {
97+
count: number;
98+
size: number;
99+
files: Array<{
100+
path: string;
101+
size: number;
102+
gzipSize?: number;
103+
initial: boolean;
104+
content?: unknown;
105+
}>;
106+
} => {
107+
let filtered = assets.filter(
108+
(e) => !isAssetMatchExtensions(e, Constants.MapExtensions),
109+
);
83110
filtered = filterAssets(filtered, filterOrExtensions);
84111
return {
85112
count: filtered.length,
@@ -94,30 +121,51 @@ const getAssetsSizeInfo = (
94121
};
95122
};
96123

97-
const getInitialAssetsSizeInfo = (assets: Asset[], chunks: Chunk[], options: { withFileContent?: boolean; filterOrExtensions?: string | string[] | ((asset: Asset) => boolean) } = {}): ReturnType<typeof getAssetsSizeInfo> =>
124+
const getInitialAssetsSizeInfo = (
125+
assets: Asset[],
126+
chunks: Chunk[],
127+
options: {
128+
withFileContent?: boolean;
129+
filterOrExtensions?: string | string[] | ((asset: Asset) => boolean);
130+
} = {},
131+
): ReturnType<typeof getAssetsSizeInfo> =>
98132
getAssetsSizeInfo(assets, chunks, {
99133
...options,
100134
filterOrExtensions: (asset: Asset) => isInitialAsset(asset, chunks),
101135
});
102136

103-
const diffSize = (bSize: number, cSize: number): { percent: number; state: 'Equal' | 'Down' | 'Up' } => {
137+
const diffSize = (
138+
bSize: number,
139+
cSize: number,
140+
): { percent: number; state: 'Equal' | 'Down' | 'Up' } => {
104141
const isEqual = bSize === cSize;
105-
const percent = isEqual ? 0 : bSize === 0 ? 100 : (Math.abs(cSize - bSize) / bSize) * 100;
106-
const state = isEqual
107-
? 'Equal'
108-
: bSize > cSize
109-
? 'Down'
110-
: 'Up';
142+
const percent = isEqual
143+
? 0
144+
: bSize === 0
145+
? 100
146+
: (Math.abs(cSize - bSize) / bSize) * 100;
147+
const state = isEqual ? 'Equal' : bSize > cSize ? 'Down' : 'Up';
111148
return { percent, state };
112149
};
113150

114-
const diffAssetsByExtensions = (baseline: ChunkGraph, current: ChunkGraph, filterOrExtensions?: string | string[] | ((asset: Asset) => boolean), isInitial = false) => {
151+
const diffAssetsByExtensions = (
152+
baseline: ChunkGraph,
153+
current: ChunkGraph,
154+
filterOrExtensions?: string | string[] | ((asset: Asset) => boolean),
155+
isInitial = false,
156+
) => {
115157
const { size: bSize, count: bCount } = isInitial
116-
? getInitialAssetsSizeInfo(baseline.assets, baseline.chunks, { filterOrExtensions })
117-
: getAssetsSizeInfo(baseline.assets, baseline.chunks, { filterOrExtensions });
158+
? getInitialAssetsSizeInfo(baseline.assets, baseline.chunks, {
159+
filterOrExtensions,
160+
})
161+
: getAssetsSizeInfo(baseline.assets, baseline.chunks, {
162+
filterOrExtensions,
163+
});
118164

119165
const { size: cSize, count: cCount } = isInitial
120-
? getInitialAssetsSizeInfo(current.assets, current.chunks, { filterOrExtensions })
166+
? getInitialAssetsSizeInfo(current.assets, current.chunks, {
167+
filterOrExtensions,
168+
})
121169
: getAssetsSizeInfo(current.assets, current.chunks, { filterOrExtensions });
122170

123171
const { percent, state } = diffSize(bSize, cSize);
@@ -136,11 +184,21 @@ const getAssetsDiffResult = (baseline: ChunkGraph, current: ChunkGraph) => ({
136184
},
137185
js: {
138186
total: diffAssetsByExtensions(baseline, current, Constants.JSExtension),
139-
initial: diffAssetsByExtensions(baseline, current, Constants.JSExtension, true),
187+
initial: diffAssetsByExtensions(
188+
baseline,
189+
current,
190+
Constants.JSExtension,
191+
true,
192+
),
140193
},
141194
css: {
142195
total: diffAssetsByExtensions(baseline, current, Constants.CSSExtension),
143-
initial: diffAssetsByExtensions(baseline, current, Constants.CSSExtension, true),
196+
initial: diffAssetsByExtensions(
197+
baseline,
198+
current,
199+
Constants.CSSExtension,
200+
true,
201+
),
144202
},
145203
imgs: {
146204
total: diffAssetsByExtensions(baseline, current, Constants.ImgExtensions),
@@ -161,7 +219,11 @@ const getAssetsDiffResult = (baseline: ChunkGraph, current: ChunkGraph) => ({
161219
(asset: Asset) =>
162220
!isAssetMatchExtensions(
163221
asset,
164-
[Constants.JSExtension, Constants.CSSExtension, Constants.HtmlExtension].concat(
222+
[
223+
Constants.JSExtension,
224+
Constants.CSSExtension,
225+
Constants.HtmlExtension,
226+
].concat(
165227
Constants.ImgExtensions,
166228
Constants.MediaExtensions,
167229
Constants.FontExtensions,
@@ -172,7 +234,11 @@ const getAssetsDiffResult = (baseline: ChunkGraph, current: ChunkGraph) => ({
172234
},
173235
});
174236

175-
export async function listAssets(): Promise<{ ok: boolean; data: unknown; description: string }> {
237+
export async function listAssets(): Promise<{
238+
ok: boolean;
239+
data: unknown;
240+
description: string;
241+
}> {
176242
const assets = await getAssets();
177243
return {
178244
ok: true,
@@ -181,7 +247,14 @@ export async function listAssets(): Promise<{ ok: boolean; data: unknown; descri
181247
};
182248
}
183249

184-
export async function diffAssets(baselineInput: string | undefined, currentInput: string | undefined): Promise<{ ok: boolean; data: { note: string; baseline: string; current: string; diff: unknown }; description: string }> {
250+
export async function diffAssets(
251+
baselineInput: string | undefined,
252+
currentInput: string | undefined,
253+
): Promise<{
254+
ok: boolean;
255+
data: { note: string; baseline: string; current: string; diff: unknown };
256+
description: string;
257+
}> {
185258
const baselinePath = path.resolve(requireArg(baselineInput, 'baseline'));
186259
const currentPath = path.resolve(requireArg(currentInput, 'current'));
187260

@@ -203,17 +276,22 @@ export async function diffAssets(baselineInput: string | undefined, currentInput
203276
current: currentPath,
204277
diff,
205278
},
206-
description: 'Diff asset counts and sizes between two rsdoctor-data.json files (baseline vs current).',
279+
description:
280+
'Diff asset counts and sizes between two rsdoctor-data.json files (baseline vs current).',
207281
};
208282
}
209283

210-
export async function getMediaAssets(): Promise<{ ok: boolean; data: { guidance: string; chunks: unknown }; description: string }> {
284+
export async function getMediaAssets(): Promise<{
285+
ok: boolean;
286+
data: { guidance: string; chunks: unknown };
287+
description: string;
288+
}> {
211289
// Get all chunks by using a very large page size
212290
const chunksResult = await getAllChunks(1, Number.MAX_SAFE_INTEGER);
213291
const chunksResultTyped = chunksResult as { items?: unknown } | unknown[];
214-
const chunks = Array.isArray(chunksResultTyped)
215-
? chunksResultTyped
216-
: (chunksResultTyped.items || chunksResult);
292+
const chunks = Array.isArray(chunksResultTyped)
293+
? chunksResultTyped
294+
: chunksResultTyped.items || chunksResult;
217295
return {
218296
ok: true,
219297
data: {
@@ -224,8 +302,13 @@ export async function getMediaAssets(): Promise<{ ok: boolean; data: { guidance:
224302
};
225303
}
226304

227-
export function registerAssetCommands(program: Command, execute: CommandExecutor): void {
228-
const assetProgram = program.command('assets').description('Asset operations');
305+
export function registerAssetCommands(
306+
program: Command,
307+
execute: CommandExecutor,
308+
): void {
309+
const assetProgram = program
310+
.command('assets')
311+
.description('Asset operations');
229312

230313
assetProgram
231314
.command('list')
@@ -236,7 +319,9 @@ export function registerAssetCommands(program: Command, execute: CommandExecutor
236319

237320
assetProgram
238321
.command('diff')
239-
.description('Diff asset counts and sizes between two rsdoctor-data.json files (baseline vs current).')
322+
.description(
323+
'Diff asset counts and sizes between two rsdoctor-data.json files (baseline vs current).',
324+
)
240325
.requiredOption('--baseline <path>', 'Path to baseline rsdoctor-data.json')
241326
.requiredOption('--current <path>', 'Path to current rsdoctor-data.json')
242327
.action(function (this: Command) {

0 commit comments

Comments
 (0)