Skip to content

Commit cd3eb1c

Browse files
committed
feat(cli): cli that installs npm package
1 parent eca0994 commit cd3eb1c

File tree

10 files changed

+759
-104
lines changed

10 files changed

+759
-104
lines changed

.babelrc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
{
22
"presets": [
33
"@babel/preset-env"
4-
]
5-
}
4+
],
5+
"plugins": [
6+
"@babel/transform-runtime",
7+
"@babel/transform-async-to-generator",
8+
"@babel/plugin-proposal-object-rest-spread",
9+
],
10+
"env": {
11+
"production": {
12+
"presets": ["minify"]
13+
}
14+
}
15+
}

package-lock.json

Lines changed: 562 additions & 100 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
"version": "0.0.0-development",
44
"description": "CLI that combines searching and installing npm packages",
55
"main": "index.js",
6-
"bin": {},
6+
"bin": {
7+
"nis": "./build/executables/nis.js"
8+
},
79
"global": true,
810
"scripts": {
911
"codecov": "codecov",
1012
"commitmsg": "commitlint -e $GIT_PARAMS",
1113
"compile": "babel -d build/ src/ --ignore node_modules,*.test.js",
14+
"compile:prod": "babel env=PROD -d build/ src/ --ignore node_modules,*.test.js",
1215
"lint": "eslint --ext .js .",
1316
"test": "jest --coverage --passWithNoTests",
1417
"prepublishOnly": "npm run compile",
@@ -29,19 +32,27 @@
2932
"testEnvironment": "node"
3033
},
3134
"dependencies": {
35+
"@babel/runtime": "^7.0.0-beta.42",
36+
"axios": "^0.18.0",
37+
"chalk": "^2.3.2",
3238
"commander": "^2.15.1",
33-
"inquirer": "^5.2.0"
39+
"inquirer": "^5.2.0",
40+
"inquirer-autocomplete-prompt": "^0.12.2"
3441
},
3542
"devDependencies": {
3643
"@babel/cli": "^7.0.0-beta.42",
3744
"@babel/core": "^7.0.0-beta.42",
45+
"@babel/plugin-proposal-object-rest-spread": "^7.0.0-beta.42",
46+
"@babel/plugin-transform-async-to-generator": "^7.0.0-beta.42",
47+
"@babel/plugin-transform-runtime": "^7.0.0-beta.42",
3848
"@babel/preset-env": "^7.0.0-beta.42",
3949
"@commitlint/cli": "^6.1.3",
4050
"@commitlint/config-angular": "^6.1.3",
4151
"@commitlint/prompt": "^6.1.3",
4252
"@commitlint/prompt-cli": "^6.1.3",
4353
"babel-core": "^7.0.0-bridge.0",
4454
"babel-jest": "^22.4.3",
55+
"babel-preset-minify": "^0.3.0",
4556
"codecov": "^3.0.0",
4657
"eslint": "^4.19.1",
4758
"eslint-config-airbnb-base": "^12.1.0",

src/PackageSearchPrompter.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import inquirer from 'inquirer';
2+
import InquirerAutocompletePrompt from 'inquirer-autocomplete-prompt';
3+
4+
import formatPackageDetails from './formatPackageDetails';
5+
import packageSearcher from './packageSearcher';
6+
7+
inquirer.registerPrompt('autocomplete', InquirerAutocompletePrompt);
8+
9+
class PackageSearchPrompter {
10+
constructor() {
11+
// goddamn hacky memoization
12+
this.packageDetailsMapping = {};
13+
}
14+
15+
async prompt() {
16+
const { npmPackage } = await inquirer.prompt([
17+
{
18+
type: 'autocomplete',
19+
name: 'npmPackage',
20+
message: 'Search for npm package',
21+
source: async (answersSoFar, searchTerm) => {
22+
const packageResults = await packageSearcher({ term: searchTerm || '' });
23+
24+
const formattedPackageDetails = [];
25+
const packageDetailsMapping = {};
26+
27+
packageResults.data.objects.forEach((result) => {
28+
const {
29+
name,
30+
version,
31+
description,
32+
publisher,
33+
} = result.package;
34+
35+
const formattedDetails = formatPackageDetails({
36+
name,
37+
version,
38+
description,
39+
author: publisher.username,
40+
});
41+
42+
formattedPackageDetails.push(formattedDetails);
43+
44+
packageDetailsMapping[formattedDetails] = { name, version };
45+
});
46+
47+
this.packageDetailsMapping = packageDetailsMapping;
48+
49+
return formattedPackageDetails;
50+
},
51+
},
52+
]);
53+
54+
return this.packageDetailsMapping[npmPackage];
55+
}
56+
}
57+
58+
export default PackageSearchPrompter;

src/buildInstallOptions.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const buildInstallOptions = ({
2+
save,
3+
saveProd,
4+
saveDev,
5+
saveOptional,
6+
saveExact,
7+
saveBundle,
8+
noSave,
9+
dryRun,
10+
}) => {
11+
const options = [];
12+
13+
if (save || saveProd) {
14+
options.push('-P');
15+
}
16+
17+
if (saveDev) {
18+
options.push('-D');
19+
}
20+
21+
if (saveOptional) {
22+
options.push('-O');
23+
}
24+
25+
if (saveExact) {
26+
options.push('-E');
27+
}
28+
29+
if (saveBundle) {
30+
options.push('-B');
31+
}
32+
33+
if (noSave) {
34+
options.push('--no-save');
35+
}
36+
37+
if (dryRun) {
38+
options.push('--dry-run');
39+
}
40+
41+
return options;
42+
};
43+
44+
export default buildInstallOptions;

src/executables/nis.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env node
2+
3+
import program from 'commander';
4+
5+
import pkg from '../../package.json';
6+
import execute from '../';
7+
8+
program.version(pkg.version)
9+
.description('CLI that combines searching and installing npm packages')
10+
// all options defined in https://docs.npmjs.com/cli/install
11+
.option('-S, --save', 'Save to dependencies')
12+
.option('-P, --save-prod', 'Save to dependencies')
13+
.option('-D, --save-dev', 'Save to devDependencies')
14+
.option('-O, --save-optional', 'Save optional')
15+
.option('-E, --save-exact', 'Save exact version')
16+
.option('-B, --save-bundle', 'Save bundle')
17+
.option('--no-save', 'Do not save')
18+
.option('--dry-run', 'Dry run')
19+
.parse(process.argv);
20+
21+
22+
execute(program);
23+

src/formatPackageDetails.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import chalk from 'chalk';
2+
3+
const formatPackageDetails = ({
4+
name,
5+
version,
6+
description,
7+
author,
8+
}) => `📦 ${chalk.green.bold(name)} | 📌 ${chalk.cyan.underline(version)} | 🏷️ ${chalk.yellow.bold(description)} | ⌨️ ${chalk.blueBright.underline(`@${author}`)}`;
9+
10+
export default formatPackageDetails;

src/index.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import PackageSearchPrompter from './PackageSearchPrompter';
2+
import install from './install';
3+
import buildInstallOptions from './buildInstallOptions';
4+
5+
const execute = async (inputs) => {
6+
try {
7+
const packageSearchPrompter = new PackageSearchPrompter();
8+
const { name, version } = await packageSearchPrompter.prompt();
9+
install({ options: buildInstallOptions(inputs), name, version });
10+
} catch (error) {
11+
console.error('😞 Rut ro, an error occurred');
12+
console.error(error);
13+
}
14+
};
15+
16+
export default execute;
17+

src/install.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { spawn } from 'child_process';
2+
3+
const install = ({ name, version, options }) => (
4+
spawn(
5+
'npm',
6+
[
7+
'install',
8+
`${name}@${version}`,
9+
...options,
10+
],
11+
{ stdio: 'inherit' },
12+
)
13+
);
14+
15+
export default install;

src/packageSearcher.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import axios from 'axios';
2+
3+
const packageSearcher = ({ term, size = 50 }) => axios.get(`https://registry.npmjs.org/-/v1/search?text=${term}&size=${size}`);
4+
5+
export default packageSearcher;

0 commit comments

Comments
 (0)