diff --git a/bin/packages/check-build-type-declaration-files.js b/bin/packages/check-build-type-declaration-files.js new file mode 100644 index 0000000000000..cd8bad2a70b53 --- /dev/null +++ b/bin/packages/check-build-type-declaration-files.js @@ -0,0 +1,121 @@ +/** + * This script verifies the published index.d.ts file for every package which both + * builds types and also sets checkJs to false in its tsconfig.json. (This scenario + * can cause unchecked errors in JS files to be included in the compiled types.) + * + * We do so by running `tsc --noEmit` on the $package/build-types/index.d.ts file. + * This also verifies everything index.d.ts references, so it checks the entire + * public api of the type declarations for that package. + * + * @see https://github.com/WordPress/gutenberg/pull/49650 for more discussion. + */ + +/** + * External dependencies + */ +const fs = require( 'fs' ).promises; +const path = require( 'path' ); +const { exec } = require( 'child_process' ); +const chalk = require( 'chalk' ); + +/** + * Returns whether a package needs its compiled types to be double-checked. This + * needs to happen when both of these are true: + * 1. The package compiles types. (It has a tsconfig file.) + * 2. The tsconfig sets checkJs to false. + * + * NOTE: In the future, if we run into issues parsing JSON, we should migrate to + * a proper json5 parser, such as the json5 npm package. The current regex just + * handles comments, which at the time is the only thing we use from JSON5. + * + * @param {string} packagePath Path to the package. + * @return {boolean} whether or not the package checksJs. + */ +async function packageNeedsExtraCheck( packagePath ) { + const configPath = path.join( packagePath, 'tsconfig.json' ); + + try { + const tsconfigRaw = await fs.readFile( configPath, 'utf-8' ); + // Removes comments from the JSON5 string to convert it to plain JSON. + const jsonString = tsconfigRaw.replace( /\s+\/\/.*$/gm, '' ); + const config = JSON.parse( jsonString ); + + // If checkJs both exists and is false, then we need the extra check. + return config.compilerOptions?.checkJs === false; + } catch ( e ) { + if ( e.code !== 'ENOENT' ) { + throw e; + } + + // No tsconfig means no checkJs + return false; + } +} + +// Returns the path to the build-types declaration file for a package if it exists. +// Throws an error and exits the script otherwise. +async function getDecFile( packagePath ) { + const decFile = path.join( packagePath, 'build-types', 'index.d.ts' ); + try { + await fs.access( decFile ); + return decFile; + } catch ( err ) { + console.error( + `Cannot access this declaration file. You may need to run tsc again: ${ decFile }` + ); + process.exit( 1 ); + } +} + +async function typecheckDeclarations( file ) { + return new Promise( ( resolve, reject ) => { + exec( `npx tsc --noEmit ${ file }`, ( error, stdout, stderr ) => { + if ( error ) { + reject( { file, error, stderr, stdout } ); + } else { + resolve( { file, stdout } ); + } + } ); + } ); +} + +async function checkUnverifiedDeclarationFiles() { + const packageDir = path.resolve( 'packages' ); + const packageDirs = ( + await fs.readdir( packageDir, { withFileTypes: true } ) + ) + .filter( ( dirent ) => dirent.isDirectory() ) + .map( ( dirent ) => path.join( packageDir, dirent.name ) ); + + // Finds the compiled type declarations for each package which both checks + // types and has checkJs disabled. + const declarations = ( + await Promise.all( + packageDirs.map( async ( pkg ) => + ( await packageNeedsExtraCheck( pkg ) ) + ? getDecFile( pkg ) + : null + ) + ) + ).filter( Boolean ); + + const tscResults = await Promise.allSettled( + declarations.map( typecheckDeclarations ) + ); + + tscResults.forEach( ( { status, reason } ) => { + if ( status !== 'fulfilled' ) { + console.error( + chalk.red( + `Incorrect published types for ${ reason.file }:\n` + ), + reason.stdout + ); + } + } ); + + if ( tscResults.some( ( { status } ) => status !== 'fulfilled' ) ) { + process.exit( 1 ); + } +} +checkUnverifiedDeclarationFiles(); diff --git a/package-lock.json b/package-lock.json index 6d804ff79a5d1..851e882622b8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16810,7 +16810,7 @@ "@wordpress/hooks": "file:packages/hooks", "@wordpress/i18n": "file:packages/i18n", "@wordpress/rich-text": "file:packages/rich-text", - "rememo": "^4.0.0", + "rememo": "^4.0.2", "uuid": "^8.3.0" } }, @@ -16946,7 +16946,7 @@ "lodash": "^4.17.21", "react-autosize-textarea": "^7.1.0", "react-easy-crop": "^4.5.1", - "rememo": "^4.0.0", + "rememo": "^4.0.2", "remove-accents": "^0.4.2", "traverse": "^0.6.6" } @@ -17034,7 +17034,7 @@ "is-plain-object": "^5.0.0", "lodash": "^4.17.21", "memize": "^1.1.0", - "rememo": "^4.0.0", + "rememo": "^4.0.2", "remove-accents": "^0.4.2", "showdown": "^1.9.1", "simple-html-tokenizer": "^0.5.7", @@ -17057,7 +17057,7 @@ "@wordpress/keyboard-shortcuts": "file:packages/keyboard-shortcuts", "@wordpress/private-apis": "file:packages/private-apis", "cmdk": "^0.2.0", - "rememo": "^4.0.0" + "rememo": "^4.0.2" } }, "@wordpress/components": { @@ -17165,7 +17165,7 @@ "equivalent-key-map": "^0.2.2", "fast-deep-equal": "^3.1.3", "memize": "^1.1.0", - "rememo": "^4.0.0", + "rememo": "^4.0.2", "uuid": "^8.3.0" } }, @@ -17383,7 +17383,7 @@ "@wordpress/widgets": "file:packages/widgets", "classnames": "^2.3.1", "memize": "^1.1.0", - "rememo": "^4.0.0" + "rememo": "^4.0.2" }, "dependencies": { "@wordpress/dom": { @@ -17459,7 +17459,7 @@ "lodash": "^4.17.21", "memize": "^1.1.0", "react-autosize-textarea": "^7.1.0", - "rememo": "^4.0.0" + "rememo": "^4.0.2" }, "dependencies": { "colord": { @@ -17539,7 +17539,7 @@ "lodash": "^4.17.21", "memize": "^1.1.0", "react-autosize-textarea": "^7.1.0", - "rememo": "^4.0.0", + "rememo": "^4.0.2", "remove-accents": "^0.4.2" }, "dependencies": { @@ -17839,7 +17839,7 @@ "@wordpress/data": "file:packages/data", "@wordpress/element": "file:packages/element", "@wordpress/keycodes": "file:packages/keycodes", - "rememo": "^4.0.0" + "rememo": "^4.0.2" } }, "@wordpress/keycodes": { @@ -18222,7 +18222,7 @@ "@wordpress/i18n": "file:packages/i18n", "@wordpress/keycodes": "file:packages/keycodes", "memize": "^1.1.0", - "rememo": "^4.0.0" + "rememo": "^4.0.2" } }, "@wordpress/scripts": { @@ -50858,9 +50858,9 @@ } }, "rememo": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/rememo/-/rememo-4.0.0.tgz", - "integrity": "sha512-6BAfg1Dqg6UteZBEH9k6EHHersM86/EcBOMtJV+h+xEn1GC3H+gAgJWpexWYAamAxD0qXNmIt50iS/zuZKnQag==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/rememo/-/rememo-4.0.2.tgz", + "integrity": "sha512-NVfSP9NstE3QPNs/TnegQY0vnJnstKQSpcrsI2kBTB3dB2PkdfKdTa+abbjMIDqpc63fE5LfjLgfMst0ULMFxQ==" }, "remove-accents": { "version": "0.4.2", diff --git a/package.json b/package.json index fb6d0a3710ab8..14309c178dea3 100644 --- a/package.json +++ b/package.json @@ -251,7 +251,7 @@ "scripts": { "build": "npm run build:packages && wp-scripts build", "build:analyze-bundles": "npm run build -- --webpack-bundle-analyzer", - "build:package-types": "node ./bin/packages/validate-typescript-version.js && tsc --build", + "build:package-types": "node ./bin/packages/validate-typescript-version.js && tsc --build && node ./bin/packages/check-build-type-declaration-files.js", "prebuild:packages": "npm run clean:packages && lerna run build", "build:packages": "npm run build:package-types && node ./bin/packages/build.js", "build:plugin-zip": "bash ./bin/build-plugin-zip.sh", diff --git a/packages/annotations/package.json b/packages/annotations/package.json index dcdeaab3180bb..231a66fcaaa5d 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -30,7 +30,7 @@ "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", "@wordpress/rich-text": "file:../rich-text", - "rememo": "^4.0.0", + "rememo": "^4.0.2", "uuid": "^8.3.0" }, "publishConfig": { diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index bb7e99ca21038..7db45af56aa2b 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -72,7 +72,7 @@ "lodash": "^4.17.21", "react-autosize-textarea": "^7.1.0", "react-easy-crop": "^4.5.1", - "rememo": "^4.0.0", + "rememo": "^4.0.2", "remove-accents": "^0.4.2", "traverse": "^0.6.6" }, diff --git a/packages/blocks/package.json b/packages/blocks/package.json index e0dae87182dc1..ad0e188502362 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -50,7 +50,7 @@ "is-plain-object": "^5.0.0", "lodash": "^4.17.21", "memize": "^1.1.0", - "rememo": "^4.0.0", + "rememo": "^4.0.2", "remove-accents": "^0.4.2", "showdown": "^1.9.1", "simple-html-tokenizer": "^0.5.7", diff --git a/packages/commands/package.json b/packages/commands/package.json index 0ba9afd4807c8..0d5cc0d81fa4d 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -35,7 +35,7 @@ "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", "@wordpress/private-apis": "file:../private-apis", "cmdk": "^0.2.0", - "rememo": "^4.0.0" + "rememo": "^4.0.2" }, "peerDependencies": { "react": "^18.0.0" diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 8ed5b7790c96b..34f0a19ee81f9 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -45,7 +45,7 @@ "equivalent-key-map": "^0.2.2", "fast-deep-equal": "^3.1.3", "memize": "^1.1.0", - "rememo": "^4.0.0", + "rememo": "^4.0.2", "uuid": "^8.3.0" }, "peerDependencies": { diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index d774dd50b0b9b..ed2e70633451b 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -57,7 +57,7 @@ "@wordpress/widgets": "file:../widgets", "classnames": "^2.3.1", "memize": "^1.1.0", - "rememo": "^4.0.0" + "rememo": "^4.0.2" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 463eceff350ff..db4d51782f93f 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -66,7 +66,7 @@ "lodash": "^4.17.21", "memize": "^1.1.0", "react-autosize-textarea": "^7.1.0", - "rememo": "^4.0.0" + "rememo": "^4.0.2" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/editor/package.json b/packages/editor/package.json index c5769be6d1720..68adcde0398c9 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -65,7 +65,7 @@ "lodash": "^4.17.21", "memize": "^1.1.0", "react-autosize-textarea": "^7.1.0", - "rememo": "^4.0.0", + "rememo": "^4.0.2", "remove-accents": "^0.4.2" }, "peerDependencies": { diff --git a/packages/keyboard-shortcuts/package.json b/packages/keyboard-shortcuts/package.json index df206934ffaaf..e06c0e637e932 100644 --- a/packages/keyboard-shortcuts/package.json +++ b/packages/keyboard-shortcuts/package.json @@ -29,7 +29,7 @@ "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", "@wordpress/keycodes": "file:../keycodes", - "rememo": "^4.0.0" + "rememo": "^4.0.2" }, "peerDependencies": { "react": "^18.0.0" diff --git a/packages/react-native-editor/jest_ui.config.js b/packages/react-native-editor/jest_ui.config.js index 6998b3b98dc00..43dc5519ee869 100644 --- a/packages/react-native-editor/jest_ui.config.js +++ b/packages/react-native-editor/jest_ui.config.js @@ -16,7 +16,7 @@ module.exports = { platforms: [ 'android', 'ios', 'native' ], }, transform: { - '^.+\\.(js|ts|tsx)$': 'babel-jest', + '^.+\\.(js|ts|tsx|cjs)$': 'babel-jest', }, setupFilesAfterEnv: [ './jest_ui_setup_after_env.js' ], testEnvironment: './jest_ui_test_environment.js', diff --git a/packages/react-native-editor/metro.config.js b/packages/react-native-editor/metro.config.js index 73bc29c15d1b1..05e57e3cfbcab 100644 --- a/packages/react-native-editor/metro.config.js +++ b/packages/react-native-editor/metro.config.js @@ -13,7 +13,7 @@ const packageNames = fs.readdirSync( PACKAGES_DIR ).filter( ( file ) => { module.exports = { watchFolders: [ path.resolve( __dirname, '../..' ) ], resolver: { - sourceExts: [ 'js', 'json', 'scss', 'sass', 'ts', 'tsx' ], + sourceExts: [ 'js', 'cjs', 'json', 'scss', 'sass', 'ts', 'tsx' ], platforms: [ 'native', 'android', 'ios' ], }, transformer: { diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 5e7867baeafce..e986aaf7365ed 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -39,7 +39,7 @@ "@wordpress/i18n": "file:../i18n", "@wordpress/keycodes": "file:../keycodes", "memize": "^1.1.0", - "rememo": "^4.0.0" + "rememo": "^4.0.2" }, "peerDependencies": { "react": "^18.0.0"