Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: exit with code (1) on build warnings #98

Merged
merged 19 commits into from Aug 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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'