├── .gitignore ├── .npmignore ├── README.md ├── bin └── cli.js ├── index.js ├── package.json ├── templates └── package.json └── tests └── versions.sh /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.tgz 3 | package/ 4 | 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tests/ 2 | package/ 3 | *.tgz 4 | .npmbundle/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) 2 | # npm-bundle 3 | 4 | Similar to `npm pack` but includes packages in the dependencies section of 5 | the package.json. 6 | 7 | If you wish to include dependencies and use `npm-pack` you must do the 8 | following: 9 | 10 | 11 | 1. create bundledDependencies section in package.json 12 | 2. remember to update bundledDependencies before executing `npm pack` 13 | 3. remember to execute `npm install` before executing `npm pack` 14 | 4. remember to execute `npm install --legacy-bundling` when using npm v3.x 15 | because deduped dependencies will not be included. 16 | 5. remember that `npm install --legacy-bundling` is not available in npm v3 17 | .x < v3.5 18 | 19 | There must be a better way... 20 | 21 | ## Prerequisites 22 | 23 | * node v0.10 or later 24 | * npm v1.x, v2.x or npm > v3.5 (npm v3 less than 3.5 does not support disabling 25 | dedup) 26 | 27 | ## Install 28 | 29 | npm install -g npm-bundle 30 | 31 | 32 | ## CLI Usage 33 | You can use the same arguments and options as [`npm install`][1]. There is an 34 | additional --verbose option to help with debugging issues. 35 | 36 | # The current directory containing a package.json 37 | npm-bundle 38 | 39 | # Verbose, useful for debugging errors 40 | npm-bundle --verbose 41 | 42 | # A tarball in the current directory 43 | npm-bundle something-1.0.0.tgz 44 | 45 | # A package from the registry 46 | npm-bundle request 47 | 48 | # A tarball url 49 | npm-bundle https://github.com/indexzero/node-portfinder/archive/v0.4.0.tar.gz 50 | 51 | # Specify a private registry 52 | npm-bundle secretPackage --registry=http://private.something.com/npm 53 | 54 | ## Programmatic Usage 55 | 56 | var npmBundle = require('npm-bundle') 57 | var args = [] 58 | var options = { 59 | verbose: true 60 | } 61 | 62 | npmBundle(args, options, function onNpmBundle (error, output) { 63 | if (error) { 64 | throw error 65 | } 66 | process.stdout.write(output.file) 67 | }) 68 | 69 | The given callback receives an error parameter and an output object parameter. 70 | 71 | The output object will have the following properties: 72 | 73 | * **file** - output from npm pack executed on temporary install directory 74 | 75 | 76 | ## Behind the Scenes 77 | 78 | The install is happening in the `.npmbundle` temporary directory, so only use 79 | npm install options relevant for that directory. 80 | 81 | The npm executable (required to be on your path) does the heavy lifting to 82 | ensure behavior is consistent with what you expect from npm. 83 | 84 | Here is a simplified view of the workflow: 85 | 86 | * `cd .npmbundle` 87 | * `npm install --production --legacy-bundling` 88 | * set `bundledDependencies` in ` 89 | .npmbundle/node_modules//package.json` 90 | * `cd startDir` 91 | * `npm pack .npmbundle/node_modules/` 92 | 93 | 94 | ## Differences from `npm pack` 95 | 96 | 1. The entire dependency tree (legacy, not deduped) is included in the output 97 | tarball 98 | 2. The additional arguments of `npm install`, ie. a tarball url 99 | 3. The additional options of `npm install`, ie. --registry=http://something 100 | 4. The package.json in the output tarball has npm install metadata 101 | 5. --verbose option for help with debugging 102 | 6. All three publish scripts are prefixed with an underscore in the package 103 | .json in the output tarball (a workaround to ensure they are only run once) 104 | 105 | 106 | ## Changelog 107 | 108 | * v3.0.3 109 | * Fixed [#10](https://github.com/majgis/npm-bundle/issues/10): The .npmrc 110 | is ignored when bundling directories 111 | 112 | * v3.0.1 113 | * run-scripts issue fixed by disabling publish scripts prior to `npm pack` 114 | * engine corrected to be Node.js v0.10 115 | 116 | * v3.0.0 117 | * The contents is no longer output, use `tar -tvf something.tgz` instead 118 | 119 | * v2.0.4 120 | * .npmbundle folder is no longer included in the output file 121 | 122 | * v2.0.3 123 | * The issue with options not being passed was fixed. 124 | 125 | * v2.0.0 126 | * Everything is now executed asynchronously 127 | * Support for node v0.10 128 | 129 | * v1.1.1 130 | * Show list of included files and folders 131 | 132 | 133 | [1]:https://docs.npmjs.com/cli/install 134 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var npmBundle = require('../index.js') 4 | var args = process.argv.slice(2) 5 | var options = { 6 | verbose: process.argv.indexOf('--verbose') > -1 7 | } 8 | 9 | npmBundle(args, options, function onNpmBundle (error, output) { 10 | if (error) { 11 | throw error 12 | } 13 | if (output) { 14 | process.stdout.write(output.file) 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var exec = require('child_process').exec 3 | var fs = require('fs') 4 | var TEMP_DIR = '.npmbundle' + path.sep 5 | var rimraf = require('rimraf') 6 | var ncp = require('ncp') 7 | var glob = require('glob') 8 | var async = require('insync') 9 | var mkdirp = require('mkdirp') 10 | var NPM_VERSION_ERROR = 'Error: Unable to install deduped dependencies.\n' + 11 | 'If you are using npm v3, make sure it is v3.5 or later.' 12 | var scriptNames = [ 13 | 'publish', 14 | 'prepublish', 15 | 'postpublish' 16 | ] 17 | 18 | function bundleDependencies (pkg, next) { 19 | if (pkg.dependencies) { 20 | pkg.bundledDependencies = Object.keys(pkg.dependencies) 21 | } 22 | next(null, pkg) 23 | } 24 | 25 | function disableScripts (pkg, next) { 26 | var scripts = pkg.scripts 27 | if (scripts) { 28 | for (var i = 0; i < scriptNames.length; i++) { 29 | var scriptName = scriptNames[i] 30 | if (scripts[scriptName]) { 31 | scripts['_' + scriptName] = scripts[scriptName] 32 | delete scripts[scriptName] 33 | } 34 | } 35 | } 36 | next(null, pkg) 37 | } 38 | 39 | function cd (dir, next) { 40 | var error 41 | try { 42 | process.chdir(dir) 43 | } catch (e) { 44 | error = e 45 | } 46 | next(error) 47 | } 48 | 49 | function resolvePath (value, next) { 50 | fs.exists(value, function onResolvePath (exists) { 51 | if (exists) { 52 | next(null, path.resolve(value)) 53 | } else { 54 | next(null, value) 55 | } 56 | }) 57 | } 58 | 59 | function outputData (data) { 60 | console.log(data) 61 | } 62 | 63 | function npmInstall (verbose, options, installable, next) { 64 | options = options.length ? ' ' + options.join(' ') : '' 65 | var command = 'npm i ' + installable + options + ' --legacy-bundling' 66 | var process = exec(command, function onNpmInstall (error, stdout) { 67 | next(error, stdout) 68 | }) 69 | if (verbose) { 70 | process.stdout.on('data', outputData) 71 | } 72 | } 73 | 74 | function npmPack (verbose, packable, next) { 75 | var command = 'npm pack ' + packable 76 | var process = exec(command, function onNpmPack (error, stdout) { 77 | next(error, stdout) 78 | }) 79 | if (verbose) { 80 | process.stdout.on('data', outputData) 81 | } 82 | } 83 | 84 | function flatten (value, next) { 85 | next(null, value[0]) 86 | } 87 | 88 | function ignoreValue (value, next) { 89 | next(null) 90 | } 91 | 92 | function pwd (next) { 93 | next(null, process.cwd()) 94 | } 95 | 96 | function storeValue (context, key, value, next) { 97 | context[key] = value 98 | next(null) 99 | } 100 | 101 | function checkLength (packages, next) { 102 | if (packages.length > 1) { 103 | throw new Error(NPM_VERSION_ERROR) 104 | } 105 | next(null, packages) 106 | } 107 | 108 | function getValue (context, key, next) { 109 | next(null, context[key]) 110 | } 111 | 112 | function splitArgAndOptions (argAndOptions) { 113 | argAndOptions = argAndOptions.slice() 114 | var argIndex = 0 115 | var firstArg = argAndOptions[argIndex] 116 | while (firstArg && firstArg.indexOf('-') === 0) { 117 | argIndex += 1 118 | firstArg = argAndOptions[argIndex] 119 | } 120 | 121 | if (firstArg) { 122 | argAndOptions.splice(argIndex, argIndex + 1) 123 | } else { 124 | firstArg = process.cwd() 125 | } 126 | 127 | var result = { 128 | arg: firstArg, 129 | options: argAndOptions 130 | } 131 | 132 | return result 133 | } 134 | 135 | function jsonParse (data, next) { 136 | next(null, JSON.parse(data)) 137 | } 138 | 139 | function jsonStringify (obj, next) { 140 | next(null, JSON.stringify(obj, null, 2)) 141 | } 142 | 143 | //Copy .npmrc only if installable is directory with .npmrc 144 | function copyNpmrc (tempDir, installable, next) { 145 | var npmrcPath = installable + '/.npmrc' 146 | fs.exists(npmrcPath, function onNpmrcExists (exists) { 147 | if (exists) { 148 | ncp(npmrcPath, tempDir + '/.npmrc', function onCopyNpmrc (err) { 149 | next(err) 150 | }) 151 | } else { 152 | next() 153 | } 154 | }) 155 | } 156 | 157 | function npmBundle (args, options, cb) { 158 | var argAndOptions = splitArgAndOptions(args) 159 | var verbose = options.verbose || false 160 | var startDir = process.cwd() + path.sep 161 | var tempDir = startDir + TEMP_DIR 162 | var templateDir = __dirname + path.sep + 'templates' 163 | var context = { 164 | installable: null, 165 | output: { 166 | contents: null, 167 | file: null 168 | } 169 | } 170 | 171 | async.waterfall([ 172 | rimraf.bind(null, tempDir), 173 | mkdirp.bind(null, tempDir), 174 | ignoreValue, 175 | resolvePath.bind(null, argAndOptions.arg), 176 | storeValue.bind(null, context, 'installable'), 177 | ncp.bind(null, templateDir, tempDir), 178 | getValue.bind(null, context, 'installable'), 179 | copyNpmrc.bind(null, tempDir), 180 | cd.bind(null, tempDir), 181 | getValue.bind(null, context, 'installable'), 182 | npmInstall.bind(null, verbose, argAndOptions.options), 183 | ignoreValue, 184 | cd.bind(null, 'node_modules'), 185 | glob.bind(null, '*'), 186 | checkLength, 187 | flatten, 188 | cd, 189 | rimraf.bind(null, '.npmbundle'), 190 | fs.readFile.bind(null, 'package.json', 'utf8'), 191 | jsonParse, 192 | bundleDependencies, 193 | disableScripts, 194 | jsonStringify, 195 | fs.writeFile.bind(null, 'package.json'), 196 | pwd, 197 | storeValue.bind(null, context, 'packable'), 198 | cd.bind(null, startDir), 199 | getValue.bind(null, context, 'packable'), 200 | npmPack.bind(null, verbose), 201 | storeValue.bind(null, context.output, 'file'), 202 | rimraf.bind(null, tempDir), 203 | getValue.bind(null, context, 'output') 204 | ], cb) 205 | } 206 | 207 | module.exports = npmBundle 208 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-bundle", 3 | "version": "3.0.3", 4 | "description": "npm pack with dependencies included", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "sh tests/versions.sh", 8 | "lint": "standard", 9 | "standard": "standard --format" 10 | }, 11 | "author": "Michael Jackson ", 12 | "license": "ISC", 13 | "bin": { 14 | "npm-bundle": "./bin/cli.js" 15 | }, 16 | "dependencies": { 17 | "glob": "^6.0.1", 18 | "insync": "^2.1.1", 19 | "mkdirp": "^0.5.1", 20 | "ncp": "^2.0.0", 21 | "rimraf": "^2.4.4" 22 | }, 23 | "directories": { 24 | "test": "tests" 25 | }, 26 | "devDependencies": { 27 | "standard": "^5.4.1" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/majgis/npm-bundle.git" 32 | }, 33 | "keywords": [ 34 | "npm", 35 | "pack" 36 | ], 37 | "bugs": { 38 | "url": "https://github.com/majgis/npm-bundle/issues" 39 | }, 40 | "homepage": "https://github.com/majgis/npm-bundle#readme", 41 | "engine": "node >= 0.10.0" 42 | } 43 | -------------------------------------------------------------------------------- /templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "temp", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | -------------------------------------------------------------------------------- /tests/versions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npm uninstall -g npm-bundle 4 | npm link 5 | 6 | source ~/.profile_nvm 7 | cd `dirname $0` 8 | 9 | nvm install 0.10 10 | echo 11 | node ../bin/cli.js ../ 12 | echo 13 | 14 | nvm install 0.12 15 | echo 16 | node ../bin/cli.js ../ 17 | echo 18 | 19 | nvm install 4 20 | echo 21 | node ../bin/cli.js ../ 22 | echo 23 | 24 | nvm install 5 25 | npm i -g npm 26 | echo 27 | node ../bin/cli.js ../ 28 | echo 29 | 30 | npm uninstall -g npm-bundle 31 | echo 32 | 33 | rm -rf *.tgz --------------------------------------------------------------------------------