@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.
- package/build/cli/cmdPublishCanary.d.mts +18 -0
- package/build/eslint/material-ui/config.d.mts +2 -1
- package/build/eslint/material-ui/rules/rules-of-use-theme-variants.d.mts +1 -1
- package/build/utils/changelog.d.mts +2 -0
- package/build/utils/pnpm.d.mts +2 -2
- package/package.json +4 -4
- package/src/cli/cmdPublishCanary.mjs +424 -42
- package/src/cli/packageJson.d.ts +6 -0
- package/src/eslint/material-ui/config.mjs +1 -0
- package/src/eslint/material-ui/rules/mui-name-matches-component-name.mjs +4 -2
- package/src/eslint/material-ui/rules/rules-of-use-theme-variants.mjs +2 -1
- package/src/utils/changelog.mjs +2 -0
- package/src/utils/pnpm.mjs +1 -1
|
@@ -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
|
|
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").
|
|
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;
|
package/build/utils/pnpm.d.mts
CHANGED
|
@@ -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<
|
|
117
|
+
* @returns {Promise<import('../cli/packageJson').PackageJson>} Parsed package.json content
|
|
118
118
|
*/
|
|
119
|
-
export function readPackageJson(packagePath: string): Promise<
|
|
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.
|
|
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.
|
|
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": "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
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:
|
|
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 =
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
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
|
});
|
package/src/cli/packageJson.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
37
|
+
while (node) {
|
|
37
38
|
if (node.type === 'BlockStatement') {
|
|
38
39
|
return node;
|
|
39
40
|
}
|
package/src/utils/changelog.mjs
CHANGED
|
@@ -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,
|
package/src/utils/pnpm.mjs
CHANGED
|
@@ -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<
|
|
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');
|