Skip to content

Commit

Permalink
feat!: exit with code (1) on build warnings (#98)
Browse files Browse the repository at this point in the history
Co-authored-by: Pooya Parsa <pooya@pi0.io>
  • Loading branch information
danielroe and pi0 committed Aug 10, 2022
1 parent b8802af commit ffc0d7c
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 45 deletions.
13 changes: 9 additions & 4 deletions src/auto.ts
Expand Up @@ -2,10 +2,10 @@ import { normalize, join } from 'pathe'
import consola from 'consola'
import chalk from 'chalk'
import type { PackageJson } from 'pkg-types'
import { extractExportFilenames, listRecursively } from './utils'
import { extractExportFilenames, listRecursively, warn } from './utils'
import { BuildEntry, definePreset, MkdistBuildEntry } from './types'

type InferEntriesResult = { entries: BuildEntry[], cjs?: boolean, dts?: boolean }
type InferEntriesResult = { entries: BuildEntry[], cjs?: boolean, dts?: boolean, warnings: string[] }

export const autoPreset = definePreset(() => {
return {
Expand All @@ -17,6 +17,9 @@ export const autoPreset = definePreset(() => {
}
const sourceFiles = listRecursively(join(ctx.options.rootDir, 'src'))
const res = inferEntries(ctx.pkg, sourceFiles)
for (const message of res.warnings) {
warn(ctx, message)
}
ctx.options.entries.push(...res.entries)
if (res.cjs) {
ctx.options.rollup.emitCJS = true
Expand All @@ -41,6 +44,8 @@ export const autoPreset = definePreset(() => {
* - if an array of source files, these will be used directly instead of accessing fs.
*/
export function inferEntries (pkg: PackageJson, sourceFiles: string[]): InferEntriesResult {
const warnings = []

// Come up with a list of all output files & their formats
const outputs = extractExportFilenames(pkg.exports)

Expand Down Expand Up @@ -93,7 +98,7 @@ export function inferEntries (pkg: PackageJson, sourceFiles: string[]): InferEnt
}, undefined)

if (!input) {
consola.warn(`could not infer entrypoint for \`${output.file}\``)
warnings.push(`Could not find entrypoint for ${output.file}`)
continue
}

Expand All @@ -113,7 +118,7 @@ export function inferEntries (pkg: PackageJson, sourceFiles: string[]): InferEnt
}
}

return { entries, cjs, dts }
return { entries, cjs, dts, warnings }
}

export const getEntrypointPaths = (path: string) => {
Expand Down
12 changes: 11 additions & 1 deletion src/build.ts
Expand Up @@ -42,6 +42,7 @@ export async function build (rootDir: string, stub: boolean, inputConfig: BuildC
peerDependencies: [],
alias: {},
replace: {},
failOnWarn: true,
rollup: {
emitCJS: false,
cjsBridge: false,
Expand Down Expand Up @@ -75,6 +76,7 @@ export async function build (rootDir: string, stub: boolean, inputConfig: BuildC
// Build context
const ctx: BuildContext = {
options,
warnings: new Set(),
pkg,
buildEntries: [],
usedImports: new Set(),
Expand Down Expand Up @@ -182,10 +184,18 @@ export async function build (rootDir: string, stub: boolean, inputConfig: BuildC

// Validate
validateDependencies(ctx)
validatePackage(pkg, rootDir)
validatePackage(pkg, rootDir, ctx)

// Call build:done
await ctx.hooks.callHook('build:done', ctx)

consola.log('')

if (ctx.warnings.size) {
consola.warn('Build is done with some warnings:\n\n' + Array.from(ctx.warnings).map(msg => '- ' + msg).join('\n'))
if (ctx.options.failOnWarn) {
consola.error('Exiting with code (1). You can change this behavior by setting `failOnWarn: false` .')
process.exit(1)
}
}
}
5 changes: 2 additions & 3 deletions src/builder/rollup.ts
Expand Up @@ -10,9 +10,8 @@ import _esbuild from 'rollup-plugin-esbuild'
import dts from 'rollup-plugin-dts'
import replace from '@rollup/plugin-replace'
import { relative, resolve, dirname } from 'pathe'
import consola from 'consola'
import { resolvePath } from 'mlly'
import { getpkg, tryResolve } from '../utils'
import { getpkg, tryResolve, warn } from '../utils'
import type { BuildContext } from '../types'
import { JSONPlugin } from './plugins/json'
import { rawPlugin } from './plugins/raw'
Expand Down Expand Up @@ -140,7 +139,7 @@ export function getRollupOptions (ctx: BuildContext): RollupOptions {
return false
}
if (!isExplicitExternal) {
consola.warn(`Inlining implicit external ${id}`)
warn(ctx, `Inlined implicit external ${id}`)
}
return isExplicitExternal
},
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Expand Up @@ -66,6 +66,7 @@ export interface BuildOptions {
devDependencies: string[]
alias: { [find: string]: string },
replace: { [find: string]: string },
failOnWarn?: boolean
rollup: RollupBuildOptions
}

Expand All @@ -74,6 +75,7 @@ export interface BuildContext {
pkg: PackageJson,
buildEntries: { path: string, bytes?: number, exports?: string[], chunks?: string[] }[]
usedImports: Set<string>
warnings: Set<string>
hooks: Hookable<BuildHooks>
}

Expand Down
9 changes: 8 additions & 1 deletion src/utils.ts
Expand Up @@ -5,14 +5,21 @@ import { dirname, resolve } from 'pathe'
import mkdirp from 'mkdirp'
import _rimraf from 'rimraf'
import jiti from 'jiti'
import consola from 'consola'
import type { PackageJson } from 'pkg-types'
import { autoPreset } from './auto'
import type { BuildPreset, BuildConfig } from './types'
import type { BuildPreset, BuildConfig, BuildContext } from './types'

export async function ensuredir (path: string) {
await mkdirp(dirname(path))
}

export function warn (ctx: BuildContext, message: string) {
if (ctx.warnings.has(message)) { return }
consola.debug('[unbuild] [warn]', message)
ctx.warnings.add(message)
}

export async function symlink (from: string, to: string, force: boolean = true) {
await ensuredir(to)
if (force) {
Expand Down
17 changes: 8 additions & 9 deletions src/validate.ts
@@ -1,15 +1,14 @@
import { existsSync } from 'fs'
import chalk from 'chalk'
import consola from 'consola'
import { resolve } from 'pathe'
import { PackageJson } from 'pkg-types'
import { extractExportFilenames, getpkg } from './utils'
import { extractExportFilenames, getpkg, warn } from './utils'
import { BuildContext } from './types'

export function validateDependencies (ctx: BuildContext) {
const usedDependencies = new Set<string>()
const unusedDependencies = new Set<string>(Object.keys(ctx.pkg.dependencies || {}))
const implicitDependnecies = new Set<string>()
const implicitDependencies = new Set<string>()
for (const id of ctx.usedImports) {
unusedDependencies.delete(id)
usedDependencies.add(id)
Expand All @@ -26,18 +25,18 @@ export function validateDependencies (ctx: BuildContext) {
!ctx.options.dependencies.includes(getpkg(id)) &&
!ctx.options.peerDependencies.includes(getpkg(id))
) {
implicitDependnecies.add(id)
implicitDependencies.add(id)
}
}
if (unusedDependencies.size) {
consola.warn('Potential unused dependencies found:', Array.from(unusedDependencies).map(id => chalk.cyan(id)).join(', '))
warn(ctx, 'Potential unused dependencies found: ' + Array.from(unusedDependencies).map(id => chalk.cyan(id)).join(', '))
}
if (implicitDependnecies.size && !ctx.options.rollup.inlineDependencies) {
consola.warn('Potential implicit dependencies found:', Array.from(implicitDependnecies).map(id => chalk.cyan(id)).join(', '))
if (implicitDependencies.size && !ctx.options.rollup.inlineDependencies) {
warn(ctx, 'Potential implicit dependencies found: ' + Array.from(implicitDependencies).map(id => chalk.cyan(id)).join(', '))
}
}

export function validatePackage (pkg: PackageJson, rootDir: string) {
export function validatePackage (pkg: PackageJson, rootDir: string, ctx: BuildContext) {
if (!pkg) { return }

const filenames = new Set([
Expand All @@ -57,6 +56,6 @@ export function validatePackage (pkg: PackageJson, rootDir: string) {
}
}
if (missingOutputs.length) {
consola.warn(`Potential missing package.json files: ${missingOutputs.map(o => chalk.cyan(o)).join(', ')}`)
warn(ctx, `Potential missing package.json files: ${missingOutputs.map(o => chalk.cyan(o)).join(', ')}`)
}
}
53 changes: 35 additions & 18 deletions test/auto.test.ts
Expand Up @@ -7,25 +7,29 @@ describe('inferEntries', () => {
expect(result).to.deep.equal({
cjs: true,
dts: false,
entries: [{ input: 'src/test' }]
entries: [{ input: 'src/test' }],
warnings: []
})
})

it('handles binary outputs', () => {
expect(inferEntries({ bin: 'dist/cli.cjs' }, ['src/', 'src/cli.ts'])).to.deep.equal({
cjs: true,
dts: false,
entries: [{ input: 'src/cli' }]
entries: [{ input: 'src/cli' }],
warnings: []
})
expect(inferEntries({ bin: { nuxt: 'dist/cli.js' } }, ['src/', 'src/cli.ts'])).to.deep.equal({
cjs: true,
dts: false,
entries: [{ input: 'src/cli' }]
entries: [{ input: 'src/cli' }],
warnings: []
})
expect(inferEntries({ bin: { nuxt: 'dist/cli.js' }, type: 'module' }, ['src/', 'src/cli.ts'])).to.deep.equal({
cjs: false,
dts: false,
entries: [{ input: 'src/cli' }]
entries: [{ input: 'src/cli' }],
warnings: []
})
})

Expand All @@ -34,7 +38,8 @@ describe('inferEntries', () => {
expect(result).to.deep.equal({
cjs: false,
dts: false,
entries: [{ input: 'src/test' }]
entries: [{ input: 'src/test' }],
warnings: []
})
})

Expand All @@ -43,28 +48,31 @@ describe('inferEntries', () => {
expect(result).to.deep.equal({
cjs: false,
dts: false,
entries: [{ input: 'src/other/runtime/index' }]
entries: [{ input: 'src/other/runtime/index' }],
warnings: []
})
})

it('handles declarations from `types`', () => {
expect(inferEntries({ main: 'dist/test.cjs', types: 'custom/handwritten.d.ts' }, ['src/', 'src/test.ts'])).to.deep.equal({
cjs: true,
dts: false,
entries: [{ input: 'src/test' }
entries: [{ input: 'src/test' }],
warnings: [
'Could not find entrypoint for custom/handwritten.d.ts'
]
})
expect(inferEntries({ main: 'dist/test.cjs', module: 'dist/test.mjs', types: 'dist/test.d.ts' }, ['src/', 'src/test.ts'])).to.deep.equal({
cjs: true,
dts: true,
entries: [{ input: 'src/test' }
]
entries: [{ input: 'src/test' }],
warnings: []
})
expect(inferEntries({ main: 'dist/test.cjs', module: 'dist/test.mjs', typings: 'dist/test.d.ts' }, ['src/', 'src/test.ts'])).to.deep.equal({
cjs: true,
dts: true,
entries: [{ input: 'src/test' }
]
entries: [{ input: 'src/test' }],
warnings: []
})
})

Expand All @@ -73,23 +81,28 @@ describe('inferEntries', () => {
expect(result).to.deep.equal({
cjs: true,
dts: true,
entries: [{ input: 'src/test' }]
entries: [{ input: 'src/test' }],
warnings: []
})
})

it('gracefully handles unknown entries', () => {
expect(inferEntries({ exports: 'dist/test.js' }, ['src/', 'src/index.ts'])).to.deep.equal({
cjs: false,
entries: [],
dts: false
dts: false,
warnings: [
'Could not find entrypoint for dist/test.js'
]
})
})

it('ignores top-level exports', () => {
expect(inferEntries({ exports: { './*': './*' } }, ['src/', 'src/', 'src/index.ts'])).to.deep.equal({
cjs: false,
entries: [],
dts: false
dts: false,
warnings: []
})
})

Expand All @@ -100,25 +113,29 @@ describe('inferEntries', () => {
entries: [
{ input: 'src/index' },
{ input: 'src/test' }
]
],
warnings: []
})
})

it('recognises directory mappings', () => {
expect(inferEntries({ exports: './dist/runtime/*' }, ['src/', 'src/runtime/', 'src/runtime/test.js'])).to.deep.equal({
cjs: false,
dts: false,
entries: [{ format: 'esm', input: 'src/runtime/', outDir: './dist/runtime/' }]
entries: [{ format: 'esm', input: 'src/runtime/', outDir: './dist/runtime/' }],
warnings: []
})
expect(inferEntries({ exports: { './runtime/*': './dist/runtime/*.mjs,' } }, ['src/', 'src/runtime/'])).to.deep.equal({
cjs: false,
dts: false,
entries: [{ format: 'esm', input: 'src/runtime/', outDir: './dist/runtime/' }]
entries: [{ format: 'esm', input: 'src/runtime/', outDir: './dist/runtime/' }],
warnings: []
})
expect(inferEntries({ exports: { './runtime/*': { require: './dist/runtime/*' } } }, ['src/', 'src/runtime/'])).to.deep.equal({
cjs: true,
dts: false,
entries: [{ format: 'cjs', input: 'src/runtime/', outDir: './dist/runtime/' }]
entries: [{ format: 'cjs', input: 'src/runtime/', outDir: './dist/runtime/' }],
warnings: []
})
})
})
Expand Down
3 changes: 3 additions & 0 deletions test/fixture/src/index.ts
Expand Up @@ -13,3 +13,6 @@ import('os').then(os => console.log(os.arch()))
import('./test.html').then(console.log)

export const foo = 'bar'

// Failing test
// export * from 'defu'

0 comments on commit ffc0d7c

Please sign in to comment.