diff --git a/misc/changelog-generator/changelog_generator.js b/misc/changelog-generator/changelog_generator.js new file mode 100644 index 00000000..393b53b4 --- /dev/null +++ b/misc/changelog-generator/changelog_generator.js @@ -0,0 +1,98 @@ +'use strict'; + +var utils = require("./utils.js"); + +// get log info (and latest tag if not first release) +const child = require("child_process"); +const fs = require("fs"); +var output; + +const pastTags = child.execSync('git tag').toString('utf-8'); +if (pastTags.length) { + const latestTag = child.execSync('git describe --long').toString('utf-8').split('-')[0]; + + output = child + .execSync(`git log ${latestTag}..HEAD --format=%B%H----DELIMITER----`) + .toString("utf-8"); +} else { + output = child + .execSync(`git log --format=%B%H----DELIMITER----`) + .toString("utf-8"); +} + +if (output.length === 0) { + console.log("No new indicated changes since last tag"); + return process.exit(1); +} + +// get array of commits since last tag +const commitsArray = output +.split("----DELIMITER----\n") +.map(commit => { + const splitCommit = commit.split("\n"); + const sha = splitCommit[1], message = splitCommit[0]; + return { sha, message }; +}) +.filter(commit => Boolean(commit.sha)); + +// get current version info +const currNotes = fs.readFileSync("../../NEWS.md", "utf-8"); +const currVersion = (require("./package.json").version).split('.'); + +var major = Number(currVersion[0]), minor = Number(currVersion[1]), patch = Number(currVersion[2]); + +// sort commits by message tags +var changes = [], features = [], fixes = []; + +commitsArray.forEach(commit => { + + if (commit.message.toLowerCase().startsWith("breaking-change:")) { + changes = utils.parseMessage("breaking-change:", changes, commit); + + } else if (commit.message.toLowerCase().startsWith("feature:")) { + features = utils.parseMessage("feature:", features, commit); + + } else if (commit.message.toLowerCase().startsWith("fix:")) { + fixes = utils.parseMessage("fix:", fixes, commit); + } +}); + +// update package version (following semantic versioning) +if (changes.length) { + major += 1; + minor = 0; + patch = 0; +} else if (features.length) { + minor += 1; + patch = 0; +} else if (fixes.length) { + patch += 1; +} + +const newVersion = [String(major), String(minor), String(patch)].join('.'); + +// format commits into markdown +let newNotes = `# azuremlsdk ${newVersion} (${ +new Date().toISOString().split("T")[0] +})\n\n`; + +if (changes.length) { + newNotes = utils.formatUpdates(newNotes, `## Breaking changes\n`, changes); +} +if (features.length) { + newNotes = utils.formatUpdates(newNotes, `## New features\n`, features); +} +if (fixes.length) { + newNotes = utils.formatUpdates(newNotes, `## Bug fixes\n`, fixes); +} + +// prepend the new release notes to the current file +fs.writeFileSync("./NEWS.md", `${newNotes}${currNotes}`); + +// update version in package.json +fs.writeFileSync("./package.json", JSON.stringify({ version: String(newVersion) }, null, 2)); + +// commit and tag new version +child.execSync('git add .'); +child.execSync(`git commit -m "Bump to version ${newVersion}"`); +child.execSync(`git tag -a -m "Tag for version ${newVersion}" version${newVersion}`); diff --git a/misc/changelog-generator/instructions.md b/misc/changelog-generator/instructions.md new file mode 100644 index 00000000..5b03b7a0 --- /dev/null +++ b/misc/changelog-generator/instructions.md @@ -0,0 +1,30 @@ + +### Creating Pull Requests for Inclusion in NEWS.md + +If a PR is significant enough to warrant a mention in the next release notes update, +its name should begin with a prefix. +There are three options depending on the PR's +purpose. + + * `breaking-change: ` if the change will make the next tag non-backward-compatible + * `feature: ` if the change is a major addition that maintains backward-compatibility + * `fix: ` if the change is a bug fix, security patch, or other improvement + +If the PR does not begin with one of these prefixes, it WILL NOT be included in +the release notes, so make sure to name important PRs accordingly. + +### Generating Notes and Creating a New Tag + +To update release notes for and commit a new git tag: + +1. Navigate to this directory in while on master branch +2. Run `node changelog_generator.js` on the command line +3. Confirm version was updated in package.json and notes were added to NEWS.md +4. Push to **origin/master** (this can only be done by owners/admins). + +The generator follows semantic versioning, so: + + * If there have been breaking changes since the last tag, it will increment the 1st (major) version digit by 1 and set the 2nd (minor) and 3rd (patch) to 0. (e.g. 0.6.8 → 1.0.0) + * If there have been no breaking changes, but there have been feature additions, it will increment the minor digit by 1 and set the patch digit to 0. (e.g. 0.6.8 → 0.7.0) + * If there have been no breaking changes nor feature additions, but there have been bug fixes, it will increment the patch digit by 1. (e.g. 0.6.8 → 0.6.9) + diff --git a/misc/changelog-generator/package.json b/misc/changelog-generator/package.json new file mode 100644 index 00000000..5d40f048 --- /dev/null +++ b/misc/changelog-generator/package.json @@ -0,0 +1,3 @@ +{ + "version": "0.6.85" +} \ No newline at end of file diff --git a/misc/changelog-generator/utils.js b/misc/changelog-generator/utils.js new file mode 100644 index 00000000..2f802cd4 --- /dev/null +++ b/misc/changelog-generator/utils.js @@ -0,0 +1,34 @@ + +function parseMessage(message_prefix, commitArray, commit) { +/* + Strips commit message of prefix and pushes to returned array +*/ + + commitArray.push( + `* ${commit.message.substring(message_prefix)} ([${commit.sha.substring( + 0, + 6 + )}](https://github.com/Azure/azureml-sdk-for-r/commit/${ + commit.sha + }))\n` + ); + + return commitArray +} + +function formatUpdates(notes, sectionHeading, messages) { +/* + Format a section with a heading a corresponding commit messages +*/ + + notes += sectionHeading; + messages.forEach(message => { + notes += message; + }); + notes += '\n'; + + return notes +} + +exports.parseMessage = parseMessage; +exports.formatUpdates = formatUpdates; \ No newline at end of file