diff --git a/package.json b/package.json index 511daab..ecabcef 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,9 @@ "lint:fix": "yarn lint --fix", "test": "jest src", "test:watch": "yarn test --watchAll", - "test:coverage": "yarn test --coverage" + "test:coverage": "yarn test --coverage", + "types": "tsc --noEmit src/**/*.ts", + "types:watch": "yarn types --watch" }, "devDependencies": { "@babel/cli": "^7.8.4", diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 880a897..42631a9 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -2,16 +2,17 @@ import { promises as fs } from 'fs' import { tmpdir } from 'os' import { join, resolve } from 'path' +import logger from '../logger' import type { Report } from '../index.d' -import runPackwatch from '..' +import packwatch from '..' async function prepareWorkspace(): Promise { - const workspacePath = await fs.mkdtemp(`${tmpdir()}/`, { recursive: true }) + const workspacePath = await fs.mkdtemp(`${tmpdir()}/`) return workspacePath } async function cleanUpWorkspace(paths: string[]): Promise { - return Promise.all( + await Promise.all( paths.map(async path => fs.rmdir(path, { recursive: true })), ) } @@ -37,9 +38,13 @@ async function createManifest( } describe('Packwatch', () => { let mockLogger + let mockWarn + let mockError let workspacePath beforeEach(() => { - mockLogger = jest.spyOn(global.console, 'log').mockImplementation() + mockLogger = jest.spyOn(console, 'log').mockImplementation() + mockWarn = jest.spyOn(console, 'warn').mockImplementation() + mockError = jest.spyOn(console, 'error').mockImplementation() }) afterEach(async () => { @@ -53,7 +58,9 @@ describe('Packwatch', () => { it('warns the user and errors if run away from package.json', async () => { workspacePath = await prepareWorkspace() - runPackwatch({ cwd: workspacePath }) + await expect(async () => + packwatch({ cwd: workspacePath }), + ).rejects.toThrow('NOT_IN_PACKAGE_ROOT') expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toEqual( @@ -68,7 +75,9 @@ describe('Packwatch', () => { workspacePath = await prepareWorkspace() await createPackageJson(workspacePath) - runPackwatch({ cwd: workspacePath }) + await expect(async () => + packwatch({ cwd: workspacePath }), + ).rejects.toThrow('NO_MANIFEST_NO_UPDATE') const generatedManifest = await fs.readFile( resolve(join(workspacePath, '.packwatch.json')), @@ -84,15 +93,18 @@ describe('Packwatch', () => { workspacePath = await prepareWorkspace() await createPackageJson(workspacePath) - runPackwatch({ cwd: workspacePath }) + await expect(async () => + packwatch({ cwd: workspacePath }), + ).rejects.toThrow() - expect(mockLogger.mock.calls).toHaveLength(2) - expect(mockLogger.mock.calls[0][0]).toEqual( + expect(mockWarn.mock.calls).toHaveLength(1) + expect(mockWarn.mock.calls[0][0]).toEqual( expect.stringMatching( /No Manifest to compare against! Current package stats written to \.packwatch\.json!\nPackage size \(\d+ B\) adopted as new limit\./, ), ) - expect(mockLogger.mock.calls[1][0]).toEqual( + expect(mockError.mock.calls).toHaveLength(1) + expect(mockError.mock.calls[0][0]).toEqual( expect.stringMatching( 'It looks like you ran PackWatch without a manifest. To prevent accidental passes in CI or hooks, packwatch will terminate with an error. If you are running packwatch for the first time in your project, this is expected!', ), @@ -104,10 +116,10 @@ describe('Packwatch', () => { await createPackageJson(workspacePath) - runPackwatch({ cwd: workspacePath, isUpdatingManifest: true }) + await packwatch({ cwd: workspacePath, isUpdatingManifest: true }) - expect(mockLogger.mock.calls).toHaveLength(1) - expect(mockLogger.mock.calls[0][0]).toEqual( + expect(mockWarn.mock.calls).toHaveLength(1) + expect(mockWarn.mock.calls[0][0]).toEqual( expect.stringMatching( /No Manifest to compare against! Current package stats written to \.packwatch\.json!\nPackage size \(\d+ B\) adopted as new limit\./, ), @@ -125,7 +137,7 @@ describe('Packwatch', () => { packageSize: '160B', unpackedSize: '150B', }) - runPackwatch({ cwd: workspacePath }) + await packwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toEqual( expect.stringMatching( @@ -144,7 +156,7 @@ describe('Packwatch', () => { unpackedSize: '150B', }) - runPackwatch({ cwd: workspacePath }) + await packwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toEqual( expect.stringMatching( @@ -162,7 +174,7 @@ describe('Packwatch', () => { unpackedSize: '140B', }) - runPackwatch({ cwd: workspacePath }) + await packwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toEqual( expect.stringMatching( @@ -180,7 +192,7 @@ describe('Packwatch', () => { unpackedSize: '140B', }) - runPackwatch({ cwd: workspacePath }) + await packwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toEqual( expect.stringMatching( @@ -198,9 +210,11 @@ describe('Packwatch', () => { unpackedSize: '140B', }) - runPackwatch({ cwd: workspacePath }) - expect(mockLogger.mock.calls).toHaveLength(1) - expect(mockLogger.mock.calls[0][0]).toEqual( + await expect(async () => + packwatch({ cwd: workspacePath }), + ).rejects.toThrow('PACKAGE_EXCEEDS_LIMIT') + expect(mockError.mock.calls).toHaveLength(1) + expect(mockError.mock.calls[0][0]).toEqual( expect.stringMatching( /Your package exceeds the limit set in \.packwatch\.json! \d+ B > 10B\nEither update the limit by using the --update-manifest flag or trim down your packed files!/, ), @@ -217,7 +231,7 @@ describe('Packwatch', () => { unpackedSize: '140B', }) - runPackwatch({ cwd: workspacePath, isUpdatingManifest: true }) + await packwatch({ cwd: workspacePath, isUpdatingManifest: true }) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toEqual( expect.stringMatching( diff --git a/src/utils.test.ts b/src/__tests__/utils.test.ts similarity index 89% rename from src/utils.test.ts rename to src/__tests__/utils.test.ts index 5a4ea33..f7cc2f9 100644 --- a/src/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -1,4 +1,4 @@ -import { convertSizeToBytes } from './utils' +import { convertSizeToBytes } from '../utils' describe('utils', () => { it.each` diff --git a/src/cli.ts b/src/cli.ts index a13de2f..3d830c3 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,8 +1,9 @@ #!/usr/bin/env node -import runPackwatch from '.' +import packwatch from '.' const isUpdatingManifest = process.argv.includes('--update-manifest') const cwd = process.cwd() -const processExit = runPackwatch({ cwd, isUpdatingManifest }) -process.exit(processExit) +packwatch({ cwd, isUpdatingManifest }) + .catch(() => process.exit(1)) + .then(() => process.exit(0)) diff --git a/src/index.d.ts b/src/index.d.ts index c0a52f0..c35984e 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,3 +1,8 @@ +export type PackwatchArguments = { + cwd?: string + isUpdatingManifest?: boolean +} + export type Report = { packageSize: string unpackedSize?: string diff --git a/src/index.ts b/src/index.ts index faced80..9adeaa7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,25 +6,19 @@ import { getCurrentPackageStats, getPreviousPackageStats, } from './utils' +import type { PackwatchArguments } from './index.d' +import { assertInPackageRoot } from './invariants' +import logger from './logger' const MANIFEST_FILENAME = '.packwatch.json' -export default function run({ +export default async function packwatch({ cwd, isUpdatingManifest, -}: { - cwd?: string - isUpdatingManifest?: boolean -}): number { - const packageJsonPath = resolve(join(cwd, 'package.json')) +}: PackwatchArguments): Promise { const manifestPath = resolve(join(cwd, MANIFEST_FILENAME)) - if (!existsSync(packageJsonPath)) { - console.log( - '🤔 There is no package.json file here. Are you in the root directory of your project?', - ) - return 1 - } + assertInPackageRoot(cwd) const currentStats = getCurrentPackageStats(cwd) @@ -35,18 +29,17 @@ export default function run({ if (!existsSync(manifestPath)) { createOrUpdateManifest({ manifestPath, current: currentStats }) - console.log( + logger.warn( `📝 No Manifest to compare against! Current package stats written to ${MANIFEST_FILENAME}!\nPackage size (${currentStats.packageSize}) adopted as new limit.`, ) if (!isUpdatingManifest) { - console.log( + logger.error( '❗ It looks like you ran PackWatch without a manifest. To prevent accidental passes in CI or hooks, packwatch will terminate with an error. If you are running packwatch for the first time in your project, this is expected!', ) + throw new Error('NO_MANIFEST_NO_UPDATE') } - // If the update flag wasn't specified, exit with a non-zero code so we - // don't "accidentally" pass CI builds if the manifest didn't exist - return isUpdatingManifest ? 0 : 1 + return } const previousStats = getPreviousPackageStats(cwd) @@ -70,10 +63,10 @@ export default function run({ updateLimit: true, manifestPath, }) - console.log( + logger.log( `📝 Updated the manifest! Package size: ${packageSize}, Limit: ${packageSize}`, ) - return 0 + return } /* @@ -82,10 +75,10 @@ export default function run({ */ if (hasExceededLimit) { - console.log( + logger.error( `🔥🔥📦🔥🔥 Your package exceeds the limit set in ${MANIFEST_FILENAME}! ${packageSize} > ${limit}\nEither update the limit by using the --update-manifest flag or trim down your packed files!`, ) - return 1 + throw new Error('PACKAGE_EXCEEDS_LIMIT') } /* @@ -95,17 +88,17 @@ export default function run({ */ if (packageSizeBytes > previousSizeBytes) { - console.log( + logger.log( `📦 👀 Your package grew! ${packageSize} > ${previousSize} (Limit: ${limit})`, ) } else if (packageSizeBytes < previousSizeBytes) { - console.log( + logger.log( `📦 💯 Your package shrank! ${packageSize} < ${previousSize} (Limit: ${limit})`, ) } else { - console.log( + logger.log( `📦 Nothing to report! Your package is the same size as the latest manifest reports! (Limit: ${limit})`, ) } - return 0 + return } diff --git a/src/invariants.ts b/src/invariants.ts new file mode 100644 index 0000000..01309aa --- /dev/null +++ b/src/invariants.ts @@ -0,0 +1,16 @@ +import { existsSync } from 'fs' +import { join, resolve } from 'path' + +import logger from './logger' + +export function assertInPackageRoot(cwd: string): void { + const packagePath = resolve(join(cwd, 'package.json')) + const packageJsonExists = existsSync(packagePath) + + if (!packageJsonExists) { + logger.log( + '🤔 There is no package.json file here. Are you in the root directory of your project?', + ) + throw new Error('NOT_IN_PACKAGE_ROOT') + } +} diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..9e7eaa8 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,11 @@ +export default { + log: (...args: unknown[]): void => { + console.log(...args) + }, + warn: (...args: unknown[]): void => { + console.warn(...args) + }, + error: (...args: unknown[]): void => { + console.error(...args) + }, +} diff --git a/src/utils.ts b/src/utils.ts index 9e254fd..0c5182b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -68,6 +68,7 @@ export function createOrUpdateManifest({ }: { previous?: Report current: Report + manifestPath: string updateLimit?: boolean }): void { const { limit } = previous || {}