@mui/internal-code-infra 0.0.3-canary.6 → 0.0.3-canary.60
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/README.md +55 -0
- package/build/babel-config.d.mts +40 -0
- package/build/brokenLinksChecker/index.d.mts +138 -0
- package/build/cli/cmdArgosPush.d.mts +13 -0
- package/build/cli/cmdBuild.d.mts +56 -0
- package/build/cli/cmdCopyFiles.d.mts +20 -0
- package/build/cli/cmdExtractErrorCodes.d.mts +3 -0
- package/build/cli/cmdGithubAuth.d.mts +6 -0
- package/build/cli/cmdListWorkspaces.d.mts +18 -0
- package/build/cli/cmdPublish.d.mts +27 -0
- package/build/cli/cmdPublishCanary.d.mts +30 -0
- package/build/cli/cmdPublishNewPackage.d.mts +8 -0
- package/build/cli/cmdSetVersionOverrides.d.mts +9 -0
- package/build/cli/cmdValidateBuiltTypes.d.mts +2 -0
- package/build/cli/index.d.mts +1 -0
- package/build/eslint/baseConfig.d.mts +10 -0
- package/build/eslint/docsConfig.d.mts +4 -0
- package/build/eslint/extensions.d.mts +8 -0
- package/build/eslint/index.d.mts +4 -0
- package/build/eslint/jsonConfig.d.mts +4 -0
- package/build/eslint/material-ui/config.d.mts +8 -0
- package/build/eslint/material-ui/index.d.mts +2 -0
- package/build/eslint/material-ui/rules/disallow-active-element-as-key-event-target.d.mts +5 -0
- package/build/eslint/material-ui/rules/disallow-react-api-in-server-components.d.mts +2 -0
- package/build/eslint/material-ui/rules/docgen-ignore-before-comment.d.mts +2 -0
- package/build/eslint/material-ui/rules/mui-name-matches-component-name.d.mts +5 -0
- package/build/eslint/material-ui/rules/no-empty-box.d.mts +5 -0
- package/build/eslint/material-ui/rules/no-restricted-resolved-imports.d.mts +12 -0
- package/build/eslint/material-ui/rules/no-styled-box.d.mts +5 -0
- package/build/eslint/material-ui/rules/rules-of-use-theme-variants.d.mts +9 -0
- package/build/eslint/material-ui/rules/straight-quotes.d.mts +5 -0
- package/build/eslint/testConfig.d.mts +14 -0
- package/build/markdownlint/duplicate-h1.d.mts +27 -0
- package/build/markdownlint/git-diff.d.mts +8 -0
- package/build/markdownlint/index.d.mts +56 -0
- package/build/markdownlint/straight-quotes.d.mts +8 -0
- package/build/markdownlint/table-alignment.d.mts +8 -0
- package/build/markdownlint/terminal-language.d.mts +8 -0
- package/build/prettier.d.mts +20 -0
- package/build/stylelint/index.d.mts +32 -0
- package/build/utils/babel.d.mts +71 -0
- package/build/utils/build.d.mts +50 -0
- package/build/utils/changelog.d.mts +64 -0
- package/build/utils/credentials.d.mts +17 -0
- package/build/utils/extractErrorCodes.d.mts +19 -0
- package/build/utils/git.d.mts +26 -0
- package/build/utils/github.d.mts +41 -0
- package/build/utils/pnpm.d.mts +238 -0
- package/build/utils/typescript.d.mts +35 -0
- package/package.json +92 -42
- package/src/babel-config.mjs +52 -8
- package/src/brokenLinksChecker/__fixtures__/static-site/broken-links.html +20 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/broken-targets.html +22 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/example.md +9 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/external-links.html +21 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/ignored-page.html +17 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/index.html +26 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/known-targets.json +5 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/nested/page.html +19 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/orphaned-page.html +20 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/page-with-api-links.html +20 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/page-with-custom-targets.html +24 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/page-with-ignored-content.html +28 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/page-with-known-target-links.html +19 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/valid.html +20 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/with-anchors.html +31 -0
- package/src/brokenLinksChecker/index.mjs +641 -0
- package/src/brokenLinksChecker/index.test.ts +178 -0
- package/src/cli/cmdArgosPush.mjs +13 -2
- package/src/cli/cmdBuild.mjs +228 -31
- package/src/cli/cmdGithubAuth.mjs +36 -0
- package/src/cli/cmdListWorkspaces.mjs +2 -2
- package/src/cli/cmdPublish.mjs +203 -49
- package/src/cli/cmdPublishCanary.mjs +404 -46
- package/src/cli/cmdPublishNewPackage.mjs +86 -0
- package/src/cli/cmdSetVersionOverrides.mjs +17 -1
- package/src/cli/cmdValidateBuiltTypes.mjs +49 -0
- package/src/cli/index.mjs +6 -2
- package/src/cli/packageJson.d.ts +729 -0
- package/src/eslint/baseConfig.mjs +96 -78
- package/src/eslint/docsConfig.mjs +13 -13
- package/src/eslint/extensions.mjs +8 -8
- package/src/eslint/jsonConfig.mjs +40 -0
- package/src/eslint/material-ui/config.mjs +8 -9
- 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/eslint/testConfig.mjs +72 -66
- package/src/stylelint/index.mjs +46 -0
- package/src/untyped-plugins.d.ts +13 -0
- package/src/{cli → utils}/babel.mjs +10 -3
- package/src/utils/build.mjs +27 -1
- package/src/utils/changelog.mjs +157 -0
- package/src/utils/credentials.mjs +71 -0
- package/src/utils/extractErrorCodes.mjs +2 -2
- package/src/utils/git.mjs +67 -0
- package/src/utils/github.mjs +263 -0
- package/src/{cli → utils}/pnpm.mjs +23 -13
- package/src/{cli → utils}/typescript.mjs +13 -7
- 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
|
+
});
|
package/src/cli/cmdArgosPush.mjs
CHANGED
|
@@ -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(
|
|
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 {
|
package/src/cli/cmdBuild.mjs
CHANGED
|
@@ -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
|
|
4
|
+
import { globby } from 'globby';
|
|
4
5
|
import * as fs from 'node:fs/promises';
|
|
5
6
|
import * as path from 'node:path';
|
|
6
|
-
import {
|
|
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 {
|
|
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 {
|
|
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<
|
|
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
|
-
|
|
109
|
-
return
|
|
110
|
-
path: [key],
|
|
111
|
-
importPath: srcPath,
|
|
112
|
-
};
|
|
126
|
+
newExports[key] = srcPath;
|
|
127
|
+
return;
|
|
113
128
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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 {
|
|
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 {
|
|
159
|
+
* @type {import('./packageJson').PackageJson.ExportConditions}
|
|
142
160
|
*/
|
|
143
|
-
const originalExports =
|
|
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 {
|
|
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
|
-
|
|
176
|
-
|
|
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
|
-
|
|
189
|
-
|
|
215
|
+
newExports[key] = null;
|
|
216
|
+
continue;
|
|
190
217
|
}
|
|
191
218
|
// eslint-disable-next-line no-await-in-loop
|
|
192
|
-
|
|
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('
|
|
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('
|
|
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('
|
|
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 '
|
|
11
|
+
import { getWorkspacePackages } from '../utils/pnpm.mjs';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @typedef {Object} Args
|