diff options
author | Timmy Willison <timmywil@users.noreply.github.com> | 2024-07-17 10:13:53 -0400 |
---|---|---|
committer | Timmy Willison <timmywil@users.noreply.github.com> | 2024-07-29 12:34:41 -0400 |
commit | 53ad94f319930a5bf8cb9bd935ebd4e028741903 (patch) | |
tree | b8adbeaa9088bfbb099c2b46a57ab81db57e18d7 /build/release/verify.js | |
parent | be048a027d0581746f71df7c8eb3ce1d9bd10a40 (diff) | |
download | jquery-53ad94f319930a5bf8cb9bd935ebd4e028741903.tar.gz jquery-53ad94f319930a5bf8cb9bd935ebd4e028741903.zip |
Release: correct build date in verification; other improvements
- the date is actually the date of the commit *prior*
to the tag commit, as the files are built and then committed.
- also, the CDN should still be checked for non-stable releases,
and should use different filenames (including in the map files).
- certain files should be skipped when checking the CDN.
- removed file diffing because it ended up being far too noisy,
making it difficult to find the info I needed.
- because the build script required an addition, release
verification will not work until the next release.
- print all files in failure case and whether each matched
- avoid npm script log in GH release notes changelog
- exclude changelog.md from release:clean command
- separate the post-release script from release-it for now, so we
can keep manual verification before each push. The exact command is
printed at the ened for convenience.
Closes gh-5521
Diffstat (limited to 'build/release/verify.js')
-rw-r--r-- | build/release/verify.js | 138 |
1 files changed, 90 insertions, 48 deletions
diff --git a/build/release/verify.js b/build/release/verify.js index f167666b2..423a63c8c 100644 --- a/build/release/verify.js +++ b/build/release/verify.js @@ -1,9 +1,6 @@ /** * Verify the latest release is reproducible - * Works with versions 4.0.0-beta.2 and later */ -import chalk from "chalk"; -import * as Diff from "diff"; import { exec as nodeExec } from "node:child_process"; import crypto from "node:crypto"; import { createWriteStream } from "node:fs"; @@ -22,7 +19,12 @@ const SRC_REPO = "https://github.com/jquery/jquery.git"; const CDN_URL = "https://code.jquery.com"; const REGISTRY_URL = "https://registry.npmjs.org/jquery"; -const rstable = /^(\d+\.\d+\.\d+)$/; +const excludeFromCDN = [ + /^package\.json$/, + /^jquery\.factory\./ +]; + +const rjquery = /^jquery/; async function verifyRelease( { version } = {} ) { if ( !version ) { @@ -33,24 +35,34 @@ async function verifyRelease( { version } = {} ) { console.log( `Verifying jQuery ${ version }...` ); let verified = true; + const matchingFiles = []; + const mismatchingFiles = []; - // Only check stable versions against the CDN - if ( rstable.test( version ) ) { - await Promise.all( - release.files.map( async( file ) => { - const cdnContents = await fetch( new URL( file.name, CDN_URL ) ).then( - ( res ) => res.text() - ); - if ( cdnContents !== file.contents ) { - console.log( `${ file.name } is different from the CDN:` ); - diffFiles( file.contents, cdnContents ); + // Check all files against the CDN + await Promise.all( + release.files + .filter( ( file ) => excludeFromCDN.every( ( re ) => !re.test( file.name ) ) ) + .map( async( file ) => { + const url = new URL( file.cdnName, CDN_URL ); + const response = await fetch( url ); + if ( !response.ok ) { + throw new Error( + `Failed to download ${ + file.cdnName + } from the CDN: ${ response.statusText }` + ); + } + const cdnContents = await response.text(); + if ( cdnContents !== file.cdnContents ) { + mismatchingFiles.push( url.href ); verified = false; + } else { + matchingFiles.push( url.href ); } } ) - ); - } + ); - // Check all releases against npm. + // Check all files against npm. // First, download npm tarball for version const npmPackage = await fetch( REGISTRY_URL ).then( ( res ) => res.json() ); @@ -66,9 +78,10 @@ async function verifyRelease( { version } = {} ) { // Check the tarball checksum const tgzSum = await sumTarball( npmTarballPath ); if ( tgzSum !== release.tgz.contents ) { - console.log( `${ version }.tgz is different from npm:` ); - diffFiles( release.tgz.contents, tgzSum ); + mismatchingFiles.push( `npm:${ version }.tgz` ); verified = false; + } else { + matchingFiles.push( `npm:${ version }.tgz` ); } await Promise.all( @@ -80,16 +93,26 @@ async function verifyRelease( { version } = {} ) { ); if ( npmContents !== file.contents ) { - console.log( `${ file.name } is different from the CDN:` ); - diffFiles( file.contents, npmContents ); + mismatchingFiles.push( `npm:${ file.path }/${ file.name }` ); verified = false; + } else { + matchingFiles.push( `npm:${ file.path }/${ file.name }` ); } } ) ); if ( verified ) { - console.log( `jQuery ${ version } is reproducible!` ); + console.log( `jQuery ${ version } is reproducible! All files match!` ); } else { + console.log(); + for ( const file of matchingFiles ) { + console.log( `✅ ${ file }` ); + } + console.log(); + for ( const file of mismatchingFiles ) { + console.log( `❌ ${ file }` ); + } + throw new Error( `jQuery ${ version } is NOT reproducible!` ); } } @@ -101,19 +124,32 @@ async function buildRelease( { version } ) { console.log( `Cloning jQuery ${ version }...` ); await rimraf( releaseFolder ); await mkdir( releaseFolder, { recursive: true } ); + + // Uses a depth of 2 so we can get the commit date of + // the commit used to build, which is the commit before the tag await exec( - `git clone -q -b ${ version } --depth=1 ${ SRC_REPO } ${ releaseFolder }` + `git clone -q -b ${ version } --depth=2 ${ SRC_REPO } ${ releaseFolder }` ); // Install node dependencies console.log( `Installing dependencies for jQuery ${ version }...` ); await exec( "npm ci", { cwd: releaseFolder } ); + // Find the date of the commit just before the release, + // which was used as the date in the built files + const { stdout: date } = await exec( "git log -1 --format=%ci HEAD~1", { + cwd: releaseFolder + } ); + // Build the release console.log( `Building jQuery ${ version }...` ); const { stdout: buildOutput } = await exec( "npm run build:all", { cwd: releaseFolder, env: { + + // Keep existing environment variables + ...process.env, + RELEASE_DATE: date, VERSION: version } } ); @@ -125,24 +161,35 @@ async function buildRelease( { version } ) { console.log( packOutput ); // Get all top-level /dist and /dist-module files - const distFiles = await readdir( path.join( releaseFolder, "dist" ), { - withFileTypes: true - } ); + const distFiles = await readdir( + path.join( releaseFolder, "dist" ), + { withFileTypes: true } + ); const distModuleFiles = await readdir( path.join( releaseFolder, "dist-module" ), - { - withFileTypes: true - } + { withFileTypes: true } ); const files = await Promise.all( [ ...distFiles, ...distModuleFiles ] .filter( ( dirent ) => dirent.isFile() ) - .map( async( dirent ) => ( { - name: dirent.name, - path: path.basename( dirent.parentPath ), - contents: await readFile( path.join( dirent.parentPath, dirent.name ), "utf8" ) - } ) ) + .map( async( dirent ) => { + const contents = await readFile( + path.join( dirent.parentPath, dirent.name ), + "utf8" + ); + return { + name: dirent.name, + path: path.basename( dirent.parentPath ), + contents, + cdnName: dirent.name.replace( rjquery, `jquery-${ version }` ), + cdnContents: dirent.name.endsWith( ".map" ) ? + + // The CDN has versioned filenames in the maps + convertMapToVersioned( contents, version ) : + contents + }; + } ) ); // Get checksum of the tarball @@ -166,20 +213,6 @@ async function downloadFile( url, dest ) { return finished( stream ); } -async function diffFiles( a, b ) { - const diff = Diff.diffLines( a, b ); - - diff.forEach( ( part ) => { - if ( part.added ) { - console.log( chalk.green( part.value ) ); - } else if ( part.removed ) { - console.log( chalk.red( part.value ) ); - } else { - console.log( part.value ); - } - } ); -} - async function getLatestVersion() { const { stdout: sha } = await exec( "git rev-list --tags --max-count=1" ); const { stdout: tag } = await exec( `git describe --tags ${ sha.trim() }` ); @@ -198,4 +231,13 @@ async function sumTarball( filepath ) { return shasum( unzipped ); } +function convertMapToVersioned( contents, version ) { + const map = JSON.parse( contents ); + return JSON.stringify( { + ...map, + file: map.file.replace( rjquery, `jquery-${ version }` ), + sources: map.sources.map( ( source ) => source.replace( rjquery, `jquery-${ version }` ) ) + } ); +} + verifyRelease(); |