@burdenoff/toolchain 0.0.0-stage → 2026.1107.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +500 -2
- package/bunfig/fragment.toml.tmpl +23 -0
- package/bunfig/release-age-excludes.json +168 -0
- package/dist/cli.js +3014 -0
- package/dist/knip/base.js +22 -0
- package/eslint/backend.d.ts +4 -0
- package/eslint/backend.js +487 -0
- package/eslint/browser.d.ts +5 -0
- package/eslint/browser.js +24 -0
- package/eslint/gateway.d.ts +4 -0
- package/eslint/gateway.js +53 -0
- package/eslint/index.d.ts +37 -0
- package/eslint/node.d.ts +5 -0
- package/eslint/node.js +43 -0
- package/eslint/react.d.ts +8 -0
- package/eslint/react.js +165 -0
- package/fmt/README.md +32 -0
- package/knip/base.ts +42 -0
- package/lefthook/base.yml +32 -0
- package/package.json +132 -4
- package/tsconfig/backend-library.json +23 -0
- package/tsconfig/backend.json +18 -0
- package/tsconfig/base.json +21 -0
- package/tsconfig/browser-extension.json +16 -0
- package/tsconfig/gateway.json +16 -0
- package/tsconfig/node-lib.json +15 -0
- package/tsconfig/react-app.json +17 -0
- package/tsconfig/react-lib.json +16 -0
- package/tsconfig/vscode-extension.json +15 -0
package/README.md
CHANGED
|
@@ -1,3 +1,501 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @burdenoff/toolchain
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The shared build toolchain for every Burdenoff repository: TypeScript 7-clean
|
|
4
|
+
tsconfig bases, ESLint profiles, the bunfig and lefthook managed blocks, a
|
|
5
|
+
knip base, and the `boff` CLI (`sanity`, `gate`, `coverage-gate`, `doctor`,
|
|
6
|
+
`sync`, `deps-mode`, `workflow-policy`).
|
|
7
|
+
|
|
8
|
+
It exists so that a lint-rule tweak or a sanity fix ships once, through one
|
|
9
|
+
exact-pinned devDependency, instead of through ~700 drifted copies of
|
|
10
|
+
`check-coverage.sh`, `deps-mode.ts`, `build-limits.sh`, Prettier and ESLint
|
|
11
|
+
configs (plan: `infra-specs/engineering/toolchain/00-bun-native-toolchain-migration-plan.md`,
|
|
12
|
+
sections 5, 7 and 9; ticket BOFF-10675).
|
|
13
|
+
|
|
14
|
+
- Source: `algoshred/toolchain` (private). Package: `@burdenoff/toolchain` on
|
|
15
|
+
npmjs (public, so PR jobs need no token).
|
|
16
|
+
- Bun: **1.4.4** (`.bun-version`, `packageManager: bun@1.4.4`, `engines.bun: >=1.4.4`).
|
|
17
|
+
- Versioning: CalVer `YYYY.MDD.PATCH`, published by `build-publish.yml` on every
|
|
18
|
+
push to `main`, tagged `v<version>`.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
Pin the exact version (the fleet installs with `--frozen-lockfile`, so a caret
|
|
23
|
+
range would never move anyway):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
bun add -d --exact @burdenoff/toolchain@<version>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@burdenoff/toolchain": "<version>",
|
|
33
|
+
"eslint": "^9.39.0",
|
|
34
|
+
"@types/bun": "^1.4.4"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The ESLint plugins (`typescript-eslint`, `eslint-plugin-sonarjs`,
|
|
40
|
+
`eslint-plugin-security`, `eslint-plugin-import`, `eslint-plugin-eslint-comments`,
|
|
41
|
+
`eslint-plugin-react`, `eslint-plugin-react-hooks`, `@eslint/js`, `globals`)
|
|
42
|
+
are dependencies of the toolchain. Consumers keep only `eslint` itself (and
|
|
43
|
+
`typescript` where typed linting or `.d.ts` emit runs) and remove their own
|
|
44
|
+
copies of those plugins plus `prettier`, `@trivago/prettier-plugin-sort-imports`,
|
|
45
|
+
`eslint-plugin-prettier` and `eslint-config-prettier`.
|
|
46
|
+
|
|
47
|
+
Then:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bunx boff sync --type <type> --scripts # bunfig + lefthook blocks, scripts, boff.type
|
|
51
|
+
bunx boff doctor # what still needs attention
|
|
52
|
+
bun run sanity
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Consumer contract
|
|
56
|
+
|
|
57
|
+
### Repo types
|
|
58
|
+
|
|
59
|
+
`boff` keys its defaults off `package.json` → `boff.type`:
|
|
60
|
+
|
|
61
|
+
`svc`, `gateway`, `be-sdk`, `mfe`, `fe-libs`, `app`, `website`, `docs`, `cli`,
|
|
62
|
+
`mcp`, `sdk-node`, `browser-ext`, `vscode-ext`, `plugin`, `doctor`, `lib`.
|
|
63
|
+
|
|
64
|
+
When `boff.type` is absent the type is inferred from fleet naming
|
|
65
|
+
(`wspace-*-svc`, `*-gateway`, `microfe-*`, `*-app`, `*-website`, ...), falling
|
|
66
|
+
back to `lib`. Declare it explicitly.
|
|
67
|
+
|
|
68
|
+
### tsconfig
|
|
69
|
+
|
|
70
|
+
Every base is TypeScript 7 clean: no `baseUrl` (a hard error in `bun check`),
|
|
71
|
+
an explicit `types` array (TypeScript 7 defaults it to `[]`), bundler or Node16
|
|
72
|
+
resolution, and no `composite`/`declaration` except `backend-library.json`.
|
|
73
|
+
|
|
74
|
+
| Base | For | Notes |
|
|
75
|
+
| --------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
76
|
+
| `tsconfig/base.json` | anything | strict, ES2022, bundler resolution, `types: []`, `noEmit` |
|
|
77
|
+
| `tsconfig/backend.json` | svc | `types: ["bun"]`, decorators + metadata, `noUncheckedIndexedAccess`, unused locals left to ESLint |
|
|
78
|
+
| `tsconfig/backend-library.json` | be-sdk | backend + `composite`, `declaration`, `declarationMap` for `.d.ts` emit |
|
|
79
|
+
| `tsconfig/gateway.json` | gateways | `types: ["bun"]`, unused locals/parameters as errors |
|
|
80
|
+
| `tsconfig/react-lib.json` | fe-libs, MFEs | DOM libs, `jsx: react-jsx`, `customConditions: ["types"]`, `types: ["vite/client"]` |
|
|
81
|
+
| `tsconfig/react-app.json` | app shells, websites | react-lib + `noUncheckedSideEffectImports`, `useDefineForClassFields` |
|
|
82
|
+
| `tsconfig/node-lib.json` | cli, mcp, sdk-node, plugin | `types: ["bun"]`, isolated modules |
|
|
83
|
+
| `tsconfig/browser-extension.json` | browser extensions | DOM libs, `types: ["chrome"]`, emits for ts-loader |
|
|
84
|
+
| `tsconfig/vscode-extension.json` | code extensions | CommonJS through `Node16`, `types: ["node", "vscode"]`, emits for mocha |
|
|
85
|
+
|
|
86
|
+
`baseUrl` is forbidden (`boff doctor` fails on it). Write `paths` values
|
|
87
|
+
relative to the tsconfig with a leading `./`. `types` in your tsconfig
|
|
88
|
+
**replaces** the base's array; repeat the base entries you still need (for
|
|
89
|
+
example `["vite/client", "bun"]` in an MFE whose tests run on `bun test`).
|
|
90
|
+
`extends` resolves relative paths (`include`, `outDir`, `paths`) against the
|
|
91
|
+
file that declares them, so keep those in your own tsconfig.
|
|
92
|
+
|
|
93
|
+
```jsonc
|
|
94
|
+
// svc
|
|
95
|
+
{
|
|
96
|
+
"extends": "@burdenoff/toolchain/tsconfig/backend.json",
|
|
97
|
+
"compilerOptions": {
|
|
98
|
+
// Repeat these two: Bun's transpiler reads decorator options from the nearest
|
|
99
|
+
// tsconfig only, not through `extends` (oven-sh/bun#6326).
|
|
100
|
+
"experimentalDecorators": true,
|
|
101
|
+
"emitDecoratorMetadata": true,
|
|
102
|
+
"paths": { "#/*": ["./src/*"], "#generated/*": ["./src/generated/*"] },
|
|
103
|
+
},
|
|
104
|
+
"include": ["src"],
|
|
105
|
+
"exclude": ["**/*.test.ts"],
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```jsonc
|
|
110
|
+
// mfe (bun check -b follows the reference into tsconfig.node.json)
|
|
111
|
+
{
|
|
112
|
+
"extends": "@burdenoff/toolchain/tsconfig/react-lib.json",
|
|
113
|
+
"compilerOptions": { "types": ["vite/client", "bun"], "paths": { "#src/*": ["./src/*"] } },
|
|
114
|
+
"include": ["src"],
|
|
115
|
+
"references": [{ "path": "./tsconfig.node.json" }],
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```jsonc
|
|
120
|
+
// app shell / website
|
|
121
|
+
{ "extends": "@burdenoff/toolchain/tsconfig/react-app.json", "include": ["src"] }
|
|
122
|
+
// gateway
|
|
123
|
+
{ "extends": "@burdenoff/toolchain/tsconfig/gateway.json", "include": ["src"] }
|
|
124
|
+
// cli / mcp / sdk-node / plugin
|
|
125
|
+
{ "extends": "@burdenoff/toolchain/tsconfig/node-lib.json", "include": ["src"] }
|
|
126
|
+
// browser extension
|
|
127
|
+
{
|
|
128
|
+
"extends": "@burdenoff/toolchain/tsconfig/browser-extension.json",
|
|
129
|
+
"compilerOptions": { "outDir": "dist", "types": ["chrome", "bun"], "paths": { "@/*": ["./src/*"] } },
|
|
130
|
+
"include": ["src"]
|
|
131
|
+
}
|
|
132
|
+
// code extension
|
|
133
|
+
{
|
|
134
|
+
"extends": "@burdenoff/toolchain/tsconfig/vscode-extension.json",
|
|
135
|
+
"compilerOptions": { "outDir": "out", "rootDir": "src" },
|
|
136
|
+
"include": ["src"]
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### ESLint (`eslint.config.js`)
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
// svc (be-sdk's factory, same rules and per-path overrides)
|
|
144
|
+
import backend from '@burdenoff/toolchain/eslint/backend';
|
|
145
|
+
export default backend(import.meta.dirname, { project: './tsconfig.json', ignores: ['scripts/**'] });
|
|
146
|
+
|
|
147
|
+
// gateway (typescript-eslint strictTypeChecked + stylisticTypeChecked, projectService)
|
|
148
|
+
import gateway from '@burdenoff/toolchain/eslint/gateway';
|
|
149
|
+
export default gateway(import.meta.dirname);
|
|
150
|
+
|
|
151
|
+
// mfe / fe-libs / app / website (raw-colour guard, Dialog restriction, react-hooks v7)
|
|
152
|
+
import { createEslintConfig } from '@burdenoff/toolchain/eslint/react';
|
|
153
|
+
export default createEslintConfig({ ignores: ['src/generated/**'], rawColors: 'error' });
|
|
154
|
+
|
|
155
|
+
// cli / mcp / sdk-node / plugin / code extension (no Prettier plugin)
|
|
156
|
+
import node from '@burdenoff/toolchain/eslint/node';
|
|
157
|
+
export default node({ ignores: ['out/**'] });
|
|
158
|
+
|
|
159
|
+
// browser extension (node + browser and webextension globals, chrome)
|
|
160
|
+
import browser from '@burdenoff/toolchain/eslint/browser';
|
|
161
|
+
export default browser();
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`@burdenoff/toolchain/eslint/react` also exports `rawColorRestrictedSyntax`,
|
|
165
|
+
`RAW_COLOR_PATTERN` and `PALETTE_CLASS_PATTERN` for repos with a standalone
|
|
166
|
+
config. Typings ship in `eslint/*.d.ts` (`eslint/index.d.ts` holds the shared
|
|
167
|
+
option types).
|
|
168
|
+
|
|
169
|
+
### bunfig.toml managed block
|
|
170
|
+
|
|
171
|
+
`boff sync` writes these keys between the marker lines
|
|
172
|
+
`# >>> boff managed (do not edit)` and `# <<< boff managed`:
|
|
173
|
+
|
|
174
|
+
```toml
|
|
175
|
+
[install]
|
|
176
|
+
minimumReleaseAge = "3d"
|
|
177
|
+
minimumReleaseAgeExcludes = [ ...every @burdenoff, @fluidgrids and @vibecontrols package... ]
|
|
178
|
+
|
|
179
|
+
[test]
|
|
180
|
+
coverageReporter = ["text", "lcov"]
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
TOML forbids defining a table twice, so when the repo already has `[install]`
|
|
184
|
+
or `[test]`, the managed keys go **inside** that table between the markers;
|
|
185
|
+
otherwise the table is created (before any `[install.*]` sub-table). Everything
|
|
186
|
+
outside the markers (`[install.scopes]`, `[test] preload`, ...) is the repo's
|
|
187
|
+
and is never touched; an unmarked copy of a managed key is replaced (it would
|
|
188
|
+
be a duplicate key). The result is validated with `Bun.TOML.parse`, and the
|
|
189
|
+
test suite runs `bun install --frozen-lockfile --dry-run` against a synced file.
|
|
190
|
+
|
|
191
|
+
`minimumReleaseAge = "3d"` is a duration string, which **Bun 1.4+ only**
|
|
192
|
+
understands: Bun 1.3.x rejects the whole file ("Invalid Bunfig"). Run `boff
|
|
193
|
+
sync` in the same commit that sets `.bun-version` to 1.4.4 (`boff sync` prints
|
|
194
|
+
a note when the pin is older or missing).
|
|
195
|
+
|
|
196
|
+
`minimumReleaseAge` has no scope wildcard, so the excludes list names every
|
|
197
|
+
package the fleet publishes; it ships as `bunfig/release-age-excludes.json`
|
|
198
|
+
(regenerated from the registry with `bun run gen:excludes`). `boff sync
|
|
199
|
+
--refresh-excludes` merges the live list from the registry (`NPM_TOKEN` adds
|
|
200
|
+
private packages). A sync never removes entries already present.
|
|
201
|
+
|
|
202
|
+
CI must use the committed `bunfig.toml`: no `cat > bunfig.toml` heredocs and no
|
|
203
|
+
`--mount=type=secret,...,target=/app/bunfig.toml` in Dockerfiles. `boff doctor
|
|
204
|
+
--ci` fails on both and on a `bunfig.toml` that differs from `HEAD` after
|
|
205
|
+
install.
|
|
206
|
+
|
|
207
|
+
### lefthook
|
|
208
|
+
|
|
209
|
+
`boff sync` manages the `pre-push` block of `lefthook.yml` (when the repo uses
|
|
210
|
+
lefthook):
|
|
211
|
+
|
|
212
|
+
```yaml
|
|
213
|
+
# >>> boff managed (do not edit)
|
|
214
|
+
pre-push:
|
|
215
|
+
commands:
|
|
216
|
+
sanity:
|
|
217
|
+
run: ./node_modules/.bin/boff sanity --pre-push
|
|
218
|
+
# <<< boff managed
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`lefthook/base.yml` holds the whole recommended set (pre-commit deps-guard,
|
|
222
|
+
pre-push sanity, post-commit deps-restore) for `extends:`.
|
|
223
|
+
|
|
224
|
+
### knip
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
// knip.config.ts
|
|
228
|
+
import { knipConfig } from '@burdenoff/toolchain/knip/base';
|
|
229
|
+
export default knipConfig({ entry: ['src/index.ts'], project: ['src/**/*.ts'] });
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### package.json
|
|
233
|
+
|
|
234
|
+
```jsonc
|
|
235
|
+
{
|
|
236
|
+
"packageManager": "bun@1.4.4",
|
|
237
|
+
"engines": { "bun": ">=1.4.4" },
|
|
238
|
+
"boff": {
|
|
239
|
+
"type": "svc", // repo type (see above)
|
|
240
|
+
"sanity": {
|
|
241
|
+
"steps": ["format", "lint", "typecheck"], // optional: replaces the type's default step list
|
|
242
|
+
"extra": ["check:migration"], // optional: package.json scripts run as extra steps
|
|
243
|
+
"formatPaths": ["src", "tests"], // optional: limit bun fmt (default: whole repo)
|
|
244
|
+
"codegenCommitted": [".hive-schema.graphql"], // optional: files codegen must leave unchanged
|
|
245
|
+
},
|
|
246
|
+
"coverage": {
|
|
247
|
+
"global": 70, // aggregate lines + functions floor (%)
|
|
248
|
+
"diff": 80, // changed-lines floor (%)
|
|
249
|
+
"exclude": ["src/legacy/**"], // extra lcov excludes
|
|
250
|
+
},
|
|
251
|
+
"scripts": { "unmanaged": ["test"] }, // keys boff sync --scripts must leave alone
|
|
252
|
+
"depsMode": {
|
|
253
|
+
// optional, for boff deps-mode
|
|
254
|
+
"paths": { "@burdenoff/be-sdk": "../../../burdenoff-be-sdk-node" },
|
|
255
|
+
"stateFile": ".vibe-env.json",
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Unknown keys under `boff` are rejected, so a typo never passes silently.
|
|
262
|
+
|
|
263
|
+
`boff sync --scripts` writes these scripts (per type):
|
|
264
|
+
|
|
265
|
+
| Script | Value |
|
|
266
|
+
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
267
|
+
| `type:check` | `boff gate -- bun check --no-pretty` (`-b` for mfe, fe-libs, website; `-p tsconfig.typecheck.json` for app shells that have one) |
|
|
268
|
+
| `format` / `format:check` | `bun fmt` / `bun fmt --check` (plus `formatPaths`) |
|
|
269
|
+
| `lint` | `eslint .` |
|
|
270
|
+
| `test` | `bun test` (not for vscode-ext and docs) |
|
|
271
|
+
| `sanity` / `sanity:force` | `boff sanity` / `boff sanity --force` |
|
|
272
|
+
| `audit` | `bun audit --audit-level=high` |
|
|
273
|
+
|
|
274
|
+
docs repos get only `sanity`, `sanity:force` and `audit`.
|
|
275
|
+
|
|
276
|
+
## CLI
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
boff <command> [options]
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### `boff sanity`
|
|
283
|
+
|
|
284
|
+
The hash-stamped sanity runner (plan section 9).
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
boff sanity [--force] [--only step,...] [--print-keys] [--pre-push]
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
- Steps, in order: `format`, `codegen`, `gendb`, `migrate`, `lint`,
|
|
291
|
+
`typecheck`, `build`, `test`, `knip`, `audit`, then `boff.sanity.extra`
|
|
292
|
+
scripts. Default lists per type:
|
|
293
|
+
|
|
294
|
+
| Type | Steps |
|
|
295
|
+
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
296
|
+
| svc | format, codegen, gendb, migrate, lint, typecheck, build, test, knip |
|
|
297
|
+
| mfe, app | format, codegen, lint, typecheck, build, test |
|
|
298
|
+
| gateway, be-sdk, fe-libs, website, cli, mcp, sdk-node, browser-ext, vscode-ext, plugin, lib | format, lint, typecheck, build, test |
|
|
299
|
+
| doctor | format, lint, typecheck, test |
|
|
300
|
+
| docs | build |
|
|
301
|
+
|
|
302
|
+
A step that does not apply (no `codegen` script, no eslint config, no test
|
|
303
|
+
files, no `DATABASE_URL` for `migrate`, ...) is skipped with the reason
|
|
304
|
+
printed. With an explicit `boff.sanity.steps` list a step that cannot run is
|
|
305
|
+
a configuration error (exit 2), except `migrate` without a database.
|
|
306
|
+
|
|
307
|
+
- Commands: `bun fmt --check`; `bun run codegen` plus `git diff --exit-code`
|
|
308
|
+
on committed outputs (`.hive-schema.graphql` for svc); `bun run gendb`;
|
|
309
|
+
`bun run migrate:deploy`; `bun run lint --cache` (or `eslint . --cache`);
|
|
310
|
+
`bun run type:check` (or `bun check --no-pretty [-b]`); `bun run build:bundle`
|
|
311
|
+
(svc) / `bun run build`; `bun test --coverage --reporter=junit` followed by
|
|
312
|
+
`boff coverage-gate` (svc, gateway, be-sdk, or any repo with
|
|
313
|
+
`boff.coverage.global`), or `bun run test` when the test script is not
|
|
314
|
+
`bun test`; `knip --cache`; `bun audit --audit-level=high`. A script named
|
|
315
|
+
`sanity:<step>` overrides any built-in command.
|
|
316
|
+
- Keys: sha256 over a base (package.json, bun.lock, bunfig.toml,
|
|
317
|
+
.bun-version, toolchain version, Bun version, `CI` flag, repo type), the
|
|
318
|
+
step's commands, the contents of its input files (from
|
|
319
|
+
`git ls-files -co --exclude-standard`, so untracked-but-not-ignored files
|
|
320
|
+
count) and the keys of the steps it depends on (lint/typecheck/test fold in
|
|
321
|
+
codegen and gendb; build folds in typecheck). `migrate` also folds in a hash
|
|
322
|
+
of `host:port/db` of the local `DATABASE_URL` (never the URL) and expires
|
|
323
|
+
after 24 hours.
|
|
324
|
+
- Stamps: an empty file `node_modules/.cache/sanity/<step>.<key>`, written
|
|
325
|
+
only when the step exits 0; `timings.jsonl` beside them records
|
|
326
|
+
`{step,key,ms,rc,ts}` for every executed step. Output is teed to
|
|
327
|
+
`sanity.log`. Each step is spawned directly and its own exit code is used;
|
|
328
|
+
the runner exits with the first failing step's code. SIGINT/SIGTERM kill the
|
|
329
|
+
running step, remove partial state and exit 130/143.
|
|
330
|
+
- Gate: keys are computed **before** a build slot is taken. A full cache hit
|
|
331
|
+
exits immediately; otherwise one slot is held for all missed steps and
|
|
332
|
+
`BOFF_BUILD_SLOT_HELD=1` is exported so nested `boff gate`,
|
|
333
|
+
`with-buildslot.sh` and `build-limits.sh --gate` calls do not re-gate.
|
|
334
|
+
Generators (`codegen`, `gendb`) re-run whenever a step that reads their
|
|
335
|
+
gitignored output runs.
|
|
336
|
+
- `--force` / `SANITY_FORCE=1` ignore stamps (use on `main` and releases).
|
|
337
|
+
`--only a,b` runs a subset. `--pre-push` (and `BOFF_GATE_STRICT=1`) fail
|
|
338
|
+
with exit 75 instead of running ungated when no slot frees in time.
|
|
339
|
+
- `--print-keys` (alias: `boff print-keys`) prints `step=key` lines and
|
|
340
|
+
`all=<key>` without running anything.
|
|
341
|
+
|
|
342
|
+
### `boff gate -- <command>`
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
boff gate [--pre-push] -- <command> [args...]
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
The machine-wide build semaphore with thread caps (plan section 5 item 7).
|
|
349
|
+
|
|
350
|
+
| Variable | Default | Meaning |
|
|
351
|
+
| ---------------------- | ---------------------- | -------------------------------------------------------------------- |
|
|
352
|
+
| `BOFF_BUILD_SLOTS` | `clamp(cores/8, 1, 4)` | concurrent heavy commands per machine |
|
|
353
|
+
| `BOFF_BUILD_THREADS` | `clamp(cores/4, 2, 8)` | thread cap exported to children |
|
|
354
|
+
| `BOFF_BUILD_WAIT` | `900` | seconds to wait for a slot |
|
|
355
|
+
| `BOFF_GATE_STRICT` | unset | `1`: fail (exit 75) on timeout instead of running ungated |
|
|
356
|
+
| `BOFF_BUILD_SLOT_HELD` | unset | `1`: an ancestor holds a slot; run directly (set by the gate itself) |
|
|
357
|
+
| `BUILDSLOT_HELD` | unset | legacy re-entrancy flag from `with-buildslot.sh`, honoured too |
|
|
358
|
+
| `BOFF_BUILD_SLOT_DIR` | see below | override the lock root |
|
|
359
|
+
| `BOFF_GATE_IN_CI` | unset | `1`: take slots even when `CI=true` |
|
|
360
|
+
|
|
361
|
+
- Lock root: `/run/user/$UID/boff-build-slots`, falling back to
|
|
362
|
+
`$HOME/.cache/boff/build-slots`. Never `/tmp` and never derived from
|
|
363
|
+
`TMPDIR`, so every session, container build and agent on the machine shares
|
|
364
|
+
one pool. Locks are `flock(2)` (released by the kernel when the holder dies).
|
|
365
|
+
- The command runs under `nice -n 10` and `ionice -c3` when available.
|
|
366
|
+
- Exported to the command: `BOFF_BUILD_THREADS`, `RAYON_NUM_THREADS`,
|
|
367
|
+
`ROLLDOWN_WORKER_THREADS`, `ROLLDOWN_MAX_BLOCKING_THREADS`, and the held flags.
|
|
368
|
+
- Injected flags when absent: `--threads=N` into `bun check` and `bun fmt`,
|
|
369
|
+
`--parallel=N` into `bun test --parallel`, `--maxWorkers=N` into vitest.
|
|
370
|
+
- `CI=true`: the runner is a dedicated machine, so no slot is taken; thread
|
|
371
|
+
caps still apply. Inside a Docker build without `/run/user`, the
|
|
372
|
+
`$HOME/.cache` fallback is used.
|
|
373
|
+
|
|
374
|
+
### `boff coverage-gate`
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
boff coverage-gate [--lcov coverage/lcov.info] [--global N] [--diff N]
|
|
378
|
+
[--compare-branch REF] [--no-diff] [--run-tests]
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Replaces every `scripts/check-coverage.sh` (no python, no diff-cover).
|
|
382
|
+
|
|
383
|
+
- Reads `coverage/lcov.info`; drops generated code, tests and node_modules
|
|
384
|
+
(`**/generated/**`, `**/*.generated.ts`, `**/__tests__/**`, `**/*.test.*`,
|
|
385
|
+
`**/*.spec.*`, `src/test/**`, `test/**`, `tests/**`) plus
|
|
386
|
+
`boff.coverage.exclude`.
|
|
387
|
+
- Aggregate: summed lines and functions must each reach the global threshold
|
|
388
|
+
(`--global`, else `GLOBAL_THRESHOLD`, else `boff.coverage.global`, else 80).
|
|
389
|
+
- Diff: lines added since the merge base with `COMPARE_BRANCH` (default
|
|
390
|
+
`origin/main`, `git diff -U0`) that lcov instruments must reach the diff
|
|
391
|
+
threshold (`--diff`, `DIFF_THRESHOLD`, `boff.coverage.diff`, else 80).
|
|
392
|
+
When the base ref is missing the diff check is skipped locally and **fails
|
|
393
|
+
in CI** (use `fetch-depth: 0`).
|
|
394
|
+
- Prints a table plus the uncovered changed lines, exits 1 on failure.
|
|
395
|
+
`--run-tests` runs `bun [--env-file=.env.local] test --coverage` first, as a
|
|
396
|
+
drop-in for the old script.
|
|
397
|
+
|
|
398
|
+
Never lower an existing threshold when migrating; copy the repo's current
|
|
399
|
+
`GLOBAL_THRESHOLD`/`DIFF_THRESHOLD` defaults into `boff.coverage`.
|
|
400
|
+
|
|
401
|
+
### `boff doctor [--ci]`
|
|
402
|
+
|
|
403
|
+
Checks: `.bun-version` is 1.4.4; `packageManager` and `engines.bun`; no
|
|
404
|
+
`baseUrl` in any tsconfig; managed blocks present and unmodified (`sync
|
|
405
|
+
--check`, including scripts when `boff.type` is set); no `cat > bunfig.toml`
|
|
406
|
+
in workflows or composite actions and no bunfig secret mount in Dockerfiles;
|
|
407
|
+
no literal `bun-version:` and no `oven/bun:<other>` base; `oven-sh/setup-bun`
|
|
408
|
+
pinned by full SHA; a `pull_request` workflow with a job named exactly
|
|
409
|
+
`PR Gate`; `bunfig.toml` unchanged versus `HEAD`. Without `--ci` failures are
|
|
410
|
+
warnings (exit 0); with `--ci` any failure exits 1.
|
|
411
|
+
|
|
412
|
+
### `boff sync`
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
boff sync [--check] [--type <type>] [--scripts] [--refresh-excludes]
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Writes the bunfig and lefthook managed blocks; `--scripts` also writes the
|
|
419
|
+
per-type scripts; `--type` records `boff.type`. Idempotent. `--check` writes
|
|
420
|
+
nothing and exits 1 on drift.
|
|
421
|
+
|
|
422
|
+
### `boff deps-mode`
|
|
423
|
+
|
|
424
|
+
```
|
|
425
|
+
boff deps-mode <status|remote|local|check> [--silent] [--skip-install]
|
|
426
|
+
boff deps-mode guard <pre-commit|post-commit>
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Replaces the `scripts/deps-mode.ts` and `scripts/deps-guard.sh` copies.
|
|
430
|
+
`remote` (alias `npm`) restores `@burdenoff/*` values from `git HEAD` (falling
|
|
431
|
+
back to the installed or published version); `local` (alias `workspace`)
|
|
432
|
+
rewrites them to `workspace:*` for an MFE inside a Bun workspace root,
|
|
433
|
+
`file:` paths for app shells, and relative paths for svc/gateway/sdk repos
|
|
434
|
+
(found from `boff.depsMode.paths`, `deps-local-paths.json`, or the fleet's
|
|
435
|
+
directory conventions). `check` fails on any local reference in package.json
|
|
436
|
+
or bun.lock (CI gate). The `guard` hooks keep commits registry-only and
|
|
437
|
+
restore local mode afterwards (python-free).
|
|
438
|
+
|
|
439
|
+
### `boff workflow-policy [root]`
|
|
440
|
+
|
|
441
|
+
The TypeScript port of `check-workflow-policy.sh`: `pinned-uses`,
|
|
442
|
+
`floating-latest`, `remote-script`, `exact-exec`, `frozen-install`,
|
|
443
|
+
`unsafe-interpolation`, `hashed-pip`. Prints GitHub error annotations; exit 0
|
|
444
|
+
clean, 1 violations, 2 usage error.
|
|
445
|
+
|
|
446
|
+
### `boff version`
|
|
447
|
+
|
|
448
|
+
Prints the toolchain version (`boff --version`).
|
|
449
|
+
|
|
450
|
+
## CI usage
|
|
451
|
+
|
|
452
|
+
Restore the sanity stamps with keys from `print-keys`, then run sanity:
|
|
453
|
+
|
|
454
|
+
```yaml
|
|
455
|
+
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
|
456
|
+
with:
|
|
457
|
+
bun-version-file: .bun-version
|
|
458
|
+
- run: bun install --frozen-lockfile
|
|
459
|
+
- run: bunx boff doctor --ci
|
|
460
|
+
- id: keys
|
|
461
|
+
run: bunx boff print-keys >> "$GITHUB_OUTPUT"
|
|
462
|
+
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
463
|
+
with:
|
|
464
|
+
path: |
|
|
465
|
+
node_modules/.cache/sanity
|
|
466
|
+
node_modules/.cache/eslint
|
|
467
|
+
node_modules/.cache/tsc
|
|
468
|
+
key: sanity-${{ runner.os }}-${{ steps.keys.outputs.all }}
|
|
469
|
+
restore-keys: sanity-${{ runner.os }}-
|
|
470
|
+
- run: bunx boff sanity
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
On `main` and release runs use `boff sanity --force`. Keep the required
|
|
474
|
+
`PR Gate` job in the caller workflow (`if: always()`, failing unless every
|
|
475
|
+
needed job succeeded).
|
|
476
|
+
|
|
477
|
+
## Developing the toolchain
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
bun install --frozen-lockfile
|
|
481
|
+
bun check --no-pretty
|
|
482
|
+
bun fmt --check
|
|
483
|
+
bun run lint
|
|
484
|
+
bun run test:coverage # unit tests + boff coverage-gate on itself (>= 80%)
|
|
485
|
+
bun run selftest # every fixture through boff sanity
|
|
486
|
+
bun run build # dist/cli.js (+ dist/knip/base.js)
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
`fixtures/<type>` holds one minimal repo per type (svc, gateway, be-sdk, mfe,
|
|
490
|
+
app, website, cli, mcp, sdk-node, browser-ext, vscode-ext, plugin, lib). The
|
|
491
|
+
selftest copies each into `.selftest/`, links this checkout as
|
|
492
|
+
`node_modules/@burdenoff/toolchain`, runs `boff sync`, `boff doctor --ci` and
|
|
493
|
+
`boff sanity` cold and warm, introduces a type error (must fail and leave no
|
|
494
|
+
stamp) and a formatting-only change (must invalidate only the format key).
|
|
495
|
+
|
|
496
|
+
**Rule: a toolchain change must keep every fixture green.** If a change needs
|
|
497
|
+
consumer-side edits, change the fixture in the same commit so the selftest
|
|
498
|
+
shows exactly what consumers must do. The PR workflow runs static checks,
|
|
499
|
+
unit tests with the coverage gate, the build smoke test and the selftest, and
|
|
500
|
+
`PR Gate` fails unless all of them pass. A published version that breaks a
|
|
501
|
+
consumer is rolled back by re-pinning the previous version (never unpublish).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Template for the block `boff sync` manages inside a consumer's bunfig.toml.
|
|
2
|
+
#
|
|
3
|
+
# Each [table] below lists the keys boff owns in that table. In the consumer
|
|
4
|
+
# file they are written between the marker lines
|
|
5
|
+
# # >>> boff managed (do not edit)
|
|
6
|
+
# # <<< boff managed
|
|
7
|
+
# inside the existing table when the repo already has one (TOML forbids a
|
|
8
|
+
# second [install] or [test] table), or as a new table otherwise. Everything
|
|
9
|
+
# outside the markers ([install.scopes], [test] preload, ...) belongs to the
|
|
10
|
+
# repo and is never touched.
|
|
11
|
+
#
|
|
12
|
+
# {{RELEASE_AGE_EXCLUDES}} expands to bunfig/release-age-excludes.json (every
|
|
13
|
+
# package the fleet publishes), merged with any entries already present.
|
|
14
|
+
#
|
|
15
|
+
# trustedDependencies is deliberately not managed: lifecycle scripts stay
|
|
16
|
+
# blocked unless a repo lists the package in its own package.json.
|
|
17
|
+
|
|
18
|
+
[install]
|
|
19
|
+
minimumReleaseAge = "3d"
|
|
20
|
+
minimumReleaseAgeExcludes = {{RELEASE_AGE_EXCLUDES}}
|
|
21
|
+
|
|
22
|
+
[test]
|
|
23
|
+
coverageReporter = ["text", "lcov"]
|