├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmignore.publishr ├── .nycrc ├── .travis.yml ├── LICENSE.md ├── README.md ├── bin └── publishr.js ├── package.json ├── src ├── args.js ├── cli.js ├── constants.js ├── dry-runner.js ├── error-handler.js ├── file-handler.js ├── file-utils.js ├── git.js ├── logger.js ├── package-utils.js ├── postpublish.js └── postversion.js └── test ├── .eslintrc ├── main.js ├── spec ├── args.spec.js ├── cli.spec.js ├── dry-runner.spec.js ├── end-to-end.spec.js ├── error-handler.spec.js ├── file-handler.spec.js ├── file-utils.spec.js ├── git.spec.js ├── logger.spec.js ├── package-utils.spec.js ├── postpublish.spec.js └── postversion.spec.js └── test-helpers.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "env": { 4 | "test": { "plugins": ["istanbul"] } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | extends: 3 | - formidable/configurations/es6-node-test 4 | 5 | parserOptions: 6 | sourceType: module 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | coverage/ 3 | lib/ 4 | node_modules/ 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.npmignore.publishr: -------------------------------------------------------------------------------- 1 | /* 2 | !/bin 3 | !/lib 4 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "branches": 100, 4 | "functions": 100, 5 | "include": ["src/*.js"], 6 | "lines": 100, 7 | "instrument": false, 8 | "reporter": [ 9 | "lcov", 10 | "html", 11 | "text", 12 | "text-summary" 13 | ], 14 | "require": [ 15 | "babel-register", 16 | "./test/main" 17 | ], 18 | "statements": 100, 19 | "sourceMap": false 20 | } 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "4" 5 | - "6" 6 | 7 | sudo: false 8 | 9 | script: 10 | - npm run test 11 | - cat coverage/lcov.info | node_modules/.bin/coveralls || echo "Coveralls upload failed" 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Formidable Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Travis Status][trav_img]][trav_site] 2 | [![Coverage Status][cov_img]][cov_site] 3 | [![NPM Package][npm_img]][npm_site] 4 | [![Maintenance Status][maintenance-image]](#maintenance-status) 5 | 6 | 7 | # Publishr 8 | 9 | A tool for harmonious publishing of git and npm packages. 10 | 11 | Publishr allows you to consistently publish different files in git and npm using an **npm version workflow**, 12 | which enables efficient installation from both types of repository. 13 | 14 | ## Motivation 15 | 16 | It can be troublesome to enable package installation from both npm and git repositories, 17 | especially when a project includes build steps. One inefficient publishing solution entails 18 | saving both source and compiled files to git and npm. Another less than ideal solution requires 19 | installing heavy build dependencies in production. Depending on the size of your 20 | repository, these solutions can be a burden for both development and production. 21 | Ideally, the git repository only contains source code and the npm repository contains 22 | compiled code. Furthermore, the npm repository should not contain any large build dependencies. 23 | Publishr solves these problems by tapping into [npm's version/publish lifecycle scripts][npm_scripts_docs]. 24 | 25 | ## Installation 26 | 27 | ```sh 28 | $ npm install publishr 29 | ``` 30 | 31 | ## Setup 32 | 33 | 1. Save all build dependencies to `package.json` as `dependencies`. 34 | 2. Save placeholder (ex. `.someconfig.publishr`) files that should be replaced in the npm repo. 35 | 3. Add a `publishr` config to `package.json`. 36 | 4. Use `publishr.dependencies` to describe which build dependencies to replace in the npm repo. 37 | 5. Use `publishr.files` to describe files to replace/create in the npm repo. 38 | 6. Use `publishr.scripts` to describe scripts to add/replace/remove in the npm repo. 39 | 7. Add `publishr postversion` to [npm's postversion script][npm_scripts_docs]. 40 | 8. Add `publishr postpublish` to [npm's postpublish script][npm_scripts_docs]. 41 | 42 | ## Configuration 43 | 44 | 1. `publishr.dependencies` - Describes build dependencies to replace in the npm repo. 45 | * Takes an array of **regular expression strings** 46 | * `["^babel$"]` matches only `babel` 47 | * `["^babel"]` matches `babel`, `babel-core` 48 | * `["babel"]` matches `babel`, `babel-core`, `is-babel` 49 | 2. `publishr.files` - Describes files to replace/create in the npm repo. 50 | * Takes an object of oldFile keys to newFile values 51 | * `{".npmignore": ".npmignore.publishr"}` replaces/creates `.npmignore` with `.npmignore.publishr` 52 | 3. `publishr.scripts` - Describes files to add/replace/remove in the npm repo. 53 | * Takes an object of script name keys to command values. 54 | * `{"hello": "echo hello"}` adds/replaces the test script `hello` with the command `echo hello` 55 | * `{"postinstall": ""}` removes the `postinstall` script. 56 | 57 | ## Publishing 58 | 59 | 1. Run `publishr dry-run` to test your configuration. 60 | 2. If the dry run fails, fix all errors and go back to `1`. 61 | 3. Run your [version][npm_version_docs] command. 62 | 4. Run your [publish][npm_publish_docs] command. 63 | 64 | ## Example 65 | 66 | An example `package.json` file will look something like this: 67 | 68 | ```json 69 | { 70 | "name": "some-neat-project", 71 | "version": "0.0.1", 72 | "dependencies": { 73 | "lodash": "^4.0.0", 74 | "babel-core": "^6.0.0" 75 | }, 76 | "devDependencies": { 77 | "eslint": "^1.0.0" 78 | }, 79 | "scripts": { 80 | "build": "gulp build", 81 | "postinstall": "npm run build", 82 | "postpublish": "publishr postpublish", 83 | "postversion": "publishr postversion" 84 | }, 85 | "publishr": { 86 | "dependencies": ["^babel"], 87 | "files": { 88 | ".npmignore": ".npmignore.publishr", 89 | ".someconfig": ".someconfig.publishr" 90 | }, 91 | "scripts": { 92 | "build": "echo 'No Build Needed'", 93 | "extra": "echo 'Extra Script'", 94 | "postinstall": "" 95 | } 96 | } 97 | } 98 | ``` 99 | 100 | The above configuration tells publishr to do a few things: 101 | 102 | 1. Move all `dependencies` matching the regular expression `^babel` to `devDependencies` before publishing to npm. 103 | 2. Replace `.npmignore` with the contents of `.npmignore.publishr` before publishing to npm. 104 | 3. Replace `.someconfig` with the contents of `.someconfig.publishr` before publishing to npm. 105 | 4. Replace the `build` script with `echo 'No Build Needed'` before publishing to npm. 106 | 5. Add the `extra` script before publishing to npm. 107 | 6. Remove the `postinstall` script before publishing to npm. 108 | 109 | The version command will look something like this: 110 | 111 | ```shell 112 | $ npm version patch 113 | ``` 114 | 115 | Result: 116 | 117 | ``` 118 | v0.0.2 119 | 120 | > some-neat-project@0.0.2 postversion /some/path 121 | > publishr postversion 122 | ``` 123 | 124 | The publish command will look something like this: 125 | 126 | ```shell 127 | $ npm publish 128 | ``` 129 | 130 | Result: 131 | 132 | ``` 133 | + some-neat-project@0.0.2 134 | 135 | > some-neat-project@0.0.2 postpublish /some/path 136 | > publishr postpublish 137 | 138 | ``` 139 | 140 | When all is said and done, the git and npm repo will have different versions of `package.json`, `.npmignore`, and `.someconfig`. Your npm package will install as quickly as possible and you still support installing from a git repo. 141 | 142 | ## Usage 143 | 144 | ``` 145 | Usage: publishr [options] 146 | 147 | Commands: 148 | dry-run Perform a dry run of postversion and postpublish 149 | postpublish Clean up any actions taken by postversion 150 | postversion Create and overwrite files for publishing 151 | 152 | Options: 153 | -h, --help Show help [boolean] 154 | -V, --verbose Log each step during postversion/postpublish [boolean] 155 | -v, --version Show version number [boolean] 156 | ``` 157 | 158 | ## Maintenance Status 159 | 160 | **Stable:** Formidable is not planning to develop any new features for this project. We are still responding to bug reports and security concerns. We are still welcoming PRs for this project, but PRs that include new features should be small and easy to integrate and should not include breaking changes. 161 | 162 | [maintenance-image]: https://img.shields.io/badge/maintenance-stable-blue.svg 163 | [trav_img]: https://img.shields.io/travis/FormidableLabs/publishr.svg 164 | [trav_site]: https://travis-ci.com/FormidableLabs/publishr 165 | [cov_img]: https://img.shields.io/coveralls/FormidableLabs/publishr.svg 166 | [cov_site]: https://coveralls.io/r/FormidableLabs/publishr 167 | [npm_img]: https://img.shields.io/npm/v/publishr.svg 168 | [npm_site]: https://www.npmjs.org/package/publishr 169 | [npm_publish_docs]: https://docs.npmjs.com/cli/publish 170 | [npm_version_docs]: https://docs.npmjs.com/cli/version 171 | [npm_scripts_docs]: https://docs.npmjs.com/misc/scripts 172 | 173 | -------------------------------------------------------------------------------- /bin/publishr.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require("../lib/cli").default(); 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "publishr", 3 | "version": "1.0.0", 4 | "description": "A tool for harmonious publishing of git and npm packages.", 5 | "bin": { 6 | "publishr": "bin/publishr.js" 7 | }, 8 | "scripts": { 9 | "build": "rimraf lib && babel src/ -d lib/", 10 | "coverage": "rimraf coverage && NODE_PATH=src NODE_ENV=test nyc --check-coverage mocha 'test/**/*.spec.js'", 11 | "dry-run": "node bin/publishr.js dry-run", 12 | "lint": "eslint bin src test", 13 | "test": "npm run lint && npm run coverage", 14 | "postinstall": "npm run build", 15 | "postpublish": "node bin/publishr.js postpublish", 16 | "postversion": "npm run build && node bin/publishr.js postversion" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/FormidableLabs/publishr.git" 21 | }, 22 | "keywords": [ 23 | "git", 24 | "npm", 25 | "publish", 26 | "version" 27 | ], 28 | "license": "MIT", 29 | "dependencies": { 30 | "babel-cli": "^6.9.1", 31 | "babel-core": "^6.9.1", 32 | "babel-plugin-istanbul": "^4.1.1", 33 | "babel-preset-es2015": "^6.9.0", 34 | "babel-register": "^6.9.1", 35 | "chalk": "^1.1.3", 36 | "es6-promise": "^3.2.1", 37 | "log-symbols": "^1.0.2", 38 | "mock-fs": "^4.0.0", 39 | "object-assign": "^4.1.0", 40 | "rimraf": "^2.5.2", 41 | "yargs": "^4.7.1" 42 | }, 43 | "devDependencies": { 44 | "babel-eslint": "^7.0.0", 45 | "chai": "^3.5.0", 46 | "coveralls": "^2.11.9", 47 | "eslint": "^2.0.0", 48 | "eslint-config-formidable": "^2.0.0", 49 | "eslint-plugin-filenames": "^1.0.0", 50 | "eslint-plugin-import": "^2.0.0", 51 | "mocha": "^3.0.0", 52 | "nyc": "^10.0.0", 53 | "sinon": "^2.0.0", 54 | "sinon-chai": "^2.8.0" 55 | }, 56 | "publishr": { 57 | "dependencies": [ 58 | "^babel" 59 | ], 60 | "files": { 61 | ".npmignore": ".npmignore.publishr" 62 | }, 63 | "scripts": { 64 | "postinstall": "" 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/args.js: -------------------------------------------------------------------------------- 1 | import yargs from "yargs"; 2 | 3 | 4 | const args = { 5 | init() { 6 | return yargs 7 | .usage("Usage: publishr [options]") 8 | .command("dry-run", "Perform a dry run of postversion and postpublish") 9 | .command("postpublish", "Clean up any actions taken by postversion") 10 | .command("postversion", "Create and overwrite files for publishing") 11 | .help().alias("h", "help") 12 | .option("verbose", { 13 | describe: "Log each step during postversion/postpublish", 14 | type: "boolean" 15 | }).alias("V", "verbose") 16 | .version().alias("v", "version"); 17 | }, 18 | 19 | showHelp() { 20 | yargs.showHelp(); 21 | } 22 | }; 23 | 24 | export default args; 25 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | import args from "./args"; 2 | import dryRunner from "./dry-runner"; 3 | import errorHandler from "./error-handler"; 4 | import logger from "./logger"; 5 | import postpublish from "./postpublish"; 6 | import postversion from "./postversion"; 7 | 8 | 9 | const cli = () => { 10 | const argv = args.init().argv; 11 | const cmd = argv._[0]; 12 | 13 | if (argv.verbose) { 14 | logger.enable(); 15 | } 16 | 17 | if (cmd === "postversion") { 18 | postversion.run().catch(errorHandler.postversionError); 19 | } else if (cmd === "postpublish") { 20 | postpublish.run().catch(errorHandler.postpublishError); 21 | } else if (cmd === "dry-run") { 22 | dryRunner.run().catch(errorHandler.dryRunnerError); 23 | } else { 24 | args.showHelp(); 25 | } 26 | }; 27 | 28 | export default cli; 29 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const NUM_JSON_SPACES = 2; 2 | -------------------------------------------------------------------------------- /src/dry-runner.js: -------------------------------------------------------------------------------- 1 | import { Promise } from "es6-promise"; 2 | import { NUM_JSON_SPACES } from "./constants"; 3 | import fileUtils from "./file-utils"; 4 | import git from "./git"; 5 | import logger from "./logger"; 6 | import postpublish from "./postpublish"; 7 | import postversion from "./postversion"; 8 | 9 | 10 | let mockfs; 11 | 12 | const dryRunner = { 13 | afterDryRun() { 14 | logger.disable(); 15 | git.disableDry(); 16 | dryRunner.restoreFileSystem(); 17 | 18 | return Promise.resolve(); 19 | }, 20 | 21 | beforeDryRun() { 22 | logger.enable(); 23 | git.enableDry(); 24 | logger.info("Validating configuration..."); 25 | 26 | return fileUtils 27 | .readPackage() 28 | .then(dryRunner.validatePackage) 29 | .then(dryRunner.validateFiles) 30 | .then((result) => { 31 | dryRunner.patchFileSystem(result.json, result.files); 32 | 33 | return Promise.resolve(); 34 | }); 35 | }, 36 | 37 | patchFileSystem(packageJSON, files) { 38 | mockfs = require("mock-fs"); // eslint-disable-line global-require 39 | 40 | const fileSystem = files.reduce((result, file) => { 41 | result[file.path] = mockfs.file({ 42 | content: `${file.path} contents`, 43 | mode: file.stats.mode 44 | }); 45 | 46 | return result; 47 | }, { 48 | "package.json": JSON.stringify(packageJSON, null, NUM_JSON_SPACES) 49 | }); 50 | 51 | mockfs(fileSystem); 52 | }, 53 | 54 | postpublish() { 55 | logger.info("Validating postpublish..."); 56 | 57 | return postpublish.run(); 58 | }, 59 | 60 | postversion() { 61 | logger.info("Validating postversion..."); 62 | 63 | return postversion.run(); 64 | }, 65 | 66 | restoreFileSystem() { 67 | mockfs.restore(); 68 | }, 69 | 70 | run() { 71 | return dryRunner 72 | .beforeDryRun() 73 | .then(dryRunner.postversion) 74 | .then(dryRunner.postpublish) 75 | .then(dryRunner.afterDryRun); 76 | }, 77 | 78 | validateFileOperation(filePath) { 79 | return fileUtils.statFile(filePath).then((stats) => { 80 | logger.pass(`validate ${filePath}`); 81 | 82 | return Promise.resolve({ 83 | path: filePath, 84 | stats 85 | }); 86 | }); 87 | }, 88 | 89 | validateFileRead(filePath) { 90 | return dryRunner.validateFileOperation(filePath).catch((err) => { 91 | logger.fail(`validate ${filePath}`, err); 92 | 93 | return Promise.reject(err); 94 | }); 95 | }, 96 | 97 | validateFiles(packageJSON) { 98 | const files = packageJSON.publishr.files || {}; 99 | const filePaths = Object.keys(files); 100 | const fileReads = filePaths.map((filePath) => { 101 | return dryRunner.validateFileRead(files[filePath]); 102 | }); 103 | const fileWrites = filePaths.map((filePath) => { 104 | return dryRunner.validateFileWrite(filePath); 105 | }); 106 | const fileOperations = [].concat(fileReads, fileWrites); 107 | 108 | return Promise.all(fileOperations).then((validFiles) => { 109 | return Promise.resolve({ 110 | files: validFiles.filter((file) => file), 111 | json: packageJSON 112 | }); 113 | }); 114 | }, 115 | 116 | validateFileWrite(filePath) { 117 | return dryRunner.validateFileOperation(filePath).catch((err) => { 118 | if (err.code !== "ENOENT") { 119 | logger.pass(`validate ${filePath}`, err); 120 | 121 | return Promise.reject(err); 122 | } 123 | 124 | logger.pass(`validate ${filePath}`); 125 | 126 | return Promise.resolve(); 127 | }); 128 | }, 129 | 130 | validatePackage(packageJSON) { 131 | let err; 132 | 133 | if (!packageJSON || !packageJSON.publishr) { 134 | err = new Error("No publishr configuration in 'package.json'"); 135 | } else if (!packageJSON.publishr.dependencies && !packageJSON.publishr.files) { 136 | err = new Error("No files or dependencies in publishr configuration"); 137 | } 138 | 139 | if (err) { 140 | logger.fail("validate 'package.json'", err); 141 | 142 | return Promise.reject(err); 143 | } 144 | 145 | logger.pass("validate 'package.json'"); 146 | 147 | return Promise.resolve(packageJSON); 148 | } 149 | }; 150 | 151 | export default dryRunner; 152 | -------------------------------------------------------------------------------- /src/error-handler.js: -------------------------------------------------------------------------------- 1 | import logger from "./logger"; 2 | 3 | 4 | const errorHandler = { 5 | messages: { 6 | checkFiles: "Check 'package.json' and files defined in the publishr config.", 7 | checkErrors: "Check the errors above for more information.", 8 | dryRunPass: "Make sure 'dry-run' passes before reattempting '%s'.", 9 | fixFiles: "Each file may need to be manually checked out or deleted.", 10 | gitStatus: "Run 'git status' to see if anything is out of place.", 11 | unexpected: "Something unexpected happend during '%s'." 12 | }, 13 | 14 | onError(err) { 15 | logger.enable(); 16 | logger.error(`${err.stack || err.toString()}\n`); 17 | }, 18 | 19 | dryRunnerError(err) { 20 | errorHandler.onError(err); 21 | 22 | logger.info(errorHandler.messages.unexpected, "dry-run"); 23 | logger.info(errorHandler.messages.checkErrors); 24 | logger.info("Make sure to address all errors before reattemping 'dry-run'."); 25 | }, 26 | 27 | postpublishError(err) { 28 | errorHandler.onError(err); 29 | 30 | logger.info(errorHandler.messages.unexpected, "postpublish"); 31 | logger.info(errorHandler.messages.checkErrors); 32 | logger.info(errorHandler.messages.gitStatus); 33 | logger.info(errorHandler.messages.checkFiles); 34 | logger.info(errorHandler.messages.fixFiles); 35 | logger.info(errorHandler.messages.dryRunPass, "publish"); 36 | }, 37 | 38 | postversionError(err) { 39 | errorHandler.onError(err); 40 | 41 | logger.info(errorHandler.messages.unexpected, "postversion"); 42 | logger.info(errorHandler.messages.checkErrors); 43 | logger.info(errorHandler.messages.gitStatus); 44 | logger.info(errorHandler.messages.checkFiles); 45 | logger.info(errorHandler.messages.fixFiles); 46 | logger.info("Do not attempt to 'publish' until all errors are addressed."); 47 | logger.info(errorHandler.messages.dryRunPass, "version"); 48 | } 49 | }; 50 | 51 | export default errorHandler; 52 | -------------------------------------------------------------------------------- /src/file-handler.js: -------------------------------------------------------------------------------- 1 | import { Promise } from "es6-promise"; 2 | import fileUtils from "./file-utils"; 3 | import git from "./git"; 4 | import packageUtils from "./package-utils"; 5 | 6 | 7 | const fileHandler = { 8 | fixFiles(json) { 9 | json._publishr = json._publishr || []; 10 | 11 | return Promise.all(json._publishr.map((file) => { 12 | return file.created ? 13 | fileUtils.removeFile(file.path) : 14 | git.checkout(file.path); 15 | })); 16 | }, 17 | 18 | overwriteFiles(json) { 19 | json.publishr = json.publishr || {}; 20 | json.publishr.files = json.publishr.files || {}; 21 | json.publishr.dependencies = json.publishr.dependencies || []; 22 | json.publishr.scripts = json.publishr.scripts || {}; 23 | 24 | const files = Object.keys(json.publishr.files).map((file) => ({ 25 | newPath: file, 26 | oldPath: json.publishr.files[file] 27 | })); 28 | 29 | return fileUtils 30 | .statFiles(files) 31 | .then(fileUtils.readFiles) 32 | .then(fileUtils.writeFiles) 33 | .then((allFiles) => fileHandler.overwritePackage(json, allFiles)); 34 | }, 35 | 36 | overwritePackage(json, files) { 37 | packageUtils.updateMeta(json, files); 38 | packageUtils.updateDependencies(json); 39 | packageUtils.updateScripts(json); 40 | 41 | return fileUtils.writePackage(json); 42 | } 43 | }; 44 | 45 | export default fileHandler; 46 | -------------------------------------------------------------------------------- /src/file-utils.js: -------------------------------------------------------------------------------- 1 | import { Promise } from "es6-promise"; 2 | import fs from "fs"; 3 | import { NUM_JSON_SPACES } from "./constants"; 4 | import logger from "./logger"; 5 | 6 | 7 | const fileUtils = { 8 | readFile(filePath) { 9 | return new Promise((resolve, reject) => { 10 | fs.readFile(filePath, "utf8", (err, contents) => { 11 | if (err) { 12 | logger.fail(`read '${filePath}'`, err); 13 | 14 | return reject(err); 15 | } 16 | 17 | logger.pass(`read '${filePath}'`); 18 | 19 | return resolve(contents); 20 | }); 21 | }); 22 | }, 23 | 24 | readFiles(files) { 25 | return Promise.all(files.map((file) => { 26 | return fileUtils.readFile(file.oldPath).then((contents) => { 27 | file.contents = contents; 28 | 29 | return Promise.resolve(file); 30 | }); 31 | })); 32 | }, 33 | 34 | readPackage() { 35 | return fileUtils 36 | .readFile("package.json") 37 | .then((contents) => Promise.resolve(JSON.parse(contents))); 38 | }, 39 | 40 | removeFile(filePath) { 41 | return new Promise((resolve, reject) => { 42 | fs.unlink(filePath, (err) => { 43 | if (err) { 44 | logger.fail(`remove '${filePath}'`, err); 45 | 46 | return reject(err); 47 | } 48 | 49 | logger.pass(`remove '${filePath}'`); 50 | 51 | return resolve(); 52 | }); 53 | }); 54 | }, 55 | 56 | statFile(filePath) { 57 | return new Promise((resolve, reject) => { 58 | fs.stat(filePath, (err, stats) => { 59 | if (err) { 60 | return reject(err); 61 | } 62 | 63 | return resolve(stats); 64 | }); 65 | }); 66 | }, 67 | 68 | statFiles(files) { 69 | return Promise.all(files.map((file) => { 70 | return fileUtils.statFile(file.newPath).then(() => { 71 | file.created = false; 72 | 73 | return Promise.resolve(file); 74 | }).catch((err) => { 75 | if (err.code !== "ENOENT") { 76 | return Promise.reject(err); 77 | } 78 | 79 | file.created = true; 80 | 81 | return Promise.resolve(file); 82 | }); 83 | })); 84 | }, 85 | 86 | writeFile(filePath, contents) { 87 | return new Promise((resolve, reject) => { 88 | fs.writeFile(filePath, contents, "utf8", (err) => { 89 | if (err) { 90 | logger.fail(`write '${filePath}'`, err); 91 | 92 | return reject(err); 93 | } 94 | 95 | logger.pass(`write '${filePath}'`); 96 | 97 | return resolve(); 98 | }); 99 | }); 100 | }, 101 | 102 | writeFiles(files) { 103 | return Promise.all(files.map((file) => { 104 | return fileUtils.writeFile(file.newPath, file.contents).then(() => { 105 | file.written = true; 106 | 107 | return Promise.resolve(file); 108 | }); 109 | })); 110 | }, 111 | 112 | writePackage(json) { 113 | let contents; 114 | 115 | try { 116 | contents = JSON.stringify(json, null, NUM_JSON_SPACES); 117 | } catch (err) { 118 | return Promise.reject(err); 119 | } 120 | 121 | return fileUtils.writeFile("package.json", contents); 122 | } 123 | }; 124 | 125 | export default fileUtils; 126 | -------------------------------------------------------------------------------- /src/git.js: -------------------------------------------------------------------------------- 1 | import { exec } from "child_process"; 2 | import { Promise } from "es6-promise"; 3 | import logger from "./logger"; 4 | 5 | 6 | const git = { 7 | dry: false, 8 | 9 | checkout(filePath) { 10 | return new Promise((resolve, reject) => { 11 | git.exec(`git checkout ${filePath}`, (err, stdout) => { 12 | if (err) { 13 | logger.fail(`checkout '${filePath}'`, err); 14 | 15 | return reject(err); 16 | } 17 | 18 | logger.pass(`checkout '${filePath}'`); 19 | 20 | return resolve(stdout); 21 | }); 22 | }); 23 | }, 24 | 25 | disableDry() { 26 | git.dry = false; 27 | }, 28 | 29 | exec(cmd, cb) { 30 | if (git.dry) { 31 | exec("git status", cb); 32 | } else { 33 | exec(cmd, cb); 34 | } 35 | }, 36 | 37 | enableDry() { 38 | git.dry = true; 39 | } 40 | }; 41 | 42 | export default git; 43 | -------------------------------------------------------------------------------- /src/logger.js: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | import logSymbols from "log-symbols"; 3 | 4 | 5 | const logger = { 6 | enabled: false, 7 | 8 | disable() { 9 | logger.enabled = false; 10 | }, 11 | 12 | enable() { 13 | logger.enabled = true; 14 | }, 15 | 16 | error(message) { 17 | if (logger.enabled) { 18 | console.error(chalk.red(message)); // eslint-disable-line no-console 19 | } 20 | }, 21 | 22 | fail(message, err) { 23 | logger.log(`${logSymbols.error} ${chalk.gray(message)}`); 24 | 25 | if (err && err.message) { 26 | logger.log(chalk.red(err.message)); 27 | } 28 | }, 29 | 30 | info(...args) { 31 | logger.log(...args); 32 | }, 33 | 34 | log(...args) { 35 | if (logger.enabled) { 36 | console.log(...args); // eslint-disable-line no-console 37 | } 38 | }, 39 | 40 | pass(message) { 41 | logger.log(`${logSymbols.success} ${chalk.gray(message)}`); 42 | } 43 | }; 44 | 45 | export default logger; 46 | -------------------------------------------------------------------------------- /src/package-utils.js: -------------------------------------------------------------------------------- 1 | import logger from "./logger"; 2 | import objectAssign from "object-assign"; 3 | 4 | 5 | const packageUtils = { 6 | updateDependencies(json) { 7 | if (!json.publishr || !json.publishr.dependencies) { 8 | return json; 9 | } 10 | 11 | json.dependencies = objectAssign({}, json.dependencies); 12 | json.devDependencies = objectAssign({}, json.devDependencies); 13 | 14 | const dependencyKeys = Object.keys(json.dependencies); 15 | 16 | json.publishr.dependencies.forEach((str) => { 17 | const regex = new RegExp(str); 18 | 19 | dependencyKeys.filter( 20 | (key) => regex.test(key) 21 | ).forEach((key) => { 22 | json.devDependencies[key] = json.dependencies[key]; 23 | logger.pass(`dependency '${key}'`); 24 | }); 25 | 26 | json.dependencies = dependencyKeys.filter( 27 | (key) => !regex.test(key) 28 | ).reduce((result, key) => { 29 | result[key] = json.dependencies[key]; 30 | 31 | return result; 32 | }, {}); 33 | }); 34 | 35 | return json; 36 | }, 37 | 38 | updateScripts(json) { 39 | if (!json.publishr || !json.publishr.scripts) { 40 | return json; 41 | } 42 | 43 | json.scripts = objectAssign({}, json.scripts); 44 | 45 | Object.keys(json.publishr.scripts).forEach((key) => { 46 | const script = json.publishr.scripts[key]; 47 | 48 | if (!script) { 49 | delete json.scripts[key]; 50 | logger.pass(`remove script '${key}'`); 51 | } else { 52 | json.scripts[key] = script; 53 | logger.pass(`replace script '${key}'`); 54 | } 55 | }); 56 | 57 | return json; 58 | }, 59 | 60 | updateMeta(json, files) { 61 | json._publishr = files.map((file) => ({ 62 | created: file.created, 63 | path: file.newPath 64 | })); 65 | json._publishr.push({ 66 | created: false, 67 | path: "package.json" 68 | }); 69 | 70 | return json; 71 | } 72 | }; 73 | 74 | export default packageUtils; 75 | -------------------------------------------------------------------------------- /src/postpublish.js: -------------------------------------------------------------------------------- 1 | import fileHandler from "./file-handler"; 2 | import fileUtils from "./file-utils"; 3 | 4 | 5 | const postpublish = { 6 | run() { 7 | return fileUtils 8 | .readPackage() 9 | .then(fileHandler.fixFiles); 10 | } 11 | }; 12 | 13 | export default postpublish; 14 | -------------------------------------------------------------------------------- /src/postversion.js: -------------------------------------------------------------------------------- 1 | import fileHandler from "./file-handler"; 2 | import fileUtils from "./file-utils"; 3 | 4 | 5 | const postversion = { 6 | run() { 7 | return fileUtils 8 | .readPackage() 9 | .then(fileHandler.overwriteFiles); 10 | } 11 | }; 12 | 13 | export default postversion; 14 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | env: 3 | mocha: true 4 | globals: 5 | expect: true 6 | 7 | rules: 8 | import/no-unresolved: off 9 | no-magic-numbers: off 10 | -------------------------------------------------------------------------------- /test/main.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import sinonChai from "sinon-chai"; 3 | 4 | 5 | chai.use(sinonChai); 6 | global.expect = chai.expect; 7 | -------------------------------------------------------------------------------- /test/spec/args.spec.js: -------------------------------------------------------------------------------- 1 | import args from "args"; 2 | import sinon from "sinon"; 3 | import yargs from "yargs"; 4 | 5 | 6 | describe("args", () => { 7 | let sandbox; 8 | 9 | beforeEach(() => { 10 | sandbox = sinon.sandbox.create(); 11 | }); 12 | 13 | afterEach(() => { 14 | sandbox.restore(); 15 | }); 16 | 17 | it("should call various yargs commands", () => { 18 | sandbox.stub(yargs, "alias").returns(yargs); 19 | sandbox.stub(yargs, "command").returns(yargs); 20 | sandbox.stub(yargs, "help").returns(yargs); 21 | sandbox.stub(yargs, "option").returns(yargs); 22 | sandbox.stub(yargs, "usage").returns(yargs); 23 | sandbox.stub(yargs, "version").returns(yargs); 24 | 25 | args.init(); 26 | expect(yargs.alias) 27 | .to.have.callCount(3).and 28 | .to.have.been.calledWith("h", "help").and 29 | .to.have.been.calledWith("V", "verbose").and 30 | .to.have.been.calledWith("v", "version"); 31 | expect(yargs.command) 32 | .to.have.callCount(3).and 33 | .to.have.been.calledWith("dry-run", "Perform a dry run of postversion and postpublish").and 34 | .to.have.been.calledWith("postpublish", "Clean up any actions taken by postversion").and 35 | .to.have.been.calledWith("postversion", "Create and overwrite files for publishing"); 36 | expect(yargs.help).to.have.callCount(1); 37 | expect(yargs.option) 38 | .to.have.callCount(1).and 39 | .to.have.been.calledWith("verbose", { 40 | describe: "Log each step during postversion/postpublish", 41 | type: "boolean" 42 | }); 43 | expect(yargs.usage).to.have.callCount(1); 44 | expect(yargs.version).to.have.callCount(1); 45 | }); 46 | 47 | it("should show help", () => { 48 | sandbox.stub(yargs, "showHelp"); 49 | 50 | args.showHelp(); 51 | expect(yargs.showHelp).to.have.callCount(1); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/spec/cli.spec.js: -------------------------------------------------------------------------------- 1 | import args from "args"; 2 | import cli from "cli"; 3 | import dryRunner from "dry-runner"; 4 | import { Promise } from "es6-promise"; 5 | import logger from "logger"; 6 | import postpublish from "postpublish"; 7 | import postversion from "postversion"; 8 | import sinon from "sinon"; 9 | 10 | 11 | describe("cli", () => { 12 | let sandbox; 13 | 14 | beforeEach(() => { 15 | sandbox = sinon.sandbox.create(); 16 | sandbox.stub(args, "init"); 17 | sandbox.stub(args, "showHelp"); 18 | sandbox.stub(dryRunner, "run"); 19 | sandbox.stub(logger, "enable"); 20 | sandbox.stub(postpublish, "run"); 21 | sandbox.stub(postversion, "run"); 22 | }); 23 | 24 | afterEach(() => { 25 | sandbox.restore(); 26 | }); 27 | 28 | it("should run postpublish", () => { 29 | postpublish.run.returns(Promise.resolve()); 30 | args.init.returns({ 31 | argv: { 32 | _: ["postpublish"] 33 | } 34 | }); 35 | 36 | cli(); 37 | expect(postpublish.run).to.have.callCount(1); 38 | expect(postversion.run).to.have.callCount(0); 39 | expect(dryRunner.run).to.have.callCount(0); 40 | }); 41 | 42 | it("should run postversion", () => { 43 | postversion.run.returns(Promise.resolve()); 44 | args.init.returns({ 45 | argv: { 46 | _: ["postversion"] 47 | } 48 | }); 49 | 50 | cli(); 51 | expect(postversion.run).to.have.callCount(1); 52 | expect(postpublish.run).to.have.callCount(0); 53 | expect(dryRunner.run).to.have.callCount(0); 54 | }); 55 | 56 | it("should run dry-run", () => { 57 | dryRunner.run.returns(Promise.resolve()); 58 | args.init.returns({ 59 | argv: { 60 | _: ["dry-run"] 61 | } 62 | }); 63 | 64 | cli(); 65 | expect(dryRunner.run).to.have.callCount(1); 66 | expect(postpublish.run).to.have.callCount(0); 67 | expect(postversion.run).to.have.callCount(0); 68 | }); 69 | 70 | it("should show help on bad commands", () => { 71 | args.init.returns({ 72 | argv: { 73 | _: [] 74 | } 75 | }); 76 | 77 | cli(); 78 | expect(args.showHelp).to.have.callCount(1); 79 | }); 80 | 81 | it("should turn on logging in verbose mode", () => { 82 | args.init.returns({ 83 | argv: { 84 | _: [], 85 | verbose: true 86 | } 87 | }); 88 | 89 | cli(); 90 | expect(logger.enable).to.have.callCount(1); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /test/spec/dry-runner.spec.js: -------------------------------------------------------------------------------- 1 | import dryRunner from "dry-runner"; 2 | import { Promise } from "es6-promise"; 3 | import fileUtils from "file-utils"; 4 | import git from "git"; 5 | import logger from "logger"; 6 | import mockfs from "mock-fs"; 7 | import postpublish from "postpublish"; 8 | import postversion from "postversion"; 9 | import sinon from "sinon"; 10 | 11 | 12 | describe("dryRunner", () => { 13 | let sandbox; 14 | 15 | beforeEach(() => { 16 | sandbox = sinon.sandbox.create(); 17 | }); 18 | 19 | afterEach(() => { 20 | sandbox.restore(); 21 | mockfs.restore(); 22 | }); 23 | 24 | describe("afterDryRun", () => { 25 | it("should tear down after the dry run", () => { 26 | sandbox.stub(dryRunner, "restoreFileSystem"); 27 | sandbox.stub(git, "disableDry"); 28 | sandbox.stub(logger, "disable"); 29 | 30 | return dryRunner.afterDryRun().then(() => { 31 | expect(git.disableDry).to.have.callCount(1); 32 | expect(dryRunner.restoreFileSystem).to.have.callCount(1); 33 | expect(logger.disable).to.have.callCount(1); 34 | }); 35 | }); 36 | }); 37 | 38 | describe("beforeDryRun", () => { 39 | it("should set up before the dry run", () => { 40 | sandbox.stub(dryRunner, "patchFileSystem").returns(Promise.resolve()); 41 | sandbox.stub(dryRunner, "validateFiles", (json) => { 42 | return Promise.resolve({ files: "mock files", json }); 43 | }); 44 | sandbox.stub(dryRunner, "validatePackage", (json) => Promise.resolve(json)); 45 | sandbox.stub(fileUtils, "readPackage").returns(Promise.resolve("mock json")); 46 | sandbox.stub(git, "enableDry"); 47 | sandbox.stub(logger, "enable"); 48 | 49 | return dryRunner.beforeDryRun().then(() => { 50 | expect(git.enableDry).to.have.callCount(1); 51 | expect(dryRunner.patchFileSystem) 52 | .to.have.callCount(1).and 53 | .to.have.been.calledWith("mock json", "mock files"); 54 | expect(dryRunner.validateFiles) 55 | .to.have.callCount(1).and 56 | .to.have.been.calledWith("mock json"); 57 | expect(dryRunner.validatePackage) 58 | .to.have.callCount(1).and 59 | .to.have.been.calledWith("mock json"); 60 | expect(logger.enable).to.have.callCount(1); 61 | }); 62 | }); 63 | }); 64 | 65 | describe("run", () => { 66 | it("should dry run postpublish", () => { 67 | sandbox.stub(logger, "info"); 68 | sandbox.stub(postpublish, "run"); 69 | 70 | dryRunner.postpublish(); 71 | expect(logger.info) 72 | .to.have.callCount(1).and 73 | .to.have.been.calledWith("Validating postpublish..."); 74 | expect(postpublish.run).to.have.callCount(1); 75 | }); 76 | 77 | it("should dry run postversion", () => { 78 | sandbox.stub(logger, "info"); 79 | sandbox.stub(postversion, "run"); 80 | 81 | dryRunner.postversion(); 82 | expect(logger.info) 83 | .to.have.callCount(1).and 84 | .to.have.been.calledWith("Validating postversion..."); 85 | expect(postversion.run).to.have.callCount(1); 86 | }); 87 | 88 | it("should call each dry run step", () => { 89 | sandbox.stub(dryRunner, "beforeDryRun").returns(Promise.resolve()); 90 | sandbox.stub(dryRunner, "postversion").returns(Promise.resolve()); 91 | sandbox.stub(dryRunner, "postpublish").returns(Promise.resolve()); 92 | sandbox.stub(dryRunner, "afterDryRun").returns(Promise.resolve()); 93 | 94 | return dryRunner.run().then(() => { 95 | expect(dryRunner.beforeDryRun).to.have.callCount(1); 96 | expect(dryRunner.postversion).to.have.callCount(1); 97 | expect(dryRunner.postpublish).to.have.callCount(1); 98 | expect(dryRunner.afterDryRun).to.have.callCount(1); 99 | }); 100 | }); 101 | }); 102 | 103 | describe("patchFileSystem", () => { 104 | it("should patch the file system", () => { 105 | const packageJSON = { 106 | dependencies: { 107 | lodash: "1.0.0" 108 | } 109 | }; 110 | 111 | dryRunner.patchFileSystem(packageJSON, [{ 112 | path: ".npmignore.publishr", 113 | stats: { 114 | mode: parseInt("0777", 8) 115 | } 116 | }, { 117 | path: ".babelrc.publishr", 118 | stats: { 119 | mode: parseInt("0777", 8) 120 | } 121 | }]); 122 | 123 | return Promise.all([ 124 | fileUtils.readFile("package.json"), 125 | fileUtils.readFile(".npmignore.publishr"), 126 | fileUtils.readFile(".babelrc.publishr") 127 | ]).then((fileContents) => { 128 | expect(fileContents[0]).to.equal(JSON.stringify(packageJSON, null, 2)); 129 | expect(fileContents[1]).to.equal(".npmignore.publishr contents"); 130 | expect(fileContents[2]).to.equal(".babelrc.publishr contents"); 131 | }); 132 | }); 133 | 134 | it("should patch the file system with existing read permissions", () => { 135 | const packageJSON = { 136 | dependencies: { 137 | lodash: "1.0.0" 138 | } 139 | }; 140 | 141 | dryRunner.patchFileSystem(packageJSON, [{ 142 | path: ".npmignore.publishr", 143 | stats: { 144 | mode: 0 145 | } 146 | }]); 147 | 148 | return fileUtils.readFile(".npmignore.publishr").catch((err) => { 149 | expect(err).to.have.property("code", "EACCES"); 150 | }); 151 | }); 152 | 153 | it("should restore the file system", () => { 154 | sandbox.stub(mockfs, "restore"); 155 | 156 | dryRunner.restoreFileSystem(); 157 | expect(mockfs.restore).to.have.callCount(1); 158 | }); 159 | }); 160 | 161 | describe("validateFiles", () => { 162 | it("should validate files", () => { 163 | const packageJSON = { 164 | publishr: { 165 | files: { 166 | ".babelrc": ".babelrc.publishr", 167 | ".npmignore": ".npmignore.publishr" 168 | } 169 | } 170 | }; 171 | 172 | sandbox.stub(dryRunner, "validateFileRead", (filePath) => Promise.resolve({ 173 | path: filePath, 174 | stats: "mock stats" 175 | })); 176 | sandbox.stub(dryRunner, "validateFileWrite").returns(Promise.resolve()); 177 | 178 | mockfs({ 179 | ".babelrc.publishr": "mock contents", 180 | ".npmignore.publishr": "mock contents" 181 | }); 182 | 183 | return dryRunner.validateFiles(packageJSON).then((result) => { 184 | expect(result).to.deep.equal({ 185 | files: [{ 186 | path: ".babelrc.publishr", 187 | stats: "mock stats" 188 | }, { 189 | path: ".npmignore.publishr", 190 | stats: "mock stats" 191 | }], 192 | json: packageJSON 193 | }); 194 | }); 195 | }); 196 | 197 | it("should validate files when there are no files", () => { 198 | const packageJSON = { 199 | publishr: { 200 | dependencies: ["^babel"] 201 | } 202 | }; 203 | 204 | sandbox.stub(dryRunner, "validateFileRead"); 205 | sandbox.stub(dryRunner, "validateFileWrite"); 206 | 207 | mockfs({}); 208 | 209 | return dryRunner.validateFiles(packageJSON).then(() => { 210 | expect(dryRunner.validateFileRead).to.have.callCount(0); 211 | expect(dryRunner.validateFileRead).to.have.callCount(0); 212 | }); 213 | }); 214 | }); 215 | 216 | describe("validateFileRead", () => { 217 | it("should validate a file read", () => { 218 | sandbox.stub(fileUtils, "statFile").returns(Promise.resolve("mock stats")); 219 | 220 | return dryRunner.validateFileRead(".npmignore.publishr").then((result) => { 221 | expect(result).to.deep.equal({ 222 | path: ".npmignore.publishr", 223 | stats: "mock stats" 224 | }); 225 | }); 226 | }); 227 | 228 | it("should invalidate a file read", () => { 229 | sandbox.stub(fileUtils, "statFile").returns(Promise.reject("mock error")); 230 | 231 | return dryRunner.validateFileRead(".npmignore.publishr").catch((err) => { 232 | expect(err).to.equal("mock error"); 233 | }); 234 | }); 235 | }); 236 | 237 | describe("validateFileWrite", () => { 238 | it("should validate a file write", () => { 239 | sandbox.stub(fileUtils, "statFile").returns(Promise.resolve("mock stats")); 240 | 241 | return dryRunner.validateFileWrite(".npmignore.publishr").then((result) => { 242 | expect(result).to.deep.equal({ 243 | path: ".npmignore.publishr", 244 | stats: "mock stats" 245 | }); 246 | }); 247 | }); 248 | 249 | it("should validate a file write for no file", () => { 250 | sandbox.stub(fileUtils, "statFile").returns(Promise.reject({ code: "ENOENT" })); 251 | 252 | return dryRunner.validateFileWrite(".npmignore.publishr").then((result) => { 253 | expect(result).to.equal(undefined); 254 | }); 255 | }); 256 | 257 | it("should invalidate a file write", () => { 258 | sandbox.stub(fileUtils, "statFile").returns(Promise.reject({ code: "EACCES" })); 259 | 260 | return dryRunner.validateFileWrite(".npmignore.publishr").catch((err) => { 261 | expect(err).to.have.property("code", "EACCES"); 262 | }); 263 | }); 264 | }); 265 | 266 | describe("validatePackage", () => { 267 | it("should validate a package", () => { 268 | const packageJSON = { 269 | publishr: { 270 | dependencies: ["^babel"] 271 | } 272 | }; 273 | 274 | sandbox.stub(logger, "pass"); 275 | 276 | return dryRunner.validatePackage(packageJSON).then((result) => { 277 | expect(logger.pass) 278 | .to.have.callCount(1).and 279 | .to.have.been.calledWith("validate 'package.json'"); 280 | expect(result).to.equal(packageJSON); 281 | }); 282 | }); 283 | 284 | it("should invalidate a package without a publishr config", () => { 285 | const packageJSON = {}; 286 | 287 | sandbox.stub(logger, "fail"); 288 | 289 | return dryRunner.validatePackage(packageJSON).catch((err) => { 290 | expect(logger.fail) 291 | .to.have.callCount(1).and 292 | .to.have.been.calledWith("validate 'package.json'", err); 293 | 294 | expect(err).to.have.property( 295 | "message", 296 | "No publishr configuration in 'package.json'" 297 | ); 298 | }); 299 | }); 300 | 301 | it("should invalidate a package without an invalid publishr config", () => { 302 | const packageJSON = { 303 | publishr: { 304 | deps: [], 305 | fils: {} 306 | } 307 | }; 308 | 309 | sandbox.stub(logger, "fail"); 310 | 311 | return dryRunner.validatePackage(packageJSON).catch((err) => { 312 | expect(logger.fail) 313 | .to.have.callCount(1).and 314 | .to.have.been.calledWith("validate 'package.json'", err); 315 | expect(err).to.have.property( 316 | "message", 317 | "No files or dependencies in publishr configuration" 318 | ); 319 | }); 320 | }); 321 | }); 322 | }); 323 | -------------------------------------------------------------------------------- /test/spec/end-to-end.spec.js: -------------------------------------------------------------------------------- 1 | import childProcess from "child_process"; 2 | import { Promise } from "es6-promise"; 3 | import fileUtils from "file-utils"; 4 | import mockfs from "mock-fs"; 5 | import postpublish from "postpublish"; 6 | import postversion from "postversion"; 7 | import sinon from "sinon"; 8 | import testHelpers from "../test-helpers"; 9 | 10 | 11 | describe("end-to-end", () => { 12 | let sandbox; 13 | 14 | beforeEach(() => { 15 | sandbox = sinon.sandbox.create(); 16 | }); 17 | 18 | afterEach(() => { 19 | mockfs.restore(); 20 | sandbox.restore(); 21 | }); 22 | 23 | it("should handle a full postversion/postpublish pipeline", () => { 24 | mockfs({ 25 | ".babelrc": "old .babelrc content", 26 | ".babelrc.publishr": "new .babelrc content", 27 | ".npmignore.publishr": "new .npmignore content", 28 | "package.json": JSON.stringify({ 29 | scripts: { 30 | cool: "echo 'not cool'", 31 | postinstall: "npm run build", 32 | test: "mocha" 33 | }, 34 | dependencies: { 35 | lodash: "^1.0.0", 36 | "babel-cli": "^6.0.0", 37 | "babel-core": "^6.0.0" 38 | }, 39 | devDependencies: { 40 | eslint: "^1.0.0" 41 | }, 42 | publishr: { 43 | dependencies: ["^babel"], 44 | files: { 45 | ".babelrc": ".babelrc.publishr", 46 | ".npmignore": ".npmignore.publishr" 47 | }, 48 | scripts: { 49 | added: "echo 'added'", 50 | cool: "echo 'cool'", 51 | postinstall: "" 52 | } 53 | } 54 | }) 55 | }); 56 | 57 | sandbox.stub(childProcess, "exec", (cmd, cb) => cb()); 58 | 59 | return postversion.run().then(() => { 60 | return Promise.all([ 61 | fileUtils.readFile(".babelrc"), 62 | fileUtils.readFile(".npmignore"), 63 | fileUtils.readFile("package.json") 64 | ]).then((results) => { 65 | expect(results[0]).to.equal("new .babelrc content"); 66 | expect(results[1]).to.equal("new .npmignore content"); 67 | expect(JSON.parse(results[2])).to.deep.equal({ 68 | scripts: { 69 | added: "echo 'added'", 70 | cool: "echo 'cool'", 71 | test: "mocha" 72 | }, 73 | dependencies: { 74 | lodash: "^1.0.0" 75 | }, 76 | devDependencies: { 77 | "babel-cli": "^6.0.0", 78 | "babel-core": "^6.0.0", 79 | eslint: "^1.0.0" 80 | }, 81 | publishr: { 82 | dependencies: ["^babel"], 83 | files: { 84 | ".babelrc": ".babelrc.publishr", 85 | ".npmignore": ".npmignore.publishr" 86 | }, 87 | scripts: { 88 | added: "echo 'added'", 89 | cool: "echo 'cool'", 90 | postinstall: "" 91 | } 92 | }, 93 | _publishr: [{ 94 | created: false, 95 | path: ".babelrc" 96 | }, { 97 | created: true, 98 | path: ".npmignore" 99 | }, { 100 | created: false, 101 | path: "package.json" 102 | }] 103 | }); 104 | 105 | return postpublish 106 | .run() 107 | .then(() => testHelpers.fileExists(".npmignore")) 108 | .then((fileExists) => { 109 | expect(fileExists).to.equal(false); 110 | expect(childProcess.exec) 111 | .to.have.callCount(2).and 112 | .to.have.been.calledWith("git checkout package.json").and 113 | .to.have.been.calledWith("git checkout .babelrc"); 114 | }); 115 | }); 116 | }); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /test/spec/error-handler.spec.js: -------------------------------------------------------------------------------- 1 | import errorHandler from "error-handler"; 2 | import logger from "logger"; 3 | import sinon from "sinon"; 4 | 5 | 6 | describe("errorHandler", () => { 7 | let sandbox; 8 | 9 | beforeEach(() => { 10 | sandbox = sinon.sandbox.create(); 11 | sandbox.stub(logger, "enable"); 12 | sandbox.stub(logger, "error"); 13 | sandbox.stub(logger, "info"); 14 | }); 15 | 16 | afterEach(() => { 17 | sandbox.restore(); 18 | }); 19 | 20 | describe("onError", () => { 21 | it("should handle an error with a stack", () => { 22 | const err = { stack: "mock stack" }; 23 | 24 | errorHandler.onError(err); 25 | expect(logger.error) 26 | .to.have.callCount(1).and 27 | .to.have.been.calledWith("mock stack\n"); 28 | }); 29 | 30 | it("should handle an error without a stack", () => { 31 | const err = { toString: () => {} }; 32 | 33 | sandbox.stub(err, "toString").returns("mock error"); 34 | 35 | errorHandler.onError(err); 36 | expect(logger.enable).to.have.callCount(1); 37 | expect(logger.error) 38 | .to.have.callCount(1).and 39 | .to.have.been.calledWith("mock error\n"); 40 | expect(err.toString).to.have.callCount(1); 41 | }); 42 | }); 43 | 44 | describe("dryRunnerError", () => { 45 | it("should handle a dry-run error", () => { 46 | sandbox.stub(errorHandler, "onError"); 47 | 48 | errorHandler.dryRunnerError("mock error"); 49 | expect(errorHandler.onError) 50 | .to.have.callCount(1).and 51 | .to.have.been.calledWith("mock error"); 52 | expect(logger.info).to.have.callCount(3); 53 | }); 54 | }); 55 | 56 | describe("postpublishError", () => { 57 | it("should handle a postpublish error", () => { 58 | sandbox.stub(errorHandler, "onError"); 59 | 60 | errorHandler.postpublishError("mock error"); 61 | expect(errorHandler.onError) 62 | .to.have.callCount(1).and 63 | .to.have.been.calledWith("mock error"); 64 | expect(logger.info).to.have.callCount(6); 65 | }); 66 | }); 67 | 68 | describe("postversionError", () => { 69 | it("should handle a postversion error", () => { 70 | sandbox.stub(errorHandler, "onError"); 71 | 72 | errorHandler.postversionError("mock error"); 73 | expect(errorHandler.onError) 74 | .to.have.callCount(1).and 75 | .to.have.been.calledWith("mock error"); 76 | expect(logger.info).to.have.callCount(7); 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/spec/file-handler.spec.js: -------------------------------------------------------------------------------- 1 | import { Promise } from "es6-promise"; 2 | import fileUtils from "file-utils"; 3 | import fileHandler from "file-handler"; 4 | import git from "git"; 5 | import packageUtils from "package-utils"; 6 | import sinon from "sinon"; 7 | 8 | 9 | describe("fileHandler", () => { 10 | let sandbox; 11 | 12 | beforeEach(() => { 13 | sandbox = sinon.sandbox.create(); 14 | }); 15 | 16 | afterEach(() => { 17 | sandbox.restore(); 18 | }); 19 | 20 | describe("fixFiles", () => { 21 | it("should checkout files", () => { 22 | sandbox.stub(git, "checkout"); 23 | sandbox.stub(fileUtils, "removeFile"); 24 | 25 | fileHandler.fixFiles({ 26 | _publishr: [{ 27 | created: false, 28 | path: "checkout.js" 29 | }] 30 | }); 31 | expect(fileUtils.removeFile).to.have.callCount(0); 32 | expect(git.checkout) 33 | .to.have.callCount(1).and 34 | .to.have.been.calledWith("checkout.js"); 35 | }); 36 | 37 | it("should remove files", () => { 38 | sandbox.stub(git, "checkout"); 39 | sandbox.stub(fileUtils, "removeFile"); 40 | 41 | fileHandler.fixFiles({ 42 | _publishr: [{ 43 | created: true, 44 | path: "remove.js" 45 | }] 46 | }); 47 | expect(git.checkout).to.have.callCount(0); 48 | expect(fileUtils.removeFile) 49 | .to.have.callCount(1).and 50 | .to.have.been.calledWith("remove.js"); 51 | }); 52 | 53 | it("should handle multiple files", () => { 54 | sandbox.stub(git, "checkout"); 55 | sandbox.stub(fileUtils, "removeFile"); 56 | 57 | fileHandler.fixFiles({ 58 | _publishr: [{ 59 | created: false, 60 | path: "checkout1.js" 61 | }, { 62 | created: true, 63 | path: "remove1.js" 64 | }, { 65 | created: false, 66 | path: "checkout2.js" 67 | }, { 68 | created: true, 69 | path: "remove2.js" 70 | }] 71 | }); 72 | expect(git.checkout) 73 | .to.have.callCount(2).and 74 | .to.have.been.calledWith("checkout1.js").and 75 | .to.have.been.calledWith("checkout2.js"); 76 | expect(fileUtils.removeFile) 77 | .to.have.callCount(2).and 78 | .to.have.been.calledWith("remove1.js").and 79 | .to.have.been.calledWith("remove2.js"); 80 | }); 81 | 82 | it("should handle no _publishr config", () => { 83 | sandbox.stub(git, "checkout"); 84 | sandbox.stub(fileUtils, "removeFile"); 85 | 86 | fileHandler.fixFiles({}); 87 | expect(git.checkout).to.have.callCount(0); 88 | expect(fileUtils.removeFile).to.have.callCount(0); 89 | }); 90 | }); 91 | 92 | describe("overwriteFiles", () => { 93 | it("should overwrite files", () => { 94 | const handler = (files) => Promise.resolve(files); 95 | 96 | sandbox.stub(fileUtils, "statFiles", handler); 97 | sandbox.stub(fileUtils, "readFiles", handler); 98 | sandbox.stub(fileUtils, "writeFiles", handler); 99 | sandbox.stub(fileHandler, "overwritePackage", handler); 100 | 101 | return fileHandler.overwriteFiles({ 102 | publishr: { 103 | files: { 104 | "first.js": "first.js.publishr", 105 | "second.js": "second.js.publishr" 106 | } 107 | } 108 | }).then((json) => { 109 | expect(fileUtils.statFiles) 110 | .to.have.callCount(1).and 111 | .to.have.been.calledWith([{ 112 | newPath: "first.js", 113 | oldPath: "first.js.publishr" 114 | }, { 115 | newPath: "second.js", 116 | oldPath: "second.js.publishr" 117 | }]); 118 | expect(fileUtils.readFiles).to.have.callCount(1); 119 | expect(fileUtils.writeFiles).to.have.callCount(1); 120 | expect(fileHandler.overwritePackage).to.have.callCount(1); 121 | expect(json).to.deep.equal({ 122 | publishr: { 123 | dependencies: [], 124 | files: { 125 | "first.js": "first.js.publishr", 126 | "second.js": "second.js.publishr" 127 | }, 128 | scripts: {} 129 | } 130 | }); 131 | }); 132 | }); 133 | 134 | it("should reject on an error", () => { 135 | const mockErr = new Error("Something bad happend!"); 136 | 137 | sandbox.stub(fileUtils, "statFiles").returns(Promise.reject(mockErr)); 138 | 139 | return fileHandler.overwriteFiles({ 140 | publishr: { 141 | files: { 142 | "file.js": "file.js.publishr" 143 | } 144 | } 145 | }).catch((err) => { 146 | expect(err).to.equal(mockErr); 147 | }); 148 | }); 149 | 150 | it("should handle no publishr config", () => { 151 | const handler = (files) => Promise.resolve(files); 152 | 153 | sandbox.stub(fileUtils, "statFiles", handler); 154 | sandbox.stub(fileUtils, "readFiles", handler); 155 | sandbox.stub(fileUtils, "writeFiles", handler); 156 | sandbox.stub(fileHandler, "overwritePackage", handler); 157 | 158 | return fileHandler.overwriteFiles({}).then((json) => { 159 | expect(fileUtils.statFiles) 160 | .to.have.callCount(1).and 161 | .to.have.been.calledWith([]); 162 | expect(fileUtils.readFiles).to.have.callCount(1); 163 | expect(fileUtils.writeFiles).to.have.callCount(1); 164 | expect(fileHandler.overwritePackage).to.have.callCount(1); 165 | expect(json).to.deep.equal({ 166 | publishr: { 167 | dependencies: [], 168 | files: {}, 169 | scripts: {} 170 | } 171 | }); 172 | }); 173 | }); 174 | }); 175 | 176 | describe("overwritePackage", () => { 177 | it("should overwrite the package.json file", () => { 178 | const packageJSON = { 179 | dependencies: { 180 | babel: "1.0.0" 181 | }, 182 | devDependencies: { 183 | eslint: "1.0.0" 184 | } 185 | }; 186 | const files = [{ 187 | created: false, 188 | path: ".npmignore" 189 | }]; 190 | 191 | sandbox.stub(fileUtils, "writePackage").returns(Promise.resolve()); 192 | sandbox.stub(packageUtils, "updateDependencies"); 193 | sandbox.stub(packageUtils, "updateMeta"); 194 | sandbox.stub(packageUtils, "updateScripts"); 195 | 196 | return fileHandler.overwritePackage(packageJSON, files).then(() => { 197 | expect(packageUtils.updateDependencies) 198 | .to.have.callCount(1).and 199 | .to.have.been.calledWith(packageJSON); 200 | expect(packageUtils.updateMeta) 201 | .to.have.callCount(1).and 202 | .to.have.been.calledWith(packageJSON, files); 203 | expect(packageUtils.updateScripts) 204 | .to.have.callCount(1).and 205 | .to.have.been.calledWith(packageJSON); 206 | expect(fileUtils.writePackage) 207 | .to.have.callCount(1).and 208 | .to.have.been.calledWith({ 209 | dependencies: { 210 | babel: "1.0.0" 211 | }, 212 | devDependencies: { 213 | eslint: "1.0.0" 214 | } 215 | }); 216 | }); 217 | }); 218 | }); 219 | }); 220 | -------------------------------------------------------------------------------- /test/spec/file-utils.spec.js: -------------------------------------------------------------------------------- 1 | import { Promise } from "es6-promise"; 2 | import fileUtils from "file-utils"; 3 | import mockfs from "mock-fs"; 4 | import testHelpers from "../test-helpers"; 5 | 6 | 7 | describe("fileUtils", () => { 8 | afterEach(() => { 9 | mockfs.restore(); 10 | }); 11 | 12 | describe("readFiles", () => { 13 | it("should append contents to files", () => { 14 | const files = [{ 15 | oldPath: "file-1.js" 16 | }, { 17 | oldPath: "file-2.js" 18 | }]; 19 | 20 | mockfs({ 21 | "file-1.js": "mock contents file 1", 22 | "file-2.js": "mock contents file 2" 23 | }); 24 | 25 | return fileUtils.readFiles(files).then((result) => { 26 | expect(result).to.deep.equal([{ 27 | contents: "mock contents file 1", 28 | oldPath: "file-1.js" 29 | }, { 30 | contents: "mock contents file 2", 31 | oldPath: "file-2.js" 32 | }]); 33 | }); 34 | }); 35 | 36 | it("should reject on read error", () => { 37 | const files = [{ 38 | oldPath: "file-1.js" 39 | }]; 40 | 41 | mockfs({}); 42 | 43 | return fileUtils.readFiles(files).catch((err) => { 44 | expect(err).to.have.property("code", "ENOENT"); 45 | }); 46 | }); 47 | }); 48 | 49 | describe("readPackage", () => { 50 | it("should read the package.json file", () => { 51 | mockfs({ 52 | "package.json": JSON.stringify({ 53 | dependencies: { 54 | lodash: "1.0.0" 55 | } 56 | }, null, 2) 57 | }); 58 | 59 | return fileUtils.readPackage().then((contents) => { 60 | expect(contents).to.deep.equal({ 61 | dependencies: { 62 | lodash: "1.0.0" 63 | } 64 | }); 65 | }); 66 | }); 67 | 68 | it("should reject on a JSON parse error", () => { 69 | mockfs({ 70 | "package.json": "bad" 71 | }); 72 | 73 | return fileUtils.readPackage().catch((err) => { 74 | expect(err).to.be.an.instanceOf(SyntaxError); 75 | }); 76 | }); 77 | 78 | it("should reject on read error", () => { 79 | mockfs({}); 80 | 81 | return fileUtils.readPackage().catch((err) => { 82 | expect(err).to.have.property("code", "ENOENT"); 83 | }); 84 | }); 85 | }); 86 | 87 | describe("removeFile", () => { 88 | it("should not throw without a remove file error", () => { 89 | mockfs({ 90 | "remove.js": "mock contents" 91 | }); 92 | 93 | return fileUtils 94 | .removeFile("remove.js") 95 | .then(() => testHelpers.fileExists("remove.js")) 96 | .then((fileExists) => { 97 | expect(fileExists).to.equal(false); 98 | }); 99 | }); 100 | 101 | it("should reject on an error", () => { 102 | mockfs({}); 103 | 104 | return fileUtils.removeFile("remove.js").catch((err) => { 105 | expect(err).to.have.property("code", "ENOENT"); 106 | }); 107 | }); 108 | }); 109 | 110 | describe("statFiles", () => { 111 | it("should mark files created or not created", () => { 112 | const files = [{ 113 | newPath: "new-file.js" 114 | }, { 115 | newPath: "existing-file.js" 116 | }]; 117 | 118 | mockfs({ 119 | "existing-file.js": "mock contents" 120 | }); 121 | 122 | return fileUtils.statFiles(files).then((result) => { 123 | expect(result).to.deep.equal([{ 124 | newPath: "new-file.js", 125 | created: true 126 | }, { 127 | newPath: "existing-file.js", 128 | created: false 129 | }]); 130 | }); 131 | }); 132 | 133 | it("should reject on other stat errors", () => { 134 | const files = [{ 135 | newPath: "private/existing-file.js" 136 | }]; 137 | 138 | mockfs({ 139 | "private": mockfs.directory({ 140 | items: { 141 | "existing-file.js": "mock contents" 142 | }, 143 | mode: 0 144 | }) 145 | }); 146 | 147 | return fileUtils.statFiles(files).catch((err) => { 148 | expect(err).to.have.property("code", "EACCES"); 149 | }); 150 | }); 151 | }); 152 | 153 | describe("writeFiles", () => { 154 | it("should write and mark files", () => { 155 | const files = [{ 156 | contents: "new contents 1", 157 | newPath: "file-1.js" 158 | }, { 159 | contents: "new contents 2", 160 | newPath: "file-2.js" 161 | }]; 162 | 163 | mockfs({ 164 | "file-1.js": "old contents 1" 165 | }); 166 | 167 | return fileUtils 168 | .writeFiles(files) 169 | .then((result) => Promise.all([ 170 | Promise.resolve(result), 171 | fileUtils.readFile(files[0].newPath), 172 | fileUtils.readFile(files[1].newPath) 173 | ])) 174 | .then((results) => { 175 | expect(results[0]).to.deep.equal([{ 176 | contents: "new contents 1", 177 | newPath: "file-1.js", 178 | written: true 179 | }, { 180 | contents: "new contents 2", 181 | newPath: "file-2.js", 182 | written: true 183 | }]); 184 | 185 | expect(results[1]).to.equal("new contents 1"); 186 | expect(results[2]).to.equal("new contents 2"); 187 | }); 188 | }); 189 | 190 | it("should reject on write error", () => { 191 | const files = [{ 192 | contents: "new mock contents", 193 | newPath: "file-1.js" 194 | }]; 195 | 196 | mockfs({ 197 | "file-1.js": mockfs.file({ 198 | mode: 0 199 | }) 200 | }); 201 | 202 | return fileUtils.writeFiles(files).catch((err) => { 203 | expect(err).to.have.property("code", "EACCES"); 204 | }); 205 | }); 206 | }); 207 | 208 | describe("writePackage", () => { 209 | it("should write the package.json file", () => { 210 | mockfs({ 211 | "package.json": JSON.stringify({ 212 | old: "contents" 213 | }) 214 | }); 215 | 216 | return fileUtils 217 | .writePackage({ 218 | dependencies: { 219 | lodash: "1.0.0" 220 | } 221 | }) 222 | .then(() => fileUtils.readFile("package.json")) 223 | .then((result) => expect(result).to.equal(JSON.stringify({ 224 | dependencies: { 225 | lodash: "1.0.0" 226 | } 227 | }, null, 2))); 228 | }); 229 | 230 | it("should reject on a stringify error", () => { 231 | const packageJSON = {}; 232 | 233 | packageJSON.packageJSON = packageJSON; // Create a circular structure 234 | 235 | return fileUtils.writePackage(packageJSON).catch((err) => { 236 | expect(err).to.be.an.instanceOf(TypeError); 237 | }); 238 | }); 239 | 240 | it("should reject on write error", () => { 241 | mockfs({ 242 | "package.json": mockfs.file({ 243 | mode: 0 244 | }) 245 | }); 246 | 247 | return fileUtils.writePackage().catch((err) => { 248 | expect(err).to.have.property("code", "EACCES"); 249 | }); 250 | }); 251 | }); 252 | }); 253 | -------------------------------------------------------------------------------- /test/spec/git.spec.js: -------------------------------------------------------------------------------- 1 | import childProcess from "child_process"; 2 | import git from "git"; 3 | import sinon from "sinon"; 4 | 5 | 6 | describe("git", () => { 7 | let sandbox; 8 | 9 | beforeEach(() => { 10 | sandbox = sinon.sandbox.create(); 11 | }); 12 | 13 | afterEach(() => { 14 | sandbox.restore(); 15 | git.disableDry(); 16 | }); 17 | 18 | describe("checkout", () => { 19 | it("should exec git checkout on file", () => { 20 | sandbox.stub(childProcess, "exec", (filePath, cb) => cb(null, "mock stdout")); 21 | 22 | return git.checkout("checkout.js").then((stdout) => { 23 | expect(childProcess.exec) 24 | .to.have.callCount(1).and 25 | .to.have.been.calledWith("git checkout checkout.js"); 26 | expect(stdout).to.equal("mock stdout"); 27 | }); 28 | }); 29 | 30 | it("should reject on an error", () => { 31 | sandbox.stub(childProcess, "exec", (filePath, cb) => cb("mock error")); 32 | 33 | return git.checkout("checkout.js").catch((err) => { 34 | expect(childProcess.exec) 35 | .to.have.callCount(1).and 36 | .to.have.been.calledWith("git checkout checkout.js"); 37 | expect(err).to.equal("mock error"); 38 | }); 39 | }); 40 | 41 | it("should exec git status while dry", () => { 42 | sandbox.stub(childProcess, "exec", (filePath, cb) => cb(null)); 43 | 44 | git.enableDry(); 45 | 46 | return git.checkout("checkout.js").then(() => { 47 | expect(childProcess.exec) 48 | .to.have.callCount(1).and 49 | .to.have.been.calledWith("git status"); 50 | }); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/spec/logger.spec.js: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | import logger from "logger"; 3 | import logSymbols from "log-symbols"; 4 | import sinon from "sinon"; 5 | 6 | 7 | describe("logger", () => { 8 | let sandbox; 9 | 10 | beforeEach(() => { 11 | sandbox = sinon.sandbox.create(); 12 | }); 13 | 14 | afterEach(() => { 15 | sandbox.restore(); 16 | }); 17 | 18 | it("should log an fail message with an error", () => { 19 | sandbox.stub(logger, "log"); 20 | 21 | logger.fail("mock message", { message: "mock error message" }); 22 | expect(logger.log) 23 | .to.have.callCount(2).and 24 | .to.have.been.calledWith(`${logSymbols.error} ${chalk.gray("mock message")}`).and 25 | .to.have.been.calledWith(chalk.red("mock error message")); 26 | }); 27 | 28 | it("should log a fail message without an error", () => { 29 | sandbox.stub(logger, "log"); 30 | 31 | logger.fail("mock message"); 32 | expect(logger.log) 33 | .to.have.callCount(1).and 34 | .to.have.been.calledWith(`${logSymbols.error} ${chalk.gray("mock message")}`); 35 | }); 36 | 37 | it("should log pass", () => { 38 | sandbox.stub(logger, "log"); 39 | 40 | logger.pass("mock message"); 41 | expect(logger.log) 42 | .to.have.callCount(1).and 43 | .to.have.been.calledWith(`${logSymbols.success} ${chalk.gray("mock message")}`); 44 | }); 45 | 46 | it("should log info", () => { 47 | sandbox.stub(logger, "log"); 48 | 49 | logger.info("mock message 1", "mock message 2"); 50 | expect(logger.log) 51 | .to.have.callCount(1).and 52 | .to.have.been.calledWith("mock message 1", "mock message 2"); 53 | }); 54 | 55 | it("should log when enabled", () => { 56 | sandbox.stub(console, "log"); 57 | 58 | logger.enable(); 59 | logger.log("mock message"); 60 | expect(console.log) // eslint-disable-line no-console 61 | .to.have.callCount(1).and 62 | .to.have.been.calledWith("mock message"); 63 | 64 | logger.disable(); 65 | }); 66 | 67 | it("should not log when disabled", () => { 68 | sandbox.stub(console, "log"); 69 | 70 | logger.log("mock message"); 71 | expect(console.log).to.have.callCount(0); // eslint-disable-line no-console 72 | 73 | logger.disable(); 74 | }); 75 | 76 | it("should error when enabled", () => { 77 | sandbox.stub(console, "error"); 78 | 79 | logger.enable(); 80 | logger.error("mock message"); 81 | expect(console.error) // eslint-disable-line no-console 82 | .to.have.callCount(1).and 83 | .to.have.been.calledWith(chalk.red("mock message")); 84 | 85 | logger.disable(); 86 | }); 87 | 88 | it("should not error when disabled", () => { 89 | sandbox.stub(console, "error"); 90 | 91 | logger.error("mock message"); 92 | expect(console.error).to.have.callCount(0); // eslint-disable-line no-console 93 | 94 | logger.disable(); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /test/spec/package-utils.spec.js: -------------------------------------------------------------------------------- 1 | import packageUtils from "package-utils"; 2 | 3 | 4 | describe("packageUtils", () => { 5 | describe("updateDependencies", () => { 6 | it("should move dependencies to devDependencies", () => { 7 | const packageJSON = { 8 | dependencies: { 9 | babel: "1.0.0", 10 | "babel-core": "1.0.0", 11 | "lodash": "1.0.0" 12 | }, 13 | devDependencies: { 14 | eslint: "1.0.0" 15 | }, 16 | publishr: { 17 | dependencies: ["^babel"] 18 | } 19 | }; 20 | 21 | packageUtils.updateDependencies(packageJSON); 22 | 23 | expect(packageJSON).to.deep.equal({ 24 | dependencies: { 25 | lodash: "1.0.0" 26 | }, 27 | devDependencies: { 28 | babel: "1.0.0", 29 | "babel-core": "1.0.0", 30 | eslint: "1.0.0" 31 | }, 32 | publishr: { 33 | dependencies: ["^babel"] 34 | } 35 | }); 36 | }); 37 | 38 | it("should handle no dependencies", () => { 39 | const packageJSON = { 40 | devDependencies: { 41 | eslint: "1.0.0" 42 | }, 43 | publishr: { 44 | dependencies: ["^babel"] 45 | } 46 | }; 47 | 48 | packageUtils.updateDependencies(packageJSON); 49 | 50 | expect(packageJSON).to.deep.equal({ 51 | dependencies: {}, 52 | devDependencies: { 53 | eslint: "1.0.0" 54 | }, 55 | publishr: { 56 | dependencies: ["^babel"] 57 | } 58 | }); 59 | }); 60 | 61 | it("should handle no devDependencies", () => { 62 | const packageJSON = { 63 | dependencies: { 64 | lodash: "1.0.0" 65 | }, 66 | publishr: { 67 | dependencies: ["^babel"] 68 | } 69 | }; 70 | 71 | packageUtils.updateDependencies(packageJSON); 72 | 73 | expect(packageJSON).to.deep.equal({ 74 | dependencies: { 75 | lodash: "1.0.0" 76 | }, 77 | devDependencies: {}, 78 | publishr: { 79 | dependencies: ["^babel"] 80 | } 81 | }); 82 | }); 83 | 84 | it("should handle no publishr", () => { 85 | const packageJSON = { 86 | dependencies: { 87 | lodash: "1.0.0" 88 | } 89 | }; 90 | 91 | packageUtils.updateDependencies(packageJSON); 92 | 93 | expect(packageJSON).to.deep.equal({ 94 | dependencies: { 95 | lodash: "1.0.0" 96 | } 97 | }); 98 | }); 99 | }); 100 | 101 | describe("updateMeta", () => { 102 | it("should add meta data", () => { 103 | const packageJSON = {}; 104 | const files = [{ 105 | created: true, 106 | newPath: "file.js" 107 | }]; 108 | 109 | packageUtils.updateMeta(packageJSON, files); 110 | 111 | expect(packageJSON).to.deep.equal({ 112 | _publishr: [{ 113 | created: true, 114 | path: "file.js" 115 | }, { 116 | created: false, 117 | path: "package.json" 118 | }] 119 | }); 120 | }); 121 | }); 122 | 123 | describe("updateScripts", () => { 124 | it("should handle no publishr", () => { 125 | const packageJSON = { 126 | scripts: { 127 | "test": "mocha" 128 | } 129 | }; 130 | 131 | packageUtils.updateScripts(packageJSON); 132 | 133 | expect(packageJSON).to.deep.equal({ 134 | scripts: { 135 | "test": "mocha" 136 | } 137 | }); 138 | }); 139 | 140 | it("should handle no scripts", () => { 141 | const packageJSON = { 142 | publishr: { 143 | "dependencies": "^babel" 144 | } 145 | }; 146 | 147 | packageUtils.updateScripts(packageJSON); 148 | 149 | expect(packageJSON).to.deep.equal({ 150 | publishr: { 151 | "dependencies": "^babel" 152 | } 153 | }); 154 | }); 155 | 156 | it("should update scripts", () => { 157 | const packageJSON = { 158 | publishr: { 159 | "scripts": { 160 | "added": "echo 'added'", 161 | "cool": "echo 'cool'", 162 | "postinstall": "" 163 | } 164 | }, 165 | scripts: { 166 | cool: "echo 'not cool'", 167 | postinstall: "npm run build", 168 | test: "mocha" 169 | } 170 | }; 171 | 172 | packageUtils.updateScripts(packageJSON); 173 | 174 | expect(packageJSON).to.deep.equal({ 175 | publishr: { 176 | "scripts": { 177 | "added": "echo 'added'", 178 | "postinstall": "", 179 | "cool": "echo 'cool'" 180 | } 181 | }, 182 | scripts: { 183 | added: "echo 'added'", 184 | cool: "echo 'cool'", 185 | test: "mocha" 186 | } 187 | }); 188 | }); 189 | }); 190 | }); 191 | -------------------------------------------------------------------------------- /test/spec/postpublish.spec.js: -------------------------------------------------------------------------------- 1 | import { Promise } from "es6-promise"; 2 | import fileUtils from "file-utils"; 3 | import fileHandler from "file-handler"; 4 | import postpublish from "postpublish"; 5 | import sinon from "sinon"; 6 | 7 | 8 | describe("postpublish", () => { 9 | let sandbox; 10 | 11 | beforeEach(() => { 12 | sandbox = sinon.sandbox.create(); 13 | }); 14 | 15 | afterEach(() => { 16 | sandbox.restore(); 17 | }); 18 | 19 | it("should fix files", () => { 20 | const contents = { 21 | _publishr: [{ 22 | created: true, 23 | path: "file.js" 24 | }] 25 | }; 26 | 27 | sandbox.stub(fileUtils, "readPackage").returns(Promise.resolve(contents)); 28 | sandbox.stub(fileHandler, "fixFiles"); 29 | 30 | return postpublish.run().then(() => { 31 | expect(fileUtils.readPackage).to.have.callCount(1); 32 | expect(fileHandler.fixFiles) 33 | .to.have.callCount(1).and 34 | .to.have.been.calledWith({ 35 | _publishr: [{ 36 | created: true, 37 | path: "file.js" 38 | }] 39 | }); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/spec/postversion.spec.js: -------------------------------------------------------------------------------- 1 | import { Promise } from "es6-promise"; 2 | import fileHandler from "file-handler"; 3 | import fileUtils from "file-utils"; 4 | import postversion from "postversion"; 5 | import sinon from "sinon"; 6 | 7 | 8 | describe("postversion", () => { 9 | let sandbox; 10 | 11 | beforeEach(() => { 12 | sandbox = sinon.sandbox.create(); 13 | }); 14 | 15 | it("should overwrite files", () => { 16 | const contents = { 17 | publishr: { 18 | dependencies: ["^babel"] 19 | } 20 | }; 21 | const files = [{ newPath: "file.js" }]; 22 | 23 | sandbox.stub(fileHandler, "overwriteFiles").returns(Promise.resolve(files)); 24 | sandbox.stub(fileUtils, "readPackage").returns(Promise.resolve(contents)); 25 | 26 | return postversion.run().then(() => { 27 | expect(fileUtils.readPackage).to.have.callCount(1); 28 | expect(fileHandler.overwriteFiles) 29 | .to.have.callCount(1).and 30 | .to.have.been.calledWith({ 31 | publishr: { 32 | dependencies: ["^babel"] 33 | } 34 | }); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/test-helpers.js: -------------------------------------------------------------------------------- 1 | import { Promise } from "es6-promise"; 2 | import fs from "fs"; 3 | 4 | 5 | const testHelpers = { 6 | fileExists(filePath) { 7 | return new Promise((resolve, reject) => { 8 | fs.stat(filePath, (err) => { 9 | if (err && err.code === "ENOENT") { 10 | resolve(false); 11 | } else if (!err) { 12 | resolve(true); 13 | } else { 14 | reject(err); 15 | } 16 | }); 17 | }); 18 | } 19 | }; 20 | 21 | export default testHelpers; 22 | --------------------------------------------------------------------------------