Skip to content

Commit 53ad94f

Browse files
committed
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 jquerygh-5521
1 parent be048a0 commit 53ad94f

File tree

8 files changed

+120
-58
lines changed

8 files changed

+120
-58
lines changed

.github/workflows/verify-release.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
name: Reproducible Builds
22
on:
3-
# On tags
43
push:
4+
# On tags
55
tags:
66
- '*'
77
# Or manually
88
workflow_dispatch:
99
inputs:
1010
version:
11-
description: 'Version to verify (>= 4.0.0-beta.2)'
11+
description: 'Version to verify (>= 4.0.0-rc.1)'
1212
required: false
1313

14+
1415
jobs:
1516
run:
1617
name: Verify release
@@ -28,6 +29,9 @@ jobs:
2829
with:
2930
node-version: ${{ env.NODE_VERSION }}
3031

32+
- name: Install dependencies
33+
run: npm ci
34+
3135
- run: npm run release:verify
3236
env:
3337
VERSION: ${{ github.event.inputs.version || github.ref_name }}

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ npm-debug.log*
3131
/test/data/qunit-fixture.js
3232

3333
# Release artifacts
34-
changelog.*
35-
contributors.*
34+
changelog.html
35+
contributors.html
3636

3737
# Ignore BrowserStack testing files
3838
local.log

.release-it.cjs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,22 @@ module.exports = {
1414
"sed -i 's/main\\/AUTHORS.txt/${version}\\/AUTHORS.txt/' package.json",
1515
"after:bump": "cross-env VERSION=${version} npm run build:all",
1616
"before:git:release": "git add -f dist/ dist-module/ changelog.md",
17-
"after:release": `bash ./build/release/post-release.sh \${version} ${ blogURL }`
17+
"after:release": "echo 'Run the following to complete the release:' && " +
18+
`echo './build/release/post-release.sh $\{version} ${ blogURL }'`
1819
},
1920
git: {
20-
changelog: "npm run release:changelog -- ${from} ${to}",
21+
22+
// Use the node script directly to avoid an npm script
23+
// command log entry in the GH release notes
24+
changelog: "node build/release/changelog.js ${from} ${to}",
2125
commitMessage: "Release: ${version}",
2226
getLatestTagFromAllRefs: true,
27+
pushRepo: "[email protected]:jquery/jquery.git",
2328
requireBranch: "main",
2429
requireCleanWorkingDir: true
2530
},
2631
github: {
32+
pushRepo: "[email protected]:jquery/jquery.git",
2733
release: true,
2834
tokenRef: "JQUERY_GITHUB_TOKEN"
2935
},

build/release/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ The release script will not run without this token.
8484

8585
**Note**: `preReleaseBase` is set in the npm script to `1` to ensure any pre-releases start at `.1` instead of `.0`. This does not interfere with stable releases.
8686

87+
1. Run the post-release script:
88+
89+
```sh
90+
./build/release/post-release.sh $VERSION $BLOG_URL
91+
```
92+
93+
This will push the release files to the CDN and jquery-dist repos, and push the commit to the jQuery repo to remove the release files and update the AUTHORS.txt URL in the package.json.
94+
8795
1. Once the release is complete, publish the blog post.
8896

8997
## Stable releases

build/release/post-release.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ git add package.json
5151
# Leave the tmp folder as some files are needed
5252
# after the release (such as for emailing archives).
5353
npm run build:clean
54-
git rm --cached -r dist/ dist-module
54+
git rm --cached -r dist/ dist-module/
5555
git add dist/package.json dist/wrappers dist-module/package.json dist-module/wrappers
5656
git commit -m "Release: remove dist files from main branch"
5757

build/release/verify.js

Lines changed: 90 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
/**
22
* Verify the latest release is reproducible
3-
* Works with versions 4.0.0-beta.2 and later
43
*/
5-
import chalk from "chalk";
6-
import * as Diff from "diff";
74
import { exec as nodeExec } from "node:child_process";
85
import crypto from "node:crypto";
96
import { createWriteStream } from "node:fs";
@@ -22,7 +19,12 @@ const SRC_REPO = "https://github.com/jquery/jquery.git";
2219
const CDN_URL = "https://code.jquery.com";
2320
const REGISTRY_URL = "https://registry.npmjs.org/jquery";
2421

25-
const rstable = /^(\d+\.\d+\.\d+)$/;
22+
const excludeFromCDN = [
23+
/^package\.json$/,
24+
/^jquery\.factory\./
25+
];
26+
27+
const rjquery = /^jquery/;
2628

2729
async function verifyRelease( { version } = {} ) {
2830
if ( !version ) {
@@ -33,24 +35,34 @@ async function verifyRelease( { version } = {} ) {
3335
console.log( `Verifying jQuery ${ version }...` );
3436

3537
let verified = true;
38+
const matchingFiles = [];
39+
const mismatchingFiles = [];
3640

37-
// Only check stable versions against the CDN
38-
if ( rstable.test( version ) ) {
39-
await Promise.all(
40-
release.files.map( async( file ) => {
41-
const cdnContents = await fetch( new URL( file.name, CDN_URL ) ).then(
42-
( res ) => res.text()
43-
);
44-
if ( cdnContents !== file.contents ) {
45-
console.log( `${ file.name } is different from the CDN:` );
46-
diffFiles( file.contents, cdnContents );
41+
// Check all files against the CDN
42+
await Promise.all(
43+
release.files
44+
.filter( ( file ) => excludeFromCDN.every( ( re ) => !re.test( file.name ) ) )
45+
.map( async( file ) => {
46+
const url = new URL( file.cdnName, CDN_URL );
47+
const response = await fetch( url );
48+
if ( !response.ok ) {
49+
throw new Error(
50+
`Failed to download ${
51+
file.cdnName
52+
} from the CDN: ${ response.statusText }`
53+
);
54+
}
55+
const cdnContents = await response.text();
56+
if ( cdnContents !== file.cdnContents ) {
57+
mismatchingFiles.push( url.href );
4758
verified = false;
59+
} else {
60+
matchingFiles.push( url.href );
4861
}
4962
} )
50-
);
51-
}
63+
);
5264

53-
// Check all releases against npm.
65+
// Check all files against npm.
5466
// First, download npm tarball for version
5567
const npmPackage = await fetch( REGISTRY_URL ).then( ( res ) => res.json() );
5668

@@ -66,9 +78,10 @@ async function verifyRelease( { version } = {} ) {
6678
// Check the tarball checksum
6779
const tgzSum = await sumTarball( npmTarballPath );
6880
if ( tgzSum !== release.tgz.contents ) {
69-
console.log( `${ version }.tgz is different from npm:` );
70-
diffFiles( release.tgz.contents, tgzSum );
81+
mismatchingFiles.push( `npm:${ version }.tgz` );
7182
verified = false;
83+
} else {
84+
matchingFiles.push( `npm:${ version }.tgz` );
7285
}
7386

7487
await Promise.all(
@@ -80,16 +93,26 @@ async function verifyRelease( { version } = {} ) {
8093
);
8194

8295
if ( npmContents !== file.contents ) {
83-
console.log( `${ file.name } is different from the CDN:` );
84-
diffFiles( file.contents, npmContents );
96+
mismatchingFiles.push( `npm:${ file.path }/${ file.name }` );
8597
verified = false;
98+
} else {
99+
matchingFiles.push( `npm:${ file.path }/${ file.name }` );
86100
}
87101
} )
88102
);
89103

90104
if ( verified ) {
91-
console.log( `jQuery ${ version } is reproducible!` );
105+
console.log( `jQuery ${ version } is reproducible! All files match!` );
92106
} else {
107+
console.log();
108+
for ( const file of matchingFiles ) {
109+
console.log( `✅ ${ file }` );
110+
}
111+
console.log();
112+
for ( const file of mismatchingFiles ) {
113+
console.log( `❌ ${ file }` );
114+
}
115+
93116
throw new Error( `jQuery ${ version } is NOT reproducible!` );
94117
}
95118
}
@@ -101,19 +124,32 @@ async function buildRelease( { version } ) {
101124
console.log( `Cloning jQuery ${ version }...` );
102125
await rimraf( releaseFolder );
103126
await mkdir( releaseFolder, { recursive: true } );
127+
128+
// Uses a depth of 2 so we can get the commit date of
129+
// the commit used to build, which is the commit before the tag
104130
await exec(
105-
`git clone -q -b ${ version } --depth=1 ${ SRC_REPO } ${ releaseFolder }`
131+
`git clone -q -b ${ version } --depth=2 ${ SRC_REPO } ${ releaseFolder }`
106132
);
107133

108134
// Install node dependencies
109135
console.log( `Installing dependencies for jQuery ${ version }...` );
110136
await exec( "npm ci", { cwd: releaseFolder } );
111137

138+
// Find the date of the commit just before the release,
139+
// which was used as the date in the built files
140+
const { stdout: date } = await exec( "git log -1 --format=%ci HEAD~1", {
141+
cwd: releaseFolder
142+
} );
143+
112144
// Build the release
113145
console.log( `Building jQuery ${ version }...` );
114146
const { stdout: buildOutput } = await exec( "npm run build:all", {
115147
cwd: releaseFolder,
116148
env: {
149+
150+
// Keep existing environment variables
151+
...process.env,
152+
RELEASE_DATE: date,
117153
VERSION: version
118154
}
119155
} );
@@ -125,24 +161,35 @@ async function buildRelease( { version } ) {
125161
console.log( packOutput );
126162

127163
// Get all top-level /dist and /dist-module files
128-
const distFiles = await readdir( path.join( releaseFolder, "dist" ), {
129-
withFileTypes: true
130-
} );
164+
const distFiles = await readdir(
165+
path.join( releaseFolder, "dist" ),
166+
{ withFileTypes: true }
167+
);
131168
const distModuleFiles = await readdir(
132169
path.join( releaseFolder, "dist-module" ),
133-
{
134-
withFileTypes: true
135-
}
170+
{ withFileTypes: true }
136171
);
137172

138173
const files = await Promise.all(
139174
[ ...distFiles, ...distModuleFiles ]
140175
.filter( ( dirent ) => dirent.isFile() )
141-
.map( async( dirent ) => ( {
142-
name: dirent.name,
143-
path: path.basename( dirent.parentPath ),
144-
contents: await readFile( path.join( dirent.parentPath, dirent.name ), "utf8" )
145-
} ) )
176+
.map( async( dirent ) => {
177+
const contents = await readFile(
178+
path.join( dirent.parentPath, dirent.name ),
179+
"utf8"
180+
);
181+
return {
182+
name: dirent.name,
183+
path: path.basename( dirent.parentPath ),
184+
contents,
185+
cdnName: dirent.name.replace( rjquery, `jquery-${ version }` ),
186+
cdnContents: dirent.name.endsWith( ".map" ) ?
187+
188+
// The CDN has versioned filenames in the maps
189+
convertMapToVersioned( contents, version ) :
190+
contents
191+
};
192+
} )
146193
);
147194

148195
// Get checksum of the tarball
@@ -166,20 +213,6 @@ async function downloadFile( url, dest ) {
166213
return finished( stream );
167214
}
168215

169-
async function diffFiles( a, b ) {
170-
const diff = Diff.diffLines( a, b );
171-
172-
diff.forEach( ( part ) => {
173-
if ( part.added ) {
174-
console.log( chalk.green( part.value ) );
175-
} else if ( part.removed ) {
176-
console.log( chalk.red( part.value ) );
177-
} else {
178-
console.log( part.value );
179-
}
180-
} );
181-
}
182-
183216
async function getLatestVersion() {
184217
const { stdout: sha } = await exec( "git rev-list --tags --max-count=1" );
185218
const { stdout: tag } = await exec( `git describe --tags ${ sha.trim() }` );
@@ -198,4 +231,13 @@ async function sumTarball( filepath ) {
198231
return shasum( unzipped );
199232
}
200233

234+
function convertMapToVersioned( contents, version ) {
235+
const map = JSON.parse( contents );
236+
return JSON.stringify( {
237+
...map,
238+
file: map.file.replace( rjquery, `jquery-${ version }` ),
239+
sources: map.sources.map( ( source ) => source.replace( rjquery, `jquery-${ version }` ) )
240+
} );
241+
}
242+
201243
verifyRelease();

build/tasks/build.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,10 @@ async function getLastModifiedDate() {
148148
async function writeCompiled( { code, dir, filename, version } ) {
149149

150150
// Use the last modified date so builds are reproducible
151-
const date = await getLastModifiedDate();
151+
const date = process.env.RELEASE_DATE ?
152+
new Date( process.env.RELEASE_DATE ) :
153+
await getLastModifiedDate();
154+
152155
const compiledContents = code
153156

154157
// Embed Version

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@
6161
"qunit-fixture": "node build/tasks/qunit-fixture.js",
6262
"release": "release-it",
6363
"release:cdn": "node build/release/cdn.js",
64-
"release:changelog": "node build/release/changelog.js",
65-
"release:clean": "rimraf tmp --glob changelog.{md,html} contributors.html",
64+
"release:clean": "rimraf tmp changelog.html contributors.html",
6665
"release:dist": "node build/release/dist.js",
6766
"release:verify": "node build/release/verify.js",
6867
"start": "node -e \"(async () => { const { buildDefaultFiles } = await import('./build/tasks/build.js'); buildDefaultFiles({ watch: true }) })()\"",

0 commit comments

Comments
 (0)