diff --git a/.gitignore b/.gitignore index 3a0dba1..152eea9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ node_modules .DS_Store coverage .nyc_output -lib/ \ No newline at end of file +lib/ +yarn.lock \ No newline at end of file diff --git a/README.md b/README.md index 3b3cd17..d20b3a8 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Plugins are listed in the recommended order: | 1. | githubVerify | verifies `GH_TOKEN` or `GITHUB_TOKEN` and repo permissions. | | 2. | npmVerify | verifies `NPM_TOKEN`. | | 3. | analyzeCommits | analyzes commit history and determines version type. | -| 4. | updatePackageJSON | updates package version and versions of dependencies. | +| 4. | updatePackage | updates package version and versions of dependencies. | | 5. | releaseNotes | generates release notes for GitHub release. | | 6. | githubPublish | publishes a new release to GitHub. | | 7. | npmPublish | publishes the package to npm. | @@ -58,7 +58,7 @@ const projectPackages = [ { name: 'package-1', location: '/my/project/packages/package-1', - packageJSON: { + package: { // Here goes the ENTIRE content of `package.json` file name: 'package-1', version: '0.0.0-semantically-released', @@ -79,7 +79,7 @@ wsr.release({ wsr.npmVerify(), wsr.analyzeCommits(), wsr.releaseNotes(), - wsr.updatePackageJSON(), + wsr.updatePackage(), wsr.githubPublish(), wsr.npmPublish() ] @@ -103,7 +103,7 @@ const packages = PackageUtilities.getPackages(new Repository()) return { name: pkg.name, location: pkg.location, - packageJSON: pkg.toJSON() + package: pkg.toJSON() }; }); ``` @@ -119,7 +119,7 @@ These Flow types will make everything much clearer: declare type Package = { name: string, location: string, - packageJSON: Object + package: Object }; declare type Params = { diff --git a/package.json b/package.json index d0e4fc2..5bcf084 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "p-reduce": "^1.0.0", "pre-commit": "^1.2.2", "prettier": "^1.10.2", - "proxyquire": "^2.0.0", + "proxyquire": "2.0.0", "sinon": "^4.4.2", "source-map-support": "^0.5.0", "tempy": "^0.2.1", diff --git a/scripts/merge.js b/scripts/merge.js new file mode 100644 index 0000000..c9808b2 --- /dev/null +++ b/scripts/merge.js @@ -0,0 +1,81 @@ +#!/usr/bin/env node + +/** + * [ MAINTAINERS ONLY ] + * This tool checks if there are any commits that are in branch A that are not in branch B + * and merges the branches. You will have to run `git push` yourself to confirm the merge. + * + * This tool is used to merge branches without creating PRs (as GitHub does) and avoid the annoying merge commit. + */ + +const execa = require("execa"); +const yargs = require("yargs"); +const chalk = require("chalk"); +const gitLogParser = require("git-log-parser"); +const getStream = require("get-stream"); + +const { argv } = yargs.command("$0 ", "merge branches").help(); + +function log(...args) { + const [format, ...rest] = args; + console.log( + `${chalk.grey("[WGIT]:")} ${format.replace(/%[^%]/g, seq => chalk.magenta(seq))}`, + ...rest + ); +} + +async function getCommits(gitHead) { + Object.assign(gitLogParser.fields, { + hash: "H", + message: "B", + gitTags: "d", + committerDate: { key: "ci", type: Date } + }); + + return (await getStream.array( + gitLogParser.parse({ _: `${gitHead ? gitHead + ".." : ""}HEAD` }) + )).map(commit => { + commit.message = commit.message.trim(); + commit.gitTags = commit.gitTags.trim(); + return commit; + }); +} + +(async () => { + const { from, into } = argv; + + log("Merging from %s into %s...", from, into); + const options = { stdio: "inherit" }; + + try { + await execa.shell("git fetch", options); + await execa.shell("git checkout " + into, options); + await execa.shell("git pull", options); + await execa.shell("git checkout " + from, options); + + // Count commits on the "from" branch starting from merge-base to HEAD + const commits = await getCommits(into, from); + if (commits.length) { + log(`Found %s commit(s) that need to be merged:`, commits.length); + commits.map(c => { + log(`* ${c.subject} (%s)`, c.commit.short); + }); + await execa.shell("git checkout " + into, options); + await execa.shell("git merge " + from, options); + log( + "Merge is complete. You must run %s yourself after you have verified everything is in order.", + "git push" + ); + } else { + log( + "No new commits were found in branch %s that are not already in branch %s.", + from, + into + ); + } + log("Exiting."); + } catch (err) { + console.error(err); + process.exit(1); + } +})(); diff --git a/scripts/release.js b/scripts/release.js index 237aa21..365d766 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -24,7 +24,7 @@ const config = { // Make sure "main" field does not start with `src/` ({ packages, logger }, next) => { packages.map(pkg => { - const json = pkg.packageJSON; + const json = pkg.package; if (json.main && (json.main.startsWith("src/") || json.main.startsWith("./src/"))) { logger.log(`Updating \`main\` field of %s`, pkg.name); json.main = json.main.replace("src/", "lib/"); @@ -32,7 +32,7 @@ const config = { }); next(); }, - wsr.updatePackageJSON(), + wsr.updatePackage(), ({ packages }, next) => { packages.map(pkg => { if (pkg.nextRelease.version === "1.0.0") { diff --git a/src/index.js b/src/index.js index 9aa6064..763211e 100644 --- a/src/index.js +++ b/src/index.js @@ -12,7 +12,7 @@ import githubPublish from "./plugins/github/publish"; import npmVerify from "./plugins/npm/verify"; import npmPublish from "./plugins/npm/publish"; import releaseNotes from "./plugins/releaseNotes"; -import updatePackageJSON from "./plugins/updatePackageJSON"; +import updatePackage from "./plugins/updatePackage"; const release = async config => { const { params, plugins } = await buildParams(config); @@ -37,6 +37,6 @@ export { npmVerify, npmPublish, releaseNotes, - updatePackageJSON, + updatePackage, getPackage }; diff --git a/src/plugins/npm/publish.js b/src/plugins/npm/publish.js index 4f9c0a2..2f5dfa5 100644 --- a/src/plugins/npm/publish.js +++ b/src/plugins/npm/publish.js @@ -2,7 +2,9 @@ import execa from "execa"; import path from "path"; import fs from "fs-extra"; -export default () => { +export default (config = {}) => { + const { registry, tag } = config; + return async ({ packages, logger, config }, next) => { for (let i = 0; i < packages.length; i++) { const pkg = packages[i]; @@ -11,24 +13,33 @@ export default () => { } logger.log( - "Publishing %s version %s to npm registry", + "Publishing %s version %s to npm registry %s", pkg.name, - pkg.nextRelease.version + pkg.nextRelease.version, + registry ); + + const command = [ + `npm publish`, + tag ? `--tag ${tag}` : null, + registry ? `--registry ${registry}` : null, + pkg.location + ] + .filter(v => v) + .join(" "); + if (config.preview) { - logger.log(`DRY: %s`, `npm publish ${pkg.location}`); + logger.log(`DRY: %s`, command); } else { try { // write the updated package.json to disk before publishing - fs.writeJsonSync(path.join(pkg.location, "package.json"), pkg.packageJSON, { + fs.writeJsonSync(path.join(pkg.location, "package.json"), pkg.package, { spaces: 2 }); // We need to unset the `npm_` env variables to make sure local `.npmrc` is being read. // This is required when running scripts with yarn: https://github.com/yarnpkg/yarn/issues/4475 const shell = await execa.shell( - `unset $(env | awk -F= '$1 ~ /^npm_/ {print $1}') && npm publish ${ - pkg.location - }` + `unset $(env | awk -F= '$1 ~ /^npm_/ {print $1}') && ${command}` ); logger.log(shell.stdout); pkg.npmPublish = { diff --git a/src/plugins/npm/verify.js b/src/plugins/npm/verify.js index 2093467..588092a 100644 --- a/src/plugins/npm/verify.js +++ b/src/plugins/npm/verify.js @@ -1,7 +1,9 @@ import execa from "execa"; import fs from "fs-extra"; -export default () => { +export default (config = {}) => { + const { registry = "//registry.npmjs.org" } = config; + return async ({ logger, config }, next) => { if (config.preview) { return next(); @@ -14,10 +16,12 @@ export default () => { logger.log("Verifying access to NPM..."); try { - await fs.appendFile("./.npmrc", `\n//registry.npmjs.org/:_authToken=${NPM_TOKEN}`); + await fs.appendFile("./.npmrc", `\n${registry}/:_authToken=${NPM_TOKEN}`); // We need to unset the `npm_` env variables to make sure local `.npmrc` is being read. // This is required when running scripts with yarn: https://github.com/yarnpkg/yarn/issues/4475 - await execa.shell("unset $(env | awk -F= '$1 ~ /^npm_/ {print $1}') && npm whoami"); + await execa.shell( + `unset $(env | awk -F= '$1 ~ /^npm_/ {print $1}') && npm whoami --registry ${registry}` + ); next(); } catch (err) { throw new Error("EINVALIDNPMTOKEN: " + err.message); diff --git a/src/plugins/updatePackageJSON/index.js b/src/plugins/updatePackage/index.js similarity index 80% rename from src/plugins/updatePackageJSON/index.js rename to src/plugins/updatePackage/index.js index 3984105..b7e095d 100644 --- a/src/plugins/updatePackageJSON/index.js +++ b/src/plugins/updatePackage/index.js @@ -28,9 +28,9 @@ export default () => { } // Update package.json data - pkg.packageJSON.version = pkg.nextRelease.version; - updateDeps(pkg.packageJSON.dependencies, packages); - updateDeps(pkg.packageJSON.devDependencies, packages); + pkg.package.version = pkg.nextRelease.version; + updateDeps(pkg.package.dependencies, packages); + updateDeps(pkg.package.devDependencies, packages); } next(); diff --git a/src/utils/buildParams.js b/src/utils/buildParams.js index 702a400..e29622e 100644 --- a/src/utils/buildParams.js +++ b/src/utils/buildParams.js @@ -42,11 +42,11 @@ export default async config => { params.packages.map(pkg => { if ( !pkg.hasOwnProperty("name") || - !pkg.hasOwnProperty("packageJSON") || + !pkg.hasOwnProperty("package") || !pkg.hasOwnProperty("location") ) { throw new Error( - `EINVALIDPACKAGE: Packages MUST contain \`name\`, \`location\` and \`packageJSON\` keys.` + `EINVALIDPACKAGE: Packages MUST contain \`name\`, \`location\` and \`package\` keys.` ); } }); diff --git a/src/utils/compose.js b/src/utils/compose.js index 965f1bd..34fc783 100644 --- a/src/utils/compose.js +++ b/src/utils/compose.js @@ -6,8 +6,8 @@ * @param {Array} functions functions * @return {Function} */ -export default function (functions: Array = []): Function { - return function (params: mixed): Promise { +export default function(functions: Array = []): Function { + return function(params: mixed): Promise { // Create a clone of function chain to prevent modifying the original array with `shift()` const chain = [...functions]; return new Promise((parentResolve, parentReject) => { diff --git a/src/utils/getPackage.js b/src/utils/getPackage.js index 8dde166..eae190a 100644 --- a/src/utils/getPackage.js +++ b/src/utils/getPackage.js @@ -3,7 +3,7 @@ import readPkg from "read-pkg"; /** * Get a single package * @param config - * @returns {{name: string, location: string, packageJSON}} + * @returns {{name: string, location: string, package}} */ export default (config = {}) => { const root = config.root || process.cwd(); @@ -11,6 +11,6 @@ export default (config = {}) => { return { name: pkg.name, location: root, - packageJSON: pkg + package: pkg }; }; diff --git a/tests/analyzeCommits.test.js b/tests/analyzeCommits.test.js index d518a49..53aa8c9 100644 --- a/tests/analyzeCommits.test.js +++ b/tests/analyzeCommits.test.js @@ -168,13 +168,16 @@ describe("analyzeCommits plugin test", function() { const isRelevant = (pkg, commit) => { if (commit.message.match(/affects:(.*)/)) { - return RegExp.$1.split(",").map(n => n.trim()).filter(name => pkg.name === name).length; + return RegExp.$1 + .split(",") + .map(n => n.trim()) + .filter(name => pkg.name === name).length; } }; - release = compose([analyzeCommitsFactory({isRelevant})]); + release = compose([analyzeCommitsFactory({ isRelevant })]); const params = { - packages: [{ name: "package-1" }, { name: "package-2" }, {name: "package-3"}], + packages: [{ name: "package-1" }, { name: "package-2" }, { name: "package-3" }], logger, git: new Git(), config: { diff --git a/tests/buildParams.test.js b/tests/buildParams.test.js index c801c7d..3f90c52 100644 --- a/tests/buildParams.test.js +++ b/tests/buildParams.test.js @@ -9,7 +9,7 @@ describe("build params test", () => { it("should build a valid default `params` object", async () => { const config = { - packages: [{ name: "package-1", location: "", packageJSON: {} }], + packages: [{ name: "package-1", location: "", package: {} }], tagFormat }; @@ -42,7 +42,7 @@ describe("build params test", () => { preview: true, repositoryUrl: "test", branch: "development", - packages: [{ name: "package-1", location: "", packageJSON: {} }], + packages: [{ name: "package-1", location: "", package: {} }], tagFormat }; @@ -70,7 +70,7 @@ describe("build params test", () => { preview: true, repositoryUrl: "test", branch: "development", - packages: [{ name: "package-1", location: "", packageJSON: {} }], + packages: [{ name: "package-1", location: "", package: {} }], tagFormat }; @@ -94,7 +94,7 @@ describe("build params test", () => { it("should convert `tagFormat` string into a function", async () => { const config = { - packages: [{ name: "package-1", location: "", packageJSON: {} }], + packages: [{ name: "package-1", location: "", package: {} }], tagFormat: "v${version}" }; @@ -105,7 +105,7 @@ describe("build params test", () => { it("should use the given `tagFormat` function", async () => { const config = { - packages: [{ name: "package-1", location: "", packageJSON: {} }], + packages: [{ name: "package-1", location: "", package: {} }], tagFormat }; @@ -115,7 +115,7 @@ describe("build params test", () => { }); it("should convert a single package to an array of packages", async () => { - const pkg1 = { name: "package-1", location: "", packageJSON: {} }; + const pkg1 = { name: "package-1", location: "", package: {} }; const { params } = await buildParams({ packages: pkg1, tagFormat @@ -133,7 +133,7 @@ describe("build params test", () => { it("should throw error if an invalid package structure is found", async () => { const config1 = { packages: [{ name: "package-1" }], tagFormat }; - const config2 = { packages: [{ packageJSON: "package-1" }], tagFormat }; + const config2 = { packages: [{ package: "package-1" }], tagFormat }; const config3 = { packages: [{ location: "package-1" }], tagFormat }; return Promise.all([ diff --git a/tests/npmPublish.test.js b/tests/npmPublish.test.js index daa2979..1279833 100644 --- a/tests/npmPublish.test.js +++ b/tests/npmPublish.test.js @@ -38,7 +38,7 @@ describe("npmPublish plugin test", function() { const pkg = { name: "package-1", location: dir, - packageJSON: { + package: { name: "package-1", version: "1.0.0" }, @@ -58,7 +58,7 @@ describe("npmPublish plugin test", function() { execa: { shell: () => { packageWritten = fileExists(dir + "/package.json"); - return { stdout: pkg.packageJSON.name + "@" + pkg.packageJSON.version }; + return { stdout: pkg.package.name + "@" + pkg.package.version }; } } }); @@ -68,8 +68,8 @@ describe("npmPublish plugin test", function() { await release(params); expect(packageWritten).to.be.true; - expect(pkg.npmPublish.stdout).to.contain(pkg.packageJSON.name); - expect(pkg.npmPublish.stdout).to.contain(pkg.packageJSON.version); + expect(pkg.npmPublish.stdout).to.contain(pkg.package.name); + expect(pkg.npmPublish.stdout).to.contain(pkg.package.version); }); it("should continue publishing packages even if one of the packages fails", async () => { @@ -79,7 +79,7 @@ describe("npmPublish plugin test", function() { const pkg1 = { name: "package-1", location: dir1, - packageJSON: { + package: { name: "package-1", version: "1.0.0" }, @@ -91,7 +91,7 @@ describe("npmPublish plugin test", function() { const pkg2 = { name: "package-2", location: dir2, - packageJSON: { + package: { name: "package-2", version: "1.2.0" }, @@ -112,7 +112,7 @@ describe("npmPublish plugin test", function() { .onFirstCall() .throws(() => new Error("Invalid package")) .onSecondCall() - .returns({ stdout: pkg2.packageJSON.name + "@" + pkg2.packageJSON.version }) + .returns({ stdout: pkg2.package.name + "@" + pkg2.package.version }) } }); @@ -121,7 +121,7 @@ describe("npmPublish plugin test", function() { await release(params); expect(pkg1.npmPublish.error.message).to.equal("Invalid package"); - expect(pkg2.npmPublish.stdout).to.contain(pkg2.packageJSON.version); + expect(pkg2.npmPublish.stdout).to.contain(pkg2.package.version); }); it("should skip publishing if `nextRelease` is not set", async () => { @@ -146,7 +146,7 @@ describe("npmPublish plugin test", function() { const pkg = { name: "package-1", location: "/dummy/location", - packageJSON: { + package: { name: "package-1", version: "1.0.0" }, diff --git a/tests/npmVerify.test.js b/tests/npmVerify.test.js index 69f4efa..d726cd4 100644 --- a/tests/npmVerify.test.js +++ b/tests/npmVerify.test.js @@ -90,8 +90,10 @@ describe("npmVerify plugin test", function() { process.env["NPM_TOKEN"] = "invalid-npm-token"; proxyquire(modulePath, { - execa: async () => { - throw new Error("Invalid token"); + execa: { + shell: async () => { + throw new Error("Invalid token"); + } } }); diff --git a/tests/updatePackageJson.test.js b/tests/updatePackage.test.js similarity index 87% rename from tests/updatePackageJson.test.js rename to tests/updatePackage.test.js index d45778f..eef27b5 100644 --- a/tests/updatePackageJson.test.js +++ b/tests/updatePackage.test.js @@ -1,14 +1,14 @@ import { expect } from "chai"; import compose from "../src/utils/compose"; -import updatePackageJSONFactory from "../src/plugins/updatePackageJSON"; +import updatePackageFactory from "../src/plugins/updatePackage"; -describe("updatePackageJSON plugin test", function() { +describe("updatePackage plugin test", function() { it("should update package version and versions of dependencies", async () => { const params = { packages: [ { name: "package-1", - packageJSON: { + package: { name: "package-1", version: "0.0.0-semantically-released", dependencies: { @@ -23,7 +23,7 @@ describe("updatePackageJSON plugin test", function() { }, { name: "package-2", - packageJSON: { + package: { name: "package-2", version: "0.0.0-semantically-released" }, @@ -36,7 +36,7 @@ describe("updatePackageJSON plugin test", function() { }, { name: "package-3", - packageJSON: { + package: { name: "package-3", version: "0.0.0-semantically-released", dependencies: { @@ -50,9 +50,9 @@ describe("updatePackageJSON plugin test", function() { ] }; - await compose([updatePackageJSONFactory()])(params); + await compose([updatePackageFactory()])(params); - const [pkg1, pkg2, pkg3] = params.packages.map(p => p.packageJSON); + const [pkg1, pkg2, pkg3] = params.packages.map(p => p.package); expect(pkg1.version).to.equal("1.0.0"); expect(pkg1.dependencies["package-2"]).to.equal("^1.6.0"); diff --git a/tests/utils/preset.js b/tests/utils/preset.js index c4f57ef..e6b6513 100644 --- a/tests/utils/preset.js +++ b/tests/utils/preset.js @@ -1,3 +1,3 @@ export const packages = () => { - return [{ name: "preset-package", location: "", packageJSON: {} }]; + return [{ name: "preset-package", location: "", package: {} }]; }; diff --git a/types.js b/types.js index 2d690fd..c74d45f 100644 --- a/types.js +++ b/types.js @@ -1,7 +1,7 @@ export type Package = { name: string, location: string, - packageJSON: Object + package: Object }; export type Params = {