Skip to content

Commit

Permalink
feat: deep link mode (#3)
Browse files Browse the repository at this point in the history
* wip: deep link mode

* tests: deep link

* docs: readme
  • Loading branch information
privatenumber committed Apr 24, 2022
1 parent b115737 commit cde9aaf
Show file tree
Hide file tree
Showing 15 changed files with 255 additions and 72 deletions.
15 changes: 15 additions & 0 deletions README.md
Expand Up @@ -32,6 +32,9 @@ The configuration has the following type schema:
```ts
type LinkConfig = {

// Whether to run link on linked packages with link.config.json
deepLink?: boolean

// List of packages to link
packages?: string[]
}
Expand All @@ -45,6 +48,18 @@ To link the dependencies defined in `link.config.json`, run:
npx link
```
### Deep linking
By default, `npx link` only links packages in the current project. However, there are cases where the linked packages also needs linking setup.
Deep linking recursively runs link on every linked package that has a `link.config.json` file.
Enable with the `--deep` flag or `deepLink` property in `link.config.json`.
```sh
npx link --deep
```
## FAQ
### Why should I use this over `npm link`?
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -37,6 +37,7 @@
},
"devDependencies": {
"@pvtnbr/eslint-config": "^0.19.1",
"@types/cmd-shim": "^5.0.0",
"@types/fs-extra": "^9.0.13",
"@types/node": "^17.0.25",
"cleye": "^1.1.0",
Expand Down
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 0 additions & 23 deletions src/@types/cmd-shim.d.ts

This file was deleted.

33 changes: 21 additions & 12 deletions src/cli.ts
@@ -1,11 +1,18 @@
import { cli } from 'cleye';
import { linkPackage } from './link-package';
import { linkPackage, linkFromConfig } from './link-package';
import { loadConfig } from './utils/load-config';

(async () => {
const argv = cli({
name: 'link',
parameters: ['[package paths...]'],
flags: {
deep: {
type: Boolean,
alias: 'd',
description: 'Run `npx link` on dependencies if they have a link.config.json',
},
},
help: {
description: 'A better `npm link` -- symlink local dependencies to the current project',

Expand All @@ -25,34 +32,36 @@ import { loadConfig } from './utils/load-config';
},
});

const linkToPackagePath = process.cwd();
const { packagePaths } = argv._;

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

return;
}

const config = await loadConfig();
const config = await loadConfig(linkToPackagePath);

if (!config) {
console.warn('Warning: Config file "link.config.json" not found in current directory.\n Read the documentation to learn more: https://www.npmjs.com/package/link\n');
argv.showHelp();
return;
}

if (!config.packages) {
return;
}

await Promise.all(
config.packages.map(
async linkPath => await linkPackage(linkPath),
),
await linkFromConfig(
linkToPackagePath,
config,
{
deep: argv.flags.deep,
},
);
})().catch((error) => {
console.error('Error:', error.message);
Expand Down
40 changes: 38 additions & 2 deletions src/link-package/index.ts
Expand Up @@ -2,10 +2,14 @@ import {
green, red, cyan, magenta,
} from 'kolorist';
import { fsExists } from '../utils/fs-exists';
import type { LinkConfig } from '../types';
import { loadConfig } from '../utils/load-config';
import { symlinkPackage } from './symlink-package';

export async function linkPackage(
linkToPackagePath: string,
packagePath: string,
deep?: boolean,
) {
const pathExists = await fsExists(packagePath);

Expand All @@ -16,11 +20,43 @@ export async function linkPackage(
}

try {
const link = await symlinkPackage(packagePath);
const link = await symlinkPackage(linkToPackagePath, packagePath);
console.log(green('✔'), `Symlinked ${magenta(link.name)}:`, cyan(link.path), '→', cyan(packagePath));
return link;
} catch (error) {
console.warn(red('✖'), 'Failed to symlink', cyan(packagePath), 'with error:', (error as any).message);
process.exitCode = 1;
return;
}

if (deep) {
const config = await loadConfig(packagePath);

if (config) {
await linkFromConfig(
packagePath,
config,
{ deep },
);
}
}
}

export async function linkFromConfig(
linkToPackagePath: string,
config: LinkConfig,
options: {
deep?: boolean;
},
) {
if (!config.packages) {
return;
}

const deep = options.deep ?? config.deepLink ?? false;

await Promise.all(
config.packages.map(
async linkPath => await linkPackage(linkToPackagePath, linkPath, deep),
),
);
}
3 changes: 2 additions & 1 deletion src/link-package/symlink-package.ts
Expand Up @@ -8,11 +8,12 @@ import { linkBinaries } from './link-binaries';
const nodeModulesDirectory = 'node_modules';

export async function symlinkPackage(
linkToPackagePath: string,
packagePath: string,
) {
const packageJson = await readPackageJson(packagePath);

const symlinkPath = path.join(nodeModulesDirectory, packageJson.name);
const symlinkPath = path.join(linkToPackagePath, nodeModulesDirectory, packageJson.name);

await fs.promises.mkdir(path.dirname(symlinkPath), { recursive: true });

Expand Down
1 change: 1 addition & 0 deletions src/types.ts
@@ -1,3 +1,4 @@
export type LinkConfig = {
deepLink?: boolean;
packages?: string[];
}
8 changes: 6 additions & 2 deletions src/utils/load-config.ts
@@ -1,10 +1,14 @@
import path from 'path';
import type { LinkConfig } from '../types';
import { fsExists } from './fs-exists';
import { readJsonFile } from './read-json-file';

const configPath = 'link.config.json';
const configFile = 'link.config.json';

export async function loadConfig() {
export async function loadConfig(
packageDirectory: string,
) {
const configPath = path.join(packageDirectory, configFile);
const configExists = await fsExists(configPath);

if (!configExists) {
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/package-entry/index.js
Expand Up @@ -3,4 +3,5 @@ console.log(JSON.stringify([
require('package-binary'),
require('package-files'),
require('@organization/package-organization'),
require('package-nested-link'),
]));
12 changes: 12 additions & 0 deletions tests/fixtures/package-nested-link/index.js
@@ -0,0 +1,12 @@
const requireSafe = (specifier) => {
try {
return require(specifier);
} catch {
return null;
}
};

module.exports = [
'package-nested-link',
requireSafe('package-files'),
];
5 changes: 5 additions & 0 deletions tests/fixtures/package-nested-link/link.config.json
@@ -0,0 +1,5 @@
{
"packages": [
"../package-files"
]
}
3 changes: 3 additions & 0 deletions tests/fixtures/package-nested-link/package.json
@@ -0,0 +1,3 @@
{
"name": "package-nested-link"
}

0 comments on commit cde9aaf

Please sign in to comment.