diff --git a/scripts/build-types/BuildApiSnapshot.js b/scripts/build-types/BuildApiSnapshot.js index b70d4dff1e0..fe0c7b14ce6 100644 --- a/scripts/build-types/BuildApiSnapshot.js +++ b/scripts/build-types/BuildApiSnapshot.js @@ -23,9 +23,11 @@ const { } = require('@microsoft/api-extractor'); const {promises: fs} = require('fs'); const glob = require('glob'); +const {diff} = require('jest-diff'); const path = require('path'); const prettier = require('prettier'); const osTempDir = require('temp-dir'); +const {styleText} = require('util'); const inputFilesPostTransforms: $ReadOnlyArray> = [ require('./transforms/renameDefaultExportedIdentifiers'), @@ -37,7 +39,7 @@ const postTransforms: $ReadOnlyArray> = [ require('./transforms/sortUnions'), ]; -async function buildAPISnapshot() { +async function buildAPISnapshot(validate: boolean) { const tempDirectory = await createTempDir('react-native-js-api-snapshot'); const packages = await findPackagesWithTypedef(); @@ -56,12 +58,23 @@ async function buildAPISnapshot() { if (extractorResult.succeeded) { const apiSnapshot = apiSnapshotTemplate( await getCleanedUpRollup(tempDirectory), - ); + ) as string; - await fs.writeFile( - path.join(REACT_NATIVE_PACKAGE_DIR, 'ReactNativeApi.d.ts'), - apiSnapshot, - ); + if (validate) { + const prevSnapshot = await fs.readFile( + path.join(REACT_NATIVE_PACKAGE_DIR, 'ReactNativeApi.d.ts'), + 'utf-8', + ); + const hasChanged = await validateSnapshots(prevSnapshot, apiSnapshot); + if (hasChanged) { + process.exitCode = 1; + } + } else { + await fs.writeFile( + path.join(REACT_NATIVE_PACKAGE_DIR, 'ReactNativeApi.d.ts'), + apiSnapshot, + ); + } } else { process.exitCode = 1; console.error( @@ -73,6 +86,32 @@ async function buildAPISnapshot() { await fs.rm(tempDirectory, {recursive: true}); } +async function validateSnapshots( + prevSnapshot: string, + newSnapshot: string, +): Promise { + const hasChanged = newSnapshot !== prevSnapshot; + if (hasChanged) { + const options = { + aAnnotation: 'Previous Snapshot', + bAnnotation: 'New Snapshot', + expand: false, + emptyFirstOrLastLinePlaceholder: '↵', + includeChangeCounts: true, + aColor: (line: string) => styleText(['red'], line), + bColor: (line: string) => styleText(['green'], line), + }; + + const diffResult = diff(prevSnapshot, newSnapshot, options); + console.error( + `\n${styleText(['inverse'], ' VALIDATE ')} ReactNativeApi.d.ts has changed. Please re-run \`yarn build-types\` and commit the updated snapshot.\n\n`, + diffResult, + ); + } + + return hasChanged; +} + async function findPackagesWithTypedef() { const packagesWithGeneratedTypes = glob .sync(`${PACKAGES_DIR}/**/types_generated`, {nodir: false}) diff --git a/scripts/build-types/index.js b/scripts/build-types/index.js index 388226c7069..cee92641066 100644 --- a/scripts/build-types/index.js +++ b/scripts/build-types/index.js @@ -20,12 +20,13 @@ const config = { debug: {type: 'boolean'}, help: {type: 'boolean'}, withSnapshot: {type: 'boolean'}, + validate: {type: 'boolean'}, }, }; async function main() { const { - values: {debug: debugEnabled, help, withSnapshot}, + values: {debug: debugEnabled, help, withSnapshot, validate}, /* $FlowFixMe[incompatible-call] Natural Inference rollout. See * https://fburl.com/workplace/6291gfvu */ } = parseArgs(config); @@ -68,7 +69,7 @@ async function main() { '\n', ); - await buildApiSnapshot(); + await buildApiSnapshot(validate); } }