@mui/internal-code-infra 0.0.2-canary.9 → 0.0.2

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/src/cli/pnpm.mjs CHANGED
@@ -1,15 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { $ } from 'execa';
4
- import * as fs from 'fs/promises';
5
- import * as path from 'path';
4
+ import * as fs from 'node:fs/promises';
5
+ import * as path from 'node:path';
6
6
  import * as semver from 'semver';
7
7
 
8
8
  /**
9
- * @typedef {Object} Package
9
+ * @typedef {Object} PrivatePackage
10
+ * @property {string} [name] - Package name
11
+ * @property {string} [version] - Package version
12
+ * @property {string} path - Package directory path
13
+ * @property {true} isPrivate - Whether the package is private
14
+ */
15
+
16
+ /**
17
+ * @typedef {Object} PublicPackage
10
18
  * @property {string} name - Package name
11
19
  * @property {string} version - Package version
12
20
  * @property {string} path - Package directory path
21
+ * @property {false} isPrivate - Whether the package is private
13
22
  */
14
23
 
15
24
  /**
@@ -21,7 +30,6 @@ import * as semver from 'semver';
21
30
  /**
22
31
  * @typedef {Object} PublishOptions
23
32
  * @property {boolean} [dryRun] - Whether to run in dry-run mode
24
- * @property {boolean} [provenance] - Whether to include provenance information
25
33
  * @property {boolean} [noGitChecks] - Whether to skip git checks
26
34
  */
27
35
 
@@ -41,8 +49,21 @@ import * as semver from 'semver';
41
49
 
42
50
  /**
43
51
  * Get workspace packages with optional filtering
52
+ *
53
+ * @overload
54
+ * @param {{ publicOnly: true } & GetWorkspacePackagesOptions} [options={}] - Options for filtering packages
55
+ * @returns {Promise<PublicPackage[]>} Array of packages
56
+ *
57
+ * @overload
58
+ * @param {{ publicOnly?: false | undefined } & GetWorkspacePackagesOptions} [options={}] - Options for filtering packages
59
+ * @returns {Promise<PrivatePackage[]>} Array of packages
60
+ *
61
+ * @overload
62
+ * @param {GetWorkspacePackagesOptions} [options={}] - Options for filtering packages
63
+ * @returns {Promise<(PrivatePackage | PublicPackage)[]>} Array of packages
64
+ *
44
65
  * @param {GetWorkspacePackagesOptions} [options={}] - Options for filtering packages
45
- * @returns {Promise<Package[]>} Array of packages
66
+ * @returns {Promise<(PrivatePackage | PublicPackage)[]>} Array of packages
46
67
  */
47
68
  export async function getWorkspacePackages(options = {}) {
48
69
  const { sinceRef = null, publicOnly = false } = options;
@@ -54,18 +75,20 @@ export async function getWorkspacePackages(options = {}) {
54
75
  const packageData = JSON.parse(result.stdout);
55
76
 
56
77
  // Filter packages based on options
57
- const filteredPackages = packageData
58
- .filter((pkg) => !publicOnly || !pkg.private)
59
- .map((pkg) => {
60
- if (!pkg.name || !pkg.version) {
61
- throw new Error(`Invalid package data: ${JSON.stringify(pkg)}`);
62
- }
63
- return {
78
+ const filteredPackages = packageData.flatMap((pkg) => {
79
+ const isPrivate = pkg.private || !pkg.name || !pkg.version;
80
+ if (publicOnly && isPrivate) {
81
+ return [];
82
+ }
83
+ return [
84
+ /** @type {PublicPackage | PrivatePackage} */ ({
64
85
  name: pkg.name,
65
86
  version: pkg.version,
66
87
  path: pkg.path,
67
- };
68
- });
88
+ isPrivate,
89
+ }),
90
+ ];
91
+ });
69
92
 
70
93
  return filteredPackages;
71
94
  }
@@ -105,7 +128,7 @@ export async function getPackageVersionInfo(packageName, baseVersion) {
105
128
 
106
129
  /**
107
130
  * Publish packages with the given options
108
- * @param {Package[]} packages - Packages to publish
131
+ * @param {PublicPackage[]} packages - Packages to publish
109
132
  * @param {string} tag - npm tag to publish with
110
133
  * @param {PublishOptions} [options={}] - Publishing options
111
134
  * @returns {Promise<void>}
@@ -127,14 +150,7 @@ export async function publishPackages(packages, tag, options = {}) {
127
150
  args.push('--no-git-checks');
128
151
  }
129
152
 
130
- // Set up environment variables
131
- /** @type {Record<string, string>} */
132
- const env = {};
133
- if (options.provenance) {
134
- env.NPM_CONFIG_PROVENANCE = 'true';
135
- }
136
-
137
- await $({ stdio: 'inherit', env })`pnpm -r publish --access public --tag=${tag} ${args}`;
153
+ await $({ stdio: 'inherit' })`pnpm -r publish --access public --tag=${tag} ${args}`;
138
154
  }
139
155
 
140
156
  /**
@@ -167,6 +183,29 @@ export async function getCurrentGitSha() {
167
183
  return result.stdout.trim();
168
184
  }
169
185
 
186
+ /**
187
+ * Resolve a package@version specifier to an exact version
188
+ * @param {string} packageSpec - Package specifier in format "package@version"
189
+ * @returns {Promise<string>} Exact version string
190
+ */
191
+ export async function resolveVersion(packageSpec) {
192
+ const result = await $`pnpm info ${packageSpec} version --json`;
193
+ const versions = JSON.parse(result.stdout);
194
+ return typeof versions === 'string' ? versions : versions[versions.length - 1];
195
+ }
196
+
197
+ /**
198
+ * Find the version of a dependency for a specific package@version
199
+ * @param {string} packageSpec - Package specifier in format "package@version"
200
+ * @param {string} dependency - Dependency name to look up
201
+ * @returns {Promise<string>} Exact version string of the dependency
202
+ */
203
+ export async function findDependencyVersionFromSpec(packageSpec, dependency) {
204
+ const result = await $`pnpm info ${packageSpec} dependencies.${dependency}`;
205
+ const spec = result.stdout.trim();
206
+ return resolveVersion(`${dependency}@${spec}`);
207
+ }
208
+
170
209
  /**
171
210
  * Get the maximum semver version between two versions
172
211
  * @param {string} a
@@ -0,0 +1,175 @@
1
+ /* eslint-disable no-console */
2
+ import * as babel from '@babel/core';
3
+ import pluginTypescriptSyntax from '@babel/plugin-syntax-typescript';
4
+ import pluginResolveImports from '@mui/internal-babel-plugin-resolve-imports';
5
+ import pluginRemoveImports from 'babel-plugin-transform-remove-imports';
6
+ import { $ } from 'execa';
7
+ import { globby } from 'globby';
8
+ import * as fs from 'node:fs/promises';
9
+ import * as os from 'node:os';
10
+ import * as path from 'node:path';
11
+
12
+ const $$ = $({ stdio: 'inherit' });
13
+
14
+ /**
15
+ * Emits TypeScript declaration files.
16
+ * @param {string} tsconfig - The path to the tsconfig.json file.
17
+ * @param {string} outDir - The output directory for the declaration files.
18
+ */
19
+ export async function emitDeclarations(tsconfig, outDir) {
20
+ const tsconfigDir = path.dirname(tsconfig);
21
+ const rootDir = path.resolve(tsconfigDir, './src');
22
+ await $$`tsc
23
+ -p ${tsconfig}
24
+ --rootDir ${rootDir}
25
+ --outDir ${outDir}
26
+ --declaration
27
+ --emitDeclarationOnly
28
+ --noEmit false
29
+ --composite false
30
+ --incremental false`;
31
+ }
32
+
33
+ /**
34
+ * @param {string} sourceDirectory
35
+ * @param {string} destinationDirectory
36
+ */
37
+ export async function copyDeclarations(sourceDirectory, destinationDirectory) {
38
+ const fullSourceDirectory = path.resolve(sourceDirectory);
39
+ const fullDestinationDirectory = path.resolve(destinationDirectory);
40
+
41
+ console.log(`Copying declarations from ${fullSourceDirectory} to ${fullDestinationDirectory}`);
42
+
43
+ await fs.cp(fullSourceDirectory, fullDestinationDirectory, {
44
+ recursive: true,
45
+ filter: async (src) => {
46
+ if (src.startsWith('.')) {
47
+ // ignore dotfiles
48
+ return false;
49
+ }
50
+ const stats = await fs.stat(src);
51
+ if (stats.isDirectory()) {
52
+ return true;
53
+ }
54
+ return src.endsWith('.d.ts') || src.endsWith('.d.mts');
55
+ },
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Post-processes TypeScript declaration files.
61
+ * @param {Object} param0
62
+ * @param {string} param0.directory - The directory containing the declaration files.
63
+ */
64
+ async function postProcessDeclarations({ directory }) {
65
+ const dtsFiles = await globby('**/*.d.ts', {
66
+ absolute: true,
67
+ cwd: directory,
68
+ });
69
+ if (dtsFiles.length === 0) {
70
+ console.log(`No d.ts files found in ${directory}. Skipping post-processing.`);
71
+ return;
72
+ }
73
+
74
+ /**
75
+ * @type {import('@babel/core').PluginItem[]}
76
+ */
77
+ const babelPlugins = [
78
+ [pluginTypescriptSyntax, { dts: true }],
79
+ [pluginResolveImports],
80
+ [pluginRemoveImports, { test: /\.css$/ }],
81
+ ];
82
+
83
+ await Promise.all(
84
+ dtsFiles.map(async (dtsFile) => {
85
+ const result = await babel.transformFileAsync(dtsFile, {
86
+ configFile: false,
87
+ plugins: babelPlugins,
88
+ });
89
+
90
+ if (typeof result?.code === 'string') {
91
+ await fs.writeFile(dtsFile, result.code);
92
+ } else {
93
+ console.error('failed to transform', dtsFile);
94
+ }
95
+ }),
96
+ );
97
+ }
98
+
99
+ /**
100
+ * Renames TypeScript declaration files.
101
+ * @param {Object} param0
102
+ * @param {string} param0.directory - The directory containing the declaration files.
103
+ */
104
+ async function renameDeclarations({ directory }) {
105
+ const dtsFiles = await globby('**/*.d.ts', { absolute: true, cwd: directory });
106
+ if (dtsFiles.length === 0) {
107
+ return;
108
+ }
109
+ console.log(`Renaming d.ts files to d.mts in ${directory}`);
110
+ await Promise.all(
111
+ dtsFiles.map(async (dtsFile) => {
112
+ const newFileName = dtsFile.replace(/\.d\.ts$/, '.d.mts');
113
+ await fs.rename(dtsFile, newFileName);
114
+ }),
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Creates TypeScript declaration files for the specified bundles.
120
+ * Types are first created in a temporary directory and then copied to the appropriate bundle directories parallelly.
121
+ * After copying, babel transformations are applied to the copied files because they need to be alongside the actual js files for proper resolution.
122
+ *
123
+ * @param {Object} param0
124
+ * @param {boolean} [param0.isMjsBuild] - Whether the build is for ESM (ECMAScript Modules).
125
+ * @param {{type: import('../utils/build.mjs').BundleType, dir: string}[]} param0.bundles - The bundles to create declarations for.
126
+ * @param {string} param0.srcDir - The source directory.
127
+ * @param {string} param0.buildDir - The build directory.
128
+ * @param {string} param0.cwd - The current working directory.
129
+ * @param {boolean} param0.skipTsc - Whether to skip running TypeScript compiler (tsc) for building types.
130
+ */
131
+ export async function createTypes({ bundles, srcDir, buildDir, cwd, skipTsc, isMjsBuild }) {
132
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'code-infra-build-tsc-'));
133
+
134
+ try {
135
+ await copyDeclarations(srcDir, tmpDir);
136
+ const tsconfigPath = path.join(cwd, 'tsconfig.build.json');
137
+ const tsconfigExists = await fs.stat(tsconfigPath).then(
138
+ (file) => file.isFile(),
139
+ () => false,
140
+ );
141
+ if (!skipTsc) {
142
+ if (!tsconfigExists) {
143
+ throw new Error(
144
+ 'Unable to find a tsconfig to build this project. ' +
145
+ `The package root needs to contain a 'tsconfig.build.json'. ` +
146
+ `The package root is '${cwd}'`,
147
+ );
148
+ }
149
+ console.log(`Building types for ${tsconfigPath} in ${tmpDir}`);
150
+ await emitDeclarations(tsconfigPath, tmpDir);
151
+ }
152
+
153
+ for (const bundle of bundles) {
154
+ const { type: bundleType, dir: bundleOutDir } = bundle;
155
+ const fullOutDir = path.join(buildDir, bundleOutDir);
156
+ // eslint-disable-next-line no-await-in-loop
157
+ await fs.cp(tmpDir, fullOutDir, {
158
+ recursive: true,
159
+ force: false,
160
+ });
161
+ // eslint-disable-next-line no-await-in-loop
162
+ await postProcessDeclarations({
163
+ directory: fullOutDir,
164
+ });
165
+ if (bundleType === 'esm' && isMjsBuild) {
166
+ // eslint-disable-next-line no-await-in-loop
167
+ await renameDeclarations({
168
+ directory: fullOutDir,
169
+ });
170
+ }
171
+ }
172
+ } finally {
173
+ await fs.rm(tmpDir, { recursive: true, force: true });
174
+ }
175
+ }
@@ -1,14 +1,16 @@
1
1
  import { includeIgnoreFile } from '@eslint/compat';
2
+ import eslintJs from '@eslint/js';
2
3
  import prettier from 'eslint-config-prettier/flat';
3
- import reactCompilerPlugin from 'eslint-plugin-react-compiler';
4
+ import importPlugin from 'eslint-plugin-import';
5
+ import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
6
+ import reactPlugin from 'eslint-plugin-react';
7
+ import { configs as reactCompilerPluginConfigs } from 'eslint-plugin-react-compiler';
4
8
  import { configs as reactHookConfigs } from 'eslint-plugin-react-hooks';
5
9
  import globals from 'globals';
6
- import * as tseslint from 'typescript-eslint';
7
10
  import * as fs from 'node:fs';
8
11
  import * as path from 'node:path';
12
+ import * as tseslint from 'typescript-eslint';
9
13
 
10
- import { airbnbBaseConfig, airbnbReactConfig } from './airbnb/base.mjs';
11
- import airbnbTypescript from './airbnb/typescript.mjs';
12
14
  import { createCoreConfig } from './material-ui/config.mjs';
13
15
  import muiPlugin from './material-ui/index.mjs';
14
16
  /**
@@ -36,16 +38,19 @@ export function createBaseConfig(
36
38
  return /** @type {import('eslint').Linter.Config[]} */ (
37
39
  tseslint.config(
38
40
  ...ignoreRules,
39
- airbnbBaseConfig,
40
- airbnbReactConfig,
41
- airbnbTypescript,
41
+ eslintJs.configs.recommended,
42
+ importPlugin.flatConfigs.recommended,
43
+ importPlugin.flatConfigs.react,
44
+ jsxA11yPlugin.flatConfigs.recommended,
45
+ reactPlugin.configs.flat.recommended,
42
46
  reactHookConfigs.recommended,
43
- enableReactCompiler ? reactCompilerPlugin.configs.recommended : {},
47
+ tseslint.configs.recommended,
48
+ importPlugin.flatConfigs.typescript,
49
+ enableReactCompiler ? reactCompilerPluginConfigs.recommended : {},
44
50
  prettier,
45
51
  {
46
52
  name: 'typescript-eslint-parser',
47
53
  languageOptions: {
48
- parser: tseslint.parser,
49
54
  ecmaVersion: 7,
50
55
  globals: {
51
56
  ...globals.es2020,
@@ -54,19 +59,8 @@ export function createBaseConfig(
54
59
  },
55
60
  },
56
61
  plugins: {
57
- '@typescript-eslint': tseslint.plugin,
58
62
  'material-ui': muiPlugin,
59
63
  },
60
- settings: {
61
- 'import/parsers': {
62
- '@typescript-eslint/parser': ['.ts', '.tsx'],
63
- },
64
- 'import/resolver': {
65
- typescript: {
66
- project: ['tsconfig.node.json', 'apps/*/tsconfig.json', 'packages/*/tsconfig.json'],
67
- },
68
- },
69
- },
70
64
  extends: createCoreConfig({ reactCompilerEnabled: enableReactCompiler }),
71
65
  },
72
66
  {
@@ -82,6 +76,27 @@ export function createBaseConfig(
82
76
  ],
83
77
  },
84
78
  },
79
+ // Lint rule to disallow usage of typescript namespaces.We've seen at least two problems with them:
80
+ // * Creates non-portable types in base ui. [1]
81
+ // * This pattern [2] leads to broken bundling in codesandbox [3].
82
+ // Gauging the ecosystem it also looks like support for namespaces in tooling is poor and tends to
83
+ // be treated as a deprecated feature.
84
+ // [1] https://github.com/mui/base-ui/pull/2324
85
+ // [2] https://github.com/mui/mui-x/blob/1cf853ed45cf301211ece1c0ca21981ea208edfb/packages/x-virtualizer/src/models/core.ts#L4-L10
86
+ // [3] https://codesandbox.io/embed/kgylpd?module=/src/Demo.tsx&fontsize=12
87
+ {
88
+ rules: {
89
+ '@typescript-eslint/no-namespace': 'error',
90
+ },
91
+ },
92
+ // Part of the migration away from airbnb config. Turned of initially.
93
+ {
94
+ rules: {
95
+ '@typescript-eslint/no-explicit-any': 'off',
96
+ '@typescript-eslint/no-unsafe-function-type': 'off',
97
+ '@typescript-eslint/no-empty-object-type': 'off',
98
+ },
99
+ },
85
100
  )
86
101
  );
87
102
  }