Skip to content

Commit

Permalink
feat: Make linkable able to automatically attempt fixing corrupted ya…
Browse files Browse the repository at this point in the history
…lc.lock files
  • Loading branch information
Iku-turso committed May 30, 2023
1 parent 2de597b commit a574e19
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 10 deletions.
8 changes: 8 additions & 0 deletions packages/linkable/src/console-log.injectable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { getInjectable } from '@ogre-tools/injectable';

export type ConsoleLog = typeof console.log;

export const consoleLogInjectable = getInjectable({
id: 'console-log',
instantiate: (): ConsoleLog => console.log,
});
8 changes: 8 additions & 0 deletions packages/linkable/src/console-warn.injectable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { getInjectable } from '@ogre-tools/injectable';

export type ConsoleWarn = typeof console.warn;

export const consoleWarnInjectable = getInjectable({
id: 'console-warn',
instantiate: (): ConsoleWarn => console.warn,
});
78 changes: 76 additions & 2 deletions packages/linkable/src/push-link.injectable.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { getInjectable } from '@ogre-tools/injectable';
import { workingDirectoryInjectable } from './shared/working-directory.injectable';
import { publishYalcPackageInjectable } from './publish-yalc-package.injectable';
import { addYalcPackagesInjectable } from './add-yalc-packages.injectable';
import { pipeline } from '@ogre-tools/fp';
import { consoleLogInjectable } from './console-log.injectable';
import { consoleWarnInjectable } from './console-warn.injectable';

export type PushLink = () => Promise<void>;

Expand All @@ -9,8 +13,78 @@ export const pushLinkInjectable = getInjectable({
instantiate: (di): PushLink => {
const workingDirectory = di.inject(workingDirectoryInjectable);
const publishYalcPackage = di.inject(publishYalcPackageInjectable);
const addYalcPackages = di.inject(addYalcPackagesInjectable);
const consoleLog = di.inject(consoleLogInjectable);
const consoleWarn = di.inject(consoleWarnInjectable);

return () =>
publishYalcPackage({ push: true, workingDir: workingDirectory });
return async () => {
const lockFileProblems: LockFileProblem[] = [];

const originalConsoleLog = console.log;
const originalConsoleWarn = console.warn;

console.log = (...messageArguments: string[]) => {
const lockFileProblem = getLockFileProblem(messageArguments);

if (lockFileProblem) {
lockFileProblems.push(lockFileProblem);
return;
}

consoleLog(...messageArguments);
};

console.warn = (...messageArguments: string[]) => {
if (isLockFileWarning(messageArguments)) {
return;
}

consoleWarn(...messageArguments);
};

await publishYalcPackage({
push: true,
workingDir: workingDirectory,
});

console.log = originalConsoleLog;
console.warn = originalConsoleWarn;

await pipeline(
lockFileProblems,

problems => {
problems.forEach(problem => {
consoleLog(
`Encountered corrupted yalc.lock in ${problem.targetDirectory}, resolving automatically by adding ${problem.moduleName}.`,
);
});

return problems;
},

async problems => {
for (let problem of problems) {
await addYalcPackages([problem.moduleName], {
link: true,
workingDir: problem.targetDirectory,
pure: false,
});
}
},
);
};
},
});

type LockFileProblem = { moduleName: string; targetDirectory: string };

const getLockFileProblem = ([message]: string[]): LockFileProblem | undefined =>
message.match(
/^Removing installation of (?<moduleName>.+?) in (?<targetDirectory>.+?)$/,
)?.groups as LockFileProblem | undefined;

const isLockFileWarning = ([message]: string[]): boolean =>
!!message.match(
/^Did not find package (?<moduleName>.+?) in lockfile, please use 'add' command to add it explicitly\.$/,
);
153 changes: 145 additions & 8 deletions packages/linkable/src/push-link.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,51 @@ import asyncFn from '@async-fn/jest';
import type { PushLink } from './push-link.injectable';
import { pushLinkInjectable } from './push-link.injectable';
import { getDi } from './get-di';

import {
PublishYalcPackage,
publishYalcPackageInjectable,
} from './publish-yalc-package.injectable';

import { getPromiseStatus } from '@ogre-tools/test-utils';
import { workingDirectoryInjectable } from './shared/working-directory.injectable';

import {
AddYalcPackages,
addYalcPackagesInjectable,
} from './add-yalc-packages.injectable';

import { ConsoleLog, consoleLogInjectable } from './console-log.injectable';
import { ConsoleWarn, consoleWarnInjectable } from './console-warn.injectable';

describe('push-links', () => {
let pushLink: PushLink;
let publishYalcPackageMock: AsyncFnMock<PublishYalcPackage>;
let addYalcPackagesMock: AsyncFnMock<AddYalcPackages>;
let consoleLogMock: jest.Mock<ConsoleLog>;
let consoleWarnMock: jest.Mock<ConsoleWarn>;
let originalConsoleLog: typeof console.log;
let originalConsoleWarn: typeof console.warn;

beforeEach(() => {
originalConsoleLog = console.log;
originalConsoleWarn = console.warn;

const di = getDi();

publishYalcPackageMock = asyncFn();
di.override(publishYalcPackageInjectable, () => publishYalcPackageMock);
di.override(workingDirectoryInjectable, () => 'some-working-directory');

addYalcPackagesMock = asyncFn();
di.override(addYalcPackagesInjectable, () => addYalcPackagesMock);

consoleLogMock = jest.fn();
di.override(consoleLogInjectable, () => consoleLogMock);

consoleWarnMock = jest.fn();
di.override(consoleWarnInjectable, () => consoleWarnMock);

pushLink = di.inject(pushLinkInjectable);
});

Expand All @@ -44,20 +71,130 @@ describe('push-links', () => {
expect(promiseStatus.fulfilled).toBe(false);
});

it('when publish resolves, ends script', async () => {
publishYalcPackageMock.resolve();
describe('given there are no missing packages in yalc-lockfiles of targets of publish, when publish resolves', () => {
beforeEach(async () => {
await publishYalcPackageMock.resolve();
});

it('ends script', async () => {
const promiseStatus = await getPromiseStatus(actualPromise);

const promiseStatus = await getPromiseStatus(actualPromise);
expect(promiseStatus.fulfilled).toBe(true);
});

expect(promiseStatus.fulfilled).toBe(true);
it('does not add packages', () => {
expect(addYalcPackagesMock).not.toHaveBeenCalled();
});

it('does not log', () => {
expect(consoleLogMock).not.toHaveBeenCalled();
});

it('does not warn', () => {
expect(consoleWarnMock).not.toHaveBeenCalled();
});

it('global console.log is restored to normal', () => {
expect(console.log).toBe(originalConsoleLog);
});

it('global console.warn is restored to normal', () => {
expect(console.warn).toBe(originalConsoleWarn);
});
});

it('when publish resolves, ends script', async () => {
publishYalcPackageMock.resolve();
describe('given there are missing packages in yalc-lockfiles of targets of publish, when publish resolves', () => {
beforeEach(async () => {
console.warn(
"Did not find package some-package in lockfile, please use 'add' command to add it explicitly.",
);

const promiseStatus = await getPromiseStatus(actualPromise);
console.log(
'Removing installation of some-package in /some/target-package/directory',
);

console.log(
'Removing installation of some-package in /some-other/target-package/directory',
);

console.log('Some irrelevant logging');
console.warn('Some irrelevant warning');

expect(promiseStatus.fulfilled).toBe(true);
await publishYalcPackageMock.resolve();
});

it('console.logs only the messages without custom handling', () => {
expect(consoleLogMock.mock.calls).toEqual([
['Some irrelevant logging'],

[
'Encountered corrupted yalc.lock in /some/target-package/directory, resolving automatically by adding some-package.',
],

[
'Encountered corrupted yalc.lock in /some-other/target-package/directory, resolving automatically by adding some-package.',
],
]);
});

it('console.warns only the messages without custom handling', () => {
expect(consoleWarnMock.mock.calls).toEqual([
['Some irrelevant warning'],
]);
});

it('does not end script yet', async () => {
const promiseStatus = await getPromiseStatus(actualPromise);

expect(promiseStatus.fulfilled).toBe(false);
});

it('global console.log is restored to normal', () => {
expect(console.log).toBe(originalConsoleLog);
});

it('global console.warn is restored to normal', () => {
expect(console.warn).toBe(originalConsoleWarn);
});

it('calls for adding the first missing package', async () => {
expect(addYalcPackagesMock.mock.calls).toEqual([
[
['some-package'],
{
link: true,
pure: false,
workingDir: '/some/target-package/directory',
},
],
]);
});

it('when add of first missing package resolves, adds the next one', async () => {
addYalcPackagesMock.mockClear();

await addYalcPackagesMock.resolve();

expect(addYalcPackagesMock.mock.calls).toEqual([
[
['some-package'],
{
link: true,
pure: false,
workingDir: '/some-other/target-package/directory',
},
],
]);
});

it('when adding all the packages resolves, resolves', async () => {
await addYalcPackagesMock.resolve();
await addYalcPackagesMock.resolve();

const promiseStatus = await getPromiseStatus(actualPromise);

expect(promiseStatus.fulfilled).toBe(true);
});
});
});
});

0 comments on commit a574e19

Please sign in to comment.