From 749274ee53dc84c16b1c1f37deddc6d44492b61f Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Wed, 17 Feb 2021 23:39:13 -0500 Subject: [PATCH 1/7] refactor: parametrize cwd --- src/index.ts | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/index.ts b/src/index.ts index fb7d0ed..dc24926 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { spawnSync } from 'child_process' import { existsSync, readFileSync, writeFileSync } from 'fs' +import { join, resolve } from 'path' import { Report } from './index.d' @@ -24,9 +25,10 @@ function convertSizeToBytes(sizeString: string): number { return multiplier * parseFloat(sizeMagnitude) } -function getCurrentPackageStats(): Report { +function getCurrentPackageStats(cwd: string): Report { const { stderr } = spawnSync('npm', ['pack', '--dry-run'], { encoding: 'utf-8', + cwd, }) const stderrString = String(stderr) const packageSize = PACKAGE_SIZE_PATT.exec(stderrString)[1] @@ -40,9 +42,10 @@ function getCurrentPackageStats(): Report { } } -function getPreviousPackageStats(): Report | null { +function getPreviousPackageStats(cwd: string): Report | null { + const manifestPath = resolve(join(cwd, MANIFEST_FILENAME)) try { - const currentManifest = readFileSync(MANIFEST_FILENAME, { + const currentManifest = readFileSync(manifestPath, { encoding: 'utf-8', }) const parsedManifest = JSON.parse(currentManifest) @@ -60,6 +63,7 @@ function getPreviousPackageStats(): Report | null { function createOrUpdateManifest({ previous, current, + manifestPath, updateLimit = false, }: { previous?: Report @@ -75,33 +79,39 @@ function createOrUpdateManifest({ unpackedSize: unpackedSize, } - writeFileSync(MANIFEST_FILENAME, JSON.stringify(newManifest)) + writeFileSync(manifestPath, JSON.stringify(newManifest)) } -export default function run( - { - manifestFn = MANIFEST_FILENAME, - isUpdatingManifest, - }: { manifestFn?: string; isUpdatingManifest?: boolean } = { - manifestFn: MANIFEST_FILENAME, - isUpdatingManifest: false, - }, -): number { - if (!existsSync('package.json')) { + +export default function run({ + cwd, + isUpdatingManifest, +}: { + cwd?: string + isUpdatingManifest?: boolean +}): number { + if (!cwd) { + cwd = process.cwd() + } + + const packageJsonPath = resolve(join(cwd, 'package.json')) + 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 } - const currentStats = getCurrentPackageStats() + const currentStats = getCurrentPackageStats(cwd) /* * If there is no manifest file yet, we can use the current package stats as * a base to build one. The current package size becomes the limit. */ - if (!existsSync(manifestFn)) { - createOrUpdateManifest({ current: currentStats }) + if (!existsSync(manifestPath)) { + createOrUpdateManifest({ manifestPath, current: currentStats }) console.log( `📝 No Manifest to compare against! Current package stats written to ${MANIFEST_FILENAME}!\nPackage size (${currentStats.packageSize}) adopted as new limit.`, ) @@ -116,7 +126,7 @@ export default function run( return isUpdatingManifest ? 0 : 1 } - const previousStats = getPreviousPackageStats() + const previousStats = getPreviousPackageStats(cwd) const { packageSizeBytes, packageSize } = currentStats const { packageSize: previousSize, @@ -135,6 +145,7 @@ export default function run( previous: previousStats, current: currentStats, updateLimit: true, + manifestPath, }) console.log( `📝 Updated the manifest! Package size: ${packageSize}, Limit: ${packageSize}`, -- 2.45.2 From 49e1d798c8cc5d36d29096fdf4beeda2fb25a7c5 Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Wed, 17 Feb 2021 23:39:48 -0500 Subject: [PATCH 2/7] test: set up helpers and validate --- src/__tests__/index.test.ts | 168 +++++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 59 deletions(-) diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index b40c800..9297999 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -1,11 +1,13 @@ import * as childProcess from 'child_process' -import { readFileSync } from 'fs' +import { promises as fs, readFileSync } from 'fs' +import { tmpdir } from 'os' +import { join, resolve } from 'path' import mockFS from 'mock-fs' import runPackwatch from '..' -jest.mock('child_process') +//jest.mock('child_process') function getPackOutput({ packageSize, unpackedSize }) { return ` @@ -28,48 +30,74 @@ npm notice ` } +async function prepareWorkspace(): Promise { + const workspacePath = await fs.mkdtemp(`${tmpdir()}/`, { recursive: true }) + return workspacePath +} + +async function cleanUpWorkspace(paths: string[]): Promise { + return Promise.all( + paths.map(async path => fs.rmdir(path, { recursive: true })), + ) +} + +async function createFile(path: string, content: string): Promise { + await fs.writeFile(path, content) +} + +/* function getManifest() { - try { - return JSON.parse(readFileSync('.packwatch.json', { encoding: 'utf8' })) - } catch { - /* No manifest */ - } + try { + return JSON.parse(readFileSync(".packwatch.json", { encoding: "utf8" })) + } catch { + } } function setupMockFS({ - hasPackageJSON, - hasManifest, - manifestLimit, - manifestSize, + hasPackageJSON, + hasManifest, + manifestLimit, + manifestSize, }) { - const fs = {} + const fs = {} - if (hasPackageJSON) fs['package.json'] = '{}' + if (hasPackageJSON) fs["package.json"] = "{}" - if (hasManifest) - fs['.packwatch.json'] = JSON.stringify({ - unpackedSize: '0.5 B', - limitBytes: manifestLimit, - limit: `${manifestLimit} B`, - packageSize: `${manifestSize} B`, - packageSizeBytes: manifestSize, - }) - mockFS(fs) + if (hasManifest) + fs[".packwatch.json"] = JSON.stringify({ + unpackedSize: "0.5 B", + limitBytes: manifestLimit, + limit: `${manifestLimit} B`, + packageSize: `${manifestSize} B`, + packageSizeBytes: manifestSize, + }) + mockFS(fs) } +*/ describe('Packwatch', () => { let mockLogger + let workspacePath beforeEach(() => { - mockFS({}) - mockLogger = jest.spyOn(global.console, 'log').mockImplementation() + //mockFS({}) + //mockLogger = jest.spyOn(global.console, 'log').mockImplementation() }) - afterEach(jest.restoreAllMocks) + afterEach(async () => { + jest.restoreAllMocks() - afterAll(mockFS.restore) + if (workspacePath) { + await cleanUpWorkspace([workspacePath]) + workspacePath = null + } + }) - it('warns the user and errors if run away from package.json', () => { - mockFS({}) - runPackwatch() + /* + afterAll(mockFS.restore) + */ + + it.skip('warns the user and errors if run away from package.json', async () => { + workspacePath = await prepareWorkspace() + runPackwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot( @@ -78,31 +106,53 @@ describe('Packwatch', () => { }) describe('without manifest', () => { - beforeEach(() => { - setupMockFS({ hasPackageJSON: true }) + /* + beforeEach(() => { + setupMockFS({ hasPackageJSON: true }) + }) + */ + + it('generates the initial manifest properly', async () => { + workspacePath = await prepareWorkspace() + + await createFile( + resolve(join(workspacePath, 'package.json')), + '{ "name": "wow", "version": "0.0.0" }', + ) + + runPackwatch({ cwd: workspacePath }) + + const generatedManifest = await fs.readFile( + resolve(join(workspacePath, '.packwatch.json')), + { encoding: 'utf8' }, + ) + + expect(generatedManifest).toMatchInlineSnapshot( + '"{\\"limit\\":\\"138 B\\",\\"packageSize\\":\\"138 B\\",\\"unpackedSize\\":\\"37 B\\"}"', + ) }) - it.each(['1 B', '1.1 B', '1 kB', '1.1 kB', '1 mB', '1.1 mB'])( - 'generates the initial manifest properly (size = %s)', - mockSize => { - jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ - stderr: getPackOutput({ - packageSize: mockSize, - unpackedSize: mockSize, - }), - }) - const returnCode = runPackwatch() - const manifest = getManifest() - expect(returnCode).toEqual(1) - expect(manifest).toEqual({ - limit: mockSize, - packageSize: mockSize, - unpackedSize: mockSize, - }) - }, - ) + /*it.each(["1 B", "1.1 B", "1 kB", "1.1 kB", "1 mB", "1.1 mB"])( + "generates the initial manifest properly (size = %s)", + (mockSize) => { + jest.spyOn(childProcess, "spawnSync").mockReturnValue({ + stderr: getPackOutput({ + packageSize: mockSize, + unpackedSize: mockSize, + }), + }) + const returnCode = runPackwatch({}) + const manifest = getManifest() + expect(returnCode).toEqual(1) + expect(manifest).toEqual({ + limit: mockSize, + packageSize: mockSize, + unpackedSize: mockSize, + }) + } + )*/ - it('outputs expected messaging', () => { + it.skip('outputs expected messaging', () => { jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ stderr: getPackOutput({ packageSize: '1 B', @@ -110,7 +160,7 @@ describe('Packwatch', () => { }), }) - runPackwatch() + runPackwatch({}) expect(mockLogger.mock.calls).toHaveLength(2) expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot(` @@ -122,7 +172,7 @@ describe('Packwatch', () => { ) }) - it('outputs expected messaging when not updating the manifest', () => { + it.skip('outputs expected messaging when not updating the manifest', () => { jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ stderr: getPackOutput({ packageSize: '1 B', @@ -140,7 +190,7 @@ describe('Packwatch', () => { }) }) - describe('with manifest', () => { + describe.skip('with manifest', () => { it('messages when the size is equal to the limit', () => { setupMockFS({ hasPackageJSON: true, @@ -154,7 +204,7 @@ describe('Packwatch', () => { unpackedSize: '2 B', }), }) - runPackwatch() + runPackwatch({}) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot( '"📦 Nothing to report! Your package is the same size as the latest manifest reports! (Limit: 1 B)"', @@ -174,7 +224,7 @@ describe('Packwatch', () => { unpackedSize: '2 B', }), }) - runPackwatch() + runPackwatch({}) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot( '"📦 Nothing to report! Your package is the same size as the latest manifest reports! (Limit: 5 B)"', @@ -193,7 +243,7 @@ describe('Packwatch', () => { unpackedSize: '2 B', }), }) - runPackwatch() + runPackwatch({}) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot( '"📦 👀 Your package grew! 3 B > 2 B (Limit: 5 B)"', @@ -212,7 +262,7 @@ describe('Packwatch', () => { unpackedSize: '2 B', }), }) - runPackwatch() + runPackwatch({}) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot( '"📦 💯 Your package shrank! 1 B < 2 B (Limit: 5 B)"', @@ -231,7 +281,7 @@ describe('Packwatch', () => { unpackedSize: '2 B', }), }) - runPackwatch() + runPackwatch({}) expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot(` "🔥🔥📦🔥🔥 Your package exceeds the limit set in .packwatch.json! 1 B > 0.5 B -- 2.45.2 From c11dc479ec169b4bc5cc1b8c9067ab41ee0025c0 Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Thu, 18 Feb 2021 23:12:16 -0500 Subject: [PATCH 3/7] refactor: remove mocks from tests --- package.json | 1 - src/__tests__/index.test.ts | 345 ++++++++++++++---------------------- yarn.lock | 8 - 3 files changed, 131 insertions(+), 223 deletions(-) diff --git a/package.json b/package.json index d4866e3..776128f 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "eslint-plugin-jest": "^24.1.5", "eslint-plugin-prettier": "^3.1.2", "jest": "^26.0.1", - "mock-fs": "^4.11.0", "prettier": "^2.0.5", "rimraf": "^3.0.2", "semantic-release": "^17.0.4", diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 9297999..880a897 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -1,35 +1,10 @@ -import * as childProcess from 'child_process' -import { promises as fs, readFileSync } from 'fs' +import { promises as fs } from 'fs' import { tmpdir } from 'os' import { join, resolve } from 'path' -import mockFS from 'mock-fs' - +import type { Report } from '../index.d' import runPackwatch from '..' -//jest.mock('child_process') - -function getPackOutput({ packageSize, unpackedSize }) { - return ` -npm notice -npm notice 📦 footprint@0.0.0 -npm notice === Tarball Contents === -npm notice 732B package.json -npm notice 1.8kB dist/helpers.js -npm notice 1.9kB dist/index.js -npm notice === Tarball Details === -npm notice name: footprint -npm notice version: 0.0.0 -npm notice filename: footprint-0.0.0.tgz -npm notice package size: ${packageSize} -npm notice unpacked size: ${unpackedSize} -npm notice shasum: bdf33d471543cd8126338a82a27b16a9010b8dbd -npm notice integrity: sha512-ZZvTg9GVcJw8J[...]bkE0xlqQhlt4Q== -npm notice total files: 3 -npm notice - ` -} - async function prepareWorkspace(): Promise { const workspacePath = await fs.mkdtemp(`${tmpdir()}/`, { recursive: true }) return workspacePath @@ -45,41 +20,26 @@ async function createFile(path: string, content: string): Promise { await fs.writeFile(path, content) } -/* -function getManifest() { - try { - return JSON.parse(readFileSync(".packwatch.json", { encoding: "utf8" })) - } catch { - } +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"] }', + ) } -function setupMockFS({ - hasPackageJSON, - hasManifest, - manifestLimit, - manifestSize, -}) { - const fs = {} - - if (hasPackageJSON) fs["package.json"] = "{}" - - if (hasManifest) - fs[".packwatch.json"] = JSON.stringify({ - unpackedSize: "0.5 B", - limitBytes: manifestLimit, - limit: `${manifestLimit} B`, - packageSize: `${manifestSize} B`, - packageSizeBytes: manifestSize, - }) - mockFS(fs) +async function createManifest( + cwd: string, + configuration: Report, +): Promise { + const path = resolve(join(cwd, '.packwatch.json')) + await createFile(path, JSON.stringify(configuration)) } -*/ describe('Packwatch', () => { let mockLogger let workspacePath beforeEach(() => { - //mockFS({}) - //mockLogger = jest.spyOn(global.console, 'log').mockImplementation() + mockLogger = jest.spyOn(global.console, 'log').mockImplementation() }) afterEach(async () => { @@ -91,34 +51,22 @@ describe('Packwatch', () => { } }) - /* - afterAll(mockFS.restore) - */ - - it.skip('warns the user and errors if run away from package.json', async () => { + it('warns the user and errors if run away from package.json', async () => { workspacePath = await prepareWorkspace() runPackwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) - expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot( - '"🤔 There is no package.json file here. Are you in the root directory of your project?"', + 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', () => { - /* - beforeEach(() => { - setupMockFS({ hasPackageJSON: true }) - }) - */ - it('generates the initial manifest properly', async () => { workspacePath = await prepareWorkspace() - - await createFile( - resolve(join(workspacePath, 'package.json')), - '{ "name": "wow", "version": "0.0.0" }', - ) + await createPackageJson(workspacePath) runPackwatch({ cwd: workspacePath }) @@ -127,185 +75,154 @@ describe('Packwatch', () => { { encoding: 'utf8' }, ) - expect(generatedManifest).toMatchInlineSnapshot( - '"{\\"limit\\":\\"138 B\\",\\"packageSize\\":\\"138 B\\",\\"unpackedSize\\":\\"37 B\\"}"', + expect(generatedManifest).toEqual( + '{"limit":"160 B","packageSize":"160 B","unpackedSize":"68 B"}', ) }) - /*it.each(["1 B", "1.1 B", "1 kB", "1.1 kB", "1 mB", "1.1 mB"])( - "generates the initial manifest properly (size = %s)", - (mockSize) => { - jest.spyOn(childProcess, "spawnSync").mockReturnValue({ - stderr: getPackOutput({ - packageSize: mockSize, - unpackedSize: mockSize, - }), - }) - const returnCode = runPackwatch({}) - const manifest = getManifest() - expect(returnCode).toEqual(1) - expect(manifest).toEqual({ - limit: mockSize, - packageSize: mockSize, - unpackedSize: mockSize, - }) - } - )*/ + it('outputs expected messaging', async () => { + workspacePath = await prepareWorkspace() + await createPackageJson(workspacePath) - it.skip('outputs expected messaging', () => { - jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ - stderr: getPackOutput({ - packageSize: '1 B', - unpackedSize: '2 B', - }), - }) - - runPackwatch({}) + runPackwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(2) - expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot(` - "📝 No Manifest to compare against! Current package stats written to .packwatch.json! - Package size (1 B) adopted as new limit." - `) - expect(mockLogger.mock.calls[1][0]).toMatchInlineSnapshot( - '"❗ 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!"', + expect(mockLogger.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.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.skip('outputs expected messaging when not updating the manifest', () => { - jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ - stderr: getPackOutput({ - packageSize: '1 B', - unpackedSize: '2 B', - }), - }) + it('outputs expected messaging when not updating the manifest', async () => { + workspacePath = await prepareWorkspace() - runPackwatch({ isUpdatingManifest: true }) + await createPackageJson(workspacePath) + + runPackwatch({ cwd: workspacePath, isUpdatingManifest: true }) expect(mockLogger.mock.calls).toHaveLength(1) - expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot(` - "📝 No Manifest to compare against! Current package stats written to .packwatch.json! - Package size (1 B) adopted as new limit." - `) + expect(mockLogger.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.skip('with manifest', () => { - it('messages when the size is equal to the limit', () => { - setupMockFS({ - hasPackageJSON: true, - hasManifest: true, - manifestLimit: 1, - manifestSize: 1, + describe('with manifest', () => { + it('messages when the size is equal to the limit', async () => { + workspacePath = await prepareWorkspace() + + await createPackageJson(workspacePath) + await createManifest(workspacePath, { + limit: '160B', + packageSize: '160B', + unpackedSize: '150B', }) - jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ - stderr: getPackOutput({ - packageSize: '1 B', - unpackedSize: '2 B', - }), - }) - runPackwatch({}) + runPackwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) - expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot( - '"📦 Nothing to report! Your package is the same size as the latest manifest reports! (Limit: 1 B)"', + 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)', () => { - setupMockFS({ - hasPackageJSON: true, - hasManifest: true, - manifestLimit: 5, - manifestSize: 1, + it('messages when the size is lower than the limit (no growth)', async () => { + workspacePath = await prepareWorkspace() + + await createPackageJson(workspacePath) + await createManifest(workspacePath, { + limit: '170B', + packageSize: '160B', + unpackedSize: '150B', }) - jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ - stderr: getPackOutput({ - packageSize: '1 B', - unpackedSize: '2 B', - }), - }) - runPackwatch({}) + + runPackwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) - expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot( - '"📦 Nothing to report! Your package is the same size as the latest manifest reports! (Limit: 5 B)"', + 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)', () => { - setupMockFS({ - hasPackageJSON: true, - hasManifest: true, - manifestLimit: 5, - manifestSize: 2, + it('messages when the size is lower than the limit (growth)', async () => { + workspacePath = await prepareWorkspace() + + await createPackageJson(workspacePath) + await createManifest(workspacePath, { + limit: '180B', + packageSize: '150B', + unpackedSize: '140B', }) - jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ - stderr: getPackOutput({ - packageSize: '3 B', - unpackedSize: '2 B', - }), - }) - runPackwatch({}) + + runPackwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) - expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot( - '"📦 👀 Your package grew! 3 B > 2 B (Limit: 5 B)"', + 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)', () => { - setupMockFS({ - hasPackageJSON: true, - hasManifest: true, - manifestLimit: 5, - manifestSize: 2, + it('messages when the size is lower than the limit (shrinkage)', async () => { + workspacePath = await prepareWorkspace() + + await createPackageJson(workspacePath) + await createManifest(workspacePath, { + limit: '180B', + packageSize: '170B', + unpackedSize: '140B', }) - jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ - stderr: getPackOutput({ - packageSize: '1 B', - unpackedSize: '2 B', - }), - }) - runPackwatch({}) + + runPackwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) - expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot( - '"📦 💯 Your package shrank! 1 B < 2 B (Limit: 5 B)"', + 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', () => { - setupMockFS({ - hasPackageJSON: true, - hasManifest: true, - manifestLimit: 0.5, - manifestSize: 0.5, + it('messages when the size exceeds the limit', async () => { + workspacePath = await prepareWorkspace() + + await createPackageJson(workspacePath) + await createManifest(workspacePath, { + limit: '10B', + packageSize: '170B', + unpackedSize: '140B', }) - jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ - stderr: getPackOutput({ - packageSize: '1 B', - unpackedSize: '2 B', - }), - }) - runPackwatch({}) + + runPackwatch({ cwd: workspacePath }) expect(mockLogger.mock.calls).toHaveLength(1) - expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot(` - "🔥🔥📦🔥🔥 Your package exceeds the limit set in .packwatch.json! 1 B > 0.5 B - Either update the limit by using the --update-manifest flag or trim down your packed files!" - `) + expect(mockLogger.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', () => { - setupMockFS({ - hasPackageJSON: true, - hasManifest: true, - manifestLimit: 0.5, - manifestSize: 0.5, + it('messages when updating the manifest', async () => { + workspacePath = await prepareWorkspace() + + await createPackageJson(workspacePath) + await createManifest(workspacePath, { + limit: '10B', + packageSize: '170B', + unpackedSize: '140B', }) - jest.spyOn(childProcess, 'spawnSync').mockReturnValue({ - stderr: getPackOutput({ - packageSize: '1 B', - unpackedSize: '2 B', - }), - }) - runPackwatch({ isUpdatingManifest: true }) + + runPackwatch({ cwd: workspacePath, isUpdatingManifest: true }) expect(mockLogger.mock.calls).toHaveLength(1) - expect(mockLogger.mock.calls[0][0]).toMatchInlineSnapshot( - '"📝 Updated the manifest! Package size: 1 B, Limit: 1 B"', + expect(mockLogger.mock.calls[0][0]).toEqual( + expect.stringMatching( + /Updated the manifest! Package size: \d+ B, Limit: \d+ B/, + ), ) }) }) diff --git a/yarn.lock b/yarn.lock index dbb0d9b..8bac12f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7501,13 +7501,6 @@ __metadata: languageName: node linkType: hard -"mock-fs@npm:^4.11.0": - version: 4.13.0 - resolution: "mock-fs@npm:4.13.0" - checksum: cfb2134de7dfa53ebbbbacdd1b4fc834cce99ee9c231e8e129d9d32b669d15936217a7cdef8325946dd4e398ecfc401aa14c79030b8421cdf6ebd904786d8578 - languageName: node - linkType: hard - "modify-values@npm:^1.0.0": version: 1.0.1 resolution: "modify-values@npm:1.0.1" @@ -8423,7 +8416,6 @@ __metadata: eslint-plugin-jest: ^24.1.5 eslint-plugin-prettier: ^3.1.2 jest: ^26.0.1 - mock-fs: ^4.11.0 prettier: ^2.0.5 rimraf: ^3.0.2 semantic-release: ^17.0.4 -- 2.45.2 From a2e3caa573a123d12182346e3ba3419b469a6f05 Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Thu, 18 Feb 2021 23:16:56 -0500 Subject: [PATCH 4/7] ci: bump node --- .github/workflows/nodejs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 4c34e85..82e90eb 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: - node-version: [10.x, 12.x] + node-version: [12.x, 14.x] steps: - uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [10.x, 12.x] + node-version: [12.x, 14.x] steps: - uses: actions/checkout@v2 @@ -59,7 +59,7 @@ jobs: - name: Node setup uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 14 - name: Prepare run: yarn && yarn build - name: Release -- 2.45.2 From 33bfd474b6a6d1e6b1098e0faa9e4d53ef6dfa6e Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Thu, 18 Feb 2021 23:17:54 -0500 Subject: [PATCH 5/7] build: bump node --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index 48082f7..8351c19 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -12 +14 -- 2.45.2 From 696d91a5f94ad1c3e0cfa3d106664bdb6699fbea Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Thu, 18 Feb 2021 23:28:12 -0500 Subject: [PATCH 6/7] refactor: extract utils, hoist cwd up --- src/cli.ts | 3 +- src/index.ts | 89 ++++------------------------------------------------ src/utils.ts | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 84 deletions(-) create mode 100644 src/utils.ts diff --git a/src/cli.ts b/src/cli.ts index 6742f26..a13de2f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -3,5 +3,6 @@ import runPackwatch from '.' const isUpdatingManifest = process.argv.includes('--update-manifest') -const processExit = runPackwatch({ isUpdatingManifest }) +const cwd = process.cwd() +const processExit = runPackwatch({ cwd, isUpdatingManifest }) process.exit(processExit) diff --git a/src/index.ts b/src/index.ts index dc24926..faced80 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,87 +1,14 @@ -import { spawnSync } from 'child_process' -import { existsSync, readFileSync, writeFileSync } from 'fs' +import { existsSync } from 'fs' import { join, resolve } from 'path' -import { Report } from './index.d' - -const PACKAGE_SIZE_PATT = /package size:\s*([0-9]+\.?[0-9]*\s+[A-Za-z]{1,2})/ -const UNPACKED_SIZE_PATT = /unpacked size:\s*([0-9]+\.?[0-9]*\s+[A-Za-z]{1,2})/ -const SIZE_SUFFIX_PATT = /([A-Za-z]+)/ -const SIZE_MAGNITUDE_PATT = /([0-9]+\.?[0-9]*)/ +import { + createOrUpdateManifest, + getCurrentPackageStats, + getPreviousPackageStats, +} from './utils' const MANIFEST_FILENAME = '.packwatch.json' -function convertSizeToBytes(sizeString: string): number { - const sizeSuffix = SIZE_SUFFIX_PATT.exec(sizeString)[1] - const sizeMagnitude = SIZE_MAGNITUDE_PATT.exec(sizeString)[1] - - let multiplier = 1 - - if (sizeSuffix === 'kB') multiplier = 1000 - else if (sizeSuffix === 'mB') { - multiplier = 1000000 - } - - return multiplier * parseFloat(sizeMagnitude) -} - -function getCurrentPackageStats(cwd: string): Report { - const { stderr } = spawnSync('npm', ['pack', '--dry-run'], { - encoding: 'utf-8', - cwd, - }) - const stderrString = String(stderr) - const packageSize = PACKAGE_SIZE_PATT.exec(stderrString)[1] - const unpackedSize = UNPACKED_SIZE_PATT.exec(stderrString)[1] - - return { - packageSize, - unpackedSize, - packageSizeBytes: convertSizeToBytes(packageSize), - unpackedSizeBytes: convertSizeToBytes(unpackedSize), - } -} - -function getPreviousPackageStats(cwd: string): Report | null { - const manifestPath = resolve(join(cwd, MANIFEST_FILENAME)) - try { - const currentManifest = readFileSync(manifestPath, { - encoding: 'utf-8', - }) - const parsedManifest = JSON.parse(currentManifest) - return { - ...parsedManifest, - packageSizeBytes: convertSizeToBytes(parsedManifest.packageSize), - unpackedSizeBytes: convertSizeToBytes(parsedManifest.unpackedSize), - limitBytes: convertSizeToBytes(parsedManifest.limit), - } - } catch { - /* No manifest */ - } -} - -function createOrUpdateManifest({ - previous, - current, - manifestPath, - updateLimit = false, -}: { - previous?: Report - current: Report - updateLimit?: boolean -}): void { - const { limit } = previous || {} - const { packageSize, unpackedSize } = current - - const newManifest = { - limit: updateLimit ? packageSize : limit || packageSize, - packageSize: packageSize, - unpackedSize: unpackedSize, - } - - writeFileSync(manifestPath, JSON.stringify(newManifest)) -} - export default function run({ cwd, isUpdatingManifest, @@ -89,10 +16,6 @@ export default function run({ cwd?: string isUpdatingManifest?: boolean }): number { - if (!cwd) { - cwd = process.cwd() - } - const packageJsonPath = resolve(join(cwd, 'package.json')) const manifestPath = resolve(join(cwd, MANIFEST_FILENAME)) diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..9e254fd --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,83 @@ +import { spawnSync } from 'child_process' +import { readFileSync, writeFileSync } from 'fs' +import { join, resolve } from 'path' + +import { Report } from './index.d' + +const PACKAGE_SIZE_PATT = /package size:\s*([0-9]+\.?[0-9]*\s+[A-Za-z]{1,2})/ +const UNPACKED_SIZE_PATT = /unpacked size:\s*([0-9]+\.?[0-9]*\s+[A-Za-z]{1,2})/ +const SIZE_SUFFIX_PATT = /([A-Za-z]+)/ +const SIZE_MAGNITUDE_PATT = /([0-9]+\.?[0-9]*)/ + +const MANIFEST_FILENAME = '.packwatch.json' + +export function convertSizeToBytes(sizeString: string): number { + const sizeSuffix = SIZE_SUFFIX_PATT.exec(sizeString)[1] + const sizeMagnitude = SIZE_MAGNITUDE_PATT.exec(sizeString)[1] + + let multiplier = 1 + + if (sizeSuffix === 'kB') multiplier = 1000 + else if (sizeSuffix === 'mB') { + multiplier = 1000000 + } + + return multiplier * parseFloat(sizeMagnitude) +} + +export function getCurrentPackageStats(cwd: string): Report { + const { stderr } = spawnSync('npm', ['pack', '--dry-run'], { + encoding: 'utf-8', + cwd, + }) + const stderrString = String(stderr) + const packageSize = PACKAGE_SIZE_PATT.exec(stderrString)[1] + const unpackedSize = UNPACKED_SIZE_PATT.exec(stderrString)[1] + + return { + packageSize, + unpackedSize, + packageSizeBytes: convertSizeToBytes(packageSize), + unpackedSizeBytes: convertSizeToBytes(unpackedSize), + } +} + +export function getPreviousPackageStats(cwd: string): Report | null { + const manifestPath = resolve(join(cwd, MANIFEST_FILENAME)) + try { + const currentManifest = readFileSync(manifestPath, { + encoding: 'utf-8', + }) + const parsedManifest = JSON.parse(currentManifest) + return { + ...parsedManifest, + packageSizeBytes: convertSizeToBytes(parsedManifest.packageSize), + unpackedSizeBytes: convertSizeToBytes(parsedManifest.unpackedSize), + limitBytes: convertSizeToBytes(parsedManifest.limit), + } + } catch { + /* No manifest */ + } +} + +export function createOrUpdateManifest({ + previous, + current, + manifestPath, + updateLimit = false, +}: { + previous?: Report + current: Report + updateLimit?: boolean +}): void { + const { limit } = previous || {} + const { packageSize, unpackedSize } = current + + const newManifest = { + limit: updateLimit ? packageSize : limit || packageSize, + packageSize: packageSize, + unpackedSize: unpackedSize, + } + + writeFileSync(manifestPath, JSON.stringify(newManifest)) +} -- 2.45.2 From f18844b5be0c8b9c2d38959630b779d930a9f47e Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Thu, 18 Feb 2021 23:28:19 -0500 Subject: [PATCH 7/7] test: cov utils --- src/utils.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/utils.test.ts diff --git a/src/utils.test.ts b/src/utils.test.ts new file mode 100644 index 0000000..5a4ea33 --- /dev/null +++ b/src/utils.test.ts @@ -0,0 +1,15 @@ +import { convertSizeToBytes } from './utils' + +describe('utils', () => { + it.each` + initialSize | expectedSize + ${'1 B'} | ${1} + ${'1 kB'} | ${1000} + ${'1 mB'} | ${1000000} + `( + 'converts sizes properly ($initialSize -> $expectedSize)', + ({ initialSize, expectedSize }) => { + expect(convertSizeToBytes(initialSize)).toEqual(expectedSize) + }, + ) +}) -- 2.45.2