Skip to content

Commit feaae8a

Browse files
committed
feat: support rsdoctor skills
1 parent ad5379b commit feaae8a

File tree

4 files changed

+119
-176
lines changed

4 files changed

+119
-176
lines changed

skills/rsdoctor/infra/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
},
1212
"devDependencies": {
1313
"@rslib/core": "^0.18.4",
14-
"@types/node": "^24.5.0",
15-
"typescript": "^5.6.3",
16-
"zod": "^4.1.13"
14+
"@types/node": "^20.14.8",
15+
"typescript": "^5.4.2",
16+
"zod": "^3.25.4"
1717
}
1818
}

skills/rsdoctor/infra/pnpm-lock.yaml

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

skills/rsdoctor/infra/rsdoctor/command.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,24 @@ program
2121
.showSuggestionAfterError();
2222

2323
export const execute = async (handler: () => Promise<unknown>): Promise<void> => {
24+
// Parse compact option once at the beginning
25+
const opts = program.opts<{ compact?: boolean | string }>();
26+
const compact = opts.compact === true || opts.compact === 'true';
27+
const spacing = compact ? 0 : 2;
28+
2429
try {
2530
const result = await handler();
2631
// Format result similar to old format
2732
if (result && typeof result === 'object' && 'ok' in result) {
28-
const opts = program.opts<{ compact?: boolean | string }>();
29-
const compact = opts.compact === true || opts.compact === 'true';
30-
const spacing = compact ? 0 : 2;
3133
console.log(JSON.stringify(result, null, spacing));
3234
if (!(result as { ok: boolean }).ok) {
3335
process.exit(1);
3436
}
3537
} else {
36-
const opts = program.opts<{ compact?: boolean | string }>();
37-
printResult(result, opts.compact === true || opts.compact === 'true');
38+
printResult(result, compact);
3839
}
3940
} catch (error) {
4041
const message = error instanceof Error ? error.message : String(error);
41-
const opts = program.opts<{ compact?: boolean | string }>();
42-
const compact = opts.compact === true || opts.compact === 'true';
43-
const spacing = compact ? 0 : 2;
4442
console.log(
4543
JSON.stringify(
4644
{

skills/rsdoctor/infra/rsdoctor/commands/build.ts

Lines changed: 95 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,96 @@ export async function getConfig(): Promise<{ ok: boolean; data: unknown; descrip
4848
};
4949
}
5050

51+
/**
52+
* Helper function to execute step 1 logic: basic optimization analysis
53+
* Returns the step 1 data structure without the wrapper
54+
*/
55+
async function executeStep1(): Promise<{
56+
duplicatePackages: { ok: boolean; data: unknown };
57+
similarPackages: { ok: boolean; data: unknown };
58+
mediaAssets: { ok: boolean; data: unknown };
59+
largeChunks: { ok: boolean; data: unknown };
60+
}> {
61+
const [rules, packages, chunksResult] = await Promise.all([
62+
getRuleInfo(),
63+
getPackageInfo(),
64+
getAllChunks(1, Number.MAX_SAFE_INTEGER),
65+
]);
66+
67+
// Extract chunks from paginated result if needed
68+
const chunksResultTyped = chunksResult as { items?: Chunk[] } | Chunk[];
69+
const chunks = Array.isArray(chunksResultTyped)
70+
? chunksResultTyped
71+
: (chunksResultTyped.items || []);
72+
73+
// Detect duplicates
74+
const rulesArray = rules as Rule[];
75+
const duplicateRule = rulesArray?.find((rule) => rule.description?.includes('E1001'));
76+
77+
// Detect similar packages
78+
const packagesArray = packages as Package[];
79+
const similarRules = [
80+
['lodash', 'lodash-es', 'string_decode'],
81+
['dayjs', 'moment', 'date-fns', 'js-joda'],
82+
['antd', 'material-ui', 'semantic-ui-react', 'arco-design'],
83+
['axios', 'node-fetch'],
84+
['redux', 'mobx', 'zustand', 'recoil', 'jotai'],
85+
['chalk', 'colors', 'picocolors', 'kleur'],
86+
['fs-extra', 'graceful-fs'],
87+
];
88+
89+
const similarMatches = similarRules
90+
.map((group) => {
91+
const found = group.filter((pkg) =>
92+
packagesArray.some((p) => p.name.toLowerCase() === pkg.toLowerCase()),
93+
);
94+
return found.length > 1 ? found : null;
95+
})
96+
.filter((match): match is string[] => match !== null);
97+
98+
// Get media assets
99+
const mediaAssets = { guidance: 'Media asset optimization guidance.', chunks };
100+
101+
// Find large chunks (>30% over median size and >= 1MB)
102+
const chunksArray = chunks as Chunk[];
103+
const median = chunksArray.length ? getMedianChunkSize(chunksArray) : 0;
104+
const operator = 1.3;
105+
const minSizeMB = 1;
106+
const minSizeBytes = minSizeMB * 1024 * 1024; // 1MB in bytes
107+
const oversized = chunksArray.filter((chunk) => chunk.size > median * operator && chunk.size >= minSizeBytes);
108+
109+
return {
110+
duplicatePackages: {
111+
ok: true,
112+
data: {
113+
rule: duplicateRule ?? null,
114+
totalRules: rulesArray?.length ?? 0,
115+
note: duplicateRule
116+
? undefined
117+
: 'No E1001 duplicate package rule found in current analysis.',
118+
},
119+
},
120+
similarPackages: {
121+
ok: true,
122+
data: {
123+
similarPackages: similarMatches,
124+
totalPackages: packagesArray.length,
125+
note: similarMatches.length
126+
? undefined
127+
: 'No similar package groups detected in current analysis.',
128+
},
129+
},
130+
mediaAssets: {
131+
ok: true,
132+
data: mediaAssets,
133+
},
134+
largeChunks: {
135+
ok: true,
136+
data: { median, operator, minSizeMB, oversized },
137+
},
138+
};
139+
}
140+
51141
export async function optimizeBundle(
52142
stepInput?: string,
53143
sideEffectsPageNumberInput?: string,
@@ -57,86 +147,12 @@ export async function optimizeBundle(
57147

58148
// Step 1: Get basic optimization data (rules, packages, chunks)
59149
if (step === 1) {
60-
const [rules, packages, chunksResult] = await Promise.all([
61-
getRuleInfo(),
62-
getPackageInfo(),
63-
getAllChunks(1, Number.MAX_SAFE_INTEGER),
64-
]);
65-
66-
// Extract chunks from paginated result if needed
67-
const chunksResultTyped = chunksResult as { items?: Chunk[] } | Chunk[];
68-
const chunks = Array.isArray(chunksResultTyped)
69-
? chunksResultTyped
70-
: (chunksResultTyped.items || []);
71-
72-
// Detect duplicates
73-
const rulesArray = rules as Rule[];
74-
const duplicateRule = rulesArray?.find((rule) => rule.description?.includes('E1001'));
75-
76-
// Detect similar packages
77-
const packagesArray = packages as Package[];
78-
const similarRules = [
79-
['lodash', 'lodash-es', 'string_decode'],
80-
['dayjs', 'moment', 'date-fns', 'js-joda'],
81-
['antd', 'material-ui', 'semantic-ui-react', 'arco-design'],
82-
['axios', 'node-fetch'],
83-
['redux', 'mobx', 'zustand', 'recoil', 'jotai'],
84-
['chalk', 'colors', 'picocolors', 'kleur'],
85-
['fs-extra', 'graceful-fs'],
86-
];
87-
88-
const similarMatches = similarRules
89-
.map((group) => {
90-
const found = group.filter((pkg) =>
91-
packagesArray.some((p) => p.name.toLowerCase() === pkg.toLowerCase()),
92-
);
93-
return found.length > 1 ? found : null;
94-
})
95-
.filter((match): match is string[] => match !== null);
96-
97-
// Get media assets
98-
const mediaAssets = { guidance: 'Media asset optimization guidance.', chunks };
99-
100-
// Find large chunks (>30% over median size and >= 1MB)
101-
const chunksArray = chunks as Chunk[];
102-
const median = chunksArray.length ? getMedianChunkSize(chunksArray) : 0;
103-
const operator = 1.3;
104-
const minSizeMB = 1;
105-
const minSizeBytes = minSizeMB * 1024 * 1024; // 1MB in bytes
106-
const oversized = chunksArray.filter((chunk) => chunk.size > median * operator && chunk.size >= minSizeBytes);
107-
150+
const step1Data = await executeStep1();
108151
return {
109152
ok: true,
110153
data: {
111154
step: 1,
112-
duplicatePackages: {
113-
ok: true,
114-
data: {
115-
rule: duplicateRule ?? null,
116-
totalRules: rulesArray?.length ?? 0,
117-
note: duplicateRule
118-
? undefined
119-
: 'No E1001 duplicate package rule found in current analysis.',
120-
},
121-
},
122-
similarPackages: {
123-
ok: true,
124-
data: {
125-
similarPackages: similarMatches,
126-
totalPackages: packagesArray.length,
127-
note: similarMatches.length
128-
? undefined
129-
: 'No similar package groups detected in current analysis.',
130-
},
131-
},
132-
mediaAssets: {
133-
ok: true,
134-
data: mediaAssets,
135-
},
136-
largeChunks: {
137-
ok: true,
138-
data: { median, operator, minSizeMB, oversized },
139-
},
155+
...step1Data,
140156
note: 'Step 1 completed. Use --step 2 to get side effects modules.',
141157
},
142158
description:
@@ -175,86 +191,15 @@ export async function optimizeBundle(
175191
const defaultPageNumber = parsePositiveInt(sideEffectsPageNumberInput, 'sideEffectsPageNumber', { min: 1 }) ?? 1;
176192
const defaultPageSize = parsePositiveInt(sideEffectsPageSizeInput, 'sideEffectsPageSize', { min: 1, max: 1000 }) ?? 100;
177193

178-
const [rules, packages, chunksResult, sideEffectsData] = await Promise.all([
179-
getRuleInfo(),
180-
getPackageInfo(),
181-
getAllChunks(1, Number.MAX_SAFE_INTEGER),
194+
const [step1Data, sideEffectsData] = await Promise.all([
195+
executeStep1(),
182196
sendRequest(API.GetSideEffects, { pageNumber: defaultPageNumber, pageSize: defaultPageSize }),
183197
]);
184-
185-
// Extract chunks from paginated result if needed
186-
const chunksResultTyped = chunksResult as { items?: Chunk[] } | Chunk[];
187-
const chunks = Array.isArray(chunksResultTyped)
188-
? chunksResultTyped
189-
: (chunksResultTyped.items || []);
190-
191-
// Detect duplicates
192-
const rulesArray = rules as Rule[];
193-
const duplicateRule = rulesArray?.find((rule) => rule.description?.includes('E1001'));
194-
195-
// Detect similar packages
196-
const packagesArray = packages as Package[];
197-
const similarRules = [
198-
['lodash', 'lodash-es', 'string_decode'],
199-
['dayjs', 'moment', 'date-fns', 'js-joda'],
200-
['antd', 'material-ui', 'semantic-ui-react', 'arco-design'],
201-
['axios', 'node-fetch'],
202-
['redux', 'mobx', 'zustand', 'recoil', 'jotai'],
203-
['chalk', 'colors', 'picocolors', 'kleur'],
204-
['fs-extra', 'graceful-fs'],
205-
];
206-
207-
const similarMatches = similarRules
208-
.map((group) => {
209-
const found = group.filter((pkg) =>
210-
packagesArray.some((p) => p.name.toLowerCase() === pkg.toLowerCase()),
211-
);
212-
return found.length > 1 ? found : null;
213-
})
214-
.filter((match): match is string[] => match !== null);
215-
216-
// Get media assets
217-
const mediaAssets = { guidance: 'Media asset optimization guidance.', chunks };
218-
219-
// Find large chunks (>30% over median size and >= 1MB)
220-
const chunksArray = chunks as Chunk[];
221-
const median = chunksArray.length ? getMedianChunkSize(chunksArray) : 0;
222-
const operator = 1.3;
223-
const minSizeMB = 1;
224-
const minSizeBytes = minSizeMB * 1024 * 1024; // 1MB in bytes
225-
const oversized = chunksArray.filter((chunk) => chunk.size > median * operator && chunk.size >= minSizeBytes);
226198

227199
return {
228200
ok: true,
229201
data: {
230-
duplicatePackages: {
231-
ok: true,
232-
data: {
233-
rule: duplicateRule ?? null,
234-
totalRules: rulesArray?.length ?? 0,
235-
note: duplicateRule
236-
? undefined
237-
: 'No E1001 duplicate package rule found in current analysis.',
238-
},
239-
},
240-
similarPackages: {
241-
ok: true,
242-
data: {
243-
similarPackages: similarMatches,
244-
totalPackages: packagesArray.length,
245-
note: similarMatches.length
246-
? undefined
247-
: 'No similar package groups detected in current analysis.',
248-
},
249-
},
250-
mediaAssets: {
251-
ok: true,
252-
data: mediaAssets,
253-
},
254-
largeChunks: {
255-
ok: true,
256-
data: { median, operator, minSizeMB, oversized },
257-
},
202+
...step1Data,
258203
sideEffectsModules: {
259204
ok: true,
260205
data: sideEffectsData,

0 commit comments

Comments
 (0)