Skip to content

Commit 23997e5

Browse files
authored
feat(build): publint publish gate, types on by default, clear entry errors (#52)
- bsh build runs publint (strict) against dist/ and fails on errors - dts generation now defaults on; --no-dts opts out - empty entry globs fail with an actionable message - publint violations render full messages, not bare codes
1 parent c276677 commit 23997e5

5 files changed

Lines changed: 55 additions & 15 deletions

File tree

.changeset/build-publish-gate.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@bomb.sh/tools': minor
3+
---
4+
5+
Runs `publint` as a publish gate in `bsh build` and generates types by default
6+
7+
- 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.
8+
- 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.
9+
- 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`.
10+
- `bsh publint` output now renders full messages (e.g. which `exports` target is broken) instead of bare rule codes.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ If you'd like to use this package for your own projects, please consider forking
66

77
- `bsh init` command for scaffolding new projects, which clones [our `template` repo](https://github.com/bombshell-dev/template)
88
- `bsh dev` command, using `node --experimental-transform-types --watch-path=./src/`
9-
- `bsh build` command, using [`tsdown`](https://tsdown.dev/) (ESM, unbundled)
9+
- `bsh build` command, using [`tsdown`](https://tsdown.dev/) (ESM, unbundled, types by default) with [`publint`](https://publint.dev/) as a publish gate
1010
- `bsh test` command, using [`vitest`](https://vitest.dev/)
1111
- `bsh format` command, using [`oxfmt`](https://oxc.rs/docs/guide/usage/formatter)
1212
- `bsh lint` command, using [`oxlint`](https://oxc.rs/docs/guide/usage/linter), [`knip`](https://knip.dev), [`tsgo`](https://npmx.dev/@typescript/native-preview)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"scripts": {
5353
"bsh": "node --experimental-strip-types --no-warnings ./src/bin.ts",
5454
"dev": "pnpm run bsh dev",
55-
"build": "pnpm run bsh build --dts",
55+
"build": "pnpm run bsh build",
5656
"format": "pnpm run bsh format",
5757
"init": "pnpm run bsh init",
5858
"lint": "pnpm run bsh lint",

src/commands/build.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { parse } from '@bomb.sh/args';
22
import { build as tsdown } from 'tsdown';
33
import type { CommandContext } from '../context.ts';
4+
import { printViolations } from './lint.ts';
5+
import { runPublint } from './publint.ts';
46

57
export async function build(ctx: CommandContext) {
68
const args = parse(ctx.args, {
@@ -9,14 +11,35 @@ export async function build(ctx: CommandContext) {
911

1012
const entry = args._.length > 0 ? args._.map(String) : ['src/**/*.ts', '!src/**/*.test.ts'];
1113

12-
await tsdown({
13-
config: false,
14-
entry,
15-
format: 'esm',
16-
sourcemap: true,
17-
clean: true,
18-
unbundle: !args.bundle,
19-
dts: args.dts ? { tsgo: true } : false,
20-
minify: args.minify,
21-
});
14+
try {
15+
await tsdown({
16+
config: false,
17+
entry,
18+
format: 'esm',
19+
sourcemap: true,
20+
clean: true,
21+
unbundle: !args.bundle,
22+
dts: args.dts === false ? false : { tsgo: true },
23+
minify: args.minify,
24+
});
25+
} catch (error) {
26+
// tsdown throws an opaque `Error: undefined Cannot find entry` when a
27+
// glob matches nothing — translate it into an actionable message.
28+
if (error instanceof Error && /Cannot find entry/.test(error.message)) {
29+
console.error(`No entry files matched: ${entry.join(', ')}`);
30+
console.error(`Looked in: ${process.cwd()}`);
31+
console.error('Pass explicit entry files (e.g. `bsh build src/index.ts`).');
32+
process.exit(1);
33+
}
34+
throw error;
35+
}
36+
37+
// Publish gate: the freshly built dist/ must satisfy package.json
38+
const violations = await runPublint();
39+
if (violations.length > 0) {
40+
printViolations(violations);
41+
}
42+
if (violations.some((v) => v.level === 'error')) {
43+
process.exit(1);
44+
}
2245
}

src/commands/publint.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
import { publint } from 'publint';
2+
import { formatMessage } from 'publint/utils';
3+
import { readFile } from 'node:fs/promises';
24
import { printViolations } from './lint.ts';
35

4-
export async function publintCommand() {
6+
export async function runPublint() {
7+
const pkg = JSON.parse(await readFile('package.json', 'utf-8'));
58
const result = await publint({ strict: true });
6-
const violations = result.messages.map((m) => ({
9+
return result.messages.map((m) => ({
710
tool: 'publint' as const,
811
level: m.type,
912
code: m.code,
10-
message: m.code,
13+
message: formatMessage(m, pkg) ?? m.code,
1114
file: 'package.json',
1215
line: undefined,
1316
column: undefined,
1417
}));
18+
}
19+
20+
export async function publintCommand() {
21+
const violations = await runPublint();
1522

1623
printViolations(violations, { warnings: true });
1724
if (violations.some((v) => v.level === 'error')) {

0 commit comments

Comments
 (0)