Skip to content

Commit

Permalink
feat(allow-multiple-targets): Allow multiple target packages
Browse files Browse the repository at this point in the history
Allow multiple target packages to install into.
* Change the `installLocal` function to accept an object hash.
* Change the command line utility to work more like `npm install`. Only installing into current directory
  • Loading branch information
nicojs committed Jun 7, 2017
1 parent b7b4d77 commit a5b2098
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 36 deletions.
17 changes: 13 additions & 4 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,30 @@
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceRoot}/src/index.js"
"name": "All Tests",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"999999",
"--colors",
"${workspaceRoot}/test/**/*.js"
],
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "node",
"request": "launch",
"name": "Mocha Tests",
"name": "Unit Tests",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"999999",
"--colors",
"${workspaceRoot}/test"
"${workspaceRoot}/test/unit"
],
"internalConsoleOptions": "openOnSessionStart"
}
Expand Down
36 changes: 32 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,49 @@ Installs npm/yarn packages locally without symlink. Exactly the same as your pro

You can use install-local from command line or programmatically.

Command line:
### Command line:

```bash
$ install-local my-package my-dependant-package [my-dependant-package2, ...]
Usage: install-local <folder>[, <folder>, ...]
```

From node:
Installs a package from the filesystem into the current directory without touching the package.json.

Examples:
* `install-local ..`
Install the package located in the parent folder into the current directory.
* `install-local ../sibling ../sibling2`
Install the packages in 2 sibling directories into the current directory.
* `install-local`
Print this help

### From node:

```javascript
const { installLocal } = require('install-local');
installLocal('my-package', 'my-dependant-package'/*, my-dependant-package2, ...*/)
installLocal({
'.': ['../my-dependency'/*, ../my-dependency2, ...*/],
'sibling': ['.']
})
.then(() => console.log('done'))
.catch(err => console.error(err));
```

Execute `installLocal` with an object as parameter. The properties of this object are the relative package locations to install into. The array values are the packages to be installed.

For example:

```javascript
installLocal({
/*1*/ '.': ['../sibling1', '../sibling2'],
/*2*/ '../dependant': ['.']
})
```

1. This will install packages located in the directories "sibling1" and "sibling2" next to the current working directory into the package located in the current working directory (`'.'`)
2. This will install the package located in the current working directory (`'.'`) into the package located in
the "dependant" directory located next to the current working directory.

_TypeScript types are also included_

## Why?
Expand Down
2 changes: 1 addition & 1 deletion bin/install-local
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/usr/bin/env node
process.title = 'install-local';
require('../src/cli');
require('../src/cli').cli(process.argv);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "tsc -p .",
"postbuild": "tslint -p .",
"pretest": "npm run build",
"test": "mocha --timeout 10000",
"test": "mocha --timeout 10000 test/**/*.js",
"start": "tsc -w",
"preversion": "npm t"
},
Expand Down
39 changes: 26 additions & 13 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
import { installLocal } from './index';

const args = process.argv
.filter((_, i) => i > 1);
export function cli(argv: string[]) {

if (args.length < 2) {
console.log();
console.log('Install packages locally without changing the package.json');
console.log();
console.log('Usage: install-local <from> <to> [<to>, ...]');
console.log('Installs package in <from> directory in package of <to> directories.');
process.exit(1);
const args = argv
.filter((_, i) => i > 1);

if (args.length < 1) {
console.log();
console.log('Install packages locally without changing the package.json');
console.log();
console.log('Usage: install-local <folder>[, <folder>, ...]');
console.log('');
console.log('Installs a package from the filesystem into the current directory without touching the package.json.');
console.log('');
console.log('Examples: ');
console.log(' install-local ..');
console.log(' install the package located in the parent folder into the current directory.');
console.log(' install-local ../sibling ../sibling2');
console.log(' install the packages in 2 sibling directories into the current directory.');
console.log(' install-local');
console.log(' Print this help.');
process.exit(1);
} else {
installLocal({ '.': args }).catch(err => {
console.error(err);
process.exit(1);
});
}
}
installLocal(args[0], ...args.slice(1)).catch(err => {
console.error(err);
process.exit(1);
});
42 changes: 34 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,41 @@ interface PackageJson {
version: string;
}

export function installLocal(from: string, ...to: string[]) {
export interface ListByPackage {
[key: string]: string[];
}

export function installLocal(packagesByTarget: ListByPackage) {
const packagesBySource = mapToPackagesBySource(packagesByTarget);
return Promise.all(Object.keys(packagesBySource)
.map(source => installPackagesInto(source, packagesBySource[source])));
}

export function mapToPackagesBySource(packagesByTarget: ListByPackage) {
const packagesBySource: ListByPackage = {};
Object.keys(packagesByTarget)
.forEach(targetLocalPackage => packagesByTarget[targetLocalPackage].forEach(sourceLocalPackage => {
const targetFullPath = path.resolve(targetLocalPackage);
const sourceFullPath = path.resolve(sourceLocalPackage);
let targetPackages = packagesBySource[sourceFullPath];
if (!targetPackages) {
targetPackages = [];
packagesBySource[sourceFullPath] = targetPackages;
}
if (targetPackages.every(pkg => pkg !== targetFullPath)) {
targetPackages.push(targetFullPath);
}
}));
return packagesBySource;
}

function installPackagesInto(source: string, targets: string[]) {
return Promise.all([
readPackageJson(from).then(pck => path.resolve(`${pck.name}-${pck.version}.tgz`)),
exec('', `npm pack ${from}`)
])
.then(([packFile, _]) => {
return Promise.all(to.map(targetPackage => exec(targetPackage, `npm i --no-save ${packFile}`)))
.then(() => del(packFile));
});
readPackageJson(source).then(pck => path.resolve(`${pck.name}-${pck.version}.tgz`)),
exec('', `npm pack ${source}`)
]).then(([packFile]) =>
Promise.all(targets.map(targetPackage => exec(targetPackage, `npm i --no-save ${packFile}`)))
.then(() => del(packFile)));
}

export function exec(cwd: string, command: string) {
Expand Down
8 changes: 4 additions & 4 deletions test/indexSpec.ts → test/integration/indexSpec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { expect } from 'chai';
import * as fs from 'fs';
import * as path from 'path';
import { exec, installLocal } from '../src/index';
import { exec, installLocal } from '../../src/index';

const readdirInSample = (dirName: string) => new Promise<string[]>((res, rej) => {
fs.readdir(path.resolve(__dirname, '..', 'sample', dirName), (err, dirs) => {
fs.readdir(path.resolve(__dirname, '..', '..', 'sample', dirName), (err, dirs) => {
if (err) {
rej(err);
} else {
Expand All @@ -15,10 +15,10 @@ const readdirInSample = (dirName: string) => new Promise<string[]>((res, rej) =>

describe('installLocal in sample project', () => {

beforeEach(() => exec(path.resolve(__dirname, '..'), 'rimraf sample/node_modules'));
beforeEach(() => exec(path.resolve(__dirname, '..', '..'), 'rimraf sample/node_modules'));

it('should install local as an actual node package (not link)', () => {
return installLocal('.', 'sample').then(() => Promise.all([
return installLocal({ sample: ['.'] }).then(() => Promise.all([
readdirInSample('node_modules').then(dirs => expect(dirs).to.deep.equal(['.bin', 'install-local'])),
readdirInSample('node_modules/.bin').then(dirs => expect(dirs[0]).to.deep.equal('install-local')),
readdirInSample('node_modules/install-local')
Expand Down
27 changes: 27 additions & 0 deletions test/unit/indexSpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expect } from 'chai';
import * as path from 'path';
import { mapToPackagesBySource } from '../../src/index';

describe('mapToPackagesBySource', () => {

it('should map { "../..": "..", "__dirname": ".."} to one and the same', () => {
const input = {
[__dirname + '/./']: ['..'],
[__dirname]: ['..']
};
const result = mapToPackagesBySource(input);
expect(result).to.deep.eq({ [path.resolve('..')]: [path.resolve(__dirname)] });
});

it('should map { ".": ["./stryker", "./stryker-api"], "../sibling": ["./stryker", "./stryker-api"] } to 2 records', () => {
const input = {
'.': ['./stryker', './stryker-api'],
'../sibling': ['./stryker', './stryker-api']
};
const result = mapToPackagesBySource(input);
expect(result).to.deep.eq({
[path.resolve('./stryker')]: [path.resolve('.'), path.resolve('../sibling')],
[path.resolve('./stryker-api')]: [path.resolve('.'), path.resolve('../sibling')]
});
});
});
3 changes: 2 additions & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"arrow-parens": false,
"trailing-comma": false,
"interface-name": false,
"no-console": false
"no-console": false,
"max-line-length": 160
},
"rulesDirectory": []
}

0 comments on commit a5b2098

Please sign in to comment.