Skip to content

Commit

Permalink
feat(log): log a summary (#32)
Browse files Browse the repository at this point in the history
Log a summary when done. Don't log details to info. Example: `INFO ParentBinLinker Symlinked 0 bin(s) (319 link(s) already exists, 4 different link(s) already exists, 0 error(s)). Run with debug log level for more info.`
  • Loading branch information
nicojs committed Oct 28, 2020
1 parent e5fc726 commit 332aa13
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 89 deletions.
100 changes: 53 additions & 47 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,49 +1,55 @@
{
// Use IntelliSense to learn about possible Node.js debug attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach",
"port": 9229,
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"type": "node",
"request": "launch",
"name": "Unit Tests",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"args": [
"--no-timeouts",
"--colors",
"${workspaceRoot}/dist/test/unit/*.js"
],
"internalConsoleOptions": "openOnSessionStart",
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/dist/**/*.js"
]
},
{
"type": "node",
"request": "launch",
"name": "Integration Tests",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"args": [
"--no-timeouts",
"--colors",
"${workspaceRoot}/dist/test/integration/*.js"
],
"internalConsoleOptions": "openOnSessionStart",
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/dist/**/*.js"
]
}
]
// Use IntelliSense to learn about possible Node.js debug attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach",
"port": 9229,
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"type": "node",
"request": "launch",
"name": "Unit Tests",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"args": [
"--no-timeouts",
"--colors",
"${workspaceRoot}/dist/test/unit/*.js"
],
"skipFiles": [
"<node_internals>/**"
],
"internalConsoleOptions": "openOnSessionStart",
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/dist/**/*.js"
]
},
{
"type": "node",
"request": "launch",
"name": "Integration Tests",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"args": [
"--no-timeouts",
"--colors",
"${workspaceRoot}/dist/test/integration/*.js"
],
"skipFiles": [
"<node_internals>/**"
],
"internalConsoleOptions": "openOnSessionStart",
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/dist/**/*.js"
]
}
]
}
76 changes: 62 additions & 14 deletions src/ParentBinLinker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class ParentBinLinker {
binName: string,
from: string,
childPackage: string,
): Promise<void> {
): Promise<link.LinkResult> {
const to = path.join(
this.options.childDirectoryRoot,
childPackage,
Expand All @@ -38,14 +38,13 @@ export class ParentBinLinker {
binName,
);
this.log.debug('Creating link at %s for command at %s', to, from);
await link.link(from, to);
return void 0;
return link.link(from, to);
}

private async linkBinsOfDependencies(
childPackages: string[],
dependenciesToLink: string[],
): Promise<void> {
): Promise<link.LinkResult[]> {
if (this.log.isInfoEnabled()) {
this.log.info(
`Linking dependencies ${JSON.stringify(
Expand All @@ -54,7 +53,7 @@ export class ParentBinLinker {
);
}

await Promise.all(
const results = await Promise.all(
dependenciesToLink.map(async (dependency) => {
const moduleDir = path.join('node_modules', dependency);
const packageFile = path.join(
Expand All @@ -67,47 +66,52 @@ export class ParentBinLinker {
const pkg: PackageJson = JSON.parse(content.toString());
if (pkg.bin) {
const binaries = this.binariesFrom(pkg, pkg.bin);
return Promise.all(
const linkResultArrays = await Promise.all(
Object.keys(binaries).map((bin) =>
Promise.all(
childPackages.map((childPackage) =>
this.linkBin(
bin,
path.resolve(moduleDir, binaries[bin]),
childPackage,
).catch((err) =>
).catch((err) => {
this.log.error(
`Could not link bin ${bin} for child ${childPackage}.`,
err,
),
),
);
const result: link.LinkResult = { status: 'error' };
return result;
}),
),
),
),
);
return flatten(linkResultArrays);
} else {
this.log.debug(
'Did not find a bin in dependency %s, skipping.',
dependency,
);
return [];
}
} catch (err) {
return this.log.error(`Could not read ${packageFile}`, err);
this.log.error(`Could not read ${packageFile}`, err);
return [];
}
}),
);
return void 0;
return flatten(results);
}

public async linkBinsToChildren(): Promise<void> {
public async linkBinsToChildren(): Promise<link.LinkResult[]> {
const [contents, childPackages] = await Promise.all([
fs.readFile('package.json'),
FSUtils.readDirs(this.options.childDirectoryRoot).then((dirs) =>
dirs.filter(minimatch.filter(this.options.filter)),
),
]);
const pkg: PackageJson = JSON.parse(contents.toString());
const allPromises: Promise<void>[] = [];
const allPromises: Promise<link.LinkResult[]>[] = [];
if (pkg.devDependencies && this.options.linkDevDependencies) {
allPromises.push(
this.linkBinsOfDependencies(
Expand All @@ -132,7 +136,18 @@ export class ParentBinLinker {
),
);
}
await Promise.all(allPromises);
const resultArrays = await Promise.all(allPromises);
const results = flatten(resultArrays);
const {
successCount,
differentLinkAlreadyExistsCount,
alreadyExistsCount,
errorCount,
} = summary(results);
this.log.info(
`Symlinked ${successCount} bin(s) (${alreadyExistsCount} link(s) already exists, ${differentLinkAlreadyExistsCount} different link(s) already exists, ${errorCount} error(s)). Run with debug log level for more info.`,
);
return results;
}

private binariesFrom(
Expand All @@ -142,3 +157,36 @@ export class ParentBinLinker {
return typeof bin === 'string' ? { [pkg.name ?? '']: bin } : bin;
}
}

function summary(linkResults: link.LinkResult[]) {
let successCount = 0;
let errorCount = 0;
let alreadyExistsCount = 0;
let differentLinkAlreadyExistsCount = 0;
for (const { status } of linkResults) {
switch (status) {
case 'success':
successCount++;
break;
case 'alreadyExists':
alreadyExistsCount++;
break;
case 'differentLinkAlreadyExists':
differentLinkAlreadyExistsCount++;
break;
case 'error':
errorCount++;
break;
}
}
return {
successCount,
errorCount,
alreadyExistsCount,
differentLinkAlreadyExistsCount,
};
}

function flatten<T>(arrayOfArrays: T[][]): T[] {
return arrayOfArrays.reduce((result, arr) => [...result, ...arr], []);
}
46 changes: 34 additions & 12 deletions src/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,80 @@ import { promises as fs } from 'fs';
import { FSUtils } from './FSUtils';
import cmdShim from 'cmd-shim';

async function symlink(from: string, to: string) {
export type LinkStatus =
| 'success'
| 'alreadyExists'
| 'differentLinkAlreadyExists'
| 'error';

export interface LinkResult {
status: LinkStatus;
}

async function symlink(from: string, to: string): Promise<LinkResult> {
to = path.resolve(to);
const toDir = path.dirname(to);
const target = path.relative(toDir, from);
await FSUtils.mkdirp(path.dirname(to));
return await fs.symlink(target, to, 'junction');
await fs.symlink(target, to, 'junction');
return {
status: 'success',
};
}

export async function link(from: string, to: string): Promise<void> {
export async function link(from: string, to: string): Promise<LinkResult> {
if (platform() === 'win32') {
return cmdShimIfExists(from, to);
} else {
return linkIfExists(from, to);
}
}

async function cmdShimIfExists(from: string, to: string): Promise<void> {
async function cmdShimIfExists(from: string, to: string): Promise<LinkResult> {
try {
await fs.stat(to);
info(`Link at '${to}' already exists. Leaving it alone.`);
debug(`Link at '${to}' already exists. Leaving it alone.`);
return { status: 'alreadyExists' };
} catch (_) {
/* link doesn't exist */
return new Promise<void>((res, rej) => {
return new Promise<LinkResult>((res, rej) => {
cmdShim.ifExists(from, to, (err: unknown) => {
if (err) {
rej(err);
} else {
res(undefined);
res({ status: 'success' });
}
});
});
}
}

async function linkIfExists(from: string, to: string): Promise<void> {
async function linkIfExists(from: string, to: string): Promise<LinkResult> {
try {
await fs.stat(from);
const fromOnDisk = await fs.readlink(to);
const toDir = path.dirname(to);
const absoluteFrom = path.resolve(toDir, from);
const absoluteFromOnDisk = path.resolve(toDir, fromOnDisk);
if (absoluteFrom !== absoluteFromOnDisk) {
info(
`Different link at '${to}' already exists. Leaving it alone, the package is probably already installed in the child package.`,
debug(
`Different link at '${to}' to '${absoluteFromOnDisk}' already exists. Leaving it alone, the package is probably already installed in the child package.`,
);
return {
status: 'differentLinkAlreadyExists',
};
} else {
debug(`Link at '${to}' already exists.`);
return {
status: 'alreadyExists',
};
}
} catch {
return symlink(from, to);
}
}

function info(message: string, ...args: unknown[]) {
function debug(message: string, ...args: unknown[]) {
const log = getLogger('link');
log.info(message, ...args);
log.debug(message, ...args);
}
20 changes: 20 additions & 0 deletions test/helpers/createLogStub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import sinon from 'sinon';
import { Logger } from 'log4js';

export function createLoggerMock(): sinon.SinonStubbedInstance<Logger> {
return {
debug: sinon.stub(),
error: sinon.stub(),
fatal: sinon.stub(),
info: sinon.stub(),
isDebugEnabled: sinon.stub(),
isErrorEnabled: sinon.stub(),
isFatalEnabled: sinon.stub(),
isInfoEnabled: sinon.stub(),
clearContext: sinon.stub(),
isLevelEnabled: sinon.stub(),
isTraceEnabled: sinon.stub(),
isWarnEnabled: sinon.stub(),
log: sinon.stub(),
} as sinon.SinonStubbedInstance<Logger>;
}

0 comments on commit 332aa13

Please sign in to comment.