diff --git a/.eslintrc.json b/.eslintrc.json index a14b4ed..36c42e7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,7 +5,7 @@ }, "extends": "eslint:recommended", "parserOptions": { - "ecmaVersion": 6, + "ecmaVersion": 2017, "ecmaFeatures": { "impliedStrict": true } diff --git a/README.md b/README.md new file mode 100644 index 0000000..402bb1a --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# JS Cookie Release API + +Release routines for JavaScript Cookie library + +## Functions + +#### `Promise bumpPackageJSON(String bumpSpec, String filePath)` + +Bumps the "version" property from a list of file paths that matches the [JSON](http://json.org/) spec. + +#### `Promise gitCommit(String message, NodeGitRepository gitRepository)` + +Add all the changes to the staging area and create a commit to the given repository. + +## Release Steps + +* Run `npm run release `, where `bumpSpec` is either `patch`, `minor` or `major` +* Run `npm publish ./` diff --git a/package.json b/package.json index 33c01f4..681b060 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,11 @@ "author": "Fagner Brack", "license": "MIT", "scripts": { - "test": "eslint \"src/**/*.js\" \"test/**/*.js\" && mocha --trace-warnings", - "test:watch": "eslint \"src/**/*.js\" \"test/**/*.js\" && mocha -w" + "lint": "eslint *.js \"src/**/*.js\" \"test/**/*.js\"", + "test": "mocha --trace-warnings", + "test:watch": "mocha -w", + "release:test": "node release.js fake patch", + "release": "node release.js" }, "devDependencies": { "chai": "4.1.2", @@ -25,7 +28,6 @@ "dependencies": { "app-root-path": "2.0.1", "bluebird": "3.5.1", - "json-update": "3.0.0", "nodegit": "0.20.2" }, "engines": { diff --git a/release.js b/release.js new file mode 100644 index 0000000..6e6f4f8 --- /dev/null +++ b/release.js @@ -0,0 +1,64 @@ +const Promise = require("bluebird"); +const Git = require("nodegit"); + +const bumpPackageJSON = require("./src/file/bump-package-json"); +const gitCommit = require("./src/git/git-commit"); +const gitTag = require("./src/git/git-tag"); +const gitPushTag = require("./src/git/git-push-tag"); + +let targetBumpSpec; +if (process.argv.includes("patch")) { + targetBumpSpec = "patch"; +} +if (process.argv.includes("minor")) { + targetBumpSpec = "minor"; +} +if (process.argv.includes("major")) { + targetBumpSpec = "major"; +} +if (!targetBumpSpec) { + console.log("Invalid bump spec, use \"patch\", \"minor\" or \"major\""); + return; +} + +const isFakeRun = process.argv.includes("fake"); + +let localRepo; + +Promise.try(() => { + console.log("Bumping package.json..."); + if (!isFakeRun) { + return bumpPackageJSON(targetBumpSpec, "package.json"); + } +}).then(() => { + return Git.Repository.discover(".", 0, "."); +}).then((foundRepositoryPath) => { + console.log("Found repository on:", foundRepositoryPath); + return Git.Repository.open(foundRepositoryPath); +}).then((_localRepo) => { + localRepo = _localRepo; +}).then(() => { + console.log("Creating release commit..."); + if (!isFakeRun) { + return gitCommit("Release new version", localRepo); + } + return "fake44ef0665e9e8e5fdf7c6bfcd61f95fe8b699"; +}).then((commitObjectId) => { + const tagName = commitObjectId && commitObjectId.substring(0, 8); + const tagReferenceCommit = commitObjectId; + console.log("Creating tag:", tagName); + if (!isFakeRun) { + return gitTag(tagName, tagReferenceCommit, localRepo); + } + return /* FakeTag */ { name: () => "fake_tag_name" }; +}).then((tag) => { + console.log("Created tag '" + tag.name() + "'"); + return localRepo.getRemotes().then((localRemotes) => { + if (!isFakeRun) { + return gitPushTag(tag.name(), localRemotes[0], localRepo); + } + }); +}); + +// TODO Don't leak NodeGitRepository to gitCommit +// TODO Allow the input of password? diff --git a/src/file/bump-json-files.js b/src/file/bump-json-files.js deleted file mode 100644 index bf53beb..0000000 --- a/src/file/bump-json-files.js +++ /dev/null @@ -1,18 +0,0 @@ -const root = require("app-root-path"); -const Promise = require("bluebird"); -const updateJSON = Promise.promisify(require("json-update").update); -const loadJSON = Promise.promisify(require("json-update").load); - -const bumpVersion = require(root + "/src/bump-version"); - -module.exports = function(bumpSpec, fileNames) { - const promisesToBumpFiles = fileNames.map(function(fileName) { - return loadJSON(fileName) - .then(function(fileContent) { - return updateJSON(fileName, { - version: bumpVersion(fileContent.version, bumpSpec) - }); - }); - }); - return Promise.join(promisesToBumpFiles); -}; diff --git a/src/file/bump-package-json.js b/src/file/bump-package-json.js new file mode 100644 index 0000000..5088b39 --- /dev/null +++ b/src/file/bump-package-json.js @@ -0,0 +1,23 @@ +const root = require("app-root-path"); +const writeFile = require("util").promisify(require("fs").writeFile); +const readFile = require("util").promisify(require("fs").readFile); + +const bumpVersion = require(root + "/src/bump-version"); + +module.exports = async (bumpSpec, filePath) => { + const VERSION_KEY_VALUE_REGEX = /"version":( +)?"(.+)"/; + const fileContent = await readFile(filePath, "UTF-8"); + const matchedVersion = fileContent.match(VERSION_KEY_VALUE_REGEX); + if (matchedVersion === null) { + throw new Error( + "Could not find 'version' property in package.json to bump" + ); + } + const version = matchedVersion[2]; + const bumpedVersion = bumpVersion(version, bumpSpec); + const replacedFileContent = fileContent.replace( + VERSION_KEY_VALUE_REGEX, + `"version":$1"${bumpedVersion}"` + ); + await writeFile(filePath, replacedFileContent); +}; diff --git a/src/git/git-push.js b/src/git/git-push-master.js similarity index 100% rename from src/git/git-push.js rename to src/git/git-push-master.js diff --git a/src/git/git-push-tag.js b/src/git/git-push-tag.js new file mode 100644 index 0000000..fd80b50 --- /dev/null +++ b/src/git/git-push-tag.js @@ -0,0 +1,5 @@ +module.exports = function(tagName, remoteName, repository) { + return repository.getRemote(remoteName).then(function(remote) { + return remote.push([`refs/tags/${tagName}:refs/tags/${tagName}`]); + }); +}; diff --git a/test/file/bump-json-files.spec.js b/test/file/bump-json-files.spec.js index 15b6fc5..5adff0e 100644 --- a/test/file/bump-json-files.spec.js +++ b/test/file/bump-json-files.spec.js @@ -1,33 +1,48 @@ -const root = require("app-root-path"); const expect = require("chai").expect; -const Promise = require("bluebird"); -const loadJSON = Promise.promisify(require("json-update").load); - -const bumpJSONFiles = require(root + "/src/file/bump-json-files"); -const createFileSync = require(root + "/test/dummy-data/create-file-sync"); - -describe("bump-json-files", function() { - - describe("Given a dummy file in JSON format", function() { - const targetFilename = "bump-minor.json"; - let removeDummyJSONFileSync; - - beforeEach(function() { - removeDummyJSONFileSync = createFileSync(targetFilename, JSON.stringify({ - version: "0.0.0" - })); - }); - - afterEach(function() { - removeDummyJSONFileSync(); - }); - - it("should bump the 'minor' version attribute", function() { - return bumpJSONFiles("minor", [targetFilename]).then(function() { - return loadJSON(targetFilename); - }).then(function(targetFile) { - expect(targetFile).to.have.property("version", "0.1.0"); - }); - }); +const readFile = require("util").promisify(require("fs").readFile); +const writeFile = require("util").promisify(require("fs").writeFile); +const unlink = require("util").promisify(require("fs").unlink); + +const bumpPackageJSON = require(require("app-root-path") + "/src/file/bump-package-json"); + +describe("Bump Package JSON", () => { + + it("bumps the patch version in the package.json", async () => { + await writeFile("target-package.json", "{\"version\":\"0.0.0\"}"); + await bumpPackageJSON("patch", "target-package.json"); + const targetFileContent = await readFile("target-package.json", "UTF-8"); + expect(targetFileContent).to.equal("{\"version\":\"0.0.1\"}"); + await unlink("target-package.json"); + + await writeFile("another-target-package.json", "{\"version\":\"0.0.0\"}"); + await bumpPackageJSON("patch", "another-target-package.json"); + const anotherTargetFileContent = await readFile("another-target-package.json", "UTF-8"); + expect(anotherTargetFileContent).to.equal("{\"version\":\"0.0.1\"}"); + await unlink("another-target-package.json"); + }); + + it("keeps existing properties intact", async () => { + await writeFile("target-package.json", "{\"existing\":1,\"version\":\"0.0.0\"}"); + await bumpPackageJSON("patch", "target-package.json"); + const bumpedFile = await readFile("target-package.json", "UTF-8"); + expect(bumpedFile).to.equal("{\"existing\":1,\"version\":\"0.0.1\"}"); + await unlink("target-package.json"); }); + + it("keeps existing spacing intact", async () => { + await writeFile("target-package.json", "{ \"version\": \"0.0.0\" }"); + await bumpPackageJSON("patch", "target-package.json"); + const bumpedFile = await readFile("target-package.json", "UTF-8"); + expect(bumpedFile).to.equal("{ \"version\": \"0.0.1\" }"); + await unlink("target-package.json"); + }); + + it("keep existing multiple spacing intact", async () => { + await writeFile("target-package.json", "{ \"version\": \"0.0.0\" }"); + await bumpPackageJSON("patch", "target-package.json"); + const bumpedFile = await readFile("target-package.json", "UTF-8"); + expect(bumpedFile).to.equal("{ \"version\": \"0.0.1\" }"); + await unlink("target-package.json"); + }); + }); diff --git a/test/git/git-push.spec.js b/test/git/git-push.spec.js index e462f40..cfb8ece 100644 --- a/test/git/git-push.spec.js +++ b/test/git/git-push.spec.js @@ -2,35 +2,36 @@ const root = require("app-root-path"); const expect = require("chai").expect; const startGitRepoWithServer = require(root + "/test/dummy-data/start-git-repo-with-server"); -const gitPush = require(root + "/src/git/git-push"); +const gitPushMaster = require(root + "/src/git/git-push-master"); +const gitPushTag = require(root + "/src/git/git-push-tag"); const gitCommit = require(root + "/src/git/git-commit"); +const gitTag = require(root + "/src/git/git-tag"); const cloneToLocalDir = require(root + "/test/dummy-data/clone-repo-to-local-dir"); -describe("git-push", function() { +describe("Given a dummy server with git enabled cloned to local dir", function() { let defaultTestRepository; let clonedTestRepository; - describe("Given a dummy server with git enabled cloned to local dir", function() { - - beforeEach(function() { - return startGitRepoWithServer().then(function(defaultTestRepositoryResult) { - defaultTestRepository = defaultTestRepositoryResult; - return cloneToLocalDir(defaultTestRepository.gitHttpUrl); - }).then(function(clonedTestRepositoryResult) { - clonedTestRepository = clonedTestRepositoryResult; - }); + beforeEach(function() { + return startGitRepoWithServer().then(function(defaultTestRepositoryResult) { + defaultTestRepository = defaultTestRepositoryResult; + return cloneToLocalDir(defaultTestRepository.gitHttpUrl); + }).then(function(clonedTestRepositoryResult) { + clonedTestRepository = clonedTestRepositoryResult; }); + }); - afterEach(function() { - defaultTestRepository.destroy(); - clonedTestRepository.remove(); - }); + afterEach(function() { + defaultTestRepository.destroy(); + clonedTestRepository.remove(); + }); + describe("Git Push Master To Remote", function() { it("should commit and push the repository sucessfully to the remote", function() { let dummyCommitId; return gitCommit("Dummy commit", clonedTestRepository.repository).then(function(oid) { dummyCommitId = oid; - return gitPush(defaultTestRepository.remotes[0].name, clonedTestRepository.repository); + return gitPushMaster(defaultTestRepository.remotes[0].name, clonedTestRepository.repository); }).then(function() { return defaultTestRepository.remotes[0].repository.getCommit(dummyCommitId); }).then(function(commit) { @@ -38,4 +39,25 @@ describe("git-push", function() { }); }); }); -}); \ No newline at end of file + + describe("Git Push Tag To Remote", function() { + it("should commit, create a tag and push successfully to the remote", function() { + const DUMMY_TAG_NAME = "dummy_tag_name"; + return gitCommit("Dummy commit", clonedTestRepository.repository).then(function(dummyCommitId) { + return gitTag(DUMMY_TAG_NAME, dummyCommitId, clonedTestRepository.repository); + }).then(function() { + return gitPushTag(DUMMY_TAG_NAME, defaultTestRepository.remotes[0].name, clonedTestRepository.repository); + }).then(function deleteLocalTag() { + return require("nodegit").Tag.delete(clonedTestRepository.repository, DUMMY_TAG_NAME); + }).then(function fetchRemoteTag() { + return clonedTestRepository.repository.getRemote(defaultTestRepository.remotes[0].name).then(function(remote) { + return remote.fetch([`refs/tags/${DUMMY_TAG_NAME}`]); + }); + }).then(function() { + return clonedTestRepository.repository.getTagByName(DUMMY_TAG_NAME).then(function(tag) { + expect(tag.name()).to.equal(DUMMY_TAG_NAME); + }); + }); + }); + }); +});