import { promises as fs } from 'fs' import { tmpdir } from 'os' import { join, resolve } from 'path' import type { Report } from '../src/types' import packwatch from '../src' let workspace: string | null async function prepareWorkspace(): Promise { const workspacePath = await fs.mkdtemp(`${tmpdir()}/`) workspace = workspacePath return workspacePath } async function cleanUpWorkspace(paths: string[]): Promise { await Promise.all( paths.map(async path => fs.rmdir(path, { recursive: true })), ) } async function createFile(path: string, content: string): Promise { await fs.writeFile(path, content) } async function createPackageJson(cwd: string): Promise { const path = resolve(join(cwd, 'package.json')) await createFile( path, '{ "name": "wow", "version": "0.0.0", "files": ["!.packwatch.json"] }', ) } async function createManifest( cwd: string, configuration: Report, ): Promise { const path = resolve(join(cwd, '.packwatch.json')) await createFile(path, JSON.stringify(configuration)) } describe('Packwatch', () => { afterEach(async () => { jest.restoreAllMocks() if (workspace) { await cleanUpWorkspace([workspace]) workspace = null } }) it('warns the user and errors if run away from package.json', async () => { const workspacePath = await prepareWorkspace() const mockLogger = jest.spyOn(console, 'log') 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( expect.stringMatching( 'There is no package.json file here. Are you in the root directory of your project?', ), ) }) describe('without manifest', () => { it('generates the initial manifest properly', async () => { const workspacePath = await prepareWorkspace() await createPackageJson(workspacePath) await expect(async () => packwatch({ cwd: workspacePath }), ).rejects.toThrow('NO_MANIFEST_NO_UPDATE') const generatedManifest = await fs.readFile( resolve(join(workspacePath, '.packwatch.json')), { encoding: 'utf8' }, ) expect(generatedManifest).toEqual( '{"limit":"160 B","packageSize":"160 B","unpackedSize":"68 B"}', ) }) it('outputs expected messaging', async () => { const workspacePath = await prepareWorkspace() const mockWarn = jest.spyOn(console, 'warn') const mockError = jest.spyOn(console, 'error') await createPackageJson(workspacePath) await expect(async () => packwatch({ cwd: workspacePath }), ).rejects.toThrow() 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(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!', ), ) }) it('outputs expected messaging when not updating the manifest', async () => { const mockWarn = jest.spyOn(console, 'warn') const workspacePath = await prepareWorkspace() await createPackageJson(workspacePath) await packwatch({ cwd: workspacePath, isUpdatingManifest: true }) 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\./, ), ) }) }) describe('with manifest', () => { it('messages when the size is equal to the limit', async () => { const workspacePath = await prepareWorkspace() const mockLogger = jest.spyOn(console, 'log') await createPackageJson(workspacePath) await createManifest(workspacePath, { limit: '160B', packageSize: '160B', packageSizeBytes: 160, unpackedSize: '150B', unpackedSizeBytes: 150, }) await packwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toEqual( expect.stringMatching( /Nothing to report! Your package is the same size as the latest manifest reports! \(Limit: 160B\)/, ), ) }) it('messages when the size is lower than the limit (no growth)', async () => { const workspacePath = await prepareWorkspace() const mockLogger = jest.spyOn(console, 'log') await createPackageJson(workspacePath) await createManifest(workspacePath, { limit: '170B', packageSize: '160B', packageSizeBytes: 160, unpackedSize: '150B', unpackedSizeBytes: 150, }) await packwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toEqual( expect.stringMatching( /Nothing to report! Your package is the same size as the latest manifest reports! \(Limit: 170B\)/, ), ) }) it('messages when the size is lower than the limit (growth)', async () => { const workspacePath = await prepareWorkspace() const mockLogger = jest.spyOn(console, 'log') await createPackageJson(workspacePath) await createManifest(workspacePath, { limit: '180B', packageSize: '150B', packageSizeBytes: 150, unpackedSize: '140B', unpackedSizeBytes: 140, }) await packwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toEqual( expect.stringMatching( /Your package grew! \d+ B > 150B \(Limit: 180B\)/, ), ) }) it('messages when the size is lower than the limit (shrinkage)', async () => { const workspacePath = await prepareWorkspace() const mockLogger = jest.spyOn(console, 'log') await createPackageJson(workspacePath) await createManifest(workspacePath, { limit: '180B', packageSize: '170B', packageSizeBytes: 170, unpackedSize: '140B', unpackedSizeBytes: 140, }) await packwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toEqual( expect.stringMatching( /Your package shrank! \d+ B < 170B \(Limit: 180B\)/, ), ) }) it('messages when the size exceeds the limit', async () => { const workspacePath = await prepareWorkspace() const mockError = jest.spyOn(console, 'error') await createPackageJson(workspacePath) await createManifest(workspacePath, { limit: '10B', packageSize: '170B', packageSizeBytes: 170, unpackedSize: '140B', unpackedSizeBytes: 140, }) 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!/, ), ) }) it('messages when updating the manifest', async () => { const workspacePath = await prepareWorkspace() const mockLogger = jest.spyOn(console, 'log') await createPackageJson(workspacePath) await createManifest(workspacePath, { limit: '10B', packageSize: '170B', packageSizeBytes: 170, unpackedSize: '140B', unpackedSizeBytes: 140, }) await packwatch({ cwd: workspacePath, isUpdatingManifest: true }) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toEqual( expect.stringMatching( /Updated the manifest! Package size: \d+ B, Limit: \d+ B/, ), ) }) }) })