Skip to content

Commit cc70c46

Browse files
committed
Build: Add audit script for reviewing changes to build artifacts
* Automate the workflow I have been using for auditing the release artefacts prior to publication. This protects against mistakes or other accidents that may have gone unnnoticed (e.g. in how Rollup is configured, or what files we embed and distribute), as well as against any compromise or other malicious activity in the build chain (in one of the many unaudited dev dependencies we use from npm, or elsewhere). * No longer recommend `npm run serve`. This can't easily be run in an isolated environment as it needs to expose a port. Unlike other build scripts, this involves unaudited dev dependencies. Recommend use of the built-in and dependency-free HTTP server from Python or PHP instead. * Minor improvements throughout RELEASE.md, especially to simplify and consolidate related steps, and make the numbering continuous throughout the page (10 steps total, more or less).
1 parent 6ea16c7 commit cc70c46

File tree

8 files changed

+178
-63
lines changed

8 files changed

+178
-63
lines changed

Gruntfile.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,6 @@ module.exports = function( grunt ) {
3232
}
3333
},
3434

35-
// For manual testing.
36-
any: {
37-
options: {
38-
port: connectPort,
39-
useAvailablePort: true,
40-
keepalive: true,
41-
base: "."
42-
}
43-
},
44-
4535
// For use by the "watch" task.
4636
livereload: {
4737
options: {

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
[![Build Status](https://travis-ci.com/qunitjs/qunit.svg?branch=master)](https://travis-ci.com/qunitjs/qunit)
44
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fqunitjs%2Fqunit.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fqunitjs%2Fqunit?ref=badge_shield)
5-
[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96)](https://reproducible-builds.org/)
65
[![Coverage Status](https://coveralls.io/repos/qunitjs/qunit/badge.svg)](https://coveralls.io/github/qunitjs/qunit)
6+
[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96)](https://reproducible-builds.org/)
77
[![Chat on Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/qunitjs/qunit?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
88
[![npm](https://img.shields.io/npm/v/qunit.svg?style=flat)](https://www.npmjs.com/package/qunit)
99

RELEASE.md

Lines changed: 58 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
1-
# Release Process
1+
# Release process
22

3-
This guide walks you through a QUnit release.
3+
This guide walks you through the QUnit release.
44

55
⚠️ **WARNING** ⚠️
66

7-
> Before attempting the process below, make sure you have:
7+
> Before starting, make sure you have:
88
>
99
> * **Permission to publish to the jQuery CDN** via [jquery/codeorigin.jquery.com](https://github.com/jquery/codeorigin.jquery.com).
1010
> * Permission to publish releases to npm for the [`qunit`](https://www.npmjs.com/package/qunit) npm package.
1111
> * Permission to publish the website via [qunitjs/qunit.com](https://github.com/qunitjs/qunitjs.com).
12-
13-
Prerequisites:
14-
15-
* Node.js 12, or later.
16-
* Git 2.11, or later.
12+
>
13+
> System prerequisites:
14+
>
15+
> * Node.js 12, or later.
16+
> * Git 2.11, or later.
1717
1818
1. Ensure that all changes for this release have been merged into the main branch. For patch releases, try landing any other bug fixes; for minor releases, ensure new features have been documented and tested. Major releases likely have their own checklist.
1919

20-
2. Ensure that you have a prestine copy of the canonical repo (not a fork), and create a local `release` branch:
21-
* Run `git remote -v` and verify the following:
20+
2. Create a local `release` branch, and ensure it is up-to-date:
21+
* Verify that the canonical repository is cloned (not a fork):
2222
```
23-
origin [email protected]:qunitjs/qunit.git
23+
git remote -v
24+
# …
25+
# origin [email protected]:qunitjs/qunit.git
2426
```
25-
* Create or reset your `release` branch:
27+
* Create or reset the `release` branch:
2628
```
2729
git remote update && git checkout -B release -t origin/master
2830
```
@@ -31,10 +33,13 @@ Prerequisites:
3133
```
3234
npm ci && npm test
3335
```
34-
Run the tests in various real browsers as well, either locally or via [BrowserStack](https://www.browserstack.com/):
36+
Run the tests in various real browsers, either locally or via [BrowserStack](https://www.browserstack.com/):
3537
```
36-
npm run serve
37-
open 'http://localhost:4000/test/'
38+
python3 -m http.server 4000
39+
# or:
40+
# php -S localhost:4000
41+
42+
open http://localhost:4000/test/
3843
```
3944

4045
4. Create and push the release preparation commit:
@@ -43,68 +48,76 @@ Prerequisites:
4348
```
4449
node build/prep-release.js @VERSION
4550
```
46-
* Review the difference in the `AUTHORS.txt` file. If you see duplicate entries proposed, then use the `.mailmap` file to normamlize those entries to a canonical name or e-mail address, and then re-run the above command.
47-
* Edit `History.md` and remove any "Build", "Tests" or other commits not relevant to end-users.
48-
2. Commit all of the above with the following message (replace `@VERSION` with the release version):
51+
* Use `git add -p` to review the changes.
52+
* In `AUTHORS.txt`, if you see duplicate entries, then use the `.mailmap` file to normalize them to a canonical name and e-mail address, and then re-run the above command.
53+
* Edit `History.md` to remove change not relevant to end-users (e.g. changes relating to tests, build, internal refactoring, doc fixes, etc.).
54+
2. Commit the above changes with the following message (replace `@VERSION` with the release version):
4955
```
5056
Build: Prepare @VERSION release
5157
```
52-
3. Push your `release` branch to GitHub.
53-
4. Create a pull request, approve it, and merge it once Travis CI is passing.
58+
3. Push the `release` branch to GitHub.
59+
4. Create a pull request, and merge it once Travis CI is passing.
5460
5561
## Performing the release
5662
57-
1. Create a local `release` branch, and ensure you're working on the canonical repo (not a fork):
63+
5. Create a local `release` branch, and ensure it is up-to-date:
5864
* Run `git remote -v` and verify the following:
5965
```
6066
origin [email protected]:qunitjs/qunit.git
6167
```
62-
* Create or reset your `release` branch:
68+
* Create or reset the `release` branch:
6369
```
6470
git remote update && git checkout -B release -t origin/master
6571
```
66-
67-
2. Ensure that the latest commit is your release preparation commit:
72+
* Verify that the latest commit is your release preparation commit:
6873
```
6974
git show
7075
# Build: Prepare x.y.z release
7176
#
7277
```
7378
74-
3. Set the release version for npm and Bower metadata, by running the below command (replace `@VERSION` with the release version):
79+
6. Make changes for the release commit:
80+
* Set the release version for npm and Bower metadata (replace `@VERSION` with the release version):
7581
```
7682
node build/set-release.js @VERSION
7783
```
78-
This script will edit `package.json` and `bower.json` for you. It does not require any credentials or permissions, apart from read-write in the project directory.
84+
This script will edit `package.json` and `bower.json`. It does not need any credentials or permissions, apart from read-write in the project directory.
7985
80-
4. Generate the release artifacts:
86+
* Generate the release artifacts:
8187
```
8288
export SOURCE_DATE_EPOCH="$(git log -s --format=%at -1)"
8389
npm run build
8490
```
8591
86-
5. Rename `dist/` to `qunit/`:
92+
* Rename `dist/` to `qunit/`:
8793
```
8894
mv dist/ qunit/
8995
```
9096
91-
6. Create the release commit, tag it, and push the tag to GitHub (replace `@VERSION` with the release version).<br>⚠️ Do not commit or push release artifacts to the main branch!
97+
* Review the changes to the package and library files, compared to the previous release.
98+
```
99+
node build/review-package.js @LAST_VERSION
100+
101+
# … reviews package.json, qunit.js, and qunit.css
102+
```
103+
104+
7. Commit and publish the release to GitHub.<br>⚠️ Do not push to the main branch!
92105
```
93106
git add package.json bower.json qunit/
94107
git commit -m "Release @VERSION"
95108
git tag -s "@VERSION" -m "Release @VERSION"
96109
git push --tags
97110
```
98111
99-
7. Verify that Bower sees the release, by running `bower info qunit` and checking that the latest
112+
8. Verify that Bower sees the release, by running `bower info qunit` and checking that the latest
100113
version is indeed the version we just published.
101114
102-
8. Publish the release to npm:
103-
* Use `git status` to confirm once more that your checkout is clean apart from the release artifacts in `qunit/`.
115+
9. Publish the release to npm:
116+
* Use `git status` to confirm once more that you have a clean working copy, apart from release artifacts in `qunit/`.
104117
* Run `npm publish`, this will bundle the current directory and publish it to npm with the name and version specified in `package.json`.
105118
* Verify that the release is displayed at <https://www.npmjs.com/package/qunit>.
106119
107-
9. Publish the release to the jQuery CDN:
120+
10. Publish the release to the jQuery CDN:
108121
* Prepare the commit locally:
109122
```
110123
node build/auth-cdn-commit.js real @VERSION
@@ -117,34 +130,31 @@ Prerequisites:
117130
#
118131
git push
119132
```
120-
* Verify via <https://code.jquery.com/qunit/qunit-x.y.z.js>
133+
* Verify that the release is listed at <https://code.jquery.com/qunit/> and accessible via <https://code.jquery.com/qunit/qunit-x.y.z.js>
121134
122135
## Updating the website
123136
124-
Once you have successfully published the new release, we need to update the website.
137+
After the release is published, we need to update the website.
125138
126-
git clone [email protected]:qunitjs/qunitjs.com.git
127-
cd qunitjs.com
139+
Check out the main branch of the [qunitjs/qunitjs.com](https://github.com/qunitjs/qunitjs.com) repository, and ensure it is clean and up-to-date. Update release links and demos to use the version we just released, using this script:
128140
129-
Checkout the latest `master` branch of the website repo.
141+
```
142+
qunitjs.com$ node build/set-version.js <version>
143+
```
130144
131-
Do a find and replace of the previous version number and insert the new version number. Commit these changes with the following message:
145+
Stage the changes it made, and commit with the following message:
132146
133-
All: Update url and version to @VERSION
147+
```
148+
All: Update url and version to <version>
149+
```
134150
135-
Then push the commit:
136-
137-
git push
138-
139-
Check the website in a few minutes after a build completes to verify it is updated.
151+
Push the commit, and check the website in a few minutes to verify the change ([deployment log](https://github.com/qunitjs/qunitjs.com/deployments/activity_log?environment=github-pages)).
140152
141153
## Final steps
142154
143155
You're almost there! Make sure you update [GitHub releases](https://github.com/qunitjs/qunit/releases) using the changelog from `History.md`.
144156
145-
Finally, make an announcement on the [@qunitjs](https://twitter.com/qunitjs) Twitter account. Mention highlights of the release if possible and feel free to include a second tweet if needed, but be sure to include a link to the release page like so:
146-
147-
Released @VERSION: https://github.com/qunitjs/qunit/releases/tag/x.y.z
157+
Finally, make an announcement on the [@qunitjs](https://twitter.com/qunitjs) Twitter account. Mention highlights of the release if possible, andinclude a link to the release page.
148158
149159
That's it! If you made it this far, congratulations you have successfully released a version of QUnit!
150160

build/prep-release.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const Repo = {
3636
"--format=* %s. (%aN)",
3737
"--no-merges",
3838
`${oldVersion}...HEAD`
39-
], { encoding: "utf-8" } );
39+
], { encoding: "utf8" } );
4040

4141
changes = changes
4242
.trim()

build/review-package.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Helper for reviewing build artefacts against a past release.
2+
//
3+
// See also RELEASE.md.
4+
5+
/* eslint-env node */
6+
7+
const cp = require( "child_process" );
8+
const fs = require( "fs" );
9+
const https = require( "https" );
10+
const path = require( "path" );
11+
const readline = require( "readline" );
12+
13+
function getDiff( from, to ) {
14+
15+
// macOS 10.15+ comes with GNU diff (2.8)
16+
// https://unix.stackexchange.com/a/338960/37512
17+
// https://stackoverflow.com/a/41770560/319266
18+
const gnuDiffVersion = cp.execFileSync( "diff", [ "--version" ], { encoding: "utf8" } );
19+
const versionStr = /diff.* (\d+\.\d+)/.exec( gnuDiffVersion );
20+
const isOld = ( versionStr && Number( versionStr[ 1 ] ) < 3.4 );
21+
22+
try {
23+
cp.execFileSync( "diff", [
24+
"--text",
25+
"--unified",
26+
...( isOld ? [] : [ "--color=always" ] ),
27+
from,
28+
to
29+
], { encoding: "utf8" } );
30+
} catch ( e ) {
31+
32+
// Expected, `diff` command yields non-zero exit status if files differ
33+
return e.stdout;
34+
}
35+
throw new Error( `Unable to diff between ${from} and ${to}` );
36+
}
37+
38+
async function confirm( text ) {
39+
const rl = readline.createInterface( { input: process.stdin, output: process.stdout } );
40+
await new Promise( ( resolve, reject ) => {
41+
rl.question( `${text} (y/N)> `, ( answer ) => {
42+
rl.close();
43+
if ( String( answer ).toLowerCase() === "y" ) {
44+
resolve();
45+
} else {
46+
reject( new Error( "Audit aborted" ) );
47+
}
48+
} );
49+
} );
50+
}
51+
52+
async function download( url, dest ) {
53+
const fileStr = fs.createWriteStream( dest );
54+
await new Promise( ( resolve, reject ) => {
55+
https.get( url, ( resp ) => {
56+
resp.pipe( fileStr );
57+
fileStr.on( "finish", () => fileStr.close( resolve ) );
58+
} ).on( "error", ( err ) => reject( err ) );
59+
} );
60+
}
61+
62+
const ReleaseAssets = {
63+
async audit( prevVersion ) {
64+
if ( typeof prevVersion !== "string" || !/^\d+\.\d+\.\d+$/.test( prevVersion ) ) {
65+
throw new Error( "Invalid or missing version argument" );
66+
}
67+
{
68+
const file = "package.json";
69+
console.log( `Auditing ${file}...` );
70+
71+
const prevContent = cp.execFileSync( "git", [
72+
"show",
73+
`${prevVersion}:package.json`
74+
], { encoding: "utf8" } );
75+
const tempPrevPath = path.join( __dirname, "../temp", file );
76+
fs.writeFileSync( tempPrevPath, prevContent );
77+
78+
const currentPath = path.join( __dirname, "..", file );
79+
process.stdout.write( getDiff( tempPrevPath, currentPath ) );
80+
await confirm( `Accept ${file}?` );
81+
}
82+
{
83+
const file = "qunit.js";
84+
console.log( `Auditing ${file}...` );
85+
86+
const prevUrl = `https://code.jquery.com/qunit/qunit-${prevVersion}.js`;
87+
const tempPrevPath = path.join( __dirname, "../temp", file );
88+
await download( prevUrl, tempPrevPath );
89+
90+
const currentPath = path.join( __dirname, "../qunit", file );
91+
process.stdout.write( getDiff( tempPrevPath, currentPath ) );
92+
await confirm( `Accept ${file}?` );
93+
}
94+
{
95+
const file = "qunit.css";
96+
console.log( `Auditing ${file}...` );
97+
98+
const prevUrl = `https://code.jquery.com/qunit/qunit-${prevVersion}.css`;
99+
const tempPrevPath = path.join( __dirname, "../temp", file );
100+
await download( prevUrl, tempPrevPath );
101+
102+
const currentPath = path.join( __dirname, "../qunit", file );
103+
process.stdout.write( getDiff( tempPrevPath, currentPath ) );
104+
await confirm( `Accept ${file}?` );
105+
}
106+
}
107+
};
108+
109+
const prevVersion = process.argv[ 2 ];
110+
111+
( async function main() {
112+
await ReleaseAssets.audit( prevVersion );
113+
}() ).catch( e => {
114+
console.error( e.toString() );
115+
process.exit( 1 );
116+
} );

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@
8989
"test:main": "npm run build && grunt test",
9090
"test:cli": "npm run build && bin/qunit.js test/cli/*.js",
9191
"authors": "grunt authors",
92-
"serve": "grunt connect:any",
9392
"coverage": "npm run build:coverage && npm run coverage:_cli && npm run coverage:_main && nyc report",
9493
"coverage:_cli": "nyc --use-spawn-wrap --silent bin/qunit.js test/cli/*.js",
9594
"coverage:_main": "nyc instrument --in-place dist/ && grunt connect:nolivereload qunit",

rollup.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ module.exports = {
3838
// variables in an imported file would be).
3939
return fs.readFileSync(
4040
__dirname + "/src/html-reporter/es6-map.js",
41-
"utf-8"
41+
"utf8"
4242
).toString().trim();
4343
},
4444

src/cli/utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ function existsStat() {
1616
function getIgnoreList( baseDir ) {
1717
const gitFilePath = path.join( baseDir, ".gitignore" );
1818
if ( fs.existsSync( gitFilePath ) ) {
19-
const gitIgnore = fs.readFileSync( gitFilePath, "utf-8" );
19+
const gitIgnore = fs.readFileSync( gitFilePath, "utf8" );
2020
return gitIgnore.trim().split( "\n" );
2121
}
2222
return [];

0 commit comments

Comments
 (0)