diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index 5d8c6d58c1d8..15701f41fac8 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -28,7 +28,7 @@ mainBuildFilters: &mainBuildFilters only: - develop - /^release\/\d+\.\d+\.\d+$/ - - 'mschile/chrome_memory_fix' + - 'emily/next-version' # usually we don't build Mac app - it takes a long time # but sometimes we want to really confirm we are doing the right thing @@ -130,7 +130,7 @@ commands: - run: name: Check current branch to persist artifacts command: | - if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "mschile/chrome_memory_fix" ]]; then + if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "emily/next-version" ]]; then echo "Not uploading artifacts or posting install comment for this branch." circleci-agent step halt fi diff --git a/cli/lib/tasks/cache.js b/cli/lib/tasks/cache.js index cb653acb7309..15a19a841c6f 100644 --- a/cli/lib/tasks/cache.js +++ b/cli/lib/tasks/cache.js @@ -33,14 +33,14 @@ const clear = () => { const prune = () => { const cacheDir = state.getCacheDir() - const currentVersion = util.pkgVersion() + const checkedInBinaryVersion = util.pkgVersion() let deletedBinary = false return fs.readdirAsync(cacheDir) .then((versions) => { return Bluebird.all(versions.map((version) => { - if (version !== currentVersion) { + if (version !== checkedInBinaryVersion) { deletedBinary = true const versionDir = join(cacheDir, version) @@ -51,7 +51,7 @@ const prune = () => { }) .then(() => { if (deletedBinary) { - logger.always(`Deleted all binary caches except for the ${currentVersion} binary cache.`) + logger.always(`Deleted all binary caches except for the ${checkedInBinaryVersion} binary cache.`) } else { logger.always(`No binary caches found to prune.`) } diff --git a/cli/test/lib/tasks/cache_spec.js b/cli/test/lib/tasks/cache_spec.js index 02d96504ba7a..bb74c2444bf0 100644 --- a/cli/test/lib/tasks/cache_spec.js +++ b/cli/test/lib/tasks/cache_spec.js @@ -136,14 +136,14 @@ describe('lib/tasks/cache', () => { it('deletes cache binaries for all version but the current one', async () => { await cache.prune() - const currentVersion = util.pkgVersion() + const checkedInBinaryVersion = util.pkgVersion() const files = await fs.readdir('/.cache/Cypress') expect(files.length).to.eq(1) files.forEach((file) => { - expect(file).to.eq(currentVersion) + expect(file).to.eq(checkedInBinaryVersion) }) defaultSnapshot() @@ -155,14 +155,14 @@ describe('lib/tasks/cache', () => { await fs.removeAsync(dir) await cache.prune() - const currentVersion = util.pkgVersion() + const checkedInBinaryVersion = util.pkgVersion() const files = await fs.readdirAsync('/.cache/Cypress') expect(files.length).to.eq(1) files.forEach((file) => { - expect(file).to.eq(currentVersion) + expect(file).to.eq(checkedInBinaryVersion) }) defaultSnapshot() diff --git a/scripts/binary/index.js b/scripts/binary/index.js index 00efe9790f68..d90a5985c26a 100644 --- a/scripts/binary/index.js +++ b/scripts/binary/index.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ // store the cwd const cwd = process.cwd() @@ -194,7 +195,7 @@ const deploy = { return askMissingOptions(['version', 'platform'])(options) .then(() => { - debug('building binary: platform %s version %s', options.platform, options.version) + console.log('building binary: platform %s version %s', options.platform, options.version) return build.buildCypressApp(options) }) diff --git a/scripts/get-next-version.js b/scripts/get-next-version.js index 58ac78c5edc0..2f71f5c6f159 100644 --- a/scripts/get-next-version.js +++ b/scripts/get-next-version.js @@ -1,18 +1,20 @@ /* eslint-disable no-console */ -// See ../guides/next-version.md for documentation. - const path = require('path') const semver = require('semver') const bumpCb = require('conventional-recommended-bump') const { promisify } = require('util') -const currentVersion = require('../package.json').version +const checkedInBinaryVersion = require('../package.json').version const { changeCatagories } = require('./semantic-commits/change-categories') +const { getCurrentReleaseData } = require('./semantic-commits/get-current-release-data') + const bump = promisify(bumpCb) const paths = ['packages', 'cli'] const getNextVersionForPath = async (path) => { + const { version: releasedVersion } = await getCurrentReleaseData(false) + let commits const whatBump = (foundCommits) => { // semantic version bump: 0 - major, 1 - minor, 2 - patch @@ -48,9 +50,20 @@ const getNextVersionForPath = async (path) => { path, }) + let nextVersion = semver.inc(checkedInBinaryVersion, releaseType || 'patch') + + const hasVersionBump = checkedInBinaryVersion !== releasedVersion + + // See ../guides/next-version.md for documentation. + // for the time being, honoring this ENV -- ideally this will be deleted to remove manually overriding without a PR + if (process.env.NEXT_VERSION) { + nextVersion = process.env.NEXT_VERSION + } else if (hasVersionBump) { + nextVersion = checkedInBinaryVersion + } + return { - // allow the semantic next version to be overridden by environment - nextVersion: process.env.NEXT_VERSION || semver.inc(currentVersion, releaseType || 'patch'), + nextVersion, commits, } } @@ -93,7 +106,7 @@ if (require.main !== module) { const { nextVersion } = await getNextVersionForBinary() - if (process.argv.includes('--npm')) { + if (process.argv.includes('--npm') && checkedInBinaryVersion !== nextVersion) { const cmd = `npm --no-git-tag-version version ${nextVersion}` console.log(`Running '${cmd}'...`) diff --git a/scripts/npm-release.js b/scripts/npm-release.js index 3610aa574f5a..70f4baf5de8b 100644 --- a/scripts/npm-release.js +++ b/scripts/npm-release.js @@ -5,8 +5,8 @@ /* eslint-disable no-console */ const execa = require('execa') const fs = require('fs') -const path = require('path') const semverSortNewestFirst = require('semver/functions/rcompare') +const checkedInBinaryVersion = require('../package.json').version const { getCurrentBranch, getPackagePath, readPackageJson, independentTagRegex } = require('./utils') @@ -27,14 +27,6 @@ const getTags = async () => { return stdout.split('\n') } -const getBinaryVersion = async () => { - const { stdout: root } = await execa('git', ['rev-parse', '--show-toplevel']) - const rootPath = path.join(root, 'package.json') - const rootPackage = JSON.parse(fs.readFileSync(rootPath)) - - return rootPackage.version -} - const parseSemanticReleaseOutput = (output) => { const currentVersion = (output.match(/associated with version (\d+\.\d+\.\d+-?\S*)/) || [])[1] const nextVersion = (output.match(/next release version is (\d+\.\d+\.\d+-?\S*)/) || [])[1] @@ -71,13 +63,11 @@ const getCurrentVersion = async (name) => { const getPackageVersions = async (packages) => { console.log(`Finding package versions...\n`) - const binaryVersion = await getBinaryVersion() - - console.log(`Cypress binary: ${binaryVersion}`) + console.log(`Cypress binary: ${checkedInBinaryVersion}`) const versions = { cypress: { - currentVersion: binaryVersion, + currentVersion: checkedInBinaryVersion, nextVersion: undefined, }, } @@ -232,7 +222,6 @@ if (require.main === module) { } module.exports = { - getBinaryVersion, parseSemanticReleaseOutput, readPackageJson, releasePackages, diff --git a/scripts/semantic-commits/get-binary-release-data.js b/scripts/semantic-commits/get-binary-release-data.js index c055af64dca2..98301b2e2340 100644 --- a/scripts/semantic-commits/get-binary-release-data.js +++ b/scripts/semantic-commits/get-binary-release-data.js @@ -1,32 +1,14 @@ /* eslint-disable no-console */ -const execa = require('execa') +const childProcess = require('child_process') const _ = require('lodash') const { Octokit } = require('@octokit/core') +const { getCurrentReleaseData } = require('./get-current-release-data') const { getNextVersionForBinary } = require('../get-next-version') const { getLinkedIssues } = require('./get-linked-issues') const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }) -/** - * Get the version, commit date and git sha of the latest tag published on npm. - */ -const getCurrentReleaseData = async () => { - console.log('Get Current Release Information\n') - const { stdout } = await execa('npm', ['info', 'cypress', '--json']) - const npmInfo = JSON.parse(stdout) - - const latestReleaseInfo = { - version: npmInfo['dist-tags'].latest, - commitDate: npmInfo.buildInfo.commitDate, - buildSha: npmInfo.buildInfo.commitSha, - } - - console.log({ latestReleaseInfo }) - - return latestReleaseInfo -} - /** * Get the list of file names that have been added, deleted or changed since the git * sha associated with the latest tag published on npm. @@ -36,8 +18,8 @@ const getCurrentReleaseData = async () => { * @param {string} latestReleaseInfo.commitDate - data of release * @param {string} latestReleaseInfo.buildSha - git commit associated with published content */ -const getChangedFilesSinceLastRelease = async (latestReleaseInfo) => { - const { stdout } = await execa('git', ['diff', `${latestReleaseInfo.buildSha}..`, '--name-only']) +const getChangedFilesSinceLastRelease = (latestReleaseInfo) => { + const stdout = childProcess.execSync(`git diff ${latestReleaseInfo.buildSha}.. --name-only`) if (!stdout) { console.log('no files changes since last release') @@ -117,7 +99,6 @@ const getReleaseData = async (latestReleaseInfo) => { if (require.main !== module) { module.exports = { - getCurrentReleaseData, getReleaseData, } diff --git a/scripts/semantic-commits/get-current-release-data.js b/scripts/semantic-commits/get-current-release-data.js new file mode 100644 index 000000000000..d031c5160d4d --- /dev/null +++ b/scripts/semantic-commits/get-current-release-data.js @@ -0,0 +1,26 @@ +/* eslint-disable no-console */ +const childProcess = require('child_process') + +/** + * Get the version, commit date and git sha of the latest tag published on npm. + */ +const getCurrentReleaseData = (verbose = true) => { + verbose && console.log('Get Current Release Information\n') + + const stdout = childProcess.execSync('npm info cypress --json') + const npmInfo = JSON.parse(stdout) + + const latestReleaseInfo = { + version: npmInfo['dist-tags'].latest, + commitDate: npmInfo.buildInfo.commitDate, + buildSha: npmInfo.buildInfo.commitSha, + } + + verbose && console.log({ latestReleaseInfo }) + + return latestReleaseInfo +} + +module.exports = { + getCurrentReleaseData, +} diff --git a/scripts/semantic-commits/validate-binary-changelog.js b/scripts/semantic-commits/validate-binary-changelog.js index 1ee3c9fa890c..865d2b557346 100644 --- a/scripts/semantic-commits/validate-binary-changelog.js +++ b/scripts/semantic-commits/validate-binary-changelog.js @@ -1,19 +1,18 @@ /* eslint-disable no-console */ -const { getBinaryVersion } = require('../npm-release') const { validateChangelog } = require('./validate-changelog') -const { getCurrentReleaseData, getReleaseData } = require('./get-binary-release-data') +const { getCurrentReleaseData } = require('./get-current-release-data') +const { getReleaseData } = require('./get-binary-release-data') +const checkedInBinaryVersion = require('../../package.json').version const changelog = async () => { const latestReleaseInfo = await getCurrentReleaseData() if (process.env.CIRCLECI) { - const checkedInBinaryVersion = await getBinaryVersion() - console.log({ checkedInBinaryVersion }) const hasVersionBump = checkedInBinaryVersion !== latestReleaseInfo.version - if (process.env.CIRCLE_BRANCH !== 'develop' || !/^release\/\d+\.\d+\.\d+$/.test(process.env.CIRCLE_BRANCH) || !hasVersionBump) { + if (process.env.CIRCLE_BRANCH !== 'develop' && !/^release\/\d+\.\d+\.\d+$/.test(process.env.CIRCLE_BRANCH) && !hasVersionBump) { console.log('Only verify the entire changelog for develop, a release branch or any branch that bumped to the Cypress version in the package.json.') return diff --git a/scripts/unit/get-next-version-spec.js b/scripts/unit/get-next-version-spec.js new file mode 100644 index 000000000000..aafcd17ac746 --- /dev/null +++ b/scripts/unit/get-next-version-spec.js @@ -0,0 +1,183 @@ +const { expect, use } = require('chai') +const proxyquire = require('proxyquire').noCallThru() +const sinon = require('sinon') + +use(require('sinon-chai')) + +describe('get-next-version', () => { + const releasedVersion = '12.2.0' + let getNextVersionForPath + let getNextVersionForBinary + let bumpStub + let getCurrentReleaseDataStub + + afterEach(() => { + delete process.env.NEXT_VERSION + }) + + beforeEach(() => { + sinon.restore() + + bumpStub = sinon.stub() + getCurrentReleaseDataStub = sinon.stub() + getCurrentReleaseDataStub.resolves({ + version: releasedVersion, + }) + + const npmRelease = proxyquire('../get-next-version', { + 'conventional-recommended-bump': bumpStub, + './semantic-commits/get-current-release-data': { + getCurrentReleaseData: getCurrentReleaseDataStub, + }, + '../package.json': sinon.stub({ version: releasedVersion }), + }) + + getNextVersionForPath = npmRelease.getNextVersionForPath + getNextVersionForBinary = npmRelease.getNextVersionForBinary + }) + + context('#getNextVersionForPath', () => { + it('determines next version is patch', async () => { + const semanticCommits = [ + { type: 'fix' }, + ] + + bumpStub.callsFake(async ({ whatBump, _path }, cb) => { + const { level } = whatBump(semanticCommits) + + expect(level, 'semantic bump level').to.eq(2) + + return cb(undefined, { releaseType: 'patch' }) + }) + + const { nextVersion, commits } = await getNextVersionForPath('packages') + + expect(nextVersion).to.eq('12.2.1') + expect(commits).to.contain.members(semanticCommits) + }) + + it('determines next version is minor', async () => { + const semanticCommits = [ + { type: 'fix' }, + { type: 'feat' }, + ] + + bumpStub.callsFake(async ({ whatBump, _path }, cb) => { + const { level } = whatBump(semanticCommits) + + expect(level, 'semantic bump level').to.eq(1) + + return cb(undefined, { releaseType: 'minor' }) + }) + + const { nextVersion, commits } = await getNextVersionForPath('packages') + + expect(nextVersion).to.eq('12.3.0') + expect(commits).to.contain.members(semanticCommits) + }) + + it('determines next version is major', async () => { + const semanticCommits = [ + { type: 'fix' }, + { type: 'feat' }, + { type: 'breaking' }, + ] + + bumpStub.callsFake(async ({ whatBump, _path }, cb) => { + const { level } = whatBump(semanticCommits) + + expect(level, 'semantic bump level').to.eq(0) + + return cb(undefined, { releaseType: 'major' }) + }) + + const { nextVersion, commits } = await getNextVersionForPath('packages') + + expect(nextVersion).to.eq('13.0.0') + expect(commits).to.contain.members(semanticCommits) + }) + + it('honors package.json version when its been bumped', async () => { + const semanticCommits = [ + { type: 'fix' }, + ] + + bumpStub.callsFake(async ({ whatBump, _path }, cb) => { + const { level } = whatBump(semanticCommits) + + expect(level, 'semantic bump level').to.eq(2) + + return cb(undefined, { releaseType: 'patch' }) + }) + + getCurrentReleaseDataStub.resolves({ + // package version !== release version assumed check in version is correct + version: '12.2.2', + }) + + const { nextVersion, commits } = await getNextVersionForPath('packages') + + expect(nextVersion).to.eq('12.2.0') + expect(commits).to.contain.members(semanticCommits) + }) + + it('honors NEXT_VERSION env', async () => { + process.env.NEXT_VERSION = '15.0.0' + const semanticCommits = [ + { type: 'fix' }, + ] + + bumpStub.callsFake(async ({ whatBump, _path }, cb) => { + const { level } = whatBump(semanticCommits) + + expect(level, 'semantic bump level').to.eq(2) + + return cb(undefined, { releaseType: 'patch' }) + }) + + const { nextVersion, commits } = await getNextVersionForPath('packages', '12.2.2') + + expect(nextVersion).to.eq('15.0.0') + expect(commits).to.contain.members(semanticCommits) + }) + }) + + context('#getNextVersionForBinary', () => { + it('determines next version for all cli & packages changes', async () => { + let calls = 0 + const cliSemanticCommits = [ + { type: 'fix', title: 'fix: cli' }, + { type: 'fix', title: 'fix: typescript' }, + { type: 'dependency', title: 'dependency: security update' }, + ] + + const packagesSemanticCommits = [ + { type: 'feat', title: 'feat: add new command' }, + ] + + bumpStub.callsFake(async ({ whatBump, _path }, cb) => { + if (calls === 0) { + const { level } = whatBump(packagesSemanticCommits) + + expect(level, 'semantic bump level').to.eq(1) + calls++ + + return cb(undefined, { releaseType: 'minor' }) + } + + const { level } = whatBump(cliSemanticCommits) + + expect(level, 'semantic bump level').to.eq(2) + + return cb(undefined, { releaseType: 'patch' }) + }) + + const { nextVersion, commits } = await getNextVersionForBinary('packages', releasedVersion) + + expect(nextVersion).to.eq('12.3.0') + + expect(commits).to.contain.members(packagesSemanticCommits) + expect(commits).to.contain.members(cliSemanticCommits) + }) + }) +}) diff --git a/system-tests/lib/performance-reporter.js b/system-tests/lib/performance-reporter.js index fe304ff2c0c2..9dddab2d6720 100644 --- a/system-tests/lib/performance-reporter.js +++ b/system-tests/lib/performance-reporter.js @@ -38,7 +38,10 @@ circleCiRootEvent.add({ // Therefore, we have each honeycomb event await this promise // before sending itself. -let asyncInfo = Promise.all([getNextVersionForPath(path.resolve(__dirname, '../../packages')), commitInfo()]) +let asyncInfo = Promise.all([ + getNextVersionForPath(path.resolve(__dirname, '../../packages')), + commitInfo(), +]) .then(([{ nextVersion }, commitInformation]) => { const ciInformation = ciProvider.commitParams() || {}