@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
@@ -0,0 +1,178 @@
1
+ import path from 'node:path';
2
+ import getPort from 'get-port';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ // eslint-disable-next-line import/extensions
6
+ import { crawl, Issue, Link } from './index.mjs';
7
+
8
+ type ExpectedIssue = Omit<Partial<Issue>, 'link'> & { link?: Partial<Link> };
9
+
10
+ function objectMatchingIssue(expectedIssue: ExpectedIssue) {
11
+ return expect.objectContaining({
12
+ ...expectedIssue,
13
+ ...(expectedIssue.link ? { link: expect.objectContaining(expectedIssue.link) } : {}),
14
+ });
15
+ }
16
+
17
+ /**
18
+ * Helper to assert that an issue with matching properties exists in the issues array
19
+ */
20
+ function expectIssue(issues: Issue[], expectedIssue: ExpectedIssue) {
21
+ expect(issues).toEqual(expect.arrayContaining([objectMatchingIssue(expectedIssue)]));
22
+ }
23
+
24
+ /**
25
+ * Helper to assert that no issue with matching properties exists in the issues array
26
+ */
27
+ function expectNotIssue(issues: Issue[], notExpectedIssue: ExpectedIssue) {
28
+ expect(issues).not.toEqual(expect.arrayContaining([objectMatchingIssue(notExpectedIssue)]));
29
+ }
30
+
31
+ describe('Broken Links Checker', () => {
32
+ const fixtureDir = path.join(import.meta.dirname, '__fixtures__', 'static-site');
33
+ const servePath = path.join(import.meta.dirname, '..', '..', 'node_modules', '.bin', 'serve');
34
+
35
+ it('should detect all broken links and targets across the entire site', async () => {
36
+ const port = await getPort();
37
+ const host = `http://localhost:${port}`;
38
+
39
+ const result = await crawl({
40
+ startCommand: `${servePath} ${fixtureDir} -p ${port}`,
41
+ host,
42
+ ignoredPaths: [/ignored-page\.html$/],
43
+ ignoredContent: ['.sidebar'],
44
+ ignoredTargets: new Set(['__should-be-ignored']),
45
+ knownTargets: new Map([['/external-page.html', new Set(['#valid-target'])]]),
46
+ knownTargetsDownloadUrl: [`${host}/known-targets.json`],
47
+ seedUrls: ['/', '/orphaned-page.html'],
48
+ });
49
+
50
+ expect(result.links).toHaveLength(54);
51
+ expect(result.issues).toHaveLength(8);
52
+
53
+ // Check broken-link type issues
54
+ expectIssue(result.issues, {
55
+ type: 'broken-link',
56
+ link: {
57
+ src: '/broken-links.html',
58
+ href: '/does-not-exist.html',
59
+ text: 'This page does not exist',
60
+ },
61
+ });
62
+
63
+ expectIssue(result.issues, {
64
+ type: 'broken-link',
65
+ link: {
66
+ src: '/broken-links.html',
67
+ href: '/another-missing-page.html',
68
+ text: 'Another missing page',
69
+ },
70
+ });
71
+
72
+ // Check broken-target type issues
73
+ expectIssue(result.issues, {
74
+ type: 'broken-target',
75
+ link: {
76
+ src: '/broken-targets.html',
77
+ href: '/with-anchors.html#nonexistent',
78
+ text: 'Non-existent anchor',
79
+ },
80
+ });
81
+
82
+ expectIssue(result.issues, {
83
+ type: 'broken-target',
84
+ link: {
85
+ src: '/broken-targets.html',
86
+ href: '/valid.html#missing-target',
87
+ text: 'Valid page, missing target',
88
+ },
89
+ });
90
+
91
+ expectIssue(result.issues, {
92
+ type: 'broken-target',
93
+ link: {
94
+ src: '/broken-targets.html',
95
+ href: '/with-anchors.html#also-missing',
96
+ text: 'Also missing',
97
+ },
98
+ });
99
+
100
+ // Verify that valid links are not reported
101
+ expectNotIssue(result.issues, { link: { href: '/' } });
102
+ expectNotIssue(result.issues, { link: { href: '/valid.html' } });
103
+ expectNotIssue(result.issues, { link: { href: '/with-anchors.html' } });
104
+ expectNotIssue(result.issues, { link: { href: '/with-anchors.html#section1' } });
105
+ expectNotIssue(result.issues, { link: { href: '/with-anchors.html#section2' } });
106
+ expectNotIssue(result.issues, { link: { href: '/with-anchors.html#section3' } });
107
+ expectNotIssue(result.issues, { link: { href: '/nested/page.html' } });
108
+
109
+ // Verify that external links are not reported
110
+ expectNotIssue(result.issues, { link: { href: 'https://example.com' } });
111
+ expectNotIssue(result.issues, { link: { href: 'https://github.com/mui' } });
112
+
113
+ // Test ignoredPaths: ignored-page.html should not be crawled
114
+ expectNotIssue(result.issues, { link: { src: '/ignored-page.html' } });
115
+ expectNotIssue(result.issues, { link: { href: '/this-link-should-not-be-checked.html' } });
116
+
117
+ // Test ignoredContent: links in .sidebar should be ignored
118
+ expectNotIssue(result.issues, { link: { href: '/sidebar-broken-link.html' } });
119
+
120
+ // Test ignoredTargets: __should-be-ignored target should not cause issues
121
+ expectNotIssue(result.issues, {
122
+ link: { href: '/page-with-custom-targets.html#__should-be-ignored' },
123
+ });
124
+
125
+ // Test that non-ignored custom target is valid
126
+ expectNotIssue(result.issues, { link: { href: '/page-with-custom-targets.html#custom-id' } });
127
+
128
+ // Test knownTargets: valid-target is known and should not cause issues
129
+ expectNotIssue(result.issues, { link: { href: '/external-page.html#valid-target' } });
130
+
131
+ // Test knownTargets: invalid-target is not in knownTargets and should cause an issue
132
+ expectIssue(result.issues, {
133
+ type: 'broken-target',
134
+ link: {
135
+ src: '/page-with-known-target-links.html',
136
+ href: '/external-page.html#invalid-target',
137
+ text: 'Invalid external target',
138
+ },
139
+ });
140
+
141
+ // Test knownTargetsDownloadUrl: method1 and method2 are in downloaded known targets
142
+ expectNotIssue(result.issues, { link: { href: '/api-page.html#method1' } });
143
+ expectNotIssue(result.issues, { link: { href: '/api-page.html#method2' } });
144
+
145
+ // Test knownTargetsDownloadUrl: unknown-method is not in downloaded known targets and should cause an issue
146
+ expectIssue(result.issues, {
147
+ type: 'broken-target',
148
+ link: {
149
+ src: '/page-with-api-links.html',
150
+ href: '/api-page.html#unknown-method',
151
+ text: 'Unknown API method',
152
+ },
153
+ });
154
+
155
+ // Test seedUrls: orphaned-page.html should be crawled even though it's not linked from anywhere
156
+ expect(result.pages.has('/orphaned-page.html')).toBe(true);
157
+
158
+ // Test seedUrls: broken link from orphaned page should be detected
159
+ expectIssue(result.issues, {
160
+ type: 'broken-link',
161
+ link: {
162
+ src: '/orphaned-page.html',
163
+ href: '/orphaned-broken-link.html',
164
+ text: 'Broken link from orphaned page',
165
+ },
166
+ });
167
+
168
+ // Test trailing slash normalization: /valid.html and /valid.html/ should be treated as the same page
169
+ // The orphaned page has both links, but they should not cause duplicate page crawls
170
+ expectNotIssue(result.issues, { link: { href: '/valid.html/' } });
171
+
172
+ // Test content-type checking: links inside markdown files should not be crawled
173
+ // The example.md file contains a link to /this-should-not-be-checked.html in an HTML snippet
174
+ expectNotIssue(result.issues, { link: { href: '/this-should-not-be-checked.html' } });
175
+ // The markdown file itself should not cause issues (it's a valid file, just not HTML)
176
+ expectNotIssue(result.issues, { link: { href: '/example.md' } });
177
+ }, 30000);
178
+ });
@@ -63,8 +63,11 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
63
63
  const screenshots = await globby(`${folder}/**/*`, {
64
64
  onlyFiles: true,
65
65
  });
66
+ const threshold = process.env.ARGOS_THRESHOLD ? parseFloat(process.env.ARGOS_THRESHOLD) : 0.5;
66
67
 
67
- console.log(`Found ${screenshots.length} screenshots.`);
68
+ console.log(
69
+ `Found ${screenshots.length} screenshots. Uploading with threshold ${threshold}.`,
70
+ );
68
71
  if (verbose) {
69
72
  console.log('Screenshots found:');
70
73
  screenshots.forEach((screenshot) => {
@@ -99,14 +102,22 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
99
102
  commit: circleSha1,
100
103
  branch: circleBranch,
101
104
  token: argosToken,
105
+ threshold,
102
106
  parallel: {
103
107
  total: batches.length,
104
108
  nonce: circleBuildNum,
105
109
  },
106
110
  });
107
111
 
112
+ if (verbose) {
113
+ console.log('Screenshots uploaded:');
114
+ for (const screenshot of result.screenshots) {
115
+ console.log(`- ${screenshot.name}. Threshold: ${screenshot.threshold}.`);
116
+ }
117
+ }
118
+
108
119
  console.log(
109
- `Batch of ${batches[i].length} screenshots uploaded. Build URL: ${result.build.url}`,
120
+ `Batch of ${batches[i].length} screenshots uploaded. Threshold: ${threshold}. Build URL: ${result.build.url}`,
110
121
  );
111
122
  }
112
123
  } finally {
@@ -1,9 +1,13 @@
1
1
  /* eslint-disable no-console */
2
+ import { findWorkspaceDir } from '@pnpm/find-workspace-dir';
2
3
  import { $ } from 'execa';
3
- import set from 'lodash-es/set.js';
4
+ import { globby } from 'globby';
4
5
  import * as fs from 'node:fs/promises';
5
6
  import * as path from 'node:path';
6
- import { getOutExtension, isMjsBuild, validatePkgJson } from '../utils/build.mjs';
7
+ import { sep as posixSep } from 'node:path/posix';
8
+ import * as semver from 'semver';
9
+
10
+ import { getOutExtension, isMjsBuild, mapConcurrently, validatePkgJson } from '../utils/build.mjs';
7
11
 
8
12
  /**
9
13
  * @typedef {Object} Args
@@ -18,6 +22,8 @@ import { getOutExtension, isMjsBuild, validatePkgJson } from '../utils/build.mjs
18
22
  * @property {boolean} skipPackageJson - Whether to skip generating the package.json file in the bundle output.
19
23
  * @property {boolean} skipMainCheck - Whether to skip checking for main field in package.json.
20
24
  * @property {string[]} ignore - Globs to be ignored by Babel.
25
+ * @property {string[]} [copy] - Files/Directories to be copied. Can be a glob pattern.
26
+ * @property {boolean} [enableReactCompiler] - Whether to use the React compiler.
21
27
  */
22
28
 
23
29
  const validBundles = [
@@ -64,16 +70,16 @@ ${content}`,
64
70
 
65
71
  /**
66
72
  * @param {Object} param0
67
- * @param {string | Record<string, string>} param0.importPath
73
+ * @param {NonNullable<import('./packageJson').PackageJson.Exports>} param0.importPath
68
74
  * @param {string} param0.key
69
75
  * @param {string} param0.cwd
70
76
  * @param {string} param0.dir
71
77
  * @param {string} param0.type
72
- * @param {Object} param0.newExports
78
+ * @param {import('./packageJson').PackageJson.ExportConditions} param0.newExports
73
79
  * @param {string} param0.typeOutExtension
74
80
  * @param {string} param0.outExtension
75
81
  * @param {boolean} param0.addTypes
76
- * @returns {Promise<{path: string[], importPath: string | Record<string, string | undefined>}>}
82
+ * @returns {Promise<void>}
77
83
  */
78
84
  async function createExportsFor({
79
85
  importPath,
@@ -86,10 +92,22 @@ async function createExportsFor({
86
92
  outExtension,
87
93
  addTypes,
88
94
  }) {
95
+ if (Array.isArray(importPath)) {
96
+ throw new Error(
97
+ `Array form of package.json exports is not supported yet. Found in export "${key}".`,
98
+ );
99
+ }
100
+
89
101
  let srcPath = typeof importPath === 'string' ? importPath : importPath['mui-src'];
90
102
  const rest = typeof importPath === 'string' ? {} : { ...importPath };
91
103
  delete rest['mui-src'];
92
104
 
105
+ if (typeof srcPath !== 'string') {
106
+ throw new Error(
107
+ `Unsupported export for "${key}". Only a string or an object with "mui-src" field is supported for now.`,
108
+ );
109
+ }
110
+
93
111
  const exportFileExists = srcPath.includes('*')
94
112
  ? true
95
113
  : await fs.stat(path.join(cwd, srcPath)).then(
@@ -105,25 +123,25 @@ async function createExportsFor({
105
123
  const ext = path.extname(srcPath);
106
124
 
107
125
  if (ext === '.css') {
108
- set(newExports, [key], srcPath);
109
- return {
110
- path: [key],
111
- importPath: srcPath,
112
- };
126
+ newExports[key] = srcPath;
127
+ return;
113
128
  }
114
- return {
115
- path: [key, type === 'cjs' ? 'require' : 'import'],
116
- importPath: {
117
- ...rest,
118
- types: addTypes ? srcPath.replace(ext, typeOutExtension) : undefined,
119
- default: srcPath.replace(ext, outExtension),
120
- },
129
+
130
+ if (typeof newExports[key] === 'string' || Array.isArray(newExports[key])) {
131
+ throw new Error(`The export "${key}" is already defined as a string or Array.`);
132
+ }
133
+
134
+ newExports[key] ??= {};
135
+ newExports[key][type === 'cjs' ? 'require' : 'import'] = {
136
+ ...rest,
137
+ ...(addTypes ? { types: srcPath.replace(ext, typeOutExtension) } : {}),
138
+ default: srcPath.replace(ext, outExtension),
121
139
  };
122
140
  }
123
141
 
124
142
  /**
125
143
  * @param {Object} param0
126
- * @param {any} param0.packageJson - The package.json content.
144
+ * @param {import('./packageJson').PackageJson} param0.packageJson - The package.json content.
127
145
  * @param {{type: import('../utils/build.mjs').BundleType; dir: string}[]} param0.bundles
128
146
  * @param {string} param0.outputDir
129
147
  * @param {string} param0.cwd
@@ -138,12 +156,15 @@ async function writePackageJson({ packageJson, bundles, outputDir, cwd, addTypes
138
156
  packageJson.type = packageJson.type || 'commonjs';
139
157
 
140
158
  /**
141
- * @type {Record<string, string | Record<string, string> | null>}
159
+ * @type {import('./packageJson').PackageJson.ExportConditions}
142
160
  */
143
- const originalExports = packageJson.exports || {};
161
+ const originalExports =
162
+ typeof packageJson.exports === 'string' || Array.isArray(packageJson.exports)
163
+ ? { '.': packageJson.exports }
164
+ : packageJson.exports || {};
144
165
  delete packageJson.exports;
145
166
  /**
146
- * @type {Record<string, string | Record<string, string> | null>}
167
+ * @type {import('./packageJson').PackageJson.ExportConditions}
147
168
  */
148
169
  const newExports = {
149
170
  './package.json': './package.json',
@@ -172,10 +193,16 @@ async function writePackageJson({ packageJson, bundles, outputDir, cwd, addTypes
172
193
  if (type === 'cjs') {
173
194
  packageJson.main = exportDir;
174
195
  }
175
- set(newExports, ['.', type === 'cjs' ? 'require' : 'import'], {
176
- types: typeFileExists ? typeExportDir : undefined,
196
+
197
+ if (typeof newExports['.'] === 'string' || Array.isArray(newExports['.'])) {
198
+ throw new Error(`The export "." is already defined as a string or Array.`);
199
+ }
200
+
201
+ newExports['.'] ??= {};
202
+ newExports['.'][type === 'cjs' ? 'require' : 'import'] = {
203
+ ...(typeFileExists ? { types: typeExportDir } : {}),
177
204
  default: exportDir,
178
- });
205
+ };
179
206
  }
180
207
  if (typeFileExists && type === 'cjs') {
181
208
  packageJson.types = typeExportDir;
@@ -185,11 +212,11 @@ async function writePackageJson({ packageJson, bundles, outputDir, cwd, addTypes
185
212
  for (const key of exportKeys) {
186
213
  const importPath = originalExports[key];
187
214
  if (!importPath) {
188
- set(newExports, [key], null);
189
- return;
215
+ newExports[key] = null;
216
+ continue;
190
217
  }
191
218
  // eslint-disable-next-line no-await-in-loop
192
- const res = await createExportsFor({
219
+ await createExportsFor({
193
220
  importPath,
194
221
  key,
195
222
  cwd,
@@ -200,7 +227,6 @@ async function writePackageJson({ packageJson, bundles, outputDir, cwd, addTypes
200
227
  outExtension,
201
228
  addTypes,
202
229
  });
203
- set(newExports, res.path, res.importPath);
204
230
  }
205
231
  }),
206
232
  );
@@ -213,6 +239,11 @@ async function writePackageJson({ packageJson, bundles, outputDir, cwd, addTypes
213
239
  // default condition should come last
214
240
  Object.keys(newExports).forEach((key) => {
215
241
  const exportVal = newExports[key];
242
+ if (Array.isArray(exportVal)) {
243
+ throw new Error(
244
+ `Array form of package.json exports is not supported yet. Found in export "${key}".`,
245
+ );
246
+ }
216
247
  if (exportVal && typeof exportVal === 'object' && (exportVal.import || exportVal.require)) {
217
248
  const defaultExport = exportVal.import || exportVal.require;
218
249
  if (exportVal.import) {
@@ -298,6 +329,18 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
298
329
  type: 'boolean',
299
330
  default: false,
300
331
  description: 'Skip checking for main field in package.json.',
332
+ })
333
+ .option('copy', {
334
+ type: 'string',
335
+ array: true,
336
+ description:
337
+ 'Files/Directories to be copied to the output directory. Can be a glob pattern.',
338
+ default: [],
339
+ })
340
+ .option('enableReactCompiler', {
341
+ type: 'boolean',
342
+ default: false,
343
+ description: 'Whether to use the React compiler.',
301
344
  });
302
345
  },
303
346
  async handler(args) {
@@ -312,12 +355,13 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
312
355
  skipTsc,
313
356
  skipBabelRuntimeCheck = false,
314
357
  skipPackageJson = false,
358
+ enableReactCompiler = false,
315
359
  } = args;
316
360
 
317
361
  const cwd = process.cwd();
318
362
  const pkgJsonPath = path.join(cwd, 'package.json');
319
363
  const packageJson = JSON.parse(await fs.readFile(pkgJsonPath, { encoding: 'utf8' }));
320
- validatePkgJson(packageJson, { skipMainCheck: args.skipMainCheck });
364
+ validatePkgJson(packageJson, { skipMainCheck: args.skipMainCheck, enableReactCompiler });
321
365
 
322
366
  const buildDirBase = /** @type {string} */ (packageJson.publishConfig?.directory);
323
367
  const buildDir = path.join(cwd, buildDirBase);
@@ -346,12 +390,21 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
346
390
  return;
347
391
  }
348
392
 
349
- const babelMod = await import('./babel.mjs');
393
+ const babelMod = await import('../utils/babel.mjs');
350
394
  const relativeOutDirs = {
351
395
  cjs: cjsOutDir,
352
396
  esm: 'esm',
353
397
  };
354
398
  const sourceDir = path.join(cwd, 'src');
399
+ const reactVersion =
400
+ semver.minVersion(packageJson.peerDependencies?.react || '')?.version ?? 'latest';
401
+
402
+ if (enableReactCompiler) {
403
+ const mode = process.env.MUI_REACT_COMPILER_MODE ?? 'opt-in';
404
+ console.log(
405
+ `[feature] Building with React compiler enabled. The compiler mode is "${mode}" right now.${mode === 'opt-in' ? ' Use explicit "use memo" directives in your components to enable the React compiler for them.' : ''}`,
406
+ );
407
+ }
355
408
 
356
409
  // js build start
357
410
  await Promise.all(
@@ -379,6 +432,11 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
379
432
  pkgVersion: packageJson.version,
380
433
  ignores: extraIgnores,
381
434
  outExtension,
435
+ reactCompiler: enableReactCompiler
436
+ ? {
437
+ reactVersion: reactVersion || 'latest',
438
+ }
439
+ : undefined,
382
440
  }),
383
441
  );
384
442
 
@@ -408,7 +466,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
408
466
  // js build end
409
467
 
410
468
  if (buildTypes) {
411
- const tsMod = await import('./typescript.mjs');
469
+ const tsMod = await import('../utils/typescript.mjs');
412
470
  /**
413
471
  * @type {{type: import('../utils/build.mjs').BundleType, dir: string}[]};
414
472
  */
@@ -442,5 +500,144 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
442
500
  outputDir: buildDir,
443
501
  addTypes: buildTypes,
444
502
  });
503
+
504
+ await copyHandler({
505
+ cwd,
506
+ globs: args.copy ?? [],
507
+ buildDir,
508
+ verbose: args.verbose,
509
+ });
445
510
  },
446
511
  });
512
+
513
+ /**
514
+ * @param {Object} param0
515
+ * @param {string} param0.cwd - The current working directory.
516
+ * @param {string[]} [param0.globs=[]] - Extra files to copy, can be specified as `source:target` pairs or just `source`.
517
+ * @param {string} param0.buildDir - The build directory to copy to.
518
+ * @param {boolean} [param0.verbose=false] - Whether to suppress output.
519
+ * @returns {Promise<void>}
520
+ */
521
+ async function copyHandler({ cwd, globs = [], buildDir, verbose = false }) {
522
+ /**
523
+ * @type {(string|{targetPath: string; sourcePath: string})[]}
524
+ */
525
+ const defaultFiles = [];
526
+ const workspaceDir = await findWorkspaceDir(cwd);
527
+ if (!workspaceDir) {
528
+ throw new Error('Workspace directory not found');
529
+ }
530
+
531
+ const localOrRootFiles = [
532
+ [path.join(cwd, 'README.md'), path.join(workspaceDir, 'README.md')],
533
+ [path.join(cwd, 'LICENSE'), path.join(workspaceDir, 'LICENSE')],
534
+ [path.join(cwd, 'CHANGELOG.md'), path.join(workspaceDir, 'CHANGELOG.md')],
535
+ ];
536
+ await Promise.all(
537
+ localOrRootFiles.map(async (filesToCopy) => {
538
+ for (const file of filesToCopy) {
539
+ if (
540
+ // eslint-disable-next-line no-await-in-loop
541
+ await fs.stat(file).then(
542
+ () => true,
543
+ () => false,
544
+ )
545
+ ) {
546
+ defaultFiles.push(file);
547
+ break;
548
+ }
549
+ }
550
+ }),
551
+ );
552
+
553
+ if (globs.length) {
554
+ const res = globs.map((globPattern) => {
555
+ const [pattern, baseDir] = globPattern.split(':');
556
+ return { pattern, baseDir };
557
+ });
558
+ /**
559
+ * Avoids redundant globby calls for the same pattern.
560
+ *
561
+ * @type {Map<string, Promise<string[]>>}
562
+ */
563
+ const globToResMap = new Map();
564
+
565
+ const result = await Promise.all(
566
+ res.map(async ({ pattern, baseDir }) => {
567
+ if (!globToResMap.has(pattern)) {
568
+ const promise = globby(pattern, { cwd });
569
+ globToResMap.set(pattern, promise);
570
+ }
571
+ const files = await globToResMap.get(pattern);
572
+ return { files: files ?? [], baseDir };
573
+ }),
574
+ );
575
+ globToResMap.clear();
576
+
577
+ result.forEach(({ files, baseDir }) => {
578
+ files.forEach((file) => {
579
+ const sourcePath = path.resolve(cwd, file);
580
+ // Use posix separator for the relative paths. So devs can only specify globs with `/` even on Windows.
581
+ const pathSegments = file.split(posixSep);
582
+ const relativePath =
583
+ // Use index 2 (when required) since users can also specify paths like `./src/index.js`
584
+ pathSegments.slice(pathSegments[0] === '.' ? 2 : 1).join(posixSep) || file;
585
+ const targetPath = baseDir
586
+ ? path.resolve(buildDir, baseDir, relativePath)
587
+ : path.resolve(buildDir, relativePath);
588
+ defaultFiles.push({ sourcePath, targetPath });
589
+ });
590
+ });
591
+ }
592
+
593
+ if (!defaultFiles.length) {
594
+ if (verbose) {
595
+ console.log('⓿ No files to copy.');
596
+ }
597
+ }
598
+ await mapConcurrently(
599
+ defaultFiles,
600
+ async (file) => {
601
+ if (typeof file === 'string') {
602
+ const sourcePath = file;
603
+ const fileName = path.basename(file);
604
+ const targetPath = path.join(buildDir, fileName);
605
+ await recursiveCopy({ source: sourcePath, target: targetPath, verbose });
606
+ } else {
607
+ await fs.mkdir(path.dirname(file.targetPath), { recursive: true });
608
+ await recursiveCopy({ source: file.sourcePath, target: file.targetPath, verbose });
609
+ }
610
+ },
611
+ 20,
612
+ );
613
+ console.log(`📋 Copied ${defaultFiles.length} files.`);
614
+ }
615
+
616
+ /**
617
+ * Recursively copies files and directories from a source path to a target path.
618
+ *
619
+ * @async
620
+ * @param {Object} options - The options for copying files.
621
+ * @param {string} options.source - The source path to copy from.
622
+ * @param {string} options.target - The target path to copy to.
623
+ * @param {boolean} [options.verbose=true] - If true, suppresses console output.
624
+ * @returns {Promise<boolean>} Resolves when the copy operation is complete.
625
+ * @throws {Error} Throws if an error occurs other than the source not existing.
626
+ */
627
+ async function recursiveCopy({ source, target, verbose = true }) {
628
+ try {
629
+ await fs.cp(source, target, { recursive: true });
630
+ if (verbose) {
631
+ console.log(`Copied ${source} to ${target}`);
632
+ }
633
+ return true;
634
+ } catch (err) {
635
+ if (/** @type {{ code: string }} */ (err).code !== 'ENOENT') {
636
+ throw err;
637
+ }
638
+ if (verbose) {
639
+ console.warn(`Source does not exist: ${source}`);
640
+ }
641
+ throw err;
642
+ }
643
+ }
@@ -0,0 +1,36 @@
1
+ /* eslint-disable no-console */
2
+
3
+ /**
4
+ * @typedef {Object} Args
5
+ * @property {boolean} authorize
6
+ * @property {boolean} clear
7
+ */
8
+
9
+ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
10
+ command: 'github',
11
+ describe: 'Authenticates the user with GitHub and stores the access token securely.',
12
+ builder: (yargs) =>
13
+ yargs
14
+ .option('authorize', {
15
+ type: 'boolean',
16
+ describe: 'Trigger the authentication flow to get a new token if not found.',
17
+ default: false,
18
+ })
19
+ .option('clear', {
20
+ type: 'boolean',
21
+ describe: 'Clear stored GitHub authentication tokens.',
22
+ default: false,
23
+ }),
24
+ async handler(args) {
25
+ const gh = await import('../utils/github.mjs');
26
+ if (args.clear) {
27
+ await gh.clearGitHubAuth();
28
+ console.log('✅ GitHub auth tokens cleared.');
29
+ return;
30
+ }
31
+ if (args.authorize) {
32
+ await gh.endToEndGhAuthGetToken({ log: true });
33
+ console.log('✅ GitHub auth tokens successfully retrieved.');
34
+ }
35
+ },
36
+ });
@@ -3,12 +3,12 @@
3
3
  /* eslint-disable no-console */
4
4
 
5
5
  /**
6
- * @typedef {import('./pnpm.mjs').PublicPackage} PublicPackage
6
+ * @typedef {import('../utils/pnpm.mjs').PublicPackage} PublicPackage
7
7
  */
8
8
 
9
9
  import * as fs from 'node:fs/promises';
10
10
  import * as path from 'node:path';
11
- import { getWorkspacePackages } from './pnpm.mjs';
11
+ import { getWorkspacePackages } from '../utils/pnpm.mjs';
12
12
 
13
13
  /**
14
14
  * @typedef {Object} Args