Skip to content

Commit ea6e446

Browse files
committed
Initial commit
1 parent 85c94ce commit ea6e446

File tree

11 files changed

+404
-0
lines changed

11 files changed

+404
-0
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
**/node_modules

.eslintrc.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module.exports = {
2+
root: true,
3+
env: {
4+
browser: true,
5+
node: true,
6+
commonjs: true,
7+
es6: true,
8+
jest: true
9+
},
10+
extends: ["eslint:recommended", "eslint-config-prettier"],
11+
parserOptions: {
12+
ecmaVersion: 2017,
13+
sourceType: "module"
14+
},
15+
plugins: ["eslint-plugin-prettier", "json"],
16+
rules: {
17+
"prettier/prettier": "error",
18+
"no-console": "off",
19+
"no-useless-escape": "off", // makes useless escapes warnings instead of errors
20+
"prefer-const": "warn",
21+
"prefer-template": "warn"
22+
}
23+
};

.gitignore

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Created by .ignore support plugin (hsz.mobi)
2+
### Node template
3+
# Logs
4+
logs
5+
*.log
6+
npm-debug.log*
7+
yarn-debug.log*
8+
yarn-error.log*
9+
10+
# Runtime data
11+
pids
12+
*.pid
13+
*.seed
14+
*.pid.lock
15+
16+
# Directory for instrumented libs generated by jscoverage/JSCover
17+
lib-cov
18+
19+
# Coverage directory used by tools like istanbul
20+
coverage
21+
22+
# nyc test coverage
23+
.nyc_output
24+
25+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26+
.grunt
27+
28+
# Bower dependency directory (https://bower.io/)
29+
bower_components
30+
31+
# node-waf configuration
32+
.lock-wscript
33+
34+
# Compiled binary addons (https://nodejs.org/api/addons.html)
35+
build/Release
36+
37+
# Dependency directories
38+
node_modules/
39+
jspm_packages/
40+
41+
# TypeScript v1 declaration files
42+
typings/
43+
44+
# Optional npm cache directory
45+
.npm
46+
47+
# Optional eslint cache
48+
.eslintcache
49+
50+
# Optional REPL history
51+
.node_repl_history
52+
53+
# Output of 'npm pack'
54+
*.tgz
55+
56+
# Yarn Integrity file
57+
.yarn-integrity
58+
59+
# dotenv environment variables file
60+
.env
61+
62+
# parcel-bundler cache (https://parceljs.org/)
63+
.cache
64+
65+
# next.js build output
66+
.next
67+
68+
# nuxt.js build output
69+
.nuxt
70+
71+
# vuepress build output
72+
.vuepress/dist
73+
74+
# Serverless directories
75+
.serverless
76+
.npm-pack-all-tmp
77+

.prettierrc.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
tabWidth: 4,
3+
endOfLine: "auto",
4+
printWidth: 130
5+
};

.travis.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
language: node_js
2+
node_js: node
3+
before_install:
4+
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version version-number
5+
- export PATH="$HOME/.yarn/bin:$PATH"
6+
cache: yarn

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,45 @@
11
# npm-pack-all
2+
[![Build Status](https://travis-ci.org/kleingtm/npm-pack-all.svg?branch=master)](https://travis-ci.org/kleingtm/npm-pack-all)
3+
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
24
A simple utility to package all node_modules dependencies when running `npm pack` (not devDependencies)
5+
6+
This can be useful when wanting to ship dependencies as part of the artifact -- side stepping the case
7+
8+
npm-pack-all utility does the following:
9+
10+
1. Removes devDependencies from the local node_modules folder (only packs production dependencies)
11+
12+
+ npm: `npm prune --production`
13+
+ yarn: `yarn install --production` (yarn install prunes automatically)
14+
15+
2. Makes copies of the untouched package.json and lock files
16+
3. Adds all dependencies as `bundledDependencies` in the active package.json
17+
4. Calls `npm pack` (`yarn pack` does not work with bundledDependenies as of 6/21/19)
18+
19+
+ The following will be packed into a .tgz archive:
20+
21+
+ Any files (via glob) called out in the package.json `files` field
22+
+ All node_modules that are production dependencies (not devDependencies)
23+
24+
5. Restores the untouched package.json and lock files
25+
26+
27+
28+
## Install
29+
```bash
30+
npm install npm-pack-all
31+
32+
```
33+
OR
34+
35+
```bash
36+
yarn add npm-pack-all
37+
38+
```
39+
40+
## Use
41+
```bash
42+
43+
44+
```
45+

__tests__/index.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* no flags -- should pass and name the artifact with pj name and
3+
* --output <nothing> --dev-deps (should fail due to output. check message)
4+
* */
5+
6+
const minimist = require(`minimist`);
7+
const fs = require(`fs`);
8+
const path = require(`path`);
9+
10+
const TEST_SUITE = `npm-pack-all: ${__filename}`;
11+
12+
beforeEach(() => {
13+
jest.resetModules();
14+
});
15+
16+
afterEach(() => {
17+
jest.restoreAllMocks();
18+
});
19+
20+
afterAll(() => {
21+
console.info(`Success: ${TEST_SUITE}`);
22+
});
23+
24+
describe(TEST_SUITE, () => {
25+
test("Can run script without flags", () => {
26+
const mockArg = minimist(``.split(` `));
27+
28+
// mock input arguments
29+
jest.mock(`minimist`, () => {
30+
return jest.fn(() => mockArg);
31+
});
32+
33+
// call script
34+
require(`../index`);
35+
36+
const packageJson = require(path.join(process.cwd(), `package.json`));
37+
expect(fs.existsSync(`${packageJson.name}-${packageJson.version}.tgz`)).toBeTruthy();
38+
});
39+
});

index.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
const fs = require(`fs`);
2+
const path = require(`path`);
3+
const shell = require(`shelljs`);
4+
5+
const { CliError, safetyDecorator, shellExecDecorator } = require(path.join(__dirname, `./utils/utils.index`));
6+
7+
const cp = safetyDecorator(shell.cp);
8+
const mv = safetyDecorator(shell.mv);
9+
const exec = shellExecDecorator(shell.exec);
10+
11+
const packageManager = determinePackageManager();
12+
13+
const FILES_TO_BACKUP = [`package.json`, `package-lock.json`, `yarn.lock`];
14+
const TMP_DIRECTORY = path.join(process.cwd(), `.npm-pack-all-tmp`);
15+
16+
console.info(`
17+
.------..------..------..------..------..------..------..------..------..------..------..------.
18+
|N.--. ||P.--. ||M.--. ||-.--. ||P.--. ||A.--. ||C.--. ||K.--. ||-.--. ||A.--. ||L.--. ||L.--. |
19+
| :(): || :/\\: || (\\/) || (\\/) || :/\\: || (\\/) || :/\\: || :/\\: || (\\/) || (\\/) || :/\\: || :/\\: |
20+
| ()() || (__) || :\\/: || :\\/: || (__) || :\\/: || :\\/: || :\\/: || :\\/: || :\\/: || (__) || (__) |
21+
| '--'N|| '--'P|| '--'M|| '--'-|| '--'P|| '--'A|| '--'C|| '--'K|| '--'-|| '--'A|| '--'L|| '--'L|
22+
\`------'\`------'\`------'\`------'\`------'\`------'\`------'\`------'\`------'\`------'\`------'\`------'\n`);
23+
24+
// parse cli args
25+
const cliArgs = require(`minimist`)(process.argv.slice(2));
26+
if (typeof cliArgs.output !== `string` && cliArgs.output) {
27+
throw new CliError(`--output`, cliArgs.output, `The \`--output\` flag requires a string filename`);
28+
}
29+
30+
const packageJson = require(path.join(process.cwd(), `package.json`));
31+
32+
shell.config.fatal = true; // error out if a shell command errors out
33+
shell.config.silent = true;
34+
35+
// create temp directory
36+
createTempDirectory(TMP_DIRECTORY);
37+
38+
// copy existing package.json and lock files (keep linting, etc in tact)
39+
console.info(`Saving existing package.json and lock files`);
40+
copyFiles(process.cwd(), TMP_DIRECTORY, FILES_TO_BACKUP);
41+
42+
// set bundledDependencies in package.json
43+
const CMDs = {
44+
prune: {
45+
npm: `npm prune --production && npm install --production`, // removes dev deps
46+
yarn: `yarn install --production` // prunes automatically
47+
},
48+
install: {
49+
npm: `npm install --force`,
50+
yarn: `yarn install --force`
51+
}
52+
};
53+
setBundledDependencies(packageJson, CMDs, packageManager);
54+
55+
// pack with npm
56+
console.info(`\nPacking source code${!cliArgs[`dev-deps`] ? `` : `, development`} and production dependencies...`);
57+
exec(`npm -dd pack`, { silent: false, timeout: cliArgs.timeout || 3 * 60 * 1000 }); // 3 min timeout
58+
59+
// restoring package.json and lock files back to project root
60+
console.info(`Restoring original package.json and lock files`);
61+
moveFiles(TMP_DIRECTORY, process.cwd(), FILES_TO_BACKUP);
62+
shell.rm(`-Rf`, TMP_DIRECTORY);
63+
64+
setArtifactName(cliArgs);
65+
66+
function createTempDirectory(dir) {
67+
shell.rm(`-Rf`, dir);
68+
shell.mkdir("-p", dir);
69+
}
70+
71+
function determinePackageManager() {
72+
const yarnLockExists = fs.existsSync(path.join(process.cwd(), `yarn.lock`));
73+
const npmLockExists = fs.existsSync(path.join(process.cwd(), `package-lock.json`));
74+
75+
// if only yarn.lock exists, convert yarn.lock to package-lock.json (we'll use npm)
76+
let result = `npm`;
77+
if (yarnLockExists && !npmLockExists) {
78+
result = `yarn`;
79+
}
80+
return result;
81+
}
82+
83+
function setBundledDependencies(pj, cmds, packageManager) {
84+
// prune - get rid of devDependencies
85+
pj.bundledDependencies = Object.keys(pj.dependencies);
86+
if (!cliArgs[`dev-deps`]) {
87+
console.info(`Pruning node_modules for packing production dependencies only...`);
88+
exec(cmds.prune[packageManager]); // cmd based on package manager. based on existing lockfile. npm takes precedence if both exist
89+
} else {
90+
console.info(`Detected --dev-deps\n\nInstalling all modules...`);
91+
console.warn(`To save time and space, you may want to think about only packaging production dependencies`);
92+
exec(cmds.install[packageManager]); // cmd based on package manager. based on existing lockfile. npm takes precedence if both exist
93+
pj.bundledDependencies = pj.bundledDependencies.concat(Object.keys(pj.devDependencies));
94+
}
95+
96+
// put dependencies into bundledDependencies in package.json
97+
console.info(`Adding dependencies${cliArgs["dev-deps"] ? " and devDependencies" : ""} to bundledDependencies`);
98+
fs.writeFileSync(path.join(process.cwd(), `package.json`), JSON.stringify(packageJson, null, 4));
99+
}
100+
101+
function setArtifactName(args) {
102+
if (args.output) {
103+
shell.mv(
104+
`-f`,
105+
path.join(process.cwd(), `${packageJson.name}-${packageJson.version}.tgz`),
106+
path.join(process.cwd(), cliArgs.output)
107+
);
108+
}
109+
}
110+
111+
function copyFiles(from, to, files) {
112+
files.forEach(file => {
113+
cp(`-Rf`, path.join(from, file), path.join(to, file));
114+
});
115+
}
116+
117+
function moveFiles(from, to, files) {
118+
files.forEach(file => {
119+
mv(`-f`, path.join(from, file), path.join(to, file));
120+
});
121+
}

jest.config.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module.exports = {
2+
testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"],
3+
collectCoverage: true,
4+
coverageDirectory: "./coverage",
5+
collectCoverageFrom: ["index.js"],
6+
coveragePathIgnorePatterns: [],
7+
coverageThreshold: {
8+
global: {
9+
functions: 100,
10+
statements: 80
11+
}
12+
},
13+
modulePathIgnorePatterns: [".npm-cache", ".npm-tmp", ".cache", ".tmp", ".nvm", ".yarn"],
14+
reporters: ["default"]
15+
};

utils/__tests__/utils.index.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* no flags -- should pass and name the artifact with pj name and
3+
* --output <nothing> --dev-deps (should fail due to output. check message)
4+
* */
5+
const path = require(`path`);
6+
7+
const TEST_SUITE = `npm-pack-all: ${__filename}`;
8+
const { CliError /*, safetyDecorator, shellExecDecorator*/ } = require(path.join(__dirname, `../utils.index`));
9+
10+
beforeEach(() => {
11+
jest.resetModules();
12+
});
13+
14+
afterEach(() => {
15+
jest.restoreAllMocks();
16+
});
17+
18+
afterAll(() => {
19+
console.info(`Success: ${TEST_SUITE}`);
20+
});
21+
22+
describe(TEST_SUITE, () => {
23+
test("Test CliError", () => {
24+
const flag = `--output`;
25+
const flagValue = `artifact.tgz`;
26+
const message = `The \`--output\` flag requires a string filename`;
27+
28+
const Error = new CliError(flag, flagValue, message);
29+
30+
expect(Error.name).toEqual(`CliInputError`);
31+
expect(Error.cliFlag).toEqual(flag);
32+
expect(Error.cliValue).toEqual(flagValue);
33+
expect(Error.message).toEqual(message);
34+
expect(Error.stack).toBeDefined();
35+
});
36+
});

0 commit comments

Comments
 (0)