Commit f975051c authored by Hunter Loftis's avatar Hunter Loftis

Merge branch 'yoga' into master-merge-yoga

parents f4798c6c 0f1caa52
Heroku Buildpack for Node.js # Heroku Node.js Buildpack: Yoga
============================
This is the official [Heroku buildpack](http://devcenter.heroku.com/articles/buildpacks) for Node.js apps. If you fork this repository, please **update this README** to explain what your fork does and why it's special. Preview the next version of the node buildpack: yoga.
It's the most powerful and flexible Node buildpack yet.
```shell
heroku config:set BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-nodejs#yoga
git commit -am 'yoga' --allow-empty
git push heroku master
```
How it Works It's still in beta and we'd love [feedback](#feedback)!
------------
Here's an overview of what this buildpack does: ## What can I do with Yoga?
- Uses the [semver.io](https://semver.io) webservice to find the latest version of node that satisfies the [engines.node semver range](https://npmjs.org/doc/json.html#engines) in your package.json. - [Specify an npm version](https://github.com/heroku/heroku-buildpack-nodejs/tree/yoga#specify-an-npm-version)
- Allows any recent version of node to be used, including [pre-release versions](https://semver.io/node.json). - [Enable or disable node_modules caching](https://github.com/heroku/heroku-buildpack-nodejs/tree/yoga#enable-or-disable-node_modules-caching)
- Uses an [S3 caching proxy](https://github.com/heroku/s3pository#readme) of nodejs.org for faster downloads of the node binary. - [Enable or disable devDependencies installation](https://github.com/heroku/heroku-buildpack-nodejs/tree/yoga#enable-or-disable-devdependencies-installation)
- Discourages use of dangerous semver ranges like `*` and `>0.10`. - [Configure npm with .npmrc](https://github.com/heroku/heroku-buildpack-nodejs/tree/yoga#configure-npm-with-npmrc)
- Uses the version of `npm` that comes bundled with `node`. - [Chain node with multiple buildpacks](https://github.com/heroku/heroku-buildpack-nodejs/tree/yoga#chain-node-with-multiple-buildpacks)
- Puts `node` and `npm` on the `PATH` so they can be executed with [heroku run](https://devcenter.heroku.com/articles/one-off-dynos#an-example-one-off-dyno).
- Caches the `node_modules` directory across builds for fast deploys.
- Doesn't use the cache if `node_modules` is checked into version control.
- Runs `npm rebuild` if `node_modules` is checked into version control.
- Always runs `npm install` to ensure [npm script hooks](https://npmjs.org/doc/misc/npm-scripts.html) are executed.
- Always runs `npm prune` after restoring cached modules to ensure cleanup of unused dependencies.
For more technical details, see the [heavily-commented compile script](https://github.com/heroku/heroku-buildpack-nodejs/blob/master/bin/compile). Yoga also outputs minimal but useful messages on success and concise debug information on error.
No more 20,000-line error logs!
## Stretch
Documentation ### Specify a node version
-------------
For more information about using Node.js and buildpacks on Heroku, see these Dev Center articles: Set engines.node in package.json to the semver range
(or specific version) of node you'd like to use.
(It's a good idea to make this the same version you use during development)
```json
"engines": {
"node": "0.11.x"
}
```
- [Heroku Node.js Support](https://devcenter.heroku.com/articles/nodejs-support) ```json
- [Getting Started with Node.js on Heroku](https://devcenter.heroku.com/articles/nodejs) "engines": {
- [10 Habits of a Happy Node Hacker](https://blog.heroku.com/archives/2014/3/11/node-habits) "node": "0.10.33"
- [Buildpacks](https://devcenter.heroku.com/articles/buildpacks) }
- [Buildpack API](https://devcenter.heroku.com/articles/buildpack-api) ```
Default: the
[latest stable version.](http://semver.io/node)
Try npm@next ### Specify an npm version
------------
Use [the preview](https://github.com/heroku/heroku-buildpack-nodejs/tree/yoga) Set engines.npm in package.json to the semver range
of the upcoming Node buildpack release, which lets you specify a version of (or specific version) of npm you'd like to use.
npm just like you already specify a version of node: (It's a good idea to make this the same version you use during development)
``` Since 'npm 2' shipped several major bugfixes, you might try:
```json
"engines": { "engines": {
"node": "0.10.x",
"npm": "2.x" "npm": "2.x"
} }
``` ```
```json
"engines": {
"npm": "^2.1.0"
}
```
Legacy Compatibility Default: the version of npm bundled with your node install (varies).
--------------------
For most Node.js apps this buildpack should work just fine. If, however, you're unable to deploy using this new version of the buildpack, you can get your app working again by using the legacy branch: ### Enable or disable node_modules caching
``` For a 'clean' build without using any cached node modules:
heroku config:set BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-nodejs#legacy -a my-app
git commit -am "empty" --allow-empty # force a git commit ```shell
heroku config:set NODE_MODULES_CACHE=false
git commit -am 'rebuild' --allow-empty
git push heroku master git push heroku master
heroku config:unset NODE_MODULES_CACHE
``` ```
Then please open a support ticket at [help.heroku.com](https://help.heroku.com/) so we can diagnose and get your app running on the default buildpack. Caching node_modules between builds dramatically speeds up build times.
However, `npm install` doesn't automatically update already-installed modules
as long as they fall within acceptable semver ranges,
which can lead to outdated modules.
Default: `NODE_MODULES_CACHE` defaults to true
Hacking ### Enable or disable devDependencies installation
-------
To make changes to this buildpack, fork it on Github. Push up changes to your fork, then create a new Heroku app to test it, or configure an existing app to use your buildpack: During local development, `npm install` installs all dependencies
and all devDependencies (test frameworks, build tools, etc).
This is usually something you want to avoid in production, so
npm has a 'production' config that can be set through the environment:
To install *dependencies only:*
```shell
heroku config:set NPM_CONFIG_PRODUCTION=true
```
To install *dependencies and devDependencies:*
```shell
heroku config:set NPM_CONFIG_PRODUCTION=false
``` ```
# Create a new Heroku app that uses your buildpack
heroku create --buildpack <your-github-url>
# Configure an existing Heroku app to use your buildpack Default: `NPM_CONFIG_PRODUCTION` defaults to true on Heroku
heroku config:set BUILDPACK_URL=<your-github-url>
# You can also use a git branch! ### Configure npm with .npmrc
heroku config:set BUILDPACK_URL=<your-github-url>#your-branch
Sometimes, a project needs custom npm behavior to set up proxies,
use a different registry, etc. For such behavior,
just include an `.npmrc` file in the root of your project:
```
# .npmrc
registry = 'https://custom-registry.com/'
``` ```
### Chain Node with multiple buildpacks
Frequently, Node is paired with other platforms like Ruby or PHP
through Heroku's Multi Buildpack. In order to use node in
another environment, specify this buildpack first in your .buildpacks file.
This buildpack automatically exports node, npm, and any node_modules binaries
into the `$PATH` for easy use in subsequent buildpacks.
## Roadmap
The next features in the pipeline include:
- Specifying io.js as your node engine
- Providing proxy settings for your locked-down enterprise environment
- Dynamically adjusting to different container sizes (especially regarding memory)
## Feedback
Having trouble? Dig it? Feature request?
- [help.heroku.com](https://help.heroku.com/)
- [@hunterloftis](http://twitter.com/hunterloftis)
- [github issues](https://github.com/heroku/heroku-buildpack-nodejs/issues)
Testing ## Testing
-------
[Anvil](https://github.com/ddollar/anvil) is a generic build server for Heroku. [Anvil](https://github.com/ddollar/anvil) is a generic build server for Heroku.
......
...@@ -3,15 +3,45 @@ error() { ...@@ -3,15 +3,45 @@ error() {
exit 1 exit 1
} }
status() { head() {
echo ""
echo "-----> $*" echo "-----> $*"
} }
info() {
#echo "`date +\"%M:%S\"` $*"
echo " $*"
}
build_failed() {
head "Build failed"
}
protip() { protip() {
tip=$1
url=$2
echo echo
echo "PRO TIP: $*" | indent echo "PRO TIP: $tip" | indent
echo "See https://devcenter.heroku.com/articles/nodejs-support" | indent echo "See ${url:-https://devcenter.heroku.com/articles/nodejs-support}" | indent
echo }
file_contents() {
if test -f $1; then
echo "$(cat $1)"
else
echo ""
fi
}
package_json() {
if test -f $build_dir/package.json; then
local result="$(cat $build_dir/package.json | $bp_dir/vendor/jq -r $1)"
if [ "$result" == "null" ]; then echo ""
else echo "$result"
fi
else
echo ""
fi
} }
# sed -l basically makes sed replace and buffer through stdin to stdout # sed -l basically makes sed replace and buffer through stdin to stdout
...@@ -31,13 +61,15 @@ cat_npm_debug_log() { ...@@ -31,13 +61,15 @@ cat_npm_debug_log() {
export_env_dir() { export_env_dir() {
env_dir=$1 env_dir=$1
whitelist_regex=${2:-''}
blacklist_regex=${3:-'^(PATH|GIT_DIR|CPATH|CPPATH|LD_PRELOAD|LIBRARY_PATH)$'}
if [ -d "$env_dir" ]; then if [ -d "$env_dir" ]; then
for e in $(ls $env_dir); do whitelist_regex=${2:-''}
echo "$e" | grep -E "$whitelist_regex" | grep -qvE "$blacklist_regex" && blacklist_regex=${3:-'^(PATH|GIT_DIR|CPATH|CPPATH|LD_PRELOAD|LIBRARY_PATH)$'}
export "$e=$(cat $env_dir/$e)" if [ -d "$env_dir" ]; then
: for e in $(ls $env_dir); do
done echo "$e" | grep -E "$whitelist_regex" | grep -qvE "$blacklist_regex" &&
export "$e=$(cat $env_dir/$e)"
:
done
fi
fi fi
} }
#!/usr/bin/env bash #!/usr/bin/env bash
####### Configure environment
set -e # fail fast set -e # fail fast
set -o pipefail # don't ignore exit codes when piping output set -o pipefail # don't ignore exit codes when piping output
# set -x # enable debugging # set -x # enable debugging
...@@ -9,141 +11,203 @@ build_dir=$1 ...@@ -9,141 +11,203 @@ build_dir=$1
cache_dir=$2 cache_dir=$2
env_dir=$3 env_dir=$3
bp_dir=$(cd $(dirname $0); cd ..; pwd) bp_dir=$(cd $(dirname $0); cd ..; pwd)
heroku_dir=$build_dir/.heroku
mkdir -p $heroku_dir/node
# Load some convenience functions like status(), echo(), and indent() # Load some convenience functions like status(), echo(), and indent()
source $bp_dir/bin/common.sh source $bp_dir/bin/common.sh
# Fix leak # Avoid GIT_DIR leak from previous build steps
status "Resetting git environment"
unset GIT_DIR unset GIT_DIR
# Output npm debug info on error # Provide hook to deal with errors
trap cat_npm_debug_log ERR trap build_failed ERR
# Load config vars into environment; start with defaults
export NPM_CONFIG_PRODUCTION=true
export NODE_MODULES_CACHE=true
export_env_dir $env_dir
####### Determine current state
# Look in package.json's engines.node field for a semver range # Which version of the buildpack did we use last time?
semver_range=$(cat $build_dir/package.json | $bp_dir/vendor/jq -r .engines.node) bp_previous=$(file_contents "$cache_dir/node/bp-version")
# Resolve node version using semver.io # What's the requested semver range for node?
node_version=$(curl --silent --get --data-urlencode "range=${semver_range}" https://semver.herokuapp.com/node/resolve) node_engine=$(package_json ".engines.node")
node_previous=$(file_contents "$cache_dir/node/node-version")
# Recommend using semver ranges in a safe manner # What's the requested semver range for npm?
if [ "$semver_range" == "null" ]; then npm_engine=$(package_json ".engines.npm")
protip "Specify a node version in package.json" npm_previous=$(file_contents "$cache_dir/node/npm-version")
semver_range=""
elif [ "$semver_range" == "*" ]; then # How does this app start?
protip "Avoid using semver ranges like '*' in engines.node" if test -f $build_dir/Procfile; then start_method="Procfile"
elif [ ${semver_range:0:1} == ">" ]; then elif [[ $(package_json ".scripts.start") != "" ]]; then start_method="npm start"
protip "Avoid using semver ranges starting with '>' in engines.node" elif [ -f $build_dir/server.js ]; then start_method="server.js"
else start_method=""
fi fi
# Output info about requested range and resolved node version # What's the source-of-truth for node_modules?
if [ "$semver_range" == "" ]; then if test -d $build_dir/node_modules; then modules_source="prebuilt"
status "Defaulting to latest stable node: $node_version" elif test -f $build_dir/npm-shrinkwrap.json; then modules_source="npm-shrinkwrap.json"
else elif test -f $build_dir/package.json; then modules_source="package.json"
status "Requested node range: $semver_range" else modules_source=""
status "Resolved node version: $node_version"
fi fi
# Download node from Heroku's S3 mirror of nodejs.org/dist # What does our cache look like?
status "Downloading and installing node" test -d $cache_dir/node/node_modules && modules_cached=true || modules_cached=false
node_url="http://s3pository.heroku.com/node/v$node_version/node-v$node_version-linux-x64.tar.gz"
curl $node_url -s -o - | tar xzf - -C $build_dir
# Move node (and npm) into ./vendor and make them executable ####### Provide debugging info and feedback
mkdir -p $build_dir/vendor
mv $build_dir/node-v$node_version-linux-x64 $build_dir/vendor/node
chmod +x $build_dir/vendor/node/bin/*
PATH=$build_dir/vendor/node/bin:$PATH
# Run subsequent node/npm commands from the build path echo ""
cd $build_dir info "Node engine: ${node_engine:-unspecified}"
info "Npm engine: ${npm_engine:-unspecified}"
info "Start mechanism: ${start_method:-none}"
info "node_modules source: ${modules_source:-none}"
info "node_modules cached: $modules_cached"
# If node_modules directory is checked into source control then echo ""
# rebuild any native deps. Otherwise, restore from the build cache.
if test -d $build_dir/node_modules; then
status "Found existing node_modules directory; skipping cache"
status "Rebuilding any native dependencies"
npm rebuild 2>&1 | indent
elif test -d $cache_dir/node/node_modules; then
status "Restoring node_modules directory from cache"
cp -r $cache_dir/node/node_modules $build_dir/
status "Pruning cached dependencies not specified in package.json" printenv | grep ^NPM_CONFIG_ | indent
npm prune --production 2>&1 | indent info "NODE_MODULES_CACHE=$NODE_MODULES_CACHE"
if test -f $cache_dir/node/.heroku/node-version && [ $(cat $cache_dir/node/.heroku/node-version) != "$node_version" ]; then source $bp_dir/bin/warnings.sh
status "Node version changed since last build; rebuilding dependencies"
npm rebuild 2>&1 | indent ####### Vendor in binaries
fi
head "Installing binaries"
# Resolve non-specific node versions using semver.herokuapp.com
if ! [[ "$node_engine" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
info "Resolving node version ${node_engine:-(latest stable)} via semver.io..."
node_engine=$(curl --silent --get --data-urlencode "range=${node_engine}" https://semver.herokuapp.com/node/resolve)
fi fi
# Scope config var availability only to `npm install` # Download node from Heroku's S3 mirror of nodejs.org/dist
( info "Downloading and installing node $node_engine..."
if [ -d "$env_dir" ]; then node_url="http://s3pository.heroku.com/node/v$node_engine/node-v$node_engine-linux-x64.tar.gz"
status "Exporting config vars to environment" curl $node_url -s -o - | tar xzf - -C /tmp
export_env_dir $env_dir
# Move node (and npm) into .heroku/node and make them executable
mv /tmp/node-v$node_engine-linux-x64/* $heroku_dir/node
chmod +x $heroku_dir/node/bin/*
PATH=$heroku_dir/node/bin:$PATH
# Optionally bootstrap a different npm version
if [ "$npm_engine" != "" ]; then
if ! [[ "$npm_engine" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
info "Resolving npm version ${npm_engine} via semver.io..."
npm_engine=$(curl --silent --get --data-urlencode "range=${npm_engine}" https://semver.herokuapp.com/npm/resolve)
fi fi
info "Downloading and installing npm $npm_engine (replacing version `npm --version`)..."
npm install --quiet -g npm@$npm_engine > /dev/null
fi
status "Installing dependencies" # Run subsequent commands from the build directory
# Make npm output to STDOUT instead of its default STDERR cd $build_dir
npm install --userconfig $build_dir/.npmrc --production 2>&1 | indent
)
# Persist goodies like node-version in the slug ####### Build the project's dependencies
mkdir -p $build_dir/.heroku
head "Building dependencies"
# Did we bust the cache?
if ! $modules_cached; then
use_cache=false
elif ! $NODE_MODULES_CACHE; then
info "Cache disabled with NODE_MODULES_CACHE"
use_cache=false
elif [ "$node_previous" != "" ] && [ "$node_engine" != "$node_previous" ]; then
info "Node version changed ($node_previous => $node_engine); invalidating cache"
use_cache=false
elif [ "$npm_previous" != "" ] && [ "$npm_engine" != "$npm_previous" ]; then
info "Npm version changed ($npm_previous => $npm_engine); invalidating cache"
use_cache=false
elif [ "$bp_version" != "$bp_previous" ]; then
info "New buildpack version ($bp_version); invalidating cache"
use_cache=false
else
use_cache=true
fi
# Save resolved node version in the slug for later reference if [ "$modules_source" == "" ]; then
echo $node_version > $build_dir/.heroku/node-version info "Skipping dependencies (no source for node_modules)"
# Purge node-related cached content, being careful not to purge the top-level elif [ $modules_source == "prebuilt" ]; then
# cache, for the sake of heroku-buildpack-multi apps. info "Rebuilding any native modules for this architecture"
rm -rf $cache_dir/node_modules # (for apps still on the older caching strategy) npm run preinstall
rm -rf $cache_dir/node npm rebuild 2>&1
mkdir -p $cache_dir/node npm run postinstall
# If app has a node_modules directory, cache it. elif $use_cache; then
if test -d $build_dir/node_modules; then info "Restoring node modules from cache"
status "Caching node_modules directory for future builds" cp -r $cache_dir/node/node_modules $build_dir/
cp -r $build_dir/node_modules $cache_dir/node info "Pruning unused dependencies"
npm prune 2>&1
info "Installing any new modules"
npm install --quiet --userconfig $build_dir/.npmrc 2>&1
else
info "Installing node modules"
touch $build_dir/.npmrc
npm install --quiet --userconfig $build_dir/.npmrc 2>&1
info "Deduping dependency tree"
fi
####### Create a Procfile if possible
head "Checking startup method"
if [ "$start_method" == "Procfile" ]; then
info "Found Procfile"
elif [ "$start_method" == "npm start" ]; then
info "No Procfile; Adding 'web: npm start' to new Procfile"
echo "web: npm start" > $build_dir/Procfile
elif [ "$start_method" == "server.js" ]; then
info "No Procfile; Adding 'web: node server.js' to new Procfile"
echo "web: node server.js" > $build_dir/Procfile
fi fi
# Copy goodies to the cache ####### Create the runtime environment (profile.d)
cp -r $build_dir/.heroku $cache_dir/node
head "Finalizing build"
# Runtime & Multi-buildpack exports
info "Creating runtime environment"
mkdir -p $build_dir/.profile.d
echo "export PATH=\"\$HOME/.heroku/node/bin:\$HOME/bin:\$HOME/node_modules/.bin:\$PATH\"" > $build_dir/.profile.d/nodejs.sh
echo "export NODE_HOME=\"\$HOME/.heroku/node\"" >> $build_dir/.profile.d/nodejs.sh
info "Exporting binary paths"
echo "export PATH=\"$build_dir/.heroku/node/bin:$build_dir/node_modules/.bin:\$PATH\"" > $bp_dir/export
echo "export NODE_HOME=\"$build_dir/.heroku/node\"" >> $bp_dir/export
####### Clean up
info "Cleaning up build artifacts"
status "Cleaning up node-gyp and npm artifacts" # Clean up after npm
rm -rf "$build_dir/.node-gyp" rm -rf "$build_dir/.node-gyp"
rm -rf "$build_dir/.npm" rm -rf "$build_dir/.npm"
# If Procfile is absent, try to create one using `npm start` # Clear the cache
if [ ! -e $build_dir/Procfile ]; then rm -rf $cache_dir/node_modules # (for apps still on the older caching strategy)
npm_start=$(cat $build_dir/package.json | $bp_dir/vendor/jq -r .scripts.start) rm -rf $cache_dir/node
# If `scripts.start` is set in package.json, or a server.js file ####### Build successful! Store results in cache
# is present in the app root, then create a default Procfile
if [ "$npm_start" != "null" ] || [ -f $build_dir/server.js ]; then # Create the cache
status "No Procfile found; Adding npm start to new Procfile" mkdir -p $cache_dir/node
echo "web: npm start" > $build_dir/Procfile
else echo $node_engine > $cache_dir/node/node-version
status "Procfile not found and npm start script is undefined" echo $npm_engine > $cache_dir/node/npm-version
protip "Create a Procfile or specify a start script in package.json" echo $bp_version > $cache_dir/node/bp-version
fi
if test -d $build_dir/node_modules; then
info "Caching node_modules for future builds"
cp -r $build_dir/node_modules $cache_dir/node
fi fi
# Update the PATH # Show the final dependency tree
status "Building runtime environment" info "Build successful!"
mkdir -p $build_dir/.profile.d (npm ls --depth=0 || true) 2>/dev/null | indent
echo "export PATH=\"\$HOME/vendor/node/bin:\$HOME/bin:\$HOME/node_modules/.bin:\$PATH\";" > $build_dir/.profile.d/nodejs.sh
# Post package.json to nomnom service
# Use a subshell so failures won't break the build.
(
curl \
--data @$build_dir/package.json \
--fail \
--silent \
--request POST \
--header "content-type: application/json" \
https://nomnom.heroku.com/?request_id=$REQUEST_ID \
> /dev/null
) &
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
if [ -f $1/package.json ]; then if [ -f $1/package.json ]; then
echo "Node.js" && exit 0 echo "Node.js" && exit 0
elif [ -f $1/server.js ]; then
echo "Node.js" && exit 0
else else
echo "no" && exit 1 echo "no" && exit 1
fi fi
...@@ -14,118 +14,278 @@ testDetectWithoutPackageJson() { ...@@ -14,118 +14,278 @@ testDetectWithoutPackageJson() {
testNoVersion() { testNoVersion() {
compile "no-version" compile "no-version"
assertCaptured "Node engine: unspecified"
assertCaptured "PRO TIP: Specify a node version in package.json" assertCaptured "PRO TIP: Specify a node version in package.json"
assertCaptured "Defaulting to latest stable node" assertCaptured "Resolving node version (latest stable) via semver.io"
assertCaptured "Downloading and installing node 0.10"
assertCapturedSuccess assertCapturedSuccess
} }
testDangerousRangeStar() { testSpecificVersion() {
compile "dangerous-range-star" compile "specific-version"
assertCaptured "PRO TIP: Avoid using semver ranges like '*'" assertNotCaptured "Resolving node version"
assertCaptured "Requested node range: *" assertCaptured "Downloading and installing node 0.10.29"
assertCaptured "Resolved node version: 0.10"
assertCapturedSuccess assertCapturedSuccess
} }
testDangerousRangeGreaterThan() { testStableVersion() {
compile "dangerous-range-greater-than" compile "stable-node"
assertCaptured "PRO TIP: Avoid using semver ranges starting with '>'" assertCaptured "Downloading and installing node 0.10"
assertCaptured "Requested node range: >" assertNotCaptured "PRO TIP"
assertCaptured "Resolved node version: 0.10."
assertCapturedSuccess assertCapturedSuccess
} }
testRangeWithSpace() { testUnstableVersion() {
compile "range-with-space" compile "unstable-version"
assertCaptured "Requested node range: >= 0.8.x" assertCaptured "Resolving node version >0.11.0 via semver.io"
assertCaptured "Resolved node version: 0.10." assertCaptured "Downloading and installing node 0.11"
assertCapturedSuccess assertCapturedSuccess
} }
testStableVersion() { testInfoEmpty() {
compile "stable-node" compile "info-empty"
assertNotCaptured "PRO TIP: Avoid using semver" assertCaptured "Node engine: unspecified"
assertNotCaptured "PRO TIP: Specify" assertCaptured "Npm engine: unspecified"
assertCaptured "Resolved node version" assertCaptured "Start mechanism: none"
assertCaptured "node_modules source: package.json"
assertCaptured "node_modules cached: false"
assertCapturedSuccess assertCapturedSuccess
} }
testUnstableVersion() { testDangerousRangeStar() {
compile "unstable-version" compile "dangerous-range-star"
assertCaptured "Requested node range: >0.11.0" assertCaptured "PRO TIP: Avoid semver ranges like '*'"
assertCaptured "Resolved node version: 0.11." assertCaptured "Node engine: *"
assertCaptured "Resolving node version * via semver.io"
assertCaptured "Downloading and installing node 0.10"
assertCapturedSuccess assertCapturedSuccess
} }
testProfileCreated() { testDangerousRangeGreaterThan() {
compile "stable-node" compile "dangerous-range-greater-than"
assertCaptured "Building runtime environment" assertCaptured "PRO TIP: Avoid semver ranges starting with '>'"
assertFile "export PATH=\"\$HOME/vendor/node/bin:\$HOME/bin:\$HOME/node_modules/.bin:\$PATH\";" ".profile.d/nodejs.sh" assertCaptured "Resolving node version >0.4 via semver.io"
assertCaptured "Downloading and installing node 0.10"
assertCapturedSuccess
}
testRangeWithSpace() {
compile "range-with-space"
assertCaptured "Resolving node version >= 0.8.x via semver.io"
assertCaptured "Downloading and installing node 0.10"
assertCapturedSuccess assertCapturedSuccess
} }
testInvalidDependency() { testInvalidDependency() {
compile "invalid-dependency" compile "invalid-dependency"
assertCaptured "not in the npm registry" assertCaptured "npm ERR! 404"
assertCapturedError 1 "" assertCapturedError 1 ""
} }
testNodeModulesCached() { testNodeModulesCached() {
cache=$(mktmpdir) cache=$(mktmpdir)
compile "caching" $cache compile "caching" $cache
assertCaptured "Caching node" assertCaptured "Caching node_modules for future builds"
assertEquals "1" "$(ls -1 $cache/ | wc -l)" assertEquals "1" "$(ls -1 $cache/ | wc -l)"
} }
testBuildWithCache() {
cache=$(mktmpdir)
compile "stable-node" $cache
assertCaptured "node_modules cached: false"
assertCaptured "Caching node_modules for future builds"
assertCapturedSuccess
compile "stable-node" $cache
assertCaptured "node_modules cached: true"
assertCaptured "Restoring node modules from cache"
assertCapturedSuccess
}
testModulesCheckedIn() { testModulesCheckedIn() {
compile "modules-checked-in" compile "modules-checked-in"
assertCaptured "Found existing node_modules directory; skipping cache" assertCaptured "node_modules source: prebuilt"
assertCaptured "Rebuilding any native dependencies" assertCaptured "(preinstall script)"
assertCaptured "Rebuilding any native modules for this architecture"
assertCaptured "(postinstall script)"
assertNotCaptured "Restoring node modules"
assertNotCaptured "Pruning unused dependencies"
assertNotCaptured "Installing any new modules"
assertNotCaptured "Installing node modules"
assertNotCaptured "Deduping dependency tree"
assertCapturedSuccess assertCapturedSuccess
} }
testUserConfig() { testUserConfig() {
compile "userconfig" compile "userconfig"
assertCaptured "https://www.google.com/" assertCaptured "www.google.com"
assertCaptured "registry error" assertCaptured "registry error"
assertCapturedError 1 "" assertCapturedError 1 ""
} }
testProcfile() {
compile "procfile-present-only"
assertCaptured "Start mechanism: Procfile"
assertNotCaptured "new Procfile"
assertCapturedSuccess
}
testProcfileAbsentNpmStartPresent() { testProcfileAbsentNpmStartPresent() {
compile "procfile-absent-npm-start-present" compile "procfile-absent-npm-start-present"
assertCaptured "No Procfile found; Adding npm start to new Procfile" assertCaptured "Start mechanism: npm start"
assertCaptured "Adding 'web: npm start' to new Procfile"
assertFile "web: npm start" "Procfile" assertFile "web: npm start" "Procfile"
assertCapturedSuccess assertCapturedSuccess
} }
testProcfileAbsentNpmStartAbsent() { testProcfileAbsentNpmStartAbsent() {
compile "procfile-absent-npm-start-absent" compile "procfile-absent-npm-start-absent"
assertCaptured "Create a Procfile or specify a start script in package.json" assertCaptured "Start mechanism: none"
assertNotCaptured "new Procfile"
assertCapturedSuccess assertCapturedSuccess
} }
testProcfileAbsentNpmStartPresent() { testProcfileAbsentServerPresent() {
compile "procfile-absent-npm-start-present" compile "procfile-absent-server-present"
assertCaptured "No Procfile found; Adding npm start to new Procfile" assertCaptured "Start mechanism: server.js"
assertFile "web: npm start" "Procfile" assertCaptured "'web: node server.js' to new Procfile"
assertFile "web: node server.js" "Procfile"
assertCapturedSuccess assertCapturedSuccess
} }
testEnvDirNotImported() { testServerPresentOnly() {
compile "stable-node" compile "server-present-only"
assertNotCaptured "Exporting config vars to environment" assertCaptured "PRO TIP: Use 'npm init'"
assertCaptured "Skipping dependencies"
assertCaptured "'web: node server.js' to new Procfile"
assertFile "web: node server.js" "Procfile"
assertCapturedSuccess assertCapturedSuccess
} }
testEnvDirExported() { testEnvVars() {
env_dir=$(mktmpdir) env_dir=$(mktmpdir)
echo "chicken" > $env_dir/birds echo "false" > $env_dir/NPM_CONFIG_PRODUCTION
echo "koi" > $env_dir/fish
compile "stable-node" "$(mktmpdir)" $env_dir compile "stable-node" "$(mktmpdir)" $env_dir
assertCaptured "Exporting config vars to environment" assertCaptured "NPM_CONFIG_PRODUCTION=false"
assertCapturedSuccess
}
testNoEnvVars() {
env_dir=$(mktmpdir)
compile "stable-node" "$(mktmpdir)" $env_dir
assertCaptured "NPM_CONFIG_PRODUCTION=true"
assertCapturedSuccess
}
testNoDevDependencies() {
compile "dev-dependencies"
assertNotCaptured "lodash"
assertCapturedSuccess assertCapturedSuccess
} }
testDevDependencies() {
env_dir=$(mktmpdir)
echo "false" > $env_dir/NPM_CONFIG_PRODUCTION
compile "dev-dependencies" "$(mktmpdir)" $env_dir
assertCaptured "lodash"
assertCapturedSuccess
}
testOptionalDependencies() {
env_dir=$(mktmpdir)
#echo "true" > $env_dir/NPM_CONFIG_OPTIONAL
compile "optional-dependencies" "$(mktmpdir)" $env_dir
assertNotCaptured "NPM_CONFIG_OPTIONAL"
assertCaptured "less"
assertCaptured "mime"
assertCaptured "mkdirp"
assertCaptured "clean-css"
assertCaptured "request"
assertCapturedSuccess
}
testNoOptionalDependencies() {
env_dir=$(mktmpdir)
echo "false" > $env_dir/NPM_CONFIG_OPTIONAL
compile "optional-dependencies" "$(mktmpdir)" $env_dir
assertCaptured "NPM_CONFIG_OPTIONAL=false"
assertCaptured "less"
assertNotCaptured "mime"
assertNotCaptured "mkdirp"
assertNotCaptured "clean-css"
assertNotCaptured "request"
assertCapturedSuccess
}
testDisableCache() {
cache=$(mktmpdir)
env_dir=$(mktmpdir)
compile "node-modules-cache-1" $cache
assertCaptured "lodash@1.0.0"
assertCapturedSuccess
compile "node-modules-cache-2" $cache
assertCaptured "lodash@1.0.0"
assertCapturedSuccess
echo "false" > $env_dir/NODE_MODULES_CACHE
compile "node-modules-cache-2" $cache $env_dir
assertCaptured "lodash@1.3.1"
assertCapturedSuccess
}
testNpmrc() {
compile "dev-dependencies"
assertNotCaptured "lodash"
assertCapturedSuccess
compile "dev-dependencies-npmrc"
assertCaptured "lodash"
assertCapturedSuccess
}
testShrinkwrap() {
compile "shrinkwrap"
assertCaptured "express@4.10.4"
assertCaptured "lodash@2.4.0"
assertNotCaptured "mocha"
assertCapturedSuccess
}
testNpmVersionRange() {
compile "npm-version-range"
assertCaptured "Resolving npm version"
assertCaptured "installing npm 1.4."
assertCapturedSuccess
}
testNpmVersionSpecific() {
compile "npm-version-specific"
assertCaptured "installing npm 2.1.11"
assertNotCaptured "Resolving npm version"
assertCapturedSuccess
}
testProfileExport() {
compile "stable-node"
assertCaptured "Creating runtime environment"
assertFileContains "export PATH=\"\$HOME/.heroku/node/bin:\$HOME/bin:\$HOME/node_modules/.bin:\$PATH\"" "${compile_dir}/.profile.d/nodejs.sh"
assertFileContains "export NODE_HOME=\"\$HOME/.heroku/node\"" "${compile_dir}/.profile.d/nodejs.sh"
assertCapturedSuccess
}
testMultiExport() {
compile "stable-node"
assertFileContains "export PATH=" "${bp_dir}/export"
assertFileContains "/.heroku/node/bin:" "${bp_dir}/export"
assertFileContains "/node_modules/.bin:\$PATH" "${bp_dir}/export"
assertFileContains "export NODE_HOME=" "${bp_dir}/export"
assertFileContains "/.heroku/node\"" "${bp_dir}/export"
assertCapturedSuccess
}
# Utils # Utils
pushd $(dirname 0) >/dev/null pushd $(dirname 0) >/dev/null
......
if [ "$node_engine" == "" ]; then
protip "Specify a node version in package.json" "https://devcenter.heroku.com/articles/nodejs-support#specifying-a-node-js-version"
elif [ "$node_engine" == "*" ]; then
protip "Avoid semver ranges like '*' in engines.node" "https://devcenter.heroku.com/articles/nodejs-support#specifying-a-node-js-version"
elif [ ${node_engine:0:1} == ">" ]; then
protip "Avoid semver ranges starting with '>' in engines.node" "https://devcenter.heroku.com/articles/nodejs-support#specifying-a-node-js-version"
fi
if [ "$modules_source" == "prebuilt" ]; then
protip "Avoid checking node_modules into source control" "https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-"
elif [ "$modules_source" == "" ]; then
protip "Use 'npm init' and 'npm install --save' to define dependencies"
fi
if [ "$start_method" == "" ]; then
protip "Include a Procfile, package.json start script, or server.js file to start your app" "https://devcenter.heroku.com/articles/nodejs-support#runtime-behavior"
fi
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository": {
"type": "git",
"url": "http://github.com/example/example.git"
},
"dependencies": {
},
"engines": {
"node": "~0.10.0"
},
"devDependencies": {
"lodash": "^2.4.1"
}
}
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository": {
"type": "git",
"url": "http://github.com/example/example.git"
},
"dependencies": {
"hashish": "*"
},
"engines": {
"node": "~0.10.0"
},
"devDependencies": {
"lodash": "^2.4.1"
}
}
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"dependencies": {
"hashish": "*"
},
"engines": {
}
}
...@@ -6,6 +6,10 @@ ...@@ -6,6 +6,10 @@
"type" : "git", "type" : "git",
"url" : "http://github.com/example/example.git" "url" : "http://github.com/example/example.git"
}, },
"scripts": {
"preinstall": "echo '(preinstall script)'",
"postinstall": "echo '(postinstall script)'"
},
"dependencies": { "dependencies": {
"hashish": "*" "hashish": "*"
}, },
......
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository": {
"type": "git",
"url": "http://github.com/example/example.git"
},
"dependencies": {
"lodash": "1.0.0"
},
"engines": {
"node": "~0.10.0"
}
}
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository": {
"type": "git",
"url": "http://github.com/example/example.git"
},
"dependencies": {
"lodash": "^1.0.0"
},
"engines": {
"node": "~0.10.0"
}
}
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"engines": {
"node": "0.10.33",
"npm": "1.4.x"
}
}
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"dependencies": {
"hashish": "*"
},
"engines": {
"node": "~0.10.0",
"npm": "2.1.11"
}
}
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"dependencies": {
"less": "1.7.0"
},
"engines": {
"node": "0.10.x"
},
"scripts": {
"start": "node foo.js"
}
}
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"dependencies": {
"hashish": "*"
},
"engines": {
"node": "~0.10.0"
}
}
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"dependencies": {
"express": {
"version": "4.10.4",
"from": "express@4.10.4",
"resolved": "https://registry.npmjs.org/express/-/express-4.10.4.tgz",
"dependencies": {
"accepts": {
"version": "1.1.3",
"from": "accepts@~1.1.3",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.1.3.tgz",
"dependencies": {
"mime-types": {
"version": "2.0.3",
"from": "mime-types@~2.0.3",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.3.tgz",
"dependencies": {
"mime-db": {
"version": "1.2.0",
"from": "mime-db@~1.2.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.2.0.tgz"
}
}
},
"negotiator": {
"version": "0.4.9",
"from": "negotiator@0.4.9",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.9.tgz"
}
}
},
"content-disposition": {
"version": "0.5.0",
"from": "content-disposition@0.5.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.0.tgz"
},
"cookie-signature": {
"version": "1.0.5",
"from": "cookie-signature@1.0.5",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.5.tgz"
},
"debug": {
"version": "2.1.0",
"from": "debug@~2.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.1.0.tgz",
"dependencies": {
"ms": {
"version": "0.6.2",
"from": "ms@0.6.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz"
}
}
},
"depd": {
"version": "1.0.0",
"from": "depd@~1.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.0.0.tgz"
},
"escape-html": {
"version": "1.0.1",
"from": "escape-html@1.0.1",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz"
},
"etag": {
"version": "1.5.1",
"from": "etag@~1.5.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.5.1.tgz",
"dependencies": {
"crc": {
"version": "3.2.1",
"from": "crc@3.2.1",
"resolved": "https://registry.npmjs.org/crc/-/crc-3.2.1.tgz"
}
}
},
"finalhandler": {
"version": "0.3.2",
"from": "finalhandler@0.3.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.3.2.tgz"
},
"fresh": {
"version": "0.2.4",
"from": "fresh@0.2.4",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.2.4.tgz"
},
"media-typer": {
"version": "0.3.0",
"from": "media-typer@0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"
},
"methods": {
"version": "1.1.0",
"from": "methods@1.1.0",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.0.tgz"
},
"on-finished": {
"version": "2.1.1",
"from": "on-finished@~2.1.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.1.1.tgz",
"dependencies": {
"ee-first": {
"version": "1.1.0",
"from": "ee-first@1.1.0",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.0.tgz"
}
}
},
"parseurl": {
"version": "1.3.0",
"from": "parseurl@~1.3.0",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.0.tgz"
},
"path-to-regexp": {
"version": "0.1.3",
"from": "path-to-regexp@0.1.3",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.3.tgz"
},
"proxy-addr": {
"version": "1.0.4",
"from": "proxy-addr@~1.0.4",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.4.tgz",
"dependencies": {
"forwarded": {
"version": "0.1.0",
"from": "forwarded@~0.1.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz"
},
"ipaddr.js": {
"version": "0.1.5",
"from": "ipaddr.js@0.1.5",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-0.1.5.tgz"
}
}
},
"qs": {
"version": "2.3.3",
"from": "qs@2.3.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz"
},
"range-parser": {
"version": "1.0.2",
"from": "range-parser@~1.0.2",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.2.tgz"
},
"send": {
"version": "0.10.1",
"from": "send@0.10.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.10.1.tgz",
"dependencies": {
"destroy": {
"version": "1.0.3",
"from": "destroy@1.0.3",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.3.tgz"
},
"mime": {
"version": "1.2.11",
"from": "mime@1.2.11",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
},
"ms": {
"version": "0.6.2",
"from": "ms@0.6.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz"
}
}
},
"serve-static": {
"version": "1.7.1",
"from": "serve-static@~1.7.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.7.1.tgz"
},
"type-is": {
"version": "1.5.3",
"from": "type-is@~1.5.3",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.5.3.tgz",
"dependencies": {
"mime-types": {
"version": "2.0.3",
"from": "mime-types@~2.0.3",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.3.tgz",
"dependencies": {
"mime-db": {
"version": "1.2.0",
"from": "mime-db@~1.2.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.2.0.tgz"
}
}
}
}
},
"vary": {
"version": "1.0.0",
"from": "vary@~1.0.0",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.0.0.tgz"
},
"cookie": {
"version": "0.1.2",
"from": "cookie@0.1.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.2.tgz"
},
"merge-descriptors": {
"version": "0.0.2",
"from": "merge-descriptors@0.0.2",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-0.0.2.tgz"
},
"utils-merge": {
"version": "1.0.0",
"from": "utils-merge@1.0.0",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz"
}
}
},
"lodash": {
"version": "2.4.0",
"from": "lodash@2.4.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.0.tgz"
}
}
}
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository": {
"type": "git",
"url": "http://github.com/example/example.git"
},
"dependencies": {
"express": "4.10.4",
"lodash": "^2.4.0"
},
"engines": {
"node": "~0.10.0"
},
"devDependencies": {
"mocha": "2.0.1"
}
}
A fake README, to keep npm from polluting stderr.
\ No newline at end of file
{
"name": "node-buildpack-test-app",
"version": "0.0.1",
"description": "node buildpack integration test app",
"repository" : {
"type" : "git",
"url" : "http://github.com/example/example.git"
},
"dependencies": {
"hashish": "*"
},
"engines": {
"node": "0.10.29"
}
}
...@@ -11,5 +11,8 @@ ...@@ -11,5 +11,8 @@
}, },
"engines": { "engines": {
"node": "~0.10.0" "node": "~0.10.0"
},
"scripts": {
"start": "node foo.js"
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment