@mui/internal-code-infra 0.0.3-canary.44 โ†’ 0.0.3-canary.46

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.
@@ -1,6 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  declare const _default: import("yargs").CommandModule<{}, Args>;
3
3
  export default _default;
4
+ export type Commit = {
5
+ /**
6
+ * - Commit SHA
7
+ */
8
+ sha: string;
9
+ /**
10
+ * - Commit message
11
+ */
12
+ message: string;
13
+ /**
14
+ * - Commit author
15
+ */
16
+ author: string;
17
+ };
4
18
  export type PublicPackage = import("../utils/pnpm.mjs").PublicPackage;
5
19
  export type VersionInfo = import("../utils/pnpm.mjs").VersionInfo;
6
20
  export type PublishOptions = import("../utils/pnpm.mjs").PublishOptions;
@@ -9,4 +23,8 @@ export type Args = {
9
23
  * - Whether to run in dry-run mode
10
24
  */
11
25
  dryRun?: boolean | undefined;
26
+ /**
27
+ * - Whether to create GitHub releases for canary packages
28
+ */
29
+ githubRelease?: boolean | undefined;
12
30
  };
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * @param {Object} [options]
3
3
  * @param {boolean} [options.enableReactCompiler] - Whether the config is for spec files.
4
+ * @returns {import('eslint').Linter.Config[]}
4
5
  */
5
6
  export function createCoreConfig(options?: {
6
7
  enableReactCompiler?: boolean | undefined;
7
- }): import("eslint/config").Config[];
8
+ }): import("eslint").Linter.Config[];
@@ -3,7 +3,7 @@ declare const _default: {
3
3
  type: "problem";
4
4
  };
5
5
  create(context: import("eslint").Rule.RuleContext): {
6
- CallExpression(node: import("estree").CallExpression & import("eslint").Rule.NodeParentExtension): void;
6
+ CallExpression(node: import("estree").SimpleCallExpression & import("eslint").Rule.NodeParentExtension): void;
7
7
  };
8
8
  };
9
9
  export default _default;
@@ -10,6 +10,7 @@
10
10
  * @property {string} message
11
11
  * @property {string[]} labels
12
12
  * @property {number} prNumber
13
+ * @property {string} html_url
13
14
  * @property {{login: string; association: AuthorAssociation} | null} author
14
15
  */
15
16
  /**
@@ -46,6 +47,7 @@ export type FetchedCommitDetails = {
46
47
  message: string;
47
48
  labels: string[];
48
49
  prNumber: number;
50
+ html_url: string;
49
51
  author: {
50
52
  login: string;
51
53
  association: AuthorAssociation;
@@ -114,9 +114,9 @@ export function publishPackages(packages: PublicPackage[], options?: PublishOpti
114
114
  /**
115
115
  * Read package.json from a directory
116
116
  * @param {string} packagePath - Path to package directory
117
- * @returns {Promise<Object>} Parsed package.json content
117
+ * @returns {Promise<import('../cli/packageJson').PackageJson>} Parsed package.json content
118
118
  */
119
- export function readPackageJson(packagePath: string): Promise<Object>;
119
+ export function readPackageJson(packagePath: string): Promise<import("../cli/packageJson").PackageJson>;
120
120
  /**
121
121
  * Write package.json to a directory
122
122
  * @param {string} packagePath - Path to package directory
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/internal-code-infra",
3
- "version": "0.0.3-canary.44",
3
+ "version": "0.0.3-canary.46",
4
4
  "description": "Infra scripts and configs to be used across MUI repos.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -60,7 +60,7 @@
60
60
  "@babel/preset-typescript": "^7.28.5",
61
61
  "@eslint/compat": "^1.4.0",
62
62
  "@eslint/js": "^9.38.0",
63
- "@eslint/json": "^0.13.2",
63
+ "@eslint/json": "^0.14.0",
64
64
  "@inquirer/confirm": "^5.1.19",
65
65
  "@inquirer/select": "^4.4.0",
66
66
  "@napi-rs/keyring": "^1.2.0",
@@ -101,8 +101,8 @@
101
101
  "stylelint-config-standard": "^39.0.1",
102
102
  "typescript-eslint": "^8.46.2",
103
103
  "yargs": "^18.0.0",
104
- "@mui/internal-babel-plugin-display-name": "1.0.4-canary.8",
105
104
  "@mui/internal-babel-plugin-minify-errors": "2.0.8-canary.11",
105
+ "@mui/internal-babel-plugin-display-name": "1.0.4-canary.8",
106
106
  "@mui/internal-babel-plugin-resolve-imports": "2.0.7-canary.28"
107
107
  },
108
108
  "peerDependencies": {
@@ -140,7 +140,7 @@
140
140
  "publishConfig": {
141
141
  "access": "public"
142
142
  },
143
- "gitSha": "5e2086fe1de7af3c8403a2052ed42a98b66a4ab9",
143
+ "gitSha": "cc7986d986a6e81ea85f8493c128770ca17e5cef",
144
144
  "scripts": {
145
145
  "build": "tsc -p tsconfig.build.json",
146
146
  "typescript": "tsc -p tsconfig.json",
@@ -8,26 +8,390 @@
8
8
  * @typedef {import('../utils/pnpm.mjs').PublishOptions} PublishOptions
9
9
  */
10
10
 
11
+ import path from 'node:path';
12
+ import { createActionAuth } from '@octokit/auth-action';
13
+ import { Octokit } from '@octokit/rest';
11
14
  import { $ } from 'execa';
15
+ import gitUrlParse from 'git-url-parse';
12
16
  import * as semver from 'semver';
13
17
 
14
- /**
15
- * @typedef {Object} Args
16
- * @property {boolean} [dryRun] - Whether to run in dry-run mode
17
- */
18
-
19
18
  import {
20
- getWorkspacePackages,
19
+ getCurrentGitSha,
21
20
  getPackageVersionInfo,
21
+ getWorkspacePackages,
22
22
  publishPackages,
23
23
  readPackageJson,
24
- writePackageJson,
25
- getCurrentGitSha,
26
24
  semverMax,
25
+ writePackageJson,
27
26
  } from '../utils/pnpm.mjs';
28
27
 
28
+ /**
29
+ * @typedef {Object} Args
30
+ * @property {boolean} [dryRun] - Whether to run in dry-run mode
31
+ * @property {boolean} [githubRelease] - Whether to create GitHub releases for canary packages
32
+ */
33
+
29
34
  const CANARY_TAG = 'canary';
30
35
 
36
+ /**
37
+ * Get Octokit instance with authentication
38
+ * @returns {Octokit} Authenticated Octokit instance
39
+ */
40
+ function getOctokit() {
41
+ return new Octokit({ authStrategy: createActionAuth });
42
+ }
43
+
44
+ /**
45
+ * Get current repository info from git remote
46
+ * @returns {Promise<{owner: string, repo: string}>} Repository owner and name
47
+ */
48
+ async function getRepositoryInfo() {
49
+ try {
50
+ const result = await $`git remote get-url origin`;
51
+ const url = result.stdout.trim();
52
+
53
+ const parsed = gitUrlParse(url);
54
+ if (parsed.source !== 'github.com') {
55
+ throw new Error('Repository is not hosted on GitHub');
56
+ }
57
+
58
+ return {
59
+ owner: parsed.owner,
60
+ repo: parsed.name,
61
+ };
62
+ } catch (/** @type {any} */ error) {
63
+ throw new Error(`Failed to get repository info: ${error.message}`);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * @typedef {Object} Commit
69
+ * @property {string} sha - Commit SHA
70
+ * @property {string} message - Commit message
71
+ * @property {string} author - Commit author
72
+ */
73
+
74
+ /**
75
+ * @param {Object} param0
76
+ * @param {string} param0.packagePath
77
+ * @returns {Promise<Commit[]>} Commits between the tag and current HEAD for the package
78
+ */
79
+ async function fetchCommitsForPackage({ packagePath }) {
80
+ /**
81
+ * @type {Commit[]}
82
+ */
83
+ const results = [];
84
+ const fieldSeparator = '\u001f'; // ASCII unit separator is extremely unlikely to appear in git metadata
85
+ const formatArg = '--format=%H%x1f%s%x1f%an%x1f%ae'; // SHA, subject, author name, author email separated by unit separator
86
+ const res = $`git log --oneline --no-decorate ${
87
+ // to avoid escaping by execa
88
+ [formatArg]
89
+ } ${CANARY_TAG}..HEAD -- ${packagePath}`;
90
+ for await (const line of res) {
91
+ const commitLine = line.trimEnd();
92
+ if (!commitLine) {
93
+ continue;
94
+ }
95
+ const parts = commitLine.split(fieldSeparator);
96
+ if (parts.length < 3) {
97
+ console.error(`Failed to parse commit log line: ${commitLine}`);
98
+ continue;
99
+ }
100
+ const [sha, message, commitAuthor, commitEmail] = parts;
101
+ let author = commitAuthor;
102
+ // try to get github username from email
103
+ if (commitEmail) {
104
+ const emailUsername = commitEmail.split('@')[0];
105
+ if (emailUsername) {
106
+ const [, githubUserName] = emailUsername.split('+');
107
+ if (githubUserName) {
108
+ author = `@${githubUserName}`;
109
+ }
110
+ }
111
+ }
112
+ results.push({ sha, message, author });
113
+ }
114
+ return results;
115
+ }
116
+
117
+ const AUTHOR_EXCLUDE_LIST = ['renovate[bot]', 'dependabot[bot]'];
118
+
119
+ /**
120
+ * @param {string} message
121
+ * @returns {string}
122
+ */
123
+ function cleanupCommitMessage(message) {
124
+ // AI generated: clean up commit message by removing leading bracketed tokens except [breaking]
125
+ let msg = message || '';
126
+
127
+ // Extract and remove leading bracketed tokens like "[foo][bar] message"
128
+ const tokens = [];
129
+ const bracketRe = /^\s*\[([^\]]+)\]\s*/;
130
+ let match = msg.match(bracketRe);
131
+ while (match) {
132
+ tokens.push(match[1]);
133
+ msg = msg.slice(match[0].length);
134
+ match = msg.match(bracketRe);
135
+ }
136
+ msg = msg.trim();
137
+
138
+ // If any of the leading tokens is "breaking" keep that token (preserve original casing)
139
+ const breakingToken = tokens.find((t) => t.toLowerCase() === 'breaking');
140
+ const prefix = breakingToken ? `[${breakingToken}]${msg ? ' ' : ''}` : '';
141
+
142
+ return `${prefix}${msg}`.trim();
143
+ }
144
+
145
+ async function getPackageToDependencyMap() {
146
+ /**
147
+ * @type {(PublicPackage & { dependencies: Record<string, unknown>; private: boolean; })[]}
148
+ */
149
+ const packagesWithDeps = JSON.parse(
150
+ (await $`pnpm ls -r --json --exclude-peers --only-projects --prod`).stdout,
151
+ );
152
+ /** @type {Record<string, string[]>} */
153
+ const directPkgDependencies = packagesWithDeps
154
+ .filter((pkg) => !pkg.private)
155
+ .reduce((acc, pkg) => {
156
+ if (!pkg.name) {
157
+ return acc;
158
+ }
159
+ const deps = Object.keys(pkg.dependencies || {});
160
+ if (!deps.length) {
161
+ return acc;
162
+ }
163
+ acc[pkg.name] = deps;
164
+ return acc;
165
+ }, /** @type {Record<string, string[]>} */ ({}));
166
+ return directPkgDependencies;
167
+ }
168
+
169
+ /**
170
+ * @param {Record<string, string[]>} pkgGraph
171
+ */
172
+ function resolveTransitiveDependencies(pkgGraph = {}) {
173
+ // Compute transitive (nested) dependencies limited to workspace packages and avoid cycles.
174
+ const workspacePkgNames = new Set(Object.keys(pkgGraph));
175
+ const nestedMap = /** @type {Record<string, string[]>} */ ({});
176
+
177
+ /**
178
+ *
179
+ * @param {string} pkgName
180
+ * @returns {string[]}
181
+ */
182
+ const getTransitiveDeps = (pkgName) => {
183
+ /**
184
+ * @type {Set<string>}
185
+ */
186
+ const seen = new Set();
187
+ const stack = (pkgGraph[pkgName] || []).slice();
188
+
189
+ while (stack.length) {
190
+ const dep = stack.pop();
191
+ if (!dep || seen.has(dep)) {
192
+ continue;
193
+ }
194
+ // Only consider workspace packages for transitive expansion
195
+ if (!workspacePkgNames.has(dep)) {
196
+ // still record external deps as direct deps but don't traverse into them
197
+ seen.add(dep);
198
+ continue;
199
+ }
200
+ seen.add(dep);
201
+ const children = pkgGraph[dep] || [];
202
+ for (const c of children) {
203
+ if (!seen.has(c)) {
204
+ stack.push(c);
205
+ }
206
+ }
207
+ }
208
+
209
+ return Array.from(seen);
210
+ };
211
+
212
+ for (const name of Object.keys(pkgGraph)) {
213
+ nestedMap[name] = getTransitiveDeps(name);
214
+ }
215
+
216
+ return nestedMap;
217
+ }
218
+
219
+ /**
220
+ * Prepare changelog data for packages using GitHub API
221
+ * @param {PublicPackage[]} packagesToPublish - Packages that will be published
222
+ * @param {PublicPackage[]} allPackages - All packages in the repository
223
+ * @param {Map<string, string>} canaryVersions - Map of package names to their canary versions
224
+ * @returns {Promise<Map<string, string[]>>} Map of package names to their changelogs
225
+ */
226
+ async function prepareChangelogsFromGitCli(packagesToPublish, allPackages, canaryVersions) {
227
+ /**
228
+ * @type {Map<string, string[]>}
229
+ */
230
+ const changelogs = new Map();
231
+
232
+ await Promise.all(
233
+ packagesToPublish.map(async (pkg) => {
234
+ const commits = await fetchCommitsForPackage({ packagePath: pkg.path });
235
+ if (commits.length > 0) {
236
+ console.log(`Found ${commits.length} commits for package ${pkg.name}`);
237
+ }
238
+ const changeLogStrs = commits
239
+ // Exclude commits authored by bots
240
+ .filter(
241
+ // We want to allow commits from copilot or other AI tools, so only filter known bots
242
+ (commit) => !AUTHOR_EXCLUDE_LIST.includes(commit.author),
243
+ )
244
+ .map((commit) => `- ${cleanupCommitMessage(commit.message)} by ${commit.author}`);
245
+
246
+ if (changeLogStrs.length > 0) {
247
+ changelogs.set(pkg.name, changeLogStrs);
248
+ }
249
+ }),
250
+ );
251
+ // Second pass: check for dependency updates in other packages not part of git history
252
+ const pkgDependencies = await getPackageToDependencyMap();
253
+ const transitiveDependencies = resolveTransitiveDependencies(pkgDependencies);
254
+
255
+ for (let i = 0; i < allPackages.length; i += 1) {
256
+ const pkg = allPackages[i];
257
+ const depsToPublish = (transitiveDependencies[pkg.name] ?? []).filter((dep) =>
258
+ packagesToPublish.some((p) => p.name === dep),
259
+ );
260
+ if (depsToPublish.length === 0) {
261
+ continue;
262
+ }
263
+ const changelog = changelogs.get(pkg.name) ?? [];
264
+ changelog.push('- Updated dependencies:');
265
+ depsToPublish.forEach((dep) => {
266
+ const depVersion = canaryVersions.get(dep);
267
+ if (depVersion) {
268
+ changelog.push(` - Bumped \`${dep}@${depVersion}\``);
269
+ }
270
+ });
271
+ }
272
+ return changelogs;
273
+ }
274
+
275
+ /**
276
+ * Prepare changelog data for packages
277
+ * @param {PublicPackage[]} packagesToPublish - Packages that will be published
278
+ * @param {PublicPackage[]} allPackages - All packages in the repository
279
+ * @param {Map<string, string>} canaryVersions - Map of package names to their canary versions
280
+ * @returns {Promise<Map<string, string[]>>} Map of package names to their changelogs
281
+ */
282
+ async function prepareChangelogsForPackages(packagesToPublish, allPackages, canaryVersions) {
283
+ console.log('\n๐Ÿ“ Preparing changelogs for packages...');
284
+
285
+ const repoInfo = await getRepositoryInfo();
286
+ console.log(`๐Ÿ“‚ Repository: ${repoInfo.owner}/${repoInfo.repo}`);
287
+
288
+ /**
289
+ * @type {Map<string, string[]>}
290
+ */
291
+ const changelogs = await prepareChangelogsFromGitCli(
292
+ packagesToPublish,
293
+ allPackages,
294
+ canaryVersions,
295
+ );
296
+
297
+ // Log changelog content for each package
298
+ for (const pkg of packagesToPublish) {
299
+ const version = canaryVersions.get(pkg.name);
300
+ if (!version) {
301
+ continue;
302
+ }
303
+
304
+ const changelog = changelogs.get(pkg.name) || [];
305
+ console.log(`\n๐Ÿ“ฆ ${pkg.name}@${version}`);
306
+ if (changelog.length > 0) {
307
+ console.log(
308
+ ` Changelog:\n${changelog.map((/** @type {string} */ line) => ` ${line}`).join('\n')}`,
309
+ );
310
+ }
311
+ }
312
+
313
+ console.log('\nโœ… Changelogs prepared successfully');
314
+ return changelogs;
315
+ }
316
+
317
+ /**
318
+ * Create GitHub releases and tags for published packages
319
+ * @param {PublicPackage[]} publishedPackages - Packages that were published
320
+ * @param {Map<string, string>} canaryVersions - Map of package names to their canary versions
321
+ * @param {Map<string, string[]>} changelogs - Map of package names to their changelogs
322
+ * @param {{dryRun?: boolean}} options - Publishing options
323
+ * @returns {Promise<void>}
324
+ */
325
+ async function createGitHubReleasesForPackages(
326
+ publishedPackages,
327
+ canaryVersions,
328
+ changelogs,
329
+ options,
330
+ ) {
331
+ console.log('\n๐Ÿš€ Creating GitHub releases and tags for published packages...');
332
+
333
+ const repoInfo = await getRepositoryInfo();
334
+ const gitSha = await getCurrentGitSha();
335
+ const octokit = getOctokit();
336
+
337
+ for (const pkg of publishedPackages) {
338
+ const version = canaryVersions.get(pkg.name);
339
+ if (!version) {
340
+ console.log(`โš ๏ธ No version found for ${pkg.name}, skipping...`);
341
+ continue;
342
+ }
343
+
344
+ const changelog = changelogs.get(pkg.name);
345
+ if (!changelog) {
346
+ console.log(`โš ๏ธ No changelog found for ${pkg.name}, skipping release creation...`);
347
+ continue;
348
+ }
349
+ const tagName = `${pkg.name}@${version}`;
350
+ const releaseName = tagName;
351
+
352
+ console.log(`\n๐Ÿ“ฆ Processing ${pkg.name}@${version}...`);
353
+
354
+ // Create git tag
355
+ if (options.dryRun) {
356
+ console.log(`๐Ÿท๏ธ Would create and push git tag: ${tagName} (dry-run)`);
357
+ console.log(`๐Ÿ“ Would publish a Github release:`);
358
+ console.log(` - Name: ${releaseName}`);
359
+ console.log(` - Tag: ${tagName}`);
360
+ console.log(` - Body:\n${changelog.join('\n')}`);
361
+ } else {
362
+ // eslint-disable-next-line no-await-in-loop
363
+ await $({
364
+ env: {
365
+ ...process.env,
366
+ GIT_COMMITTER_NAME: 'Code infra',
367
+ GIT_COMMITTER_EMAIL: 'code-infra@mui.com',
368
+ },
369
+ })`git tag -a ${tagName} -m ${`Canary release ${pkg.name}@${version}`}`;
370
+
371
+ // eslint-disable-next-line no-await-in-loop
372
+ await $`git push origin ${tagName}`;
373
+ console.log(`โœ… Created and pushed git tag: ${tagName}`);
374
+
375
+ // Create GitHub release
376
+ // eslint-disable-next-line no-await-in-loop
377
+ const res = await octokit.repos.createRelease({
378
+ owner: repoInfo.owner,
379
+ repo: repoInfo.repo,
380
+ tag_name: tagName,
381
+ target_commitish: gitSha,
382
+ name: releaseName,
383
+ body: changelog.join('\n'),
384
+ draft: false,
385
+ prerelease: true, // Mark as prerelease since these are canary versions
386
+ });
387
+
388
+ console.log(`โœ… Created GitHub release: ${releaseName} at ${res.data.html_url}`);
389
+ }
390
+ }
391
+
392
+ console.log('\nโœ… Finished creating GitHub releases');
393
+ }
394
+
31
395
  /**
32
396
  * Check if the canary git tag exists
33
397
  * @returns {Promise<string|null>} Canary tag name if exists, null otherwise
@@ -66,11 +430,13 @@ async function createCanaryTag(dryRun = false) {
66
430
  }
67
431
 
68
432
  /**
69
- * Publish canary versions with updated dependencies
433
+ * Publish canary versions with updated dependencies. A big assumption here is that
434
+ * all packages are already built before calling this function.
435
+ *
70
436
  * @param {PublicPackage[]} packagesToPublish - Packages that need canary publishing
71
437
  * @param {PublicPackage[]} allPackages - All workspace packages
72
438
  * @param {Map<string, VersionInfo>} packageVersionInfo - Version info map
73
- * @param {PublishOptions} [options={}] - Publishing options
439
+ * @param {PublishOptions & {githubRelease?: boolean}} [options={}] - Publishing options
74
440
  * @returns {Promise<void>}
75
441
  */
76
442
  async function publishCanaryVersions(
@@ -90,7 +456,6 @@ async function publishCanaryVersions(
90
456
 
91
457
  const gitSha = await getCurrentGitSha();
92
458
  const canaryVersions = new Map();
93
- const originalPackageJsons = new Map();
94
459
 
95
460
  // First pass: determine canary version numbers for all packages
96
461
  const changedPackageNames = new Set(packagesToPublish.map((pkg) => pkg.name));
@@ -117,41 +482,44 @@ async function publishCanaryVersions(
117
482
  }
118
483
 
119
484
  // Second pass: read and update ALL package.json files in parallel
485
+ // Packages are already built at this point.
120
486
  const packageUpdatePromises = allPackages.map(async (pkg) => {
121
- const originalPackageJson = await readPackageJson(pkg.path);
487
+ let basePkgJson = await readPackageJson(pkg.path);
488
+ let pkgJsonDirectory = pkg.path;
489
+ if (basePkgJson.publishConfig?.directory) {
490
+ pkgJsonDirectory = path.join(pkg.path, basePkgJson.publishConfig.directory);
491
+ basePkgJson = await readPackageJson(pkgJsonDirectory);
492
+ }
122
493
 
123
494
  const canaryVersion = canaryVersions.get(pkg.name);
124
495
  if (canaryVersion) {
125
496
  const updatedPackageJson = {
126
- ...originalPackageJson,
497
+ ...basePkgJson,
127
498
  version: canaryVersion,
128
499
  gitSha,
129
500
  };
130
-
131
- await writePackageJson(pkg.path, updatedPackageJson);
501
+ await writePackageJson(pkgJsonDirectory, updatedPackageJson);
132
502
  console.log(`๐Ÿ“ Updated ${pkg.name} package.json to ${canaryVersion}`);
133
503
  }
134
-
135
- return { pkg, originalPackageJson };
504
+ return { pkg, basePkgJson, pkgJsonDirectory };
136
505
  });
137
506
 
138
507
  const updateResults = await Promise.all(packageUpdatePromises);
139
508
 
140
- // Build the original package.json map
141
- for (const { pkg, originalPackageJson } of updateResults) {
142
- originalPackageJsons.set(pkg.name, originalPackageJson);
509
+ // Prepare changelogs before building and publishing (so it can error out early if there are issues)
510
+ /**
511
+ * @type {Map<string, string[]>}
512
+ */
513
+ let changelogs = new Map();
514
+ if (options.githubRelease) {
515
+ changelogs = await prepareChangelogsForPackages(packagesToPublish, allPackages, canaryVersions);
143
516
  }
144
517
 
145
- // Run release build after updating package.json files
146
- console.log('\n๐Ÿ”จ Running release build...');
147
- await $({ stdio: 'inherit' })`pnpm release:build`;
148
- console.log('โœ… Release build completed successfully');
149
-
150
518
  // Third pass: publish only the changed packages using recursive publish
151
519
  let publishSuccess = false;
152
520
  try {
153
521
  console.log(`๐Ÿ“ค Publishing ${packagesToPublish.length} canary versions...`);
154
- await publishPackages(packagesToPublish, { ...options, noGitChecks: true, tag: 'canary' });
522
+ await publishPackages(packagesToPublish, { ...options, noGitChecks: true, tag: CANARY_TAG });
155
523
 
156
524
  packagesToPublish.forEach((pkg) => {
157
525
  const canaryVersion = canaryVersions.get(pkg.name);
@@ -161,9 +529,11 @@ async function publishCanaryVersions(
161
529
  } finally {
162
530
  // Always restore original package.json files in parallel
163
531
  console.log('\n๐Ÿ”„ Restoring original package.json files...');
164
- const restorePromises = allPackages.map(async (pkg) => {
165
- const originalPackageJson = originalPackageJsons.get(pkg.name);
166
- await writePackageJson(pkg.path, originalPackageJson);
532
+ const restorePromises = updateResults.map(async ({ pkg, basePkgJson, pkgJsonDirectory }) => {
533
+ // no need to restore package.json files in build directories
534
+ if (pkgJsonDirectory === pkg.path) {
535
+ await writePackageJson(pkg.path, basePkgJson);
536
+ }
167
537
  });
168
538
 
169
539
  await Promise.all(restorePromises);
@@ -172,6 +542,12 @@ async function publishCanaryVersions(
172
542
  if (publishSuccess) {
173
543
  // Create/update the canary tag after successful publish
174
544
  await createCanaryTag(options.dryRun);
545
+
546
+ // Create GitHub releases if requested
547
+ if (options.githubRelease) {
548
+ await createGitHubReleasesForPackages(packagesToPublish, canaryVersions, changelogs, options);
549
+ }
550
+
175
551
  console.log('\n๐ŸŽ‰ All canary versions published successfully!');
176
552
  }
177
553
  }
@@ -180,21 +556,31 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
180
556
  command: 'publish-canary',
181
557
  describe: 'Publish canary packages to npm',
182
558
  builder: (yargs) => {
183
- return yargs.option('dry-run', {
184
- type: 'boolean',
185
- default: false,
186
- description: 'Run in dry-run mode without publishing',
187
- });
559
+ return yargs
560
+ .option('dry-run', {
561
+ type: 'boolean',
562
+ default: false,
563
+ description: 'Run in dry-run mode without publishing',
564
+ })
565
+ .option('github-release', {
566
+ type: 'boolean',
567
+ default: false,
568
+ description: 'Create GitHub releases for published packages',
569
+ });
188
570
  },
189
571
  handler: async (argv) => {
190
- const { dryRun = false } = argv;
572
+ const { dryRun = false, githubRelease = false } = argv;
191
573
 
192
- const options = { dryRun };
574
+ const options = { dryRun, githubRelease };
193
575
 
194
576
  if (dryRun) {
195
577
  console.log('๐Ÿงช Running in DRY RUN mode - no actual publishing will occur\n');
196
578
  }
197
579
 
580
+ if (githubRelease) {
581
+ console.log('๐Ÿ“ GitHub releases will be created for published packages\n');
582
+ }
583
+
198
584
  // Always get all packages first
199
585
  console.log('๐Ÿ” Discovering all workspace packages...');
200
586
  const allPackages = await getWorkspacePackages({ publicOnly: true });
@@ -207,16 +593,12 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
207
593
  // Check for canary tag to determine selective publishing
208
594
  const canaryTag = await getLastCanaryTag();
209
595
 
210
- console.log(
211
- canaryTag
212
- ? '๐Ÿ” Checking for packages changed since canary tag...'
213
- : '๐Ÿ” No canary tag found, will publish all packages',
214
- );
596
+ console.log('๐Ÿ” Checking for packages changed since canary tag...');
215
597
  const packages = canaryTag
216
598
  ? await getWorkspacePackages({ sinceRef: canaryTag, publicOnly: true })
217
599
  : allPackages;
218
600
 
219
- console.log(`๐Ÿ“‹ Found ${packages.length} packages that need canary publishing:`);
601
+ console.log(`๐Ÿ“‹ Found ${packages.length} packages(s) for canary publishing:`);
220
602
  packages.forEach((pkg) => {
221
603
  console.log(` โ€ข ${pkg.name}@${pkg.version}`);
222
604
  });
@@ -708,6 +708,12 @@ declare namespace PackageJson {
708
708
  Default: `'latest'`
709
709
  */
710
710
  tag?: string;
711
+
712
+ /**
713
+ Specifies the directory to publish.
714
+ Default: The package root directory.
715
+ */
716
+ directory?: string;
711
717
  };
712
718
  }
713
719
 
@@ -305,6 +305,7 @@ const airbnbJsxA11y = {
305
305
  /**
306
306
  * @param {Object} [options]
307
307
  * @param {boolean} [options.enableReactCompiler] - Whether the config is for spec files.
308
+ * @returns {import('eslint').Linter.Config[]}
308
309
  */
309
310
  export function createCoreConfig(options = {}) {
310
311
  return defineConfig([
@@ -88,7 +88,8 @@ const rule = {
88
88
  callee.name === 'useDefaultProps' || callee.name === 'useThemeProps';
89
89
  if (isUseDefaultPropsCall) {
90
90
  let isCalledFromCustomHook = false;
91
- let { parent } = node;
91
+ /** @type {import('eslint').Rule.Node | null} */
92
+ let parent = node.parent;
92
93
  while (parent != null) {
93
94
  if (parent.type === 'FunctionExpression' || parent.type === 'FunctionDeclaration') {
94
95
  if (
@@ -112,7 +113,8 @@ const rule = {
112
113
 
113
114
  if (nameLiteral !== null) {
114
115
  let componentName = null;
115
- let { parent } = node;
116
+ /** @type {import('eslint').Rule.Node | null} */
117
+ let parent = node.parent;
116
118
  while (parent != null && componentName === null) {
117
119
  if (parent.type === 'FunctionExpression' || parent.type === 'FunctionDeclaration') {
118
120
  componentName = /** @type {import('estree').Identifier} */ (parent.id).name;
@@ -32,8 +32,9 @@ export default {
32
32
  * @param {import("estree").CallExpression & import("eslint").Rule.NodeParentExtension} hookCallNode
33
33
  */
34
34
  function getComponentBlockNode(hookCallNode) {
35
+ /** @type {import("eslint").Rule.Node | null} */
35
36
  let node = hookCallNode.parent;
36
- while (node !== undefined) {
37
+ while (node) {
37
38
  if (node.type === 'BlockStatement') {
38
39
  return node;
39
40
  }
@@ -17,6 +17,7 @@ import { persistentAuthStrategy } from './github.mjs';
17
17
  * @property {string} message
18
18
  * @property {string[]} labels
19
19
  * @property {number} prNumber
20
+ * @property {string} html_url
20
21
  * @property {{login: string; association: AuthorAssociation} | null} author
21
22
  */
22
23
 
@@ -122,6 +123,7 @@ async function fetchCommitsRest({ octokit, repo, lastRelease, release, org = 'mu
122
123
  message: commit.commit.message,
123
124
  labels,
124
125
  prNumber,
126
+ html_url: pr.data.html_url,
125
127
  author: pr.data.user?.login
126
128
  ? {
127
129
  login: pr.data.user.login,
@@ -175,7 +175,7 @@ export async function publishPackages(packages, options = {}) {
175
175
  /**
176
176
  * Read package.json from a directory
177
177
  * @param {string} packagePath - Path to package directory
178
- * @returns {Promise<Object>} Parsed package.json content
178
+ * @returns {Promise<import('../cli/packageJson').PackageJson>} Parsed package.json content
179
179
  */
180
180
  export async function readPackageJson(packagePath) {
181
181
  const content = await fs.readFile(path.join(packagePath, 'package.json'), 'utf8');