Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/tidy-dragons-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@lg-tools/link': minor
'@lg-tools/cli': minor
---

developer experience slightly improves by adding more detailed logging to spawn processes in the link script. Previously, running link script would lead to a group of processes being spawned in parallel all piping their stdout and stderr to the console in verbose mode which made it difficult to distinguish what each line of output was from which process. The command and working directory are also now logged for each process along with their exit code.
22 changes: 11 additions & 11 deletions tools/link/src/utils/createLinkFrom.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { ChildProcess } from 'child_process';
import xSpawn from 'cross-spawn';
import fsx from 'fs-extra';
import path from 'path';

import { createLinkFrom } from './createLinkFrom';
import { MockChildProcess } from './mocks.testutils';
import * as spawnLoggedModule from './spawnLogged';

describe('tools/link/createLinkFrom', () => {
let spawnSpy: jest.SpyInstance<ChildProcess>;
let spawnLoggedSpy: jest.SpyInstance;

beforeAll(() => {
fsx.emptyDirSync('./tmp');
Expand All @@ -18,30 +16,32 @@ describe('tools/link/createLinkFrom', () => {
});

beforeEach(() => {
spawnSpy = jest.spyOn(xSpawn, 'spawn');
spawnSpy.mockImplementation((..._args) => new MockChildProcess());
spawnLoggedSpy = jest
.spyOn(spawnLoggedModule, 'spawnLogged')
.mockResolvedValue(undefined);
});

afterEach(() => {
spawnSpy.mockRestore();
spawnLoggedSpy.mockRestore();
fsx.emptyDirSync('./tmp');
});

afterAll(() => {
fsx.rmdirSync('./tmp/');
fsx.removeSync('./tmp/');
});

test('calls `npm link` command from package directory', () => {
createLinkFrom(path.resolve('./tmp/'), {
test('calls `npm link` command from package directory', async () => {
await createLinkFrom(path.resolve('./tmp/'), {
scopeName: '@example',
scopePath: 'scope',
packageName: 'test-package',
});

expect(spawnSpy).toHaveBeenCalledWith(
expect(spawnLoggedSpy).toHaveBeenCalledWith(
'npm',
['link'],
expect.objectContaining({
name: 'link_src:test-package',
cwd: expect.stringContaining('tmp/scope/test-package'),
}),
);
Expand Down
47 changes: 24 additions & 23 deletions tools/link/src/utils/createLinkFrom.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* eslint-disable no-console */
import { getPackageManager, SupportedPackageManager } from '@lg-tools/meta';
import chalk from 'chalk';
import { spawn } from 'cross-spawn';
import path from 'path';

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

interface CreateLinkOptions extends PackageDetails {
Expand All @@ -16,7 +16,7 @@ interface CreateLinkOptions extends PackageDetails {
* Runs the pnpm link command in a leafygreen-ui package directory
* @returns Promise that resolves when the pnpm link command has finished
*/
export function createLinkFrom(
export async function createLinkFrom(
source: string = process.cwd(),
{
scopeName,
Expand All @@ -27,29 +27,30 @@ export function createLinkFrom(
}: CreateLinkOptions,
): Promise<void> {
const scopeSrc = scopePath;
return new Promise<void>(resolve => {
const packagesDirectory = findDirectory(source, scopeSrc);
packageManager = packageManager ?? getPackageManager(source);
const packagesDirectory = findDirectory(source, scopeSrc);

if (packagesDirectory) {
verbose &&
console.log(
'Creating link for:',
chalk.green(`${scopeName}/${packageName}`),
);
if (packagesDirectory) {
verbose &&
console.log(
'Creating link for:',
chalk.green(`${scopeName}/${packageName}`),
);

const resolvedPackageManager =
packageManager ?? getPackageManager(packagesDirectory);

spawn(packageManager, ['link'], {
try {
await spawnLogged(resolvedPackageManager, ['link'], {
name: `link_src:${packageName}`,
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.`,
);
verbose,
});
} catch (_) {
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.`,
);
}
}
25 changes: 14 additions & 11 deletions tools/link/src/utils/install.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import { ChildProcess } from 'child_process';
import xSpawn from 'cross-spawn';
import fsx from 'fs-extra';

import { installPackages } from './install';
import { MockChildProcess } from './mocks.testutils';
import * as spawnLoggedModule from './spawnLogged';

describe('tools/link/utils/install', () => {
let spawnSpy: jest.SpyInstance<ChildProcess>;
let spawnLoggedSpy: jest.SpyInstance;

beforeAll(() => {
fsx.ensureDirSync('./tmp/');
});

beforeEach(() => {
spawnSpy = jest.spyOn(xSpawn, 'spawn');
spawnSpy.mockImplementation((..._args) => new MockChildProcess());
spawnLoggedSpy = jest
.spyOn(spawnLoggedModule, 'spawnLogged')
.mockResolvedValue(undefined);
});

afterEach(() => {
spawnSpy.mockRestore();
spawnLoggedSpy.mockRestore();
fsx.emptyDirSync('./tmp');
});

Expand All @@ -28,20 +27,24 @@ describe('tools/link/utils/install', () => {

test('runs `npm install` command', async () => {
await installPackages('./tmp');
expect(spawnSpy).toHaveBeenCalledWith(
expect(spawnLoggedSpy).toHaveBeenCalledWith(
'npm',
['install'],
expect.objectContaining({}),
expect.objectContaining({
cwd: './tmp',
}),
);
});

test('runs install command using local package manager', async () => {
fsx.createFileSync('./tmp/pnpm-lock.yaml');
await installPackages('./tmp');
expect(spawnSpy).toHaveBeenCalledWith(
expect(spawnLoggedSpy).toHaveBeenCalledWith(
'pnpm',
['install'],
expect.objectContaining({}),
expect.objectContaining({
cwd: './tmp',
}),
);
});
});
29 changes: 14 additions & 15 deletions tools/link/src/utils/install.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
import { getPackageManager, SupportedPackageManager } from '@lg-tools/meta';
import { spawn } from 'cross-spawn';
import fsx from 'fs-extra';

import { spawnLogged } from './spawnLogged';

export async function installPackages(
path: string,
options?: {
packageManager?: SupportedPackageManager;
verbose?: boolean;
},
): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (fsx.existsSync(path)) {
const pkgMgr = options?.packageManager ?? getPackageManager(path);
if (fsx.existsSync(path)) {
const pkgMgr = options?.packageManager ?? getPackageManager(path);

spawn(pkgMgr, ['install'], {
try {
await spawnLogged(pkgMgr, ['install'], {
name: 'install',
cwd: path,
stdio: options?.verbose ? 'inherit' : 'ignore',
})
.on('close', resolve)
.on('error', err => {
throw new Error(`Error installing packages\n` + err);
});
} else {
console.error(`Path ${path} does not exist`);
reject();
verbose: options?.verbose,
});
} catch (err) {
throw new Error(`Error installing packages\n` + err);
}
});
} else {
throw new Error(`Path ${path} does not exist`);
}
}
20 changes: 10 additions & 10 deletions tools/link/src/utils/linkPackageTo.spec.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
import { ChildProcess } from 'child_process';
import xSpawn from 'cross-spawn';
import fsx from 'fs-extra';
import path from 'path';

import { linkPackageTo } from './linkPackageTo';
import { MockChildProcess } from './mocks.testutils';
import * as spawnLoggedModule from './spawnLogged';

describe('tools/link/linkPackageTo', () => {
let spawnSpy: jest.SpyInstance<ChildProcess>;
let spawnLoggedSpy: jest.SpyInstance;

beforeAll(() => {
fsx.ensureDirSync('./tmp/app');
fsx.emptyDirSync('./tmp/app');
});

beforeEach(() => {
spawnSpy = jest.spyOn(xSpawn, 'spawn');
spawnSpy.mockImplementation((..._args) => new MockChildProcess());
spawnLoggedSpy = jest
.spyOn(spawnLoggedModule, 'spawnLogged')
.mockResolvedValue(undefined);
});

afterEach(() => {
spawnSpy.mockRestore();
spawnLoggedSpy.mockRestore();
fsx.emptyDirSync('./tmp');
});

afterAll(() => {
fsx.rmdirSync('./tmp/');
});

test('calls `npm link <package>` from the destination directory', () => {
linkPackageTo(path.resolve('./tmp/app'), {
test('calls `npm link <package>` from the destination directory', async () => {
await linkPackageTo(path.resolve('./tmp/app'), {
scopeName: '@example',
packageName: 'test-package',
});

expect(spawnSpy).toHaveBeenCalledWith(
expect(spawnLoggedSpy).toHaveBeenCalledWith(
'npm',
['link', '@example/test-package'],
expect.objectContaining({
name: 'link_dst:test-package',
cwd: expect.stringContaining('tmp/app'),
}),
);
Expand Down
29 changes: 15 additions & 14 deletions tools/link/src/utils/linkPackageTo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getPackageManager, SupportedPackageManager } from '@lg-tools/meta';
import chalk from 'chalk';
import { spawn } from 'cross-spawn';

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

interface LinkPackagesToOptions
Expand All @@ -14,23 +14,24 @@ interface LinkPackagesToOptions
* Runs the pnpm link <packageName> command in the destination directory
* @returns Promise that resolves when the pnpm link <packageName> command has finished
*/
export function linkPackageTo(
export async function linkPackageTo(
destination: string,
{ scopeName, packageName, verbose, packageManager }: LinkPackagesToOptions,
): 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));
packageManager = packageManager ?? getPackageManager(destination);
// eslint-disable-next-line no-console
verbose && console.log('Linking package:', chalk.blue(fullPackageName));

spawn(packageManager, ['link', fullPackageName], {
const resolvedPackageManager =
packageManager ?? getPackageManager(destination);

try {
await spawnLogged(resolvedPackageManager, ['link', fullPackageName], {
name: `link_dst:${packageName}`,
cwd: destination,
stdio: verbose ? 'inherit' : 'ignore',
})
.on('close', resolve)
.on('error', () => {
throw new Error(`Couldn't link package: ${fullPackageName}`);
});
});
verbose,
});
} catch (_) {
throw new Error(`Couldn't link package: ${fullPackageName}`);
}
}
19 changes: 10 additions & 9 deletions tools/link/src/utils/linkPackagesForScope.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { ChildProcess } from 'child_process';
import xSpawn from 'cross-spawn';
import fsx from 'fs-extra';
import path from 'path';

import { linkPackagesForScope } from './linkPackagesForScope';
import { MockChildProcess } from './mocks.testutils';
import * as spawnLoggedModule from './spawnLogged';

describe('tools/link/linkPackagesForScope', () => {
let spawnSpy: jest.SpyInstance<ChildProcess>;
let spawnLoggedSpy: jest.SpyInstance;

beforeAll(() => {
fsx.emptyDirSync('./tmp');
Expand All @@ -16,12 +14,13 @@ describe('tools/link/linkPackagesForScope', () => {
});

beforeEach(() => {
spawnSpy = jest.spyOn(xSpawn, 'spawn');
spawnSpy.mockImplementation((..._args) => new MockChildProcess());
spawnLoggedSpy = jest
.spyOn(spawnLoggedModule, 'spawnLogged')
.mockResolvedValue(undefined);
});

afterEach(() => {
spawnSpy.mockRestore();
spawnLoggedSpy.mockRestore();
fsx.emptyDirSync('./tmp');
});

Expand All @@ -48,19 +47,21 @@ describe('tools/link/linkPackagesForScope', () => {
);

// Creates links
expect(spawnSpy).toHaveBeenCalledWith(
expect(spawnLoggedSpy).toHaveBeenCalledWith(
'npm',
['link'],
expect.objectContaining({
name: 'link_src:test-package',
cwd: expect.stringContaining('tmp/packages/scope/test-package'),
}),
);

// Consumes links
expect(spawnSpy).toHaveBeenCalledWith(
expect(spawnLoggedSpy).toHaveBeenCalledWith(
'npm',
['link', '@example/test-package'],
expect.objectContaining({
name: 'link_dst:test-package',
cwd: expect.stringContaining('tmp/app'),
}),
);
Expand Down
Loading
Loading