diff --git a/.changeset/build-publish-gate.md b/.changeset/build-publish-gate.md new file mode 100644 index 0000000..8db6c66 --- /dev/null +++ b/.changeset/build-publish-gate.md @@ -0,0 +1,10 @@ +--- +'@bomb.sh/tools': minor +--- + +Runs `publint` as a publish gate in `bsh build` and generates types by default + +- After a successful build, `bsh build` now runs `publint` (strict mode) against the emitted `dist/` and fails on errors — publishing mistakes like missing declaration files or broken `exports` targets are caught at build time instead of at publish. +- Type declarations (`.d.mts`) are now generated by default; pass `--no-dts` to opt out. If your `package.json` declares types but they aren't emitted, the build now fails. +- Entry globs that match nothing now fail with a clear message (`No entry files matched: …`) instead of tsdown's opaque `Error: undefined Cannot find entry`. +- `bsh publint` output now renders full messages (e.g. which `exports` target is broken) instead of bare rule codes. diff --git a/README.md b/README.md index 4096fd2..1ee4350 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ If you'd like to use this package for your own projects, please consider forking - `bsh init` command for scaffolding new projects, which clones [our `template` repo](https://github.com/bombshell-dev/template) - `bsh dev` command, using `node --experimental-transform-types --watch-path=./src/` -- `bsh build` command, using [`tsdown`](https://tsdown.dev/) (ESM, unbundled) +- `bsh build` command, using [`tsdown`](https://tsdown.dev/) (ESM, unbundled, types by default) with [`publint`](https://publint.dev/) as a publish gate - `bsh test` command, using [`vitest`](https://vitest.dev/) - `bsh format` command, using [`oxfmt`](https://oxc.rs/docs/guide/usage/formatter) - `bsh lint` command, using [`oxlint`](https://oxc.rs/docs/guide/usage/linter), [`knip`](https://knip.dev), [`tsgo`](https://npmx.dev/@typescript/native-preview) diff --git a/package.json b/package.json index 037c621..714f5c0 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "scripts": { "bsh": "node --experimental-strip-types --no-warnings ./src/bin.ts", "dev": "pnpm run bsh dev", - "build": "pnpm run bsh build --dts", + "build": "pnpm run bsh build", "format": "pnpm run bsh format", "init": "pnpm run bsh init", "lint": "pnpm run bsh lint", diff --git a/src/commands/build.ts b/src/commands/build.ts index 4902b51..fa7ade1 100644 --- a/src/commands/build.ts +++ b/src/commands/build.ts @@ -1,6 +1,8 @@ import { parse } from '@bomb.sh/args'; import { build as tsdown } from 'tsdown'; import type { CommandContext } from '../context.ts'; +import { printViolations } from './lint.ts'; +import { runPublint } from './publint.ts'; export async function build(ctx: CommandContext) { const args = parse(ctx.args, { @@ -9,14 +11,35 @@ export async function build(ctx: CommandContext) { const entry = args._.length > 0 ? args._.map(String) : ['src/**/*.ts', '!src/**/*.test.ts']; - await tsdown({ - config: false, - entry, - format: 'esm', - sourcemap: true, - clean: true, - unbundle: !args.bundle, - dts: args.dts ? { tsgo: true } : false, - minify: args.minify, - }); + try { + await tsdown({ + config: false, + entry, + format: 'esm', + sourcemap: true, + clean: true, + unbundle: !args.bundle, + dts: args.dts === false ? false : { tsgo: true }, + minify: args.minify, + }); + } catch (error) { + // tsdown throws an opaque `Error: undefined Cannot find entry` when a + // glob matches nothing — translate it into an actionable message. + if (error instanceof Error && /Cannot find entry/.test(error.message)) { + console.error(`No entry files matched: ${entry.join(', ')}`); + console.error(`Looked in: ${process.cwd()}`); + console.error('Pass explicit entry files (e.g. `bsh build src/index.ts`).'); + process.exit(1); + } + throw error; + } + + // Publish gate: the freshly built dist/ must satisfy package.json + const violations = await runPublint(); + if (violations.length > 0) { + printViolations(violations); + } + if (violations.some((v) => v.level === 'error')) { + process.exit(1); + } } diff --git a/src/commands/publint.ts b/src/commands/publint.ts index abec641..875f108 100644 --- a/src/commands/publint.ts +++ b/src/commands/publint.ts @@ -1,17 +1,24 @@ import { publint } from 'publint'; +import { formatMessage } from 'publint/utils'; +import { readFile } from 'node:fs/promises'; import { printViolations } from './lint.ts'; -export async function publintCommand() { +export async function runPublint() { + const pkg = JSON.parse(await readFile('package.json', 'utf-8')); const result = await publint({ strict: true }); - const violations = result.messages.map((m) => ({ + return result.messages.map((m) => ({ tool: 'publint' as const, level: m.type, code: m.code, - message: m.code, + message: formatMessage(m, pkg) ?? m.code, file: 'package.json', line: undefined, column: undefined, })); +} + +export async function publintCommand() { + const violations = await runPublint(); printViolations(violations, { warnings: true }); if (violations.some((v) => v.level === 'error')) {