ci: manual releases #194

Merged
mcataford merged 3 commits from manual-releases into master 2021-03-19 21:09:48 +00:00
9 changed files with 94 additions and 51 deletions
Showing only changes of commit 9257933aa6 - Show all commits

View file

@ -33,7 +33,9 @@
"lint:fix": "yarn lint --fix", "lint:fix": "yarn lint --fix",
"test": "jest src", "test": "jest src",
"test:watch": "yarn test --watchAll", "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": { "devDependencies": {
"@babel/cli": "^7.8.4", "@babel/cli": "^7.8.4",

View file

@ -2,16 +2,17 @@ import { promises as fs } from 'fs'
import { tmpdir } from 'os' import { tmpdir } from 'os'
import { join, resolve } from 'path' import { join, resolve } from 'path'
import logger from '../logger'
import type { Report } from '../index.d' import type { Report } from '../index.d'
import runPackwatch from '..' import packwatch from '..'
async function prepareWorkspace(): Promise<string> { async function prepareWorkspace(): Promise<string> {
const workspacePath = await fs.mkdtemp(`${tmpdir()}/`, { recursive: true }) const workspacePath = await fs.mkdtemp(`${tmpdir()}/`)
return workspacePath return workspacePath
} }
async function cleanUpWorkspace(paths: string[]): Promise<void> { async function cleanUpWorkspace(paths: string[]): Promise<void> {
return Promise.all( await Promise.all(
paths.map(async path => fs.rmdir(path, { recursive: true })), paths.map(async path => fs.rmdir(path, { recursive: true })),
) )
} }
@ -37,9 +38,13 @@ async function createManifest(
} }
describe('Packwatch', () => { describe('Packwatch', () => {
let mockLogger let mockLogger
let mockWarn
let mockError
let workspacePath let workspacePath
beforeEach(() => { 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 () => { afterEach(async () => {
@ -53,7 +58,9 @@ describe('Packwatch', () => {
it('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() 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).toHaveLength(1)
expect(mockLogger.mock.calls[0][0]).toEqual( expect(mockLogger.mock.calls[0][0]).toEqual(
@ -68,7 +75,9 @@ describe('Packwatch', () => {
workspacePath = await prepareWorkspace() workspacePath = await prepareWorkspace()
await createPackageJson(workspacePath) await createPackageJson(workspacePath)
runPackwatch({ cwd: workspacePath }) await expect(async () =>
packwatch({ cwd: workspacePath }),
).rejects.toThrow('NO_MANIFEST_NO_UPDATE')
const generatedManifest = await fs.readFile( const generatedManifest = await fs.readFile(
resolve(join(workspacePath, '.packwatch.json')), resolve(join(workspacePath, '.packwatch.json')),
@ -84,15 +93,18 @@ describe('Packwatch', () => {
workspacePath = await prepareWorkspace() workspacePath = await prepareWorkspace()
await createPackageJson(workspacePath) await createPackageJson(workspacePath)
runPackwatch({ cwd: workspacePath }) await expect(async () =>
packwatch({ cwd: workspacePath }),
).rejects.toThrow()
expect(mockLogger.mock.calls).toHaveLength(2) expect(mockWarn.mock.calls).toHaveLength(1)
expect(mockLogger.mock.calls[0][0]).toEqual( expect(mockWarn.mock.calls[0][0]).toEqual(
expect.stringMatching( expect.stringMatching(
/No Manifest to compare against! Current package stats written to \.packwatch\.json!\nPackage size \(\d+ B\) adopted as new limit\./, /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( 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 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) await createPackageJson(workspacePath)
runPackwatch({ cwd: workspacePath, isUpdatingManifest: true }) await packwatch({ cwd: workspacePath, isUpdatingManifest: true })
expect(mockLogger.mock.calls).toHaveLength(1) expect(mockWarn.mock.calls).toHaveLength(1)
expect(mockLogger.mock.calls[0][0]).toEqual( expect(mockWarn.mock.calls[0][0]).toEqual(
expect.stringMatching( expect.stringMatching(
/No Manifest to compare against! Current package stats written to \.packwatch\.json!\nPackage size \(\d+ B\) adopted as new limit\./, /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', packageSize: '160B',
unpackedSize: '150B', unpackedSize: '150B',
}) })
runPackwatch({ cwd: workspacePath }) await packwatch({ cwd: workspacePath })
expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls).toHaveLength(1)
expect(mockLogger.mock.calls[0][0]).toEqual( expect(mockLogger.mock.calls[0][0]).toEqual(
expect.stringMatching( expect.stringMatching(
@ -144,7 +156,7 @@ describe('Packwatch', () => {
unpackedSize: '150B', unpackedSize: '150B',
}) })
runPackwatch({ cwd: workspacePath }) await packwatch({ cwd: workspacePath })
expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls).toHaveLength(1)
expect(mockLogger.mock.calls[0][0]).toEqual( expect(mockLogger.mock.calls[0][0]).toEqual(
expect.stringMatching( expect.stringMatching(
@ -162,7 +174,7 @@ describe('Packwatch', () => {
unpackedSize: '140B', unpackedSize: '140B',
}) })
runPackwatch({ cwd: workspacePath }) await packwatch({ cwd: workspacePath })
expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls).toHaveLength(1)
expect(mockLogger.mock.calls[0][0]).toEqual( expect(mockLogger.mock.calls[0][0]).toEqual(
expect.stringMatching( expect.stringMatching(
@ -180,7 +192,7 @@ describe('Packwatch', () => {
unpackedSize: '140B', unpackedSize: '140B',
}) })
runPackwatch({ cwd: workspacePath }) await packwatch({ cwd: workspacePath })
expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls).toHaveLength(1)
expect(mockLogger.mock.calls[0][0]).toEqual( expect(mockLogger.mock.calls[0][0]).toEqual(
expect.stringMatching( expect.stringMatching(
@ -198,9 +210,11 @@ describe('Packwatch', () => {
unpackedSize: '140B', unpackedSize: '140B',
}) })
runPackwatch({ cwd: workspacePath }) await expect(async () =>
expect(mockLogger.mock.calls).toHaveLength(1) packwatch({ cwd: workspacePath }),
expect(mockLogger.mock.calls[0][0]).toEqual( ).rejects.toThrow('PACKAGE_EXCEEDS_LIMIT')
expect(mockError.mock.calls).toHaveLength(1)
expect(mockError.mock.calls[0][0]).toEqual(
expect.stringMatching( 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!/, /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', unpackedSize: '140B',
}) })
runPackwatch({ cwd: workspacePath, isUpdatingManifest: true }) await packwatch({ cwd: workspacePath, isUpdatingManifest: true })
expect(mockLogger.mock.calls).toHaveLength(1) expect(mockLogger.mock.calls).toHaveLength(1)
expect(mockLogger.mock.calls[0][0]).toEqual( expect(mockLogger.mock.calls[0][0]).toEqual(
expect.stringMatching( expect.stringMatching(

View file

@ -1,4 +1,4 @@
import { convertSizeToBytes } from './utils' import { convertSizeToBytes } from '../utils'
describe('utils', () => { describe('utils', () => {
it.each` it.each`

View file

@ -1,8 +1,9 @@
#!/usr/bin/env node #!/usr/bin/env node
import runPackwatch from '.' import packwatch from '.'
const isUpdatingManifest = process.argv.includes('--update-manifest') const isUpdatingManifest = process.argv.includes('--update-manifest')
const cwd = process.cwd() const cwd = process.cwd()
const processExit = runPackwatch({ cwd, isUpdatingManifest }) packwatch({ cwd, isUpdatingManifest })
process.exit(processExit) .catch(() => process.exit(1))
.then(() => process.exit(0))

5
src/index.d.ts vendored
View file

@ -1,3 +1,8 @@
export type PackwatchArguments = {
cwd?: string
isUpdatingManifest?: boolean
}
export type Report = { export type Report = {
packageSize: string packageSize: string
unpackedSize?: string unpackedSize?: string

View file

@ -6,25 +6,19 @@ import {
getCurrentPackageStats, getCurrentPackageStats,
getPreviousPackageStats, getPreviousPackageStats,
} from './utils' } from './utils'
import type { PackwatchArguments } from './index.d'
import { assertInPackageRoot } from './invariants'
import logger from './logger'
const MANIFEST_FILENAME = '.packwatch.json' const MANIFEST_FILENAME = '.packwatch.json'
export default function run({ export default async function packwatch({
cwd, cwd,
isUpdatingManifest, isUpdatingManifest,
}: { }: PackwatchArguments): Promise<void> {
cwd?: string
isUpdatingManifest?: boolean
}): number {
const packageJsonPath = resolve(join(cwd, 'package.json'))
const manifestPath = resolve(join(cwd, MANIFEST_FILENAME)) const manifestPath = resolve(join(cwd, MANIFEST_FILENAME))
if (!existsSync(packageJsonPath)) { assertInPackageRoot(cwd)
console.log(
'🤔 There is no package.json file here. Are you in the root directory of your project?',
)
return 1
}
const currentStats = getCurrentPackageStats(cwd) const currentStats = getCurrentPackageStats(cwd)
@ -35,18 +29,17 @@ export default function run({
if (!existsSync(manifestPath)) { if (!existsSync(manifestPath)) {
createOrUpdateManifest({ manifestPath, current: currentStats }) 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.`, `📝 No Manifest to compare against! Current package stats written to ${MANIFEST_FILENAME}!\nPackage size (${currentStats.packageSize}) adopted as new limit.`,
) )
if (!isUpdatingManifest) { 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!', '❗ 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 return
// don't "accidentally" pass CI builds if the manifest didn't exist
return isUpdatingManifest ? 0 : 1
} }
const previousStats = getPreviousPackageStats(cwd) const previousStats = getPreviousPackageStats(cwd)
@ -70,10 +63,10 @@ export default function run({
updateLimit: true, updateLimit: true,
manifestPath, manifestPath,
}) })
console.log( logger.log(
`📝 Updated the manifest! Package size: ${packageSize}, Limit: ${packageSize}`, `📝 Updated the manifest! Package size: ${packageSize}, Limit: ${packageSize}`,
) )
return 0 return
} }
/* /*
@ -82,10 +75,10 @@ export default function run({
*/ */
if (hasExceededLimit) { 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!`, `🔥🔥📦🔥🔥 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) { if (packageSizeBytes > previousSizeBytes) {
console.log( logger.log(
`📦 👀 Your package grew! ${packageSize} > ${previousSize} (Limit: ${limit})`, `📦 👀 Your package grew! ${packageSize} > ${previousSize} (Limit: ${limit})`,
) )
} else if (packageSizeBytes < previousSizeBytes) { } else if (packageSizeBytes < previousSizeBytes) {
console.log( logger.log(
`📦 💯 Your package shrank! ${packageSize} < ${previousSize} (Limit: ${limit})`, `📦 💯 Your package shrank! ${packageSize} < ${previousSize} (Limit: ${limit})`,
) )
} else { } else {
console.log( logger.log(
`📦 Nothing to report! Your package is the same size as the latest manifest reports! (Limit: ${limit})`, `📦 Nothing to report! Your package is the same size as the latest manifest reports! (Limit: ${limit})`,
) )
} }
return 0 return
} }

16
src/invariants.ts Normal file
View file

@ -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')
}
}

11
src/logger.ts Normal file
View file

@ -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)
},
}

View file

@ -68,6 +68,7 @@ export function createOrUpdateManifest({
}: { }: {
previous?: Report previous?: Report
current: Report current: Report
manifestPath: string
updateLimit?: boolean updateLimit?: boolean
}): void { }): void {
const { limit } = previous || {} const { limit } = previous || {}