@mui/internal-code-infra 0.0.3-canary.6 → 0.0.3-canary.61

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.
Files changed (99) hide show
  1. package/README.md +55 -0
  2. package/build/babel-config.d.mts +40 -0
  3. package/build/brokenLinksChecker/index.d.mts +138 -0
  4. package/build/cli/cmdArgosPush.d.mts +13 -0
  5. package/build/cli/cmdBuild.d.mts +56 -0
  6. package/build/cli/cmdCopyFiles.d.mts +20 -0
  7. package/build/cli/cmdExtractErrorCodes.d.mts +3 -0
  8. package/build/cli/cmdGithubAuth.d.mts +6 -0
  9. package/build/cli/cmdListWorkspaces.d.mts +18 -0
  10. package/build/cli/cmdPublish.d.mts +27 -0
  11. package/build/cli/cmdPublishCanary.d.mts +30 -0
  12. package/build/cli/cmdPublishNewPackage.d.mts +8 -0
  13. package/build/cli/cmdSetVersionOverrides.d.mts +9 -0
  14. package/build/cli/cmdValidateBuiltTypes.d.mts +2 -0
  15. package/build/cli/index.d.mts +1 -0
  16. package/build/eslint/baseConfig.d.mts +10 -0
  17. package/build/eslint/docsConfig.d.mts +4 -0
  18. package/build/eslint/extensions.d.mts +8 -0
  19. package/build/eslint/index.d.mts +4 -0
  20. package/build/eslint/jsonConfig.d.mts +4 -0
  21. package/build/eslint/material-ui/config.d.mts +8 -0
  22. package/build/eslint/material-ui/index.d.mts +2 -0
  23. package/build/eslint/material-ui/rules/disallow-active-element-as-key-event-target.d.mts +5 -0
  24. package/build/eslint/material-ui/rules/disallow-react-api-in-server-components.d.mts +2 -0
  25. package/build/eslint/material-ui/rules/docgen-ignore-before-comment.d.mts +2 -0
  26. package/build/eslint/material-ui/rules/mui-name-matches-component-name.d.mts +5 -0
  27. package/build/eslint/material-ui/rules/no-empty-box.d.mts +5 -0
  28. package/build/eslint/material-ui/rules/no-restricted-resolved-imports.d.mts +12 -0
  29. package/build/eslint/material-ui/rules/no-styled-box.d.mts +5 -0
  30. package/build/eslint/material-ui/rules/rules-of-use-theme-variants.d.mts +9 -0
  31. package/build/eslint/material-ui/rules/straight-quotes.d.mts +5 -0
  32. package/build/eslint/testConfig.d.mts +14 -0
  33. package/build/markdownlint/duplicate-h1.d.mts +27 -0
  34. package/build/markdownlint/git-diff.d.mts +8 -0
  35. package/build/markdownlint/index.d.mts +56 -0
  36. package/build/markdownlint/straight-quotes.d.mts +8 -0
  37. package/build/markdownlint/table-alignment.d.mts +8 -0
  38. package/build/markdownlint/terminal-language.d.mts +8 -0
  39. package/build/prettier.d.mts +20 -0
  40. package/build/stylelint/index.d.mts +32 -0
  41. package/build/utils/babel.d.mts +71 -0
  42. package/build/utils/build.d.mts +50 -0
  43. package/build/utils/changelog.d.mts +64 -0
  44. package/build/utils/credentials.d.mts +17 -0
  45. package/build/utils/extractErrorCodes.d.mts +19 -0
  46. package/build/utils/git.d.mts +26 -0
  47. package/build/utils/github.d.mts +41 -0
  48. package/build/utils/pnpm.d.mts +238 -0
  49. package/build/utils/typescript.d.mts +35 -0
  50. package/package.json +92 -42
  51. package/src/babel-config.mjs +52 -8
  52. package/src/brokenLinksChecker/__fixtures__/static-site/broken-links.html +20 -0
  53. package/src/brokenLinksChecker/__fixtures__/static-site/broken-targets.html +22 -0
  54. package/src/brokenLinksChecker/__fixtures__/static-site/example.md +9 -0
  55. package/src/brokenLinksChecker/__fixtures__/static-site/external-links.html +21 -0
  56. package/src/brokenLinksChecker/__fixtures__/static-site/ignored-page.html +17 -0
  57. package/src/brokenLinksChecker/__fixtures__/static-site/index.html +26 -0
  58. package/src/brokenLinksChecker/__fixtures__/static-site/known-targets.json +5 -0
  59. package/src/brokenLinksChecker/__fixtures__/static-site/nested/page.html +19 -0
  60. package/src/brokenLinksChecker/__fixtures__/static-site/orphaned-page.html +20 -0
  61. package/src/brokenLinksChecker/__fixtures__/static-site/page-with-api-links.html +20 -0
  62. package/src/brokenLinksChecker/__fixtures__/static-site/page-with-custom-targets.html +24 -0
  63. package/src/brokenLinksChecker/__fixtures__/static-site/page-with-ignored-content.html +28 -0
  64. package/src/brokenLinksChecker/__fixtures__/static-site/page-with-known-target-links.html +19 -0
  65. package/src/brokenLinksChecker/__fixtures__/static-site/valid.html +20 -0
  66. package/src/brokenLinksChecker/__fixtures__/static-site/with-anchors.html +31 -0
  67. package/src/brokenLinksChecker/index.mjs +641 -0
  68. package/src/brokenLinksChecker/index.test.ts +178 -0
  69. package/src/cli/cmdArgosPush.mjs +13 -2
  70. package/src/cli/cmdBuild.mjs +228 -31
  71. package/src/cli/cmdGithubAuth.mjs +36 -0
  72. package/src/cli/cmdListWorkspaces.mjs +2 -2
  73. package/src/cli/cmdPublish.mjs +203 -49
  74. package/src/cli/cmdPublishCanary.mjs +404 -46
  75. package/src/cli/cmdPublishNewPackage.mjs +86 -0
  76. package/src/cli/cmdSetVersionOverrides.mjs +17 -1
  77. package/src/cli/cmdValidateBuiltTypes.mjs +49 -0
  78. package/src/cli/index.mjs +6 -2
  79. package/src/cli/packageJson.d.ts +729 -0
  80. package/src/eslint/baseConfig.mjs +96 -78
  81. package/src/eslint/docsConfig.mjs +26 -13
  82. package/src/eslint/extensions.mjs +8 -8
  83. package/src/eslint/jsonConfig.mjs +40 -0
  84. package/src/eslint/material-ui/config.mjs +8 -9
  85. package/src/eslint/material-ui/rules/mui-name-matches-component-name.mjs +4 -2
  86. package/src/eslint/material-ui/rules/rules-of-use-theme-variants.mjs +2 -1
  87. package/src/eslint/testConfig.mjs +72 -66
  88. package/src/stylelint/index.mjs +46 -0
  89. package/src/untyped-plugins.d.ts +13 -0
  90. package/src/{cli → utils}/babel.mjs +10 -3
  91. package/src/utils/build.mjs +27 -1
  92. package/src/utils/changelog.mjs +157 -0
  93. package/src/utils/credentials.mjs +71 -0
  94. package/src/utils/extractErrorCodes.mjs +2 -2
  95. package/src/utils/git.mjs +67 -0
  96. package/src/utils/github.mjs +263 -0
  97. package/src/{cli → utils}/pnpm.mjs +23 -13
  98. package/src/{cli → utils}/typescript.mjs +13 -7
  99. package/src/cli/cmdJsonLint.mjs +0 -69
@@ -3,26 +3,36 @@
3
3
  /* eslint-disable no-console */
4
4
 
5
5
  /**
6
- * @typedef {import('./pnpm.mjs').PublicPackage} PublicPackage
7
- * @typedef {import('./pnpm.mjs').PublishOptions} PublishOptions
6
+ * @typedef {import('../utils/pnpm.mjs').PublicPackage} PublicPackage
7
+ * @typedef {import('../utils/pnpm.mjs').PublishOptions} PublishOptions
8
8
  */
9
9
 
10
+ import select from '@inquirer/select';
11
+ import { createActionAuth } from '@octokit/auth-action';
10
12
  import { Octokit } from '@octokit/rest';
13
+ import chalk from 'chalk';
14
+ import envCI from 'env-ci';
15
+ import { $ } from 'execa';
11
16
  import * as fs from 'node:fs/promises';
12
17
  import * as semver from 'semver';
13
- import gitUrlParse from 'git-url-parse';
14
- import { $ } from 'execa';
15
- import { createActionAuth } from '@octokit/auth-action';
16
- import { getWorkspacePackages, publishPackages } from './pnpm.mjs';
18
+
19
+ import { persistentAuthStrategy } from '../utils/github.mjs';
20
+ import { getWorkspacePackages, publishPackages } from '../utils/pnpm.mjs';
21
+ import { getCurrentGitSha, getRepositoryInfo } from '../utils/git.mjs';
22
+
23
+ const isCI = envCI().isCi;
17
24
 
18
25
  function getOctokit() {
19
- return new Octokit({ authStrategy: createActionAuth });
26
+ return new Octokit({ authStrategy: isCI ? createActionAuth : persistentAuthStrategy });
20
27
  }
21
28
 
22
29
  /**
23
30
  * @typedef {Object} Args
24
31
  * @property {boolean} dry-run Run in dry-run mode without publishing
25
32
  * @property {boolean} github-release Create a GitHub draft release after publishing
33
+ * @property {string} tag NPM dist tag to publish to
34
+ * @property {boolean} ci Runs in CI environment
35
+ * @property {string} [sha] Git SHA to use for the GitHub release workflow (local only)
26
36
  */
27
37
 
28
38
  /**
@@ -46,8 +56,9 @@ async function parseChangelog(changelogPath, version) {
46
56
  const content = await fs.readFile(changelogPath, 'utf8');
47
57
  const lines = content.split('\n');
48
58
 
49
- const versionHeader = `## ${version}`;
50
- const startIndex = lines.findIndex((line) => line.startsWith(versionHeader));
59
+ const startIndex = lines.findIndex(
60
+ (line) => line.startsWith(`## ${version}`) || line.startsWith(`## v${version}`),
61
+ );
51
62
 
52
63
  if (startIndex === -1) {
53
64
  throw new Error(`Version ${version} not found in changelog`);
@@ -115,38 +126,6 @@ async function checkGitHubReleaseExists(owner, repo, version) {
115
126
  }
116
127
  }
117
128
 
118
- /**
119
- * Get current repository info from git remote
120
- * @returns {Promise<{owner: string, repo: string}>} Repository owner and name
121
- */
122
- async function getRepositoryInfo() {
123
- try {
124
- const result = await $`git remote get-url origin`;
125
- const url = result.stdout.trim();
126
-
127
- const parsed = gitUrlParse(url);
128
- if (parsed.source !== 'github.com') {
129
- throw new Error('Repository is not hosted on GitHub');
130
- }
131
-
132
- return {
133
- owner: parsed.owner,
134
- repo: parsed.name,
135
- };
136
- } catch (/** @type {any} */ error) {
137
- throw new Error(`Failed to get repository info: ${error.message}`);
138
- }
139
- }
140
-
141
- /**
142
- * Get current git SHA
143
- * @returns {Promise<string>} Current git commit SHA
144
- */
145
- async function getCurrentGitSha() {
146
- const result = await $`git rev-parse HEAD`;
147
- return result.stdout.trim();
148
- }
149
-
150
129
  /**
151
130
  * Create and push a git tag
152
131
  * @param {string} version - Version to tag
@@ -157,7 +136,13 @@ async function createGitTag(version, dryRun = false) {
157
136
  const tagName = `v${version}`;
158
137
 
159
138
  try {
160
- await $`git tag ${tagName}`;
139
+ await $({
140
+ env: {
141
+ ...process.env,
142
+ GIT_COMMITTER_NAME: 'Code infra',
143
+ GIT_COMMITTER_EMAIL: 'code-infra@mui.com',
144
+ },
145
+ })`git tag -a ${tagName} -m ${`Version ${version}`}`;
161
146
  const pushArgs = dryRun ? ['--dry-run'] : [];
162
147
  await $({ stdio: 'inherit' })`git push origin ${tagName} ${pushArgs}`;
163
148
 
@@ -169,7 +154,7 @@ async function createGitTag(version, dryRun = false) {
169
154
 
170
155
  /**
171
156
  * Validate GitHub release requirements
172
- * @param {string | null} version - Version to validate
157
+ * @param {string} version - Version to validate
173
158
  * @returns {Promise<{changelogContent: string, version: string, repoInfo: {owner: string, repo: string}}>}
174
159
  */
175
160
  async function validateGitHubRelease(version) {
@@ -214,7 +199,7 @@ async function publishToNpm(packages, options) {
214
199
  });
215
200
 
216
201
  // Use pnpm's built-in duplicate checking - no need to check versions ourselves
217
- await publishPackages(packages, 'latest', options);
202
+ await publishPackages(packages, options);
218
203
  console.log('āœ… Successfully published to npm');
219
204
  }
220
205
 
@@ -261,15 +246,47 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
261
246
  type: 'boolean',
262
247
  default: false,
263
248
  description: 'Create a GitHub draft release after publishing',
249
+ })
250
+ .option('tag', {
251
+ type: 'string',
252
+ default: 'latest',
253
+ description: 'NPM dist tag to publish to',
254
+ })
255
+ .option('ci', {
256
+ type: 'boolean',
257
+ description:
258
+ 'Runs in CI environment. On local environments, it triggers the GitHub publish workflow instead of publishing directly.',
259
+ })
260
+ .option('sha', {
261
+ type: 'string',
262
+ description: 'Git SHA to use for the GitHub release workflow (local only)',
264
263
  });
265
264
  },
266
265
  handler: async (argv) => {
267
- const { dryRun = false, githubRelease = false } = argv;
266
+ const { dryRun = false, githubRelease = false, tag = 'latest', sha } = argv;
267
+
268
+ if (isCI && !argv.ci) {
269
+ console.error(
270
+ chalk.yellow(
271
+ 'āŒ Error: CI environment detected but the "--ci" flag was not passed. Pass it explicitly to run in CI.',
272
+ ),
273
+ );
274
+ process.exit(1);
275
+ }
276
+ argv.ci = argv.ci ?? isCI;
277
+
278
+ if (argv.ci && sha) {
279
+ throw new Error('The --sha option can only be used in non-CI environments');
280
+ }
268
281
 
269
282
  if (dryRun) {
270
283
  console.log('🧪 Running in DRY RUN mode - no actual publishing will occur\n');
271
284
  }
272
285
 
286
+ if (!argv.ci) {
287
+ await triggerLocalGithubPublishWorkflow(argv);
288
+ return;
289
+ }
273
290
  // Get all packages
274
291
  console.log('šŸ” Discovering all workspace packages...');
275
292
 
@@ -283,6 +300,10 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
283
300
  // Get version from root package.json
284
301
  const version = await getReleaseVersion();
285
302
 
303
+ if (!version) {
304
+ throw new Error('No valid version found in root package.json');
305
+ }
306
+
286
307
  // Early validation for GitHub release (before any publishing)
287
308
  let githubReleaseData = null;
288
309
  if (githubRelease) {
@@ -290,9 +311,21 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
290
311
  githubReleaseData = await validateGitHubRelease(version);
291
312
  }
292
313
 
314
+ const newPackages = await getWorkspacePackages({ nonPublishedOnly: true });
315
+
316
+ if (newPackages.length > 0) {
317
+ throw new Error(
318
+ `The following packages are new and need to be published manually first: ${newPackages.join(
319
+ ', ',
320
+ )}. Read more about it here: https://github.com/mui/mui-public/blob/master/packages/code-infra/README.md#adding-and-publishing-new-packages`,
321
+ );
322
+ }
323
+
293
324
  // Publish to npm (pnpm handles duplicate checking automatically)
294
325
  // No git checks, we'll do our own
295
- await publishToNpm(allPackages, { dryRun, noGitChecks: true });
326
+ await publishToNpm(allPackages, { dryRun, noGitChecks: true, tag });
327
+
328
+ await createGitTag(version, dryRun);
296
329
 
297
330
  // Create GitHub release or git tag after successful npm publishing
298
331
  if (githubRelease && githubReleaseData) {
@@ -306,11 +339,132 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
306
339
  githubReleaseData.repoInfo,
307
340
  );
308
341
  }
309
- } else if (version) {
310
- // Create git tag when not doing GitHub release
311
- await createGitTag(version, dryRun);
312
342
  }
313
343
 
314
344
  console.log('\nšŸ Publishing complete!');
315
345
  },
316
346
  });
347
+
348
+ const WORKFLOW_PATH = 'workflows/publish.yml';
349
+ const PUBLISH_WORKFLOW_ID = `.github/${WORKFLOW_PATH}`;
350
+
351
+ /**
352
+ * @param {Omit<Args, 'ci'>} opts
353
+ */
354
+ async function triggerLocalGithubPublishWorkflow(opts) {
355
+ console.log(`šŸ” Checking if there are new packages to publish in the workspace...`);
356
+ const newPackages = await getWorkspacePackages({ nonPublishedOnly: true });
357
+ if (newPackages.length) {
358
+ console.warn(
359
+ `āš ļø Found new packages that should be published to npm first before triggering a release:
360
+ * ${newPackages.map((pkg) => pkg.name).join(' * ')}
361
+ Please run the command "${chalk.bold('pnpm code-infra publish-new-package')}" first to publish and configure npm.`,
362
+ );
363
+ return;
364
+ }
365
+ console.log('āœ… No new packages found, proceeding...');
366
+ const repoInfo = await getRepositoryInfo();
367
+ console.log(`šŸ“‚ Repository: ${repoInfo.owner}/${repoInfo.repo}`);
368
+ const params = {
369
+ owner: repoInfo.owner,
370
+ repo: repoInfo.repo,
371
+ workflow_id: PUBLISH_WORKFLOW_ID,
372
+ };
373
+ const octokit = getOctokit();
374
+
375
+ try {
376
+ const sha = opts.sha || (await determineGitSha(octokit, repoInfo));
377
+ if (!sha) {
378
+ console.error('āŒšŸšØ No commit SHA provided, cannot proceed.');
379
+ return;
380
+ }
381
+
382
+ await octokit.actions.createWorkflowDispatch({
383
+ ...params,
384
+ ref: 'master',
385
+ inputs: {
386
+ sha,
387
+ 'dry-run': opts['dry-run'] ? 'true' : 'false',
388
+ 'github-release': opts['github-release'] ? 'true' : 'false',
389
+ 'dist-tag': opts.tag,
390
+ },
391
+ });
392
+ console.log(
393
+ `šŸŽ‰āœ… Release created successfully! Check the status at: https://github.com/${params.owner}/${params.repo}/actions/${WORKFLOW_PATH} .`,
394
+ );
395
+ } catch (error) {
396
+ const err =
397
+ /** @type {import('@octokit/types').RequestError & {response: {data: {message: string; documentation_url: string}}}} */ (
398
+ error
399
+ );
400
+ const manualTriggerUrl = `You can also trigger the workflow manually at: https://github.com/${params.owner}/${params.repo}/actions/${WORKFLOW_PATH}`;
401
+ if (err.status === 422) {
402
+ console.error(`āŒšŸš« ${err.response.data.message}\n. ${manualTriggerUrl}`);
403
+ return;
404
+ }
405
+ if (err.status === 403) {
406
+ const isAppPermissionIssue = /not accessible by integration/.exec(err.response.data.message);
407
+ console.error(
408
+ `āŒšŸ”’ ${isAppPermissionIssue ? '"Code Infra" app doesn\'t' : "You don't"} seem to have sufficient permissions to perform this action. ${isAppPermissionIssue ? 'Contact' : 'If this seems incorrect, contact'} the infra team regarding the app permissions.${err.response.data.documentation_url ? ` See ${err.response.data.documentation_url} for more information.` : ''}.
409
+ ${manualTriggerUrl}`,
410
+ );
411
+ return;
412
+ }
413
+ console.error(`āŒšŸ”„ Error while invoking the publish workflow.\n. ${manualTriggerUrl}`);
414
+ throw error;
415
+ }
416
+ }
417
+
418
+ /**
419
+ * @param {import('@octokit/rest').Octokit} octokit
420
+ * @param {{owner: string; repo: string}} repoInfo
421
+ * @returns {Promise<string | undefined>}
422
+ */
423
+ async function determineGitSha(octokit, repoInfo) {
424
+ console.log(`šŸ” Determining the git SHA to use for the release...`);
425
+ // Avoid the deprecation warning when calling octokit.search.issuesAndPullRequests
426
+ // It has been deprecated but new method is not available in @octokit/rest yet.
427
+ octokit.log.warn = () => {};
428
+ const pulls = (
429
+ await octokit.search.issuesAndPullRequests({
430
+ advanced_search: 'true',
431
+ q: `is:pr is:merged label:release repo:${repoInfo.owner}/${repoInfo.repo}`,
432
+ per_page: 1,
433
+ })
434
+ ).data.items;
435
+ if (!pulls.length) {
436
+ console.log(`āŒšŸšØ Could not find any merged release PRs in the repository.`);
437
+ return undefined;
438
+ }
439
+
440
+ console.log(
441
+ `šŸ«† Found the latest merged release PR: ${chalk.bold(pulls[0].title)} (${pulls[0].html_url})`,
442
+ );
443
+
444
+ const commits = (
445
+ await octokit.search.commits({
446
+ q: `repo:${repoInfo.owner}/${repoInfo.repo} author:${pulls[0].user?.login} [release]`,
447
+ per_page: 100,
448
+ })
449
+ ).data.items;
450
+
451
+ if (!commits.length) {
452
+ console.error(
453
+ `āŒšŸšØ Could not find any commits associated with the release PR: ${pulls[0].html_url}`,
454
+ );
455
+ return undefined;
456
+ }
457
+ const relevantData = commits.map((commit) => ({
458
+ value: commit.sha,
459
+ name: `(${commit.sha.slice(0, 7)}) ${commit.commit.message.split('\n')[0]} by ${commit.author?.login ?? 'no author'} on ${new Date(commit.commit.committer?.date ?? '').toISOString()}`,
460
+ desciption: commit.commit.message,
461
+ }));
462
+
463
+ const result = await select({
464
+ message: 'Select the commit to release from:',
465
+ choices: relevantData,
466
+ default: relevantData[0].value,
467
+ pageSize: 10,
468
+ });
469
+ return result;
470
+ }