Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/build-publish-gate.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
43 changes: 33 additions & 10 deletions src/commands/build.ts
Original file line number Diff line number Diff line change
@@ -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, {
Expand All @@ -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);
}
}
13 changes: 10 additions & 3 deletions src/commands/publint.ts
Original file line number Diff line number Diff line change
@@ -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);
if (violations.some((v) => v.level === 'error')) {
Expand Down
Loading