Skip to content

Commit

Permalink
Adds to & from options in link script (#1917)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheSonOfThomp authored Aug 10, 2023
1 parent c15ee2a commit 0100840
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 117 deletions.
7 changes: 6 additions & 1 deletion tools/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ cli
cli
.command('link')
.description('Link local LeafyGreen packages to a destination app.')
.argument('destination', 'The destination app path')
.argument('[destination]', 'The destination app path')
.option('--to <destination>', 'Alias for `destination`')
.option(
'--from <source>',
'When running from a consuming application, defines the source of linked packages',
)
.option('-v --verbose', 'Prints additional information to the console', false)
.option('--scope <name>', 'The NPM organization')
.option(
Expand Down
184 changes: 71 additions & 113 deletions tools/link/src/link.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,86 @@
/* eslint-disable no-console */
import { getLGConfig } from '@lg-tools/meta';
import chalk from 'chalk';
import { spawn } from 'cross-spawn';
import fse from 'fs-extra';
import { homedir } from 'os';
import path from 'path';

import { formatLog } from './utils';
import { createLinkFrom } from './utils/createLinkFrom';
import { formatLog } from './utils/formatLog';
import { yarnInstall } from './utils/install';
import { linkPackageTo } from './utils/linkPackageTo';
import { PackageDetails } from './utils/types';

interface LinkOptions {
packages: Array<string>;
scope: string;
verbose: boolean;
to?: string;
from?: string;
}

const ignorePackages = ['mongo-nav'];

export async function linkPackages(destination: string, opts: LinkOptions) {
const { verbose, scope: scopeFlag, packages } = opts;
export async function linkPackages(
dest: string | undefined,
opts: LinkOptions,
) {
const { verbose, scope: scopeFlag, packages, to, from } = opts;

const rootDir = process.cwd();
const relativeDestination = path.relative(rootDir, destination);

if (!to && !dest && !from) {
console.error('Error linking. Must provide either a destination or source');
}

const destination = path.resolve(path.join(rootDir, dest || to || '.'));
const source = path.resolve(from ? path.join(rootDir, from) : rootDir);

// Check if the destination exists
if (
!(fse.existsSync(destination) && fse.lstatSync(destination).isDirectory())
!(
destination &&
fse.existsSync(destination) &&
fse.lstatSync(destination).isDirectory()
)
) {
throw new Error(
`Can't find the directory ${formatLog.path(relativeDestination)}.`,
`Can't find the directory ${formatLog.path(destination ?? '')}.`,
);
}

if (dest ?? to) {
console.log(
chalk.green(`Linking packages to ${formatLog.path(destination)} ...`),
);
}

console.log(
chalk.green(
`Linking packages to ${formatLog.path(relativeDestination)} ...`,
),
);
if (from) {
console.log(
chalk.green(`Linking packages from ${formatLog.path(source)} ...`),
);
}

const { scopes: availableScopes } = getLGConfig();
const { scopes: availableScopes } = getLGConfig(source);

verbose &&
console.log({
availableScopes,
dest,
to,
from,
destination,
source,
rootDir,
});

const linkPromises: Array<Promise<void>> = [];

for (const [scopeName, scopePath] of Object.entries(availableScopes)) {
if (!scopeFlag || scopeFlag.includes(scopeName)) {
linkPromises.push(
linkPackagesForScope(
scopeName,
scopePath as string,
{ scopeName, scopePath },
source,
destination,
packages,
verbose,
Expand All @@ -60,8 +95,8 @@ export async function linkPackages(destination: string, opts: LinkOptions) {
}

async function linkPackagesForScope(
scopeName: string,
scopePath: string,
{ scopeName, scopePath }: Pick<PackageDetails, 'scopeName' | 'scopePath'>,
source: string,
destination: string,
packages?: Array<string>,
verbose?: boolean,
Expand All @@ -86,16 +121,23 @@ async function linkPackagesForScope(
packages.some(pkgFlag => pkgFlag.includes(installedPkg))),
);

/** Create links */
console.log(
chalk.gray(
` Creating links to ${formatLog.scope(scopeName)} packages...`,
),
);
await Promise.all(
packagesToLink.map(pkg => {
createYarnLinkForPackage(scopeName, scopePath, pkg, verbose);
createLinkFrom(
source,
{ scopeName, scopePath, packageName: pkg },
verbose,
);
}),
);

/** Connect link */
console.log(
chalk.gray(
` Connecting links for ${formatLog.scope(
Expand All @@ -105,7 +147,14 @@ async function linkPackagesForScope(
);
await Promise.all(
packagesToLink.map((pkg: string) =>
linkPackageToDestination(scopeName, pkg, destination, verbose),
linkPackageTo(
destination,
{
scopeName,
packageName: pkg,
},
verbose,
),
),
);
} else {
Expand All @@ -124,102 +173,11 @@ async function linkPackagesForScope(
// TODO: Prompt user to install instead of just running it
await yarnInstall(destination);
await linkPackagesForScope(
scopeName,
scopePath,
{ scopeName, scopePath },
destination,
source,
packages,
verbose,
);
}
}

/**
* Runs the yarn link command in a leafygreen-ui package directory
* @returns Promise that resolves when the yarn link command has finished
*/
function createYarnLinkForPackage(
scopeName: string,
scopePath: string,
packageName: string,
verbose?: boolean,
): Promise<void> {
const scopeSrc = scopePath;
return new Promise<void>(resolve => {
const packagesDirectory = findDirectory(process.cwd(), scopeSrc);

if (packagesDirectory) {
verbose &&
console.log(
'Creating link for:',
chalk.green(`${scopeName}/${packageName}`),
);
spawn('yarn', ['link'], {
cwd: path.join(packagesDirectory, packageName),
stdio: verbose ? 'inherit' : 'ignore',
})
.on('close', resolve)
.on('error', () => {
throw new Error(`Couldn't create link for package: ${packageName}`);
});
} else {
throw new Error(
`Can't find a ${scopeSrc} directory in ${process.cwd()} or any of its parent directories.`,
);
}
});
}

/**
* Runs the yarn link <packageName> command in the destination directory
* @returns Promise that resolves when the yarn link <packageName> command has finished
*/
function linkPackageToDestination(
scopeName: string,
packageName: string,
destination: string,
verbose?: boolean,
): Promise<void> {
const fullPackageName = `${scopeName}/${packageName}`;
return new Promise<void>(resolve => {
verbose && console.log('Linking package:', chalk.blue(fullPackageName));
spawn('yarn', ['link', fullPackageName], {
cwd: destination,
stdio: verbose ? 'inherit' : 'ignore',
})
.on('close', resolve)
.on('error', () => {
throw new Error(`Couldn't link package: ${fullPackageName}`);
});
});
}

function findDirectory(
startDir: string,
targetDir: string,
): string | undefined {
const testDir = path.join(startDir, targetDir);

if (fse.existsSync(testDir) && fse.lstatSync(testDir).isDirectory()) {
return testDir;
} else {
const parentDir = path.join(startDir, '..');

// If we haven't reached the users home directory, recursively look for the packages directory
if (parentDir !== homedir()) {
return findDirectory(path.join(startDir, '..'), targetDir);
}
}
}

function yarnInstall(path: string) {
return new Promise((resolve, reject) => {
spawn('yarn', ['install'], {
cwd: path,
stdio: 'ignore',
})
.on('close', resolve)
.on('error', () => {
throw new Error(`Error installing packages`);
});
});
}
2 changes: 1 addition & 1 deletion tools/link/src/unlink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { spawn } from 'cross-spawn';
import fse from 'fs-extra';
import path from 'path';

import { formatLog } from './utils';
import { formatLog } from './utils/formatLog';

interface UnlinkOpts {
verbose: boolean;
Expand Down
42 changes: 42 additions & 0 deletions tools/link/src/utils/createLinkFrom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* eslint-disable no-console */
import chalk from 'chalk';
import { spawn } from 'cross-spawn';
import path from 'path';

import { findDirectory } from './findDirectory';
import { PackageDetails } from './types';

/**
* Runs the yarn link command in a leafygreen-ui package directory
* @returns Promise that resolves when the yarn link command has finished
*/
export function createLinkFrom(
source: string,
{ scopeName, scopePath, packageName }: PackageDetails,
verbose?: boolean,
): Promise<void> {
const scopeSrc = scopePath;
return new Promise<void>(resolve => {
const packagesDirectory = findDirectory(process.cwd(), scopeSrc);

if (packagesDirectory) {
verbose &&
console.log(
'Creating link for:',
chalk.green(`${scopeName}/${packageName}`),
);
spawn('yarn', ['link'], {
cwd: path.join(packagesDirectory, packageName),
stdio: verbose ? 'inherit' : 'ignore',
})
.on('close', resolve)
.on('error', () => {
throw new Error(`Couldn't create link for package: ${packageName}`);
});
} else {
throw new Error(
`Can't find a ${scopeSrc} directory in ${process.cwd()} or any of its parent directories.`,
);
}
});
}
21 changes: 21 additions & 0 deletions tools/link/src/utils/findDirectory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import fse from 'fs-extra';
import { homedir } from 'os';
import path from 'path';

export function findDirectory(
startDir: string,
targetDir: string,
): string | undefined {
const testDir = path.join(startDir, targetDir);

if (fse.existsSync(testDir) && fse.lstatSync(testDir).isDirectory()) {
return testDir;
} else {
const parentDir = path.join(startDir, '..');

// If we haven't reached the users home directory, recursively look for the packages directory
if (parentDir !== homedir()) {
return findDirectory(path.join(startDir, '..'), targetDir);
}
}
}
File renamed without changes.
14 changes: 14 additions & 0 deletions tools/link/src/utils/install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { spawn } from 'cross-spawn';

export function yarnInstall(path: string) {
return new Promise((resolve, reject) => {
spawn('yarn', ['install'], {
cwd: path,
stdio: 'ignore',
})
.on('close', resolve)
.on('error', () => {
throw new Error(`Error installing packages`);
});
});
}
28 changes: 28 additions & 0 deletions tools/link/src/utils/linkPackageTo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import chalk from 'chalk';
import { spawn } from 'cross-spawn';

import { PackageDetails } from './types';

/**
* Runs the yarn link <packageName> command in the destination directory
* @returns Promise that resolves when the yarn link <packageName> command has finished
*/
export function linkPackageTo(
destination: string,
{ scopeName, packageName }: Pick<PackageDetails, 'scopeName' | 'packageName'>,
verbose?: boolean,
): Promise<void> {
const fullPackageName = `${scopeName}/${packageName}`;
return new Promise<void>(resolve => {
// eslint-disable-next-line no-console
verbose && console.log('Linking package:', chalk.blue(fullPackageName));
spawn('yarn', ['link', fullPackageName], {
cwd: destination,
stdio: verbose ? 'inherit' : 'ignore',
})
.on('close', resolve)
.on('error', () => {
throw new Error(`Couldn't link package: ${fullPackageName}`);
});
});
}
5 changes: 5 additions & 0 deletions tools/link/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface PackageDetails {
scopeName: string;
packageName: string;
scopePath: string;
}
Loading

0 comments on commit 0100840

Please sign in to comment.