code-foundry 1.48.2 → 1.48.4

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.
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "Code Foundry configuration",
4
+ "type": "object",
5
+ "properties": {
6
+ "formatter": {
7
+ "description": "Explicit JavaScript formatter. Omit to preserve repository scripts and the Oxfmt fallback.",
8
+ "type": "string",
9
+ "enum": ["oxfmt", "prettier", "bun"]
10
+ }
11
+ },
12
+ "additionalProperties": true
13
+ }
@@ -48,7 +48,7 @@ on:
48
48
  bun-version:
49
49
  required: false
50
50
  type: string
51
- default: '1.4.0'
51
+ default: '1.4.4'
52
52
  wrangler-version:
53
53
  description: 'local uses the locked workspace installation; otherwise specify an exact version.'
54
54
  required: false
@@ -38,7 +38,7 @@ on:
38
38
  bun-version:
39
39
  required: false
40
40
  type: string
41
- default: '1.4.0'
41
+ default: '1.4.4'
42
42
  runner:
43
43
  required: false
44
44
  type: string
@@ -802,7 +802,7 @@ jobs:
802
802
  # fail with `bun: command not found` before publish.
803
803
  uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
804
804
  with:
805
- bun-version: latest
805
+ bun-version: 1.4.4
806
806
  - name: Enable Corepack (pnpm/yarn managers)
807
807
  run: corepack enable
808
808
  - name: Install dependencies
@@ -311,7 +311,7 @@ jobs:
311
311
  steps.audit-toolchain.outputs.package_manager == 'bun'
312
312
  uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
313
313
  with:
314
- bun-version: 1.4.3
314
+ bun-version: 1.4.4
315
315
  - name: Enable Corepack
316
316
  if: >-
317
317
  matrix.entry.language == 'javascript' &&
@@ -950,7 +950,7 @@ jobs:
950
950
  steps.audit-toolchain.outputs.package_manager == 'bun'
951
951
  uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
952
952
  with:
953
- bun-version: 1.4.3
953
+ bun-version: 1.4.4
954
954
  - name: Enable Corepack
955
955
  if: >-
956
956
  steps.profile.outputs.javascript == 'true' &&
package/.mise.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [tools]
2
2
  node = "24.18.0"
3
- bun = "1.4.3"
3
+ bun = "1.4.4"
4
4
 
5
5
  [settings]
6
6
  experimental = true
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.48.4](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.3...v1.48.4) (2026-10-22)
4
+
5
+
6
+ ### Maintenance
7
+
8
+ * bun-1.4.4-cloud ([#771](https://github.com/0xPlayerOne/code-foundry/issues/771)) ([c99f0ed](https://github.com/0xPlayerOne/code-foundry/commit/c99f0ed8b66129f3b599326bc67d900719b921f4))
9
+
10
+ ## [1.48.3](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.2...v1.48.3) (2026-10-17)
11
+
12
+
13
+ ### Tests
14
+
15
+ * read the chained hook fixture with fs.readFileSync ([#769](https://github.com/0xPlayerOne/code-foundry/issues/769)) ([5d5925a](https://github.com/0xPlayerOne/code-foundry/commit/5d5925ac38975f5b1cc9b0785aa492db029e420c))
16
+
3
17
  ## [1.48.2](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.1...v1.48.2) (2026-10-17)
4
18
 
5
19
 
@@ -126,23 +140,9 @@
126
140
 
127
141
  * **billing:** declare the runner inputs the callers always forward ([#733](https://github.com/0xPlayerOne/code-foundry/issues/733)) ([f7a29cb](https://github.com/0xPlayerOne/code-foundry/commit/f7a29cb6dbc9a8695a9bef9e85ccce82aa9c84db))
128
142
 
129
- ## [1.45.0](https://github.com/0xPlayerOne/code-foundry/compare/v1.44.11...v1.45.0) (2026-10-06)
130
-
131
-
132
- ### Features
133
-
134
- * add a merged-lane billing orchestrator for minute-billed consumers ([#731](https://github.com/0xPlayerOne/code-foundry/issues/731)) ([a91402e](https://github.com/0xPlayerOne/code-foundry/commit/a91402e701f458ece0d2ea4715f9586a06b15108))
135
-
136
- ## [1.44.11](https://github.com/0xPlayerOne/code-foundry/compare/v1.44.10...v1.44.11) (2026-10-06)
137
-
138
-
139
- ### Bug Fixes
140
-
141
- * **release:** retry the token probe and REST fallback writes with backoff ([#729](https://github.com/0xPlayerOne/code-foundry/issues/729)) ([ad28e18](https://github.com/0xPlayerOne/code-foundry/commit/ad28e18326f0ee8c25950fb649eb81cdb3dc0a1a))
142
-
143
143
  <!-- code-foundry: packaged changelog -->
144
144
 
145
145
  ---
146
146
 
147
- This packaged changelog lists the 20 most recent releases; 250 older releases are omitted to keep the package small.
147
+ This packaged changelog lists the 20 most recent releases; 252 older releases are omitted to keep the package small.
148
148
  See the [full changelog](https://github.com/0xPlayerOne/code-foundry/blob/main/CHANGELOG.md) for the complete history.
@@ -1,5 +1,43 @@
1
1
  # Configuration reference
2
2
 
3
+ ## JavaScript formatter
4
+
5
+ Set `formatter: oxfmt`, `formatter: prettier`, or `formatter: bun` in
6
+ `.github/code-foundry.yml` to select the formatter explicitly. The shipped
7
+ `.github/code-foundry.schema.json` describes this enum; sync, doctor, task
8
+ discovery and execution reject unknown choices. With no key, existing package
9
+ format scripts retain precedence and the existing Oxfmt fallback is unchanged.
10
+ Current application repositories continue to use Oxfmt.
11
+
12
+ An explicit choice controls the format gate without changing lint, type-check,
13
+ test or build scripts. Sync retains repository-owned Prettier configuration
14
+ for Prettier/Bun choices, and only installs/migrates the Oxfmt baseline for
15
+ Oxfmt. It does not install packages or rewrite dependency lockfiles: install
16
+ the selected Prettier/Oxfmt dependency using the repository's package manager,
17
+ or install official Bun 1.4.4 for the Bun choice.
18
+
19
+ `code-foundry format --check` checks the selected formatter; omit `--check`
20
+ to write. Bun opt-in also supports `code-foundry format --staged` and
21
+ `code-foundry format --changed REF`, with optional `--check`. These invoke
22
+ `bun fmt --no-script --staged` and `bun fmt --no-script --changed=REF`.
23
+ The scopes are mutually exclusive and fail for other formatters. Changed mode
24
+ does not stage files. Staged write mode restages formatted files and refuses
25
+ partially staged files before running Bun, protecting intentionally unstaged
26
+ edits. The commit gate uses check-only staged formatting and continues its
27
+ independent staged lint, type-check, Python and Rust checks.
28
+
29
+ For Bun opt-in, a hook can replace its **formatter-only** lint-staged operation
30
+ with `code-foundry format --staged`. Retain every other lint-staged lint/test
31
+ command and any hook ordering. Remove the lint-staged dependency only when no
32
+ remaining command needs it, and regenerate the dependency lockfile normally.
33
+ Sync never removes lint-staged, its configuration or other hook operations
34
+ automatically. Oxfmt/Prettier consumers retain them unchanged.
35
+
36
+ Type-check-only native fallbacks use `bun check --no-script -p tsconfig.json`
37
+ on Bun 1.4.4. Repository scripts remain authoritative, including framework
38
+ checks and declaration-emitting builds. Bun's checker emits no files; it must
39
+ not replace a `tsc` build that emits declarations or other compiler output.
40
+
3
41
  Code Foundry has one repository-owned control plane:
4
42
  `.github/code-foundry.yml`. `init` creates it, `sync` renders the selected
5
43
  baseline, and `doctor` checks local and GitHub-facing prerequisites.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "1.48.2",
3
+ "version": "1.48.4",
4
4
  "description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
5
5
  "homepage": "https://github.com/0xPlayerOne/code-foundry#readme",
6
6
  "bugs": {
@@ -47,7 +47,7 @@
47
47
  "performance:check": "node scripts/performance-check.mjs",
48
48
  "test": "node --test",
49
49
  "test:unit": "node --test",
50
- "type-check": "tsc --project jsconfig.json",
50
+ "type-check": "bun check --no-script -p jsconfig.json",
51
51
  "pack:check": "npm pack --dry-run",
52
52
  "prepublishOnly": "npm test && npm run pack:check"
53
53
  },
@@ -60,5 +60,5 @@
60
60
  "engines": {
61
61
  "node": ">=24"
62
62
  },
63
- "packageManager": "bun@1.4.3"
63
+ "packageManager": "bun@1.4.4"
64
64
  }
package/src/cli.mjs CHANGED
@@ -16,6 +16,7 @@ Usage:
16
16
  npx code-foundry init [--target PATH]
17
17
  npx code-foundry sync [--target PATH]
18
18
  npx code-foundry doctor [--target PATH]
19
+ npx code-foundry format [--target PATH] [--check] [--staged | --changed REF]
19
20
  npx code-foundry plan [--target PATH] [--tier fast|audit] [--changed] [--base REF] [--json]
20
21
  npx code-foundry check [--target PATH] [--tier fast|audit] [--changed] [--base REF] [--timeout SECONDS] [--json]
21
22
  npx code-foundry affected [--target PATH] [--base REF] [--head REF] [--tool auto|turbo|vite-plus]
@@ -179,6 +180,19 @@ function parseArgs(argv) {
179
180
  }
180
181
 
181
182
  async function main() {
183
+ if (process.argv[2] === 'format') {
184
+ if (process.argv.slice(3).some((arg) => arg === '--help' || arg === '-h')) {
185
+ console.log('Usage: code-foundry format [--target PATH] [--check] [--staged | --changed REF]')
186
+ return
187
+ }
188
+ try {
189
+ const { formatCommand } = await import('./commands/format.mjs')
190
+ process.exitCode = formatCommand(process.argv.slice(3))
191
+ } catch (error) {
192
+ fail(error instanceof Error ? error.message : String(error))
193
+ }
194
+ return
195
+ }
182
196
  if (['plan', 'check'].includes(process.argv[2])) {
183
197
  const { agentCommand } = await import('./commands/agent-check.mjs')
184
198
  process.exitCode = agentCommand(process.argv.slice(2))
@@ -8,6 +8,7 @@ import { recommendRunners, resolveMonorepoTool, resolveProfile } from '../lib/pr
8
8
  import { packageManagerForLockfile } from '../lib/lockfiles.mjs'
9
9
  import { doctorGithub } from '../lib/github-doctor.mjs'
10
10
  import { isGeneratedEventCaller } from './sync.mjs'
11
+ import { formatterChoice } from '../lib/formatter.mjs'
11
12
 
12
13
  /** @param {string} message */
13
14
  function warn(message) {
@@ -28,6 +29,11 @@ export function doctor(root, options = {}) {
28
29
  }
29
30
 
30
31
  const toolchain = config.toolchain ?? 'auto'
32
+ try {
33
+ formatterChoice(config.formatter)
34
+ } catch (cause) {
35
+ error(cause instanceof Error ? cause.message : String(cause))
36
+ }
31
37
  if (!['auto', 'native', 'mise'].includes(toolchain)) error(`unsupported toolchain: ${toolchain}`)
32
38
  if (toolchain === 'mise' && !existsSync(join(target, '.mise.toml')))
33
39
  error('.mise.toml is required when toolchain: mise')
@@ -237,7 +243,7 @@ export function doctor(root, options = {}) {
237
243
  error('validation caller registers draft-time checks; run code-foundry sync.')
238
244
  }
239
245
  if (
240
- !/uses:\s+(?:\.\/\.github\/workflows\/validation(?:-no-codeql)?\.yml|\S+\/\.github\/workflows\/validation(?:-no-codeql)?\.yml@)/.test(
246
+ !/uses:\s+(?:\.\/\.github\/workflows\/validation(?:-no-codeql|-billing)?\.yml|\S+\/\.github\/workflows\/validation(?:-no-codeql|-billing)?\.yml@)/.test(
241
247
  caller
242
248
  )
243
249
  ) {
@@ -0,0 +1,44 @@
1
+ // @ts-check
2
+
3
+ import { resolve } from 'node:path'
4
+ import { spawnSync } from 'node:child_process'
5
+ import { readConfig } from '../lib/config.mjs'
6
+ import { formatterCommand } from '../lib/formatter.mjs'
7
+
8
+ /** @param {string[]} argv */
9
+ export function formatCommand(argv) {
10
+ let target = process.cwd()
11
+ let check = false
12
+ let staged = false
13
+ /** @type {string|undefined} */
14
+ let changed
15
+ while (argv.length) {
16
+ const arg = argv.shift()
17
+ if (arg === '--target') target = argv.shift() || ''
18
+ else if (arg === '--check') check = true
19
+ else if (arg === '--staged') staged = true
20
+ else if (arg === '--changed') changed = argv.shift() || ''
21
+ else throw new Error(`Unknown format option: ${arg}`)
22
+ }
23
+ if (!target || target.startsWith('-')) throw new Error('--target requires a path')
24
+ target = resolve(target)
25
+ const config = readConfig(resolve(target, '.github/code-foundry.yml'))
26
+ const [tool, ...args] = formatterCommand(config.formatter, { check, staged, changed })
27
+ if (staged && !check) {
28
+ // Bun restages working-tree contents. Refuse partial staging rather than
29
+ // silently including edits that the caller deliberately left out.
30
+ /** @param {string[]} diffArgs */
31
+ const paths = (diffArgs) => {
32
+ const result = spawnSync('git', diffArgs, { cwd: target, encoding: 'utf8' })
33
+ if (result.status !== 0) throw new Error('Cannot inspect staged formatter changes')
34
+ return result.stdout.split('\0').filter(Boolean)
35
+ }
36
+ const index = new Set(paths(['diff', '--cached', '--name-only', '-z']))
37
+ if (paths(['diff', '--name-only', '-z']).some((file) => index.has(file)))
38
+ throw new Error('Partially staged files: format and stage those files explicitly first')
39
+ }
40
+ const command = tool === 'bun' ? [tool, ...args] : ['npx', '--no-install', tool, ...args]
41
+ const result = spawnSync(command[0], command.slice(1), { cwd: target, stdio: 'inherit' })
42
+ if (result.error) throw result.error
43
+ return result.status ?? 1
44
+ }
@@ -27,6 +27,7 @@ import {
27
27
  } from '../lib/config.mjs'
28
28
  import { buildReleaseConfig, buildReleaseManifest } from '../lib/release-manifest.mjs'
29
29
  import { customWorkflowFiles, overlayPolicy } from '../lib/overlay.mjs'
30
+ import { formatterChoice } from '../lib/formatter.mjs'
30
31
  import {
31
32
  inspectQueueCaller,
32
33
  mergeQueueEnabled,
@@ -54,6 +55,7 @@ const standardFiles = [
54
55
  '.github/PULL_REQUEST_TEMPLATE.md',
55
56
  '.github/SECURITY.md',
56
57
  '.github/dependabot.yml',
58
+ '.github/code-foundry.schema.json',
57
59
  '.github/ISSUE_TEMPLATE/bug_report.yml',
58
60
  '.github/ISSUE_TEMPLATE/config.yml',
59
61
  '.github/ISSUE_TEMPLATE/feature_request.yml',
@@ -153,6 +155,7 @@ function synchronize(options) {
153
155
  }
154
156
  const defaults = createDefaultConfig(target, source, existingConfig.git_workflow)
155
157
  let config = { ...defaults, ...existingConfig }
158
+ formatterChoice(config.formatter)
156
159
  const workflow = gitWorkflow(config.git_workflow)
157
160
  if (!['direct', 'staging-release'].includes(workflow)) {
158
161
  throw new Error(`Unsupported git_workflow: ${workflow}; use direct or staging-release.`)
@@ -514,7 +517,7 @@ function synchronize(options) {
514
517
  else rmSync(join(target, file), { force: true })
515
518
  }
516
519
  }
517
- if (includesValue(languages, 'typescript')) {
520
+ if (includesValue(languages, 'typescript') && formatterChoice(config.formatter) === 'oxfmt') {
518
521
  // The Oxfmt baseline supersedes .prettierrc/.prettierignore. Entries the
519
522
  // consumer added beyond the baseline survive the migration inside the
520
523
  // .oxfmtrc.json ignorePatterns list.
@@ -543,7 +546,7 @@ function synchronize(options) {
543
546
  changed,
544
547
  dryRun
545
548
  )
546
- } else {
549
+ } else if (!includesValue(languages, 'typescript')) {
547
550
  for (const file of ['.prettierrc', '.prettierignore']) {
548
551
  if (existsSync(join(target, file))) {
549
552
  changed.push(file)
@@ -690,9 +693,11 @@ function renderReleaseConfig(target, sourceFile, config) {
690
693
 
691
694
  /** @param {string} file @param {string} languages @param {string} features @param {Record<string, string>} config */
692
695
  function shouldInclude(file, languages, features, config) {
696
+ if (file === '.github/code-foundry.schema.json') return includesValue(languages, 'typescript')
693
697
  if (file === 'ruff.toml') return includesValue(languages, 'python')
694
- if (file === '.oxfmtrc.json' || file === '.oxlintrc.json')
695
- return includesValue(languages, 'typescript')
698
+ if (file === '.oxfmtrc.json')
699
+ return includesValue(languages, 'typescript') && formatterChoice(config.formatter) === 'oxfmt'
700
+ if (file === '.oxlintrc.json') return includesValue(languages, 'typescript')
696
701
  if (file === '.github/dependabot.yml')
697
702
  return (
698
703
  configured(config.dependency_updater, 'dependabot') === 'dependabot' &&
@@ -0,0 +1,32 @@
1
+ // @ts-check
2
+
3
+ /** Explicit formatter choices. An absent key preserves repository-script precedence. */
4
+ export const FORMATTERS = Object.freeze(['oxfmt', 'prettier', 'bun'])
5
+
6
+ /** @param {string|undefined} value */
7
+ export function formatterChoice(value) {
8
+ const choice = value || 'oxfmt'
9
+ if (!FORMATTERS.includes(choice))
10
+ throw new Error(`Unsupported formatter: ${choice}; use oxfmt, prettier, or bun.`)
11
+ return choice
12
+ }
13
+
14
+ /** @param {string|undefined} value @param {{check?: boolean, staged?: boolean, changed?: string, files?: string[]}} [options] */
15
+ export function formatterCommand(value, options = {}) {
16
+ const choice = formatterChoice(value)
17
+ const { check = true, staged = false, changed, files = ['.'] } = options
18
+ if (staged && changed !== undefined) throw new Error('--staged and --changed are exclusive')
19
+ if ((staged || changed !== undefined) && choice !== 'bun')
20
+ throw new Error('--staged and --changed require formatter: bun')
21
+ if (changed !== undefined && (!changed.trim() || changed.startsWith('-')))
22
+ throw new Error('--changed requires a Git branch or commit')
23
+ if (choice === 'bun')
24
+ return [
25
+ 'bun',
26
+ 'fmt',
27
+ '--no-script',
28
+ ...(check ? ['--check'] : []),
29
+ ...(staged ? ['--staged'] : changed !== undefined ? [`--changed=${changed}`] : files),
30
+ ]
31
+ return [choice, ...(check ? ['--check'] : ['--write']), ...files]
32
+ }
@@ -20,7 +20,8 @@ const LINTABLE = /\.(?:[cm]?[jt]sx?|vue|svelte|astro)$/i
20
20
  const TYPED = /\.(?:ts|tsx|mts|cts)$|(?:^|\/)tsconfig[^/]*\.json$/i
21
21
  /** JavaScript sources, type-checked only when the repository opts in with a root jsconfig. */
22
22
  const JAVASCRIPT = /\.(?:js|jsx|mjs|cjs)$|(?:^|\/)jsconfig[^/]*\.json$/i
23
- const FORMAT_CONFIG = /^(?:\.oxfmtrc\.jsonc?|oxfmt\.config\.[^/]+|\.prettierignore)$/
23
+ const FORMAT_CONFIG =
24
+ /^(?:\.oxfmtrc\.jsonc?|oxfmt\.config\.[^/]+|\.prettierrc(?:\.[^/]+)?|prettier\.config\.[^/]+|\.prettierignore|bunfig\.toml|\.github\/code-foundry\.yml)$/
24
25
  const LINT_CONFIG = /^(?:\.oxlintrc\.json|oxlint\.config\.[^/]+)$/
25
26
  const PYTHON = /\.pyi?$/i
26
27
  const PYTHON_CONFIG =
@@ -5,6 +5,7 @@ import { spawnSync } from 'node:child_process'
5
5
  import { isAbsolute, relative, resolve, sep } from 'node:path'
6
6
  import { createHash } from 'node:crypto'
7
7
  import { listValue, readConfig } from './config.mjs'
8
+ import { formatterChoice } from './formatter.mjs'
8
9
 
9
10
  export const TASKS = Object.freeze([
10
11
  'format',
@@ -36,6 +37,7 @@ export const TASK_SCRIPTS = Object.freeze({
36
37
  /** @param {string} root */
37
38
  export function readTaskPolicy(root) {
38
39
  const config = readConfig(resolve(root, '.github/code-foundry.yml'))
40
+ formatterChoice(config.formatter)
39
41
  const required = listValue(config.required_capabilities ?? '')
40
42
  for (const capability of required) {
41
43
  if (![...TASKS, 'coverage'].includes(capability))
@@ -114,7 +116,9 @@ export function describeTask(root, task, profile) {
114
116
  rust ||
115
117
  (['format', 'lint'].includes(task) && python) ||
116
118
  (task === 'type_check' && existsSync(resolve(root, 'tsconfig.json'))) ||
117
- (js && task === 'format' && hasNativeToolSetup(root, pkg, 'oxfmt')) ||
119
+ (js &&
120
+ task === 'format' &&
121
+ (Boolean(policy.config.formatter) || hasNativeToolSetup(root, pkg, 'oxfmt'))) ||
118
122
  (js && task === 'lint' && hasNativeToolSetup(root, pkg, 'oxlint'))
119
123
  if (!native) {
120
124
  applicable = false
@@ -28,6 +28,7 @@ import {
28
28
  freshCloneBuildHint,
29
29
  } from './lib/pre-commit.mjs'
30
30
  import { runNodePackagePerformance } from './lib/node-package-performance.mjs'
31
+ import { formatterChoice, formatterCommand } from './lib/formatter.mjs'
31
32
  import {
32
33
  EVAL_BUDGET_FILE_DEFAULT,
33
34
  EVAL_REPORT_FILE,
@@ -38,6 +39,7 @@ import {
38
39
 
39
40
  const root = process.cwd()
40
41
  const config = readConfig(resolve(root, '.github/code-foundry.yml'))
42
+ formatterChoice(config.formatter)
41
43
  const repoProfile = resolveProfile(root)
42
44
  /** @type {string[]} */
43
45
  const languages = configured(config.languages, repoProfile.languages).split(',').filter(Boolean)
@@ -737,7 +739,8 @@ function ci(task) {
737
739
  return
738
740
  }
739
741
  if (task === 'format') {
740
- const scripted = runScript(['format:check', 'format', 'fmt'])
742
+ if (config.formatter && hasRootJavascriptProject()) runSelectedFormatter()
743
+ const scripted = config.formatter || runScript(['format:check', 'format', 'fmt'])
741
744
  if (
742
745
  !scripted &&
743
746
  (hasLanguage('typescript') || hasLanguage('javascript')) &&
@@ -774,7 +777,8 @@ function ci(task) {
774
777
  }
775
778
  if (task === 'type_check') {
776
779
  const scripted = runScript(['type-check', 'typecheck', 'type:check'])
777
- if (!scripted && existsSync(resolve(root, 'tsconfig.json'))) runTool('tsc', ['--noEmit'])
780
+ if (!scripted && existsSync(resolve(root, 'tsconfig.json')))
781
+ run('bun', ['check', '--no-script', '-p', 'tsconfig.json'])
778
782
  if (hasLanguage('rust') && hasRootRustProject()) run('cargo', ['check', '--all-targets'])
779
783
  return
780
784
  }
@@ -1189,6 +1193,13 @@ function runToolOnFiles(tool, args, files) {
1189
1193
  for (const chunk of chunkFiles(files)) runTool(tool, [...args, ...chunk])
1190
1194
  }
1191
1195
 
1196
+ /** @param {{staged?: boolean, files?: string[]}} [options] */
1197
+ function runSelectedFormatter(options = {}) {
1198
+ const [tool, ...args] = formatterCommand(config.formatter, options)
1199
+ if (tool === 'bun') run(tool, args)
1200
+ else runTool(tool, args)
1201
+ }
1202
+
1192
1203
  /**
1193
1204
  * The local commit gate.
1194
1205
  *
@@ -1221,7 +1232,15 @@ export function preCommit() {
1221
1232
  const js = (hasLanguage('typescript') || hasLanguage('javascript')) && hasRootJavascriptProject()
1222
1233
 
1223
1234
  if (js && (plan.formatAll || plan.formatFiles.length)) {
1224
- if (plan.formatAll) {
1235
+ if (config.formatter) {
1236
+ runSelectedFormatter(
1237
+ plan.formatAll
1238
+ ? {}
1239
+ : config.formatter === 'bun'
1240
+ ? { staged: true }
1241
+ : { files: plan.formatFiles }
1242
+ )
1243
+ } else if (plan.formatAll) {
1225
1244
  if (!runScript(['format:check', 'format', 'fmt']) && hasOxfmtSetup())
1226
1245
  runTool('oxfmt', ['--check', '.'])
1227
1246
  } else if (hasOxfmtSetup()) {
@@ -1265,7 +1284,7 @@ export function preCommit() {
1265
1284
  onFailure: buildHint,
1266
1285
  })
1267
1286
  if (!scripted && existsSync(resolve(root, 'tsconfig.json')))
1268
- runTool('tsc', ['--noEmit'], { onFailure: buildHint })
1287
+ run('bun', ['check', '--no-script', '-p', 'tsconfig.json'], { onFailure: buildHint })
1269
1288
  }
1270
1289
  if (plan.rust) {
1271
1290
  run('cargo', ['fmt', '--check'])