Skip to content

Commit

Permalink
feat: support link.config.json (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
privatenumber authored Apr 21, 2022
1 parent 445799c commit 7adf3a2
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 10 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,37 @@ From the project you want to link a package to:
npx link <package-path>
```

### Configuration file

Create a `link.config.json` configuration file at the root of your npm project to automatically setup links to multiple packages.

Example _link.config.json_:
```json5
{
"packages": [
"/path/to/package-path-a",
"../package-path-b"
]
}
```

The configuration has the following type schema:
```ts
type LinkConfig = {

// List of packages to link
packages?: string[]
}
```
> Note: It's not recommended to commit this file to source control since this is for local development with local paths.
To link the dependencies defined in `link.config.json`, run:
```sh
npx link
```
## FAQ
### Why should I use this over `npm link`?
Expand Down
28 changes: 25 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
import { cli } from 'cleye';
import { linkPackage } from './link-package';
import { loadConfig } from './utils/load-config';

(async () => {
const argv = cli({
name: 'link',
parameters: ['<package paths...>'],
parameters: ['[package paths...]'],
help: {
description: 'A better `npm link` - Link a package to the current project',
},
});

const { packagePaths } = argv._;

if (packagePaths.length > 0) {
await Promise.all(
packagePaths.map(
packagePath => linkPackage(packagePath),
),
);

return;
}

const config = await loadConfig();

if (!config) {
console.warn('Warning: Config file "link.config.json" not found in current directory.');
return;
}

if (!config.packages) {
return;
}

await Promise.all(
packagePaths.map(
packagePath => linkPackage(packagePath),
config.packages.map(
async linkPath => await linkPackage(linkPath),
),
);
})().catch((error) => {
Expand Down
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type LinkConfig = {
packages?: string[];
}
19 changes: 19 additions & 0 deletions src/utils/load-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { LinkConfig } from '../types';
import { fsExists } from './fs-exists';
import { readJsonFile } from './read-json-file';

const configPath = 'link.config.json';

export async function loadConfig() {
const configExists = await fsExists(configPath);

if (!configExists) {
return null;
}

try {
return readJsonFile<LinkConfig>(configPath);
} catch (error) {
throw new Error(`Failed to parse config JSON: ${(error as any).message}`);
}
}
2 changes: 2 additions & 0 deletions tests/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe } from 'manten';
import getNode from 'get-node';
import specCli from './specs/cli';
import specLinkConfig from './specs/link-config';

const nodeVersions = [
'12.22.9',
Expand All @@ -19,6 +20,7 @@ const nodeVersions = [
const node = await getNode(nodeVersion);
await describe(`Node ${node.version}`, ({ runTestSuite }) => {
runTestSuite(specCli, node.path);
runTestSuite(specLinkConfig, node.path);
});
}
})();
12 changes: 5 additions & 7 deletions tests/specs/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,16 @@ export default testSuite(({ describe }, nodePath: string) => {
);
expect(packageA.stdout).toBe('["package-entry","package-binary","package-files","@organization/package-organization"]');

// Test binary
await fixture.writeJson('package-entry/package.json', {
scripts: {
test: 'binary',
},
});

// Executable
const binary = await execa(path.join(entryPackagePath, 'node_modules/.bin/binary'));
expect(binary.stdout).toMatch('package-binary');

// Executable via npm
await fixture.writeJson('package-entry/package.json', {
scripts: {
test: 'binary',
},
});
const binaryNpm = await execa('npm', ['test'], {
cwd: entryPackagePath,
});
Expand Down
62 changes: 62 additions & 0 deletions tests/specs/link-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import path from 'path';
import { testSuite, expect } from 'manten';
import { execa, execaNode } from 'execa';
import { createFixture } from '../utils/create-fixture';
import { link } from '../utils/link';

export default testSuite(({ describe }, nodePath: string) => {
describe('link.config.json', ({ test }) => {
test('symlink', async () => {
const fixture = await createFixture('./tests/fixtures/');

const entryPackagePath = path.join(fixture.path, 'package-entry');

await fixture.writeJson('package-entry/link.config.json', {
packages: [
// Relative path & binary
'../package-binary',

// Absolute path
path.join(fixture.path, 'package-files'),

// Package with @org in name
'../package-organization',
],
});

await link([], {
cwd: entryPackagePath,
nodePath,
});

const packageA = await execaNode(
path.join(entryPackagePath, 'index.js'),
[],
{
nodePath,
nodeOptions: [],
},
);
expect(packageA.stdout).toBe('["package-entry","package-binary","package-files","@organization/package-organization"]');

// Executable via npm
await fixture.writeJson('package-entry/package.json', {
scripts: {
test: 'binary',
},
});
const binaryNpm = await execa('npm', ['test'], {
cwd: entryPackagePath,
});
expect(binaryNpm.stdout).toMatch('package-binary');

const binary = await execa(path.join(entryPackagePath, 'node_modules/.bin/binary'));
expect(binary.stdout).toBe('package-binary');

const nonPublishFileExists = await fixture.exists('package-entry/node_modules/package-files/non-publish-file.js');
expect(nonPublishFileExists).toBe(true);

await fixture.rm();
});
});
});

0 comments on commit 7adf3a2

Please sign in to comment.