├── .gitignore ├── LICENSE ├── README.md ├── bin ├── install.js └── validate.sh ├── index.js ├── lib └── utils.js ├── package.json └── test ├── utils.js └── validate.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage.* 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nathan LaFreniere 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 | # git-validate 2 | 3 | This is a super simple framework to facilitate creating your own modules similar to [precommit-hook](https://github.com/nlf/precommit-hook). 4 | 5 | ## Usage 6 | 7 | This module isn't intended to be used directly in your projects (thought it can be), but rather as the dependency of a module that you create that will act as a template of sorts. 8 | 9 | To create a validate module, first make a new directory and use `npm init` to initialize your module: 10 | 11 | ```bash 12 | mkdir validate-nlf 13 | cd validate-nlf 14 | npm init 15 | ``` 16 | 17 | Follow the prompts, and when complete install this module: 18 | 19 | ```bash 20 | npm install --save git-validate 21 | ``` 22 | 23 | Now, let's say we want to provide a default `.jshintrc` file, let's go ahead and create that file in our new directory and fill it with some options: 24 | 25 | ```bash 26 | vim jshintrc 27 | ``` 28 | 29 | ```javascript 30 | { 31 | "node": true, 32 | 33 | "curly": true, 34 | "latedef": true, 35 | "quotmark": true, 36 | "undef": true, 37 | "unused": true, 38 | "trailing": true 39 | } 40 | ``` 41 | 42 | Note that we saved the file as `jshintrc` without the leading dot. 43 | 44 | Next, let's create our install script: 45 | 46 | ```bash 47 | vim install.js 48 | ``` 49 | 50 | ```javascript 51 | var Validate = require('git-validate'); 52 | 53 | Validate.copy('jshintrc', '.jshintrc'); 54 | ``` 55 | 56 | This instructs **git-validate** to copy the `jshintrc` file in our module to `.jshintrc` in the root of the project that installs it. 57 | 58 | Now we edit our `package.json` to tell it about our install script: 59 | 60 | ```javascript 61 | "scripts": { 62 | "install": "node install.js" 63 | } 64 | ``` 65 | 66 | And that's it for the simplest possible example. Now anytime you install `validate-nlf` you'll automatically get a `.jshintrc` file in your project. 67 | 68 | This wouldn't be any fun without the git hooks though, so let's extend it a bit further to make sure that `jshint` is run any time a user tries to `git commit` after installing our module. We can do that by configuring the hook in our install script like so: 69 | 70 | ```javascript 71 | Validate.installScript('lint', 'jshint .'); 72 | Validate.configureHook('pre-commit', ['lint']); 73 | ``` 74 | 75 | Great, that's it! 76 | 77 | Now when a user installs your package the `installScript` method will see if they already have a script in their package.json named `lint`, if they do not it will add one that runs `"jshint ."`. The second line will also check their package.json for a `pre-commit` key, which is used to configure that specific git hook. If the key does not exist, it will be added with the value `["lint"]` telling git-validate to run the "lint" script on `pre-commit`. 78 | 79 | 80 | ## The Details 81 | 82 | **git-validate** exports a few methods to be used for creating your custom hooks. 83 | 84 | ### `copy` 85 | 86 | Copy a file or directory from your hook to a target project. 87 | 88 | ```javascript 89 | Validate.copy(source, target, options); 90 | ``` 91 | 92 | Where `source` is a path relative to your install script, and `target` is a path relative to the root of the project that is installing the module. For example if my module has the layout: 93 | 94 | ``` 95 | bin/install 96 | jshintrc 97 | ``` 98 | 99 | And I wish for the file `jshintrc` to be placed in the root of projects as `.jshintrc` when running `bin/install`, I would call `Validate.copy('../jshintrc', '.jshintrc')`. 100 | 101 | Note that `source` may be a file *or* a directory. If a directory is specified than a new directory will be created at `target` and the *full contents* of source will be copied to the `target` directory recursively. 102 | 103 | The only `option` currently available is `overwrite`. When set to `true` overwrite will *always* copy the given file, overwriting any existing destination file. If this is not set, `copy` will instead silently fail and leave the old file in place. 104 | 105 | 106 | ### `installHooks` 107 | 108 | Install one or more git hooks to the current repo. 109 | 110 | ```javascript 111 | Validate.installHooks('pre-commit'); 112 | Validate.installHooks(['pre-commit', 'pre-push']); 113 | ``` 114 | 115 | This method will copy the hook script to the appropriate path in your repo's `.git/hooks` path. 116 | 117 | ### `configureHook` 118 | 119 | Provide a default configuration for a given hook. 120 | 121 | ```javascript 122 | Validate.configureHook('pre-commit', ['lint', 'test']); 123 | ``` 124 | 125 | would write 126 | 127 | ```javascript 128 | { 129 | "pre-commit": ["lint", "test"] 130 | } 131 | ``` 132 | 133 | to your package.json, but *only* if the `"pre-commit"` key was not already set, or you specify so explicitly: 134 | 135 | ```javascript 136 | { 137 | "pre-commit": ["test"] 138 | } 139 | ``` 140 | 141 | with: 142 | 143 | ```javascript 144 | var overwrite = true; 145 | Validate.configureHook('pre-commit', ['lint', 'test'], overwrite); 146 | ``` 147 | 148 | would change package.json to: 149 | 150 | ```javascript 151 | { 152 | "pre-commit": ["lint", "test"] 153 | } 154 | ``` 155 | 156 | 157 | ### `installScript` 158 | 159 | Configure a script (if it is not already configured) for the project via package.json. 160 | 161 | ```javascript 162 | Validate.installScript('test', 'lab -a code'); 163 | ``` 164 | 165 | would write 166 | 167 | ```javascript 168 | { 169 | "scripts": { 170 | "test": "lab -a code" 171 | } 172 | } 173 | ``` 174 | 175 | to your package.json. If the `"test"` script was already defined, this method will do nothing. 176 | 177 | 178 | ## Configuration 179 | 180 | In addition to the `scripts` property, your package.json file will be parsed and checked for keys matching the name of your git hooks (e.g. `pre-commit`, `pre-push`, etc) and used to provide a list of hooks to be run for each hook. The keys must be an array of script names to be run. If any of the scripts are not defined, they will be skipped and a message will be printed showing that no script was found. 181 | 182 | ### per-branch hooks 183 | 184 | It is possible to run scripts only for a specific branch by specifying the key in your `package.json` as `hook-name#branch`: 185 | 186 | ```javascript 187 | { 188 | "pre-commit": ["lint", "test"], 189 | "pre-commit#dev": ["lint"] 190 | } 191 | ``` 192 | 193 | In the above example, when run in the `dev` branch only the `lint` script will be run, however in all other branches both `lint` and `test` will be run. 194 | -------------------------------------------------------------------------------- /bin/install.js: -------------------------------------------------------------------------------- 1 | var Utils = require('../lib/utils'); 2 | 3 | Utils.installHooks(['pre-commit', 'pre-push']); 4 | -------------------------------------------------------------------------------- /bin/validate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Get the exported path from the default login shell, and append it to the 4 | # path from the environment. We ignore the stderr because there are 5 | # potential errors and all we care about is the PATH. 6 | LOGIN_PATH="$(bash -lc 'echo "$PATH"' 2>/dev/null)" 7 | if [ "$LOGIN_PATH" != '' ]; then 8 | PATH="$PATH:$LOGIN_PATH" 9 | fi 10 | 11 | # This section copied from github.com/dominictarr/JSON.sh and simplified a bit 12 | ############################################################################## 13 | throw() { 14 | echo "$*" >&2 15 | exit 1 16 | } 17 | 18 | awk_egrep() { 19 | local pattern_string=$1 20 | gawk '{ 21 | while ($0) { 22 | start=match($0,pattern); 23 | token=substr($0,start,RLENGTH); 24 | print token; 25 | $0=substr($0,start+RLENGTH); 26 | } 27 | }' pattern=$pattern_string 28 | } 29 | 30 | tokenize() { 31 | local GREP 32 | local ESCAPE 33 | local CHAR 34 | 35 | if echo "test string" | grep -ao --color=never "test" &>/dev/null; then 36 | GREP="egrep -ao --color=never" 37 | else 38 | GREP="egrep -ao" 39 | fi 40 | 41 | if echo "test string" | egrep -o "test" &>/dev/null; then 42 | ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' 43 | CHAR='[^[:cntrl:]"\\]' 44 | else 45 | GREP=awk_egrep 46 | ESCAPE='(\\\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' 47 | CHAR='[^[:cntrl:]"\\\\]' 48 | fi 49 | 50 | local STRING="\"$CHAR*($ESCAPE$CHAR*)*\"" 51 | local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?' 52 | local KEYWORD='null|false|true' 53 | local SPACE='[[:space:]]+' 54 | 55 | sed 's/\\r//' | $GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | egrep -v "^$SPACE$" 56 | } 57 | 58 | parse_array() { 59 | local index=0 60 | local ary='' 61 | 62 | read -r token 63 | case "$token" in 64 | ']') 65 | ;; 66 | *) 67 | while : 68 | do 69 | parse_value "$1" "$index" 70 | index=$((index+1)) 71 | ary="$ary""$value" 72 | read -r token 73 | 74 | case "$token" in 75 | ']') 76 | break 77 | ;; 78 | ',') 79 | ary="$ary," 80 | ;; 81 | *) 82 | throw "EXPECTED , or ] GOT ${token:-EOF}" 83 | ;; 84 | esac 85 | read -r token 86 | done 87 | ;; 88 | esac 89 | value= 90 | : 91 | } 92 | 93 | parse_object() { 94 | local key 95 | local obj='' 96 | read -r token 97 | 98 | case "$token" in 99 | '}') 100 | ;; 101 | *) 102 | while : 103 | do 104 | case "$token" in 105 | '"'*'"') 106 | key=$token 107 | ;; 108 | *) 109 | throw "EXPECTED string GOT ${token:-EOF}" 110 | ;; 111 | esac 112 | 113 | read -r token 114 | case "$token" in 115 | ':') 116 | ;; 117 | *) 118 | throw "EXPECTED : GOT ${token:-EOF}" 119 | ;; 120 | esac 121 | 122 | read -r token 123 | parse_value "$1" "$key" 124 | obj="$obj$key:$value" 125 | 126 | read -r token 127 | case "$token" in 128 | '}') 129 | break 130 | ;; 131 | ',') 132 | obj="$obj," 133 | ;; 134 | *) 135 | throw "EXPECTED , or } GOT ${token:-EOF}" 136 | ;; 137 | esac 138 | 139 | read -r token 140 | done 141 | ;; 142 | esac 143 | value= 144 | 145 | : 146 | } 147 | 148 | parse_value() { 149 | local jpath="${1:+$1,}$2" 150 | local isleaf=0 151 | local isempty=0 152 | 153 | case "$token" in 154 | '{') 155 | parse_object "$jpath" 156 | ;; 157 | '[') 158 | parse_array "$jpath" 159 | ;; 160 | ''|[!0-9]) 161 | throw "EXPECTED value GOT ${token:-EOF}" 162 | ;; 163 | *) 164 | value=$token 165 | isleaf=1 166 | [ "$value" = '""' ] && isempty=1 167 | ;; 168 | esac 169 | 170 | [ "$value" = '' ] && return 171 | [ "$isleaf" -eq 1 ] && [ $isempty -eq 0 ] && printf "[%s]\t%s\n" "$jpath" "$value" 172 | 173 | : 174 | } 175 | 176 | parse() { 177 | read -r token 178 | parse_value 179 | 180 | read -r token 181 | case "$token" in 182 | '') 183 | ;; 184 | *) 185 | throw "EXPECTED EOF GOT $token" 186 | ;; 187 | esac 188 | } 189 | 190 | parse_json() { 191 | tokenize | parse 192 | } 193 | ############################################################################## 194 | # End of copied code 195 | 196 | # Search parsed JSON for a named script, return the script as a string 197 | find_script() { 198 | local json=$1 199 | local name=$2 200 | local script="" 201 | local oifs="${IFS}" 202 | 203 | IFS=$'\n' 204 | 205 | for line in $json; do 206 | IFS="${oifs}" 207 | 208 | echo "$line" | grep '\["scripts"' 2>&1 >/dev/null 209 | if [[ $? -eq 0 ]]; then 210 | local script_name="" 211 | local match=$(echo $line | sed -n 's/\["scripts","\([^"]*\)"\] "\(.*\)"/\1 \2/p') 212 | 213 | IFS=' ' read -r script_name string <<< $match 214 | if [[ "$script_name" == "$name" ]]; then 215 | script=${match:${#script_name}+1} 216 | break 217 | fi 218 | fi 219 | 220 | IFS=$'\n' 221 | done 222 | 223 | IFS="${oifs}" 224 | 225 | echo "$script" 226 | } 227 | 228 | # Run the named script, searches both package.json and .validate.json 229 | run_script() { 230 | local json=$1 231 | local name=$2 232 | 233 | echo -n "running $name..." 234 | 235 | local script=$(find_script "$json" "$name") 236 | 237 | if [[ "$script" == "" ]]; then 238 | echo "not found!" 239 | return 0 240 | fi 241 | 242 | local output="" 243 | output=$(PATH=./node_modules/.bin:$PATH eval $script 2>&1) 244 | local result=$? 245 | if [[ $result -ne 0 ]]; then 246 | echo "failed!" 247 | echo "$output" 248 | else 249 | echo "passed!" 250 | fi 251 | return $result 252 | } 253 | 254 | find_commands() { 255 | local json=$1 256 | local branch=$2 257 | 258 | _find_commands() { 259 | local cmd=$1 260 | local commands="" 261 | local oifs="${IFS}" 262 | IFS=$'\n' 263 | 264 | for line in $json; do 265 | IFS="${oifs}" 266 | 267 | echo "$line" | grep "\[\"$cmd\"" 2>&1 >/dev/null 268 | if [[ $? -eq 0 ]]; then 269 | local match=$(echo $line | sed -n "s/\[\"$cmd\",[0-9]*\] \"\([^\"]*\)\"/\1/p") 270 | commands="$commands $match" 271 | fi 272 | 273 | IFS=$'\n' 274 | done 275 | IFS="${oifs}" 276 | echo "$commands" 277 | } 278 | 279 | local commands=$(_find_commands "$hook_cmd#$branch") 280 | if [[ "$commands" == "" ]]; then 281 | commands=$(_find_commands "$hook_cmd") 282 | fi 283 | 284 | echo "$commands" 285 | } 286 | 287 | # Finds the appropriate commands to run for a project and runs them 288 | check_project() { 289 | local package=$1 290 | local dir=$(dirname "$package") 291 | local json; json=$(cat "$package" | parse_json) 292 | if [[ $? -ne 0 ]]; then 293 | echo "failed parsing $package.. exiting" 294 | exit 1 295 | fi 296 | 297 | pushd "$dir" >/dev/null 298 | local branch; 299 | if [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then 300 | branch=$(git rev-parse --abbrev-ref HEAD 2>&1) 301 | else 302 | branch=$(env -i git rev-parse --abbrev-ref HEAD 2>&1) 303 | fi 304 | if [[ $? -ne 0 ]]; then 305 | popd >/dev/null 306 | return 0 307 | fi 308 | 309 | local commands=$(find_commands "$json" "$branch") 310 | 311 | if [[ "$commands" == "" ]]; then 312 | popd >/dev/null 313 | return 0 314 | fi 315 | 316 | for cmd in $commands; do 317 | run_script "$json" "$cmd" "$branch" 318 | local result=$? 319 | [[ $result -ne 0 ]] && break 320 | done 321 | 322 | popd >/dev/null 323 | 324 | return $result 325 | } 326 | 327 | # Find all projects in the current repo and check each one 328 | run_hook() { 329 | # Guard to avoid running hooks when rebasing, just exit with success immediately 330 | if [[ $(git branch | grep '*' | sed 's/* //') == "(no branch)" ]]; then 331 | exit 0 332 | fi 333 | 334 | local hook_cmd=$(basename $0) 335 | local git_root; 336 | if [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then 337 | git_root=$(git rev-parse --show-toplevel) 338 | else 339 | git_root=$(env -i git rev-parse --show-toplevel) 340 | fi 341 | if [[ $? -ne 0 ]]; then 342 | echo "this does not look like a git repository.. exiting" 343 | exit 0 344 | fi 345 | 346 | pushd "$git_root" >/dev/null 347 | local projects; projects=$(find . -not -iwholename '*node_modules*' -not -iwholename '*bower_components*' -maxdepth 3 -name package.json -print 2>/dev/null) 348 | if [[ $? -ne 0 ]]; then 349 | projects=$(find . -not -ipath '*node_modules*' -not -ipath '*bower_components*' -maxdepth 3 -name package.json -print 2>/dev/null) 350 | fi 351 | projects=$(echo "$projects" | sed s/\.//) 352 | popd >/dev/null 353 | 354 | local result=0 355 | 356 | for project in $projects; do 357 | check_project "$git_root$project" 358 | result=$? 359 | [[ $result -ne 0 ]] && break 360 | done 361 | 362 | return $result 363 | } 364 | 365 | run_hook 366 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Utils = require('./lib/utils'); 2 | 3 | exports.copy = Utils.copy; 4 | exports.installHooks = Utils.installHooks; 5 | exports.installScript = Utils.installScript; 6 | exports.configureHook = Utils.configureHook; 7 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | var Fs = require('fs'); 2 | var Path = require('path'); 3 | 4 | var internals = {}; 5 | 6 | 7 | /* $lab:coverage:off$ */ 8 | // Coverage disabled for this method because we use a child process to test it due to the process.exit() call 9 | internals.throwWarn = function (warning) { 10 | 11 | console.error('WARNING: ' + warning + ', installation aborted.'); 12 | process.exit(0); 13 | }; 14 | /* $lab:coverage:on$ */ 15 | 16 | // Find the topmost parent of the given module. 17 | internals.findParent = function (mod) { 18 | 19 | return mod.parent ? internals.findParent(mod.parent) : mod; 20 | }; 21 | 22 | 23 | // Similar to mkdir -p, recursively creates directories until `path` exists 24 | internals.mkdir = function (path) { 25 | 26 | var mode = ~process.umask() & parseInt('777', 8); 27 | 28 | if (exports.isDir(path)) { 29 | return; 30 | } 31 | 32 | try { 33 | Fs.mkdirSync(path, mode); 34 | } 35 | catch (err) { 36 | if (err.code !== 'ENOENT') { 37 | throw err; 38 | } 39 | 40 | internals.mkdir(Path.dirname(path)); 41 | internals.mkdir(path); 42 | } 43 | }; 44 | 45 | 46 | // Expands source and target to absolute paths, then calls internals.copy 47 | exports.copy = function (source, target, options) { 48 | 49 | if (typeof target === 'object') { 50 | options = target; 51 | target = undefined; 52 | } 53 | 54 | options = options || {}; 55 | 56 | var root = Path.dirname(internals.findParent(module).filename); 57 | var projectRoot = exports.findProjectRoot(root); 58 | 59 | var sourcePath = Path.resolve(root, source); 60 | var targetPath = Path.resolve(projectRoot, target || source); 61 | 62 | if (targetPath.indexOf(projectRoot) !== 0) { 63 | throw new Error('Destination must be within project root'); 64 | } 65 | 66 | return internals.copy(sourcePath, targetPath, options); 67 | }; 68 | 69 | 70 | // Determine if source is a directory or a file and call the appropriate method 71 | internals.copy = function (source, target, options) { 72 | 73 | if (exports.isDir(source)) { 74 | internals.copyDirectory(source, target, options); 75 | } 76 | else { 77 | return internals.copyFile(source, target, options); 78 | } 79 | }; 80 | 81 | 82 | // Recursively copy a directory 83 | internals.copyDirectory = function (source, target, options) { 84 | 85 | internals.mkdir(target); 86 | 87 | var sources = Fs.readdirSync(source); 88 | for (var i = 0, l = sources.length; i < l; ++i) { 89 | var sourcePath = Path.join(source, sources[i]); 90 | var targetPath = Path.join(target, sources[i]); 91 | 92 | internals.copy(sourcePath, targetPath, options); 93 | } 94 | }; 95 | 96 | 97 | // Copy a single file 98 | internals.copyFile = function (source, target, options) { 99 | 100 | internals.mkdir(Path.dirname(target)); 101 | 102 | var mode = ~process.umask() & parseInt('666', 8); 103 | 104 | if (Fs.existsSync(target) && 105 | !options.overwrite) { 106 | 107 | return new Error(target + ' already exists'); 108 | } 109 | 110 | var sourceContent = ''; 111 | try { 112 | sourceContent = Fs.readFileSync(source); 113 | } catch (e) { 114 | /* no source to copy */ 115 | } 116 | 117 | Fs.writeFileSync(target, sourceContent, { flag: 'w', mode: mode }); 118 | }; 119 | 120 | 121 | // Given a path, determine if the path is a directory 122 | exports.isDir = function (path) { 123 | 124 | try { 125 | var stat = Fs.statSync(path); 126 | return stat.isDirectory(); 127 | } 128 | catch (e) { 129 | return false; 130 | } 131 | }; 132 | 133 | 134 | // Given a starting directory, find the root of a git repository. 135 | // In this case, the root is defined as the first directory that contains 136 | // a directory named ".git" 137 | // 138 | // Returns a string if found, otherwise undefined 139 | exports.findGitRoot = function (start) { 140 | 141 | start = start || Path.dirname(internals.findParent(module).filename); 142 | var root; 143 | 144 | if (exports.isDir(Path.join(start, '.git'))) { 145 | root = start; 146 | } 147 | /* $lab:coverage:off$ */ 148 | // Coverage disabled here due to false positive on else if, since we have to trap the throwWarn method 149 | else if (Path.dirname(start) !== start) { 150 | root = exports.findGitRoot(Path.dirname(start)); 151 | } 152 | else { 153 | return internals.throwWarn('Unable to find a .git directory for this project'); 154 | } 155 | /* $lab:coverage:on$ */ 156 | 157 | return root; 158 | }; 159 | 160 | 161 | // Given a starting directory, find the root of the current project. 162 | // The root of the project is defined as the topmost directory that is 163 | // *not* contained within a directory named "node_modules" that also 164 | // contains a file named "package.json" 165 | // 166 | // Returns a string 167 | exports.findProjectRoot = function (start) { 168 | 169 | start = start || Path.dirname(internals.findParent(module).filename); 170 | var position = start.indexOf('node_modules'); 171 | var root = start.slice(0, position === -1 ? undefined : position - Path.sep.length); 172 | /* $lab:coverage:off$ */ 173 | // Coverage disabled here due to having to trap the throwWarn method 174 | if (root === Path.resolve(root, '..')) { 175 | return internals.throwWarn('Unable to find a package.json for this project'); 176 | } 177 | /* $lab:coverage:on$ */ 178 | 179 | while (!Fs.existsSync(Path.join(root, 'package.json'))) { 180 | root = exports.findProjectRoot(Path.dirname(root)); 181 | } 182 | 183 | return root; 184 | }; 185 | 186 | 187 | // Given a root path, find a list of projects. 188 | // A project is defined as any directory within 4 levels of the starting 189 | // directory that contains a file named "package.json" 190 | // 191 | // Returns an array 192 | exports.findProjects = function (start, depth) { 193 | 194 | start = start || exports.findGitRoot(internals.findParent(module).filename); 195 | depth = depth || 0; 196 | ++depth; 197 | 198 | if (depth > 4 || 199 | !exports.isDir(start) || 200 | start.indexOf('node_modules') !== -1) { 201 | 202 | return []; 203 | } 204 | 205 | var dirs = Fs.readdirSync(start); 206 | var projects = []; 207 | 208 | if (Fs.existsSync(Path.join(start, 'package.json'))) { 209 | projects.push(start); 210 | } 211 | 212 | for (var i = 0, il = dirs.length; i < il; ++i) { 213 | var dir = dirs[i]; 214 | var path = Path.join(start, dir); 215 | 216 | if (Fs.existsSync(Path.join(path, 'package.json'))) { 217 | projects.push(path); 218 | } 219 | else { 220 | projects = projects.concat(exports.findProjects(path, depth)); 221 | } 222 | } 223 | 224 | return projects; 225 | }; 226 | 227 | // Install the git hook as specified by `hook`. 228 | // For example, Validate.installHook('pre-commit'); 229 | exports.installHooks = function (hooks, root) { 230 | 231 | hooks = Array.isArray(hooks) ? hooks : [hooks]; 232 | var gitRoot = exports.findGitRoot(root); 233 | var hookRoot = Path.join(gitRoot, '.git', 'hooks'); 234 | var source = Path.resolve(__dirname, '..', 'bin', 'validate.sh'); 235 | 236 | if (!exports.isDir(hookRoot)) { 237 | internals.mkdir(hookRoot); 238 | } 239 | 240 | for (var i = 0, il = hooks.length; i < il; ++i) { 241 | var hook = hooks[i]; 242 | var dest = Path.join(hookRoot, hook); 243 | 244 | if (Fs.existsSync(dest)) { 245 | Fs.renameSync(dest, dest + '.backup'); 246 | } else { 247 | // There is no harm in removing a non-existant file. 248 | // It could also be a broken symlink, which is also fine to be 249 | // removed. If it wasn't removed, writing the new hook would fail. 250 | try { 251 | Fs.unlinkSync(dest) 252 | } catch (err) { 253 | if (err.code !== 'ENOENT') { 254 | throw err; 255 | } 256 | } 257 | } 258 | 259 | Fs.writeFileSync(dest, Fs.readFileSync(source), { mode: 511 }); 260 | } 261 | }; 262 | 263 | // Provide a default configuration for a git hook as specified by `hook`. 264 | // For example, Validate.configureHook('pre-commit', ['test', 'lint']); 265 | exports.configureHook = function (hook, defaults, overwrite, root) { 266 | 267 | var packagePath = Path.join(exports.findProjectRoot(root), 'package.json'); 268 | var package = JSON.parse(Fs.readFileSync(packagePath, { encoding: 'utf8' })); 269 | 270 | if (!package.hasOwnProperty(hook) || overwrite) { 271 | package[hook] = Array.isArray(defaults) ? defaults : [defaults]; 272 | Fs.writeFileSync(packagePath, JSON.stringify(package, null, 2), { encoding: 'utf8' }); 273 | } 274 | }; 275 | 276 | // Configure a default script by name and content 277 | // For example, Validate.installScript('test', 'lab -a code -L'); 278 | // Or Validate.installScript('test', 'lab -a code -L', {overwrite: true}); to force updating 279 | exports.installScript = function (name, script, options, root) { 280 | 281 | if (typeof options === 'string') { 282 | root = options; 283 | options = null; 284 | } 285 | 286 | options = options || {}; 287 | 288 | var packagePath = Path.join(exports.findProjectRoot(root), 'package.json'); 289 | var package = JSON.parse(Fs.readFileSync(packagePath, { encoding: 'utf8' })); 290 | if (!package.hasOwnProperty('scripts') || 291 | !package.scripts.hasOwnProperty(name) || 292 | options.overwrite) { 293 | 294 | package.scripts = package.scripts || {}; 295 | package.scripts[name] = script; 296 | Fs.writeFileSync(packagePath, JSON.stringify(package, null, 2), { encoding: 'utf8' }); 297 | } 298 | }; 299 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "git-validate", 3 | "version": "2.2.4", 4 | "description": "the extensible core of precommit-hook", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "lab -L --leaks -a code -t 100", 8 | "install": "node bin/install" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/nlf/git-validate.git" 13 | }, 14 | "keywords": [ 15 | "precommit", 16 | "pre-commit", 17 | "git", 18 | "hook" 19 | ], 20 | "author": "Nathan LaFreniere ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/nlf/git-validate/issues" 24 | }, 25 | "homepage": "https://github.com/nlf/git-validate", 26 | "devDependencies": { 27 | "code": "1.x.x", 28 | "lab": "5.x.x", 29 | "mkdirp": "^0.5.0", 30 | "rimraf": "2.x.x" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | var ChildProcess = require('child_process'); 2 | var Utils = require('../lib/utils'); 3 | var Fs = require('fs'); 4 | var Path = require('path'); 5 | var Mkdirp = require('mkdirp'); 6 | var Rimraf = require('rimraf'); 7 | 8 | var Code = require('code'); 9 | var Lab = require('lab'); 10 | var lab = exports.lab = Lab.script(); 11 | var expect = Code.expect; 12 | var describe = lab.experiment; 13 | var it = lab.test; 14 | var before = lab.before; 15 | var beforeEach = lab.beforeEach; 16 | var after = lab.after; 17 | var afterEach = lab.afterEach; 18 | 19 | var internals = {}; 20 | internals.fixturePath = Path.join(__dirname, 'fixtures'); 21 | 22 | internals.mkdir = function (path) { 23 | 24 | var args = [internals.fixturePath]; 25 | for (var i = 0, l = arguments.length; i < l; ++i) { 26 | args.push(arguments[i]); 27 | } 28 | 29 | Mkdirp.sync(Path.join.apply(null, args)); 30 | }; 31 | 32 | internals.createFile = function (path) { 33 | 34 | var args = [internals.fixturePath]; 35 | for (var i = 0, l = arguments.length; i < l; ++i) { 36 | args.push(arguments[i]); 37 | } 38 | 39 | Fs.writeFileSync(Path.join.apply(null, args), '{}', { encoding: 'utf8' }); 40 | }; 41 | 42 | internals.createFixture = function (done) { 43 | 44 | internals.mkdir('project1', 'not_a_project'); 45 | internals.createFile('project1', 'package.json'); 46 | internals.mkdir('project2', '.git', 'hooks'); 47 | internals.createFile('project2', 'package.json'); 48 | internals.mkdir('project3', 'actual_project'); 49 | internals.createFile('project3', 'actual_project', 'package.json'); 50 | internals.mkdir('project4', 'this', 'is', 'too', 'deep', 'to', 'find'); 51 | internals.createFile('project4', 'this', 'is', 'too', 'deep', 'to', 'find', 'package.json'); 52 | internals.mkdir('project5', '.git'); 53 | internals.createFile('project5', 'package.json'); 54 | internals.mkdir('project6', '.git'); 55 | internals.createFile('project6', 'package.json'); 56 | done(); 57 | }; 58 | 59 | internals.cleanupFixture = function (done) { 60 | 61 | Rimraf(internals.fixturePath, done); 62 | }; 63 | 64 | describe('isDir()', function () { 65 | 66 | it('returns true for a directory', function (done) { 67 | 68 | expect(Utils.isDir(__dirname)).to.be.true(); 69 | done(); 70 | }); 71 | 72 | it('returns false for a file', function (done) { 73 | 74 | expect(Utils.isDir(__filename)).to.be.false(); 75 | done(); 76 | }); 77 | 78 | it('returns false when the path does not exist', function (done) { 79 | 80 | expect(Utils.isDir('nothere')).to.be.false(); 81 | done(); 82 | }); 83 | }); 84 | 85 | describe('copy()', function () { 86 | 87 | beforeEach(internals.createFixture); 88 | 89 | it('can copy an entire directory', function (done) { 90 | 91 | Utils.copy(internals.fixturePath, Path.join(__dirname, 'fixtures2')); 92 | expect(Utils.isDir(Path.join(__dirname, 'fixtures2'))).to.be.true(); 93 | expect(Utils.isDir(Path.join(__dirname, 'fixtures2', 'project1'))).to.be.true(); 94 | expect(Fs.existsSync(Path.join(__dirname, 'fixtures2', 'project1', 'package.json'))).to.be.true(); 95 | expect(Utils.isDir(Path.join(__dirname, 'fixtures2', 'project1', 'not_a_project'))).to.be.true(); 96 | expect(Utils.isDir(Path.join(__dirname, 'fixtures2', 'project2'))).to.be.true(); 97 | expect(Utils.isDir(Path.join(__dirname, 'fixtures2', 'project3', 'actual_project'))).to.be.true(); 98 | expect(Fs.existsSync(Path.join(__dirname, 'fixtures2', 'project3', 'actual_project', 'package.json'))).to.be.true(); 99 | expect(Utils.isDir(Path.join(__dirname, 'fixtures2', 'project4', 'this', 'is', 'too', 'deep', 'to', 'find'))).to.be.true(); 100 | expect(Fs.existsSync(Path.join(__dirname, 'fixtures2', 'project4', 'this', 'is', 'too', 'deep', 'to', 'find', 'package.json'))).to.be.true(); 101 | done(); 102 | }); 103 | 104 | it('throws when trying to overwrite a file by default', function (done) { 105 | 106 | Utils.copy(Path.join(internals.fixturePath, 'project1', 'package.json'), Path.join(__dirname, 'fixtures2', 'project1', 'package.json')); 107 | var err = Utils.copy(Path.join(internals.fixturePath, 'project1', 'package.json'), Path.join(__dirname, 'fixtures2', 'project1', 'package.json')); 108 | expect(err).to.not.be.undefined(); 109 | expect(err.message).to.contain('already exists'); 110 | done(); 111 | }); 112 | 113 | it('allows overwriting a file when setting overwrite to true', function (done) { 114 | 115 | Utils.copy(Path.join(internals.fixturePath, 'project1', 'package.json'), Path.join(__dirname, 'fixtures2', 'project1', 'package.json')); 116 | var err = Utils.copy(Path.join(internals.fixturePath, 'project1', 'package.json'), Path.join(__dirname, 'fixtures2', 'project1', 'package.json'), { overwrite: true }); 117 | expect(err).to.be.undefined(); 118 | done(); 119 | }); 120 | 121 | it('can copy a file without specifying a target', function (done) { 122 | 123 | Utils.copy(Path.join(internals.fixturePath, 'project1', 'package.json')); 124 | var err = Utils.copy(Path.join(internals.fixturePath, 'project1', 'package.json'), { overwrite: true }); 125 | expect(err).to.be.undefined(); 126 | done(); 127 | }); 128 | 129 | it('throws when trying to write outside of the project root', function (done) { 130 | 131 | expect(function () { 132 | 133 | Utils.copy(Path.join(internals.fixturePath, 'project1', 'package.json'), Path.join(__dirname, '..', '..')); 134 | }).to.throw(Error, /within project root/); 135 | done(); 136 | }); 137 | 138 | it('throws when trying to copy a directory over a file', function (done) { 139 | 140 | Utils.copy(Path.join(internals.fixturePath, 'project1', 'package.json'), Path.join(__dirname, 'fixtures2', 'project1', 'package.json')); 141 | expect(function () { 142 | 143 | Utils.copy(Path.join(internals.fixturePath, 'project1'), Path.join(__dirname, 'fixtures2', 'project1', 'package.json')); 144 | }).to.throw(); 145 | done(); 146 | }); 147 | 148 | afterEach(function (done) { 149 | 150 | internals.cleanupFixture(function () { 151 | 152 | Rimraf(Path.join(__dirname, 'fixtures2'), done); 153 | }); 154 | }); 155 | }); 156 | 157 | describe('findGitRoot()', function () { 158 | 159 | it('can find a git root', function (done) { 160 | 161 | var root = Path.resolve(__dirname, '..'); 162 | expect(Utils.findGitRoot()).to.equal(root); 163 | done(); 164 | }); 165 | 166 | it('logs an error and exits cleanly when no git root is found', function (done) { 167 | 168 | ChildProcess.exec('node -e \'var path = require("path"); var utils = require("./lib/utils"); utils.findGitRoot(path.resolve(__dirname, "..", ".."));\'', function (err, stdout, stderr) { 169 | 170 | expect(err).to.not.exist(); 171 | expect(stderr).to.equal('WARNING: Unable to find a .git directory for this project, installation aborted.\n'); 172 | done(); 173 | }); 174 | }); 175 | }); 176 | 177 | describe('findProjectRoot()', function () { 178 | 179 | before(internals.createFixture); 180 | 181 | it('can find a project root', function (done) { 182 | 183 | var root = Path.resolve(__dirname, '..'); 184 | expect(Utils.findProjectRoot()).to.equal(root); 185 | done(); 186 | }); 187 | 188 | it('can find a project root from a child directory', function (done) { 189 | 190 | var root = Path.join(internals.fixturePath, 'project1', 'not_a_project'); 191 | expect(Utils.findProjectRoot(root)).to.equal(Path.join(internals.fixturePath, 'project1')); 192 | done(); 193 | }); 194 | 195 | it('can return an error when no project is found', function (done) { 196 | 197 | ChildProcess.exec('node -e \'var path = require("path"); var utils = require("./lib/utils"); utils.findProjectRoot(path.resolve(__dirname, "..", ".."));\'', function (err, stdout, stderr) { 198 | 199 | expect(err).to.not.exist(); 200 | expect(stderr).to.equal('WARNING: Unable to find a package.json for this project, installation aborted.\n'); 201 | done(); 202 | }); 203 | }); 204 | 205 | after(internals.cleanupFixture); 206 | }); 207 | 208 | describe('findProjects()', function () { 209 | 210 | before(internals.createFixture); 211 | 212 | it('can find projects', function (done) { 213 | 214 | var projects = Utils.findProjects(); 215 | expect(projects).to.be.an.array(); 216 | expect(projects).to.have.length(6); 217 | expect(projects).to.contain(Path.dirname(__dirname)); 218 | expect(projects).to.contain(Path.join(internals.fixturePath, 'project1')); 219 | expect(projects).to.contain(Path.join(internals.fixturePath, 'project2')); 220 | expect(projects).to.contain(Path.join(internals.fixturePath, 'project3', 'actual_project')); 221 | expect(projects).to.contain(Path.join(internals.fixturePath, 'project6')); 222 | done(); 223 | }); 224 | 225 | after(internals.cleanupFixture); 226 | }); 227 | 228 | describe('installHooks()', function () { 229 | 230 | beforeEach(internals.createFixture); 231 | 232 | it('can install a hook', function (done) { 233 | 234 | Utils.installHooks('pre-commit', Path.join(internals.fixturePath, 'project2')); 235 | expect(Fs.existsSync(Path.join(internals.fixturePath, 'project2', '.git', 'hooks', 'pre-commit'))).to.be.true(); 236 | done(); 237 | }); 238 | 239 | it('can install a hook to a .git directory without hooks subdir', function (done) { 240 | 241 | Utils.installHooks('pre-commit', Path.join(internals.fixturePath, 'project5')); 242 | expect(Fs.existsSync(Path.join(internals.fixturePath, 'project5', '.git', 'hooks', 'pre-commit'))).to.be.true(); 243 | done(); 244 | }); 245 | 246 | it('can install multiple hooks at once', function (done) { 247 | 248 | Utils.installHooks(['pre-commit', 'pre-push'], Path.join(internals.fixturePath, 'project2')); 249 | expect(Fs.existsSync(Path.join(internals.fixturePath, 'project2', '.git', 'hooks', 'pre-commit'))).to.be.true(); 250 | expect(Fs.existsSync(Path.join(internals.fixturePath, 'project2', '.git', 'hooks', 'pre-push'))).to.be.true(); 251 | done(); 252 | }); 253 | 254 | it('backs up an existing hook when installing', function (done) { 255 | 256 | Utils.installHooks('pre-commit', Path.join(internals.fixturePath, 'project2')); 257 | expect(Fs.existsSync(Path.join(internals.fixturePath, 'project2', '.git', 'hooks', 'pre-commit'))).to.be.true(); 258 | Utils.installHooks('pre-commit', Path.join(internals.fixturePath, 'project2')); 259 | expect(Fs.existsSync(Path.join(internals.fixturePath, 'project2', '.git', 'hooks', 'pre-commit'))).to.be.true(); 260 | expect(Fs.existsSync(Path.join(internals.fixturePath, 'project2', '.git', 'hooks', 'pre-commit.backup'))).to.be.true(); 261 | done(); 262 | }); 263 | 264 | afterEach(internals.cleanupFixture); 265 | }); 266 | 267 | describe('configureHook()', function () { 268 | 269 | beforeEach(internals.createFixture); 270 | 271 | it('can install a hook with defaults as a string', function (done) { 272 | 273 | Utils.installHooks('pre-commit', Path.join(internals.fixturePath, 'project2')); 274 | Utils.configureHook('pre-commit', 'test', false, Path.join(internals.fixturePath, 'project2')); 275 | expect(Fs.existsSync(Path.join(internals.fixturePath, 'project2', '.git', 'hooks', 'pre-commit'))).to.be.true(); 276 | var fixturePackage = JSON.parse(Fs.readFileSync(Path.join(internals.fixturePath, 'project2', 'package.json'), { encoding: 'utf8' })); 277 | expect(fixturePackage['pre-commit']).to.deep.equal(['test']); 278 | done(); 279 | }); 280 | 281 | it('can install a hook with defaults as an array', function (done) { 282 | 283 | Utils.installHooks('pre-commit', Path.join(internals.fixturePath, 'project2')); 284 | Utils.configureHook('pre-commit', ['lint', 'test'], false, Path.join(internals.fixturePath, 'project2')); 285 | expect(Fs.existsSync(Path.join(internals.fixturePath, 'project2', '.git', 'hooks', 'pre-commit'))).to.be.true(); 286 | var fixturePackage = JSON.parse(Fs.readFileSync(Path.join(internals.fixturePath, 'project2', 'package.json'), { encoding: 'utf8' })); 287 | expect(fixturePackage['pre-commit']).to.deep.equal(['lint', 'test']); 288 | done(); 289 | }); 290 | 291 | it('won\'t overwrite existing hook settings', function (done) { 292 | 293 | Utils.installHooks('pre-commit', Path.join(internals.fixturePath, 'project2')); 294 | Utils.configureHook('pre-commit', 'test', false, Path.join(internals.fixturePath, 'project2')); 295 | expect(Fs.existsSync(Path.join(internals.fixturePath, 'project2', '.git', 'hooks', 'pre-commit'))).to.be.true(); 296 | var fixturePackageOne = JSON.parse(Fs.readFileSync(Path.join(internals.fixturePath, 'project2', 'package.json'), { encoding: 'utf8' })); 297 | expect(fixturePackageOne['pre-commit']).to.deep.equal(['test']); 298 | Utils.configureHook('pre-commit', ['lint', 'test'], false, Path.join(internals.fixturePath, 'project2')); 299 | var fixturePackageTwo = JSON.parse(Fs.readFileSync(Path.join(internals.fixturePath, 'project2', 'package.json'), { encoding: 'utf8' })); 300 | expect(fixturePackageTwo['pre-commit']).to.deep.equal(['test']); 301 | done(); 302 | }); 303 | 304 | it('will overwrite existing hook settings if we say so', function (done) { 305 | 306 | Utils.installHooks('pre-commit', Path.join(internals.fixturePath, 'project6')); 307 | Utils.configureHook('pre-commit', 'test', true, Path.join(internals.fixturePath, 'project6')); 308 | expect(Fs.existsSync(Path.join(internals.fixturePath, 'project6', '.git', 'hooks', 'pre-commit'))).to.be.true(); 309 | var fixturePackageOne = JSON.parse(Fs.readFileSync(Path.join(internals.fixturePath, 'project6', 'package.json'), { encoding: 'utf8' })); 310 | expect(fixturePackageOne['pre-commit']).to.deep.equal(['test']); 311 | Utils.configureHook('pre-commit', ['lint', 'bla'], true, Path.join(internals.fixturePath, 'project6')); 312 | var fixturePackageTwo = JSON.parse(Fs.readFileSync(Path.join(internals.fixturePath, 'project6', 'package.json'), { encoding: 'utf8' })); 313 | expect(fixturePackageTwo['pre-commit']).to.deep.equal(['lint', 'bla']); 314 | done(); 315 | }); 316 | 317 | afterEach(internals.cleanupFixture); 318 | }); 319 | 320 | describe('installScript()', function () { 321 | 322 | beforeEach(internals.createFixture); 323 | 324 | it('can install a script', function (done) { 325 | 326 | Utils.installScript('test', 'lab -a code -L', Path.join(internals.fixturePath, 'project2')); 327 | var fixturePackage = JSON.parse(Fs.readFileSync(Path.join(internals.fixturePath, 'project2', 'package.json'), { encoding: 'utf8' })); 328 | expect(fixturePackage).to.deep.equal({ scripts: { test: 'lab -a code -L' } }); 329 | done(); 330 | }); 331 | 332 | it('can install a script to an existing scripts object', function (done) { 333 | 334 | var packagePath = Path.join(internals.fixturePath, 'project2', 'package.json'); 335 | Fs.writeFileSync(packagePath, '{"scripts":{}}', { encoding: 'utf8' }); 336 | Utils.installScript('test', 'lab -a code -L', Path.join(internals.fixturePath, 'project2')); 337 | var fixturePackage = JSON.parse(Fs.readFileSync(packagePath, { encoding: 'utf8' })); 338 | expect(fixturePackage).to.deep.equal({ scripts: { test: 'lab -a code -L' } }); 339 | done(); 340 | }); 341 | 342 | it('does not overwrite an existing script', function (done) { 343 | 344 | Utils.installScript('test', 'lab -a code -L', Path.join(internals.fixturePath, 'project2')); 345 | var fixturePackageOne = JSON.parse(Fs.readFileSync(Path.join(internals.fixturePath, 'project2', 'package.json'), { encoding: 'utf8' })); 346 | expect(fixturePackageOne).to.deep.equal({ scripts: { test: 'lab -a code -L' } }); 347 | Utils.installScript('test', 'mocha', Path.join(internals.fixturePath, 'project2')); 348 | var fixturePackage = JSON.parse(Fs.readFileSync(Path.join(internals.fixturePath, 'project2', 'package.json'), { encoding: 'utf8' })); 349 | expect(fixturePackage).to.deep.equal({ scripts: { test: 'lab -a code -L' } }); 350 | done(); 351 | }); 352 | 353 | it('overwrite an existing script when option is specified', function (done) { 354 | 355 | Utils.installScript('test', 'lab -a code -L', {}, Path.join(internals.fixturePath, 'project2')); 356 | var fixturePackageOne = JSON.parse(Fs.readFileSync(Path.join(internals.fixturePath, 'project2', 'package.json'), { encoding: 'utf8' })); 357 | expect(fixturePackageOne).to.deep.equal({ scripts: { test: 'lab -a code -L' } }); 358 | 359 | Utils.installScript('test', 'mocha', {}, Path.join(internals.fixturePath, 'project2')); 360 | var fixturePackageTwo = JSON.parse(Fs.readFileSync(Path.join(internals.fixturePath, 'project2', 'package.json'), { encoding: 'utf8' })); 361 | expect(fixturePackageTwo).to.deep.equal({ scripts: { test: 'lab -a code -L' } }); 362 | 363 | Utils.installScript('test', 'mocha', { overwrite: true }, Path.join(internals.fixturePath, 'project2')); 364 | var fixturePackage = JSON.parse(Fs.readFileSync(Path.join(internals.fixturePath, 'project2', 'package.json'), { encoding: 'utf8' })); 365 | expect(fixturePackage).to.deep.equal({ scripts: { test: 'mocha' } }); 366 | 367 | done(); 368 | }); 369 | 370 | afterEach(internals.cleanupFixture); 371 | }); 372 | -------------------------------------------------------------------------------- /test/validate.js: -------------------------------------------------------------------------------- 1 | var Spawn = require('child_process').spawn; 2 | var Path = require('path'); 3 | var Writable = require('stream').Writable; 4 | 5 | var Code = require('code'); 6 | var Lab = require('lab'); 7 | var lab = exports.lab = Lab.script(); 8 | var expect = Code.expect; 9 | var describe = lab.experiment; 10 | var it = lab.test; 11 | 12 | describe('validate', function () { 13 | 14 | it('runs', function (done) { 15 | 16 | var validate = Spawn(Path.resolve(__dirname, '..', 'bin', 'validate.sh'), [], { stdio: 'pipe' }); 17 | 18 | validate.on('close', function (code) { 19 | 20 | expect(code).to.equal(0); 21 | done(); 22 | }); 23 | }); 24 | }); 25 | --------------------------------------------------------------------------------