@mui/internal-bundle-size-checker 1.0.9-canary.3 → 1.0.9-canary.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/internal-bundle-size-checker",
3
- "version": "1.0.9-canary.3",
3
+ "version": "1.0.9-canary.5",
4
4
  "description": "Bundle size checker for MUI packages.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -22,6 +22,7 @@
22
22
  "@aws-sdk/client-s3": "^3.515.0",
23
23
  "@aws-sdk/credential-providers": "^3.787.0",
24
24
  "@babel/core": "^7.27.4",
25
+ "@octokit/rest": "^22.0.0",
25
26
  "@babel/preset-react": "^7.18.6",
26
27
  "@babel/preset-typescript": "^7.27.1",
27
28
  "babel-loader": "^10.0.0",
@@ -32,6 +33,7 @@
32
33
  "execa": "^7.2.0",
33
34
  "fast-glob": "^3.3.2",
34
35
  "file-loader": "^6.2.0",
36
+ "git-url-parse": "^16.1.0",
35
37
  "micromatch": "^4.0.8",
36
38
  "piscina": "^4.2.1",
37
39
  "rollup-plugin-visualizer": "^6.0.1",
@@ -48,7 +50,7 @@
48
50
  "@types/webpack-bundle-analyzer": "^4.7.0",
49
51
  "@types/yargs": "^17.0.33"
50
52
  },
51
- "gitSha": "d07b12b0d9e2420d77cb9b7212b836e588b03e1d",
53
+ "gitSha": "8ddf4c81e7d28c0a1b0aa151d76697873870f123",
52
54
  "scripts": {
53
55
  "typescript": "tsc -p tsconfig.json",
54
56
  "test": "pnpm -w test --project @mui/internal-bundle-size-checker"
package/src/cli.js CHANGED
@@ -6,8 +6,12 @@ import fs from 'fs/promises';
6
6
  import yargs from 'yargs';
7
7
  import { Piscina } from 'piscina';
8
8
  import micromatch from 'micromatch';
9
+ import { execa } from 'execa';
10
+ import gitUrlParse from 'git-url-parse';
9
11
  import { loadConfig } from './configLoader.js';
10
12
  import { uploadSnapshot } from './uploadSnapshot.js';
13
+ import { renderMarkdownReport } from './renderMarkdownReport.js';
14
+ import { octokit } from './github.js';
11
15
 
12
16
  /**
13
17
  * @typedef {import('./sizeDiff.js').SizeSnapshot} SizeSnapshot
@@ -18,6 +22,26 @@ const DEFAULT_CONCURRENCY = os.availableParallelism();
18
22
 
19
23
  const rootDir = process.cwd();
20
24
 
25
+ /**
26
+ * Gets the current repository owner and name from git remote
27
+ * @returns {Promise<{owner: string | null, repo: string | null}>}
28
+ */
29
+ async function getCurrentRepoInfo() {
30
+ try {
31
+ const { stdout } = await execa('git', ['remote', 'get-url', 'origin']);
32
+ const parsed = gitUrlParse(stdout.trim());
33
+ return {
34
+ owner: parsed.owner,
35
+ repo: parsed.name,
36
+ };
37
+ } catch (error) {
38
+ return {
39
+ owner: null,
40
+ repo: null,
41
+ };
42
+ }
43
+ }
44
+
21
45
  /**
22
46
  * creates size snapshot for every bundle that built with webpack
23
47
  * @param {CommandLineArgs} args
@@ -72,6 +96,42 @@ async function getWebpackSizes(args, config) {
72
96
  return sizeArrays.flat();
73
97
  }
74
98
 
99
+ /**
100
+ * Report command handler
101
+ * @param {ReportCommandArgs} argv - Command line arguments
102
+ */
103
+ async function reportCommand(argv) {
104
+ const { pr, owner: argOwner, repo: argRepo } = argv;
105
+
106
+ // Get current repo info and coerce with provided arguments
107
+ const currentRepo = await getCurrentRepoInfo();
108
+ const owner = argOwner ?? currentRepo.owner;
109
+ const repo = argRepo ?? currentRepo.repo;
110
+
111
+ if (typeof pr !== 'number') {
112
+ throw new Error('Invalid pull request number. Please provide a valid --pr option.');
113
+ }
114
+
115
+ // Validate that both owner and repo are available
116
+ if (!owner || !repo) {
117
+ throw new Error(
118
+ 'Repository owner and name are required. Please provide --owner and --repo options, or run this command from within a git repository.',
119
+ );
120
+ }
121
+
122
+ // Fetch PR information
123
+ const { data: prInfo } = await octokit.pulls.get({
124
+ owner,
125
+ repo,
126
+ pull_number: pr,
127
+ });
128
+
129
+ // Generate and print the markdown report
130
+ const report = await renderMarkdownReport(prInfo);
131
+ // eslint-disable-next-line no-console
132
+ console.log(report);
133
+ }
134
+
75
135
  /**
76
136
  * Main runner function
77
137
  * @param {CommandLineArgs} argv - Command line arguments
@@ -113,52 +173,76 @@ async function run(argv) {
113
173
  }
114
174
 
115
175
  yargs(process.argv.slice(2))
116
- // @ts-expect-error
117
- .command({
118
- command: '$0',
119
- describe: 'Saves a size snapshot in size-snapshot.json',
120
- builder: (cmdYargs) => {
121
- return cmdYargs
122
- .option('analyze', {
123
- default: false,
124
- describe: 'Creates a webpack-bundle-analyzer report for each bundle.',
125
- type: 'boolean',
126
- })
127
- .option('accurateBundles', {
128
- default: false,
129
- describe: 'Displays used bundles accurately at the cost of more CPU cycles.',
130
- type: 'boolean',
131
- })
132
- .option('verbose', {
133
- default: false,
134
- describe: 'Show more detailed information during compilation.',
135
- type: 'boolean',
136
- })
137
- .option('vite', {
138
- default: false,
139
- describe: 'Use Vite instead of webpack for bundling.',
140
- type: 'boolean',
141
- })
142
- .option('output', {
143
- alias: 'o',
144
- describe:
145
- 'Path to output the size snapshot JSON file (defaults to size-snapshot.json in current directory).',
146
- type: 'string',
147
- })
148
- .option('filter', {
149
- alias: 'F',
150
- describe: 'Filter entry points by glob pattern(s) applied to their IDs',
151
- type: 'array',
152
- })
153
- .option('concurrency', {
154
- alias: 'c',
155
- describe: 'Number of workers to use for parallel processing',
156
- type: 'number',
157
- default: DEFAULT_CONCURRENCY,
158
- });
159
- },
160
- handler: run,
161
- })
176
+ .command(
177
+ /** @type {import('yargs').CommandModule<{}, CommandLineArgs>} */ ({
178
+ command: '$0',
179
+ describe: 'Saves a size snapshot in size-snapshot.json',
180
+ builder: (cmdYargs) => {
181
+ return cmdYargs
182
+ .option('analyze', {
183
+ default: false,
184
+ describe: 'Creates a webpack-bundle-analyzer report for each bundle.',
185
+ type: 'boolean',
186
+ })
187
+ .option('accurateBundles', {
188
+ default: false,
189
+ describe: 'Displays used bundles accurately at the cost of more CPU cycles.',
190
+ type: 'boolean',
191
+ })
192
+ .option('verbose', {
193
+ default: false,
194
+ describe: 'Show more detailed information during compilation.',
195
+ type: 'boolean',
196
+ })
197
+ .option('vite', {
198
+ default: false,
199
+ describe: 'Use Vite instead of webpack for bundling.',
200
+ type: 'boolean',
201
+ })
202
+ .option('output', {
203
+ alias: 'o',
204
+ describe:
205
+ 'Path to output the size snapshot JSON file (defaults to size-snapshot.json in current directory).',
206
+ type: 'string',
207
+ })
208
+ .option('filter', {
209
+ alias: 'F',
210
+ describe: 'Filter entry points by glob pattern(s) applied to their IDs',
211
+ type: 'array',
212
+ })
213
+ .option('concurrency', {
214
+ alias: 'c',
215
+ describe: 'Number of workers to use for parallel processing',
216
+ type: 'number',
217
+ default: DEFAULT_CONCURRENCY,
218
+ });
219
+ },
220
+ handler: run,
221
+ }),
222
+ )
223
+ .command(
224
+ /** @type {import('yargs').CommandModule<{}, ReportCommandArgs>} */ ({
225
+ command: 'report',
226
+ describe: 'Generate a markdown report for a pull request',
227
+ builder: (cmdYargs) => {
228
+ return cmdYargs
229
+ .option('pr', {
230
+ describe: 'Pull request number',
231
+ type: 'number',
232
+ demandOption: true,
233
+ })
234
+ .option('owner', {
235
+ describe: 'Repository owner (defaults to current git repo owner)',
236
+ type: 'string',
237
+ })
238
+ .option('repo', {
239
+ describe: 'Repository name (defaults to current git repo name)',
240
+ type: 'string',
241
+ });
242
+ },
243
+ handler: reportCommand,
244
+ }),
245
+ )
162
246
  .help()
163
247
  .strict(true)
164
248
  .version(false)
@@ -1,3 +1,5 @@
1
+ import { octokit } from './github.js';
2
+
1
3
  /**
2
4
  *
3
5
  * @param {string} repo - The name of the repository e.g. 'mui/material-ui'
@@ -44,15 +46,15 @@ export async function fetchSnapshot(repo, sha) {
44
46
  */
45
47
  async function getParentCommits(repo, commit, depth = 4) {
46
48
  try {
47
- const response = await fetch(
48
- `https://api.github.com/repos/${repo}/commits?sha=${commit}&per_page=${depth}`,
49
- );
50
- if (!response.ok) {
51
- throw new Error(`GitHub API request failed: ${response.status}`);
52
- }
49
+ const [owner, repoName] = repo.split('/');
50
+
51
+ const { data: commits } = await octokit.repos.listCommits({
52
+ owner,
53
+ repo: repoName,
54
+ sha: commit,
55
+ per_page: depth,
56
+ });
53
57
 
54
- /** @type {{ sha: string }[]} */
55
- const commits = await response.json();
56
58
  // Skip the first commit (which is the starting commit) and return the rest
57
59
  return commits.slice(1).map((commitDetails) => commitDetails.sha);
58
60
  } catch (/** @type {any} */ error) {
package/src/github.js ADDED
@@ -0,0 +1,7 @@
1
+ // @ts-check
2
+
3
+ import { Octokit } from '@octokit/rest';
4
+
5
+ // Create and export Octokit instance
6
+ /** @type {import('@octokit/rest').Octokit} */
7
+ export const octokit = new Octokit();
@@ -7,6 +7,7 @@
7
7
  import { calculateSizeDiff } from './sizeDiff.js';
8
8
  import { fetchSnapshot, fetchSnapshotWithFallback } from './fetchSnapshot.js';
9
9
  import { displayPercentFormatter, byteSizeChangeFormatter } from './formatUtils.js';
10
+ import { octokit } from './github.js';
10
11
 
11
12
  /**
12
13
  * Generates a symbol based on the relative change value.
@@ -224,11 +225,19 @@ function getDetailsUrl(prInfo, options = {}) {
224
225
  export async function renderMarkdownReport(prInfo, circleciBuildNumber, options = {}) {
225
226
  let markdownContent = '';
226
227
 
227
- const baseCommit = prInfo.base.sha;
228
228
  const prCommit = prInfo.head.sha;
229
229
  const repo = prInfo.base.repo.full_name;
230
230
  const { fallbackDepth = 3 } = options;
231
231
 
232
+ const [owner, repoName] = repo.split('/');
233
+ const { data } = await octokit.repos.compareCommits({
234
+ owner,
235
+ repo: repoName,
236
+ base: prInfo.base.sha,
237
+ head: prCommit,
238
+ });
239
+ const baseCommit = data.merge_base_commit.sha;
240
+
232
241
  const [baseResult, prSnapshot] = await Promise.all([
233
242
  fetchSnapshotWithFallback(repo, baseCommit, fallbackDepth),
234
243
  fetchSnapshot(repo, prCommit),
@@ -237,9 +246,9 @@ export async function renderMarkdownReport(prInfo, circleciBuildNumber, options
237
246
  const { snapshot: baseSnapshot, actualCommit: actualBaseCommit } = baseResult;
238
247
 
239
248
  if (!baseSnapshot) {
240
- markdownContent += `_:no_entry_sign: No bundle size snapshot found for base commit ${baseCommit} or any of its ${fallbackDepth} parent commits._\n\n`;
249
+ markdownContent += `_:no_entry_sign: No bundle size snapshot found for merge base ${baseCommit} or any of its ${fallbackDepth} parent commits._\n\n`;
241
250
  } else if (actualBaseCommit !== baseCommit) {
242
- markdownContent += `_:information_source: Using snapshot from parent commit ${actualBaseCommit} (fallback from ${baseCommit})._\n\n`;
251
+ markdownContent += `_:information_source: Using snapshot from parent commit ${actualBaseCommit} (fallback from merge base ${baseCommit})._\n\n`;
243
252
  }
244
253
 
245
254
  const sizeDiff = calculateSizeDiff(baseSnapshot ?? {}, prSnapshot);
@@ -5,6 +5,18 @@ import * as fetchSnapshotModule from './fetchSnapshot.js';
5
5
 
6
6
  // Mock the fetchSnapshot module
7
7
  vi.mock('./fetchSnapshot.js');
8
+ // Mock the @octokit/rest module
9
+ vi.mock('@octokit/rest', () => ({
10
+ Octokit: vi.fn(() => ({
11
+ repos: {
12
+ compareCommits: vi.fn(),
13
+ listCommits: vi.fn(),
14
+ },
15
+ pulls: {
16
+ get: vi.fn(),
17
+ },
18
+ })),
19
+ }));
8
20
 
9
21
  describe('renderMarkdownReport', () => {
10
22
  const mockFetchSnapshot = vi.mocked(fetchSnapshotModule.fetchSnapshot);
@@ -24,9 +36,26 @@ describe('renderMarkdownReport', () => {
24
36
  },
25
37
  };
26
38
 
27
- beforeEach(() => {
39
+ beforeEach(async () => {
28
40
  mockFetchSnapshot.mockClear();
29
41
  mockFetchSnapshotWithFallback.mockClear();
42
+
43
+ // Import and mock the octokit instance after mocking the module
44
+ const { octokit } = await import('./github.js');
45
+
46
+ // Set up default mock for compareCommits to return the base commit SHA
47
+ vi.mocked(octokit.repos.compareCommits).mockResolvedValue(
48
+ /** @type {any} */ ({
49
+ data: {
50
+ merge_base_commit: {
51
+ sha: mockPrInfo.base.sha,
52
+ },
53
+ },
54
+ }),
55
+ );
56
+
57
+ // Clear any previous mock calls
58
+ vi.mocked(octokit.repos.compareCommits).mockClear();
30
59
  });
31
60
 
32
61
  it('should generate markdown report with size increases', async () => {
@@ -109,7 +138,7 @@ describe('renderMarkdownReport', () => {
109
138
  const result = await renderMarkdownReport(mockPrInfo);
110
139
 
111
140
  expect(result).toContain(
112
- 'No bundle size snapshot found for base commit abc123 or any of its 3 parent commits.',
141
+ 'No bundle size snapshot found for merge base abc123 or any of its 3 parent commits.',
113
142
  );
114
143
  });
115
144
 
@@ -512,7 +541,9 @@ describe('renderMarkdownReport', () => {
512
541
 
513
542
  const result = await renderMarkdownReport(mockPrInfo);
514
543
 
515
- expect(result).toContain('Using snapshot from parent commit parent1 (fallback from abc123)');
544
+ expect(result).toContain(
545
+ 'Using snapshot from parent commit parent1 (fallback from merge base abc123)',
546
+ );
516
547
  expect(result).toContain('baseCommit=parent1');
517
548
  });
518
549
 
@@ -527,7 +558,7 @@ describe('renderMarkdownReport', () => {
527
558
  const result = await renderMarkdownReport(mockPrInfo);
528
559
 
529
560
  expect(result).toContain(
530
- 'No bundle size snapshot found for base commit abc123 or any of its 3 parent commits.',
561
+ 'No bundle size snapshot found for merge base abc123 or any of its 3 parent commits.',
531
562
  );
532
563
  });
533
564
 
@@ -548,7 +579,9 @@ describe('renderMarkdownReport', () => {
548
579
 
549
580
  const result = await renderMarkdownReport(mockPrInfo, undefined, { fallbackDepth: 1 });
550
581
 
551
- expect(result).toContain('Using snapshot from parent commit parent1 (fallback from abc123)');
582
+ expect(result).toContain(
583
+ 'Using snapshot from parent commit parent1 (fallback from merge base abc123)',
584
+ );
552
585
  expect(mockFetchSnapshotWithFallback).toHaveBeenCalledWith('mui/material-ui', 'abc123', 1);
553
586
  });
554
587
  });
package/src/types.d.ts CHANGED
@@ -73,6 +73,12 @@ interface CommandLineArgs {
73
73
  vite?: boolean;
74
74
  }
75
75
 
76
+ interface ReportCommandArgs {
77
+ pr?: number;
78
+ owner?: string;
79
+ repo?: string;
80
+ }
81
+
76
82
  // Diff command argument types
77
83
  interface DiffCommandArgs {
78
84
  base: string;