@mui/internal-code-infra 0.1.1-canary.2 → 0.1.1-canary.20

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 (35) hide show
  1. package/README.md +3 -3
  2. package/build/changelog/fetchChangelogs.d.mts +9 -10
  3. package/build/changelog/index.d.mts +1 -0
  4. package/build/cli/cmdBaseline.d.mts +18 -0
  5. package/build/cli/cmdNetlifyIgnore.d.mts +1 -1
  6. package/build/eslint/mui/rules/no-constructor-assigned-field.d.mts +50 -0
  7. package/build/eslint/packageJsonConfig.d.mts +7 -0
  8. package/build/remark/config.d.mts +5 -14
  9. package/build/utils/build.d.mts +14 -0
  10. package/build/utils/credentials.d.mts +2 -2
  11. package/build/utils/git.d.mts +30 -9
  12. package/build/utils/github.d.mts +8 -0
  13. package/package.json +38 -35
  14. package/src/changelog/fetchChangelogs.mjs +66 -26
  15. package/src/changelog/index.mjs +3 -0
  16. package/src/cli/cmdBaseline.mjs +31 -0
  17. package/src/cli/cmdBuild.mjs +2 -0
  18. package/src/cli/cmdNetlifyIgnore.mjs +14 -7
  19. package/src/cli/index.mjs +2 -0
  20. package/src/eslint/baseConfig.mjs +2 -0
  21. package/src/eslint/docsConfig.mjs +28 -10
  22. package/src/eslint/jsonConfig.mjs +2 -1
  23. package/src/eslint/mui/config.mjs +16 -1
  24. package/src/eslint/mui/index.mjs +2 -0
  25. package/src/eslint/mui/rules/no-constructor-assigned-field.mjs +183 -0
  26. package/src/eslint/packageJsonConfig.mjs +50 -0
  27. package/src/eslint/testConfig.mjs +1 -2
  28. package/src/remark/config.mjs +60 -13
  29. package/src/utils/build.mjs +34 -0
  30. package/src/utils/credentials.mjs +4 -3
  31. package/src/utils/extractErrorCodes.mjs +1 -3
  32. package/src/utils/git.mjs +107 -46
  33. package/src/utils/github.mjs +27 -0
  34. package/vale/styles/MUI/GoogleLatin.yml +5 -2
  35. package/vale/styles/MUI/CorrectRererenceCased.yml +0 -14
package/src/utils/git.mjs CHANGED
@@ -9,68 +9,63 @@ import gitUrlParse from 'git-url-parse';
9
9
  */
10
10
 
11
11
  /**
12
- * Get current repository info from git remote
12
+ * The repository a remote URL points at, if it is a GitHub repository under `mui`.
13
+ * @param {string} url
14
+ * @returns {{ owner: string, repo: string } | null}
15
+ */
16
+ function muiRepositoryOf(url) {
17
+ try {
18
+ const parsed = gitUrlParse(url);
19
+ return parsed.source === 'github.com' && parsed.owner === 'mui'
20
+ ? { owner: parsed.owner, repo: parsed.name }
21
+ : null;
22
+ } catch {
23
+ // Not a URL git-url-parse understands, such as a local path: not a mui repository either.
24
+ return null;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * The canonical repository's remote, as opposed to a fork's: `upstream`, else `origin`, each only if
30
+ * it points at a mui repository. Reads the configured URLs, which `insteadOf` does not rewrite.
13
31
  * @param {string} [cwd=process.cwd()]
14
- * @returns {Promise<RepoInfo>} Repository owner and name
32
+ * @returns {Promise<RepoInfo>}
15
33
  */
16
34
  export async function getRepositoryInfo(cwd = process.cwd()) {
17
- /**
18
- * @type {Record<string, string>}
19
- */
20
- const cause = {};
21
- const { stdout } = await $({ cwd })`git remote -v`;
22
- const lines = stdout.trim().split('\n');
23
- /**
24
- * @type {Set<string>}
25
- */
26
- const repoRemotes = new Set();
27
- /**
28
- * @type {Map<string, { owner: string, repo: string }>}
29
- */
30
- const validRemotes = new Map();
31
-
32
- for (const line of lines) {
33
- // Match pattern: "remoteName url (fetch|push)"
34
- const [remoteName, url, type] = line.trim().split(/\s+/, 3);
35
- repoRemotes.add(remoteName);
36
- if (type === '(fetch)') {
37
- try {
38
- const parsed = gitUrlParse(url);
39
- if (parsed.source !== 'github.com' || parsed.owner !== 'mui') {
40
- cause[remoteName] = `Remote is not a GitHub repository under 'mui' organization: ${url}`;
41
- continue;
42
- }
43
- if (!validRemotes.has(remoteName)) {
44
- validRemotes.set(remoteName, { owner: parsed.owner, repo: parsed.name });
45
- }
46
- } catch (error) {
47
- cause[remoteName] = `Failed to parse URL for remote ${remoteName}: ${url}`;
48
- }
49
- } else if (type !== '(push)') {
50
- throw new Error(`Unexpected line format for "git remote -v": "${line}"`);
51
- }
35
+ // Exits 1 when neither remote is configured; the error below reports that.
36
+ const { stdout } = await $({
37
+ cwd,
38
+ reject: false,
39
+ })`git config --get-regexp ${'^remote\\.(upstream|origin)\\.url$'}`;
40
+ /** @type {Map<string, string>} */
41
+ const urls = new Map();
42
+ for (const line of stdout.split('\n').filter(Boolean)) {
43
+ const [key, url] = line.split(' ', 2);
44
+ urls.set(key.slice('remote.'.length, -'.url'.length), url);
52
45
  }
53
46
 
54
- const preferredOrder = ['upstream', 'origin', ...validRemotes.keys()];
55
- for (const name of preferredOrder) {
56
- const match = validRemotes.get(name);
57
- if (match) {
58
- return { ...match, remoteName: name };
47
+ for (const remoteName of ['upstream', 'origin']) {
48
+ const url = urls.get(remoteName);
49
+ const repository = url ? muiRepositoryOf(url) : null;
50
+ if (repository) {
51
+ return { ...repository, remoteName };
59
52
  }
60
53
  }
61
54
 
55
+ const found = [...urls].map(([name, url]) => `${name}: ${url}`).join(', ') || 'none';
62
56
  throw new Error(
63
- `Failed to find correct remote(s) in : ${Array.from(repoRemotes.keys()).join(', ')}`,
64
- { cause },
57
+ `No canonical remote: neither \`upstream\` nor \`origin\` points at a mui repository (${found}). ` +
58
+ 'Add an `upstream` remote for it, or name the remote explicitly where the command allows it.',
65
59
  );
66
60
  }
67
61
 
68
62
  /**
69
63
  * Get current git SHA
64
+ * @param {string} [cwd=process.cwd()]
70
65
  * @returns {Promise<string>} Current git commit SHA
71
66
  */
72
- export async function getCurrentGitSha() {
73
- const result = await $`git rev-parse HEAD`;
67
+ export async function getCurrentGitSha(cwd = process.cwd()) {
68
+ const result = await $({ cwd })`git rev-parse HEAD`;
74
69
  return result.stdout.trim();
75
70
  }
76
71
 
@@ -84,3 +79,69 @@ export async function remoteGitTagExists(tagName, cwd = process.cwd()) {
84
79
  const { stdout } = await $({ cwd })`git ls-remote --tags origin refs/tags/${tagName}`;
85
80
  return stdout.trim().length > 0;
86
81
  }
82
+
83
+ /**
84
+ * The default branch of `remote`, as the remote itself reports it.
85
+ * @param {string} remote
86
+ * @param {string} cwd
87
+ * @returns {Promise<string>}
88
+ */
89
+ async function getDefaultBranchOf(remote, cwd) {
90
+ const { stdout } = await $({ cwd })`git ls-remote --symref ${remote} HEAD`;
91
+ const match = /^ref: refs\/heads\/(\S+)\tHEAD$/m.exec(stdout);
92
+ if (!match) {
93
+ throw new Error(`Remote "${remote}" does not report a default branch.`);
94
+ }
95
+ return match[1];
96
+ }
97
+
98
+ /**
99
+ * The commit HEAD should be compared against: where it forked from the base branch, or its parent
100
+ * when HEAD is already on the base branch.
101
+ * @param {Object} [options]
102
+ * @param {string} [options.cwd]
103
+ * @param {string} [options.remote] - Defaults to the canonical remote.
104
+ * @param {string} [options.baseBranch] - Defaults to the remote's default branch.
105
+ * @returns {Promise<string>}
106
+ */
107
+ export async function resolveBaseline(options = {}) {
108
+ const cwd = options.cwd ?? process.cwd();
109
+ const remote = options.remote ?? (await getRepositoryInfo(cwd)).remoteName;
110
+ const baseBranch = options.baseBranch ?? (await getDefaultBranchOf(remote, cwd));
111
+
112
+ await $({ cwd })`git fetch --no-tags ${remote} ${baseBranch}`;
113
+ const [forkPoint, head] = await Promise.all([
114
+ $({ cwd })`git merge-base HEAD FETCH_HEAD`.then((result) => result.stdout.trim()),
115
+ getCurrentGitSha(cwd),
116
+ ]);
117
+ if (forkPoint !== head) {
118
+ return forkPoint;
119
+ }
120
+
121
+ const parent = await $({ cwd, reject: false })`git rev-parse --verify HEAD~1`;
122
+ if (parent.exitCode !== 0) {
123
+ throw new Error(
124
+ 'HEAD has no parent commit to compare against. A shallow clone is the usual cause.',
125
+ );
126
+ }
127
+ return parent.stdout.trim();
128
+ }
129
+
130
+ /**
131
+ * The most recent version tag reachable from HEAD — where a changelog's range starts.
132
+ * @param {Object} opts
133
+ * @param {string} opts.cwd
134
+ * @param {boolean} [opts.fetchAll=true] Whether to fetch all tags from all remotes before finding the latest tag.
135
+ * @returns {Promise<string>}
136
+ */
137
+ export async function findLatestTaggedVersion(opts) {
138
+ const $$ = $({ cwd: opts.cwd });
139
+ const fetchAll = opts.fetchAll ?? true;
140
+ if (fetchAll) {
141
+ // --force to update any existing tags that may have changed to avoid the clobering error.
142
+ const { remoteName } = await getRepositoryInfo(opts.cwd);
143
+ await $$`git fetch --tags --force ${remoteName}`;
144
+ }
145
+ const { stdout } = await $$`git describe --tags --abbrev=0 --match ${'v*'}`; // only include "version-tags"
146
+ return stdout.trim();
147
+ }
@@ -261,3 +261,30 @@ export async function endToEndGhAuthGetToken({ log = false } = {}) {
261
261
  export async function clearGitHubAuth() {
262
262
  await credentials.deleteKey(GITHUB_APP_CREDENTIAL_KEY);
263
263
  }
264
+
265
+ /**
266
+ * Returns when a rate-limited request may be retried, or `null` for other errors.
267
+ * https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api#exceeding-the-rate-limit
268
+ *
269
+ * @param {unknown} error
270
+ * @returns {Date | null}
271
+ */
272
+ export function getRateLimitRetryTime(error) {
273
+ const { status, response, message } = /** @type {any} */ (error) ?? {};
274
+ if (status !== 403 && status !== 429) {
275
+ return null;
276
+ }
277
+ const headers = response?.headers ?? {};
278
+ const now = Date.now();
279
+ let retryTime;
280
+ if (headers['retry-after']) {
281
+ retryTime = now + Number(headers['retry-after']) * 1000;
282
+ } else if (headers['x-ratelimit-remaining'] === '0' && headers['x-ratelimit-reset']) {
283
+ retryTime = Number(headers['x-ratelimit-reset']) * 1000;
284
+ } else if (/rate limit/i.test(message ?? '')) {
285
+ retryTime = now;
286
+ } else {
287
+ return null;
288
+ }
289
+ return new Date(Math.max(retryTime, now + 60_000));
290
+ }
@@ -6,6 +6,9 @@ level: error
6
6
  nonword: true
7
7
  action:
8
8
  name: replace
9
+ # Wider than upstream Google.Latin: also catches e.g/eg./i.e/ie. and any trailing
10
+ # punctuation, so no separate rule asks for "e.g." (which this one forbids).
11
+ # The lookbehind skips matches after a dot or inside a word (example.eg).
9
12
  swap:
10
- '\b(?:eg|e\.g\.)(?=[\s,;])': for example
11
- '\b(?:ie|i\.e\.)(?=[\s,;])': that is
13
+ '(?<![\w.])e\.?g\.?(?=[^\w.]|$)': for example
14
+ '(?<![\w.])i\.?e\.?(?=[^\w.]|$)': that is
@@ -1,14 +0,0 @@
1
- # Write things correctly, please no wrong references.
2
- extends: substitution
3
- message: Use '%s' instead of '%s'
4
- level: error
5
- ignorecase: false
6
- # swap maps tokens in form of bad: good
7
- # for more information: https://vale.sh/docs/topics/styles/#substitution
8
- swap:
9
- eg: e.g.
10
- eg\.: e.g.
11
- e\.g: e.g.
12
- ie: i.e.
13
- ie\.: i.e.
14
- i\.e: i.e.