diff --git a/script/vsts/lib/release-notes.js b/script/vsts/lib/release-notes.js index 640ebd2cf..d19df9c17 100644 --- a/script/vsts/lib/release-notes.js +++ b/script/vsts/lib/release-notes.js @@ -1,8 +1,9 @@ const semver = require('semver') -const changelog = require('pr-changelog') const octokit = require('@octokit/rest')() +const changelog = require('pr-changelog') +const childProcess = require('child_process') -module.exports.get = async function(releaseVersion, githubToken) { +module.exports.get = async function (releaseVersion, githubToken) { if (githubToken) { octokit.authenticate({ type: 'oauth', @@ -10,12 +11,12 @@ module.exports.get = async function(releaseVersion, githubToken) { }) } - let releases = await octokit.repos.getReleases({owner: 'atom', repo: 'atom'}) - let release = releases.data.find(r => semver.eq(r.name, releaseVersion)) + const releases = await octokit.repos.getReleases({owner: 'atom', repo: 'atom'}) + const release = releases.data.find(r => semver.eq(r.name, releaseVersion)) return release ? release.body : undefined } -module.exports.generate = async function(releaseVersion, githubToken) { +module.exports.generateForVersion = async function (releaseVersion, githubToken, oldReleaseNotes) { let oldVersion = null let oldVersionName = null const parsedVersion = semver.parse(releaseVersion) @@ -36,8 +37,7 @@ module.exports.generate = async function(releaseVersion, githubToken) { oldVersionName = `v${parsedVersion.major}.${parsedVersion.minor - 1}.0` } else { let releases = await octokit.repos.getReleases({owner: 'atom', repo: 'atom'}) - let versions = releases.data.map(r => r.name) - oldVersion = 'v' + getPreviousVersion(releaseVersion, versions) + oldVersion = 'v' + getPreviousRelease(releaseVersion, releases.data).name oldVersionName = oldVersion } @@ -59,28 +59,61 @@ module.exports.generate = async function(releaseVersion, githubToken) { } }) - return `## Notable Changes\n\n\ -**TODO**: Pull relevant changes here!\n\n\ + const writtenReleaseNotes = + extractWrittenReleaseNotes(oldReleaseNotes) || + '**TODO**: Pull relevant changes here!' + + return `## Notable Changes\n +${writtenReleaseNotes}\n
-All Changes\n\n -${allChangesText}\n\n +All Changes\n +${allChangesText}
` } -function getPreviousVersion (version, allVersions) { +module.exports.generateForNightly = async function(releaseVersion, githubToken) { + const releases = await octokit.repos.getReleases({owner: 'atom', repo: 'atom-nightly-releases'}) + const previousRelease = getPreviousRelease(releaseVersion, releases.data) + const oldReleaseNotes = previousRelease ? previousRelease.body : undefined + + const latestCommitResult = childProcess.spawnSync('git', ['rev-parse', '--short', 'HEAD']) + + if (latestCommitResult && oldReleaseNotes) { + const latestCommit = latestCommitResult.stdout.toString().trim() + const extractMatch = oldReleaseNotes.match(/atom\/atom\/compare\/([0-9a-f]{5,40})\.\.\.([0-9a-f]{5,40})/) + if (extractMatch) { + return `### Click [here](https://github.com/atom/atom/compare/${extractMatch[2]}...${latestCommit}) to see the changes included with this release! :atom: :night_with_stars:` + } + } + + return undefined +} + +function extractWrittenReleaseNotes (oldReleaseNotes) { + if (oldReleaseNotes) { + const extractMatch = oldReleaseNotes.match(/^## Notable Changes\r\n([\s\S]*)
/) + if (extractMatch && extractMatch[1]) { + return extractMatch[1].trim() + } + } + + return undefined +} + +function getPreviousRelease (version, allReleases) { const versionIsStable = semver.prerelease(version) === null // Make sure versions are sorted before using them - allVersions.sort(semver.rcompare) + allReleases.sort((v1, v2) => semver.rcompare(v1.name, v2.name)) - for (let otherVersion of allVersions) { - if (versionIsStable && semver.prerelease(otherVersion)) { + for (let release of allReleases) { + if (versionIsStable && semver.prerelease(release.name)) { continue } - if (semver.lt(otherVersion, version)) { - return otherVersion + if (semver.lt(release.name, version)) { + return release } } diff --git a/script/vsts/release-branch-build.yml b/script/vsts/release-branch-build.yml index b48afebce..e3dfdf16e 100644 --- a/script/vsts/release-branch-build.yml +++ b/script/vsts/release-branch-build.yml @@ -80,3 +80,12 @@ jobs: ATOM_RELEASES_S3_BUCKET: $(ATOM_RELEASES_S3_BUCKET) displayName: Upload CI Artifacts to S3 condition: and(succeeded(), eq(variables['IsSignedZipBranch'], 'true')) + + - task: PublishBuildArtifacts@1 + inputs: + PathtoPublish: $(Build.SourcesDirectory)/out/OLD_RELEASE_NOTES.md + ArtifactName: OLD_RELEASE_NOTES.md + ArtifactType: Container + displayName: Upload OLD_RELEASE_NOTES.md + condition: and(succeeded(), eq(variables['IsReleaseBranch'], 'true'), eq(variables['buildArch'], 'x86')) + continueOnError: true diff --git a/script/vsts/upload-artifacts.js b/script/vsts/upload-artifacts.js index ad5fa8d05..ea568e8e6 100644 --- a/script/vsts/upload-artifacts.js +++ b/script/vsts/upload-artifacts.js @@ -1,8 +1,10 @@ 'use strict' +const fs = require('fs') const path = require('path') const glob = require('glob') const publishRelease = require('publish-release') +const releaseNotes = require('./lib/release-notes') const uploadToS3 = require('./lib/upload-to-s3') const uploadLinuxPackages = require('./lib/upload-linux-packages') @@ -19,11 +21,12 @@ const argv = yargs .wrap(yargs.terminalWidth()) .argv +const releaseVersion = CONFIG.computedAppVersion const isNightlyRelease = CONFIG.channel === 'nightly' -const assetsPath = argv.assetsPath || path.join(CONFIG.repositoryRootPath, 'out') +const assetsPath = argv.assetsPath || CONFIG.buildOutputPath const assetsPattern = '/**/*(*.exe|*.zip|*.nupkg|*.tar.gz|*.rpm|*.deb|RELEASES*|atom-api.json)' const assets = glob.sync(assetsPattern, { root: assetsPath, nodir: true }) -const bucketPath = argv.s3Path || `releases/v${CONFIG.computedAppVersion}/` +const bucketPath = argv.s3Path || `releases/v${releaseVersion}/` if (!assets || assets.length === 0) { console.error(`No assets found under specified path: ${assetsPath}`) @@ -31,7 +34,7 @@ if (!assets || assets.length === 0) { } async function uploadArtifacts() { - console.log(`Uploading ${assets.length} release assets for ${CONFIG.computedAppVersion} to S3 under '${bucketPath}'`) + console.log(`Uploading ${assets.length} release assets for ${releaseVersion} to S3 under '${bucketPath}'`) await uploadToS3( process.env.ATOM_RELEASES_S3_KEY, @@ -44,20 +47,50 @@ async function uploadArtifacts() { await uploadLinuxPackages( argv.linuxRepoName, process.env.PACKAGE_CLOUD_API_KEY, - CONFIG.computedAppVersion, + releaseVersion, assets) } else { - console.log("Skipping upload of Linux packages") + console.log("Skipping upload of Linux packages") + } + + const oldReleaseNotes = + await releaseNotes.get( + releaseVersion, + process.env.GITHUB_TOKEN) + + if (oldReleaseNotes) { + const oldReleaseNotesPath = path.resolve(CONFIG.buildOutputPath, "OLD_RELEASE_NOTES.md") + console.log(`Saving existing ${releaseVersion} release notes to ${oldReleaseNotesPath}`) + fs.writeFileSync(oldReleaseNotesPath, oldReleaseNotes, 'utf8') } if (argv.createGithubRelease) { - console.log(`Creating GitHub release v${CONFIG.computedAppVersion}`) + console.log(`\nGenerating new release notes for ${releaseVersion}`) + let newReleaseNotes = '' + if (isNightlyRelease) { + newReleaseNotes = + await releaseNotes.generateForNightly( + releaseVersion, + process.env.GITHUB_TOKEN, + oldReleaseNotes) + } else { + newReleaseNotes = + await releaseNotes.generateForVersion( + releaseVersion, + process.env.GITHUB_TOKEN, + oldReleaseNotes) + } + + console.log(`New release notes:\n\n${newReleaseNotes}`) + + console.log(`Creating GitHub release v${releaseVersion}`) const release = await publishReleaseAsync({ token: process.env.GITHUB_TOKEN, owner: 'atom', repo: !isNightlyRelease ? 'atom' : 'atom-nightly-releases', name: CONFIG.computedAppVersion, + body: newReleaseNotes, tag: `v${CONFIG.computedAppVersion}`, draft: !isNightlyRelease, prerelease: CONFIG.channel !== 'stable',