From 0ba7c5c7f43317ecf93591ca2795fc63e3b40eb9 Mon Sep 17 00:00:00 2001 From: Eric Eldredge Date: Tue, 3 May 2022 17:09:07 -0400 Subject: [PATCH 1/4] Add script to update self-published versions --- package.json | 2 +- scripts/update-self-published.js | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100755 scripts/update-self-published.js diff --git a/package.json b/package.json index 5855e7bbd89..f5219c6bbd9 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "nightly:release": "lerna publish -y --canary --preid nightly --dist-tag=nightly --exact --force-publish=* --no-git-tag-version --no-push", "tag:prerelease": "lerna version --exact --force-publish=* --no-git-tag-version --no-push && yarn adjust-versions --exact", "tag:release": "lerna version --exact --force-publish=* --no-git-tag-version --no-push && yarn adjust-versions", - "adjust-versions": "node scripts/update-config-dependencies.js && node scripts/update-engines-peerdeps.js", + "adjust-versions": "node scripts/update-config-dependencies.js && node scripts/update-engines-peerdeps.js && node scripts/update-self-published.js", "release": "lerna publish -y from-package --pre-dist-tag=next --no-git-tag-version --no-push", "prepare": "husky install" }, diff --git a/scripts/update-self-published.js b/scripts/update-self-published.js new file mode 100755 index 00000000000..a8a6d5c6712 --- /dev/null +++ b/scripts/update-self-published.js @@ -0,0 +1,35 @@ +#!/usr/bin/env node + +/* eslint-disable no-console */ +const fs = require('fs'); +const path = require('path'); +const exec = require('child_process').execSync; + +const fromVersionString = str => str.match(/npm:.+@(.+)$/)[1]; +const toVersionString = (name, version) => `npm:${name}@${version}`; + +let packages = JSON.parse( + exec( + `${path.join(__dirname, '..', 'node_modules', '.bin', 'lerna')} ls --json`, + {stdio: [0, 'pipe', 2]}, + ), +); + +for (let {location, name} of packages) { + let pkgPath = path.join(location, 'package.json'); + let pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + if (pkg.dependencies && 'self-published' in pkg.dependencies) { + let current = fromVersionString(pkg.dependencies['self-published']); + let info = JSON.parse( + exec(`yarn info ${name} --json`, {stdio: [0, 'pipe', 2]}), + ); + let nightly = info.data['dist-tags'].nightly; + if (current === nightly) { + console.error(`${name} is already on latest nightly ${nightly}`); + } else { + console.error(`updating ${name} to latest nightly ${nightly}`); + pkg.dependencies['self-published'] = toVersionString(name, nightly); + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); + } + } +} From 8d65fd7696c6989d5e7a5f86e316326253db0c67 Mon Sep 17 00:00:00 2001 From: Eric Eldredge Date: Wed, 4 May 2022 14:19:02 -0400 Subject: [PATCH 2/4] Extract run function --- scripts/update-self-published.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/scripts/update-self-published.js b/scripts/update-self-published.js index a8a6d5c6712..84cf7d7f792 100755 --- a/scripts/update-self-published.js +++ b/scripts/update-self-published.js @@ -8,11 +8,17 @@ const exec = require('child_process').execSync; const fromVersionString = str => str.match(/npm:.+@(.+)$/)[1]; const toVersionString = (name, version) => `npm:${name}@${version}`; -let packages = JSON.parse( - exec( - `${path.join(__dirname, '..', 'node_modules', '.bin', 'lerna')} ls --json`, - {stdio: [0, 'pipe', 2]}, - ), +function run(cmd) { + let result = exec(cmd, {stdio: [0, 'pipe', 2]}); + try { + return JSON.parse(result); + } catch { + return result.toString(); + } +} + +let packages = run( + `${path.join(__dirname, '..', 'node_modules', '.bin', 'lerna')} ls --json`, ); for (let {location, name} of packages) { @@ -20,9 +26,7 @@ for (let {location, name} of packages) { let pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); if (pkg.dependencies && 'self-published' in pkg.dependencies) { let current = fromVersionString(pkg.dependencies['self-published']); - let info = JSON.parse( - exec(`yarn info ${name} --json`, {stdio: [0, 'pipe', 2]}), - ); + let info = run(`yarn info ${name} --json`); let nightly = info.data['dist-tags'].nightly; if (current === nightly) { console.error(`${name} is already on latest nightly ${nightly}`); From e597c61a5dd41b1480b319eec158526765c23089 Mon Sep 17 00:00:00 2001 From: Eric Eldredge Date: Wed, 4 May 2022 16:28:12 -0400 Subject: [PATCH 3/4] Use temporally closest nightly version --- scripts/update-self-published.js | 63 +++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/scripts/update-self-published.js b/scripts/update-self-published.js index 84cf7d7f792..8e16fb3ac7e 100755 --- a/scripts/update-self-published.js +++ b/scripts/update-self-published.js @@ -5,6 +5,9 @@ const fs = require('fs'); const path = require('path'); const exec = require('child_process').execSync; +const UPSTREAM = /github.+parcel-bundler\/parcel/; +const NIGHTLY = /.*-nightly\..*/; + const fromVersionString = str => str.match(/npm:.+@(.+)$/)[1]; const toVersionString = (name, version) => `npm:${name}@${version}`; @@ -12,26 +15,74 @@ function run(cmd) { let result = exec(cmd, {stdio: [0, 'pipe', 2]}); try { return JSON.parse(result); - } catch { - return result.toString(); + } catch (e) { + if (e instanceof SyntaxError) { + return result.toString().trim(); + } else { + throw e; + } + } +} + +function getUpstreamRemoteName() { + for (let name of run(`git remote`).split(/\s+/)) { + if (UPSTREAM.test(run(`git remote get-url ${name}`))) { + return name; + } } + throw new Error('Could not determine an upstream remote name!'); +} + +function getDefaultBranchName(remote) { + return run(`git rev-parse --abbrev-ref ${remote}`).split(`${remote}/`).pop(); +} + +/** + * Check nightly version timestamps for the given `pkgName` + * in reverse chronological order, returning the version + * with the timestamp closest to `time` without being younger. + */ +function getNearestNightlyVersion(pkgName, time) { + let candidate; + let info = run(`yarn info ${pkgName} --json`); + let versions = [...Object.entries(info.data.time)].reverse(); + for (let [version, timestamp] of versions) { + if (NIGHTLY.test(version)) { + let versionTime = new Date(timestamp); + if (versionTime >= time) { + candidate = version; + } else { + break; + } + } + } + if (candidate) return candidate; + throw new Error('Could not determine an appropriate nightly version!'); } let packages = run( `${path.join(__dirname, '..', 'node_modules', '.bin', 'lerna')} ls --json`, ); +// Fetch the default upstream branch... +let upstream = getUpstreamRemoteName(); +let branch = getDefaultBranchName(upstream); +run(`git fetch ${upstream} ${branch}`); +// ...so we can determine the latest common ancestor commit. +let baseRef = run(`git merge-base HEAD ${upstream}/${branch}`).split(/\s+/)[0]; +// Get the commit time of the latest common ancestor between HEAD and upstream. +let baseRefTime = new Date(run(`git show -s --format=%cI ${baseRef}`)); + for (let {location, name} of packages) { let pkgPath = path.join(location, 'package.json'); let pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); if (pkg.dependencies && 'self-published' in pkg.dependencies) { let current = fromVersionString(pkg.dependencies['self-published']); - let info = run(`yarn info ${name} --json`); - let nightly = info.data['dist-tags'].nightly; + let nightly = getNearestNightlyVersion(name, baseRefTime); if (current === nightly) { - console.error(`${name} is already on latest nightly ${nightly}`); + console.log(`${name} is already on nearest nightly ${nightly}`); } else { - console.error(`updating ${name} to latest nightly ${nightly}`); + console.log(`updating ${name} to nearest nightly ${nightly}`); pkg.dependencies['self-published'] = toVersionString(name, nightly); fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); } From 9ff055fd3befcfcbb569678e156874140419b020 Mon Sep 17 00:00:00 2001 From: Eric Eldredge Date: Wed, 4 May 2022 18:59:51 -0400 Subject: [PATCH 4/4] Add post-merge hook to update nightly versions --- .husky/post-merge | 15 +++++++++++ .husky/pre-commit | 11 ++++++++ package.json | 3 ++- scripts/update-self-published.js | 46 +++++++++++++++++++++++--------- 4 files changed, 62 insertions(+), 13 deletions(-) create mode 100755 .husky/post-merge diff --git a/.husky/post-merge b/.husky/post-merge new file mode 100755 index 00000000000..a9157b5f60d --- /dev/null +++ b/.husky/post-merge @@ -0,0 +1,15 @@ +#!/bin/sh +# Generated with husky-init and https://github.com/typicode/husky-4-to-6 +. "$(dirname "$0")/_/husky.sh" + +# ATLASSIAN: Update self-published (nightly) versions as part of a merge. +# Ideally, this would be done as part of a `pre-merge-commit` hook, +# but git does not support modifying the index during a merge commit. +# So, we hack it in this `post-merge` hook by immediately ammending the commit. +# Note that in cases where the merge commit did not automatically succeed, +# this hook will not be run. See comments in `pre-commit` hook for more. +yarn update-self-published -a +if [[ -f .git/MERGE_HEAD ]]; then + rm .git/MERGE_HEAD +fi +git log -n 1 --pretty=tformat:%s%n%n%b | git commit -F - --amend diff --git a/.husky/pre-commit b/.husky/pre-commit index 5ec2f9970aa..3f301f9a53c 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -3,3 +3,14 @@ . "$(dirname "$0")/_/husky.sh" yarn lint-staged + +# ATLASSIAN: Update self-published (nightly) versions as part of a merge. +# If a merge is committed automatically (e.g., no conflicts), this hook +# will not be run (see comments in `post-merge` for more). However, +# if a merge is not committed automatically (e.g., there are conflicts), +# the `post-merge` hook will not be run. Here we detect that a merge +# is being manually committed and replicate the work our `post-merge` hook +# would have done if the merge commit had proceeded automatically. +if git rev-parse -q --verify MERGE_HEAD >/dev/null 2>&1; then + yarn update-self-published -a +fi diff --git a/package.json b/package.json index f5219c6bbd9..0397b5f41a9 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,12 @@ "test:integration-ci": "yarn workspace @parcel/integration-tests test-ci", "test": "yarn test:unit && yarn test:integration", "update-readme-toc": "doctoc README.md", + "update-self-published": "node scripts/update-self-published.js", "version:atlassian": "yarn lerna version patch --exact --force-publish=@atlassian/internal-parcel-utils,@atlassian/parcel-reporter-analytics", "nightly:release": "lerna publish -y --canary --preid nightly --dist-tag=nightly --exact --force-publish=* --no-git-tag-version --no-push", "tag:prerelease": "lerna version --exact --force-publish=* --no-git-tag-version --no-push && yarn adjust-versions --exact", "tag:release": "lerna version --exact --force-publish=* --no-git-tag-version --no-push && yarn adjust-versions", - "adjust-versions": "node scripts/update-config-dependencies.js && node scripts/update-engines-peerdeps.js && node scripts/update-self-published.js", + "adjust-versions": "node scripts/update-config-dependencies.js && node scripts/update-engines-peerdeps.js", "release": "lerna publish -y from-package --pre-dist-tag=next --no-git-tag-version --no-push", "prepare": "husky install" }, diff --git a/scripts/update-self-published.js b/scripts/update-self-published.js index 8e16fb3ac7e..4a0cb938388 100755 --- a/scripts/update-self-published.js +++ b/scripts/update-self-published.js @@ -8,6 +8,31 @@ const exec = require('child_process').execSync; const UPSTREAM = /github.+parcel-bundler\/parcel/; const NIGHTLY = /.*-nightly\..*/; +if (process.argv.includes('-h') || process.argv.includes('--help')) { + console.log( + [ + ` Usage: ${path.basename(process.argv[1])} [opts]`, + '', + ' Options:', + ' -h, --help Show help', + ' -a Add updated files to git index', + ' (handy in a precommit hook)', + '', + ' Looks for self-published packages (e.g., @parcel/transformer-js),', + ' and compares their nightly version numbers to the list', + ' of published nightly versions (via `yarn info`).', + ' If it finds a version that is newer, it updates the version', + ' in the package.json.', + '', + ' It will use the oldest nightly version that is newer than', + ' the latest common commit between HEAD and the upstream default branch.', + ].join('\n'), + ); + process.exit(); +} + +const shouldStage = process.argv.includes('-a'); + const fromVersionString = str => str.match(/npm:.+@(.+)$/)[1]; const toVersionString = (name, version) => `npm:${name}@${version}`; @@ -43,23 +68,21 @@ function getDefaultBranchName(remote) { * with the timestamp closest to `time` without being younger. */ function getNearestNightlyVersion(pkgName, time) { - let candidate; + let candidate = null; let info = run(`yarn info ${pkgName} --json`); let versions = [...Object.entries(info.data.time)].reverse(); for (let [version, timestamp] of versions) { if (NIGHTLY.test(version)) { let versionTime = new Date(timestamp); - if (versionTime >= time) { - candidate = version; - } else { - break; - } + if (versionTime < time) break; + candidate = version; } } - if (candidate) return candidate; - throw new Error('Could not determine an appropriate nightly version!'); + return candidate; } +console.log(`Updating self-published (nightly) versions...`); + let packages = run( `${path.join(__dirname, '..', 'node_modules', '.bin', 'lerna')} ls --json`, ); @@ -67,7 +90,7 @@ let packages = run( // Fetch the default upstream branch... let upstream = getUpstreamRemoteName(); let branch = getDefaultBranchName(upstream); -run(`git fetch ${upstream} ${branch}`); +run(`git fetch -q ${upstream} ${branch}`); // ...so we can determine the latest common ancestor commit. let baseRef = run(`git merge-base HEAD ${upstream}/${branch}`).split(/\s+/)[0]; // Get the commit time of the latest common ancestor between HEAD and upstream. @@ -79,12 +102,11 @@ for (let {location, name} of packages) { if (pkg.dependencies && 'self-published' in pkg.dependencies) { let current = fromVersionString(pkg.dependencies['self-published']); let nightly = getNearestNightlyVersion(name, baseRefTime); - if (current === nightly) { - console.log(`${name} is already on nearest nightly ${nightly}`); - } else { + if (nightly && current !== nightly) { console.log(`updating ${name} to nearest nightly ${nightly}`); pkg.dependencies['self-published'] = toVersionString(name, nightly); fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); + if (shouldStage) run(`git add -u ${pkgPath}`); } } }