From 33d3cb5cbc210d399b6765acc5d9208d43df1ff4 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Wed, 15 Jul 2026 17:45:33 +0800 Subject: [PATCH 1/8] feat: distribute fmtkit as a single self-contained binary Adds the missing distribution piece that #46's module collapse set up: one `fmtkit` binary per platform, published on GitHub Releases and installable through the oullin/homebrew-fmtkit tap, with zero runtime dependencies (no Node.js, no Docker). - packages/devx/scripts/sidecar.ts: bun-compiled multiplexer that runs the TS pipeline, oxfmt, and oxlint from one executable, loading the napi bindings via NAPI_RS_NATIVE_LIBRARY_PATH. - infra/scripts/release/stage-ts-assets.sh: builds the sidecar per platform and fetches the oxc-parser/oxfmt/oxlint bindings, pinned by packages/devx/package.json. - packages/driver/internal/tsruntime: embeds the staged assets behind the fmtkit_sidecar build tag and extracts them to the user cache on first run. - packages/driver/internal/orchestrator: Go port of the infra/bin/fmtkit pipeline with live-streamed, prettified tool logs plus the existing condensed summaries (--quiet restores summary-only output). - packages/driver/cmd/fmtkit: the distributed entry point; subsumes the fmtkit-go surface under `fmtkit go`. - .goreleaser.yaml + publish-release.yml `binaries` job: cross-platform tar.gz archives, checksums, and the Homebrew cask push (needs the HOMEBREW_TAP_TOKEN secret). - tests.yml `binary` job: goreleaser config check plus an end-to-end smoke test of the compiled binary. --- .github/workflows/publish-release.yml | 40 +++ .github/workflows/tests.yml | 34 +++ .gitignore | 2 + .goreleaser.yaml | 73 +++++ LICENSE | 21 ++ README.md | 21 +- embedded.go | 18 ++ infra/scripts/release/stage-ts-assets.sh | 199 +++++++++++++ infra/scripts/tasks/test-binary-smoke.sh | 56 ++++ .../__tests__/alias-specifiers.test.ts | 11 +- packages/devx/scripts/format-all.ts | 2 +- packages/devx/scripts/sidecar.ts | 72 +++++ packages/driver/cmd/fmtkit/main.go | 217 ++++++++++++++ packages/driver/cmd/fmtkit/main_test.go | 277 ++++++++++++++++++ .../driver/internal/orchestrator/logging.go | 115 ++++++++ .../driver/internal/orchestrator/pipeline.go | 137 +++++++++ .../internal/orchestrator/pipeline_test.go | 231 +++++++++++++++ .../driver/internal/orchestrator/summarize.go | 132 +++++++++ .../internal/tsruntime/embedded/.gitignore | 1 + .../embedded/embedded_darwin_amd64.go | 22 ++ .../embedded/embedded_darwin_arm64.go | 22 ++ .../tsruntime/embedded/embedded_dev.go | 15 + .../embedded/embedded_linux_amd64.go | 22 ++ .../embedded/embedded_linux_arm64.go | 22 ++ packages/driver/internal/tsruntime/run.go | 203 +++++++++++++ .../driver/internal/tsruntime/run_test.go | 236 +++++++++++++++ packages/driver/internal/tsruntime/support.go | 249 ++++++++++++++++ .../driver/internal/tsruntime/support_test.go | 150 ++++++++++ 28 files changed, 2596 insertions(+), 4 deletions(-) create mode 100644 .goreleaser.yaml create mode 100644 LICENSE create mode 100644 embedded.go create mode 100755 infra/scripts/release/stage-ts-assets.sh create mode 100755 infra/scripts/tasks/test-binary-smoke.sh create mode 100644 packages/devx/scripts/sidecar.ts create mode 100644 packages/driver/cmd/fmtkit/main.go create mode 100644 packages/driver/cmd/fmtkit/main_test.go create mode 100644 packages/driver/internal/orchestrator/logging.go create mode 100644 packages/driver/internal/orchestrator/pipeline.go create mode 100644 packages/driver/internal/orchestrator/pipeline_test.go create mode 100644 packages/driver/internal/orchestrator/summarize.go create mode 100644 packages/driver/internal/tsruntime/embedded/.gitignore create mode 100644 packages/driver/internal/tsruntime/embedded/embedded_darwin_amd64.go create mode 100644 packages/driver/internal/tsruntime/embedded/embedded_darwin_arm64.go create mode 100644 packages/driver/internal/tsruntime/embedded/embedded_dev.go create mode 100644 packages/driver/internal/tsruntime/embedded/embedded_linux_amd64.go create mode 100644 packages/driver/internal/tsruntime/embedded/embedded_linux_arm64.go create mode 100644 packages/driver/internal/tsruntime/run.go create mode 100644 packages/driver/internal/tsruntime/run_test.go create mode 100644 packages/driver/internal/tsruntime/support.go create mode 100644 packages/driver/internal/tsruntime/support_test.go diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 3be18fe..5e33280 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -139,3 +139,43 @@ jobs: - `${{ env.IMAGE_NAME }}:latest-node-ts` - `${{ env.IMAGE_NAME }}:latest-full` - `${{ env.IMAGE_NAME }}:latest` aliases the verified full image + + # Attaches the self-contained fmtkit binaries to the release created by + # the publish job and pushes the Homebrew cask to oullin/homebrew-fmtkit. + binaries: + needs: publish + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Verify release tag and checkout tagged commit + run: ./infra/scripts/release/verify-release-tag.sh + shell: bash + + - uses: actions/setup-node@v5 + with: + node-version-file: .nvmrc + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.14 + + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache-dependency-path: | + go.mod + go.sum + + - name: Build and publish release binaries + uses: goreleaser/goreleaser-action@v6 + with: + version: '~> v2' + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + GORELEASER_CURRENT_TAG: ${{ env.RELEASE_TAG }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5b27a48..00a512b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -144,6 +144,40 @@ jobs: path: packages/driver/coverage.out if-no-files-found: error + binary: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + name: Checkout repository + + - uses: actions/setup-node@v5 + name: Install Node + with: + node-version-file: .nvmrc + + - uses: oven-sh/setup-bun@v2 + name: Install Bun + with: + bun-version: 1.3.14 + + - uses: actions/setup-go@v6 + name: Install Go + with: + go-version-file: go.mod + cache-dependency-path: | + go.mod + go.sum + + - uses: goreleaser/goreleaser-action@v6 + name: Validate GoReleaser config + with: + version: '~> v2' + args: check + + - name: Smoke test the self-contained binary + run: ./infra/scripts/tasks/test-binary-smoke.sh + coverage: needs: test runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index f060c3e..4976188 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /fmt +/dist/ +*.bun-build /storage/bin*/ /storage/dist/ /storage/dist-test/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..53af82c --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,73 @@ +version: 2 + +project_name: fmtkit + +# Releases stage every platform; PR validation sets FMTKIT_STAGE_TARGETS=host +# to stage only the runner's platform before a --single-target build. +env: + - FMTKIT_STAGE_TARGETS={{ if index .Env "FMTKIT_STAGE_TARGETS" }}{{ .Env.FMTKIT_STAGE_TARGETS }}{{ else }}all{{ end }} + +before: + hooks: + - ./infra/scripts/release/stage-ts-assets.sh {{ .Env.FMTKIT_STAGE_TARGETS }} + +builds: + - id: fmtkit + main: ./packages/driver/cmd/fmtkit + binary: fmtkit + env: + - CGO_ENABLED=0 + goos: + - darwin + - linux + goarch: + - amd64 + - arm64 + flags: + - -trimpath + tags: + - fmtkit_sidecar + ldflags: + - -s -w -X main.version={{ .Tag }} + +archives: + - formats: + - tar.gz + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' + files: + - README.md + - LICENSE + +checksum: + name_template: checksums.txt + +changelog: + disable: true + +release: + # publish-release.yml creates the GitHub Release (with the Docker image + # notes) before this runs; goreleaser only attaches the binary assets. + mode: keep-existing + +homebrew_casks: + - name: fmtkit + repository: + owner: oullin + name: homebrew-fmtkit + branch: main + token: '{{ .Env.HOMEBREW_TAP_TOKEN }}' + directory: Casks + homepage: https://github.com/oullin/fmtkit + description: TS/Vue and Go formatting pipeline in a single self-contained binary + caveats: | + The TS/Vue toolchain (oxfmt, oxlint, oxc-parser) is embedded in the + binary and extracted to your user cache directory on first run; no + Node.js installation is required. + # The release binaries are not notarized; strip the quarantine flag so + # Gatekeeper does not block them. + hooks: + post: + install: | + if system_command("/usr/bin/xattr", args: ["-h"]).exit_status == 0 + system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/fmtkit"] + end diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1a540c6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 oullin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e98211d..90feb67 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,29 @@ - AST-based spacing rules, then `gofmt` and `goimports`, in one deterministic pipeline. - Runs `go vet ./...` automatically when invoked inside a Go module or workspace. - Three output modes: `text` for humans, `json` for scripts, `agent` for CI and AI tools. -- One CLI binary (`fmtkit-go`) or one Docker image with a thin host wrapper. +- One self-contained `fmtkit` binary (Homebrew or GitHub Releases), a Go-only CLI (`fmtkit-go`), or one Docker image with a thin host wrapper. - Engine in [`packages/formatter/engine`](packages/formatter/engine) is importable from Go. ## Install -Two ways to run it. Pick the one that fits your workflow — both produce identical output. +Three ways to run it. Pick the one that fits your workflow — all produce identical output. + +**With Homebrew** (recommended: one self-contained binary with the full TS/Vue + Go pipeline — no Node.js, no Docker): + +```bash +brew tap oullin/fmtkit +brew install --cask fmtkit +fmtkit format . +``` + +The binary embeds the TS toolchain (oxfmt, oxlint, oxc-parser and the support scripts, compiled with Bun) and extracts it to your user cache directory on first run. Homebrew casks are macOS-only; on Linux, download the same binary from GitHub Releases instead: + +```bash +curl -fsSL https://github.com/oullin/fmtkit/releases/download/v0.5.0/fmtkit_0.5.0_linux_amd64.tar.gz | tar -xz fmtkit +sudo install -m 0755 fmtkit /usr/local/bin/fmtkit +``` + +Archives are published for `darwin`/`linux` × `amd64`/`arm64` with a `checksums.txt`; swap in the [latest release](https://github.com/oullin/fmtkit/releases/latest) tag and your platform. **With Go** (good for local hacking and contributors): diff --git a/embedded.go b/embedded.go new file mode 100644 index 0000000..177c1a4 --- /dev/null +++ b/embedded.go @@ -0,0 +1,18 @@ +// Package fmtkit exposes repository-level assets embedded into the fmtkit +// binaries. It lives at the module root because go:embed cannot reference +// files above the embedding package. +package fmtkit + +import _ "embed" + +// OxfmtConfig is the bundled oxfmt configuration, used when a target project +// has no .oxfmtrc.* of its own. +// +//go:embed .oxfmtrc.json +var OxfmtConfig []byte + +// OxlintConfig is the bundled oxlint configuration, used when a target +// project has no .oxlintrc* of its own. +// +//go:embed .oxlintrc.json +var OxlintConfig []byte diff --git a/infra/scripts/release/stage-ts-assets.sh b/infra/scripts/release/stage-ts-assets.sh new file mode 100755 index 0000000..99ddf3f --- /dev/null +++ b/infra/scripts/release/stage-ts-assets.sh @@ -0,0 +1,199 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Builds the self-contained TS toolchain assets embedded into the `fmtkit` +# release binary (see packages/driver/internal/tsruntime): +# +# - fmtkit-ts-sidecar bun-compiled bundle of packages/devx/scripts/sidecar.ts +# - oxc-parser.node napi binding, loaded via NAPI_RS_NATIVE_LIBRARY_PATH +# - oxfmt.node napi binding for the oxfmt CLI +# - oxlint.node napi binding for the oxlint CLI +# +# Tool versions come from packages/devx/package.json devDependencies, the same +# source of truth the Docker images use. Requires bash, node, npm, and bun. +# +# usage: stage-ts-assets.sh + +usage() { + printf 'usage: %s \n' "${0##*/}" >&2 + printf ' supported targets: darwin_arm64 darwin_amd64 linux_arm64 linux_amd64\n' >&2 +} + +if [[ $# -eq 0 ]]; then + usage + exit 2 +fi + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +dist="${FMTKIT_TS_ASSET_DIR:-${root}/packages/driver/internal/tsruntime/embedded/dist}" + +all_targets=(darwin_arm64 darwin_amd64 linux_arm64 linux_amd64) + +bun_target() { + case "$1" in + darwin_arm64) printf 'bun-darwin-arm64' ;; + darwin_amd64) printf 'bun-darwin-x64' ;; + linux_arm64) printf 'bun-linux-arm64' ;; + linux_amd64) printf 'bun-linux-x64' ;; + *) return 1 ;; + esac +} + +binding_suffix() { + case "$1" in + darwin_arm64) printf 'darwin-arm64' ;; + darwin_amd64) printf 'darwin-x64' ;; + linux_arm64) printf 'linux-arm64-gnu' ;; + linux_amd64) printf 'linux-x64-gnu' ;; + *) return 1 ;; + esac +} + +host_target() { + local os arch + + case "$(uname -s)" in + Darwin) os='darwin' ;; + Linux) os='linux' ;; + *) + printf 'unsupported host OS: %s\n' "$(uname -s)" >&2 + return 1 + ;; + esac + + case "$(uname -m)" in + arm64 | aarch64) arch='arm64' ;; + x86_64) arch='amd64' ;; + *) + printf 'unsupported host arch: %s\n' "$(uname -m)" >&2 + return 1 + ;; + esac + + printf '%s_%s' "${os}" "${arch}" +} + +declare -a targets=() + +for arg in "$@"; do + case "${arg}" in + all) + targets=("${all_targets[@]}") + ;; + host) + targets+=("$(host_target)") + ;; + *) + if ! bun_target "${arg}" >/dev/null; then + printf 'unknown target: %s\n' "${arg}" >&2 + usage + exit 2 + fi + + targets+=("${arg}") + ;; + esac +done + +for tool in node npm bun; do + if ! command -v "${tool}" >/dev/null; then + printf 'stage-ts-assets: %s is required on PATH\n' "${tool}" >&2 + exit 1 + fi +done + +pin() { + node -e "const p = require('${root}/packages/devx/package.json'); const v = p.devDependencies['$1']; if (!v) { throw new Error('no pin for $1'); } console.log(v);" +} + +oxfmt_pin="$(pin oxfmt)" +oxlint_pin="$(pin oxlint)" +oxc_parser_pin="$(pin oxc-parser)" + +workdir="$(mktemp -d)" +trap 'rm -rf "${workdir}"' EXIT + +# Mirror the layout sidecar.ts expects in the repo: the scripts next to their +# package.json (for the #devx imports map) with node_modules one level up. +mkdir -p "${workdir}/scripts" + +for script in "${root}"/packages/devx/scripts/*.ts; do + case "${script##*/}" in + *.test.ts) continue ;; + esac + + cp "${script}" "${workdir}/scripts/" +done + +cp "${root}/packages/devx/scripts/package.json" "${workdir}/scripts/package.json" + +( + cd "${workdir}" + + npm install --no-save --no-audit --no-fund \ + "oxfmt@${oxfmt_pin}" \ + "oxlint@${oxlint_pin}" \ + "oxc-parser@${oxc_parser_pin}" >/dev/null +) + +# The napi bindings stay external: every target loads them from files staged +# next to the sidecar through NAPI_RS_NATIVE_LIBRARY_PATH, which keeps the JS +# bundle platform-independent. oxfmt's optional prettier plugins are external +# too; they are not installed by any fmtkit distribution channel. +bun_externals=( + --external '@oxc-parser/binding-*' + --external '@oxfmt/binding-*' + --external '@oxlint/binding-*' + --external '@prettier/*' + --external 'prettier-plugin-*' + --external '@shopify/prettier-plugin-liquid' + --external '@zackad/prettier-plugin-twig' +) + +fetch_binding() { + local package="$1" + local file="$2" + local destination="$3" + local pack_dir + + pack_dir="$(mktemp -d "${workdir}/pack.XXXXXX")" + + ( + cd "${pack_dir}" + + npm pack --silent "${package}" >/dev/null + tar -xzf ./*.tgz + ) + + install -m 0644 "${pack_dir}/package/${file}" "${destination}" + rm -rf "${pack_dir}" +} + +for target in "${targets[@]}"; do + suffix="$(binding_suffix "${target}")" + out="${dist}/${target}" + + printf '==> staging TS assets for %s\n' "${target}" >&2 + + rm -rf "${out}" + mkdir -p "${out}" + + # Run bun from the throwaway workdir: bun drops *.bun-build scratch files + # into the invoking directory, and a dirty repo would fail the release's + # git state check. + ( + cd "${workdir}" + + bun build --compile \ + --target "$(bun_target "${target}")" \ + --outfile "${out}/fmtkit-ts-sidecar" \ + "${bun_externals[@]}" \ + "${workdir}/scripts/sidecar.ts" >/dev/null + ) + + fetch_binding "@oxc-parser/binding-${suffix}@${oxc_parser_pin}" "parser.${suffix}.node" "${out}/oxc-parser.node" + fetch_binding "@oxfmt/binding-${suffix}@${oxfmt_pin}" "oxfmt.${suffix}.node" "${out}/oxfmt.node" + fetch_binding "@oxlint/binding-${suffix}@${oxlint_pin}" "oxlint.${suffix}.node" "${out}/oxlint.node" + + printf '==> staged %s\n' "${out}" >&2 +done diff --git a/infra/scripts/tasks/test-binary-smoke.sh b/infra/scripts/tasks/test-binary-smoke.sh new file mode 100755 index 0000000..f761179 --- /dev/null +++ b/infra/scripts/tasks/test-binary-smoke.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Builds the self-contained fmtkit binary for the host platform and exercises +# the full pipeline against a scratch project: the bun-compiled sidecar, the +# oxc-parser/oxfmt/oxlint napi bindings, and the in-process Go formatter. +# Requires bash, git, go, node, npm, and bun. + +repo_root="$(cd "$(dirname "$0")/../../.." && pwd)" + +"${repo_root}/infra/scripts/release/stage-ts-assets.sh" host + +tmp_root="$(mktemp -d)" + +cleanup() { + rm -rf "$tmp_root" +} + +trap cleanup EXIT + +bin="${tmp_root}/fmtkit" + +( + cd "$repo_root" + + go build -tags fmtkit_sidecar -o "$bin" ./packages/driver/cmd/fmtkit +) + +fixture="${tmp_root}/fixture" + +mkdir -p "$fixture" +cd "$fixture" + +git init --quiet . + +printf 'const a = { x:1 }\nexport default a\n' > app.ts +printf 'package p\n\nfunc f() {\n\tdefer println("d")\n\treturn\n}\n' > app.go +printf 'module fixture\n\ngo 1.26.4\n' > go.mod + +XDG_CACHE_HOME="${tmp_root}/cache" "$bin" version +XDG_CACHE_HOME="${tmp_root}/cache" "$bin" format . + +expected_ts=$'const a = { x: 1 };\n\nexport default a;\n' +expected_go=$'package p\n\nfunc f() {\n\tdefer println("d")\n\n\treturn\n}\n' + +if ! diff <(printf '%s' "$expected_ts") app.ts; then + printf 'app.ts was not formatted as expected\n' >&2 + exit 1 +fi + +if ! diff <(printf '%s' "$expected_go") app.go; then + printf 'app.go was not formatted as expected\n' >&2 + exit 1 +fi + +printf 'binary smoke test passed\n' diff --git a/packages/devx/scripts/__tests__/alias-specifiers.test.ts b/packages/devx/scripts/__tests__/alias-specifiers.test.ts index 0217813..ae5e081 100644 --- a/packages/devx/scripts/__tests__/alias-specifiers.test.ts +++ b/packages/devx/scripts/__tests__/alias-specifiers.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import { readdir, readFile } from 'node:fs/promises'; -import { dirname, join } from 'node:path'; +import { basename, dirname, join } from 'node:path'; import { test } from 'node:test'; import { fileURLToPath } from 'node:url'; import { parseSync } from 'oxc-parser'; @@ -16,6 +16,11 @@ type Node = { const sourceExtensions = new Set(['.cjs', '.js', '.jsx', '.mjs', '.ts', '.tsx']); +// sidecar.ts is a bundler-only entry point that must reach the oxfmt/oxlint +// CLI internals in node_modules; oxlint's exports map blocks the bare +// specifier the alias map would need, so its relative imports are exempt. +const exemptFiles = new Set(['sidecar.ts']); + const scriptsDir = dirname(fileURLToPath(import.meta.resolve('#devx/ast'))); function isRelativeSpecifier(value: string): boolean { @@ -123,6 +128,10 @@ test('script module specifiers use aliases instead of relative paths', async () const violations: string[] = []; for (const file of files) { + if (exemptFiles.has(basename(file))) { + continue; + } + const source = await readFile(file, 'utf8'); const specifiers = collectModuleSpecifiers(file, source); diff --git a/packages/devx/scripts/format-all.ts b/packages/devx/scripts/format-all.ts index 2bcc968..cf60532 100644 --- a/packages/devx/scripts/format-all.ts +++ b/packages/devx/scripts/format-all.ts @@ -180,7 +180,7 @@ async function runValidate(files: string[]): Promise { console.log(`[validate-syntax] checked ${files.length} file(s) in ${process.cwd()}`); } -async function main(): Promise { +export async function main(): Promise { const options = parseArgs(process.argv.slice(2)); const formatTargets = [...new Set(options.formatFiles.filter(isTargetFile))]; diff --git a/packages/devx/scripts/sidecar.ts b/packages/devx/scripts/sidecar.ts new file mode 100644 index 0000000..c8e4b20 --- /dev/null +++ b/packages/devx/scripts/sidecar.ts @@ -0,0 +1,72 @@ +/** + * Entry point for the self-contained TS toolchain sidecar bundled into the + * `fmtkit` release binary via `bun build --compile` (see + * infra/scripts/release/stage-ts-assets.sh). + * + * One executable multiplexes the three Node-based tools so the Bun runtime is + * only shipped once. The napi bindings (oxc-parser, oxfmt, oxlint) are NOT + * bundled; they are extracted next to this executable and loaded through the + * napi-rs NAPI_RS_NATIVE_LIBRARY_PATH override. + */ +import { dirname, join } from 'node:path'; + +const modes = ['pipeline', 'oxfmt', 'oxlint'] as const; + +type Mode = (typeof modes)[number]; + +const here = dirname(process.execPath); + +const bindings: Record = { + pipeline: join(here, 'oxc-parser.node'), + oxfmt: join(here, 'oxfmt.node'), + oxlint: join(here, 'oxlint.node'), +}; + +let mode: Mode | undefined; + +if (modes.includes(process.argv[2] as Mode)) { + mode = process.argv[2] as Mode; + + process.argv.splice(2, 1); +} else if (modes.includes(process.env.FMTKIT_SIDECAR_MODE as Mode)) { + // The pipeline spawns this executable as its `--oxfmt-bin` with oxfmt's own + // argv, so the mode has to travel through the environment instead. + mode = process.env.FMTKIT_SIDECAR_MODE as Mode; +} + +switch (mode) { + case 'pipeline': { + process.env.NAPI_RS_NATIVE_LIBRARY_PATH = bindings.pipeline; + process.env.FMTKIT_SIDECAR_MODE = 'oxfmt'; + + // Every bundled module shares this executable's import.meta.url, which + // matches argv[1] and would fire each script's run-as-main guard on + // import; blank argv[1] so only the explicit main() call below runs. + process.argv[1] = ''; + + const { main } = await import('#devx/format-all'); + + await main(); + + break; + } + + case 'oxfmt': + process.env.NAPI_RS_NATIVE_LIBRARY_PATH = bindings.oxfmt; + + await import('../node_modules/oxfmt/dist/cli.js'); + + break; + + case 'oxlint': + process.env.NAPI_RS_NATIVE_LIBRARY_PATH = bindings.oxlint; + + // @ts-expect-error -- the oxlint CLI entry ships without declarations. + await import('../node_modules/oxlint/dist/cli.js'); + + break; + + default: + console.error('usage: fmtkit-ts-sidecar [args...]'); + process.exit(2); +} diff --git a/packages/driver/cmd/fmtkit/main.go b/packages/driver/cmd/fmtkit/main.go new file mode 100644 index 0000000..6cc5664 --- /dev/null +++ b/packages/driver/cmd/fmtkit/main.go @@ -0,0 +1,217 @@ +// Command fmtkit is the self-contained fmtkit binary distributed through +// GitHub Releases and Homebrew: the pipeline orchestration that +// infra/bin/fmtkit provides in the container images, fused with the Go +// formatter CLI and the embedded TS toolchain (see internal/tsruntime). +package main + +import ( + "errors" + "fmt" + "io" + "os" + "os/exec" + + "github.com/oullin/fmtkit/packages/driver/internal/cli" + "github.com/oullin/fmtkit/packages/driver/internal/orchestrator" + "github.com/oullin/fmtkit/packages/driver/internal/tsruntime" +) + +var version = "dev" + +func main() { + os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) +} + +func run(args []string, stdout, stderr io.Writer) int { + if len(args) == 0 { + printUsage(stderr) + + return 2 + } + + mode := args[0] + rest := args[1:] + + switch mode { + case "format": + quiet, paths := splitQuiet(rest) + + return runPipeline(paths, quiet, stderr) + case "format-all": + quiet, extra := splitQuiet(rest) + + if len(extra) != 0 { + printUsage(stderr) + + return 2 + } + + return runPipeline([]string{"."}, quiet, stderr) + case "ts": + return runTS(rest, stdout, stderr) + case "lint": + return runLint(rest, stdout, stderr) + case "go": + return runGo(rest, stdout, stderr) + case "check": + return cli. + NewRunner(stdout, stderr). + Run(cli.CheckMode, rest) + case "version", "--version", "-version": + _, _ = fmt.Fprintf(stdout, "fmtkit %s\n", version) + + return 0 + case "help", "--help", "-h": + printUsage(stderr) + + return 0 + default: + _, _ = fmt.Fprintf(stderr, "unknown subcommand - {%q}\n\n", mode) + + printUsage(stderr) + + return 2 + } +} + +func runPipeline(paths []string, quiet bool, stderr io.Writer) int { + pipeline := orchestrator.Pipeline{ + Tools: orchestrator.Tools{ + TS: func(scopes []string, output io.Writer) error { + support, err := tsruntime.Resolve(version) + + if err != nil { + return err + } + + return support.RunPipeline(tsruntime.RunOptions{Scopes: scopes, Stdout: output, Stderr: output}) + }, + Lint: func(scopes []string, output io.Writer) error { + support, err := tsruntime.Resolve(version) + + if err != nil { + return err + } + + return support.RunLint(tsruntime.RunOptions{Scopes: scopes, Stdout: output, Stderr: output}) + }, + Go: func(args []string, output io.Writer) int { + return cli. + NewRunner(output, output). + Run(cli.FormatMode, args[1:]) + }, + }, + Quiet: quiet, + Stderr: stderr, + } + + return pipeline.RunFormat(paths) +} + +func runTS(paths []string, stdout, stderr io.Writer) int { + support, err := tsruntime.Resolve(version) + + if err != nil { + return reportError(err, stderr) + } + + return reportError(support.RunPipeline(tsruntime.RunOptions{Scopes: paths, Stdout: stdout, Stderr: stderr}), stderr) +} + +func runLint(paths []string, stdout, stderr io.Writer) int { + support, err := tsruntime.Resolve(version) + + if err != nil { + return reportError(err, stderr) + } + + return reportError(support.RunLint(tsruntime.RunOptions{Scopes: paths, Stdout: stdout, Stderr: stderr}), stderr) +} + +// runGo mirrors the fmtkit-go command surface so `fmtkit go ...` behaves like +// the container's Go formatter CLI. +func runGo(args []string, stdout, stderr io.Writer) int { + if len(args) == 0 { + printGoUsage(stderr) + + return 2 + } + + switch args[0] { + case "check": + return cli. + NewRunner(stdout, stderr). + Run(cli.CheckMode, args[1:]) + case "format": + return cli. + NewRunner(stdout, stderr). + Run(cli.FormatMode, args[1:]) + case "sources": + return cli.RunSources(args[1:], stdout, stderr) + case "version", "--version", "-version": + _, _ = fmt.Fprintf(stdout, "fmtkit %s\n", version) + + return 0 + case "help", "--help", "-h": + printGoUsage(stderr) + + return 0 + default: + _, _ = fmt.Fprintf(stderr, "unknown subcommand - {%q}\n\n", args[0]) + + printGoUsage(stderr) + + return 2 + } +} + +func splitQuiet(args []string) (bool, []string) { + quiet := false + + var rest []string + + for _, arg := range args { + if arg == "--quiet" || arg == "-q" { + quiet = true + + continue + } + + rest = append(rest, arg) + } + + return quiet, rest +} + +func reportError(err error, stderr io.Writer) int { + if err == nil { + return 0 + } + + var exit *exec.ExitError + + if errors.As(err, &exit) { + return exit.ExitCode() + } + + _, _ = fmt.Fprintf(stderr, "fmtkit: %v\n", err) + + return 1 +} + +func printUsage(w io.Writer) { + _, _ = fmt.Fprintf(w, "usage: fmtkit [args...]\n") + _, _ = fmt.Fprintf(w, " format [--quiet] [paths...] run TS/Vue support + lint, then Go formatting\n") + _, _ = fmt.Fprintf(w, " format-all [--quiet] run the full formatter pipeline against .\n") + _, _ = fmt.Fprintf(w, " go run the Go formatter CLI\n") + _, _ = fmt.Fprintf(w, " ts [paths...] run TS/Vue formatting support and oxfmt\n") + _, _ = fmt.Fprintf(w, " lint [paths...] lint TS/Vue files with oxlint\n") + _, _ = fmt.Fprintf(w, " check [args...] run the Go formatter in check mode\n") + _, _ = fmt.Fprintf(w, " version print the fmtkit version\n") +} + +func printGoUsage(w io.Writer) { + _, _ = fmt.Fprintf(w, "fmtkit go check [--host-path /absolute/host/path] [paths...]\n\n") + _, _ = fmt.Fprintf(w, "fmtkit go format [--host-path /absolute/host/path] [paths...]\n\n") + _, _ = fmt.Fprintf(w, "fmtkit go sources [--include-declarations] [paths...]\n\n") +} diff --git a/packages/driver/cmd/fmtkit/main_test.go b/packages/driver/cmd/fmtkit/main_test.go new file mode 100644 index 0000000..8062ee9 --- /dev/null +++ b/packages/driver/cmd/fmtkit/main_test.go @@ -0,0 +1,277 @@ +package main + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/oullin/fmtkit/packages/driver/testutil" +) + +func runCLI(t *testing.T, workdir string, args ...string) (int, string, string) { + t.Helper() + + oldwd, err := os.Getwd() + + if err != nil { + t.Fatalf("getwd: %v", err) + } + + if err := os.Chdir(workdir); err != nil { + t.Fatalf("chdir: %v", err) + } + + defer func() { + _ = os.Chdir(oldwd) + }() + + var stdout strings.Builder + + var stderr strings.Builder + exitCode := run(args, &stdout, &stderr) + + return exitCode, stdout.String(), stderr.String() +} + +// stubSupportDir builds a FMTKIT_SUPPORT_DIR whose sidecar logs its argv and +// prints canned tool output, the Go analog of the entrypoint test stubs. +func stubSupportDir(t *testing.T) (string, string) { + t.Helper() + + dir := t.TempDir() + logFile := filepath.Join(dir, "invocations.log") + + script := "#!/usr/bin/env bash\n" + + "set -euo pipefail\n" + + "printf '%s\\n' \"$*\" >> " + logFile + "\n" + + "case \"${1:-}\" in\n" + + "pipeline)\n" + + "\tprintf '[blank-lines] processed 3 file(s) in /work, 0 changed\\n'\n" + + "\tprintf 'Finished in 10ms on 3 files using 8 threads.\\n'\n" + + "\tprintf '[fluent-chains] processed 3 file(s) in /work, 1 changed\\n'\n" + + "\t;;\n" + + "oxlint)\n" + + "\tprintf 'Found 0 warnings and 0 errors.\\n'\n" + + "\t;;\n" + + "esac\n" + + if err := os.WriteFile(filepath.Join(dir, "fmtkit-ts-sidecar"), []byte(script), 0o755); err != nil { + t.Fatalf("write stub sidecar: %v", err) + } + + return dir, logFile +} + +func gitWorkdir(t *testing.T) string { + t.Helper() + + dir := t.TempDir() + + cmd := exec.Command("git", "init", "--quiet") + cmd.Dir = dir + + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("git init: %v\n%s", err, out) + } + + if err := os.WriteFile(filepath.Join(dir, "app.ts"), []byte("export const a = 1;\n"), 0o644); err != nil { + t.Fatalf("write app.ts: %v", err) + } + + testutil.WriteGoFile(t, filepath.Join(dir, "sample.go"), `package sample + +func run() { + defer println("done") + return +} +`) + + return dir +} + +func TestRunWithoutArgsPrintsUsage(t *testing.T) { + exitCode, _, stderr := runCLI(t, t.TempDir()) + + if exitCode != 2 { + t.Fatalf("expected exit code 2, got %d", exitCode) + } + + if !strings.Contains(stderr, "usage: fmtkit [args...]") { + t.Fatalf("unexpected usage output:\n%s", stderr) + } +} + +func TestRunUnknownSubcommandFails(t *testing.T) { + exitCode, _, stderr := runCLI(t, t.TempDir(), "unknown") + + if exitCode != 2 { + t.Fatalf("expected exit code 2, got %d", exitCode) + } + + if !strings.Contains(stderr, "unknown subcommand") { + t.Fatalf("unexpected stderr:\n%s", stderr) + } +} + +func TestRunVersion(t *testing.T) { + exitCode, stdout, _ := runCLI(t, t.TempDir(), "version") + + if exitCode != 0 { + t.Fatalf("expected exit code 0, got %d", exitCode) + } + + if !strings.Contains(stdout, "fmtkit dev") { + t.Fatalf("unexpected stdout:\n%s", stdout) + } +} + +func TestFormatRunsFullPipeline(t *testing.T) { + support, logFile := stubSupportDir(t) + + t.Setenv("FMTKIT_SUPPORT_DIR", support) + + workdir := gitWorkdir(t) + + exitCode, _, stderr := runCLI(t, workdir, "format", ".") + + if exitCode != 0 { + t.Fatalf("expected exit code 0, got %d\n%s", exitCode, stderr) + } + + log, err := os.ReadFile(logFile) + + if err != nil { + t.Fatalf("read invocation log: %v", err) + } + + invocations := strings.Split(strings.TrimSpace(string(log)), "\n") + + if len(invocations) != 2 { + t.Fatalf("expected 2 sidecar invocations, got %q", invocations) + } + + if !strings.HasPrefix(invocations[0], "pipeline ") { + t.Fatalf("first invocation = %q, want pipeline", invocations[0]) + } + + if !strings.HasPrefix(invocations[1], "oxlint ") { + t.Fatalf("second invocation = %q, want oxlint", invocations[1]) + } + + for _, needle := range []string{ + "==> Formatting target(s)", + "==> Running TS/Vue formatting", + "blank-lines processed 3 file(s) in /work, 0 changed", + "==> Running TS/Vue lint", + "oxlint Found 0 warnings and 0 errors.", + "==> Running Go formatting", + "==> Formatting complete", + } { + if !strings.Contains(stderr, needle) { + t.Fatalf("stderr missing %q:\n%s", needle, stderr) + } + } +} + +func TestFormatAllRejectsExtraArgs(t *testing.T) { + exitCode, _, stderr := runCLI(t, t.TempDir(), "format-all", "extra") + + if exitCode != 2 { + t.Fatalf("expected exit code 2, got %d", exitCode) + } + + if !strings.Contains(stderr, "usage: fmtkit") { + t.Fatalf("unexpected stderr:\n%s", stderr) + } +} + +func TestTSRunsSidecarOnly(t *testing.T) { + support, logFile := stubSupportDir(t) + + t.Setenv("FMTKIT_SUPPORT_DIR", support) + + workdir := gitWorkdir(t) + + exitCode, stdout, stderr := runCLI(t, workdir, "ts", ".") + + if exitCode != 0 { + t.Fatalf("expected exit code 0, got %d\n%s", exitCode, stderr) + } + + if strings.Contains(stderr, "==> Running Go formatting") { + t.Fatalf("ts mode ran the pipeline:\n%s", stderr) + } + + if !strings.Contains(stdout, "[blank-lines] processed") { + t.Fatalf("expected raw sidecar output on stdout:\n%s", stdout) + } + + log, err := os.ReadFile(logFile) + + if err != nil { + t.Fatalf("read invocation log: %v", err) + } + + if got := strings.TrimSpace(string(log)); !strings.HasPrefix(got, "pipeline ") || strings.Contains(got, "\n") { + t.Fatalf("expected a single pipeline invocation, got %q", got) + } +} + +func TestGoDelegatesToFormatterCLI(t *testing.T) { + workdir := gitWorkdir(t) + + exitCode, stdout, stderr := runCLI(t, workdir, "go", "format", ".") + + if exitCode != 0 { + t.Fatalf("expected exit code 0, got %d\nstdout:%s\nstderr:%s", exitCode, stdout, stderr) + } + + if !strings.Contains(stdout, "Formatter") { + t.Fatalf("expected formatter output:\n%s", stdout) + } +} + +func TestCheckDelegatesToFormatterCLI(t *testing.T) { + dir := t.TempDir() + + testutil.WriteGoFile(t, filepath.Join(dir, "sample.go"), `package sample + +func run() { + if true { + println("ok") + } + println("next") +} +`) + + exitCode, stdout, _ := runCLI(t, dir, "check", dir) + + if exitCode != 1 { + t.Fatalf("expected exit code 1, got %d", exitCode) + } + + if !strings.Contains(stdout, "Result: fail") { + t.Fatalf("unexpected stdout:\n%s", stdout) + } +} + +func TestFormatFailsWithoutToolchain(t *testing.T) { + workdir := gitWorkdir(t) + + // No FMTKIT_SUPPORT_DIR and dev builds carry no embedded assets. + exitCode, _, stderr := runCLI(t, workdir, "format", ".") + + if exitCode != 1 { + t.Fatalf("expected exit code 1, got %d\n%s", exitCode, stderr) + } + + if !strings.Contains(stderr, "!! Running TS/Vue formatting failed") { + t.Fatalf("stderr missing failure banner:\n%s", stderr) + } + + if !strings.Contains(stderr, "no TS toolchain") { + t.Fatalf("stderr missing toolchain guidance:\n%s", stderr) + } +} diff --git a/packages/driver/internal/orchestrator/logging.go b/packages/driver/internal/orchestrator/logging.go new file mode 100644 index 0000000..37e4d3f --- /dev/null +++ b/packages/driver/internal/orchestrator/logging.go @@ -0,0 +1,115 @@ +// Package orchestrator drives the full fmtkit formatting pipeline (TS/Vue +// formatting, TS/Vue lint, Go formatting) with the sectioned, colorized +// progress output the infra/bin/fmtkit entrypoint established. Unlike the +// bash entrypoint, tool output streams live (indented under each section) +// and is followed by the condensed summary lines. +package orchestrator + +import ( + "fmt" + "io" + "os" + "strings" + + "github.com/mattn/go-isatty" +) + +type logger struct { + w io.Writer + quiet bool + + bold string + dim string + cyan string + green string + red string + reset string +} + +// stream returns a writer that renders tool output live, dimmed and indented +// under the current section. Callers must Close it to flush a trailing +// partial line. + +type indentWriter struct { + logger *logger + partial strings.Builder +} + +func newLogger(w io.Writer, quiet bool) *logger { + l := &logger{w: w, quiet: quiet} + + if colorEnabled(w) { + l.bold = "\033[1m" + l.dim = "\033[2m" + l.cyan = "\033[36m" + l.green = "\033[32m" + l.red = "\033[31m" + l.reset = "\033[0m" + } + + return l +} + +func colorEnabled(w io.Writer) bool { + if os.Getenv("FORCE_COLOR") != "" { + return true + } + + if os.Getenv("NO_COLOR") != "" { + return false + } + + file, ok := w.(*os.File) + + return ok && isatty.IsTerminal(file.Fd()) +} + +func (l *logger) section(msg string) { + _, _ = fmt.Fprintf(l.w, "\n%s==>%s %s%s%s\n", l.cyan, l.reset, l.bold, msg, l.reset) +} + +func (l *logger) detail(label, value string) { + _, _ = fmt.Fprintf(l.w, " %s%-12s%s %s\n", l.dim, label, l.reset, value) +} + +func (l *logger) successDetail(label, value string) { + _, _ = fmt.Fprintf(l.w, " %s%-12s%s %s%s%s\n", l.green, label, l.reset, l.green, value, l.reset) +} + +func (l *logger) failure(msg string) { + _, _ = fmt.Fprintf(l.w, "\n%s!!%s %s%s%s\n", l.red, l.reset, l.bold, msg, l.reset) +} + +func (l *logger) stream() io.WriteCloser { + return &indentWriter{logger: l} +} + +func (w *indentWriter) Write(p []byte) (int, error) { + for _, b := range p { + if b != '\n' { + w.partial.WriteByte(b) + + continue + } + + w.flushLine() + } + + return len(p), nil +} + +func (w *indentWriter) Close() error { + if w.partial.Len() > 0 { + w.flushLine() + } + + return nil +} + +func (w *indentWriter) flushLine() { + l := w.logger + + _, _ = fmt.Fprintf(l.w, " %s%s%s\n", l.dim, w.partial.String(), l.reset) + + w.partial.Reset() +} diff --git a/packages/driver/internal/orchestrator/pipeline.go b/packages/driver/internal/orchestrator/pipeline.go new file mode 100644 index 0000000..116f106 --- /dev/null +++ b/packages/driver/internal/orchestrator/pipeline.go @@ -0,0 +1,137 @@ +package orchestrator + +import ( + "bytes" + "errors" + "io" + "os/exec" + "strings" +) + +// Tools carries the three pipeline steps. The TS steps return an error whose +// exec.ExitError code propagates; the Go step reports its exit code directly. +type Tools struct { + TS func(scopes []string, output io.Writer) error + Lint func(scopes []string, output io.Writer) error + Go func(args []string, output io.Writer) int +} + +// Pipeline renders sectioned progress on Stderr while running the steps. +type Pipeline struct { + Tools Tools + + // Quiet restores the entrypoint's summary-only output; tool logs then + // only appear when a step fails. + Quiet bool + + Stderr io.Writer +} + +// RunFormat runs TS/Vue formatting, TS/Vue lint, and Go formatting against +// the given paths, the Go port of run_format_pipeline in infra/bin/fmtkit. +func (p Pipeline) RunFormat(paths []string) int { + if len(paths) == 0 { + paths = []string{"."} + } + + log := newLogger(p.Stderr, p.Quiet) + + log.section("Formatting target(s)") + log.detail("paths", strings.Join(paths, " ")) + + steps := []struct { + label string + summarize func(string, *logger) + run func(output io.Writer) int + }{ + { + label: "Running TS/Vue formatting", + summarize: summarizeTSFormat, + run: func(output io.Writer) int { + return exitCode(p.Tools.TS(paths, output), output) + }, + }, + { + label: "Running TS/Vue lint", + summarize: summarizeTSLint, + run: func(output io.Writer) int { + return exitCode(p.Tools.Lint(paths, output), output) + }, + }, + { + label: "Running Go formatting", + summarize: summarizeGoFormat, + run: func(output io.Writer) int { + return p.Tools.Go(append([]string{"format"}, paths...), output) + }, + }, + } + + for _, step := range steps { + if code := p.runStep(log, step.label, step.summarize, step.run); code != 0 { + return code + } + } + + log.section("Formatting complete") + log.successDetail("status", "done") + + return 0 +} + +// runStep captures a step's combined output, streaming it live unless quiet, +// and prints either its summary details or (on failure) the captured log. +func (p Pipeline) runStep(log *logger, label string, summarize func(string, *logger), run func(io.Writer) int) int { + log.section(label) + + var captured bytes.Buffer + + output := io.Writer(&captured) + + var live io.WriteCloser + + if !p.Quiet { + live = log.stream() + output = io.MultiWriter(&captured, live) + } + + code := run(output) + + if live != nil { + _ = live.Close() + } + + if code != 0 { + log.failure(label + " failed") + + if p.Quiet { + _, _ = io.Copy(p.Stderr, bytes.NewReader(captured.Bytes())) + } + + return code + } + + summarize(captured.String(), log) + + return 0 +} + +// exitCode maps a step error to its exit code. Failures that never produced +// tool output (a missing sidecar, an unreadable working tree) surface their +// message through the step's output writer so they are visible both live and +// in the failure dump. +func exitCode(err error, output io.Writer) int { + if err == nil { + return 0 + } + + var exit *exec.ExitError + + if errors.As(err, &exit) { + return exit.ExitCode() + } + + _, _ = io.WriteString(output, err.Error()+"\n") + + return 1 +} diff --git a/packages/driver/internal/orchestrator/pipeline_test.go b/packages/driver/internal/orchestrator/pipeline_test.go new file mode 100644 index 0000000..52b4601 --- /dev/null +++ b/packages/driver/internal/orchestrator/pipeline_test.go @@ -0,0 +1,231 @@ +package orchestrator + +import ( + "bytes" + "errors" + "fmt" + "io" + "strings" + "testing" +) + +// The stub outputs mirror infra/scripts/tasks/test-fmtkit-entrypoint.sh so +// the Go orchestrator preserves the entrypoint's summary contract. + +type invocation struct { + tool string + args []string +} + +const ( + stubTSOutput = "[blank-lines] processed 3 file(s) in /work, 0 changed\n" + + "Finished in 10ms on 3 files using 8 threads.\n" + + "[fluent-chains] processed 3 file(s) in /work, 1 changed\n" + + stubLintOutput = "Found 0 warnings and 0 errors.\n" + + stubGoOutput = "\nFormatter\n\n" + + " Formatted 2 file(s).\n\n" + + " Result: pass. 0 changed, 0 violation(s), 0 error(s).\n\n" + + "Vet\n\n" + + " go vet ./... passed.\n\n" + + " Result: pass. 0 error(s).\n" +) + +func stubTools(log *[]invocation, tsErr, lintErr error, goCode int) Tools { + return Tools{ + TS: func(scopes []string, output io.Writer) error { + *log = append(*log, invocation{"ts", scopes}) + + _, _ = io.WriteString(output, stubTSOutput) + + return tsErr + }, + Lint: func(scopes []string, output io.Writer) error { + *log = append(*log, invocation{"lint", scopes}) + + _, _ = io.WriteString(output, stubLintOutput) + + return lintErr + }, + Go: func(args []string, output io.Writer) int { + *log = append(*log, invocation{"go", args}) + + _, _ = io.WriteString(output, stubGoOutput) + + return goCode + }, + } +} + +func TestRunFormatRunsStepsInOrder(t *testing.T) { + var log []invocation + + var stderr bytes.Buffer + + pipeline := Pipeline{ + Tools: stubTools(&log, nil, nil, 0), + Stderr: &stderr, + } + + if code := pipeline.RunFormat([]string{"."}); code != 0 { + t.Fatalf("RunFormat = %d, want 0\n%s", code, stderr.String()) + } + + want := []invocation{ + {"ts", []string{"."}}, + {"lint", []string{"."}}, + {"go", []string{"format", "."}}, + } + + if fmt.Sprint(log) != fmt.Sprint(want) { + t.Fatalf("invocations = %v, want %v", log, want) + } + + for _, needle := range []string{ + "==> Formatting target(s)", + "paths .", + "==> Running TS/Vue formatting", + "blank-lines processed 3 file(s) in /work, 0 changed", + "oxfmt Finished in 10ms on 3 files using 8 threads.", + "fluent processed 3 file(s) in /work, 1 changed", + "==> Running TS/Vue lint", + "oxlint Found 0 warnings and 0 errors.", + "==> Running Go formatting", + "fmtkit Formatted 2 file(s).", + "result pass. 0 changed, 0 violation(s), 0 error(s).", + "vet go vet ./... passed.", + "vet result pass. 0 error(s).", + "==> Formatting complete", + "status", + "done", + } { + if !strings.Contains(stderr.String(), needle) { + t.Fatalf("stderr missing %q:\n%s", needle, stderr.String()) + } + } +} + +func TestRunFormatStreamsToolOutputLive(t *testing.T) { + var log []invocation + + var stderr bytes.Buffer + + pipeline := Pipeline{ + Tools: stubTools(&log, nil, nil, 0), + Stderr: &stderr, + } + + if code := pipeline.RunFormat(nil); code != 0 { + t.Fatalf("RunFormat = %d, want 0", code) + } + + // The raw tool line appears indented (live stream) in addition to the + // condensed summary line. + if !strings.Contains(stderr.String(), " [blank-lines] processed 3 file(s) in /work, 0 changed") { + t.Fatalf("stderr missing live-streamed tool output:\n%s", stderr.String()) + } +} + +func TestRunFormatQuietHidesToolOutput(t *testing.T) { + var log []invocation + + var stderr bytes.Buffer + + pipeline := Pipeline{ + Tools: stubTools(&log, nil, nil, 0), + Quiet: true, + Stderr: &stderr, + } + + if code := pipeline.RunFormat(nil); code != 0 { + t.Fatalf("RunFormat = %d, want 0", code) + } + + if strings.Contains(stderr.String(), " [blank-lines]") { + t.Fatalf("quiet mode streamed tool output:\n%s", stderr.String()) + } + + if !strings.Contains(stderr.String(), "blank-lines processed 3 file(s) in /work, 0 changed") { + t.Fatalf("quiet mode lost summary:\n%s", stderr.String()) + } +} + +func TestRunFormatShortCircuitsOnTSFailure(t *testing.T) { + var log []invocation + + var stderr bytes.Buffer + + pipeline := Pipeline{ + Tools: stubTools(&log, errors.New("sidecar exploded"), nil, 0), + Stderr: &stderr, + } + + if code := pipeline.RunFormat(nil); code != 1 { + t.Fatalf("RunFormat = %d, want 1", code) + } + + if len(log) != 1 || log[0].tool != "ts" { + t.Fatalf("invocations = %v, want only ts", log) + } + + if !strings.Contains(stderr.String(), "!! Running TS/Vue formatting failed") { + t.Fatalf("stderr missing failure banner:\n%s", stderr.String()) + } + + if !strings.Contains(stderr.String(), "sidecar exploded") { + t.Fatalf("stderr missing error message:\n%s", stderr.String()) + } +} + +func TestRunFormatQuietDumpsLogOnFailure(t *testing.T) { + var log []invocation + + var stderr bytes.Buffer + + pipeline := Pipeline{ + Tools: stubTools(&log, nil, nil, 3), + Quiet: true, + Stderr: &stderr, + } + + if code := pipeline.RunFormat(nil); code != 3 { + t.Fatalf("RunFormat = %d, want 3", code) + } + + if !strings.Contains(stderr.String(), "Formatted 2 file(s).") { + t.Fatalf("quiet failure did not dump captured log:\n%s", stderr.String()) + } +} + +func TestSummarizeTSLintFallbacks(t *testing.T) { + var out bytes.Buffer + + log := newLogger(&out, true) + + summarizeTSLint("[lint] no TS/Vue files to lint.\n", log) + + if !strings.Contains(out.String(), "oxlint no TS/Vue files to lint.") { + t.Fatalf("missing skip summary: %q", out.String()) + } + + out.Reset() + + summarizeTSLint("nothing interesting\n", log) + + if !strings.Contains(out.String(), "oxlint no issues found") { + t.Fatalf("missing fallback summary: %q", out.String()) + } +} + +func TestSummarizeTSFormatCountsMissing(t *testing.T) { + var out bytes.Buffer + + log := newLogger(&out, true) + + summarizeTSFormat("[sources] path not found, skipping: /work/a\n[sources] path not found, skipping: /work/b\n", log) + + if !strings.Contains(out.String(), "skipped 2 missing tracked file(s)") { + t.Fatalf("missing skipped summary: %q", out.String()) + } +} diff --git a/packages/driver/internal/orchestrator/summarize.go b/packages/driver/internal/orchestrator/summarize.go new file mode 100644 index 0000000..24314e1 --- /dev/null +++ b/packages/driver/internal/orchestrator/summarize.go @@ -0,0 +1,132 @@ +package orchestrator + +import ( + "fmt" + "regexp" + "strings" +) + +// The summarizers are ports of the summarize_* helpers in infra/bin/fmtkit: +// they distill a step's captured output into the aligned detail lines shown +// under its section header. + +var ( + lintResultPattern = regexp.MustCompile(`Found [0-9]+ warning|[0-9]+ error`) + goFileSummaryPattern = regexp.MustCompile(`^ (Formatted|Checked) [0-9]+ file\(s\)\.$|^ No Go files found\.$`) + goVetSummaryPattern = regexp.MustCompile(`^ go vet \./\.\.\. passed\.$|^ Skipped automatic go vet `) + sourcesMissingPrefix = "[sources] path not found, skipping:" + blankLinesPrefix = "[blank-lines] processed " + fluentChainsPrefix = "[fluent-chains] processed " + oxfmtFinishedPrefix = "Finished in " + validateSyntaxPrefix = "[validate-syntax] checked " + lintNothingToLintLine = "[lint] no TS/Vue files to lint." + goResultPrefix = " Result: " +) + +func lines(log string) []string { + return strings.Split(log, "\n") +} + +func lastWithPrefix(log, prefix string) string { + var match string + + for _, line := range lines(log) { + if strings.HasPrefix(line, prefix) { + match = line + } + } + + return match +} + +func summarizeTSFormat(log string, l *logger) { + missing := 0 + + for _, line := range lines(log) { + if strings.HasPrefix(line, sourcesMissingPrefix) { + missing++ + } + } + + if line := lastWithPrefix(log, blankLinesPrefix); line != "" { + l.detail("blank-lines", strings.TrimPrefix(line, "[blank-lines] ")) + } + + if missing > 0 { + l.detail("skipped", fmt.Sprintf("%d missing tracked file(s)", missing)) + } + + if line := lastWithPrefix(log, oxfmtFinishedPrefix); line != "" { + l.detail("oxfmt", line) + } + + if line := lastWithPrefix(log, fluentChainsPrefix); line != "" { + l.detail("fluent", strings.TrimPrefix(line, "[fluent-chains] ")) + } + + if line := lastWithPrefix(log, validateSyntaxPrefix); line != "" { + l.detail("validated", strings.TrimPrefix(line, "[validate-syntax] ")) + } +} + +func summarizeTSLint(log string, l *logger) { + if lastWithPrefix(log, lintNothingToLintLine) != "" { + l.detail("oxlint", strings.TrimPrefix(lintNothingToLintLine, "[lint] ")) + + return + } + + var match string + + for _, line := range lines(log) { + if lintResultPattern.MatchString(line) { + match = line + } + } + + if match != "" { + l.detail("oxlint", match) + + return + } + + l.detail("oxlint", "no issues found") +} + +func summarizeGoFormat(log string, l *logger) { + var fileSummary, formatterResult, vetSummary, vetResult string + + for _, line := range lines(log) { + if fileSummary == "" && goFileSummaryPattern.MatchString(line) { + fileSummary = strings.TrimPrefix(line, " ") + } + + if vetSummary == "" && goVetSummaryPattern.MatchString(line) { + vetSummary = strings.TrimPrefix(line, " ") + } + + if strings.HasPrefix(line, goResultPrefix) { + if formatterResult == "" { + formatterResult = strings.TrimPrefix(line, goResultPrefix) + } + + vetResult = strings.TrimPrefix(line, goResultPrefix) + } + } + + if fileSummary != "" { + l.detail("fmtkit", fileSummary) + } + + if formatterResult != "" { + l.detail("result", formatterResult) + } + + if vetSummary != "" { + l.detail("vet", vetSummary) + } + + if vetResult != "" && vetResult != formatterResult { + l.detail("vet result", vetResult) + } +} diff --git a/packages/driver/internal/tsruntime/embedded/.gitignore b/packages/driver/internal/tsruntime/embedded/.gitignore new file mode 100644 index 0000000..849ddff --- /dev/null +++ b/packages/driver/internal/tsruntime/embedded/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/packages/driver/internal/tsruntime/embedded/embedded_darwin_amd64.go b/packages/driver/internal/tsruntime/embedded/embedded_darwin_amd64.go new file mode 100644 index 0000000..f046d54 --- /dev/null +++ b/packages/driver/internal/tsruntime/embedded/embedded_darwin_amd64.go @@ -0,0 +1,22 @@ +//go:build fmtkit_sidecar && darwin && amd64 + +package embedded + +import ( + "embed" + "io/fs" +) + +//go:embed dist/darwin_amd64 +var assets embed.FS + +// Assets returns the TS toolchain staged for this platform. +func Assets() (fs.FS, bool) { + sub, err := fs.Sub(assets, "dist/darwin_amd64") + + if err != nil { + panic(err) + } + + return sub, true +} diff --git a/packages/driver/internal/tsruntime/embedded/embedded_darwin_arm64.go b/packages/driver/internal/tsruntime/embedded/embedded_darwin_arm64.go new file mode 100644 index 0000000..2a9335f --- /dev/null +++ b/packages/driver/internal/tsruntime/embedded/embedded_darwin_arm64.go @@ -0,0 +1,22 @@ +//go:build fmtkit_sidecar && darwin && arm64 + +package embedded + +import ( + "embed" + "io/fs" +) + +//go:embed dist/darwin_arm64 +var assets embed.FS + +// Assets returns the TS toolchain staged for this platform. +func Assets() (fs.FS, bool) { + sub, err := fs.Sub(assets, "dist/darwin_arm64") + + if err != nil { + panic(err) + } + + return sub, true +} diff --git a/packages/driver/internal/tsruntime/embedded/embedded_dev.go b/packages/driver/internal/tsruntime/embedded/embedded_dev.go new file mode 100644 index 0000000..5cbb1dd --- /dev/null +++ b/packages/driver/internal/tsruntime/embedded/embedded_dev.go @@ -0,0 +1,15 @@ +//go:build !fmtkit_sidecar + +// Package embedded carries the per-platform TS toolchain assets that +// infra/scripts/release/stage-ts-assets.sh places under dist/. Only builds +// tagged fmtkit_sidecar embed them; regular builds stay lightweight and rely +// on FMTKIT_SUPPORT_DIR instead. +package embedded + +import "io/fs" + +// Assets reports that this binary carries no TS toolchain: it was built +// without the fmtkit_sidecar build tag. +func Assets() (fs.FS, bool) { + return nil, false +} diff --git a/packages/driver/internal/tsruntime/embedded/embedded_linux_amd64.go b/packages/driver/internal/tsruntime/embedded/embedded_linux_amd64.go new file mode 100644 index 0000000..bf21fd2 --- /dev/null +++ b/packages/driver/internal/tsruntime/embedded/embedded_linux_amd64.go @@ -0,0 +1,22 @@ +//go:build fmtkit_sidecar && linux && amd64 + +package embedded + +import ( + "embed" + "io/fs" +) + +//go:embed dist/linux_amd64 +var assets embed.FS + +// Assets returns the TS toolchain staged for this platform. +func Assets() (fs.FS, bool) { + sub, err := fs.Sub(assets, "dist/linux_amd64") + + if err != nil { + panic(err) + } + + return sub, true +} diff --git a/packages/driver/internal/tsruntime/embedded/embedded_linux_arm64.go b/packages/driver/internal/tsruntime/embedded/embedded_linux_arm64.go new file mode 100644 index 0000000..f98634c --- /dev/null +++ b/packages/driver/internal/tsruntime/embedded/embedded_linux_arm64.go @@ -0,0 +1,22 @@ +//go:build fmtkit_sidecar && linux && arm64 + +package embedded + +import ( + "embed" + "io/fs" +) + +//go:embed dist/linux_arm64 +var assets embed.FS + +// Assets returns the TS toolchain staged for this platform. +func Assets() (fs.FS, bool) { + sub, err := fs.Sub(assets, "dist/linux_arm64") + + if err != nil { + panic(err) + } + + return sub, true +} diff --git a/packages/driver/internal/tsruntime/run.go b/packages/driver/internal/tsruntime/run.go new file mode 100644 index 0000000..5852165 --- /dev/null +++ b/packages/driver/internal/tsruntime/run.go @@ -0,0 +1,203 @@ +package tsruntime + +import ( + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + + "github.com/oullin/fmtkit/packages/driver/internal/sourcefiles" +) + +// Env override names shared with the container entrypoints; see +// infra/bin/fmtkit-ts-files and infra/bin/fmtkit-ts-lint. + +// RunOptions describes one TS toolchain invocation. +type RunOptions struct { + // Scopes are the paths to process, defaulting to ".". + Scopes []string + + Stdout io.Writer + Stderr io.Writer +} + +const ( + PipelineBinEnv = "FMTKIT_TS_PIPELINE_BIN" + OxfmtBinEnv = "OXFMT_BIN" + OxlintBinEnv = "OXLINT_BIN" + OxfmtConfigEnv = "FMTKIT_OXFMTRC" + OxlintConfigEnv = "FMTKIT_OXLINTRC" + SourcesCwdEnv = "FMTKIT_SOURCES_CWD" +) + +// RunPipeline runs the full TS/Vue formatting pipeline (blank-lines -> oxfmt +// -> fluent-chains -> oxfmt -> validate-syntax), the Go port of +// infra/bin/fmtkit-ts-files. +func (s Support) RunPipeline(opts RunOptions) error { + cwd, err := sourcesCwd() + + if err != nil { + return err + } + + formatFiles, warnings, err := collect(cwd, opts.Scopes, false) + + if err != nil { + return err + } + + for _, warning := range warnings { + _, _ = fmt.Fprintf(opts.Stderr, "[sources] %s\n", warning) + } + + syntaxFiles, _, err := collect(cwd, opts.Scopes, true) + + if err != nil { + return err + } + + oxfmtBin := os.Getenv(OxfmtBinEnv) + + args := []string{"pipeline"} + + if oxfmtBin != "" { + args = append(args, "--oxfmt-bin", oxfmtBin) + } else { + args = append(args, "--oxfmt-bin", s.Sidecar()) + } + + if config := s.oxfmtConfigFor(cwd); config != "" { + args = append(args, "--oxfmt-config", config) + } + + args = append(args, "--format-files") + args = append(args, formatFiles...) + args = append(args, "--syntax-files") + args = append(args, syntaxFiles...) + + return s.spawn(pipelineBin(s.Sidecar()), args, opts) +} + +// RunLint lints the collected TS/Vue files with oxlint, the Go port of +// infra/bin/fmtkit-ts-lint. +func (s Support) RunLint(opts RunOptions) error { + cwd, err := sourcesCwd() + + if err != nil { + return err + } + + files, warnings, err := collect(cwd, opts.Scopes, false) + + if err != nil { + return err + } + + for _, warning := range warnings { + _, _ = fmt.Fprintf(opts.Stderr, "[sources] %s\n", warning) + } + + if len(files) == 0 { + _, _ = fmt.Fprintln(opts.Stdout, "[lint] no TS/Vue files to lint.") + + return nil + } + + var args []string + + bin := os.Getenv(OxlintBinEnv) + + if bin == "" { + bin = s.Sidecar() + args = append(args, "oxlint") + } + + if config := s.oxlintConfigFor(cwd); config != "" { + args = append(args, "--config", config) + } + + args = append(args, files...) + + return s.spawn(bin, args, opts) +} + +func pipelineBin(sidecar string) string { + if bin := os.Getenv(PipelineBinEnv); bin != "" { + return bin + } + + return sidecar +} + +func sourcesCwd() (string, error) { + if cwd := os.Getenv(SourcesCwdEnv); cwd != "" { + return cwd, nil + } + + cwd, err := os.Getwd() + + if err != nil { + return "", fmt.Errorf("resolve cwd: %w", err) + } + + return cwd, nil +} + +func collect(cwd string, scopes []string, includeDeclarations bool) ([]string, []string, error) { + return sourcefiles.Collect(sourcefiles.Options{ + Cwd: cwd, + IncludeDeclarations: includeDeclarations, + Scopes: scopes, + }) +} + +// oxfmtConfigFor mirrors the entrypoint rule: a project-local .oxfmtrc.* +// wins via oxfmt's own auto-discovery, otherwise fall back to the bundled +// configuration. +func (s Support) oxfmtConfigFor(cwd string) string { + if config := os.Getenv(OxfmtConfigEnv); config != "" { + return existingFile(config) + } + + if matches, err := filepath.Glob(filepath.Join(cwd, ".oxfmtrc.*")); err == nil && len(matches) > 0 { + return "" + } + + return s.OxfmtConfig() +} + +// oxlintConfigFor mirrors infra/bin/fmtkit-ts-lint: both the extensionless +// .oxlintrc and .oxlintrc.* count as project configuration. +func (s Support) oxlintConfigFor(cwd string) string { + if config := os.Getenv(OxlintConfigEnv); config != "" { + return existingFile(config) + } + + if existingFile(filepath.Join(cwd, ".oxlintrc")) != "" { + return "" + } + + if matches, err := filepath.Glob(filepath.Join(cwd, ".oxlintrc.*")); err == nil && len(matches) > 0 { + return "" + } + + return s.OxlintConfig() +} + +func (s Support) spawn(bin string, args []string, opts RunOptions) error { + cmd := exec.Command(bin, args...) + + cmd.Stdout = opts.Stdout + cmd.Stderr = opts.Stderr + + // Match the container entrypoints: let git treat any working tree as safe + // so file collection inside bind mounts and caches works. + cmd.Env = append(os.Environ(), + "GIT_CONFIG_COUNT=1", + "GIT_CONFIG_KEY_0=safe.directory", + "GIT_CONFIG_VALUE_0=*", + ) + + return cmd.Run() +} diff --git a/packages/driver/internal/tsruntime/run_test.go b/packages/driver/internal/tsruntime/run_test.go new file mode 100644 index 0000000..5f8c6cb --- /dev/null +++ b/packages/driver/internal/tsruntime/run_test.go @@ -0,0 +1,236 @@ +package tsruntime + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +// writeStub creates an executable that echoes its argv, one per line, so +// tests can assert the exact tool invocation. +func writeStub(t *testing.T, path string) { + t.Helper() + + script := "#!/bin/sh\nfor arg in \"$@\"; do printf '%s\\n' \"$arg\"; done\n" + + if err := os.WriteFile(path, []byte(script), 0o755); err != nil { + t.Fatalf("write stub %s: %v", path, err) + } +} + +func gitScratchRepo(t *testing.T, files map[string]string) string { + t.Helper() + + dir := t.TempDir() + + run := func(args ...string) { + cmd := exec.Command("git", args...) + cmd.Dir = dir + + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("git %v: %v\n%s", args, err, out) + } + } + + run("init", "--quiet") + + for name, contents := range files { + if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0o644); err != nil { + t.Fatalf("write %s: %v", name, err) + } + } + + return dir +} + +func supportWithStub(t *testing.T) Support { + t.Helper() + + dir := t.TempDir() + + writeStub(t, filepath.Join(dir, sidecarName)) + + return Support{Dir: dir} +} + +func TestRunPipelineInvokesSidecar(t *testing.T) { + repo := gitScratchRepo(t, map[string]string{ + "app.ts": "export const a = 1;\n", + "types.ts": "export type T = number;\n", + "decl.d.ts": "declare const d: number;\n" + + "export default d;\n", + }) + + support := supportWithStub(t) + + if err := os.WriteFile(filepath.Join(support.Dir, ".oxfmtrc.json"), []byte("{}"), 0o644); err != nil { + t.Fatalf("write bundled config: %v", err) + } + + t.Setenv(SourcesCwdEnv, repo) + + var stdout, stderr bytes.Buffer + + err := support.RunPipeline(RunOptions{Stdout: &stdout, Stderr: &stderr}) + + if err != nil { + t.Fatalf("RunPipeline: %v\nstderr: %s", err, stderr.String()) + } + + lines := strings.Split(strings.TrimSpace(stdout.String()), "\n") + + wantPrefix := []string{ + "pipeline", + "--oxfmt-bin", support.Sidecar(), + "--oxfmt-config", filepath.Join(support.Dir, ".oxfmtrc.json"), + "--format-files", + filepath.Join(repo, "app.ts"), + filepath.Join(repo, "types.ts"), + "--syntax-files", + filepath.Join(repo, "app.ts"), + filepath.Join(repo, "decl.d.ts"), + filepath.Join(repo, "types.ts"), + } + + if len(lines) != len(wantPrefix) { + t.Fatalf("argv = %q, want %q", lines, wantPrefix) + } + + for i, want := range wantPrefix { + if lines[i] != want { + t.Fatalf("argv[%d] = %q, want %q\nfull: %q", i, lines[i], want, lines) + } + } +} + +func TestRunPipelineSkipsBundledConfigWhenProjectHasOne(t *testing.T) { + repo := gitScratchRepo(t, map[string]string{ + "app.ts": "export const a = 1;\n", + ".oxfmtrc.json": "{}", + }) + + support := supportWithStub(t) + + if err := os.WriteFile(filepath.Join(support.Dir, ".oxfmtrc.json"), []byte("{}"), 0o644); err != nil { + t.Fatalf("write bundled config: %v", err) + } + + t.Setenv(SourcesCwdEnv, repo) + + var stdout, stderr bytes.Buffer + + if err := support.RunPipeline(RunOptions{Stdout: &stdout, Stderr: &stderr}); err != nil { + t.Fatalf("RunPipeline: %v\nstderr: %s", err, stderr.String()) + } + + if strings.Contains(stdout.String(), "--oxfmt-config") { + t.Fatalf("bundled config passed despite project config:\n%s", stdout.String()) + } +} + +func TestRunPipelineReportsMissingScopes(t *testing.T) { + repo := gitScratchRepo(t, map[string]string{"app.ts": "export const a = 1;\n"}) + + support := supportWithStub(t) + + t.Setenv(SourcesCwdEnv, repo) + + var stdout, stderr bytes.Buffer + + err := support.RunPipeline(RunOptions{ + Scopes: []string{"missing-dir"}, + Stdout: &stdout, + Stderr: &stderr, + }) + + if err != nil { + t.Fatalf("RunPipeline: %v", err) + } + + want := fmt.Sprintf("[sources] path not found, skipping: %s", filepath.Join(repo, "missing-dir")) + + if !strings.Contains(stderr.String(), want) { + t.Fatalf("stderr = %q, want it to contain %q", stderr.String(), want) + } +} + +func TestRunLintInvokesOxlintMode(t *testing.T) { + repo := gitScratchRepo(t, map[string]string{"app.ts": "export const a = 1;\n"}) + + support := supportWithStub(t) + + if err := os.WriteFile(filepath.Join(support.Dir, ".oxlintrc.json"), []byte("{}"), 0o644); err != nil { + t.Fatalf("write bundled config: %v", err) + } + + t.Setenv(SourcesCwdEnv, repo) + + var stdout, stderr bytes.Buffer + + if err := support.RunLint(RunOptions{Stdout: &stdout, Stderr: &stderr}); err != nil { + t.Fatalf("RunLint: %v\nstderr: %s", err, stderr.String()) + } + + want := []string{ + "oxlint", + "--config", filepath.Join(support.Dir, ".oxlintrc.json"), + filepath.Join(repo, "app.ts"), + } + + got := strings.Split(strings.TrimSpace(stdout.String()), "\n") + + if len(got) != len(want) { + t.Fatalf("argv = %q, want %q", got, want) + } + + for i := range want { + if got[i] != want[i] { + t.Fatalf("argv[%d] = %q, want %q", i, got[i], want[i]) + } + } +} + +func TestRunLintSkipsSpawnWithoutFiles(t *testing.T) { + repo := gitScratchRepo(t, map[string]string{"main.go": "package main\n"}) + + support := Support{Dir: t.TempDir()} // no sidecar: spawning would fail + + t.Setenv(SourcesCwdEnv, repo) + + var stdout, stderr bytes.Buffer + + if err := support.RunLint(RunOptions{Stdout: &stdout, Stderr: &stderr}); err != nil { + t.Fatalf("RunLint: %v", err) + } + + if !strings.Contains(stdout.String(), "[lint] no TS/Vue files to lint.") { + t.Fatalf("stdout = %q, want no-files notice", stdout.String()) + } +} + +func TestRunLintHonorsOxlintBinOverride(t *testing.T) { + repo := gitScratchRepo(t, map[string]string{"app.ts": "export const a = 1;\n"}) + + support := supportWithStub(t) + + override := filepath.Join(t.TempDir(), "oxlint") + + writeStub(t, override) + + t.Setenv(SourcesCwdEnv, repo) + t.Setenv(OxlintBinEnv, override) + + var stdout, stderr bytes.Buffer + + if err := support.RunLint(RunOptions{Stdout: &stdout, Stderr: &stderr}); err != nil { + t.Fatalf("RunLint: %v\nstderr: %s", err, stderr.String()) + } + + if strings.HasPrefix(strings.TrimSpace(stdout.String()), "oxlint") { + t.Fatalf("mode argument passed to standalone oxlint override:\n%s", stdout.String()) + } +} diff --git a/packages/driver/internal/tsruntime/support.go b/packages/driver/internal/tsruntime/support.go new file mode 100644 index 0000000..df32c79 --- /dev/null +++ b/packages/driver/internal/tsruntime/support.go @@ -0,0 +1,249 @@ +// Package tsruntime manages the self-contained TS toolchain shipped inside +// release binaries: a bun-compiled sidecar plus the oxc-parser, oxfmt, and +// oxlint napi bindings. On first use the embedded assets are extracted to a +// per-version cache directory and spawned as child processes from there. +package tsruntime + +import ( + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "sort" + + fmtkit "github.com/oullin/fmtkit" + "github.com/oullin/fmtkit/packages/driver/internal/tsruntime/embedded" +) + +// SupportDirEnv points at a pre-extracted toolchain directory and skips +// both the embedded assets and the cache. + +// Support locates the extracted TS toolchain on disk. +type Support struct { + Dir string +} + +const ( + SupportDirEnv = "FMTKIT_SUPPORT_DIR" + + sidecarName = "fmtkit-ts-sidecar" + sentinelName = ".fmtkit-complete" +) + +// Sidecar returns the path of the multiplexed toolchain executable. +func (s Support) Sidecar() string { + return filepath.Join(s.Dir, sidecarName) +} + +// OxfmtConfig returns the bundled oxfmt configuration path, or "" when the +// support directory carries none. +func (s Support) OxfmtConfig() string { + return existingFile(filepath.Join(s.Dir, ".oxfmtrc.json")) +} + +// OxlintConfig returns the bundled oxlint configuration path, or "" when the +// support directory carries none. +func (s Support) OxlintConfig() string { + return existingFile(filepath.Join(s.Dir, ".oxlintrc.json")) +} + +func existingFile(path string) string { + if info, err := os.Stat(path); err == nil && info.Mode().IsRegular() { + return path + } + + return "" +} + +// Resolve locates the TS toolchain, extracting the embedded assets into the +// user cache on first use. version tells extractions of different releases +// apart; dev builds derive a digest from the assets instead. +func Resolve(version string) (Support, error) { + if dir := os.Getenv(SupportDirEnv); dir != "" { + support := Support{Dir: dir} + + if existingFile(support.Sidecar()) == "" { + return Support{}, fmt.Errorf("%s (%s) does not contain %s", SupportDirEnv, dir, sidecarName) + } + + return support, nil + } + + assets, ok := embedded.Assets() + + if !ok { + return Support{}, errors.New( + "this fmtkit build carries no TS toolchain (built without the fmtkit_sidecar tag); " + + "point " + SupportDirEnv + " at a staged toolchain directory " + + "(see infra/scripts/release/stage-ts-assets.sh), or use a release binary " + + "or the ghcr.io/oullin/fmtkit image", + ) + } + + if version == "" || version == "dev" { + digest, err := assetsDigest(assets) + + if err != nil { + return Support{}, err + } + + version = "dev-" + digest + } + + cacheRoot, err := os.UserCacheDir() + + if err != nil { + return Support{}, fmt.Errorf("resolve user cache dir: %w", err) + } + + dir := filepath.Join(cacheRoot, "fmtkit", version) + + if err := extractOnce(dir, assets); err != nil { + return Support{}, err + } + + return Support{Dir: dir}, nil +} + +// extractOnce materializes the toolchain into dir unless a completed +// extraction is already there. Concurrent first runs race benignly: each +// extracts into its own temp sibling and the first rename wins. +func extractOnce(dir string, assets fs.FS) error { + if existingFile(filepath.Join(dir, sentinelName)) != "" { + return nil + } + + parent := filepath.Dir(dir) + + if err := os.MkdirAll(parent, 0o755); err != nil { + return fmt.Errorf("create cache dir: %w", err) + } + + tmp, err := os.MkdirTemp(parent, filepath.Base(dir)+".tmp-") + + if err != nil { + return fmt.Errorf("create extraction dir: %w", err) + } + + defer func() { + _ = os.RemoveAll(tmp) + }() + + if err := extract(tmp, assets); err != nil { + return err + } + + if err := os.Rename(tmp, dir); err != nil { + if existingFile(filepath.Join(dir, sentinelName)) != "" { + return nil + } + + return fmt.Errorf("activate toolchain dir: %w", err) + } + + return nil +} + +func extract(dst string, assets fs.FS) error { + entries, err := fs.ReadDir(assets, ".") + + if err != nil { + return fmt.Errorf("read embedded toolchain: %w", err) + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + mode := os.FileMode(0o644) + + if entry.Name() == sidecarName { + mode = 0o755 + } + + if err := writeFileFrom(assets, entry.Name(), filepath.Join(dst, entry.Name()), mode); err != nil { + return err + } + } + + configs := map[string][]byte{ + ".oxfmtrc.json": fmtkit.OxfmtConfig, + ".oxlintrc.json": fmtkit.OxlintConfig, + } + + for name, contents := range configs { + if err := os.WriteFile(filepath.Join(dst, name), contents, 0o644); err != nil { + return fmt.Errorf("write %s: %w", name, err) + } + } + + if err := os.WriteFile(filepath.Join(dst, sentinelName), nil, 0o644); err != nil { + return fmt.Errorf("write %s: %w", sentinelName, err) + } + + return nil +} + +func writeFileFrom(assets fs.FS, name, dst string, mode os.FileMode) error { + src, err := assets.Open(name) + + if err != nil { + return fmt.Errorf("open embedded %s: %w", name, err) + } + + defer func() { + _ = src.Close() + }() + + out, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) + + if err != nil { + return fmt.Errorf("write %s: %w", dst, err) + } + + if _, err := io.Copy(out, src); err != nil { + _ = out.Close() + + return fmt.Errorf("write %s: %w", dst, err) + } + + return out.Close() +} + +func assetsDigest(assets fs.FS) (string, error) { + hash := sha256.New() + + var names []string + + entries, err := fs.ReadDir(assets, ".") + + if err != nil { + return "", fmt.Errorf("read embedded toolchain: %w", err) + } + + for _, entry := range entries { + if !entry.IsDir() { + names = append(names, entry.Name()) + } + } + + sort.Strings(names) + + for _, name := range names { + contents, err := fs.ReadFile(assets, name) + + if err != nil { + return "", fmt.Errorf("read embedded %s: %w", name, err) + } + + hash.Write([]byte(name)) + hash.Write(contents) + } + + return hex.EncodeToString(hash.Sum(nil))[:12], nil +} diff --git a/packages/driver/internal/tsruntime/support_test.go b/packages/driver/internal/tsruntime/support_test.go new file mode 100644 index 0000000..a4bd727 --- /dev/null +++ b/packages/driver/internal/tsruntime/support_test.go @@ -0,0 +1,150 @@ +package tsruntime + +import ( + "os" + "path/filepath" + "testing" + "testing/fstest" +) + +func fakeAssets() fstest.MapFS { + return fstest.MapFS{ + sidecarName: &fstest.MapFile{Data: []byte("#!/bin/sh\n"), Mode: 0o755}, + "oxc-parser.node": &fstest.MapFile{Data: []byte("parser")}, + "oxfmt.node": &fstest.MapFile{Data: []byte("fmt")}, + "oxlint.node": &fstest.MapFile{Data: []byte("lint")}, + } +} + +func TestExtractOncePopulatesSupportDir(t *testing.T) { + dir := filepath.Join(t.TempDir(), "v1.0.0") + + if err := extractOnce(dir, fakeAssets()); err != nil { + t.Fatalf("extractOnce: %v", err) + } + + support := Support{Dir: dir} + + if _, err := os.Stat(support.Sidecar()); err != nil { + t.Fatalf("sidecar missing: %v", err) + } + + info, err := os.Stat(support.Sidecar()) + + if err != nil { + t.Fatalf("stat sidecar: %v", err) + } + + if info.Mode().Perm()&0o111 == 0 { + t.Fatalf("sidecar is not executable: %v", info.Mode()) + } + + for _, name := range []string{"oxc-parser.node", "oxfmt.node", "oxlint.node", ".oxfmtrc.json", ".oxlintrc.json", sentinelName} { + if _, err := os.Stat(filepath.Join(dir, name)); err != nil { + t.Fatalf("%s missing: %v", name, err) + } + } + + if support.OxfmtConfig() == "" { + t.Fatal("expected bundled oxfmt config") + } + + if support.OxlintConfig() == "" { + t.Fatal("expected bundled oxlint config") + } +} + +func TestExtractOnceIsIdempotent(t *testing.T) { + dir := filepath.Join(t.TempDir(), "v1.0.0") + + if err := extractOnce(dir, fakeAssets()); err != nil { + t.Fatalf("first extractOnce: %v", err) + } + + marker := filepath.Join(dir, "marker") + + if err := os.WriteFile(marker, []byte("keep"), 0o644); err != nil { + t.Fatalf("write marker: %v", err) + } + + if err := extractOnce(dir, fakeAssets()); err != nil { + t.Fatalf("second extractOnce: %v", err) + } + + if _, err := os.Stat(marker); err != nil { + t.Fatalf("completed extraction was redone: %v", err) + } +} + +func TestExtractOnceLosingRaceKeepsWinner(t *testing.T) { + dir := filepath.Join(t.TempDir(), "v1.0.0") + + // Simulate another process having completed extraction between the + // sentinel check and the rename: pre-create the final dir with a sentinel. + if err := extractOnce(dir, fakeAssets()); err != nil { + t.Fatalf("winner extractOnce: %v", err) + } + + if err := extractOnce(dir, fakeAssets()); err != nil { + t.Fatalf("loser extractOnce: %v", err) + } +} + +func TestResolvePrefersSupportDirEnv(t *testing.T) { + dir := t.TempDir() + + if err := os.WriteFile(filepath.Join(dir, sidecarName), []byte("#!/bin/sh\n"), 0o755); err != nil { + t.Fatalf("write sidecar: %v", err) + } + + t.Setenv(SupportDirEnv, dir) + + support, err := Resolve("v1.0.0") + + if err != nil { + t.Fatalf("Resolve: %v", err) + } + + if support.Dir != dir { + t.Fatalf("Dir = %q, want %q", support.Dir, dir) + } +} + +func TestResolveRejectsSupportDirWithoutSidecar(t *testing.T) { + t.Setenv(SupportDirEnv, t.TempDir()) + + if _, err := Resolve("v1.0.0"); err == nil { + t.Fatal("expected error for support dir without sidecar") + } +} + +func TestAssetsDigestIsStable(t *testing.T) { + first, err := assetsDigest(fakeAssets()) + + if err != nil { + t.Fatalf("assetsDigest: %v", err) + } + + second, err := assetsDigest(fakeAssets()) + + if err != nil { + t.Fatalf("assetsDigest: %v", err) + } + + if first != second { + t.Fatalf("digest not stable: %q vs %q", first, second) + } + + changed := fakeAssets() + changed["oxfmt.node"] = &fstest.MapFile{Data: []byte("different")} + + third, err := assetsDigest(changed) + + if err != nil { + t.Fatalf("assetsDigest: %v", err) + } + + if third == first { + t.Fatal("digest did not change with contents") + } +} From f3b749b43dfd5a3ebb2c5057fddef62f73ef9173 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Wed, 15 Jul 2026 17:48:57 +0800 Subject: [PATCH 2/8] fix: pin color-free test env and disable setup-node pnpm cache detection - orchestrator/cmd tests assert plain output; CI task runners export FORCE_COLOR, which injected ANSI codes into the captured buffers. - setup-node v5 auto-detects pnpm from pnpm-lock.yaml for caching, but the binary jobs only use npm. --- .github/workflows/publish-release.yml | 3 +++ .github/workflows/tests.yml | 3 +++ packages/driver/cmd/fmtkit/main_test.go | 9 +++++++++ packages/driver/internal/orchestrator/pipeline_test.go | 10 ++++++++++ 4 files changed, 25 insertions(+) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 5e33280..a111fb1 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -158,6 +158,9 @@ jobs: - uses: actions/setup-node@v5 with: node-version-file: .nvmrc + # This job uses npm only; without this, setup-node v5's cache + # auto-detection finds pnpm-lock.yaml and requires pnpm. + package-manager-cache: false - uses: oven-sh/setup-bun@v2 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 00a512b..788b3bb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -155,6 +155,9 @@ jobs: name: Install Node with: node-version-file: .nvmrc + # This job uses npm only; without this, setup-node v5's cache + # auto-detection finds pnpm-lock.yaml and requires pnpm. + package-manager-cache: false - uses: oven-sh/setup-bun@v2 name: Install Bun diff --git a/packages/driver/cmd/fmtkit/main_test.go b/packages/driver/cmd/fmtkit/main_test.go index 8062ee9..cf74dad 100644 --- a/packages/driver/cmd/fmtkit/main_test.go +++ b/packages/driver/cmd/fmtkit/main_test.go @@ -10,6 +10,15 @@ import ( "github.com/oullin/fmtkit/packages/driver/testutil" ) +// TestMain pins a color-free environment: CI task runners export FORCE_COLOR, +// which would inject ANSI codes into the captured output these tests assert. +func TestMain(m *testing.M) { + _ = os.Unsetenv("FORCE_COLOR") + _ = os.Setenv("NO_COLOR", "1") + + os.Exit(m.Run()) +} + func runCLI(t *testing.T, workdir string, args ...string) (int, string, string) { t.Helper() diff --git a/packages/driver/internal/orchestrator/pipeline_test.go b/packages/driver/internal/orchestrator/pipeline_test.go index 52b4601..e1cf8d2 100644 --- a/packages/driver/internal/orchestrator/pipeline_test.go +++ b/packages/driver/internal/orchestrator/pipeline_test.go @@ -5,10 +5,20 @@ import ( "errors" "fmt" "io" + "os" "strings" "testing" ) +// TestMain pins a color-free environment: CI task runners export FORCE_COLOR, +// which would inject ANSI codes into the captured output these tests assert. +func TestMain(m *testing.M) { + _ = os.Unsetenv("FORCE_COLOR") + _ = os.Setenv("NO_COLOR", "1") + + os.Exit(m.Run()) +} + // The stub outputs mirror infra/scripts/tasks/test-fmtkit-entrypoint.sh so // the Go orchestrator preserves the entrypoint's summary contract. From 361fbe3324af9a502e26a6debdc2b59080bb4643 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 16 Jul 2026 11:18:47 +0800 Subject: [PATCH 3/8] refactor: stage TS sidecar assets under infra/bin and add --ts/--go step flags - move per-platform go:embed files to the module root so the staged bun sidecar + oxc binaries live in gitignored infra/bin/_/ directories instead of packages/driver/internal/tsruntime/embedded - drop the now-empty tsruntime/embedded package; tsruntime reads the toolchain via fmtkit.SidecarAssets() - add --ts / --go step-selection flags to format and format-all (no flags runs everything; unknown flags exit 2 with usage) - cover project-local .oxlintrc.json overriding the bundled config --- .gitignore | 4 + embedded.go | 4 +- embedded_sidecar_darwin_amd64.go | 22 +++++ embedded_sidecar_darwin_arm64.go | 22 +++++ embedded_sidecar_dev.go | 11 +++ embedded_sidecar_linux_amd64.go | 22 +++++ embedded_sidecar_linux_arm64.go | 22 +++++ infra/scripts/release/stage-ts-assets.sh | 2 +- packages/driver/cmd/fmtkit/main.go | 67 +++++++++++----- packages/driver/cmd/fmtkit/main_test.go | 80 +++++++++++++++++++ .../driver/internal/orchestrator/pipeline.go | 59 ++++++++++---- .../internal/tsruntime/embedded/.gitignore | 1 - .../embedded/embedded_darwin_amd64.go | 22 ----- .../embedded/embedded_darwin_arm64.go | 22 ----- .../tsruntime/embedded/embedded_dev.go | 15 ---- .../embedded/embedded_linux_amd64.go | 22 ----- .../embedded/embedded_linux_arm64.go | 22 ----- .../driver/internal/tsruntime/run_test.go | 25 ++++++ packages/driver/internal/tsruntime/support.go | 3 +- 19 files changed, 304 insertions(+), 143 deletions(-) create mode 100644 embedded_sidecar_darwin_amd64.go create mode 100644 embedded_sidecar_darwin_arm64.go create mode 100644 embedded_sidecar_dev.go create mode 100644 embedded_sidecar_linux_amd64.go create mode 100644 embedded_sidecar_linux_arm64.go delete mode 100644 packages/driver/internal/tsruntime/embedded/.gitignore delete mode 100644 packages/driver/internal/tsruntime/embedded/embedded_darwin_amd64.go delete mode 100644 packages/driver/internal/tsruntime/embedded/embedded_darwin_arm64.go delete mode 100644 packages/driver/internal/tsruntime/embedded/embedded_dev.go delete mode 100644 packages/driver/internal/tsruntime/embedded/embedded_linux_amd64.go delete mode 100644 packages/driver/internal/tsruntime/embedded/embedded_linux_arm64.go diff --git a/.gitignore b/.gitignore index 4976188..5b379af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ /fmt /dist/ *.bun-build + +# TS toolchain assets staged per platform by stage-ts-assets.sh; the tracked +# entrypoint scripts in infra/bin itself stay versioned. +/infra/bin/*/ /storage/bin*/ /storage/dist/ /storage/dist-test/ diff --git a/embedded.go b/embedded.go index 177c1a4..27d7856 100644 --- a/embedded.go +++ b/embedded.go @@ -1,6 +1,8 @@ // Package fmtkit exposes repository-level assets embedded into the fmtkit // binaries. It lives at the module root because go:embed cannot reference -// files above the embedding package. +// files above the embedding package; that also covers the TS toolchain +// staged under infra/bin/ by infra/scripts/release/stage-ts-assets.sh, which +// only builds tagged fmtkit_sidecar embed (see embedded_sidecar_*.go). package fmtkit import _ "embed" diff --git a/embedded_sidecar_darwin_amd64.go b/embedded_sidecar_darwin_amd64.go new file mode 100644 index 0000000..09c24a2 --- /dev/null +++ b/embedded_sidecar_darwin_amd64.go @@ -0,0 +1,22 @@ +//go:build fmtkit_sidecar && darwin && amd64 + +package fmtkit + +import ( + "embed" + "io/fs" +) + +//go:embed infra/bin/darwin_amd64 +var sidecarAssets embed.FS + +// SidecarAssets returns the TS toolchain staged for this platform. +func SidecarAssets() (fs.FS, bool) { + sub, err := fs.Sub(sidecarAssets, "infra/bin/darwin_amd64") + + if err != nil { + panic(err) + } + + return sub, true +} diff --git a/embedded_sidecar_darwin_arm64.go b/embedded_sidecar_darwin_arm64.go new file mode 100644 index 0000000..d1fd60d --- /dev/null +++ b/embedded_sidecar_darwin_arm64.go @@ -0,0 +1,22 @@ +//go:build fmtkit_sidecar && darwin && arm64 + +package fmtkit + +import ( + "embed" + "io/fs" +) + +//go:embed infra/bin/darwin_arm64 +var sidecarAssets embed.FS + +// SidecarAssets returns the TS toolchain staged for this platform. +func SidecarAssets() (fs.FS, bool) { + sub, err := fs.Sub(sidecarAssets, "infra/bin/darwin_arm64") + + if err != nil { + panic(err) + } + + return sub, true +} diff --git a/embedded_sidecar_dev.go b/embedded_sidecar_dev.go new file mode 100644 index 0000000..abfc3ee --- /dev/null +++ b/embedded_sidecar_dev.go @@ -0,0 +1,11 @@ +//go:build !fmtkit_sidecar + +package fmtkit + +import "io/fs" + +// SidecarAssets reports that this binary carries no TS toolchain: it was +// built without the fmtkit_sidecar build tag. +func SidecarAssets() (fs.FS, bool) { + return nil, false +} diff --git a/embedded_sidecar_linux_amd64.go b/embedded_sidecar_linux_amd64.go new file mode 100644 index 0000000..76b1fa0 --- /dev/null +++ b/embedded_sidecar_linux_amd64.go @@ -0,0 +1,22 @@ +//go:build fmtkit_sidecar && linux && amd64 + +package fmtkit + +import ( + "embed" + "io/fs" +) + +//go:embed infra/bin/linux_amd64 +var sidecarAssets embed.FS + +// SidecarAssets returns the TS toolchain staged for this platform. +func SidecarAssets() (fs.FS, bool) { + sub, err := fs.Sub(sidecarAssets, "infra/bin/linux_amd64") + + if err != nil { + panic(err) + } + + return sub, true +} diff --git a/embedded_sidecar_linux_arm64.go b/embedded_sidecar_linux_arm64.go new file mode 100644 index 0000000..7bd7aab --- /dev/null +++ b/embedded_sidecar_linux_arm64.go @@ -0,0 +1,22 @@ +//go:build fmtkit_sidecar && linux && arm64 + +package fmtkit + +import ( + "embed" + "io/fs" +) + +//go:embed infra/bin/linux_arm64 +var sidecarAssets embed.FS + +// SidecarAssets returns the TS toolchain staged for this platform. +func SidecarAssets() (fs.FS, bool) { + sub, err := fs.Sub(sidecarAssets, "infra/bin/linux_arm64") + + if err != nil { + panic(err) + } + + return sub, true +} diff --git a/infra/scripts/release/stage-ts-assets.sh b/infra/scripts/release/stage-ts-assets.sh index 99ddf3f..1349117 100755 --- a/infra/scripts/release/stage-ts-assets.sh +++ b/infra/scripts/release/stage-ts-assets.sh @@ -25,7 +25,7 @@ if [[ $# -eq 0 ]]; then fi root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" -dist="${FMTKIT_TS_ASSET_DIR:-${root}/packages/driver/internal/tsruntime/embedded/dist}" +dist="${FMTKIT_TS_ASSET_DIR:-${root}/infra/bin}" all_targets=(darwin_arm64 darwin_amd64 linux_arm64 linux_amd64) diff --git a/packages/driver/cmd/fmtkit/main.go b/packages/driver/cmd/fmtkit/main.go index 6cc5664..2985f33 100644 --- a/packages/driver/cmd/fmtkit/main.go +++ b/packages/driver/cmd/fmtkit/main.go @@ -10,6 +10,7 @@ import ( "io" "os" "os/exec" + "strings" "github.com/oullin/fmtkit/packages/driver/internal/cli" "github.com/oullin/fmtkit/packages/driver/internal/orchestrator" @@ -34,19 +35,31 @@ func run(args []string, stdout, stderr io.Writer) int { switch mode { case "format": - quiet, paths := splitQuiet(rest) + opts, paths, err := parseFormatArgs(rest) - return runPipeline(paths, quiet, stderr) + if err != nil { + _, _ = fmt.Fprintf(stderr, "%v\n\n", err) + + printUsage(stderr) + + return 2 + } + + return runPipeline(paths, opts, stderr) case "format-all": - quiet, extra := splitQuiet(rest) + opts, extra, err := parseFormatArgs(rest) + + if err != nil || len(extra) != 0 { + if err != nil { + _, _ = fmt.Fprintf(stderr, "%v\n\n", err) + } - if len(extra) != 0 { printUsage(stderr) return 2 } - return runPipeline([]string{"."}, quiet, stderr) + return runPipeline([]string{"."}, opts, stderr) case "ts": return runTS(rest, stdout, stderr) case "lint": @@ -74,7 +87,7 @@ func run(args []string, stdout, stderr io.Writer) int { } } -func runPipeline(paths []string, quiet bool, stderr io.Writer) int { +func runPipeline(paths []string, opts formatOptions, stderr io.Writer) int { pipeline := orchestrator.Pipeline{ Tools: orchestrator.Tools{ TS: func(scopes []string, output io.Writer) error { @@ -101,7 +114,8 @@ func runPipeline(paths []string, quiet bool, stderr io.Writer) int { Run(cli.FormatMode, args[1:]) }, }, - Quiet: quiet, + Steps: opts.steps, + Quiet: opts.quiet, Stderr: stderr, } @@ -165,22 +179,36 @@ func runGo(args []string, stdout, stderr io.Writer) int { } } -func splitQuiet(args []string) (bool, []string) { - quiet := false +type formatOptions struct { + steps orchestrator.Steps + quiet bool +} - var rest []string +// parseFormatArgs splits the format/format-all flags from the paths. With no +// step flags the whole pipeline runs; --ts and --go narrow it. +func parseFormatArgs(args []string) (formatOptions, []string, error) { + var opts formatOptions - for _, arg := range args { - if arg == "--quiet" || arg == "-q" { - quiet = true + var paths []string - continue + for _, arg := range args { + switch arg { + case "--ts": + opts.steps.TS = true + case "--go": + opts.steps.Go = true + case "--quiet", "-q": + opts.quiet = true + default: + if strings.HasPrefix(arg, "-") { + return formatOptions{}, nil, fmt.Errorf("unknown flag - {%q}", arg) + } + + paths = append(paths, arg) } - - rest = append(rest, arg) } - return quiet, rest + return opts, paths, nil } func reportError(err error, stderr io.Writer) int { @@ -201,8 +229,9 @@ func reportError(err error, stderr io.Writer) int { func printUsage(w io.Writer) { _, _ = fmt.Fprintf(w, "usage: fmtkit [args...]\n") - _, _ = fmt.Fprintf(w, " format [--quiet] [paths...] run TS/Vue support + lint, then Go formatting\n") - _, _ = fmt.Fprintf(w, " format-all [--quiet] run the full formatter pipeline against .\n") + _, _ = fmt.Fprintf(w, " format [--ts] [--go] [--quiet] [paths...] run the formatting pipeline\n") + _, _ = fmt.Fprintf(w, " format-all [--ts] [--go] [--quiet] run the full formatter pipeline against .\n") + _, _ = fmt.Fprintf(w, " --ts only TS/Vue formatting + lint; --go only Go formatting; default: all\n") _, _ = fmt.Fprintf(w, " go run the Go formatter CLI\n") _, _ = fmt.Fprintf(w, " ts [paths...] run TS/Vue formatting support and oxfmt\n") _, _ = fmt.Fprintf(w, " lint [paths...] lint TS/Vue files with oxlint\n") diff --git a/packages/driver/cmd/fmtkit/main_test.go b/packages/driver/cmd/fmtkit/main_test.go index cf74dad..b953bd9 100644 --- a/packages/driver/cmd/fmtkit/main_test.go +++ b/packages/driver/cmd/fmtkit/main_test.go @@ -184,6 +184,86 @@ func TestFormatRunsFullPipeline(t *testing.T) { } } +func TestFormatTSFlagSkipsGoStep(t *testing.T) { + support, logFile := stubSupportDir(t) + + t.Setenv("FMTKIT_SUPPORT_DIR", support) + + workdir := gitWorkdir(t) + + exitCode, _, stderr := runCLI(t, workdir, "format", "--ts", ".") + + if exitCode != 0 { + t.Fatalf("expected exit code 0, got %d\n%s", exitCode, stderr) + } + + if strings.Contains(stderr, "==> Running Go formatting") { + t.Fatalf("--ts still ran the Go step:\n%s", stderr) + } + + log, err := os.ReadFile(logFile) + + if err != nil { + t.Fatalf("read invocation log: %v", err) + } + + if invocations := strings.Split(strings.TrimSpace(string(log)), "\n"); len(invocations) != 2 { + t.Fatalf("expected pipeline + oxlint invocations, got %q", invocations) + } +} + +func TestFormatGoFlagSkipsTSSteps(t *testing.T) { + workdir := gitWorkdir(t) + + // No FMTKIT_SUPPORT_DIR: the TS toolchain is unavailable, so --go must + // succeed without touching it. + exitCode, _, stderr := runCLI(t, workdir, "format", "--go", ".") + + if exitCode != 0 { + t.Fatalf("expected exit code 0, got %d\n%s", exitCode, stderr) + } + + if strings.Contains(stderr, "==> Running TS/Vue formatting") { + t.Fatalf("--go still ran the TS step:\n%s", stderr) + } + + if !strings.Contains(stderr, "==> Running Go formatting") { + t.Fatalf("--go did not run the Go step:\n%s", stderr) + } +} + +func TestFormatBothFlagsRunEverything(t *testing.T) { + support, _ := stubSupportDir(t) + + t.Setenv("FMTKIT_SUPPORT_DIR", support) + + workdir := gitWorkdir(t) + + exitCode, _, stderr := runCLI(t, workdir, "format", "--ts", "--go", ".") + + if exitCode != 0 { + t.Fatalf("expected exit code 0, got %d\n%s", exitCode, stderr) + } + + for _, needle := range []string{"==> Running TS/Vue formatting", "==> Running TS/Vue lint", "==> Running Go formatting"} { + if !strings.Contains(stderr, needle) { + t.Fatalf("stderr missing %q:\n%s", needle, stderr) + } + } +} + +func TestFormatRejectsUnknownFlag(t *testing.T) { + exitCode, _, stderr := runCLI(t, t.TempDir(), "format", "--nope") + + if exitCode != 2 { + t.Fatalf("expected exit code 2, got %d", exitCode) + } + + if !strings.Contains(stderr, "unknown flag") { + t.Fatalf("unexpected stderr:\n%s", stderr) + } +} + func TestFormatAllRejectsExtraArgs(t *testing.T) { exitCode, _, stderr := runCLI(t, t.TempDir(), "format-all", "extra") diff --git a/packages/driver/internal/orchestrator/pipeline.go b/packages/driver/internal/orchestrator/pipeline.go index 116f106..a1effe8 100644 --- a/packages/driver/internal/orchestrator/pipeline.go +++ b/packages/driver/internal/orchestrator/pipeline.go @@ -16,9 +16,25 @@ type Tools struct { Go func(args []string, output io.Writer) int } +// Steps selects which parts of the pipeline run; the zero value (no +// selection flags) runs everything. +type Steps struct { + TS bool + Go bool +} + +func (s Steps) normalized() Steps { + if !s.TS && !s.Go { + return Steps{TS: true, Go: true} + } + + return s +} + // Pipeline renders sectioned progress on Stderr while running the steps. type Pipeline struct { Tools Tools + Steps Steps // Quiet restores the entrypoint's summary-only output; tool logs then // only appear when a step fails. @@ -39,32 +55,43 @@ func (p Pipeline) RunFormat(paths []string) int { log.section("Formatting target(s)") log.detail("paths", strings.Join(paths, " ")) - steps := []struct { + selected := p.Steps.normalized() + + type step struct { label string summarize func(string, *logger) run func(output io.Writer) int - }{ - { - label: "Running TS/Vue formatting", - summarize: summarizeTSFormat, - run: func(output io.Writer) int { - return exitCode(p.Tools.TS(paths, output), output) + } + + var steps []step + + if selected.TS { + steps = append(steps, + step{ + label: "Running TS/Vue formatting", + summarize: summarizeTSFormat, + run: func(output io.Writer) int { + return exitCode(p.Tools.TS(paths, output), output) + }, }, - }, - { - label: "Running TS/Vue lint", - summarize: summarizeTSLint, - run: func(output io.Writer) int { - return exitCode(p.Tools.Lint(paths, output), output) + step{ + label: "Running TS/Vue lint", + summarize: summarizeTSLint, + run: func(output io.Writer) int { + return exitCode(p.Tools.Lint(paths, output), output) + }, }, - }, - { + ) + } + + if selected.Go { + steps = append(steps, step{ label: "Running Go formatting", summarize: summarizeGoFormat, run: func(output io.Writer) int { return p.Tools.Go(append([]string{"format"}, paths...), output) }, - }, + }) } for _, step := range steps { diff --git a/packages/driver/internal/tsruntime/embedded/.gitignore b/packages/driver/internal/tsruntime/embedded/.gitignore deleted file mode 100644 index 849ddff..0000000 --- a/packages/driver/internal/tsruntime/embedded/.gitignore +++ /dev/null @@ -1 +0,0 @@ -dist/ diff --git a/packages/driver/internal/tsruntime/embedded/embedded_darwin_amd64.go b/packages/driver/internal/tsruntime/embedded/embedded_darwin_amd64.go deleted file mode 100644 index f046d54..0000000 --- a/packages/driver/internal/tsruntime/embedded/embedded_darwin_amd64.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build fmtkit_sidecar && darwin && amd64 - -package embedded - -import ( - "embed" - "io/fs" -) - -//go:embed dist/darwin_amd64 -var assets embed.FS - -// Assets returns the TS toolchain staged for this platform. -func Assets() (fs.FS, bool) { - sub, err := fs.Sub(assets, "dist/darwin_amd64") - - if err != nil { - panic(err) - } - - return sub, true -} diff --git a/packages/driver/internal/tsruntime/embedded/embedded_darwin_arm64.go b/packages/driver/internal/tsruntime/embedded/embedded_darwin_arm64.go deleted file mode 100644 index 2a9335f..0000000 --- a/packages/driver/internal/tsruntime/embedded/embedded_darwin_arm64.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build fmtkit_sidecar && darwin && arm64 - -package embedded - -import ( - "embed" - "io/fs" -) - -//go:embed dist/darwin_arm64 -var assets embed.FS - -// Assets returns the TS toolchain staged for this platform. -func Assets() (fs.FS, bool) { - sub, err := fs.Sub(assets, "dist/darwin_arm64") - - if err != nil { - panic(err) - } - - return sub, true -} diff --git a/packages/driver/internal/tsruntime/embedded/embedded_dev.go b/packages/driver/internal/tsruntime/embedded/embedded_dev.go deleted file mode 100644 index 5cbb1dd..0000000 --- a/packages/driver/internal/tsruntime/embedded/embedded_dev.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !fmtkit_sidecar - -// Package embedded carries the per-platform TS toolchain assets that -// infra/scripts/release/stage-ts-assets.sh places under dist/. Only builds -// tagged fmtkit_sidecar embed them; regular builds stay lightweight and rely -// on FMTKIT_SUPPORT_DIR instead. -package embedded - -import "io/fs" - -// Assets reports that this binary carries no TS toolchain: it was built -// without the fmtkit_sidecar build tag. -func Assets() (fs.FS, bool) { - return nil, false -} diff --git a/packages/driver/internal/tsruntime/embedded/embedded_linux_amd64.go b/packages/driver/internal/tsruntime/embedded/embedded_linux_amd64.go deleted file mode 100644 index bf21fd2..0000000 --- a/packages/driver/internal/tsruntime/embedded/embedded_linux_amd64.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build fmtkit_sidecar && linux && amd64 - -package embedded - -import ( - "embed" - "io/fs" -) - -//go:embed dist/linux_amd64 -var assets embed.FS - -// Assets returns the TS toolchain staged for this platform. -func Assets() (fs.FS, bool) { - sub, err := fs.Sub(assets, "dist/linux_amd64") - - if err != nil { - panic(err) - } - - return sub, true -} diff --git a/packages/driver/internal/tsruntime/embedded/embedded_linux_arm64.go b/packages/driver/internal/tsruntime/embedded/embedded_linux_arm64.go deleted file mode 100644 index f98634c..0000000 --- a/packages/driver/internal/tsruntime/embedded/embedded_linux_arm64.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build fmtkit_sidecar && linux && arm64 - -package embedded - -import ( - "embed" - "io/fs" -) - -//go:embed dist/linux_arm64 -var assets embed.FS - -// Assets returns the TS toolchain staged for this platform. -func Assets() (fs.FS, bool) { - sub, err := fs.Sub(assets, "dist/linux_arm64") - - if err != nil { - panic(err) - } - - return sub, true -} diff --git a/packages/driver/internal/tsruntime/run_test.go b/packages/driver/internal/tsruntime/run_test.go index 5f8c6cb..e66b94a 100644 --- a/packages/driver/internal/tsruntime/run_test.go +++ b/packages/driver/internal/tsruntime/run_test.go @@ -194,6 +194,31 @@ func TestRunLintInvokesOxlintMode(t *testing.T) { } } +func TestRunLintSkipsBundledConfigWhenProjectHasOne(t *testing.T) { + repo := gitScratchRepo(t, map[string]string{ + "app.ts": "export const a = 1;\n", + ".oxlintrc.json": "{}", + }) + + support := supportWithStub(t) + + if err := os.WriteFile(filepath.Join(support.Dir, ".oxlintrc.json"), []byte("{}"), 0o644); err != nil { + t.Fatalf("write bundled config: %v", err) + } + + t.Setenv(SourcesCwdEnv, repo) + + var stdout, stderr bytes.Buffer + + if err := support.RunLint(RunOptions{Stdout: &stdout, Stderr: &stderr}); err != nil { + t.Fatalf("RunLint: %v\nstderr: %s", err, stderr.String()) + } + + if strings.Contains(stdout.String(), "--config") { + t.Fatalf("bundled config passed despite project config:\n%s", stdout.String()) + } +} + func TestRunLintSkipsSpawnWithoutFiles(t *testing.T) { repo := gitScratchRepo(t, map[string]string{"main.go": "package main\n"}) diff --git a/packages/driver/internal/tsruntime/support.go b/packages/driver/internal/tsruntime/support.go index df32c79..91c6a77 100644 --- a/packages/driver/internal/tsruntime/support.go +++ b/packages/driver/internal/tsruntime/support.go @@ -16,7 +16,6 @@ import ( "sort" fmtkit "github.com/oullin/fmtkit" - "github.com/oullin/fmtkit/packages/driver/internal/tsruntime/embedded" ) // SupportDirEnv points at a pre-extracted toolchain directory and skips @@ -73,7 +72,7 @@ func Resolve(version string) (Support, error) { return support, nil } - assets, ok := embedded.Assets() + assets, ok := fmtkit.SidecarAssets() if !ok { return Support{}, errors.New( From 02f5682ddac3d5ad72b33d6925f73795b6cabdff Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 16 Jul 2026 11:20:19 +0800 Subject: [PATCH 4/8] docs: document distributed binary usage and --ts/--go flags --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 90feb67..f5d12fc 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,19 @@ If `fmtkit-go` is not on your `PATH` after `go install`, add the Go bin director ## Usage +The distributed `fmtkit` binary runs the whole pipeline (TS/Vue formatting, TS/Vue lint, Go formatting) with `format` / `format-all`, and narrows it with step flags: + +```bash +fmtkit format . # everything (default) +fmtkit format --ts . # TS/Vue formatting + lint only +fmtkit format --go . # Go formatting only +fmtkit format-all --quiet +``` + +`ts`, `lint`, `go `, `check`, `version`, and `help` are also available; `fmtkit help` lists them. + +The `fmtkit-go` CLI (the Go-only formatter used by the Docker images and `go install`) accepts: + | Command | What it does | | ------------------- | ----------------------------------- | | `check [paths...]` | Reports violations without writing. | From c0c0d90d4d143a7e32524a2a30a71b6f6ba4189a Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 16 Jul 2026 12:54:00 +0800 Subject: [PATCH 5/8] fix formatter --- .dockerignore | 50 --- .github/workflows/cleanup-images.yml | 43 --- .github/workflows/docker-smoke.yml | 53 --- .github/workflows/publish-release.yml | 127 +------ .github/workflows/release.yml | 3 - .github/workflows/tests.yml | 3 - .gitignore | 8 +- .goreleaser.yaml | 6 +- Makefile | 30 +- README.md | 129 +++---- embedded.go | 20 -- infra/bin/fmtkit | 204 ----------- infra/bin/fmtkit-lint | 17 - infra/bin/fmtkit-ts | 17 - infra/bin/fmtkit-ts-files | 92 ----- infra/bin/fmtkit-ts-lint | 78 ---- infra/docker/Dockerfile.full | 88 ----- infra/docker/Dockerfile.golang | 55 --- infra/docker/Dockerfile.node-ts | 38 -- infra/scripts/host-target.sh | 29 ++ infra/scripts/release/image/assertions.sh | 55 --- infra/scripts/release/image/config.sh | 17 - infra/scripts/release/image/docker.sh | 13 - infra/scripts/release/image/fixtures.sh | 46 --- infra/scripts/release/image/full.sh | 18 - infra/scripts/release/image/go.sh | 30 -- infra/scripts/release/image/node-ts.sh | 18 - infra/scripts/release/stage-ts-assets.sh | 43 +-- infra/scripts/release/verify-release-image.sh | 17 - infra/scripts/tasks/docker-env.sh | 35 -- infra/scripts/tasks/docker-image.sh | 42 --- infra/scripts/tasks/fmtkit-host.sh | 49 --- infra/scripts/tasks/fmtkit.sh | 43 +++ infra/scripts/tasks/format-docker.sh | 108 ------ infra/scripts/tasks/format.sh | 40 +-- .../scripts/tasks/install-docker-makefile.sh | 25 -- infra/scripts/tasks/test-binary-smoke.sh | 7 +- infra/scripts/tasks/test-entrypoints.sh | 9 - infra/scripts/tasks/test-fmtkit-entrypoint.sh | 147 -------- .../tasks/test-fmtkit-host-entrypoint.sh | 54 --- .../tasks/test-fmtkit-lint-entrypoint.sh | 76 ---- .../tasks/test-fmtkit-ts-entrypoint.sh | 67 ---- infra/scripts/tasks/test-format.sh | 334 ------------------ infra/tooling/docker/Makefile | 73 ---- packages/devx/scripts/format-all.ts | 7 +- packages/driver/cmd/fmtkit-go/main.go | 4 +- packages/driver/cmd/fmtkit-go/main_test.go | 130 +------ packages/driver/cmd/fmtkit/main.go | 240 +------------ packages/driver/internal/app/app.go | 71 ++++ .../main_test.go => internal/app/app_test.go} | 6 +- packages/driver/internal/app/doc.go | 5 + packages/driver/internal/app/exit.go | 25 ++ packages/driver/internal/app/format.go | 78 ++++ packages/driver/internal/app/golang.go | 42 +++ packages/driver/internal/app/options.go | 40 +++ packages/driver/internal/app/ts.go | 25 ++ packages/driver/internal/app/usage.go | 24 ++ packages/driver/internal/cli/host_path.go | 62 ---- .../driver/internal/cli/host_path_test.go | 99 ------ packages/driver/internal/cli/options.go | 1 - packages/driver/internal/cli/parser.go | 2 - packages/driver/internal/cli/parser_test.go | 2 - packages/driver/internal/cli/runner.go | 8 +- packages/driver/internal/cli/runner_test.go | 18 - packages/driver/internal/embedded/doc.go | 7 + .../internal/embedded/sidecar_darwin_amd64.go | 9 +- .../internal/embedded/sidecar_darwin_arm64.go | 9 +- .../driver/internal/embedded/sidecar_dev.go | 2 +- .../internal/embedded/sidecar_linux_amd64.go | 9 +- .../internal/embedded/sidecar_linux_arm64.go | 9 +- .../driver/internal/orchestrator/logging.go | 7 +- .../driver/internal/orchestrator/pipeline.go | 18 +- .../internal/orchestrator/pipeline_test.go | 13 +- .../driver/internal/orchestrator/summarize.go | 5 +- packages/driver/internal/tsruntime/run.go | 13 +- packages/driver/internal/tsruntime/support.go | 18 +- .../driver/internal/tsruntime/support_test.go | 4 + vite.config.ts | 11 +- 78 files changed, 589 insertions(+), 2890 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .github/workflows/cleanup-images.yml delete mode 100644 .github/workflows/docker-smoke.yml delete mode 100644 embedded.go delete mode 100755 infra/bin/fmtkit delete mode 100755 infra/bin/fmtkit-lint delete mode 100755 infra/bin/fmtkit-ts delete mode 100755 infra/bin/fmtkit-ts-files delete mode 100755 infra/bin/fmtkit-ts-lint delete mode 100644 infra/docker/Dockerfile.full delete mode 100644 infra/docker/Dockerfile.golang delete mode 100644 infra/docker/Dockerfile.node-ts create mode 100755 infra/scripts/host-target.sh delete mode 100644 infra/scripts/release/image/assertions.sh delete mode 100644 infra/scripts/release/image/config.sh delete mode 100644 infra/scripts/release/image/docker.sh delete mode 100644 infra/scripts/release/image/fixtures.sh delete mode 100644 infra/scripts/release/image/full.sh delete mode 100644 infra/scripts/release/image/go.sh delete mode 100644 infra/scripts/release/image/node-ts.sh delete mode 100755 infra/scripts/release/verify-release-image.sh delete mode 100755 infra/scripts/tasks/docker-env.sh delete mode 100755 infra/scripts/tasks/docker-image.sh delete mode 100755 infra/scripts/tasks/fmtkit-host.sh create mode 100755 infra/scripts/tasks/fmtkit.sh delete mode 100755 infra/scripts/tasks/format-docker.sh delete mode 100755 infra/scripts/tasks/install-docker-makefile.sh delete mode 100755 infra/scripts/tasks/test-entrypoints.sh delete mode 100755 infra/scripts/tasks/test-fmtkit-entrypoint.sh delete mode 100755 infra/scripts/tasks/test-fmtkit-host-entrypoint.sh delete mode 100755 infra/scripts/tasks/test-fmtkit-lint-entrypoint.sh delete mode 100755 infra/scripts/tasks/test-fmtkit-ts-entrypoint.sh delete mode 100755 infra/scripts/tasks/test-format.sh delete mode 100644 infra/tooling/docker/Makefile create mode 100644 packages/driver/internal/app/app.go rename packages/driver/{cmd/fmtkit/main_test.go => internal/app/app_test.go} (98%) create mode 100644 packages/driver/internal/app/doc.go create mode 100644 packages/driver/internal/app/exit.go create mode 100644 packages/driver/internal/app/format.go create mode 100644 packages/driver/internal/app/golang.go create mode 100644 packages/driver/internal/app/options.go create mode 100644 packages/driver/internal/app/ts.go create mode 100644 packages/driver/internal/app/usage.go delete mode 100644 packages/driver/internal/cli/host_path.go delete mode 100644 packages/driver/internal/cli/host_path_test.go create mode 100644 packages/driver/internal/embedded/doc.go rename embedded_sidecar_darwin_amd64.go => packages/driver/internal/embedded/sidecar_darwin_amd64.go (53%) rename embedded_sidecar_darwin_arm64.go => packages/driver/internal/embedded/sidecar_darwin_arm64.go (53%) rename embedded_sidecar_dev.go => packages/driver/internal/embedded/sidecar_dev.go (92%) rename embedded_sidecar_linux_amd64.go => packages/driver/internal/embedded/sidecar_linux_amd64.go (53%) rename embedded_sidecar_linux_arm64.go => packages/driver/internal/embedded/sidecar_linux_arm64.go (53%) diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index e4846da..0000000 --- a/.dockerignore +++ /dev/null @@ -1,50 +0,0 @@ -# Version control -.git -.github - -# IDE / Editor -.idea -.vscode -*.swp -*.swo -*~ - -# OS -.DS_Store -Thumbs.db - -# Go caches -# Storage-managed caches and build artifacts -storage/bin*/ -storage/dist/ -storage/dist-test/ -storage/.cache/ -builds - -# Node (not needed for Go Docker build) -node_modules -infra/tooling/node_modules - -# Docs and tests (never needed inside images) -docs/ -*.md -**/testdata -**/*_test.go -**/__tests__ -**/*.test.ts - -# Runtime / generated -config.yml -coverage.out - -# Compiled binaries -*.exe -*.exe~ -*.dll -*.so -*.dylib -*.test -*.out - -# Claude -.claude diff --git a/.github/workflows/cleanup-images.yml b/.github/workflows/cleanup-images.yml deleted file mode 100644 index f0ee700..0000000 --- a/.github/workflows/cleanup-images.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Cleanup Old Container Images - -# Prunes stale fmtkit container versions from GHCR so old release tags do not -# accumulate forever. Protects every `latest*` tag and the 2 most recent -# versions; only deletes versions older than the cut-off beyond those. -# -# Requires a PAT secret `GHCR_CLEANUP_TOKEN` with `packages:delete` scope: -# the default GITHUB_TOKEN cannot use tag-filter operators (e.g. `!latest*`). -# Scheduled runs delete for real; manual runs default to a dry-run preview. - -on: - schedule: - - cron: '0 4 * * 1' # Mondays 04:00 UTC - workflow_dispatch: - inputs: - dry-run: - description: Only list what would be deleted, without deleting - type: boolean - default: true - -permissions: - packages: write - -concurrency: - group: cleanup-images - cancel-in-progress: false - -jobs: - cleanup: - runs-on: ubuntu-latest - - steps: - - name: Prune old fmtkit image versions - uses: snok/container-retention-policy@v3.1.0 - with: - account: oullin # change to "user" if oullin is a personal account - token: ${{ secrets.GHCR_CLEANUP_TOKEN }} - image-names: fmtkit - image-tags: '!latest*' # never delete latest, latest-go, latest-node-ts, latest-full - tag-selection: both - cut-off: 12w - keep-n-most-recent: 2 - dry-run: ${{ github.event_name == 'schedule' && 'false' || inputs.dry-run }} diff --git a/.github/workflows/docker-smoke.yml b/.github/workflows/docker-smoke.yml deleted file mode 100644 index 01d22a9..0000000 --- a/.github/workflows/docker-smoke.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Docker Smoke - -# Build-only verification that the Docker images still assemble. Runs on -# pull requests that touch the build context inputs; images are never -# pushed. The gha cache scopes are shared with publish-release.yml so -# green smoke builds pre-warm the release builds. -on: - pull_request: - branches: - - main - paths: - - infra/docker/** - - infra/bin/** - - .dockerignore - - infra/scripts/tasks/env.sh - - packages/devx/package.json - - .github/workflows/docker-smoke.yml - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' - -jobs: - build: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - image: go - dockerfile: ./infra/docker/Dockerfile.golang - - image: node-ts - dockerfile: ./infra/docker/Dockerfile.node-ts - - image: full - dockerfile: ./infra/docker/Dockerfile.full - - steps: - - uses: actions/checkout@v6 - name: Checkout repository - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Build ${{ matrix.image }} image - uses: docker/build-push-action@v7 - with: - context: . - file: ${{ matrix.dockerfile }} - push: false - platforms: linux/amd64 - build-args: | - VERSION=smoke - cache-from: type=gha,scope=${{ matrix.image }} - cache-to: type=gha,mode=max,scope=${{ matrix.image }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index a111fb1..50ab188 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -18,10 +18,8 @@ on: permissions: contents: write - packages: write env: - IMAGE_NAME: ghcr.io/oullin/fmtkit FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' RELEASE_TAG: ${{ inputs.tag }} @@ -30,120 +28,9 @@ concurrency: cancel-in-progress: false jobs: - publish: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Verify release tag and checkout tagged commit - run: ./infra/scripts/release/verify-release-tag.sh - shell: bash - - - name: Set up QEMU - uses: docker/setup-qemu-action@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Log in to GHCR - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push Go image - id: build-go - uses: docker/build-push-action@v7 - with: - context: . - file: ./infra/docker/Dockerfile.golang - push: true - platforms: linux/amd64,linux/arm64 - tags: | - ${{ env.IMAGE_NAME }}:${{ env.RELEASE_TAG }}-go - ${{ env.IMAGE_NAME }}:latest-go - build-args: | - VERSION=${{ env.RELEASE_TAG }} - cache-from: type=gha,scope=go - cache-to: type=gha,mode=max,scope=go - - - name: Build and push Node/TS image - id: build-node-ts - uses: docker/build-push-action@v7 - with: - context: . - file: ./infra/docker/Dockerfile.node-ts - push: true - platforms: linux/amd64,linux/arm64 - tags: | - ${{ env.IMAGE_NAME }}:${{ env.RELEASE_TAG }}-node-ts - ${{ env.IMAGE_NAME }}:latest-node-ts - cache-from: type=gha,scope=node-ts - cache-to: type=gha,mode=max,scope=node-ts - - - name: Build and push full image - id: build-full - uses: docker/build-push-action@v7 - with: - context: . - file: ./infra/docker/Dockerfile.full - push: true - platforms: linux/amd64,linux/arm64 - tags: | - ${{ env.IMAGE_NAME }}:${{ env.RELEASE_TAG }}-full - ${{ env.IMAGE_NAME }}:latest-full - build-args: | - VERSION=${{ env.RELEASE_TAG }} - cache-from: type=gha,scope=full - cache-to: type=gha,mode=max,scope=full - - - name: Verify published release image - env: - NEW_TAG: ${{ env.RELEASE_TAG }} - run: ./infra/scripts/release/verify-release-image.sh - shell: bash - - - name: Promote verified full image to compatibility tags - env: - DIGEST: ${{ steps.build-full.outputs.digest }} - run: | - set -euo pipefail - - docker buildx imagetools create \ - --tag "${IMAGE_NAME}:${RELEASE_TAG}" \ - --tag "${IMAGE_NAME}:latest" \ - "${IMAGE_NAME}@${DIGEST}" - shell: bash - - - name: Create or update release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ env.RELEASE_TAG }} - prerelease: false - generate_release_notes: true - body: | - Docker images published: - - - `${{ env.IMAGE_NAME }}:${{ env.RELEASE_TAG }}-go` - - `${{ env.IMAGE_NAME }}:${{ env.RELEASE_TAG }}-node-ts` - - `${{ env.IMAGE_NAME }}:${{ env.RELEASE_TAG }}-full` - - `${{ env.IMAGE_NAME }}:${{ env.RELEASE_TAG }}` aliases the verified full image - - Latest tags updated: - - - `${{ env.IMAGE_NAME }}:latest-go` - - `${{ env.IMAGE_NAME }}:latest-node-ts` - - `${{ env.IMAGE_NAME }}:latest-full` - - `${{ env.IMAGE_NAME }}:latest` aliases the verified full image - - # Attaches the self-contained fmtkit binaries to the release created by - # the publish job and pushes the Homebrew cask to oullin/homebrew-fmtkit. + # Creates the release, attaches the self-contained fmtkit binaries to it, and + # pushes the Homebrew cask to oullin/homebrew-fmtkit. binaries: - needs: publish runs-on: ubuntu-latest steps: @@ -173,6 +60,16 @@ jobs: go.mod go.sum + # Creates the release up front so goreleaser (release.mode + # keep-existing) attaches its assets to it, and so the notes come + # from GitHub's generator rather than goreleaser's changelog. + - name: Create or update release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ env.RELEASE_TAG }} + prerelease: false + generate_release_notes: true + - name: Build and publish release binaries uses: goreleaser/goreleaser-action@v6 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff165d9..eba3d87 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,9 +56,6 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Shell - Run entrypoint tests - run: ./infra/scripts/tasks/test-entrypoints.sh - - uses: voidzero-dev/setup-vp@v1 with: node-version: 25.8.2 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 788b3bb..c410ca1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -94,9 +94,6 @@ jobs: - uses: actions/checkout@v6 name: Checkout repository - - name: Shell - Run entrypoint tests - run: ./infra/scripts/tasks/test-entrypoints.sh - - uses: voidzero-dev/setup-vp@v1 name: Install Vite+ with: diff --git a/.gitignore b/.gitignore index 5b379af..f6a68a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,9 @@ /fmt -/dist/ *.bun-build -# TS toolchain assets staged per platform by stage-ts-assets.sh; the tracked -# entrypoint scripts in infra/bin itself stay versioned. -/infra/bin/*/ +# TS toolchain assets staged per platform by stage-ts-assets.sh, next to the +# package that embeds them. +/packages/driver/internal/embedded/bin/ /storage/bin*/ /storage/dist/ /storage/dist-test/ @@ -17,7 +16,6 @@ *.test *.out node_modules/ -infra/tooling/node_modules/ # IDE .idea/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 53af82c..8c620b6 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -2,6 +2,10 @@ version: 2 project_name: fmtkit +# Build output belongs under storage/ like every other artifact this repo +# produces; infra/scripts/tasks/env.sh rejects a repo-root dist/. +dist: storage/dist + # Releases stage every platform; PR validation sets FMTKIT_STAGE_TARGETS=host # to stage only the runner's platform before a --single-target build. env: @@ -45,7 +49,7 @@ changelog: disable: true release: - # publish-release.yml creates the GitHub Release (with the Docker image + # publish-release.yml creates the GitHub Release (with GitHub's generated # notes) before this runs; goreleaser only attaches the binary assets. mode: keep-existing diff --git a/Makefile b/Makefile index 6483f09..5b22e8c 100644 --- a/Makefile +++ b/Makefile @@ -1 +1,29 @@ -include infra/tooling/docker/Makefile +SHELL := /bin/bash +.DEFAULT_GOAL := help + +# fmtkit formats itself with the binary it ships: these targets run the same Go +# orchestrator and bun-compiled TS sidecar that a release carries. +ARGS ?= . + +.PHONY: help format format-all check version + +help: ## Show the available targets + @printf 'fmtkit\n\n' + @printf ' make format Run the formatter pipeline against ARGS (default ".")\n' + @printf ' make format-all Run the formatter pipeline against the whole repository\n' + @printf ' make check Run the Go formatter in check mode against ARGS\n' + @printf ' make version Print the version the working tree builds as\n' + @printf '\nAdd --ts or --go to ARGS to run only that half of the pipeline.\n' + @printf '\nVariables: ARGS\n' + +format: ## Run the formatter pipeline against ARGS + @./infra/scripts/tasks/format.sh $(ARGS) + +format-all: ## Run the formatter pipeline against the whole repository + @./infra/scripts/tasks/fmtkit.sh format-all + +check: ## Run the Go formatter in check mode against ARGS + @./infra/scripts/tasks/fmtkit.sh check $(ARGS) + +version: ## Print the version the working tree builds as + @./infra/scripts/tasks/fmtkit.sh version diff --git a/README.md b/README.md index f5d12fc..56e6d92 100644 --- a/README.md +++ b/README.md @@ -5,23 +5,22 @@ [![Tests](https://github.com/oullin/fmtkit/actions/workflows/tests.yml/badge.svg)](https://github.com/oullin/fmtkit/actions/workflows/tests.yml) [![Release](https://github.com/oullin/fmtkit/actions/workflows/release.yml/badge.svg)](https://github.com/oullin/fmtkit/actions/workflows/release.yml) [![Codecov](https://codecov.io/gh/oullin/fmtkit/graph/badge.svg?branch=main)](https://app.codecov.io/github/oullin/fmtkit) -[![Docker](https://img.shields.io/badge/docker-ghcr.io%2Foullin%2Ffmtkit-2496ED?logo=docker&logoColor=white)](https://github.com/oullin/fmtkit/pkgs/container/fmtkit) -`fmtkit` is a rule-driven formatter for Go. It enforces layout and structure that `gofmt` leaves alone — blank lines around control flow, type hoisting, declaration grouping — then hands off to `gofmt` and `goimports` for the final pass. You can run it as a local CLI or as a shared Docker image; either way the output is the same. +`fmtkit` is a rule-driven formatter for Go. It enforces layout and structure that `gofmt` leaves alone — blank lines around control flow, type hoisting, declaration grouping — then hands off to `gofmt` and `goimports` for the final pass. ## At a glance - AST-based spacing rules, then `gofmt` and `goimports`, in one deterministic pipeline. - Runs `go vet ./...` automatically when invoked inside a Go module or workspace. - Three output modes: `text` for humans, `json` for scripts, `agent` for CI and AI tools. -- One self-contained `fmtkit` binary (Homebrew or GitHub Releases), a Go-only CLI (`fmtkit-go`), or one Docker image with a thin host wrapper. +- One self-contained `fmtkit` binary (Homebrew or GitHub Releases), or a Go-only CLI (`fmtkit-go`). - Engine in [`packages/formatter/engine`](packages/formatter/engine) is importable from Go. ## Install -Three ways to run it. Pick the one that fits your workflow — all produce identical output. +Two ways to run it. Pick the one that fits your workflow — both produce identical output. -**With Homebrew** (recommended: one self-contained binary with the full TS/Vue + Go pipeline — no Node.js, no Docker): +**With Homebrew** (recommended: one self-contained binary with the full TS/Vue + Go pipeline — no Node.js required): ```bash brew tap oullin/fmtkit @@ -46,18 +45,10 @@ fmtkit-go check . fmtkit-go format . ``` -**With Docker** (good for CI and pinning one version across many projects): - -```bash -curl -fsSL -o /tmp/fmtkit-host.sh https://raw.githubusercontent.com/oullin/fmtkit/main/infra/scripts/tasks/fmtkit-host.sh -sudo install -m 0755 /tmp/fmtkit-host.sh /usr/local/bin/fmtkit -fmtkit format . -``` - -The Docker wrapper mounts the current directory at `/work` and reuses a shared `fmtkit-cache` volume across projects, so you do not need a per-project Dockerfile or version pin. Pin a specific release for a single run with `FMTKIT_IMAGE=ghcr.io/oullin/fmtkit:v0.0.18 fmtkit …`, or swap in a flavour tag (`latest-go`, `latest-node-ts`, `latest-full`) when you only need one formatter layer. - If `fmtkit-go` is not on your `PATH` after `go install`, add the Go bin directory: `export PATH="$(go env GOPATH)/bin:$PATH"`. +For CI, download the release binary for the runner's platform and pin the tag — it needs no daemon, no image pull, and no Node.js. + ## Usage The distributed `fmtkit` binary runs the whole pipeline (TS/Vue formatting, TS/Vue lint, Go formatting) with `format` / `format-all`, and narrows it with step flags: @@ -71,7 +62,7 @@ fmtkit format-all --quiet `ts`, `lint`, `go `, `check`, `version`, and `help` are also available; `fmtkit help` lists them. -The `fmtkit-go` CLI (the Go-only formatter used by the Docker images and `go install`) accepts: +The `fmtkit-go` CLI (the Go-only formatter published via `go install`) accepts: | Command | What it does | | ------------------- | ----------------------------------- | @@ -80,13 +71,12 @@ The `fmtkit-go` CLI (the Go-only formatter used by the Docker images and `go ins Both default to `.` when no paths are given. Both run `go vet ./...` automatically when the working directory is inside a Go module or workspace. -| Flag | Default | Description | -| ------------- | ------- | ------------------------------------------------------------------------ | -| `--config` | auto | Path to a `config.yml`. Auto-detected if omitted. | -| `--cwd` | `.` | Base path for config discovery and relative output paths. | -| `--format` | `text` | Output mode: `text`, `json`, or `agent`. | -| `--jobs` | `0` | Max files in parallel; `0` uses `runtime.NumCPU()`. Reads `FMTKIT_JOBS`. | -| `--host-path` | off | Absolute host path under `HOST_PROJECT_PATH` (Docker host-mount flow). | +| Flag | Default | Description | +| ---------- | ------- | ------------------------------------------------------------------------ | +| `--config` | auto | Path to a `config.yml`. Auto-detected if omitted. | +| `--cwd` | `.` | Base path for config discovery and relative output paths. | +| `--format` | `text` | Output mode: `text`, `json`, or `agent`. | +| `--jobs` | `0` | Max files in parallel; `0` uses `runtime.NumCPU()`. Reads `FMTKIT_JOBS`. | A handful of common invocations: @@ -141,7 +131,7 @@ concurrency: 0 ### TS/Vue formatting (`.oxfmtrc.json`) -The TS/Vue layer runs [`oxfmt`](https://www.npmjs.com/package/oxfmt) over your sources, then applies project-specific syntax passes for blank lines and fluent builder chains. The images ship a bundled `.oxfmtrc.json` (tabs, single quotes, trailing commas, 200-column width) that is applied by default, so you get the same style out of the box without any setup. +The TS/Vue layer runs [`oxfmt`](https://www.npmjs.com/package/oxfmt) over your sources, then applies project-specific syntax passes for blank lines and fluent builder chains. The binary ships a bundled `.oxfmtrc.json` (tabs, single quotes, trailing commas, 200-column width) that is applied by default, so you get the same style out of the box without any setup. A project-local oxfmt config takes precedence: if the directory being formatted contains its own `.oxfmtrc.*` (`.json`, `.jsonc`, `.ts`, `.js`, …), the bundled default is skipped and oxfmt uses yours. Override the bundled path explicitly with the `FMTKIT_OXFMTRC` environment variable, matching the other `FMTKIT_*` knobs. @@ -210,47 +200,6 @@ When given directories, the engine walks recursively for `.go` files and always } ``` -## Docker images - -Published to `ghcr.io/oullin/fmtkit` for `linux/amd64` and `linux/arm64`. - -| Tag | Contents | Entrypoint | -| --------------------------------- | ----------------------------------- | ----------- | -| `latest`, `` | Full Go + TS/Vue formatter (alias). | `fmtkit` | -| `latest-full`, `-full` | Full Go + TS/Vue formatter. | `fmtkit` | -| `latest-go`, `-go` | Go formatter CLI only. | `fmtkit-go` | -| `latest-node-ts`, `-node-ts` | TS/Vue formatter only. | `fmtkit-ts` | - -The Go-containing images bundle a trimmed Go SDK (Go's own test suite, API data, -std-library test fixtures, and unused tool binaries are stripped) because both -`goimports` and `go vet` invoke the `go` toolchain at runtime. If the toolchain -is ever absent, `go vet` is skipped gracefully rather than failing. - -### Docker maintenance - -Reclaim space taken by fmtkit's own Docker artifacts (local `*:local` images, -fingerprinted build images, and the `fmtkit-cache` volume) without touching -unrelated Docker state: - -```bash -vp run docker:clean -``` - -`make docker-clean` is available as a Docker compatibility alias for the same -cleanup flow. - -The global Docker **build cache** is shared across all projects and cannot be -pruned per project. Cap it so it can't grow unbounded by adding this to -`~/.docker/daemon.json` and restarting Docker: - -```json -{ "builder": { "gc": { "enabled": true, "defaultKeepStorage": "20GB" } } } -``` - -fmtkit's own run scripts already use `docker run --rm`, so normal usage leaves no -stopped containers behind. Old `ghcr.io/oullin/fmtkit` tags on the registry are -pruned automatically by the `Cleanup Old Container Images` workflow. - ## Exit codes | Command | Code | Meaning | @@ -262,7 +211,7 @@ pruned automatically by the `Cleanup Old Container Images` workflow. ## Development -You will need Go 1.26.4+, Vite+, and a Docker runtime if you plan to touch the published images. Vite+ manages the project Node.js runtime and pnpm version declared by the workspace. +You will need Go 1.26.4+, Vite+, and [Bun](https://bun.com) (used to compile the TS sidecar the binary embeds). Vite+ manages the project Node.js runtime and pnpm version declared by the workspace. ```bash curl -fsSL https://vite.plus -o install-vp.sh @@ -273,35 +222,39 @@ vp install Use Vite+ tasks for day-to-day development: ```bash -vp run build # build the local fmtkit-go binary into storage/bin -vp run check # run package checks across the workspace -vp run test # run all package tests -vp run test-race # tests with the race detector (forces CGO_ENABLED=1) -vp run vet # run go vet across the Go module packages -vp run format:local -- . # run the local formatter pipeline -vp run format:docker -- . # run the Dockerized formatter pipeline -vp run install-cli # install fmtkit-go from the local source tree -vp run release # build cross-platform binaries into storage/dist +vp run build # build the local fmtkit-go binary into storage/bin +vp run check # run package checks across the workspace +vp run test # run all package tests +vp run test-race # tests with the race detector (forces CGO_ENABLED=1) +vp run test:binary # build the self-contained binary and smoke test it +vp run vet # run go vet across the Go module packages +vp run format -- . # format this repo with fmtkit's own binary +vp run install-cli # install fmtkit-go from the local source tree +vp run release # build cross-platform binaries into storage/dist ``` -### Docker compatibility Makefile +### Formatting fmtkit with fmtkit -Vite+ is the default developer interface. The root `Makefile` remains as a -Docker compatibility shim for the existing Docker-first workflow: +fmtkit formats itself with the binary it ships, so the development loop and the +release exercise the same Go orchestrator and the same Bun-compiled TS sidecar. +The root `Makefile` is the shortest way in: ```bash -make format # Dockerized formatter against ARGS (default ".") -make format-all # Dockerized formatter against the whole repository -make check # Dockerized fmtkit check against ARGS (default ".") -make image-go # build the local Go-only formatter image -make image-node-ts -make image-full -make docker-clean +make format # format the repo (ARGS defaults to ".") +make format ARGS=--ts # only the TS/Vue half +make format-all # the whole repository +make check # Go formatter in check mode ``` -External consumers do not need this Makefile. They can keep using -`infra/scripts/tasks/fmtkit-host.sh` or the published `ghcr.io/oullin/fmtkit` tags -(`latest`, `latest-go`, `latest-node-ts`, and `latest-full`). +The first run stages the host TS toolchain assets into +`packages/driver/internal/embedded/bin/_/` (this needs Bun and takes a +few seconds); later runs reuse them and re-stage only when the support scripts, +the tool pins, or the `.oxfmtrc.json` / `.oxlintrc.json` configs change. The +inner loop is then a plain `go run`. + +That loop points `FMTKIT_SUPPORT_DIR` at the staged assets rather than embedding +them, which keeps it fast. The embedded-asset path a release actually uses is +covered by `vp run test:binary`. Package layout: diff --git a/embedded.go b/embedded.go deleted file mode 100644 index 27d7856..0000000 --- a/embedded.go +++ /dev/null @@ -1,20 +0,0 @@ -// Package fmtkit exposes repository-level assets embedded into the fmtkit -// binaries. It lives at the module root because go:embed cannot reference -// files above the embedding package; that also covers the TS toolchain -// staged under infra/bin/ by infra/scripts/release/stage-ts-assets.sh, which -// only builds tagged fmtkit_sidecar embed (see embedded_sidecar_*.go). -package fmtkit - -import _ "embed" - -// OxfmtConfig is the bundled oxfmt configuration, used when a target project -// has no .oxfmtrc.* of its own. -// -//go:embed .oxfmtrc.json -var OxfmtConfig []byte - -// OxlintConfig is the bundled oxlint configuration, used when a target -// project has no .oxlintrc* of its own. -// -//go:embed .oxlintrc.json -var OxlintConfig []byte diff --git a/infra/bin/fmtkit b/infra/bin/fmtkit deleted file mode 100755 index 7d97054..0000000 --- a/infra/bin/fmtkit +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -usage() { - printf 'usage: %s [args...]\n' "${0##*/}" >&2 - printf ' format [paths...] run TS/Vue support + lint, then Go formatting\n' >&2 - printf ' format-all run the full formatter pipeline against .\n' >&2 - printf ' go [check|format|version|help] [args...] run the Go formatter CLI\n' >&2 - printf ' ts [paths...] run TS/Vue formatting support and oxfmt\n' >&2 - printf ' check|version|help [args...] run the matching Go formatter CLI command\n' >&2 -} - -if [[ $# -eq 0 ]]; then - usage - exit 2 -fi - -mode="$1" -shift - -fmtkit_bin="${FMTKIT_BIN:-/usr/local/bin/fmtkit-go}" -format_ts_bin="${FORMAT_TS_BIN:-/usr/local/bin/fmtkit-ts}" -lint_ts_bin="${FORMAT_LINT_BIN:-/usr/local/bin/fmtkit-lint}" - -bold='' -dim='' -cyan='' -green='' -red='' -reset='' - -if [[ -n "${FORCE_COLOR:-}" || (-z "${NO_COLOR:-}" && -t 2) ]]; then - bold=$'\033[1m' - dim=$'\033[2m' - cyan=$'\033[36m' - green=$'\033[32m' - red=$'\033[31m' - reset=$'\033[0m' -fi - -section() { - printf '\n%s==>%s %s%s%s\n' "$cyan" "$reset" "$bold" "$1" "$reset" >&2 -} - -detail() { - printf ' %s%-12s%s %s\n' "$dim" "$1" "$reset" "$2" >&2 -} - -success_detail() { - printf ' %s%-12s%s %s%s%s\n' "$green" "$1" "$reset" "$green" "$2" "$reset" >&2 -} - -failure() { - printf '\n%s!!%s %s%s%s\n' "$red" "$reset" "$bold" "$1" "$reset" >&2 -} - -run_logged() { - local label="$1" - local summary="$2" - shift 2 - - local log_file - log_file="$(mktemp)" - - section "$label" - - local status=0 - "$@" >"$log_file" 2>&1 || status="$?" - - if [[ "$status" -eq 0 ]]; then - "$summary" "$log_file" - rm -f "$log_file" - - return 0 - fi - - failure "$label failed" - cat "$log_file" >&2 - rm -f "$log_file" - - return "$status" -} - -summarize_ts_format() { - local log_file="$1" - local missing_count - local blank_lines_summary - local fluent_chains_summary - local oxfmt_summary - local validation_summary - - missing_count="$(grep -c '^\[blank-lines\] path not found, skipping:' "$log_file" || true)" - blank_lines_summary="$(grep '^\[blank-lines\] processed ' "$log_file" | tail -n 1 | sed 's/^\[blank-lines\] //' || true)" - fluent_chains_summary="$(grep '^\[fluent-chains\] processed ' "$log_file" | tail -n 1 | sed 's/^\[fluent-chains\] //' || true)" - oxfmt_summary="$(grep '^Finished in ' "$log_file" | tail -n 1 || true)" - validation_summary="$(grep '^\[validate-syntax\] checked ' "$log_file" | tail -n 1 | sed 's/^\[validate-syntax\] //' || true)" - - if [[ -n "$blank_lines_summary" ]]; then - detail 'blank-lines' "$blank_lines_summary" - fi - - if [[ "$missing_count" -gt 0 ]]; then - detail 'skipped' "$missing_count missing tracked file(s)" - fi - - if [[ -n "$oxfmt_summary" ]]; then - detail 'oxfmt' "$oxfmt_summary" - fi - - if [[ -n "$fluent_chains_summary" ]]; then - detail 'fluent' "$fluent_chains_summary" - fi - - if [[ -n "$validation_summary" ]]; then - detail 'validated' "$validation_summary" - fi -} - -summarize_go_format() { - local log_file="$1" - local file_summary - local formatter_result - local vet_summary - local vet_result - - file_summary="$(grep -E ' (Formatted|Checked) [0-9]+ file\(s\)\.| No Go files found\.' "$log_file" | head -n 1 | sed 's/^ //' || true)" - formatter_result="$(grep ' Result: ' "$log_file" | head -n 1 | sed 's/^ Result: //' || true)" - vet_summary="$(grep -E ' go vet \./\.\.\. passed\.| Skipped automatic go vet ' "$log_file" | head -n 1 | sed 's/^ //' || true)" - vet_result="$(grep ' Result: ' "$log_file" | tail -n 1 | sed 's/^ Result: //' || true)" - - if [[ -n "$file_summary" ]]; then - detail 'fmtkit' "$file_summary" - fi - - if [[ -n "$formatter_result" ]]; then - detail 'result' "$formatter_result" - fi - - if [[ -n "$vet_summary" ]]; then - detail 'vet' "$vet_summary" - fi - - if [[ -n "$vet_result" && "$vet_result" != "$formatter_result" ]]; then - detail 'vet result' "$vet_result" - fi -} - -summarize_ts_lint() { - local log_file="$1" - local skipped_summary - local warnings_summary - - skipped_summary="$(grep '^\[lint\] no TS/Vue files to lint\.' "$log_file" | tail -n 1 | sed 's/^\[lint\] //' || true)" - warnings_summary="$(grep -E 'Found [0-9]+ warning|[0-9]+ error' "$log_file" | tail -n 1 || true)" - - if [[ -n "$skipped_summary" ]]; then - detail 'oxlint' "$skipped_summary" - elif [[ -n "$warnings_summary" ]]; then - detail 'oxlint' "$warnings_summary" - else - detail 'oxlint' 'no issues found' - fi -} - -run_format_pipeline() { - if [[ $# -eq 0 ]]; then - set -- . - fi - - section 'Formatting target(s)' - detail 'paths' "$*" - run_logged 'Running TS/Vue formatting' summarize_ts_format "$format_ts_bin" "$@" - run_logged 'Running TS/Vue lint' summarize_ts_lint "$lint_ts_bin" "$@" - run_logged 'Running Go formatting' summarize_go_format "$fmtkit_bin" format "$@" - section 'Formatting complete' - success_detail 'status' 'done' -} - -case "$mode" in - format) - run_format_pipeline "$@" - ;; - format-all) - if [[ $# -ne 0 ]]; then - usage - exit 2 - fi - - run_format_pipeline . - ;; - go) - exec "$fmtkit_bin" "$@" - ;; - ts) - exec "$format_ts_bin" "$@" - ;; - check | version | help) - exec "$fmtkit_bin" "$mode" "$@" - ;; - *) - usage - exit 2 - ;; -esac diff --git a/infra/bin/fmtkit-lint b/infra/bin/fmtkit-lint deleted file mode 100755 index ac1ada3..0000000 --- a/infra/bin/fmtkit-lint +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -fmt_ts_lint="${FMTKIT_TS_LINT_SH:-${script_dir}/fmtkit-ts-lint}" - -declare -a args=("$@") - -if [[ ${#args[@]} -eq 0 ]]; then - args=(.) -fi - -export GIT_CONFIG_COUNT=1 -export GIT_CONFIG_KEY_0=safe.directory -export GIT_CONFIG_VALUE_0="*" - -"$fmt_ts_lint" "${args[@]}" diff --git a/infra/bin/fmtkit-ts b/infra/bin/fmtkit-ts deleted file mode 100755 index e316f19..0000000 --- a/infra/bin/fmtkit-ts +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -fmt_ts_files="${FMTKIT_TS_FILES_SH:-${script_dir}/fmtkit-ts-files}" - -declare -a args=("$@") - -if [[ ${#args[@]} -eq 0 ]]; then - args=(.) -fi - -export GIT_CONFIG_COUNT=1 -export GIT_CONFIG_KEY_0=safe.directory -export GIT_CONFIG_VALUE_0="*" - -"$fmt_ts_files" "${args[@]}" diff --git a/infra/bin/fmtkit-ts-files b/infra/bin/fmtkit-ts-files deleted file mode 100755 index 8a1c5fb..0000000 --- a/infra/bin/fmtkit-ts-files +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -support_dir="${FMTKIT_SUPPORT_DIR:-/opt/fmtkit/support}" -tsx_bin="${TSX_BIN:-${support_dir}/node_modules/.bin/tsx}" -oxfmt_bin="${OXFMT_BIN:-${support_dir}/node_modules/.bin/oxfmt}" -oxfmtrc="${FMTKIT_OXFMTRC:-${support_dir}/.oxfmtrc.json}" -format_all_script="${FMTKIT_FORMAT_ALL_SCRIPT:-${support_dir}/format-all.ts}" - -declare -a sources_cmd - -if [[ -n "${FMTKIT_SOURCES_BIN:-}" ]]; then - sources_cmd=("${FMTKIT_SOURCES_BIN}") -elif [[ -x /usr/local/bin/fmtkit-sources ]]; then - sources_cmd=(/usr/local/bin/fmtkit-sources) -else - sources_cmd=("${GO_BIN:-go}" -C "${FMTKIT_SOURCES_GO_WORKDIR:-.}" run "${FMTKIT_SOURCES_CMD:-./packages/driver/cmd/fmtkit-sources}") -fi - -declare -a sources_args=() - -if [[ -n "${FMTKIT_SOURCES_CWD:-}" ]]; then - sources_args=(--cwd "${FMTKIT_SOURCES_CWD}") -fi - -declare -a args=("$@") - -if [[ ${#args[@]} -eq 0 ]]; then - args=(.) -fi - -declare -a format_files=() -declare -a syntax_files=() - -# Capture the source listings through temp files rather than process -# substitution: a `while … done < <(cmd)` loop swallows cmd's exit status, so a -# failing sources_cmd would silently yield an empty list and pass. Writing to a -# file lets set -e catch the failure. -tmp_format="$(mktemp)" -tmp_syntax="$(mktemp)" -trap 'rm -f "$tmp_format" "$tmp_syntax"' EXIT - -if [[ ${#sources_args[@]} -gt 0 ]]; then - "${sources_cmd[@]}" "${sources_args[@]}" "${args[@]}" > "$tmp_format" - "${sources_cmd[@]}" "${sources_args[@]}" --include-declarations "${args[@]}" > "$tmp_syntax" -else - "${sources_cmd[@]}" "${args[@]}" > "$tmp_format" - "${sources_cmd[@]}" --include-declarations "${args[@]}" > "$tmp_syntax" -fi - -while IFS= read -r -d '' file; do - format_files+=("$file") -done < "$tmp_format" - -while IFS= read -r -d '' file; do - syntax_files+=("$file") -done < "$tmp_syntax" - -rm -f "$tmp_format" "$tmp_syntax" -trap - EXIT - -# Fall back to the bundled config only when the project has no oxfmt config of -# its own; a project-local config (.oxfmtrc.json, .ts, .js, …) takes precedence -# via oxfmt's native auto-discovery. -detect_dir="${FMTKIT_SOURCES_CWD:-$PWD}" -declare -a oxfmt_config_args=() -shopt -s nullglob -project_configs=("$detect_dir"/.oxfmtrc.*) -shopt -u nullglob - -if [[ ${#project_configs[@]} -eq 0 && -f "$oxfmtrc" ]]; then - oxfmt_config_args=(--oxfmt-config "$oxfmtrc") -fi - -# format-all.ts runs the whole pipeline (blank-lines → oxfmt → fluent-chains -# → oxfmt → validate-syntax) in one Node process instead of three tsx spawns. -declare -a format_all_args=(--oxfmt-bin "$oxfmt_bin") - -format_all_args+=(${oxfmt_config_args[@]+"${oxfmt_config_args[@]}"}) -format_all_args+=(--format-files) - -if [[ ${#format_files[@]} -gt 0 ]]; then - format_all_args+=("${format_files[@]}") -fi - -format_all_args+=(--syntax-files) - -if [[ ${#syntax_files[@]} -gt 0 ]]; then - format_all_args+=("${syntax_files[@]}") -fi - -"$tsx_bin" "$format_all_script" "${format_all_args[@]}" diff --git a/infra/bin/fmtkit-ts-lint b/infra/bin/fmtkit-ts-lint deleted file mode 100755 index 276d223..0000000 --- a/infra/bin/fmtkit-ts-lint +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -support_dir="${FMTKIT_SUPPORT_DIR:-/opt/fmtkit/support}" -oxlint_bin="${OXLINT_BIN:-${support_dir}/node_modules/.bin/oxlint}" -oxlintrc="${FMTKIT_OXLINTRC:-${support_dir}/.oxlintrc.json}" - -declare -a sources_cmd - -if [[ -n "${FMTKIT_SOURCES_BIN:-}" ]]; then - sources_cmd=("${FMTKIT_SOURCES_BIN}") -elif [[ -x /usr/local/bin/fmtkit-sources ]]; then - sources_cmd=(/usr/local/bin/fmtkit-sources) -else - sources_cmd=("${GO_BIN:-go}" -C "${FMTKIT_SOURCES_GO_WORKDIR:-.}" run "${FMTKIT_SOURCES_CMD:-./packages/driver/cmd/fmtkit-sources}") -fi - -declare -a sources_args=() - -if [[ -n "${FMTKIT_SOURCES_CWD:-}" ]]; then - sources_args=(--cwd "${FMTKIT_SOURCES_CWD}") -fi - -declare -a args=("$@") - -if [[ ${#args[@]} -eq 0 ]]; then - args=(.) -fi - -declare -a lint_files=() - -# Capture the source listing through a temp file rather than process -# substitution: a `while … done < <(cmd)` loop swallows cmd's exit status, so a -# failing sources_cmd would silently yield an empty list and pass. Writing to a -# file lets set -e catch the failure. -tmp_sources="$(mktemp)" -trap 'rm -f "$tmp_sources"' EXIT - -if [[ ${#sources_args[@]} -gt 0 ]]; then - "${sources_cmd[@]}" "${sources_args[@]}" "${args[@]}" > "$tmp_sources" -else - "${sources_cmd[@]}" "${args[@]}" > "$tmp_sources" -fi - -while IFS= read -r -d '' file; do - lint_files+=("$file") -done < "$tmp_sources" - -rm -f "$tmp_sources" -trap - EXIT - -if [[ ${#lint_files[@]} -eq 0 ]]; then - echo "[lint] no TS/Vue files to lint." - exit 0 -fi - -# Fall back to the bundled config only when the project has no oxlint config of -# its own; a project-local config (.oxlintrc.json, .jsonc, …) takes precedence -# via oxlint's native auto-discovery. -detect_dir="${FMTKIT_SOURCES_CWD:-$PWD}" -declare -a oxlint_config_args=() -# Match both the extensioned configs (.oxlintrc.json/.jsonc/…) and the -# extensionless .oxlintrc. nullglob strips the glob when nothing matches, but -# the literal .oxlintrc (no metacharacters) survives even when absent, so the -# -f guard below filters to configs that actually exist on disk. -shopt -s nullglob -declare -a project_configs=() -for candidate in "$detect_dir"/.oxlintrc "$detect_dir"/.oxlintrc.*; do - [[ -f "$candidate" ]] && project_configs+=("$candidate") -done -shopt -u nullglob - -if [[ ${#project_configs[@]} -eq 0 && -f "$oxlintrc" ]]; then - oxlint_config_args=(--config "$oxlintrc") -fi - -printf '%s\0' "${lint_files[@]}" \ - | xargs -0 "$oxlint_bin" ${oxlint_config_args[@]+"${oxlint_config_args[@]}"} diff --git a/infra/docker/Dockerfile.full b/infra/docker/Dockerfile.full deleted file mode 100644 index ac2e782..0000000 --- a/infra/docker/Dockerfile.full +++ /dev/null @@ -1,88 +0,0 @@ -FROM --platform=$BUILDPLATFORM golang:1.26.4-bookworm AS builder - -ARG TARGETOS -ARG TARGETARCH - -WORKDIR /src - -COPY go.mod go.sum /src/ -RUN --mount=type=cache,target=/go/pkg/mod \ - go mod download - -RUN --mount=type=cache,target=/root/.cache/go-build \ - --mount=type=cache,target=/tmp/go/pkg/mod \ - mkdir -p /out && \ - CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOPATH=/tmp/go \ - go install -trimpath -ldflags="-s -w" golang.org/x/tools/cmd/goimports@v0.43.0 && \ - find /tmp/go/bin -name goimports -exec cp {} /out/goimports \; - -COPY infra/scripts /src/infra/scripts -COPY packages/formatter /src/packages/formatter -COPY packages/vet /src/packages/vet -COPY packages/driver /src/packages/driver - -RUN bash -lc 'source /src/infra/scripts/tasks/env.sh && assert_no_legacy_artifacts' - -ARG VERSION=dev - -RUN --mount=type=cache,target=/root/.cache/go-build \ - --mount=type=cache,target=/go/pkg/mod \ - CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ - go build -trimpath -ldflags="-s -w -X main.version=${VERSION}" -o /out/fmtkit-go ./packages/driver/cmd/fmtkit-go -RUN --mount=type=cache,target=/root/.cache/go-build \ - --mount=type=cache,target=/go/pkg/mod \ - CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ - go build -trimpath -ldflags="-s -w" -o /out/fmtkit-sources ./packages/driver/cmd/fmtkit-sources - -FROM golang:1.26.4-alpine AS gosdk - -# The runtime needs `go` for goimports (resolves imports via `go env`/std-lib scan) -# and `go vet` (compiles packages for analysis). Strip the parts neither uses at -# runtime: Go's own test suite, API compatibility data, docs, and the std-library -# test fixtures (`testdata` dirs and `*_test.go`, which are never compiled when -# building or vetting userland packages). -RUN cd /usr/local/go && \ - rm -rf test api doc misc && \ - find src -type d -name testdata -prune -exec rm -rf {} + && \ - find src -type f -name '*_test.go' -delete && \ - rm -f pkg/tool/*/fix pkg/tool/*/cover pkg/tool/*/preprofile - -FROM node:25.8.2-alpine AS node-support - -RUN apk add --no-cache bash git - -WORKDIR /opt/fmtkit/support -COPY packages/devx/package.json /opt/fmtkit/support/package.json -RUN --mount=type=cache,target=/root/.npm \ - node -e 'const p = require("./package.json"); const deps = ["oxc-parser", "oxfmt", "oxlint", "tsx"]; console.log(deps.map((name) => `${name}@${p.devDependencies[name]}`).join(" "));' \ - | xargs npm install --no-save - -FROM node-support - -WORKDIR /work - -COPY --from=gosdk /usr/local/go /usr/local/go -ENV PATH="/usr/local/go/bin:${PATH}" \ - GOCACHE="/work/storage/.cache/go-build" \ - GOPATH="/work/storage/.cache/gopath" \ - GOMODCACHE="/work/storage/.cache/gopath/pkg/mod" - -COPY --from=builder /out/fmtkit-go /usr/local/bin/fmtkit-go -COPY --from=builder /out/fmtkit-sources /usr/local/bin/fmtkit-sources -COPY --from=builder /out/goimports /usr/local/bin/goimports - -WORKDIR /opt/fmtkit/support -COPY packages/devx/scripts/*.ts /opt/fmtkit/support/ -COPY packages/devx/scripts/package.json /opt/fmtkit/support/package.json -COPY .oxfmtrc.json /opt/fmtkit/support/.oxfmtrc.json -COPY .oxlintrc.json /opt/fmtkit/support/.oxlintrc.json -COPY infra/bin/fmtkit-ts /usr/local/bin/fmtkit-ts -COPY infra/bin/fmtkit-ts-files /usr/local/bin/fmtkit-ts-files -COPY infra/bin/fmtkit-lint /usr/local/bin/fmtkit-lint -COPY infra/bin/fmtkit-ts-lint /usr/local/bin/fmtkit-ts-lint -COPY infra/bin/fmtkit /usr/local/bin/fmtkit -RUN chmod +x /usr/local/bin/fmtkit-ts /usr/local/bin/fmtkit-ts-files /usr/local/bin/fmtkit-lint /usr/local/bin/fmtkit-ts-lint /usr/local/bin/fmtkit - -WORKDIR /work - -ENTRYPOINT ["/usr/local/bin/fmtkit"] diff --git a/infra/docker/Dockerfile.golang b/infra/docker/Dockerfile.golang deleted file mode 100644 index fab19cb..0000000 --- a/infra/docker/Dockerfile.golang +++ /dev/null @@ -1,55 +0,0 @@ -FROM --platform=$BUILDPLATFORM golang:1.26.4-bookworm AS builder - -ARG TARGETOS -ARG TARGETARCH - -WORKDIR /src - -COPY go.mod go.sum /src/ -RUN --mount=type=cache,target=/go/pkg/mod \ - go mod download - -RUN --mount=type=cache,target=/root/.cache/go-build \ - --mount=type=cache,target=/tmp/go/pkg/mod \ - mkdir -p /out && \ - CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOPATH=/tmp/go \ - go install -trimpath -ldflags="-s -w" golang.org/x/tools/cmd/goimports@v0.43.0 && \ - find /tmp/go/bin -name goimports -exec cp {} /out/goimports \; - -COPY infra/scripts /src/infra/scripts -COPY packages/formatter /src/packages/formatter -COPY packages/vet /src/packages/vet -COPY packages/driver /src/packages/driver - -RUN bash -lc 'source /src/infra/scripts/tasks/env.sh && assert_no_legacy_artifacts' - -ARG VERSION=dev - -RUN --mount=type=cache,target=/root/.cache/go-build \ - --mount=type=cache,target=/go/pkg/mod \ - CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ - go build -trimpath -ldflags="-s -w -X main.version=${VERSION}" -o /out/fmtkit-go ./packages/driver/cmd/fmtkit-go - -FROM golang:1.26.4-alpine - -RUN apk add --no-cache bash git - -# Strip Go SDK parts neither `goimports` nor `go vet` uses at runtime: Go's own -# test suite, API compatibility data, docs, std-library test fixtures, and tool -# binaries only used by `go fix`/`go test -cover`/PGO. -RUN cd /usr/local/go && \ - rm -rf test api doc misc && \ - find src -type d -name testdata -prune -exec rm -rf {} + && \ - find src -type f -name '*_test.go' -delete && \ - rm -f pkg/tool/*/fix pkg/tool/*/cover pkg/tool/*/preprofile - -WORKDIR /work - -ENV GOCACHE="/work/storage/.cache/go-build" \ - GOPATH="/work/storage/.cache/gopath" \ - GOMODCACHE="/work/storage/.cache/gopath/pkg/mod" - -COPY --from=builder /out/fmtkit-go /usr/local/bin/fmtkit-go -COPY --from=builder /out/goimports /usr/local/bin/goimports - -ENTRYPOINT ["/usr/local/bin/fmtkit-go"] diff --git a/infra/docker/Dockerfile.node-ts b/infra/docker/Dockerfile.node-ts deleted file mode 100644 index cc3ba7d..0000000 --- a/infra/docker/Dockerfile.node-ts +++ /dev/null @@ -1,38 +0,0 @@ -FROM --platform=$BUILDPLATFORM golang:1.26.4-bookworm AS source-builder - -ARG TARGETOS -ARG TARGETARCH - -WORKDIR /src -COPY go.mod go.sum /src/ -RUN --mount=type=cache,target=/go/pkg/mod \ - go mod download - -COPY packages/driver /src/packages/driver -RUN --mount=type=cache,target=/root/.cache/go-build \ - --mount=type=cache,target=/go/pkg/mod \ - CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ - go build -trimpath -ldflags="-s -w" -o /out/fmtkit-sources ./packages/driver/cmd/fmtkit-sources - -FROM node:25.8.2-alpine - -RUN apk add --no-cache bash git - -WORKDIR /opt/fmtkit/support - -COPY packages/devx/package.json /opt/fmtkit/support/package.json -RUN --mount=type=cache,target=/root/.npm \ - node -e 'const p = require("./package.json"); const deps = ["oxc-parser", "oxfmt", "tsx"]; console.log(deps.map((name) => `${name}@${p.devDependencies[name]}`).join(" "));' \ - | xargs npm install --no-save - -COPY packages/devx/scripts/*.ts /opt/fmtkit/support/ -COPY packages/devx/scripts/package.json /opt/fmtkit/support/package.json -COPY .oxfmtrc.json /opt/fmtkit/support/.oxfmtrc.json -COPY --from=source-builder /out/fmtkit-sources /usr/local/bin/fmtkit-sources -COPY infra/bin/fmtkit-ts /usr/local/bin/fmtkit-ts -COPY infra/bin/fmtkit-ts-files /usr/local/bin/fmtkit-ts-files -RUN chmod +x /usr/local/bin/fmtkit-ts /usr/local/bin/fmtkit-ts-files - -WORKDIR /work - -ENTRYPOINT ["/usr/local/bin/fmtkit-ts"] diff --git a/infra/scripts/host-target.sh b/infra/scripts/host-target.sh new file mode 100755 index 0000000..238fa34 --- /dev/null +++ b/infra/scripts/host-target.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# Maps the running machine onto one of the _ names that the staged +# TS toolchain assets are keyed by. Sourced by stage-ts-assets.sh to pick what to +# build, and by tasks/fmtkit.sh to find what was built. + +host_target() { + local os arch + + case "$(uname -s)" in + Darwin) os='darwin' ;; + Linux) os='linux' ;; + *) + printf 'unsupported host OS: %s\n' "$(uname -s)" >&2 + return 1 + ;; + esac + + case "$(uname -m)" in + arm64 | aarch64) arch='arm64' ;; + x86_64) arch='amd64' ;; + *) + printf 'unsupported host arch: %s\n' "$(uname -m)" >&2 + return 1 + ;; + esac + + printf '%s_%s' "${os}" "${arch}" +} diff --git a/infra/scripts/release/image/assertions.sh b/infra/scripts/release/image/assertions.sh deleted file mode 100644 index 769f431..0000000 --- a/infra/scripts/release/image/assertions.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - -release_image_fail() { - printf '%s\n' "$1" >&2 - exit 1 -} - -assert_version_output() { - local image="$1" - local actual="$2" - local expected="fmtkit ${NEW_TAG}" - - if [ "$actual" != "$expected" ]; then - release_image_fail "unexpected version output for ${image}: ${actual}" - fi -} - -assert_status() { - local expected="$1" - local actual="$2" - local message="$3" - - if [ "$actual" -ne "$expected" ]; then - release_image_fail "${message}, got ${actual}" - fi -} - -assert_output_contains() { - local output="$1" - local expected="$2" - - grep -Fq "$expected" <<<"$output" -} - -assert_output_not_contains() { - local image="$1" - local output="$2" - local unexpected="$3" - local message="$4" - - if grep -Fq "$unexpected" <<<"$output"; then - release_image_fail "${message}: ${image}" - fi -} - -assert_file_contains() { - local file="$1" - local expected="$2" - - if [ ! -f "$file" ]; then - release_image_fail "file not found: ${file}" - elif ! grep -Fq "$expected" "$file"; then - release_image_fail "expected ${file} to contain: ${expected}" - fi -} diff --git a/infra/scripts/release/image/config.sh b/infra/scripts/release/image/config.sh deleted file mode 100644 index 9880652..0000000 --- a/infra/scripts/release/image/config.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -if [ -z "${IMAGE_NAME:-}" ]; then - printf 'IMAGE_NAME is required\n' >&2 - exit 1 -fi - -if [ -z "${NEW_TAG:-}" ]; then - printf 'NEW_TAG is required\n' >&2 - exit 1 -fi - -release_image_ref() { - local tag="$1" - - printf '%s:%s\n' "$IMAGE_NAME" "$tag" -} diff --git a/infra/scripts/release/image/docker.sh b/infra/scripts/release/image/docker.sh deleted file mode 100644 index 03d2ffd..0000000 --- a/infra/scripts/release/image/docker.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -release_image_run() { - docker run --rm "$@" -} - -release_image_run_in_workdir() { - local workdir="$1" - local image="$2" - - shift 2 - docker run --rm -v "${workdir}:/work" -w /work "$image" "$@" -} diff --git a/infra/scripts/release/image/fixtures.sh b/infra/scripts/release/image/fixtures.sh deleted file mode 100644 index c425fe9..0000000 --- a/infra/scripts/release/image/fixtures.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash - -declare -a release_image_tmpdirs=() - -cleanup_release_image_tmpdirs() { - local tmpdir - - for tmpdir in ${release_image_tmpdirs[@]+"${release_image_tmpdirs[@]}"}; do - rm -rf "$tmpdir" - done -} - -trap cleanup_release_image_tmpdirs EXIT - -create_release_image_tmpdir() { - local tmpdir - - tmpdir="$(mktemp -d)" - release_image_tmpdirs+=("$tmpdir") - printf '%s\n' "$tmpdir" -} - -write_go_spacing_fixture() { - local tmpdir="$1" - - cat > "${tmpdir}/sample.go" <<'EOF' -package sample - -func run() { - if true { - println("ok") - } - println("next") -} -EOF -} - -write_node_ts_fixture() { - local tmpdir="$1" - - git -C "$tmpdir" init -q - - cat > "${tmpdir}/sample.ts" <<'EOF' -const value={name:"demo"}; -EOF -} diff --git a/infra/scripts/release/image/full.sh b/infra/scripts/release/image/full.sh deleted file mode 100644 index 519e88a..0000000 --- a/infra/scripts/release/image/full.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -verify_full_image() { - local tag="$1" - local image - local version_output - - image="$(release_image_ref "$tag")" - version_output="$(release_image_run "$image" version)" - assert_version_output "$image" "$version_output" - - verify_go_image "$tag" - - version_output="$(release_image_run "$image" go version)" - if [ "$version_output" != "fmtkit ${NEW_TAG}" ]; then - release_image_fail "full image did not forward go version correctly: ${image}" - fi -} diff --git a/infra/scripts/release/image/go.sh b/infra/scripts/release/image/go.sh deleted file mode 100644 index 3a2b79f..0000000 --- a/infra/scripts/release/image/go.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -verify_go_image() { - local tag="$1" - local image - local version_output - local tmpdir - local report_output - local report_status - - image="$(release_image_ref "$tag")" - version_output="$(release_image_run "$image" version)" - assert_version_output "$image" "$version_output" - - tmpdir="$(create_release_image_tmpdir)" - write_go_spacing_fixture "$tmpdir" - - set +e - report_output="$(release_image_run_in_workdir "$tmpdir" "$image" check . 2>&1)" - report_status=$? - set -e - - printf '%s\n' "$report_output" - - assert_status 1 "$report_status" "expected check to exit 1 for ${image}" - assert_output_contains "$report_output" " sample.go" - assert_output_contains "$report_output" " [spacing] line 7: missing blank line after if statement" - assert_output_contains "$report_output" " Result: fail. 1 changed, 1 violation(s), 0 error(s)." - assert_output_not_contains "$image" "$report_output" "~ sample.go:7 [spacing]" "legacy flat renderer detected" -} diff --git a/infra/scripts/release/image/node-ts.sh b/infra/scripts/release/image/node-ts.sh deleted file mode 100644 index 5f1d916..0000000 --- a/infra/scripts/release/image/node-ts.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -verify_node_ts_image() { - local tag="$1" - local image - local tmpdir - - image="$(release_image_ref "$tag")" - tmpdir="$(create_release_image_tmpdir)" - write_node_ts_fixture "$tmpdir" - - release_image_run_in_workdir "$tmpdir" "$image" . - assert_file_contains "${tmpdir}/sample.ts" "const value = { name: 'demo' };" - - if release_image_run --entrypoint sh "$image" -c 'command -v go' >/dev/null 2>&1; then - release_image_fail "node-ts image unexpectedly ships a Go toolchain: ${image}" - fi -} diff --git a/infra/scripts/release/stage-ts-assets.sh b/infra/scripts/release/stage-ts-assets.sh index 1349117..c551193 100755 --- a/infra/scripts/release/stage-ts-assets.sh +++ b/infra/scripts/release/stage-ts-assets.sh @@ -8,9 +8,14 @@ set -euo pipefail # - oxc-parser.node napi binding, loaded via NAPI_RS_NATIVE_LIBRARY_PATH # - oxfmt.node napi binding for the oxfmt CLI # - oxlint.node napi binding for the oxlint CLI +# - .oxfmtrc.json repo-root config, the default for projects without one +# - .oxlintrc.json repo-root config, the default for projects without one # -# Tool versions come from packages/devx/package.json devDependencies, the same -# source of truth the Docker images use. Requires bash, node, npm, and bun. +# Output lands in packages/driver/internal/embedded/bin//, next to the +# package that embeds it: go:embed cannot reach outside its own directory. +# +# Tool versions come from packages/devx/package.json devDependencies. Requires +# bash, node, npm, and bun. # # usage: stage-ts-assets.sh @@ -25,7 +30,9 @@ if [[ $# -eq 0 ]]; then fi root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" -dist="${FMTKIT_TS_ASSET_DIR:-${root}/infra/bin}" +dist="${FMTKIT_TS_ASSET_DIR:-${root}/packages/driver/internal/embedded/bin}" + +source "${root}/infra/scripts/host-target.sh" all_targets=(darwin_arm64 darwin_amd64 linux_arm64 linux_amd64) @@ -49,30 +56,6 @@ binding_suffix() { esac } -host_target() { - local os arch - - case "$(uname -s)" in - Darwin) os='darwin' ;; - Linux) os='linux' ;; - *) - printf 'unsupported host OS: %s\n' "$(uname -s)" >&2 - return 1 - ;; - esac - - case "$(uname -m)" in - arm64 | aarch64) arch='arm64' ;; - x86_64) arch='amd64' ;; - *) - printf 'unsupported host arch: %s\n' "$(uname -m)" >&2 - return 1 - ;; - esac - - printf '%s_%s' "${os}" "${arch}" -} - declare -a targets=() for arg in "$@"; do @@ -195,5 +178,11 @@ for target in "${targets[@]}"; do fetch_binding "@oxfmt/binding-${suffix}@${oxfmt_pin}" "oxfmt.${suffix}.node" "${out}/oxfmt.node" fetch_binding "@oxlint/binding-${suffix}@${oxlint_pin}" "oxlint.${suffix}.node" "${out}/oxlint.node" + # The repo root configs are the single source of truth: they double as the + # configs this repo formats itself with, and tsruntime extracts these + # copies as the fallback for projects that carry none. + install -m 0644 "${root}/.oxfmtrc.json" "${out}/.oxfmtrc.json" + install -m 0644 "${root}/.oxlintrc.json" "${out}/.oxlintrc.json" + printf '==> staged %s\n' "${out}" >&2 done diff --git a/infra/scripts/release/verify-release-image.sh b/infra/scripts/release/verify-release-image.sh deleted file mode 100755 index ee47e56..0000000 --- a/infra/scripts/release/verify-release-image.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -release_image_dir="${script_dir}/image" - -source "${release_image_dir}/config.sh" -source "${release_image_dir}/assertions.sh" -source "${release_image_dir}/fixtures.sh" -source "${release_image_dir}/docker.sh" -source "${release_image_dir}/go.sh" -source "${release_image_dir}/node-ts.sh" -source "${release_image_dir}/full.sh" - -verify_go_image "${NEW_TAG}-go" -verify_node_ts_image "${NEW_TAG}-node-ts" -verify_full_image "${NEW_TAG}-full" diff --git a/infra/scripts/tasks/docker-env.sh b/infra/scripts/tasks/docker-env.sh deleted file mode 100755 index b0cb9f3..0000000 --- a/infra/scripts/tasks/docker-env.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash - -source "$(dirname "${BASH_SOURCE[0]}")/env.sh" - -export VERSION="${VERSION:-$(git -C "$REPO_ROOT" describe --tags --always --dirty 2>/dev/null || echo dev)}" -export FORMATTER_IMAGE="${FORMATTER_IMAGE:-fmtkit-full:local}" -export FORMATTER_DOCKERFILE="${FORMATTER_DOCKERFILE:-infra/docker/Dockerfile.full}" -export FORMATTER_BUILD="${FORMATTER_BUILD:-auto}" -export GO_IMAGE="${GO_IMAGE:-fmtkit-go:local}" -export NODE_TS_IMAGE="${NODE_TS_IMAGE:-fmtkit-node-ts:local}" -export FULL_IMAGE="${FULL_IMAGE:-fmtkit-full:local}" -export FMTKIT_CACHE_VOLUME="${FMTKIT_CACHE_VOLUME:-fmtkit-cache}" -export FMTKIT_PROJECT_DIR="${FMTKIT_PROJECT_DIR:-$REPO_ROOT}" - -formatter_fingerprint() { - { - printf '%s\n' \ - "$FORMATTER_DOCKERFILE" \ - package.json \ - pnpm-lock.yaml \ - infra/bin/fmtkit-ts \ - infra/bin/fmtkit-ts-files \ - infra/bin/fmtkit-lint \ - infra/bin/fmtkit-ts-lint \ - infra/bin/fmtkit \ - .oxlintrc.json \ - packages/devx/package.json \ - packages/devx/scripts/package.json - (cd "$REPO_ROOT" && find packages/devx/scripts -maxdepth 1 -type f -name '*.ts') - } | sort | while IFS= read -r file; do - [[ -f "$REPO_ROOT/$file" ]] && shasum -a 256 "$REPO_ROOT/$file" - done | shasum -a 256 | awk '{print $1}' -} - -export FORMATTER_FINGERPRINT="${FORMATTER_FINGERPRINT:-$(formatter_fingerprint)}" diff --git a/infra/scripts/tasks/docker-image.sh b/infra/scripts/tasks/docker-image.sh deleted file mode 100755 index ef000c6..0000000 --- a/infra/scripts/tasks/docker-image.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -source "$(dirname "${BASH_SOURCE[0]}")/docker-env.sh" - -usage() { - printf 'usage: %s \n' "${0##*/}" >&2 -} - -mode="${1:-}" - -case "$mode" in - go) - docker build --build-arg "VERSION=$VERSION" -f "$REPO_ROOT/infra/docker/Dockerfile.golang" -t "$GO_IMAGE" "$REPO_ROOT" - ;; - node-ts) - docker build --label "local.fmtkit.formatter-fingerprint=$FORMATTER_FINGERPRINT" -f "$REPO_ROOT/infra/docker/Dockerfile.node-ts" -t "$NODE_TS_IMAGE" "$REPO_ROOT" - ;; - full) - docker build --build-arg "VERSION=$VERSION" --label "local.fmtkit.formatter-fingerprint=$FORMATTER_FINGERPRINT" -f "$REPO_ROOT/infra/docker/Dockerfile.full" -t "$FULL_IMAGE" "$REPO_ROOT" - ;; - clean) - containers="$(docker ps -aq --filter "ancestor=$GO_IMAGE" --filter "ancestor=$NODE_TS_IMAGE" --filter "ancestor=$FULL_IMAGE")" - if [[ -n "$containers" ]]; then - printf '%s\n' "$containers" | xargs docker rm -f - fi - - docker rmi -f "$GO_IMAGE" "$NODE_TS_IMAGE" "$FULL_IMAGE" 2>/dev/null || true - - images="$(docker images -q --filter label=local.fmtkit.formatter-fingerprint | sort -u)" - if [[ -n "$images" ]]; then - printf '%s\n' "$images" | xargs docker rmi -f - fi - - docker volume rm "$FMTKIT_CACHE_VOLUME" 2>/dev/null || true - docker image prune -f --filter label=local.fmtkit.formatter-fingerprint - ;; - *) - usage - exit 2 - ;; -esac diff --git a/infra/scripts/tasks/fmtkit-host.sh b/infra/scripts/tasks/fmtkit-host.sh deleted file mode 100755 index bce7dec..0000000 --- a/infra/scripts/tasks/fmtkit-host.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -# fmtkit-host.sh — single-image, no-compose host wrapper for fmtkit. -# -# Every project on the host shares one selected image (FMTKIT_IMAGE, default -# ghcr.io/oullin/fmtkit:latest) and one named cache volume (FMTKIT_CACHE_VOLUME, -# default fmtkit-cache). Drop this script anywhere on $PATH (or symlink it as -# `fmtkit`) and invoke from any project root: -# -# fmtkit-host.sh format . -# fmtkit-host.sh format-all -# fmtkit-host.sh go check ./pkg ./cmd -# fmtkit-host.sh ts . -# -# Select a formatter flavor by exporting FMTKIT_IMAGE: -# -# FMTKIT_IMAGE=ghcr.io/oullin/fmtkit:latest-full -# FMTKIT_IMAGE=ghcr.io/oullin/fmtkit:latest-go -# FMTKIT_IMAGE=ghcr.io/oullin/fmtkit:latest-node-ts -# -# Versioned flavor tags follow the same shape, for example v0.0.18-go, -# v0.0.18-node-ts, and v0.0.18-full. - -set -euo pipefail - -image="${FMTKIT_IMAGE:-ghcr.io/oullin/fmtkit:latest}" -cache_volume="${FMTKIT_CACHE_VOLUME:-fmtkit-cache}" -project_dir="${FMTKIT_PROJECT_DIR:-$PWD}" - -if ! command -v docker >/dev/null 2>&1; then - printf 'fmtkit-host: docker is required on PATH\n' >&2 - exit 127 -fi - -if [ ! -d "${project_dir}" ]; then - printf 'fmtkit-host: project dir %s does not exist\n' "${project_dir}" >&2 - exit 1 -fi - -project_dir="$(cd "${project_dir}" && pwd)" - -exec docker run --rm \ - -v "${project_dir}:/work" \ - -v "${cache_volume}:/cache" \ - -w /work \ - -e "HOST_PROJECT_PATH=${project_dir}" \ - -e GOCACHE=/cache/go-build \ - -e GOPATH=/cache/gopath \ - -e GOMODCACHE=/cache/gopath/pkg/mod \ - "${image}" "$@" diff --git a/infra/scripts/tasks/fmtkit.sh b/infra/scripts/tasks/fmtkit.sh new file mode 100755 index 0000000..161bbd8 --- /dev/null +++ b/infra/scripts/tasks/fmtkit.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Runs this repository through fmtkit's own binary — the same Go orchestrator and +# bun-compiled TS sidecar a release carries. The host toolchain assets are staged +# on demand and reused until their sources change, so the inner loop stays a +# plain `go run`; the embedded-asset path releases use is covered separately by +# test-binary-smoke.sh. +# +# usage: fmtkit.sh [args...] + +source "$(dirname "${BASH_SOURCE[0]}")/env.sh" +source "${REPO_ROOT}/infra/scripts/host-target.sh" + +support_dir="${REPO_ROOT}/packages/driver/internal/embedded/bin/$(host_target)" +sidecar="${support_dir}/fmtkit-ts-sidecar" + +# The sidecar is stale once anything it is compiled from outdates it: the support +# scripts, the tool pins, or the configs staged alongside it. +sidecar_is_stale() { + [[ -x "$sidecar" ]] || return 0 + + local newer + + newer="$(find \ + "${REPO_ROOT}/packages/devx/scripts" \ + "${REPO_ROOT}/packages/devx/package.json" \ + "${REPO_ROOT}/.oxfmtrc.json" \ + "${REPO_ROOT}/.oxlintrc.json" \ + -newer "$sidecar" -print 2>/dev/null | head -n 1)" + + [[ -n "$newer" ]] +} + +if sidecar_is_stale; then + "${REPO_ROOT}/infra/scripts/release/stage-ts-assets.sh" host +fi + +ensure_storage_layout + +cd "$REPO_ROOT" + +FMTKIT_SUPPORT_DIR="$support_dir" exec "${GO_BIN:-go}" run ./packages/driver/cmd/fmtkit "$@" diff --git a/infra/scripts/tasks/format-docker.sh b/infra/scripts/tasks/format-docker.sh deleted file mode 100755 index 1e3875e..0000000 --- a/infra/scripts/tasks/format-docker.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -source "$(dirname "${BASH_SOURCE[0]}")/docker-env.sh" - -usage() { - printf 'usage: %s [paths...]\n' "${0##*/}" >&2 -} - -ensure_formatter_image() { - local image="$FORMATTER_IMAGE" - local dockerfile="$REPO_ROOT/$FORMATTER_DOCKERFILE" - local policy="$FORMATTER_BUILD" - local expected_version="fmtkit $VERSION" - local expected_fingerprint="$FORMATTER_FINGERPRINT" - local build_reason='' - - case "$policy" in - auto) - if ! docker image inspect "$image" >/dev/null 2>&1; then - build_reason='missing' - else - image_version="$(docker run --rm "$image" version 2>/dev/null || true)" - if [[ "$image_version" != "$expected_version" ]]; then - build_reason='version changed' - else - image_fingerprint="$(docker image inspect "$image" --format '{{ index .Config.Labels "local.fmtkit.formatter-fingerprint" }}' 2>/dev/null || true)" - if [[ "$image_fingerprint" != "$expected_fingerprint" ]]; then - build_reason='support changed' - fi - fi - fi - ;; - always) - build_reason='forced' - ;; - never) - if ! docker image inspect "$image" >/dev/null 2>&1; then - printf 'formatter image is missing and FORMATTER_BUILD=never: %s\n' "$image" >&2 - printf 'Build it with `vp run image:full` or rerun with FORMATTER_BUILD=auto.\n' >&2 - exit 1 - fi - ;; - *) - printf 'invalid FORMATTER_BUILD value: %s\n' "$policy" >&2 - printf 'Expected one of: auto, always, never.\n' >&2 - exit 2 - ;; - esac - - if [[ -n "$build_reason" ]]; then - printf 'Building formatter image %s (%s)...\n' "$image" "$build_reason" >&2 - docker build --build-arg "VERSION=$VERSION" --label "local.fmtkit.formatter-fingerprint=$expected_fingerprint" -f "$dockerfile" -t "$image" "$REPO_ROOT" - fi -} - -run_formatter() { - local mode="$1" - shift - - docker run --rm \ - -v "$FMTKIT_PROJECT_DIR:/work" \ - -v "$FMTKIT_CACHE_VOLUME:/cache" \ - -w /work \ - -e "HOST_PROJECT_PATH=$FMTKIT_PROJECT_DIR" \ - -e GOCACHE=/cache/go-build \ - -e GOPATH=/cache/gopath \ - -e GOMODCACHE=/cache/gopath/pkg/mod \ - "$FORMATTER_IMAGE" "$mode" "$@" -} - -mode="${1:-}" -if [[ $# -gt 0 ]]; then - shift -fi - -if [[ "${1:-}" == "--" ]]; then - shift -fi - -case "$mode" in - format) - if [[ $# -eq 0 ]]; then - set -- . - fi - ensure_formatter_image - run_formatter format "$@" - ;; - format-all) - if [[ $# -ne 0 ]]; then - usage - exit 2 - fi - ensure_formatter_image - run_formatter format . - ;; - check) - if [[ $# -eq 0 ]]; then - set -- . - fi - ensure_formatter_image - run_formatter go check "$@" - ;; - *) - usage - exit 2 - ;; -esac diff --git a/infra/scripts/tasks/format.sh b/infra/scripts/tasks/format.sh index a228abc..83cea46 100755 --- a/infra/scripts/tasks/format.sh +++ b/infra/scripts/tasks/format.sh @@ -1,12 +1,11 @@ #!/usr/bin/env bash set -euo pipefail -source "$(dirname "$0")/env.sh" +# Formats this repository with fmtkit's own binary. Paths are resolved against +# the repository root rather than the invoking directory, so `format.sh .` means +# the whole repo no matter where it is run from. -repo_root="$REPO_ROOT" -oxfmt_bin="${OXFMT_BIN:-packages/devx/node_modules/.bin/oxfmt}" -tsx_bin="${TSX_BIN:-packages/devx/node_modules/.bin/tsx}" -go_bin="${GO_BIN:-go}" +source "$(dirname "${BASH_SOURCE[0]}")/env.sh" declare -a args=("$@") declare -a fmtkit_args=() @@ -23,17 +22,21 @@ to_repo_path() { local arg="$1" case "$arg" in + -*) + # A step or output flag (--ts, --go, --quiet): pass it through as-is. + printf '%s\n' "$arg" + ;; .) - printf '%s\n' "$repo_root" + printf '%s\n' "$REPO_ROOT" ;; ./*) - printf '%s\n' "$repo_root/${arg#./}" + printf '%s\n' "$REPO_ROOT/${arg#./}" ;; /*) printf '%s\n' "$arg" ;; *) - printf '%s\n' "$repo_root/$arg" + printf '%s\n' "$REPO_ROOT/$arg" ;; esac } @@ -42,23 +45,4 @@ for raw_arg in "${args[@]}"; do fmtkit_args+=("$(to_repo_path "$raw_arg")") done -sources_workdir="$GO_WORKDIR" - -if [[ "$sources_workdir" != /* ]]; then - sources_workdir="$repo_root/$sources_workdir" -fi - -ensure_storage_layout -"$go_bin" -C "$GO_WORKDIR" run "$CMD" format --cwd "$repo_root" "${fmtkit_args[@]}" - -( - cd "$repo_root" - GO_BIN="$go_bin" \ - FMTKIT_SUPPORT_DIR="$repo_root/packages/devx" \ - FMTKIT_SOURCES_GO_WORKDIR="$sources_workdir" \ - FMTKIT_SOURCES_CWD="$repo_root" \ - FMTKIT_FORMAT_ALL_SCRIPT="$repo_root/packages/devx/scripts/format-all.ts" \ - TSX_BIN="$tsx_bin" \ - OXFMT_BIN="$oxfmt_bin" \ - "$repo_root/infra/bin/fmtkit-ts-files" "${fmtkit_args[@]}" -) +exec "$(dirname "${BASH_SOURCE[0]}")/fmtkit.sh" format "${fmtkit_args[@]}" diff --git a/infra/scripts/tasks/install-docker-makefile.sh b/infra/scripts/tasks/install-docker-makefile.sh deleted file mode 100755 index aa7f007..0000000 --- a/infra/scripts/tasks/install-docker-makefile.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd -P)" -template="$repo_root/infra/tooling/docker/Makefile" -target="$repo_root/Makefile" - -if [[ ! -f "$template" ]]; then - printf 'docker Makefile template not found: %s\n' "$template" >&2 - exit 1 -fi - -if [[ -f "$target" ]] && grep -qx 'include infra/tooling/docker/Makefile' "$target"; then - printf 'Docker compatibility Makefile is already available at %s\n' "$target" - exit 0 -fi - -if [[ -e "$target" && "${FORCE:-0}" != 1 ]]; then - printf 'refusing to overwrite existing Makefile: %s\n' "$target" >&2 - printf 'Set FORCE=1 to replace it.\n' >&2 - exit 1 -fi - -cp "$template" "$target" -printf 'Installed Docker compatibility Makefile at %s\n' "$target" diff --git a/infra/scripts/tasks/test-binary-smoke.sh b/infra/scripts/tasks/test-binary-smoke.sh index f761179..e39d57f 100755 --- a/infra/scripts/tasks/test-binary-smoke.sh +++ b/infra/scripts/tasks/test-binary-smoke.sh @@ -33,14 +33,17 @@ cd "$fixture" git init --quiet . -printf 'const a = { x:1 }\nexport default a\n' > app.ts +# The fixture carries no .oxfmtrc.* of its own, so oxfmt must pick up the +# bundled config. The double-quoted string is the probe: singleQuote there +# rewrites it, while a dropped config leaves oxfmt on its double-quote default. +printf 'const a = { x:1, s:"hi" }\nexport default a\n' > app.ts printf 'package p\n\nfunc f() {\n\tdefer println("d")\n\treturn\n}\n' > app.go printf 'module fixture\n\ngo 1.26.4\n' > go.mod XDG_CACHE_HOME="${tmp_root}/cache" "$bin" version XDG_CACHE_HOME="${tmp_root}/cache" "$bin" format . -expected_ts=$'const a = { x: 1 };\n\nexport default a;\n' +expected_ts=$'const a = { x: 1, s: \'hi\' };\n\nexport default a;\n' expected_go=$'package p\n\nfunc f() {\n\tdefer println("d")\n\n\treturn\n}\n' if ! diff <(printf '%s' "$expected_ts") app.ts; then diff --git a/infra/scripts/tasks/test-entrypoints.sh b/infra/scripts/tasks/test-entrypoints.sh deleted file mode 100755 index 5ef68f3..0000000 --- a/infra/scripts/tasks/test-entrypoints.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -"${script_dir}/test-fmtkit-entrypoint.sh" -"${script_dir}/test-fmtkit-host-entrypoint.sh" -"${script_dir}/test-fmtkit-ts-entrypoint.sh" -"${script_dir}/test-fmtkit-lint-entrypoint.sh" diff --git a/infra/scripts/tasks/test-fmtkit-entrypoint.sh b/infra/scripts/tasks/test-fmtkit-entrypoint.sh deleted file mode 100755 index 11714b4..0000000 --- a/infra/scripts/tasks/test-fmtkit-entrypoint.sh +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -repo_root="$(cd "$(dirname "$0")/../../.." && pwd)" -tmp_root="$(mktemp -d)" - -cleanup() { - rm -rf "$tmp_root" -} - -trap cleanup EXIT - -log_file="$tmp_root/invocations.log" -stdout_file="$tmp_root/stdout.log" -stderr_file="$tmp_root/stderr.log" - -write_stub() { - local path="$1" - local name="$2" - - cat >"$path" <> "$log_file" - case "$name" in - fmtkit-ts) - printf '[blank-lines] processed 3 file(s) in /work, 0 changed\n' - printf 'Finished in 10ms on 3 files using 8 threads.\n' - printf '[fluent-chains] processed 3 file(s) in /work, 1 changed\n' - ;; - fmtkit-lint) - printf 'Found 0 warnings and 0 errors.\n' - ;; - fmtkit-go) - if [[ "\${1:-}" == "format" ]]; then - printf '\nFormatter\n\n' - printf ' Formatted 2 file(s).\n\n' - printf ' Result: pass. 0 changed, 0 violation(s), 0 error(s).\n\n' - printf 'Vet\n\n' - printf ' go vet ./... passed.\n\n' - printf ' Result: pass. 0 error(s).\n' - else - printf 'fmtkit output\n' - fi - ;; -esac -EOF - chmod +x "$path" -} - -assert_contains() { - local path="$1" - local needle="$2" - local content - - content="$(<"$path")" - - if [[ "$content" != *"$needle"* ]]; then - printf 'expected %s to contain %q\n' "$path" "$needle" >&2 - exit 1 - fi -} - -assert_not_contains() { - local path="$1" - local needle="$2" - local content - - content="$(<"$path")" - - if [[ "$content" == *"$needle"* ]]; then - printf 'expected %s to not contain %q\n' "$path" "$needle" >&2 - exit 1 - fi -} - -assert_log_equals() { - local expected="$1" - local content - - content="$(<"$log_file")" - - if [[ "$content" != "$expected" ]]; then - printf 'unexpected invocation log\nexpected:\n%s\nactual:\n%s\n' "$expected" "$content" >&2 - exit 1 - fi -} - -run_entrypoint() { - : >"$log_file" - : >"$stdout_file" - : >"$stderr_file" - - FMTKIT_BIN="$tmp_root/fmtkit-go-stub" \ - FORMAT_TS_BIN="$tmp_root/fmtkit-ts-stub" \ - FORMAT_LINT_BIN="$tmp_root/fmtkit-lint-stub" \ - "$repo_root/infra/bin/fmtkit" "$@" >"$stdout_file" 2>"$stderr_file" -} - -write_stub "$tmp_root/fmtkit-go-stub" fmtkit-go -write_stub "$tmp_root/fmtkit-ts-stub" fmtkit-ts -write_stub "$tmp_root/fmtkit-lint-stub" fmtkit-lint - -run_entrypoint format . -assert_log_equals $'fmtkit-ts .\nfmtkit-lint .\nfmtkit-go format .' -assert_contains "$stderr_file" '==> Formatting target(s)' -assert_contains "$stderr_file" 'paths .' -assert_contains "$stderr_file" '==> Running TS/Vue formatting' -assert_contains "$stderr_file" 'blank-lines processed 3 file(s) in /work, 0 changed' -assert_contains "$stderr_file" 'oxfmt Finished in 10ms on 3 files using 8 threads.' -assert_contains "$stderr_file" 'fluent processed 3 file(s) in /work, 1 changed' -assert_contains "$stderr_file" '==> Running TS/Vue lint' -assert_contains "$stderr_file" 'oxlint Found 0 warnings and 0 errors.' -assert_contains "$stderr_file" '==> Running Go formatting' -assert_contains "$stderr_file" 'fmtkit' -assert_contains "$stderr_file" 'Formatted 2 file(s).' -assert_contains "$stderr_file" 'result' -assert_contains "$stderr_file" 'pass. 0 changed, 0 violation(s), 0 error(s).' -assert_contains "$stderr_file" 'vet' -assert_contains "$stderr_file" 'go vet ./... passed.' -assert_contains "$stderr_file" '==> Formatting complete' -assert_contains "$stderr_file" 'status' -assert_contains "$stderr_file" 'done' - -run_entrypoint format-all -assert_log_equals $'fmtkit-ts .\nfmtkit-lint .\nfmtkit-go format .' - -run_entrypoint go format . -assert_log_equals 'fmtkit-go format .' -assert_not_contains "$stderr_file" '==> Running TS/Vue formatting' - -run_entrypoint ts . -assert_log_equals 'fmtkit-ts .' -assert_not_contains "$stderr_file" '==> Running Go formatting' - -run_entrypoint check . -assert_log_equals 'fmtkit-go check .' - -run_entrypoint version -assert_log_equals 'fmtkit-go version' - -if run_entrypoint unknown; then - printf 'expected unknown mode to fail\n' >&2 - exit 1 -fi - -assert_contains "$stderr_file" 'usage: fmtkit [args...]' diff --git a/infra/scripts/tasks/test-fmtkit-host-entrypoint.sh b/infra/scripts/tasks/test-fmtkit-host-entrypoint.sh deleted file mode 100755 index a268aa9..0000000 --- a/infra/scripts/tasks/test-fmtkit-host-entrypoint.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -repo_root="$(cd "$(dirname "$0")/../../.." && pwd)" -tmp_root="$(mktemp -d)" - -cleanup() { - rm -rf "$tmp_root" -} - -trap cleanup EXIT - -bin_dir="$tmp_root/bin" -project_dir="$tmp_root/project" -log_file="$tmp_root/docker.log" - -mkdir -p "$bin_dir" "$project_dir" - -cat >"$bin_dir/docker" < "$log_file" -EOF - -chmod +x "$bin_dir/docker" - -assert_contains() { - local path="$1" - local needle="$2" - local content - - content="$(<"$path")" - - if [[ "$content" != *"$needle"* ]]; then - printf 'expected %s to contain %q\n' "$path" "$needle" >&2 - printf 'actual:\n%s\n' "$content" >&2 - exit 1 - fi -} - -( - cd "$tmp_root" - PATH="$bin_dir:$PATH" \ - FMTKIT_IMAGE="ghcr.io/oullin/fmtkit:test" \ - FMTKIT_CACHE_VOLUME="fmtkit-cache-test" \ - FMTKIT_PROJECT_DIR="project" \ - "$repo_root/infra/scripts/tasks/fmtkit-host.sh" format . -) - -assert_contains "$log_file" "run --rm" -assert_contains "$log_file" "-v $project_dir:/work" -assert_contains "$log_file" "-v fmtkit-cache-test:/cache" -assert_contains "$log_file" "-e HOST_PROJECT_PATH=$project_dir" -assert_contains "$log_file" "ghcr.io/oullin/fmtkit:test format ." diff --git a/infra/scripts/tasks/test-fmtkit-lint-entrypoint.sh b/infra/scripts/tasks/test-fmtkit-lint-entrypoint.sh deleted file mode 100755 index c9c5f46..0000000 --- a/infra/scripts/tasks/test-fmtkit-lint-entrypoint.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -repo_root="$(cd "$(dirname "$0")/../../.." && pwd)" -tmp_root="$(mktemp -d)" - -cleanup() { - rm -rf "$tmp_root" -} - -trap cleanup EXIT - -support_dir="$tmp_root/support" -bin_dir="$tmp_root/bin" -workdir="$tmp_root/work" -log_file="$tmp_root/invocations.log" - -mkdir -p "$support_dir/node_modules/.bin" "$bin_dir" "$workdir" -: >"$log_file" - -write_executable() { - local path="$1" - local body="$2" - - printf '%s\n' "$body" >"$path" - chmod +x "$path" -} - -write_executable "$bin_dir/fmtkit-sources" '#!/usr/bin/env bash -set -euo pipefail -printf "fmtkit-sources %s\n" "$*" >> "'"$log_file"'" -printf "fmtkit-sources-config %s %s %s\n" "${GIT_CONFIG_COUNT:-}" "${GIT_CONFIG_KEY_0:-}" "${GIT_CONFIG_VALUE_0:-}" >> "'"$log_file"'" -printf "sample.ts\0"' - -write_executable "$support_dir/node_modules/.bin/oxlint" '#!/usr/bin/env bash -set -euo pipefail -printf "oxlint %s\n" "$*" >> "'"$log_file"'"' - -# Bundled config exists but no project-local config -> --config fallback is used. -touch "$support_dir/.oxlintrc.json" - -( - cd "$workdir" - PATH="$bin_dir:$PATH" \ - FMTKIT_SUPPORT_DIR="$support_dir" \ - FMTKIT_SOURCES_BIN="$bin_dir/fmtkit-sources" \ - "$repo_root/infra/bin/fmtkit-lint" . -) - -expected=$'fmtkit-sources .\nfmtkit-sources-config 1 safe.directory *\noxlint --config '"$support_dir"$'/.oxlintrc.json sample.ts' -actual="$(<"$log_file")" - -if [[ "$actual" != "$expected" ]]; then - printf 'unexpected invocation log\nexpected:\n%s\nactual:\n%s\n' "$expected" "$actual" >&2 - exit 1 -fi - -# A project-local config takes precedence: no --config flag is passed. -: >"$log_file" -touch "$workdir/.oxlintrc.json" - -( - cd "$workdir" - PATH="$bin_dir:$PATH" \ - FMTKIT_SUPPORT_DIR="$support_dir" \ - FMTKIT_SOURCES_BIN="$bin_dir/fmtkit-sources" \ - "$repo_root/infra/bin/fmtkit-lint" . -) - -expected=$'fmtkit-sources .\nfmtkit-sources-config 1 safe.directory *\noxlint sample.ts' -actual="$(<"$log_file")" - -if [[ "$actual" != "$expected" ]]; then - printf 'unexpected invocation log (project-local config)\nexpected:\n%s\nactual:\n%s\n' "$expected" "$actual" >&2 - exit 1 -fi diff --git a/infra/scripts/tasks/test-fmtkit-ts-entrypoint.sh b/infra/scripts/tasks/test-fmtkit-ts-entrypoint.sh deleted file mode 100755 index b43cddb..0000000 --- a/infra/scripts/tasks/test-fmtkit-ts-entrypoint.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -repo_root="$(cd "$(dirname "$0")/../../.." && pwd)" -tmp_root="$(mktemp -d)" - -cleanup() { - rm -rf "$tmp_root" -} - -trap cleanup EXIT - -support_dir="$tmp_root/support" -bin_dir="$tmp_root/bin" -workdir="$tmp_root/work" -log_file="$tmp_root/invocations.log" - -mkdir -p "$support_dir/node_modules/.bin" "$bin_dir" "$workdir" -: >"$log_file" - -write_executable() { - local path="$1" - local body="$2" - - printf '%s\n' "$body" >"$path" - chmod +x "$path" -} - -write_executable "$bin_dir/fmtkit-sources" '#!/usr/bin/env bash -set -euo pipefail -printf "fmtkit-sources %s\n" "$*" >> "'"$log_file"'" -printf "fmtkit-sources-config %s %s %s\n" "${GIT_CONFIG_COUNT:-}" "${GIT_CONFIG_KEY_0:-}" "${GIT_CONFIG_VALUE_0:-}" >> "'"$log_file"'" -if [[ "${1:-}" == "--include-declarations" ]]; then - printf "sample.ts\0types.d.ts\0" - exit 0 -fi -printf "sample.ts\0"' - -write_executable "$support_dir/node_modules/.bin/tsx" '#!/usr/bin/env bash -set -euo pipefail -printf "tsx %s\n" "$*" >> "'"$log_file"'" -printf "tsx-config %s %s %s\n" "${GIT_CONFIG_COUNT:-}" "${GIT_CONFIG_KEY_0:-}" "${GIT_CONFIG_VALUE_0:-}" >> "'"$log_file"'"' - -write_executable "$support_dir/node_modules/.bin/oxfmt" '#!/usr/bin/env bash -set -euo pipefail -printf "oxfmt %s\n" "$*" >> "'"$log_file"'" -while IFS= read -r -d "" file; do - printf "oxfmt-file %s\n" "$file" >> "'"$log_file"'" -done' - -touch "$support_dir/format-all.ts" - -( - cd "$workdir" - PATH="$bin_dir:$PATH" \ - FMTKIT_SUPPORT_DIR="$support_dir" \ - FMTKIT_SOURCES_BIN="$bin_dir/fmtkit-sources" \ - "$repo_root/infra/bin/fmtkit-ts" . -) - -expected=$'fmtkit-sources .\nfmtkit-sources-config 1 safe.directory *\nfmtkit-sources --include-declarations .\nfmtkit-sources-config 1 safe.directory *\ntsx '"$support_dir"$'/format-all.ts --oxfmt-bin '"$support_dir"$'/node_modules/.bin/oxfmt --format-files sample.ts --syntax-files sample.ts types.d.ts\ntsx-config 1 safe.directory *' -actual="$(<"$log_file")" - -if [[ "$actual" != "$expected" ]]; then - printf 'unexpected invocation log\nexpected:\n%s\nactual:\n%s\n' "$expected" "$actual" >&2 - exit 1 -fi diff --git a/infra/scripts/tasks/test-format.sh b/infra/scripts/tasks/test-format.sh deleted file mode 100755 index bec5df8..0000000 --- a/infra/scripts/tasks/test-format.sh +++ /dev/null @@ -1,334 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -repo_root="$(cd "$(dirname "$0")/../../.." && pwd)" -actual_go="$(command -v go)" -tmp_root="$(mktemp -d)" - -cleanup() { - chmod -R u+w "$tmp_root" 2>/dev/null || true - rm -rf "$tmp_root" -} - -trap cleanup EXIT - -write_file() { - local path="$1" - local content="$2" - - mkdir -p "$(dirname "$path")" - printf '%s' "$content" >"$path" -} - -assert_contains() { - local path="$1" - local needle="$2" - local content - - content="$(<"$path")" - - if [[ "$content" != *"$needle"* ]]; then - printf 'expected %s to contain %q\n' "$path" "$needle" >&2 - exit 1 - fi -} - -assert_not_contains() { - local path="$1" - local needle="$2" - local content - - content="$(<"$path")" - - if [[ "$content" == *"$needle"* ]]; then - printf 'expected %s to not contain %q\n' "$path" "$needle" >&2 - exit 1 - fi -} - -assert_go_invocation_equals() { - local fixture_root="$1" - local expected_target="$2" - local expected - - expected="-C $repo_root run ./packages/driver/cmd/fmtkit-go format --cwd $fixture_root $expected_target" - - assert_contains "$fixture_root/go-invocations.log" "$expected" -} - -create_fixture() { - local name="$1" - local fixture_root - fixture_root="$(cd "$tmp_root" && pwd -P)/$name" - - mkdir -p "$fixture_root" - cp -R "$repo_root/cmd" "$fixture_root/cmd" - cp -R "$repo_root/scripts" "$fixture_root/scripts" - mkdir -p "$fixture_root/semantic" - mkdir -p "$fixture_root/storage/test-bin" - - cat >"$fixture_root/oxfmt-stub.sh" <<'EOF' -#!/usr/bin/env bash -set -euo pipefail -printf 'oxfmt %s\n' "$*" >> "${TOOL_WRAPPER_LOG:?}" -exit 0 -EOF - chmod +x "$fixture_root/oxfmt-stub.sh" - - cat >"$fixture_root/tsx-stub.sh" <<'EOF' -#!/usr/bin/env bash -set -euo pipefail -printf 'tsx %s\n' "$*" >> "${TOOL_WRAPPER_LOG:?}" -exit 0 -EOF - chmod +x "$fixture_root/tsx-stub.sh" - - cat >"$fixture_root/storage/test-bin/go" <> "\${GO_WRAPPER_LOG:?}" -exec "$actual_go" "\$@" -EOF - chmod +x "$fixture_root/storage/test-bin/go" - - ( - cd "$fixture_root" - git init -q - git config user.email tests@example.com - git config user.name 'Test Runner' - git add cmd scripts oxfmt-stub.sh tsx-stub.sh - git commit -q -m 'initial' - ) - - printf '%s\n' "$fixture_root" -} - -run_format() { - local fixture_root="$1" - shift - - : >"$fixture_root/go-invocations.log" - : >"$fixture_root/tool-invocations.log" - - ( - cd "$fixture_root" - GO_BIN="$fixture_root/storage/test-bin/go" \ - GO_WRAPPER_LOG="$fixture_root/go-invocations.log" \ - GO_WORKDIR="$repo_root" \ - OXFMT_BIN="$fixture_root/oxfmt-stub.sh" \ - TOOL_WRAPPER_LOG="$fixture_root/tool-invocations.log" \ - TSX_BIN="$fixture_root/tsx-stub.sh" \ - ./infra/scripts/tasks/format.sh "$@" - ) -} - -test_tracked_go_uses_git_diff() { - local fixture_root - fixture_root="$(create_fixture tracked-go)" - - write_file "$fixture_root/semantic/pkg/api/changed.go" 'package sample - -func run() { - println("ok") -} -' - - ( - cd "$fixture_root" - git add semantic/pkg/api/changed.go - git commit -q -m 'add tracked file' - ) - - write_file "$fixture_root/semantic/pkg/api/changed.go" 'package sample - -func run() { - defer println("done") - return -} -' - - run_format "$fixture_root" . - - assert_go_invocation_equals "$fixture_root" "$fixture_root" - assert_contains "$fixture_root/semantic/pkg/api/changed.go" 'defer println("done") - - return' -} - -test_untracked_go_disables_git_diff_and_formats_both() { - local fixture_root - fixture_root="$(create_fixture untracked-go)" - - write_file "$fixture_root/semantic/pkg/api/changed.go" 'package sample - -func run() { - println("ok") -} -' - - ( - cd "$fixture_root" - git add semantic/pkg/api/changed.go - git commit -q -m 'add tracked file' - ) - - write_file "$fixture_root/semantic/pkg/api/changed.go" 'package sample - -func run() { - defer println("done") - return -} -' - write_file "$fixture_root/semantic/pkg/api/new.go" 'package sample - -func create() { - defer println("new") - return -} -' - - run_format "$fixture_root" . - - assert_go_invocation_equals "$fixture_root" "$fixture_root" - assert_contains "$fixture_root/semantic/pkg/api/changed.go" 'defer println("done") - - return' - assert_contains "$fixture_root/semantic/pkg/api/new.go" 'defer println("new") - - return' -} - -test_untracked_non_go_keeps_git_diff() { - local fixture_root - fixture_root="$(create_fixture untracked-non-go)" - - write_file "$fixture_root/semantic/pkg/api/changed.go" 'package sample - -func run() { - println("ok") -} -' - - ( - cd "$fixture_root" - git add semantic/pkg/api/changed.go - git commit -q -m 'add tracked file' - ) - - write_file "$fixture_root/semantic/pkg/api/changed.go" 'package sample - -func run() { - defer println("done") - return -} -' - write_file "$fixture_root/semantic/pkg/api/notes.txt" 'keep me' - - run_format "$fixture_root" . - - assert_go_invocation_equals "$fixture_root" "$fixture_root" - assert_contains "$fixture_root/semantic/pkg/api/changed.go" 'defer println("done") - - return' -} - -test_explicit_path_uses_explicit_args() { - local fixture_root - fixture_root="$(create_fixture explicit-path)" - - write_file "$fixture_root/semantic/pkg/api/changed.go" 'package sample - -func run() { - println("ok") -} -' - - ( - cd "$fixture_root" - git add semantic/pkg/api/changed.go - git commit -q -m 'add tracked file' - ) - - write_file "$fixture_root/semantic/pkg/api/changed.go" 'package sample - -func run() { - defer println("done") - return -} -' - - run_format "$fixture_root" semantic/pkg/api/changed.go - - assert_go_invocation_equals "$fixture_root" "$fixture_root/semantic/pkg/api/changed.go" - assert_contains "$fixture_root/semantic/pkg/api/changed.go" 'defer println("done") - - return' -} - -test_repo_root_falls_back_to_full_semantic_workspace() { - local fixture_root - fixture_root="$(create_fixture full-workspace-fallback)" - - write_file "$fixture_root/semantic/pkg/api/ordered.go" 'package sample - -type Name string - -const DefaultName Name = "demo" -' - write_file "$fixture_root/semantic/pkg/api/changed.go" 'package sample - -func run() { - println("ok") -} -' - - ( - cd "$fixture_root" - git add semantic/pkg/api/ordered.go semantic/pkg/api/changed.go - git commit -q -m 'add tracked go files' - ) - - write_file "$fixture_root/semantic/pkg/api/changed.go" 'package sample - -func run() { - defer println("done") - return -} -' - - run_format "$fixture_root" . - - assert_go_invocation_equals "$fixture_root" "$fixture_root" - assert_contains "$fixture_root/semantic/pkg/api/changed.go" 'defer println("done") - - return' -} - -test_ts_only_target_runs_full_pipeline_without_go_files() { - local fixture_root - fixture_root="$(create_fixture ts-only-target)" - - write_file "$fixture_root/src/app.ts" 'const value=1 -' - - ( - cd "$fixture_root" - git add "$fixture_root/src/app.ts" - git commit -q -m 'add tracked ts file' - ) - - run_format "$fixture_root" "$fixture_root/src/app.ts" - - assert_go_invocation_equals "$fixture_root" "$fixture_root/src/app.ts" - assert_contains "$fixture_root/go-invocations.log" "-C $repo_root run ./packages/driver/cmd/fmtkit-sources --cwd " - assert_contains "$fixture_root/tool-invocations.log" "tsx $fixture_root/packages/devx/scripts/format-all.ts " - assert_contains "$fixture_root/tool-invocations.log" "--oxfmt-bin $fixture_root/oxfmt-stub.sh" - assert_contains "$fixture_root/tool-invocations.log" "$fixture_root/src/app.ts" -} - -test_tracked_go_uses_git_diff -test_untracked_go_disables_git_diff_and_formats_both -test_untracked_non_go_keeps_git_diff -test_explicit_path_uses_explicit_args -test_repo_root_falls_back_to_full_semantic_workspace -test_ts_only_target_runs_full_pipeline_without_go_files diff --git a/infra/tooling/docker/Makefile b/infra/tooling/docker/Makefile deleted file mode 100644 index 7949d7e..0000000 --- a/infra/tooling/docker/Makefile +++ /dev/null @@ -1,73 +0,0 @@ -SHELL := /bin/bash -.DEFAULT_GOAL := help - -ARGS ?= . -VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) -FORMATTER_IMAGE ?= fmtkit-full:local -FORMATTER_DOCKERFILE ?= infra/docker/Dockerfile.full -FORMATTER_BUILD ?= auto -GO_IMAGE ?= fmtkit-go:local -NODE_TS_IMAGE ?= fmtkit-node-ts:local -FULL_IMAGE ?= fmtkit-full:local -FMTKIT_IMAGE ?= ghcr.io/oullin/fmtkit:latest -FMTKIT_CACHE_VOLUME ?= fmtkit-cache -FMTKIT_PROJECT_DIR ?= $(CURDIR) - -export VERSION FORMATTER_IMAGE FORMATTER_DOCKERFILE FORMATTER_BUILD -export GO_IMAGE NODE_TS_IMAGE FULL_IMAGE -export FMTKIT_IMAGE FMTKIT_CACHE_VOLUME FMTKIT_PROJECT_DIR - -.PHONY: help format format-all check image-go image-node-ts image-full docker-clean host-format host-format-go host-format-ts host-format-full host-check host-version host-help - -help: ## Show Docker compatibility targets - @printf 'fmtkit Docker compatibility targets\n\n' - @printf ' make format Dockerized full formatter against ARGS (default ".")\n' - @printf ' make format-all Dockerized full formatter against the whole repository\n' - @printf ' make check Dockerized fmtkit check against ARGS (default ".")\n' - @printf ' make image-go Build the local Go-only formatter image\n' - @printf ' make image-node-ts Build the local Node/TS-only formatter image\n' - @printf ' make image-full Build the local full formatter image\n' - @printf ' make docker-clean Remove fmtkit local images and cache volume\n' - @printf '\nVariables: ARGS VERSION FORMATTER_IMAGE FORMATTER_BUILD FMTKIT_IMAGE FMTKIT_CACHE_VOLUME\n' - -format: ## Run the dockerized full formatter pipeline against ARGS - @./infra/scripts/tasks/format-docker.sh format $(ARGS) - -format-all: ## Run the dockerized full formatter pipeline against the whole repository - @./infra/scripts/tasks/format-docker.sh format-all - -check: ## Run dockerized fmtkit check against ARGS - @./infra/scripts/tasks/format-docker.sh check $(ARGS) - -image-go: ## Build the local Go-only formatter image - @./infra/scripts/tasks/docker-image.sh go - -image-node-ts: ## Build the local Node/TS-only formatter image - @./infra/scripts/tasks/docker-image.sh node-ts - -image-full: ## Build the local full Go + TS formatter image - @./infra/scripts/tasks/docker-image.sh full - -docker-clean: ## Remove fmtkit's local images and cache volume - @./infra/scripts/tasks/docker-image.sh clean - -host-format: ## Run the published dockerized full formatter pipeline against ARGS - @./infra/scripts/tasks/fmtkit-host.sh format $(ARGS) - -host-format-go: ## Run the published dockerized Go-only formatter against ARGS - @FMTKIT_IMAGE=ghcr.io/oullin/fmtkit:latest-go ./infra/scripts/tasks/fmtkit-host.sh format $(ARGS) - -host-format-ts: ## Run the published dockerized Node/TS-only formatter against ARGS - @FMTKIT_IMAGE=ghcr.io/oullin/fmtkit:latest-node-ts ./infra/scripts/tasks/fmtkit-host.sh $(ARGS) - -host-format-full: ## Run the published dockerized full formatter against ARGS - @FMTKIT_IMAGE=ghcr.io/oullin/fmtkit:latest-full ./infra/scripts/tasks/fmtkit-host.sh format $(ARGS) - -host-check: ## Run published dockerized fmtkit check against ARGS - @./infra/scripts/tasks/fmtkit-host.sh go check $(ARGS) - -host-version: ## Print the published dockerized fmtkit version - @./infra/scripts/tasks/fmtkit-host.sh go version - -host-help: ## Print the published dockerized fmtkit usage - @./infra/scripts/tasks/fmtkit-host.sh go help diff --git a/packages/devx/scripts/format-all.ts b/packages/devx/scripts/format-all.ts index cf60532..92cecdd 100644 --- a/packages/devx/scripts/format-all.ts +++ b/packages/devx/scripts/format-all.ts @@ -7,10 +7,9 @@ import { isNotFoundError, isTargetFile } from '#devx/pass-utils'; import { validateFile } from '#devx/validate-syntax'; // format-all runs the full TS pipeline (blank-lines → oxfmt → fluent-chains -// → oxfmt → validate-syntax) inside a single Node process, replacing the -// three tsx spawns infra/bin/fmtkit-ts-files used to make per invocation. Files -// within a pass are processed concurrently; results are reported in input -// order so the output stays deterministic. +// → oxfmt → validate-syntax) inside a single process. Files within a pass are +// processed concurrently; results are reported in input order so the output +// stays deterministic. type CliOptions = { check: boolean; diff --git a/packages/driver/cmd/fmtkit-go/main.go b/packages/driver/cmd/fmtkit-go/main.go index d459b7f..1fdd046 100644 --- a/packages/driver/cmd/fmtkit-go/main.go +++ b/packages/driver/cmd/fmtkit-go/main.go @@ -50,7 +50,7 @@ func run(args []string, stdout, stderr io.Writer) int { } func printUsage(w io.Writer) { - _, _ = fmt.Fprintf(w, "fmtkit check [--host-path /absolute/host/path] [paths...]\n\n") - _, _ = fmt.Fprintf(w, "fmtkit format [--host-path /absolute/host/path] [paths...]\n\n") + _, _ = fmt.Fprintf(w, "fmtkit check [paths...]\n\n") + _, _ = fmt.Fprintf(w, "fmtkit format [paths...]\n\n") _, _ = fmt.Fprintf(w, "fmtkit sources [--include-declarations] [paths...]\n\n") } diff --git a/packages/driver/cmd/fmtkit-go/main_test.go b/packages/driver/cmd/fmtkit-go/main_test.go index 6f47c12..bbdb824 100644 --- a/packages/driver/cmd/fmtkit-go/main_test.go +++ b/packages/driver/cmd/fmtkit-go/main_test.go @@ -7,7 +7,6 @@ import ( "strings" "testing" - "github.com/oullin/fmtkit/packages/driver/internal/cli" "github.com/oullin/fmtkit/packages/driver/testutil" ) @@ -146,105 +145,6 @@ func run() { } } -func TestRunCheckWithHostPath(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "sample.go") - testutil.WriteGoFile(t, path, `package sample - -func run() { - if true { - println("ok") - } - println("next") -} -`) - t.Setenv(cli.HostRootEnv, dir) - - exitCode, stdout, stderr := runCLI(t, dir, "check", "--host-path", path) - - if exitCode != 1 { - t.Fatalf("expected exit code 1, got %d", exitCode) - } - - if !strings.Contains(stdout, "Result: fail") { - t.Fatalf("unexpected stdout:\n%s", stdout) - } - - if stderr != "" { - t.Fatalf("unexpected stderr:\n%s", stderr) - } -} - -func TestRunFormatWithHostPath(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "sample.go") - testutil.WriteGoFile(t, path, `package sample - -func run() { - defer println("done") - return -} -`) - t.Setenv(cli.HostRootEnv, dir) - - exitCode, stdout, stderr := runCLI(t, dir, "format", "--host-path", path) - - if exitCode != 0 { - t.Fatalf("expected exit code 0, got %d", exitCode) - } - - if !strings.Contains(stdout, "Result: fixed") { - t.Fatalf("unexpected stdout:\n%s", stdout) - } - - if stderr != "" { - t.Fatalf("unexpected stderr:\n%s", stderr) - } - - content, err := os.ReadFile(path) - - if err != nil { - t.Fatalf("read file: %v", err) - } - - if !strings.Contains(string(content), "defer println(\"done\")\n\n\treturn") { - t.Fatalf("expected formatted file, got:\n%s", content) - } -} - -func TestRunWithHostPathRequiresEnv(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "sample.go") - testutil.WriteGoFile(t, path, "package sample\n") - - exitCode, _, stderr := runCLI(t, dir, "check", "--host-path", path) - - if exitCode != 1 { - t.Fatalf("expected exit code 1, got %d", exitCode) - } - - if !strings.Contains(stderr, cli.HostRootEnv) { - t.Fatalf("unexpected stderr:\n%s", stderr) - } -} - -func TestRunWithHostPathRejectsPositionalPaths(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "sample.go") - testutil.WriteGoFile(t, path, "package sample\n") - t.Setenv(cli.HostRootEnv, dir) - - exitCode, _, stderr := runCLI(t, dir, "check", "--host-path", path, dir) - - if exitCode != 1 { - t.Fatalf("expected exit code 1, got %d", exitCode) - } - - if !strings.Contains(stderr, "cannot be used with positional paths") { - t.Fatalf("unexpected stderr:\n%s", stderr) - } -} - func TestRunSourcesEmitsNullDelimitedTypeScriptFiles(t *testing.T) { dir := t.TempDir() canonicalDir, err := filepath.EvalSymlinks(dir) @@ -319,7 +219,7 @@ func TestPrintUsage(t *testing.T) { t.Errorf("expected empty stdout, got %q", stdout) } - if !strings.Contains(stderr, "fmtkit check [--host-path /absolute/host/path] [paths...]") { + if !strings.Contains(stderr, "fmtkit check [paths...]") { t.Errorf("expected stderr to contain usage, got %q", stderr) } @@ -494,34 +394,6 @@ func run() { } } -func TestRunWithHostPathRunsGoVetInModule(t *testing.T) { - dir := writeTempModule(t, "example.com/sample") - path := filepath.Join(dir, "sample.go") - testutil.WriteGoFile(t, path, `package sample - -import "fmt" - -func run() { - fmt.Printf("%d", "not-a-number") -} -`) - t.Setenv(cli.HostRootEnv, dir) - - exitCode, stdout, stderr := runCLI(t, dir, "check", "--host-path", path) - - if exitCode != 1 { - t.Fatalf("expected exit code 1, got %d", exitCode) - } - - if !strings.Contains(stdout, "automatic go vet ./... failed") { - t.Fatalf("expected stdout to include go vet failure, got:\n%s", stdout) - } - - if stderr != "" { - t.Fatalf("unexpected stderr:\n%s", stderr) - } -} - func TestRunCheckWithVetDisabledSkipsGoVet(t *testing.T) { dir := writeTempModule(t, "example.com/sample") configPath := filepath.Join(dir, "config.yml") diff --git a/packages/driver/cmd/fmtkit/main.go b/packages/driver/cmd/fmtkit/main.go index 2985f33..2a2d4d4 100644 --- a/packages/driver/cmd/fmtkit/main.go +++ b/packages/driver/cmd/fmtkit/main.go @@ -1,246 +1,18 @@ // Command fmtkit is the self-contained fmtkit binary distributed through -// GitHub Releases and Homebrew: the pipeline orchestration that -// infra/bin/fmtkit provides in the container images, fused with the Go -// formatter CLI and the embedded TS toolchain (see internal/tsruntime). +// GitHub Releases and Homebrew. The command surface lives in internal/app; this +// entrypoint only carries the version stamped in by -X main.version. package main import ( - "errors" - "fmt" - "io" "os" - "os/exec" - "strings" - "github.com/oullin/fmtkit/packages/driver/internal/cli" - "github.com/oullin/fmtkit/packages/driver/internal/orchestrator" - "github.com/oullin/fmtkit/packages/driver/internal/tsruntime" + "github.com/oullin/fmtkit/packages/driver/internal/app" ) var version = "dev" func main() { - os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) -} - -func run(args []string, stdout, stderr io.Writer) int { - if len(args) == 0 { - printUsage(stderr) - - return 2 - } - - mode := args[0] - rest := args[1:] - - switch mode { - case "format": - opts, paths, err := parseFormatArgs(rest) - - if err != nil { - _, _ = fmt.Fprintf(stderr, "%v\n\n", err) - - printUsage(stderr) - - return 2 - } - - return runPipeline(paths, opts, stderr) - case "format-all": - opts, extra, err := parseFormatArgs(rest) - - if err != nil || len(extra) != 0 { - if err != nil { - _, _ = fmt.Fprintf(stderr, "%v\n\n", err) - } - - printUsage(stderr) - - return 2 - } - - return runPipeline([]string{"."}, opts, stderr) - case "ts": - return runTS(rest, stdout, stderr) - case "lint": - return runLint(rest, stdout, stderr) - case "go": - return runGo(rest, stdout, stderr) - case "check": - return cli. - NewRunner(stdout, stderr). - Run(cli.CheckMode, rest) - case "version", "--version", "-version": - _, _ = fmt.Fprintf(stdout, "fmtkit %s\n", version) - - return 0 - case "help", "--help", "-h": - printUsage(stderr) - - return 0 - default: - _, _ = fmt.Fprintf(stderr, "unknown subcommand - {%q}\n\n", mode) - - printUsage(stderr) - - return 2 - } -} - -func runPipeline(paths []string, opts formatOptions, stderr io.Writer) int { - pipeline := orchestrator.Pipeline{ - Tools: orchestrator.Tools{ - TS: func(scopes []string, output io.Writer) error { - support, err := tsruntime.Resolve(version) - - if err != nil { - return err - } - - return support.RunPipeline(tsruntime.RunOptions{Scopes: scopes, Stdout: output, Stderr: output}) - }, - Lint: func(scopes []string, output io.Writer) error { - support, err := tsruntime.Resolve(version) - - if err != nil { - return err - } - - return support.RunLint(tsruntime.RunOptions{Scopes: scopes, Stdout: output, Stderr: output}) - }, - Go: func(args []string, output io.Writer) int { - return cli. - NewRunner(output, output). - Run(cli.FormatMode, args[1:]) - }, - }, - Steps: opts.steps, - Quiet: opts.quiet, - Stderr: stderr, - } - - return pipeline.RunFormat(paths) -} - -func runTS(paths []string, stdout, stderr io.Writer) int { - support, err := tsruntime.Resolve(version) - - if err != nil { - return reportError(err, stderr) - } - - return reportError(support.RunPipeline(tsruntime.RunOptions{Scopes: paths, Stdout: stdout, Stderr: stderr}), stderr) -} - -func runLint(paths []string, stdout, stderr io.Writer) int { - support, err := tsruntime.Resolve(version) - - if err != nil { - return reportError(err, stderr) - } - - return reportError(support.RunLint(tsruntime.RunOptions{Scopes: paths, Stdout: stdout, Stderr: stderr}), stderr) -} - -// runGo mirrors the fmtkit-go command surface so `fmtkit go ...` behaves like -// the container's Go formatter CLI. -func runGo(args []string, stdout, stderr io.Writer) int { - if len(args) == 0 { - printGoUsage(stderr) - - return 2 - } - - switch args[0] { - case "check": - return cli. - NewRunner(stdout, stderr). - Run(cli.CheckMode, args[1:]) - case "format": - return cli. - NewRunner(stdout, stderr). - Run(cli.FormatMode, args[1:]) - case "sources": - return cli.RunSources(args[1:], stdout, stderr) - case "version", "--version", "-version": - _, _ = fmt.Fprintf(stdout, "fmtkit %s\n", version) - - return 0 - case "help", "--help", "-h": - printGoUsage(stderr) - - return 0 - default: - _, _ = fmt.Fprintf(stderr, "unknown subcommand - {%q}\n\n", args[0]) - - printGoUsage(stderr) - - return 2 - } -} - -type formatOptions struct { - steps orchestrator.Steps - quiet bool -} - -// parseFormatArgs splits the format/format-all flags from the paths. With no -// step flags the whole pipeline runs; --ts and --go narrow it. -func parseFormatArgs(args []string) (formatOptions, []string, error) { - var opts formatOptions - - var paths []string - - for _, arg := range args { - switch arg { - case "--ts": - opts.steps.TS = true - case "--go": - opts.steps.Go = true - case "--quiet", "-q": - opts.quiet = true - default: - if strings.HasPrefix(arg, "-") { - return formatOptions{}, nil, fmt.Errorf("unknown flag - {%q}", arg) - } - - paths = append(paths, arg) - } - } - - return opts, paths, nil -} - -func reportError(err error, stderr io.Writer) int { - if err == nil { - return 0 - } - - var exit *exec.ExitError - - if errors.As(err, &exit) { - return exit.ExitCode() - } - - _, _ = fmt.Fprintf(stderr, "fmtkit: %v\n", err) - - return 1 -} - -func printUsage(w io.Writer) { - _, _ = fmt.Fprintf(w, "usage: fmtkit [args...]\n") - _, _ = fmt.Fprintf(w, " format [--ts] [--go] [--quiet] [paths...] run the formatting pipeline\n") - _, _ = fmt.Fprintf(w, " format-all [--ts] [--go] [--quiet] run the full formatter pipeline against .\n") - _, _ = fmt.Fprintf(w, " --ts only TS/Vue formatting + lint; --go only Go formatting; default: all\n") - _, _ = fmt.Fprintf(w, " go run the Go formatter CLI\n") - _, _ = fmt.Fprintf(w, " ts [paths...] run TS/Vue formatting support and oxfmt\n") - _, _ = fmt.Fprintf(w, " lint [paths...] lint TS/Vue files with oxlint\n") - _, _ = fmt.Fprintf(w, " check [args...] run the Go formatter in check mode\n") - _, _ = fmt.Fprintf(w, " version print the fmtkit version\n") -} - -func printGoUsage(w io.Writer) { - _, _ = fmt.Fprintf(w, "fmtkit go check [--host-path /absolute/host/path] [paths...]\n\n") - _, _ = fmt.Fprintf(w, "fmtkit go format [--host-path /absolute/host/path] [paths...]\n\n") - _, _ = fmt.Fprintf(w, "fmtkit go sources [--include-declarations] [paths...]\n\n") + os.Exit(app. + New(version, os.Stdout, os.Stderr). + Run(os.Args[1:])) } diff --git a/packages/driver/internal/app/app.go b/packages/driver/internal/app/app.go new file mode 100644 index 0000000..9f2d322 --- /dev/null +++ b/packages/driver/internal/app/app.go @@ -0,0 +1,71 @@ +package app + +import ( + "fmt" + "io" + + "github.com/oullin/fmtkit/packages/driver/internal/cli" +) + +// App is the fmtkit command surface. The version is injected by the binary so +// release builds keep stamping it through -X main.version. +type App struct { + version string + stdout io.Writer + stderr io.Writer +} + +func New(version string, stdout, stderr io.Writer) App { + return App{ + version: version, + stdout: stdout, + stderr: stderr, + } +} + +// Run dispatches a subcommand to its handler; each mode lives in its own file. +func (a App) Run(args []string) int { + if len(args) == 0 { + printUsage(a.stderr) + + return 2 + } + + mode := args[0] + rest := args[1:] + + switch mode { + case "format": + return a.runFormat(rest) + case "format-all": + return a.runFormatAll(rest) + case "ts": + return a.runTS(rest) + case "lint": + return a.runLint(rest) + case "go": + return a.runGo(rest) + case "check": + return cli. + NewRunner(a.stdout, a.stderr). + Run(cli.CheckMode, rest) + case "version", "--version", "-version": + return a.printVersion() + case "help", "--help", "-h": + printUsage(a.stderr) + + return 0 + default: + _, _ = fmt.Fprintf(a.stderr, "unknown subcommand - {%q}\n\n", mode) + + printUsage(a.stderr) + + return 2 + } +} + +func (a App) printVersion() int { + _, _ = fmt.Fprintf(a.stdout, "fmtkit %s\n", a.version) + + return 0 +} diff --git a/packages/driver/cmd/fmtkit/main_test.go b/packages/driver/internal/app/app_test.go similarity index 98% rename from packages/driver/cmd/fmtkit/main_test.go rename to packages/driver/internal/app/app_test.go index b953bd9..c2dd94d 100644 --- a/packages/driver/cmd/fmtkit/main_test.go +++ b/packages/driver/internal/app/app_test.go @@ -1,4 +1,4 @@ -package main +package app import ( "os" @@ -39,7 +39,9 @@ func runCLI(t *testing.T, workdir string, args ...string) (int, string, string) var stdout strings.Builder var stderr strings.Builder - exitCode := run(args, &stdout, &stderr) + + // "dev" mirrors the unstamped binary: no embedded TS assets. + exitCode := New("dev", &stdout, &stderr).Run(args) return exitCode, stdout.String(), stderr.String() } diff --git a/packages/driver/internal/app/doc.go b/packages/driver/internal/app/doc.go new file mode 100644 index 0000000..b84e779 --- /dev/null +++ b/packages/driver/internal/app/doc.go @@ -0,0 +1,5 @@ +// Package app implements the fmtkit command surface: the pipeline +// orchestration that infra/bin/fmtkit provides in the container images, fused +// with the Go formatter CLI and the embedded TS toolchain (see +// internal/tsruntime). +package app diff --git a/packages/driver/internal/app/exit.go b/packages/driver/internal/app/exit.go new file mode 100644 index 0000000..d8964ae --- /dev/null +++ b/packages/driver/internal/app/exit.go @@ -0,0 +1,25 @@ +package app + +import ( + "errors" + "fmt" + "os/exec" +) + +// reportError maps a tool failure onto an exit code, propagating the child's +// own code when it already reported the problem itself. +func (a App) reportError(err error) int { + if err == nil { + return 0 + } + + var exit *exec.ExitError + + if errors.As(err, &exit) { + return exit.ExitCode() + } + + _, _ = fmt.Fprintf(a.stderr, "fmtkit: %v\n", err) + + return 1 +} diff --git a/packages/driver/internal/app/format.go b/packages/driver/internal/app/format.go new file mode 100644 index 0000000..4bbf9f6 --- /dev/null +++ b/packages/driver/internal/app/format.go @@ -0,0 +1,78 @@ +package app + +import ( + "fmt" + "io" + + "github.com/oullin/fmtkit/packages/driver/internal/cli" + "github.com/oullin/fmtkit/packages/driver/internal/orchestrator" + "github.com/oullin/fmtkit/packages/driver/internal/tsruntime" +) + +// runFormat formats the given paths, defaulting to the whole pipeline. +func (a App) runFormat(args []string) int { + opts, paths, err := parseFormatArgs(args) + + if err != nil { + _, _ = fmt.Fprintf(a.stderr, "%v\n\n", err) + + printUsage(a.stderr) + + return 2 + } + + return a.runPipeline(paths, opts) +} + +// runFormatAll is runFormat pinned to the current directory, so it takes flags +// but rejects paths. +func (a App) runFormatAll(args []string) int { + opts, extra, err := parseFormatArgs(args) + + if err != nil || len(extra) != 0 { + if err != nil { + _, _ = fmt.Fprintf(a.stderr, "%v\n\n", err) + } + + printUsage(a.stderr) + + return 2 + } + + return a.runPipeline([]string{"."}, opts) +} + +func (a App) runPipeline(paths []string, opts formatOptions) int { + pipeline := orchestrator.Pipeline{ + Tools: orchestrator.Tools{ + TS: func(scopes []string, output io.Writer) error { + support, err := tsruntime.Resolve(a.version) + + if err != nil { + return err + } + + return support.RunPipeline(tsruntime.RunOptions{Scopes: scopes, Stdout: output, Stderr: output}) + }, + Lint: func(scopes []string, output io.Writer) error { + support, err := tsruntime.Resolve(a.version) + + if err != nil { + return err + } + + return support.RunLint(tsruntime.RunOptions{Scopes: scopes, Stdout: output, Stderr: output}) + }, + Go: func(args []string, output io.Writer) int { + return cli. + NewRunner(output, output). + Run(cli.FormatMode, args[1:]) + }, + }, + Steps: opts.steps, + Quiet: opts.quiet, + Stderr: a.stderr, + } + + return pipeline.RunFormat(paths) +} diff --git a/packages/driver/internal/app/golang.go b/packages/driver/internal/app/golang.go new file mode 100644 index 0000000..4fde2c0 --- /dev/null +++ b/packages/driver/internal/app/golang.go @@ -0,0 +1,42 @@ +package app + +import ( + "fmt" + + "github.com/oullin/fmtkit/packages/driver/internal/cli" +) + +// runGo mirrors the fmtkit-go command surface so `fmtkit go ...` behaves like +// the container's Go formatter CLI. +func (a App) runGo(args []string) int { + if len(args) == 0 { + printGoUsage(a.stderr) + + return 2 + } + + switch args[0] { + case "check": + return cli. + NewRunner(a.stdout, a.stderr). + Run(cli.CheckMode, args[1:]) + case "format": + return cli. + NewRunner(a.stdout, a.stderr). + Run(cli.FormatMode, args[1:]) + case "sources": + return cli.RunSources(args[1:], a.stdout, a.stderr) + case "version", "--version", "-version": + return a.printVersion() + case "help", "--help", "-h": + printGoUsage(a.stderr) + + return 0 + default: + _, _ = fmt.Fprintf(a.stderr, "unknown subcommand - {%q}\n\n", args[0]) + + printGoUsage(a.stderr) + + return 2 + } +} diff --git a/packages/driver/internal/app/options.go b/packages/driver/internal/app/options.go new file mode 100644 index 0000000..9767ca3 --- /dev/null +++ b/packages/driver/internal/app/options.go @@ -0,0 +1,40 @@ +package app + +import ( + "fmt" + "strings" + + "github.com/oullin/fmtkit/packages/driver/internal/orchestrator" +) + +type formatOptions struct { + steps orchestrator.Steps + quiet bool +} + +// parseFormatArgs splits the format/format-all flags from the paths. With no +// step flags the whole pipeline runs; --ts and --go narrow it. +func parseFormatArgs(args []string) (formatOptions, []string, error) { + var opts formatOptions + + var paths []string + + for _, arg := range args { + switch arg { + case "--ts": + opts.steps.TS = true + case "--go": + opts.steps.Go = true + case "--quiet", "-q": + opts.quiet = true + default: + if strings.HasPrefix(arg, "-") { + return formatOptions{}, nil, fmt.Errorf("unknown flag - {%q}", arg) + } + + paths = append(paths, arg) + } + } + + return opts, paths, nil +} diff --git a/packages/driver/internal/app/ts.go b/packages/driver/internal/app/ts.go new file mode 100644 index 0000000..0710fa7 --- /dev/null +++ b/packages/driver/internal/app/ts.go @@ -0,0 +1,25 @@ +package app + +import ( + "github.com/oullin/fmtkit/packages/driver/internal/tsruntime" +) + +func (a App) runTS(paths []string) int { + support, err := tsruntime.Resolve(a.version) + + if err != nil { + return a.reportError(err) + } + + return a.reportError(support.RunPipeline(tsruntime.RunOptions{Scopes: paths, Stdout: a.stdout, Stderr: a.stderr})) +} + +func (a App) runLint(paths []string) int { + support, err := tsruntime.Resolve(a.version) + + if err != nil { + return a.reportError(err) + } + + return a.reportError(support.RunLint(tsruntime.RunOptions{Scopes: paths, Stdout: a.stdout, Stderr: a.stderr})) +} diff --git a/packages/driver/internal/app/usage.go b/packages/driver/internal/app/usage.go new file mode 100644 index 0000000..a35f6ed --- /dev/null +++ b/packages/driver/internal/app/usage.go @@ -0,0 +1,24 @@ +package app + +import ( + "fmt" + "io" +) + +func printUsage(w io.Writer) { + _, _ = fmt.Fprintf(w, "usage: fmtkit [args...]\n") + _, _ = fmt.Fprintf(w, " format [--ts] [--go] [--quiet] [paths...] run the formatting pipeline\n") + _, _ = fmt.Fprintf(w, " format-all [--ts] [--go] [--quiet] run the full formatter pipeline against .\n") + _, _ = fmt.Fprintf(w, " --ts only TS/Vue formatting + lint; --go only Go formatting; default: all\n") + _, _ = fmt.Fprintf(w, " go run the Go formatter CLI\n") + _, _ = fmt.Fprintf(w, " ts [paths...] run TS/Vue formatting support and oxfmt\n") + _, _ = fmt.Fprintf(w, " lint [paths...] lint TS/Vue files with oxlint\n") + _, _ = fmt.Fprintf(w, " check [args...] run the Go formatter in check mode\n") + _, _ = fmt.Fprintf(w, " version print the fmtkit version\n") +} + +func printGoUsage(w io.Writer) { + _, _ = fmt.Fprintf(w, "fmtkit go check [paths...]\n\n") + _, _ = fmt.Fprintf(w, "fmtkit go format [paths...]\n\n") + _, _ = fmt.Fprintf(w, "fmtkit go sources [--include-declarations] [paths...]\n\n") +} diff --git a/packages/driver/internal/cli/host_path.go b/packages/driver/internal/cli/host_path.go deleted file mode 100644 index 2288c7c..0000000 --- a/packages/driver/internal/cli/host_path.go +++ /dev/null @@ -1,62 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "path/filepath" - "strings" -) - -type HostPath string - -const HostRootEnv = "HOST_PROJECT_PATH" - -func (h HostPath) Resolve(workRoot string, positional []string) ([]string, error) { - hostPath := string(h) - - if hostPath == "" { - return positional, nil - } - - if len(positional) > 0 { - return nil, fmt.Errorf("--host-path cannot be used with positional paths") - } - - if !filepath.IsAbs(hostPath) { - return nil, fmt.Errorf("--host-path must be an absolute path") - } - - hostRoot, ok := os.LookupEnv(HostRootEnv) - - if !ok || strings.TrimSpace(hostRoot) == "" { - return nil, fmt.Errorf("--host-path requires %s to be set", HostRootEnv) - } - - hostRootAbs, err := filepath.Abs(hostRoot) - - if err != nil { - return nil, fmt.Errorf("resolve %s: %w", HostRootEnv, err) - } - - hostPathAbs, err := filepath.Abs(hostPath) - - if err != nil { - return nil, fmt.Errorf("resolve --host-path: %w", err) - } - - rel, err := filepath.Rel(hostRootAbs, hostPathAbs) - - if err != nil { - return nil, fmt.Errorf("map --host-path: %w", err) - } - - if rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { - return nil, fmt.Errorf("--host-path must be within %s (%s)", HostRootEnv, hostRootAbs) - } - - if rel == "." { - return []string{workRoot}, nil - } - - return []string{filepath.Join(workRoot, rel)}, nil -} diff --git a/packages/driver/internal/cli/host_path_test.go b/packages/driver/internal/cli/host_path_test.go deleted file mode 100644 index 17dd05f..0000000 --- a/packages/driver/internal/cli/host_path_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package cli_test - -import ( - "path/filepath" - "testing" - - "github.com/oullin/fmtkit/packages/driver/internal/cli" -) - -func TestResolveRunPathsRootMapsToWorkRoot(t *testing.T) { - workRoot := filepath.Join(string(filepath.Separator), "work") - hostRoot := filepath.Join(string(filepath.Separator), "host", "project") - t.Setenv(cli.HostRootEnv, hostRoot) - - paths, err := cli.HostPath(hostRoot).Resolve(workRoot, nil) - - if err != nil { - t.Fatalf("resolve host path: %v", err) - } - - if len(paths) != 1 || paths[0] != workRoot { - t.Fatalf("unexpected paths: %#v", paths) - } -} - -func TestResolveRunPathsNestedDirectoryMapsToWorkRoot(t *testing.T) { - workRoot := filepath.Join(string(filepath.Separator), "work") - hostRoot := filepath.Join(string(filepath.Separator), "host", "project") - hostPath := filepath.Join(hostRoot, "pkg", "api") - t.Setenv(cli.HostRootEnv, hostRoot) - - paths, err := cli.HostPath(hostPath).Resolve(workRoot, nil) - - if err != nil { - t.Fatalf("resolve host path: %v", err) - } - - want := filepath.Join(workRoot, "pkg", "api") - - if len(paths) != 1 || paths[0] != want { - t.Fatalf("unexpected paths: got %#v want %q", paths, want) - } -} - -func TestResolveRunPathsSingleFileMapsToWorkRoot(t *testing.T) { - workRoot := filepath.Join(string(filepath.Separator), "work") - hostRoot := filepath.Join(string(filepath.Separator), "host", "project") - hostPath := filepath.Join(hostRoot, "pkg", "api", "sample.go") - t.Setenv(cli.HostRootEnv, hostRoot) - - paths, err := cli.HostPath(hostPath).Resolve(workRoot, nil) - - if err != nil { - t.Fatalf("resolve host path: %v", err) - } - - want := filepath.Join(workRoot, "pkg", "api", "sample.go") - - if len(paths) != 1 || paths[0] != want { - t.Fatalf("unexpected paths: got %#v want %q", paths, want) - } -} - -func TestResolveRunPathsRejectsOutsideRoot(t *testing.T) { - workRoot := filepath.Join(string(filepath.Separator), "work") - hostRoot := filepath.Join(string(filepath.Separator), "host", "project") - hostPath := filepath.Join(string(filepath.Separator), "host", "other") - t.Setenv(cli.HostRootEnv, hostRoot) - - _, err := cli.HostPath(hostPath).Resolve(workRoot, nil) - - if err == nil { - t.Fatal("expected error") - } -} - -func TestResolveRunPathsRejectsMissingHostRoot(t *testing.T) { - workRoot := filepath.Join(string(filepath.Separator), "work") - hostPath := filepath.Join(string(filepath.Separator), "host", "project") - t.Setenv(cli.HostRootEnv, "") - - _, err := cli.HostPath(hostPath).Resolve(workRoot, nil) - - if err == nil { - t.Fatal("expected error") - } -} - -func TestResolveRunPathsRejectsPositionalPaths(t *testing.T) { - workRoot := filepath.Join(string(filepath.Separator), "work") - hostRoot := filepath.Join(string(filepath.Separator), "host", "project") - t.Setenv(cli.HostRootEnv, hostRoot) - - _, err := cli.HostPath(hostRoot).Resolve(workRoot, []string{"."}) - - if err == nil { - t.Fatal("expected error") - } -} diff --git a/packages/driver/internal/cli/options.go b/packages/driver/internal/cli/options.go index 2196a11..b703525 100644 --- a/packages/driver/internal/cli/options.go +++ b/packages/driver/internal/cli/options.go @@ -5,7 +5,6 @@ type options struct { configPath string reportRoot string outputFormat string - hostPath HostPath positional []string // jobs overrides config.Concurrency when not -1. -1 means "unset" // (no override); 0 means "use NumCPU"; positive values pin the worker count. diff --git a/packages/driver/internal/cli/parser.go b/packages/driver/internal/cli/parser.go index 87082a2..80aab8e 100644 --- a/packages/driver/internal/cli/parser.go +++ b/packages/driver/internal/cli/parser.go @@ -23,7 +23,6 @@ func (p parser) Parse(mode Mode, args []string) (options, error) { configPath := fs.String("config", "", "Path to fmtkit YAML config") reportRoot := fs.String("cwd", "", "Path used for config discovery and report-relative file paths") outputFormat := fs.String("format", "text", "Output format: text, json, agent") - hostPath := fs.String("host-path", "", "Absolute host path under HOST_PROJECT_PATH to check or format") jobs := fs.Int("jobs", envJobs(), "Max files processed in parallel (0 = NumCPU; also reads FMTKIT_JOBS)") if err := fs.Parse(args); err != nil { @@ -35,7 +34,6 @@ func (p parser) Parse(mode Mode, args []string) (options, error) { configPath: *configPath, reportRoot: *reportRoot, outputFormat: *outputFormat, - hostPath: HostPath(*hostPath), positional: fs.Args(), jobs: *jobs, }, nil diff --git a/packages/driver/internal/cli/parser_test.go b/packages/driver/internal/cli/parser_test.go index 9c4cba5..1af3eea 100644 --- a/packages/driver/internal/cli/parser_test.go +++ b/packages/driver/internal/cli/parser_test.go @@ -34,7 +34,6 @@ func TestParseAllFlags(t *testing.T) { "--config", "custom.yml", "--cwd", "/repo", "--format", "json", - "--host-path", "/host/project", "--jobs", "4", "main.go", "pkg", } @@ -50,7 +49,6 @@ func TestParseAllFlags(t *testing.T) { configPath: "custom.yml", reportRoot: "/repo", outputFormat: "json", - hostPath: HostPath("/host/project"), positional: []string{"main.go", "pkg"}, jobs: 4, } diff --git a/packages/driver/internal/cli/runner.go b/packages/driver/internal/cli/runner.go index 3515bf7..d08625e 100644 --- a/packages/driver/internal/cli/runner.go +++ b/packages/driver/internal/cli/runner.go @@ -57,13 +57,7 @@ func (r Runner) Run(mode Mode, args []string) int { return 1 } - runPaths, err := opts.hostPath.Resolve(workRoot, opts.positional) - - if err != nil { - r.writeError("%v\n", err) - - return 1 - } + runPaths := opts.positional formatterCfg := cfg.FormatterConfig() diff --git a/packages/driver/internal/cli/runner_test.go b/packages/driver/internal/cli/runner_test.go index 848310d..795cd60 100644 --- a/packages/driver/internal/cli/runner_test.go +++ b/packages/driver/internal/cli/runner_test.go @@ -169,24 +169,6 @@ func TestRunnerRunRejectsUnsupportedFormat(t *testing.T) { } } -func TestRunnerRunRejectsRelativeHostPath(t *testing.T) { - dir := t.TempDir() - - t.Chdir(dir) - - var out, errOut bytes.Buffer - - code := NewRunner(&out, &errOut).Run(CheckMode, []string{"--host-path", "relative/path"}) - - if code != 1 { - t.Fatalf("exit = %d", code) - } - - if !strings.Contains(errOut.String(), "--host-path must be an absolute path") { - t.Fatalf("unexpected stderr: %s", errOut.String()) - } -} - func TestRunnerRunRejectsUnknownFlag(t *testing.T) { dir := t.TempDir() diff --git a/packages/driver/internal/embedded/doc.go b/packages/driver/internal/embedded/doc.go new file mode 100644 index 0000000..eb2da19 --- /dev/null +++ b/packages/driver/internal/embedded/doc.go @@ -0,0 +1,7 @@ +// Package embedded carries the TS toolchain baked into release binaries. +// +// The assets are staged under bin/_/ by +// infra/scripts/release/stage-ts-assets.sh and are only compiled in under the +// fmtkit_sidecar build tag (see sidecar_*.go); ordinary builds get the +// sidecar_dev.go stub instead, so the staged directories need not exist. +package embedded diff --git a/embedded_sidecar_darwin_amd64.go b/packages/driver/internal/embedded/sidecar_darwin_amd64.go similarity index 53% rename from embedded_sidecar_darwin_amd64.go rename to packages/driver/internal/embedded/sidecar_darwin_amd64.go index 09c24a2..814de04 100644 --- a/embedded_sidecar_darwin_amd64.go +++ b/packages/driver/internal/embedded/sidecar_darwin_amd64.go @@ -1,18 +1,21 @@ //go:build fmtkit_sidecar && darwin && amd64 -package fmtkit +package embedded import ( "embed" "io/fs" ) -//go:embed infra/bin/darwin_amd64 +// all: keeps the bundled .oxfmtrc.json and .oxlintrc.json, which a directory +// pattern would drop for starting with a dot. +// +//go:embed all:bin/darwin_amd64 var sidecarAssets embed.FS // SidecarAssets returns the TS toolchain staged for this platform. func SidecarAssets() (fs.FS, bool) { - sub, err := fs.Sub(sidecarAssets, "infra/bin/darwin_amd64") + sub, err := fs.Sub(sidecarAssets, "bin/darwin_amd64") if err != nil { panic(err) diff --git a/embedded_sidecar_darwin_arm64.go b/packages/driver/internal/embedded/sidecar_darwin_arm64.go similarity index 53% rename from embedded_sidecar_darwin_arm64.go rename to packages/driver/internal/embedded/sidecar_darwin_arm64.go index d1fd60d..ade5e19 100644 --- a/embedded_sidecar_darwin_arm64.go +++ b/packages/driver/internal/embedded/sidecar_darwin_arm64.go @@ -1,18 +1,21 @@ //go:build fmtkit_sidecar && darwin && arm64 -package fmtkit +package embedded import ( "embed" "io/fs" ) -//go:embed infra/bin/darwin_arm64 +// all: keeps the bundled .oxfmtrc.json and .oxlintrc.json, which a directory +// pattern would drop for starting with a dot. +// +//go:embed all:bin/darwin_arm64 var sidecarAssets embed.FS // SidecarAssets returns the TS toolchain staged for this platform. func SidecarAssets() (fs.FS, bool) { - sub, err := fs.Sub(sidecarAssets, "infra/bin/darwin_arm64") + sub, err := fs.Sub(sidecarAssets, "bin/darwin_arm64") if err != nil { panic(err) diff --git a/embedded_sidecar_dev.go b/packages/driver/internal/embedded/sidecar_dev.go similarity index 92% rename from embedded_sidecar_dev.go rename to packages/driver/internal/embedded/sidecar_dev.go index abfc3ee..cf009a1 100644 --- a/embedded_sidecar_dev.go +++ b/packages/driver/internal/embedded/sidecar_dev.go @@ -1,6 +1,6 @@ //go:build !fmtkit_sidecar -package fmtkit +package embedded import "io/fs" diff --git a/embedded_sidecar_linux_amd64.go b/packages/driver/internal/embedded/sidecar_linux_amd64.go similarity index 53% rename from embedded_sidecar_linux_amd64.go rename to packages/driver/internal/embedded/sidecar_linux_amd64.go index 76b1fa0..03850ba 100644 --- a/embedded_sidecar_linux_amd64.go +++ b/packages/driver/internal/embedded/sidecar_linux_amd64.go @@ -1,18 +1,21 @@ //go:build fmtkit_sidecar && linux && amd64 -package fmtkit +package embedded import ( "embed" "io/fs" ) -//go:embed infra/bin/linux_amd64 +// all: keeps the bundled .oxfmtrc.json and .oxlintrc.json, which a directory +// pattern would drop for starting with a dot. +// +//go:embed all:bin/linux_amd64 var sidecarAssets embed.FS // SidecarAssets returns the TS toolchain staged for this platform. func SidecarAssets() (fs.FS, bool) { - sub, err := fs.Sub(sidecarAssets, "infra/bin/linux_amd64") + sub, err := fs.Sub(sidecarAssets, "bin/linux_amd64") if err != nil { panic(err) diff --git a/embedded_sidecar_linux_arm64.go b/packages/driver/internal/embedded/sidecar_linux_arm64.go similarity index 53% rename from embedded_sidecar_linux_arm64.go rename to packages/driver/internal/embedded/sidecar_linux_arm64.go index 7bd7aab..47cc907 100644 --- a/embedded_sidecar_linux_arm64.go +++ b/packages/driver/internal/embedded/sidecar_linux_arm64.go @@ -1,18 +1,21 @@ //go:build fmtkit_sidecar && linux && arm64 -package fmtkit +package embedded import ( "embed" "io/fs" ) -//go:embed infra/bin/linux_arm64 +// all: keeps the bundled .oxfmtrc.json and .oxlintrc.json, which a directory +// pattern would drop for starting with a dot. +// +//go:embed all:bin/linux_arm64 var sidecarAssets embed.FS // SidecarAssets returns the TS toolchain staged for this platform. func SidecarAssets() (fs.FS, bool) { - sub, err := fs.Sub(sidecarAssets, "infra/bin/linux_arm64") + sub, err := fs.Sub(sidecarAssets, "bin/linux_arm64") if err != nil { panic(err) diff --git a/packages/driver/internal/orchestrator/logging.go b/packages/driver/internal/orchestrator/logging.go index 37e4d3f..c93f71c 100644 --- a/packages/driver/internal/orchestrator/logging.go +++ b/packages/driver/internal/orchestrator/logging.go @@ -1,8 +1,7 @@ // Package orchestrator drives the full fmtkit formatting pipeline (TS/Vue -// formatting, TS/Vue lint, Go formatting) with the sectioned, colorized -// progress output the infra/bin/fmtkit entrypoint established. Unlike the -// bash entrypoint, tool output streams live (indented under each section) -// and is followed by the condensed summary lines. +// formatting, TS/Vue lint, Go formatting) with sectioned, colorized progress +// output: each step's tool output streams live, indented under its section +// header, and is followed by the condensed summary lines. package orchestrator import ( diff --git a/packages/driver/internal/orchestrator/pipeline.go b/packages/driver/internal/orchestrator/pipeline.go index a1effe8..9016d90 100644 --- a/packages/driver/internal/orchestrator/pipeline.go +++ b/packages/driver/internal/orchestrator/pipeline.go @@ -23,14 +23,6 @@ type Steps struct { Go bool } -func (s Steps) normalized() Steps { - if !s.TS && !s.Go { - return Steps{TS: true, Go: true} - } - - return s -} - // Pipeline renders sectioned progress on Stderr while running the steps. type Pipeline struct { Tools Tools @@ -43,8 +35,16 @@ type Pipeline struct { Stderr io.Writer } +func (s Steps) normalized() Steps { + if !s.TS && !s.Go { + return Steps{TS: true, Go: true} + } + + return s +} + // RunFormat runs TS/Vue formatting, TS/Vue lint, and Go formatting against -// the given paths, the Go port of run_format_pipeline in infra/bin/fmtkit. +// the given paths. func (p Pipeline) RunFormat(paths []string) int { if len(paths) == 0 { paths = []string{"."} diff --git a/packages/driver/internal/orchestrator/pipeline_test.go b/packages/driver/internal/orchestrator/pipeline_test.go index e1cf8d2..760c01e 100644 --- a/packages/driver/internal/orchestrator/pipeline_test.go +++ b/packages/driver/internal/orchestrator/pipeline_test.go @@ -12,12 +12,6 @@ import ( // TestMain pins a color-free environment: CI task runners export FORCE_COLOR, // which would inject ANSI codes into the captured output these tests assert. -func TestMain(m *testing.M) { - _ = os.Unsetenv("FORCE_COLOR") - _ = os.Setenv("NO_COLOR", "1") - - os.Exit(m.Run()) -} // The stub outputs mirror infra/scripts/tasks/test-fmtkit-entrypoint.sh so // the Go orchestrator preserves the entrypoint's summary contract. @@ -27,6 +21,13 @@ type invocation struct { args []string } +func TestMain(m *testing.M) { + _ = os.Unsetenv("FORCE_COLOR") + _ = os.Setenv("NO_COLOR", "1") + + os.Exit(m.Run()) +} + const ( stubTSOutput = "[blank-lines] processed 3 file(s) in /work, 0 changed\n" + "Finished in 10ms on 3 files using 8 threads.\n" + diff --git a/packages/driver/internal/orchestrator/summarize.go b/packages/driver/internal/orchestrator/summarize.go index 24314e1..253655b 100644 --- a/packages/driver/internal/orchestrator/summarize.go +++ b/packages/driver/internal/orchestrator/summarize.go @@ -6,9 +6,8 @@ import ( "strings" ) -// The summarizers are ports of the summarize_* helpers in infra/bin/fmtkit: -// they distill a step's captured output into the aligned detail lines shown -// under its section header. +// The summarizers distill a step's captured output into the aligned detail +// lines shown under its section header. var ( lintResultPattern = regexp.MustCompile(`Found [0-9]+ warning|[0-9]+ error`) diff --git a/packages/driver/internal/tsruntime/run.go b/packages/driver/internal/tsruntime/run.go index 5852165..9494f98 100644 --- a/packages/driver/internal/tsruntime/run.go +++ b/packages/driver/internal/tsruntime/run.go @@ -10,9 +10,6 @@ import ( "github.com/oullin/fmtkit/packages/driver/internal/sourcefiles" ) -// Env override names shared with the container entrypoints; see -// infra/bin/fmtkit-ts-files and infra/bin/fmtkit-ts-lint. - // RunOptions describes one TS toolchain invocation. type RunOptions struct { // Scopes are the paths to process, defaulting to ".". @@ -32,8 +29,7 @@ const ( ) // RunPipeline runs the full TS/Vue formatting pipeline (blank-lines -> oxfmt -// -> fluent-chains -> oxfmt -> validate-syntax), the Go port of -// infra/bin/fmtkit-ts-files. +// -> fluent-chains -> oxfmt -> validate-syntax). func (s Support) RunPipeline(opts RunOptions) error { cwd, err := sourcesCwd() @@ -79,8 +75,7 @@ func (s Support) RunPipeline(opts RunOptions) error { return s.spawn(pipelineBin(s.Sidecar()), args, opts) } -// RunLint lints the collected TS/Vue files with oxlint, the Go port of -// infra/bin/fmtkit-ts-lint. +// RunLint lints the collected TS/Vue files with oxlint. func (s Support) RunLint(opts RunOptions) error { cwd, err := sourcesCwd() @@ -167,8 +162,8 @@ func (s Support) oxfmtConfigFor(cwd string) string { return s.OxfmtConfig() } -// oxlintConfigFor mirrors infra/bin/fmtkit-ts-lint: both the extensionless -// .oxlintrc and .oxlintrc.* count as project configuration. +// oxlintConfigFor treats both the extensionless .oxlintrc and .oxlintrc.* as +// project configuration. func (s Support) oxlintConfigFor(cwd string) string { if config := os.Getenv(OxlintConfigEnv); config != "" { return existingFile(config) diff --git a/packages/driver/internal/tsruntime/support.go b/packages/driver/internal/tsruntime/support.go index 91c6a77..1536ce4 100644 --- a/packages/driver/internal/tsruntime/support.go +++ b/packages/driver/internal/tsruntime/support.go @@ -15,7 +15,7 @@ import ( "path/filepath" "sort" - fmtkit "github.com/oullin/fmtkit" + "github.com/oullin/fmtkit/packages/driver/internal/embedded" ) // SupportDirEnv points at a pre-extracted toolchain directory and skips @@ -72,14 +72,13 @@ func Resolve(version string) (Support, error) { return support, nil } - assets, ok := fmtkit.SidecarAssets() + assets, ok := embedded.SidecarAssets() if !ok { return Support{}, errors.New( "this fmtkit build carries no TS toolchain (built without the fmtkit_sidecar tag); " + "point " + SupportDirEnv + " at a staged toolchain directory " + - "(see infra/scripts/release/stage-ts-assets.sh), or use a release binary " + - "or the ghcr.io/oullin/fmtkit image", + "(see infra/scripts/release/stage-ts-assets.sh), or use a release binary", ) } @@ -170,17 +169,6 @@ func extract(dst string, assets fs.FS) error { } } - configs := map[string][]byte{ - ".oxfmtrc.json": fmtkit.OxfmtConfig, - ".oxlintrc.json": fmtkit.OxlintConfig, - } - - for name, contents := range configs { - if err := os.WriteFile(filepath.Join(dst, name), contents, 0o644); err != nil { - return fmt.Errorf("write %s: %w", name, err) - } - } - if err := os.WriteFile(filepath.Join(dst, sentinelName), nil, 0o644); err != nil { return fmt.Errorf("write %s: %w", sentinelName, err) } diff --git a/packages/driver/internal/tsruntime/support_test.go b/packages/driver/internal/tsruntime/support_test.go index a4bd727..210d7cd 100644 --- a/packages/driver/internal/tsruntime/support_test.go +++ b/packages/driver/internal/tsruntime/support_test.go @@ -7,12 +7,16 @@ import ( "testing/fstest" ) +// fakeAssets mirrors a directory staged by stage-ts-assets.sh: the bindings +// and the sidecar, plus the configs that ride along with them. func fakeAssets() fstest.MapFS { return fstest.MapFS{ sidecarName: &fstest.MapFile{Data: []byte("#!/bin/sh\n"), Mode: 0o755}, "oxc-parser.node": &fstest.MapFile{Data: []byte("parser")}, "oxfmt.node": &fstest.MapFile{Data: []byte("fmt")}, "oxlint.node": &fstest.MapFile{Data: []byte("lint")}, + ".oxfmtrc.json": &fstest.MapFile{Data: []byte("{}")}, + ".oxlintrc.json": &fstest.MapFile{Data: []byte("{}")}, } } diff --git a/vite.config.ts b/vite.config.ts index f858e5f..fafd964 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -23,18 +23,13 @@ export default defineConfig({ }, tasks: { check: `vp run ${workspacePackages} check`, - 'check:docker': './infra/scripts/tasks/format-docker.sh check', - 'docker:clean': './infra/scripts/tasks/docker-image.sh clean', - 'format:docker': './infra/scripts/tasks/format-docker.sh format', - 'format:local': './infra/scripts/tasks/format.sh', + // fmtkit formats itself with the binary it ships. + format: './infra/scripts/tasks/format.sh', gofmt: './infra/scripts/tasks/fmt-source.sh', - 'image:full': './infra/scripts/tasks/docker-image.sh full', - 'image:go': './infra/scripts/tasks/docker-image.sh go', - 'image:node-ts': './infra/scripts/tasks/docker-image.sh node-ts', 'install-cli': './infra/scripts/tasks/with-storage-env.sh go install ./packages/driver/cmd/fmtkit-go', release: './infra/scripts/release/release.sh', + 'test:binary': './infra/scripts/tasks/test-binary-smoke.sh', 'test:coverage': './infra/scripts/tasks/test-coverage.sh', - 'test:entrypoints': './infra/scripts/tasks/test-entrypoints.sh', 'test-race': 'CGO_ENABLED=1 ./infra/scripts/tasks/with-storage-env.sh go -C packages/formatter test ./... -race -v && CGO_ENABLED=1 ./infra/scripts/tasks/with-storage-env.sh go -C packages/vet test ./... -race -v && CGO_ENABLED=1 ./infra/scripts/tasks/with-storage-env.sh go -C packages/driver test ./... -race -v', vet: `vp run ${goPackages} vet`, From 56545640015f75b8b690fe28581810d78ec64482 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 16 Jul 2026 13:52:21 +0800 Subject: [PATCH 6/8] perf: stream embedded assets into digest and split logs once Addresses review feedback on #48: - assetsDigest streams each asset through the hash via io.Copy instead of fs.ReadFile, so the 30MB+ bun sidecar is never held in memory. - lastWithPrefix takes pre-split lines; summarizers split the log once rather than re-splitting per prefix lookup. - Quote the log redirect target in the app test stub script so temp dirs containing spaces do not break it. --- packages/driver/internal/app/app_test.go | 2 +- .../driver/internal/orchestrator/summarize.go | 21 +++++++------ packages/driver/internal/tsruntime/support.go | 30 +++++++++++++++---- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/packages/driver/internal/app/app_test.go b/packages/driver/internal/app/app_test.go index c2dd94d..1c78996 100644 --- a/packages/driver/internal/app/app_test.go +++ b/packages/driver/internal/app/app_test.go @@ -56,7 +56,7 @@ func stubSupportDir(t *testing.T) (string, string) { script := "#!/usr/bin/env bash\n" + "set -euo pipefail\n" + - "printf '%s\\n' \"$*\" >> " + logFile + "\n" + + "printf '%s\\n' \"$*\" >> \"" + logFile + "\"\n" + "case \"${1:-}\" in\n" + "pipeline)\n" + "\tprintf '[blank-lines] processed 3 file(s) in /work, 0 changed\\n'\n" + diff --git a/packages/driver/internal/orchestrator/summarize.go b/packages/driver/internal/orchestrator/summarize.go index 253655b..9bc9451 100644 --- a/packages/driver/internal/orchestrator/summarize.go +++ b/packages/driver/internal/orchestrator/summarize.go @@ -26,10 +26,10 @@ func lines(log string) []string { return strings.Split(log, "\n") } -func lastWithPrefix(log, prefix string) string { +func lastWithPrefix(logLines []string, prefix string) string { var match string - for _, line := range lines(log) { + for _, line := range logLines { if strings.HasPrefix(line, prefix) { match = line } @@ -39,15 +39,16 @@ func lastWithPrefix(log, prefix string) string { } func summarizeTSFormat(log string, l *logger) { + logLines := lines(log) missing := 0 - for _, line := range lines(log) { + for _, line := range logLines { if strings.HasPrefix(line, sourcesMissingPrefix) { missing++ } } - if line := lastWithPrefix(log, blankLinesPrefix); line != "" { + if line := lastWithPrefix(logLines, blankLinesPrefix); line != "" { l.detail("blank-lines", strings.TrimPrefix(line, "[blank-lines] ")) } @@ -55,21 +56,23 @@ func summarizeTSFormat(log string, l *logger) { l.detail("skipped", fmt.Sprintf("%d missing tracked file(s)", missing)) } - if line := lastWithPrefix(log, oxfmtFinishedPrefix); line != "" { + if line := lastWithPrefix(logLines, oxfmtFinishedPrefix); line != "" { l.detail("oxfmt", line) } - if line := lastWithPrefix(log, fluentChainsPrefix); line != "" { + if line := lastWithPrefix(logLines, fluentChainsPrefix); line != "" { l.detail("fluent", strings.TrimPrefix(line, "[fluent-chains] ")) } - if line := lastWithPrefix(log, validateSyntaxPrefix); line != "" { + if line := lastWithPrefix(logLines, validateSyntaxPrefix); line != "" { l.detail("validated", strings.TrimPrefix(line, "[validate-syntax] ")) } } func summarizeTSLint(log string, l *logger) { - if lastWithPrefix(log, lintNothingToLintLine) != "" { + logLines := lines(log) + + if lastWithPrefix(logLines, lintNothingToLintLine) != "" { l.detail("oxlint", strings.TrimPrefix(lintNothingToLintLine, "[lint] ")) return @@ -77,7 +80,7 @@ func summarizeTSLint(log string, l *logger) { var match string - for _, line := range lines(log) { + for _, line := range logLines { if lintResultPattern.MatchString(line) { match = line } diff --git a/packages/driver/internal/tsruntime/support.go b/packages/driver/internal/tsruntime/support.go index 1536ce4..91fa026 100644 --- a/packages/driver/internal/tsruntime/support.go +++ b/packages/driver/internal/tsruntime/support.go @@ -222,15 +222,33 @@ func assetsDigest(assets fs.FS) (string, error) { sort.Strings(names) for _, name := range names { - contents, err := fs.ReadFile(assets, name) + hash.Write([]byte(name)) - if err != nil { - return "", fmt.Errorf("read embedded %s: %w", name, err) + if err := hashFile(hash, assets, name); err != nil { + return "", err } - - hash.Write([]byte(name)) - hash.Write(contents) } return hex.EncodeToString(hash.Sum(nil))[:12], nil } + +// hashFile streams one asset through hash. The sidecar alone is tens of +// megabytes, so reading assets whole would spike memory for a digest that is +// only used to name a cache directory. +func hashFile(hash io.Writer, assets fs.FS, name string) error { + file, err := assets.Open(name) + + if err != nil { + return fmt.Errorf("open embedded %s: %w", name, err) + } + + defer func() { + _ = file.Close() + }() + + if _, err := io.Copy(hash, file); err != nil { + return fmt.Errorf("read embedded %s: %w", name, err) + } + + return nil +} From 88ddf87f393120d843201e773a5a5fc47a3936f8 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 16 Jul 2026 13:52:31 +0800 Subject: [PATCH 7/8] chore(deps): upgrade Go and TS toolchain dependencies Bumps TypeScript 6.0.3 -> 7.0.2, @types/node 25 -> 26, oxc-parser 0.132 -> 0.140, oxfmt 0.41 -> 0.59, oxlint 1.66 -> 1.74, tsx 4.22 -> 4.23, vite-plus 0.2.1 -> 0.2.4, viper 1.20 -> 1.21 and x/tools 0.43 -> 0.48. Also promotes github.com/mattn/go-isatty to a direct require, matching its existing use in the orchestrator logger. Verified locally: go build, go test ./..., tsc --noEmit, devx check (blank-lines/oxlint/oxfmt) and the devx test suite all pass. oxfmt 0.59 leaves the tree unchanged, so the embedded napi bindings move together with the formatter that produced the current formatting. --- go.mod | 36 +- go.sum | 76 +- package.json | 2 +- packages/devx/package.json | 12 +- pnpm-lock.yaml | 1618 ++++++++++++++++++++++-------------- 5 files changed, 1052 insertions(+), 692 deletions(-) diff --git a/go.mod b/go.mod index 57c3ad1..42f8997 100644 --- a/go.mod +++ b/go.mod @@ -4,27 +4,25 @@ go 1.26.4 require ( github.com/fatih/color v1.19.0 - github.com/spf13/viper v1.20.1 - golang.org/x/tools v0.43.0 + github.com/mattn/go-isatty v0.0.22 + github.com/spf13/viper v1.21.0 + golang.org/x/tools v0.48.0 ) require ( - github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.12.0 // indirect - github.com/spf13/cast v1.7.1 // indirect - github.com/spf13/pflag v1.0.6 // indirect + github.com/fsnotify/fsnotify v1.10.1 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect + github.com/pelletier/go-toml/v2 v2.4.3 // indirect + github.com/sagikazarmark/locafero v0.12.0 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect - golang.org/x/mod v0.34.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.21.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/mod v0.38.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/go.sum b/go.sum index d119db5..1a2054c 100644 --- a/go.sum +++ b/go.sum @@ -1,63 +1,55 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= -github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= +github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/package.json b/package.json index 5237753..b5dabdf 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "typecheck": "vp run --filter devx --fail-if-no-match typecheck" }, "devDependencies": { - "vite-plus": "0.2.1" + "vite-plus": "0.2.4" }, "packageManager": "pnpm@10.33.0" } diff --git a/packages/devx/package.json b/packages/devx/package.json index c6df67d..5d8c92e 100644 --- a/packages/devx/package.json +++ b/packages/devx/package.json @@ -50,11 +50,11 @@ "typecheck": "tsc --noEmit" }, "devDependencies": { - "@types/node": "25.9.1", - "oxc-parser": "0.132.0", - "oxfmt": "0.41.0", - "oxlint": "1.66.0", - "tsx": "4.22.3", - "typescript": "6.0.3" + "@types/node": "26.1.1", + "oxc-parser": "0.140.0", + "oxfmt": "0.59.0", + "oxlint": "1.74.0", + "tsx": "4.23.1", + "typescript": "7.0.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d7840d8..ba04868 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,29 +9,29 @@ importers: .: devDependencies: vite-plus: - specifier: 0.2.1 - version: 0.2.1(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)) + specifier: 0.2.4 + version: 0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@6.0.3)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) packages/devx: devDependencies: '@types/node': - specifier: 25.9.1 - version: 25.9.1 + specifier: 26.1.1 + version: 26.1.1 oxc-parser: - specifier: 0.132.0 - version: 0.132.0 + specifier: 0.140.0 + version: 0.140.0 oxfmt: - specifier: 0.41.0 - version: 0.41.0 + specifier: 0.59.0 + version: 0.59.0(vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))) oxlint: - specifier: 1.66.0 - version: 1.66.0(oxlint-tsgolint@0.23.0) + specifier: 1.74.0 + version: 1.74.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))) tsx: - specifier: 4.22.3 - version: 4.22.3 + specifier: 4.23.1 + version: 4.23.1 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 packages/driver: {} @@ -59,12 +59,21 @@ packages: '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.11.2': + resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} + '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@esbuild/aix-ppc64@0.28.0': resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} engines: {node: '>=18'} @@ -224,666 +233,666 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@napi-rs/wasm-runtime@1.1.4': - resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@oxc-parser/binding-android-arm-eabi@0.132.0': - resolution: {integrity: sha512-KrLaPWa5c9Y7LkW+rKkaUE3y7DBDrQtaf7rlsSDfv6KAHUjgzAIRA761Lrrp6//Yd/Rlie/yEOt9YENCoJnOcw==} + '@oxc-parser/binding-android-arm-eabi@0.140.0': + resolution: {integrity: sha512-ZfjDZ422mo7eo3b3VltqNsV9kmv1qt/sPEAMSl64iOSwhVfd0eIZ9LB79Mbs1xYXJnk7WSROwzBCKDIiVxPTvQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxc-parser/binding-android-arm64@0.132.0': - resolution: {integrity: sha512-SThDrSeamB/kG2+NxcJ5/wSLcV6dUqDknrPLqFYQ0ST/55mtBP4M7Q/f3QbubH6aAd11wpzZn/nwbVRSdobOpg==} + '@oxc-parser/binding-android-arm64@0.140.0': + resolution: {integrity: sha512-Ia8jSvikUX6Sf+Ht+KOCUF/k1HpR0VlmqIYymubmWDebOEGtsyliHDR6JxsZ4IX3/c/GbrB1uh09aVGQv/LQmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxc-parser/binding-darwin-arm64@0.132.0': - resolution: {integrity: sha512-Lc0f/TYoKBghE5/2Gsv7bLXk+TJZunx2Tf61X8hG4ARXdc8UYI26dCGccFSd1AyFbK3jfaNXtMnupggDbjPXdQ==} + '@oxc-parser/binding-darwin-arm64@0.140.0': + resolution: {integrity: sha512-G6VK0nK61pH0d0mBjUqSZbVxGqqO5uzeginLDQj+gOO6ObfJjXRwgkD/ol0w1INcnFeAb6YGGO7qc3ueGHaycQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxc-parser/binding-darwin-x64@0.132.0': - resolution: {integrity: sha512-RG2eJIpf7C21z9HSSXFw1bTArdpKe7Y4fwcJTwRq1yCSe1vSavaN9GA1sm9KqzemTLAGVktQ+7qBTGp0vQeUZg==} + '@oxc-parser/binding-darwin-x64@0.140.0': + resolution: {integrity: sha512-HazBOuZzd2pO1C2uMmp8Gv7mhzMHqKSKDS1OZfcLEvpIcgA+48J92HEtNanVHDIzRD9PRPCV6aS6fkZIWOVl8Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxc-parser/binding-freebsd-x64@0.132.0': - resolution: {integrity: sha512-wQIPntPLtJ8NcBpvKPbEv3NqzV6k8eP8tP/jE9Rg8HTg/j7urZGFSsTCPCW5k77Qfw2DM4vRvc9p3I4yq/Shvw==} + '@oxc-parser/binding-freebsd-x64@0.140.0': + resolution: {integrity: sha512-9hSUU+HmTUyOe4JzMHxNGgLWNY7rrO+6ShicZwImNJacEAACDMIkuEQQkvXSL+WJN50jaNtLYJv8s4OcBdpyUQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxc-parser/binding-linux-arm-gnueabihf@0.132.0': - resolution: {integrity: sha512-PixKEpeSe3yxQWqNyOCBALRYc72+Tj7ILDofUl3iXo25cVOzLA6jHUhmOINRtWIPh7dbUie3QNeabwaQpZTw6w==} + '@oxc-parser/binding-linux-arm-gnueabihf@0.140.0': + resolution: {integrity: sha512-RAEuQsYtS0KcDFqN0ABTjyyNlokS91JeuDuoW9tEbG0JTbRNXnpQUdbYc/16JoA6Z/2ALbNrE3KmxtqDiuIjCQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-parser/binding-linux-arm-musleabihf@0.132.0': - resolution: {integrity: sha512-sCR+DzGHlyHKnbA2z9zWjTUhIo8Sy0enJl4RDsBwPmkxYynPatpwOAWe8W5127SlW0boqUWHGtr1NWn5UwIhXQ==} + '@oxc-parser/binding-linux-arm-musleabihf@0.140.0': + resolution: {integrity: sha512-c4CkHvPvqfojouredJ0w3e6+jiBq0SbFyhH61kr/zPb/7XsaYTNKQ54vmlSsopfdQbNDX40ZeK9Abs2Qet6wcw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-parser/binding-linux-arm64-gnu@0.132.0': - resolution: {integrity: sha512-sQBix5P2cW+IpzTcCwYxnh9yALrKSIkKJThspBvMGcygSMnbzkSvhN7SfuX1hvBk8y1XEChsdkU3ET0V5DmzUw==} + '@oxc-parser/binding-linux-arm64-gnu@0.140.0': + resolution: {integrity: sha512-yrjmLj8ixPB25yqvPGr28meGjb+keed7m1GqqY/0uqkhZIoT4t9zmfwUgFEtC33C7dtE+UQ7TU0IaVxf97SWJg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-arm64-musl@0.132.0': - resolution: {integrity: sha512-WozHg3Kc//8Sk756HXXgMbEAvqtG+Lzb9JOojwQzIGDtN78Az2dLttkb71akWYUF/8IgYfDSlfKh4Uot8is5Vw==} + '@oxc-parser/binding-linux-arm64-musl@0.140.0': + resolution: {integrity: sha512-ggGMQTN8Agwxp2WiLMpdY671dt0qTDJWiWlJeig3HnUwTnerRl0J2JdGVghWBeDcss2D9S2V2Js6dZHEiVabVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxc-parser/binding-linux-ppc64-gnu@0.132.0': - resolution: {integrity: sha512-CmX/ulNBOEwWTyVRmcpYKAcAizW6+OjtLJgo7fXoL9OqQvjF4VER8tPomv44vwzfSCy1BHbsB0ZlZYzYJNj4cA==} + '@oxc-parser/binding-linux-ppc64-gnu@0.140.0': + resolution: {integrity: sha512-IgTs8xYAFgAUGNmR65tIqjlJ8vKgrfXzC515e9goSdfMyKQV4aJpd2pUUudU4u51G64H0/DSEJEXKOraxm9ZCA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-riscv64-gnu@0.132.0': - resolution: {integrity: sha512-j9oQS+hM90SdhviNGWbPgT4+Rlq+ac++q/zjgwPD1mVHgxHzATvoRGtDx0sXGmFOQ9J9YkwAhYGb5MAHL6TAsA==} + '@oxc-parser/binding-linux-riscv64-gnu@0.140.0': + resolution: {integrity: sha512-A1x+PMWZmSGaFVOx2YeNTFau8uD+QO14/vLP4GrcuvUPs3+nBkUOjy9Lus86ftHsDojjYMbvBelmKc3F7Rv08g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-riscv64-musl@0.132.0': - resolution: {integrity: sha512-bLz+Xi+Agnfmd7kWPEsSVwCn2k4EyIalZkNBcQ0OGIv9rqn8VgCPLNd03tM9mKX/5TdlvDXalz0q71BIrOPNqg==} + '@oxc-parser/binding-linux-riscv64-musl@0.140.0': + resolution: {integrity: sha512-zBqpfRo2myWPrPo5xUjeZqlnPXPXsX8BcWtWff66/eGRQdbPjhzPgXa/F+AtxT2afUViPxbuDlwscMKzQ5tg+g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxc-parser/binding-linux-s390x-gnu@0.132.0': - resolution: {integrity: sha512-U6t2qbJU0ypTfyj9QV3W1Y6mITDTL8ai/OR6NUn85vyHthOvobKWgXzU4tu0EskSzlpuVFz1g0jFGulDIUKHxQ==} + '@oxc-parser/binding-linux-s390x-gnu@0.140.0': + resolution: {integrity: sha512-2M1DPm/8w9I//YzFlFC9qXw+r2tJFh5CYwRlYTq2vUJQS7qoQftEDeCZ8EnN7KHtvSiXvYj8mZI5pR7DpXmcEw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-x64-gnu@0.132.0': - resolution: {integrity: sha512-WcEaSNHFk8yz5YFlQQAlhq6jOFmZBB/RKE7uzhyCIf+pF1Lmv9gUH4221mle2Gd9iHyWT3ySNph8yZgb1xYdWg==} + '@oxc-parser/binding-linux-x64-gnu@0.140.0': + resolution: {integrity: sha512-8aRDbZ/U/jO8N7go1MO72jtbpb4uswV8d7vOkMvt/BPgZiyEYvl1VIWK4ESxZZhnJ4tqwVldgX7dNiP/eB1Jdg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-x64-musl@0.132.0': - resolution: {integrity: sha512-iQrV4iJzQgRwK3BWRmQl1C3C6g3wYpXN2WLdQdyR+efoUnncdShZAVp9OgcojtlD3MDRbuOMGG3SjxF4fL4nlQ==} + '@oxc-parser/binding-linux-x64-musl@0.140.0': + resolution: {integrity: sha512-xRqpeI8U2sQQS1W5BMWRyMTxtagkuLG2dEWruet5lFsWHTvBth11/TpSaJatHdqVVwHN0q3uuoS9zRsGinq8hg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxc-parser/binding-openharmony-arm64@0.132.0': - resolution: {integrity: sha512-FWzmUGrZ6GUby4U7WIwcCtab6tdmlTO3xTRRKyb5kjIJVEiaUAT8animUG/nK8ZCA8gkRkPOTId4rl6uTqUmJQ==} + '@oxc-parser/binding-openharmony-arm64@0.140.0': + resolution: {integrity: sha512-GbGRe26MqAKciFRvXeHNQJ6VAHYs9R4miP89sEAncysM3n+f4lnyLWgsa9kklJNpfnxdq2yRoNYHFqwBckVimw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxc-parser/binding-wasm32-wasi@0.132.0': - resolution: {integrity: sha512-TlbMppxJI5CjWDes0QaP6G3aneVg1yikBu5QYI+DUShF9WDL66ccgKFNNGmi/Wybtszw6hxwAvv76T4DaPKnHw==} + '@oxc-parser/binding-wasm32-wasi@0.140.0': + resolution: {integrity: sha512-vFiC1hqys+hkX1GnQkIoiTQJNiUm43Z0lO35ETKXTw0YtpW7+cN58YRRXFAQQ+TgpkIi3lrhcxdlnqz+Oi3ptQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@oxc-parser/binding-win32-arm64-msvc@0.132.0': - resolution: {integrity: sha512-RH/NbFjGKqdUAUi7Oh3LQPxUk2hsWFEEQ38HSnbRQT8QjBZFKqL1fMbmsB3N4jy/KPh9iX94+9dmkEMBBbambw==} + '@oxc-parser/binding-win32-arm64-msvc@0.140.0': + resolution: {integrity: sha512-fGSQldwEYKhM+H8uLt76Op8hh5+FYaR6lvvQ1Txw3Mhn86DyQXLcI0fi1EkFlTK7F+46OCk/j0AJMzZQm6g5Xg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxc-parser/binding-win32-ia32-msvc@0.132.0': - resolution: {integrity: sha512-JUr4jQY9jxoIB/YTLXr6XofSi5xikj6p5/Ns1h0VOBDT0j1jKU+kMsv2xxv51RwnETcXpA1Yw/9oUAfcqfaqEA==} + '@oxc-parser/binding-win32-ia32-msvc@0.140.0': + resolution: {integrity: sha512-sDS2Bai+g3ZWYwfZqmosiSuFDBcVnZ3Ta6pszzsiJoLMqsJEWKcxXXbGa7b7yXr++W2lQNPb3ZRJ8czseqL7RA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxc-parser/binding-win32-x64-msvc@0.132.0': - resolution: {integrity: sha512-2dapgHpA5X8DSXF4AU36hJWYf6zP0tKjMXFRAZFBD62pkevW/uhFDXoFH9Y/3Fd2EtDrw5ByNnR1wVE9X9y0SQ==} + '@oxc-parser/binding-win32-x64-msvc@0.140.0': + resolution: {integrity: sha512-kHbE1zWyb5OQgJA6/5P4WjiuB01sYdQwtZnSSyE58FQEXDAMnyeeq4vj7KgN75i5SlBzOs8A5MrtlD3gOlDKqQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxc-project/runtime@0.136.0': - resolution: {integrity: sha512-u0EutjK5y6NHJkl5jNJCs8zbup1z6A/UEWgajrYzqcEU3UX05HjqybhMQOLhSM0eKGISyM6WfSMMuklYSmH2wA==} + '@oxc-project/runtime@0.138.0': + resolution: {integrity: sha512-yHhoXsN8tYxgdJCdD91PbySNjEEaBX/tH2OQRDXJpsQv5b184oC4/qVbU7qlblvfil/JP15Lh2HW7+HN5DS90Q==} engines: {node: ^20.19.0 || >=22.12.0} - '@oxc-project/types@0.132.0': - resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} - '@oxc-project/types@0.133.0': resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} - '@oxc-project/types@0.136.0': - resolution: {integrity: sha512-39Al/B3v9esnHCX7S8l9Se2+s2tb9b2jcMd+bZ2L659VG73kNyGPpPrL5Zi/p0ty7p4pTTU2/Dd+g27hv94XCg==} + '@oxc-project/types@0.138.0': + resolution: {integrity: sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==} - '@oxfmt/binding-android-arm-eabi@0.41.0': - resolution: {integrity: sha512-REfrqeMKGkfMP+m/ScX4f5jJBSmVNYcpoDF8vP8f8eYPDuPGZmzp56NIUsYmx3h7f6NzC6cE3gqh8GDWrJHCKw==} + '@oxc-project/types@0.140.0': + resolution: {integrity: sha512-h5LUOzGArYemnW1NMz/DuuQhBi96J6JL2Bk8zE4kvqxB5Sg3jxmCiH4uyOWHDkiKSt5vWlG4FIwCR/DbstcNRQ==} + + '@oxfmt/binding-android-arm-eabi@0.57.0': + resolution: {integrity: sha512-qVBsEO+KugOsCmUHcO8iqNnqc65p7PCKpCs8M66mPZ+Ri+CWbcpoQOEJBg2OTu03+0qu++NK1jj6IzvQVs0Sig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm-eabi@0.55.0': - resolution: {integrity: sha512-+rFDOqQe5LOWgxrAJaZgLRudr6GQm0wGI6gtu7vVkrdLGjNMUSGbAlaCr8j7F2H2Er97vYQCU8WDb30onqMM1g==} + '@oxfmt/binding-android-arm-eabi@0.59.0': + resolution: {integrity: sha512-bNTnfbuG7sAwb2PakMNaDukx5kXeW9duXOBeWtTOiLz3fXz3q2DlWguufPZ+c2IHEVrRXHD+M4aUgEWm841LDA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.41.0': - resolution: {integrity: sha512-s0b1dxNgb2KomspFV2LfogC2XtSJB42POXF4bMCLJyvQmAGos4ZtjGPfQreToQEaY0FQFjz3030ggI36rF1q5g==} + '@oxfmt/binding-android-arm64@0.57.0': + resolution: {integrity: sha512-mp6PibWbao3aizijcheOeHQaYEhcUAt8pwLniYbtLfHxL/psFF0BykAwCj+s3c6qIpa8yN8keZICWrqtZ70w8g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-android-arm64@0.55.0': - resolution: {integrity: sha512-ctulLq8s3x8Zmvw6+iccB09TIKERAklRSmbJ10gk8mlAn05qZxoyo52dj3Hi9IJcmDSwF54fQaTVh2CbL6PInw==} + '@oxfmt/binding-android-arm64@0.59.0': + resolution: {integrity: sha512-R/Sn7z52QtdAKNqQLLY0EK7hVMjXiz3XUlvoCFCm/60jgIzAnQtiqLKBCFaBkimCQL5rs2ezPMcicpjCsrl54Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.41.0': - resolution: {integrity: sha512-EGXGualADbv/ZmamE7/2DbsrYmjoPlAmHEpTL4vapLF4EfVD6fr8/uQDFnPJkUBjiSWFJZtFNsGeN1B6V3owmA==} + '@oxfmt/binding-darwin-arm64@0.57.0': + resolution: {integrity: sha512-T+0stuCBqmUVY+aMIvrgXhzGhHO3sD5tNiiEcYqgSdPsnukskQqn2u5qOVD0sv1l7RLdFS5Z/f5Wi9Ktyjr3Eg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-arm64@0.55.0': - resolution: {integrity: sha512-xDQczLH9pw/RBk1h/GH0qcGMm8hQtmtVHBNLSH3lk1gEIR09hZ4L+mJQl4VqiVAvPK9VG9PYrWWuSQLt7xTbiA==} + '@oxfmt/binding-darwin-arm64@0.59.0': + resolution: {integrity: sha512-vm/ynUqE4HjC0ZIEjmXv1UJu1/GngccQ+T+TJudTMxUxm6r+GQTg1TO3E5jJfI71pBaXxSzs1+vWHIwuilGHhw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.41.0': - resolution: {integrity: sha512-WxySJEvdQQYMmyvISH3qDpTvoS0ebnIP63IMxLLWowJyPp/AAH0hdWtlo+iGNK5y3eVfa5jZguwNaQkDKWpGSw==} + '@oxfmt/binding-darwin-x64@0.57.0': + resolution: {integrity: sha512-O+3JbqWs/mCI2oi4xfhRO2IVPFJNDDEBV8Odo+ZpmsUOeKJfjXoNH7nDmBEQcDgK7NfjDIyE7kRgYSZcTLDO0A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.55.0': - resolution: {integrity: sha512-JaNoFCkF2CJdGgpPSMbuO9HVyXyoNGIhMHPvp6NYAjeVKw9XEYc0HcUWJLPQa3Q69WV5wMa9m5jPMJPtbLtcRg==} + '@oxfmt/binding-darwin-x64@0.59.0': + resolution: {integrity: sha512-uTtYDpLN/obfKVWGpgEc8BqYlLZBQTPz2uYEvLRy3HPZxjZ34wiFzukUBU2bf64JuCYZI//GTV1EOMmWlPjf/w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.41.0': - resolution: {integrity: sha512-Y2kzMkv3U3oyuYaR4wTfGjOTYTXiFC/hXmG0yVASKkbh02BJkvD98Ij8bIevr45hNZ0DmZEgqiXF+9buD4yMYQ==} + '@oxfmt/binding-freebsd-x64@0.57.0': + resolution: {integrity: sha512-pxwhxVC+JkLX9twOQ/8C/vbuOQcMZyKIDmiRDZfO7yITuVcIdZCiLRqqf4QOxb2+8FWrRXzQpm+1DBKcMpHSSQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-freebsd-x64@0.55.0': - resolution: {integrity: sha512-DNbszhpg6S2MIzax5azdHFTTBIVkR5xr8yyRZuA4yoDAwOkzIp3tmldgKZM2+VlT+hJIG0xUksA+elISzMEAfA==} + '@oxfmt/binding-freebsd-x64@0.59.0': + resolution: {integrity: sha512-e2UnxL/ifStSPy8ffBCDbdy595SYsGy+U1pur4G65TuMmWxAMBzYGG7atZo/3mp515p8rZdsflxVD/E1FAdPLQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.41.0': - resolution: {integrity: sha512-ptazDjdUyhket01IjPTT6ULS1KFuBfTUU97osTP96X5y/0oso+AgAaJzuH81oP0+XXyrWIHbRzozSAuQm4p48g==} + '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': + resolution: {integrity: sha512-pxBU4zH2imB/MDBfth2rOMeVxXUMjRQLCazagwLARIFH3hVlxZJBlM4nSnHXaIHJK4/qezoFCIORN6AY8Mra4A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-gnueabihf@0.55.0': - resolution: {integrity: sha512-2snoaoRfFFyGnbOcKUK36rREBYxe/Xgz3uHbiA5zbCB/s6R4DQj4mHqYAaWWhgizCUSDxV8cE9zAZ0XleNpKGw==} + '@oxfmt/binding-linux-arm-gnueabihf@0.59.0': + resolution: {integrity: sha512-LtdeZ1l0urxte3VNi3g8cocZwv1xGM1NKHSgF/fJEEVhyQmlgGh7WFWKFd/pNuO7djfvPNtNO1+MS+FEWkgVSA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.41.0': - resolution: {integrity: sha512-UkoL2OKxFD+56bPEBcdGn+4juTW4HRv/T6w1dIDLnvKKWr6DbarB/mtHXlADKlFiJubJz8pRkttOR7qjYR6lTA==} + '@oxfmt/binding-linux-arm-musleabihf@0.57.0': + resolution: {integrity: sha512-JAprOzt8tycYou36ZgEw14DlRHTiN8qdtKANdV3VZIRIvTI/lh/cX13c9pJ/EnDk2GT3FASH7KvCgQ2AufAifQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.55.0': - resolution: {integrity: sha512-q1aktHF/WRpSK81BX1dE/9vWrS2jGw1Nax2kb4DBLGAewubCLcoNyp4Zl/NSMgbv3vUS46Z33wIQkBVYOP3PYg==} + '@oxfmt/binding-linux-arm-musleabihf@0.59.0': + resolution: {integrity: sha512-dBTciSsj9GTMl7p+h2gMSI0hoPn2ijfc/dUsbnWsP0RbwgPl2r0C/5zkMb3Pb+gGj17LH7f1o4qLo9aes/pAvA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.41.0': - resolution: {integrity: sha512-gofu0PuumSOHYczD8p62CPY4UF6ee+rSLZJdUXkpwxg6pILiwSDBIouPskjF/5nF3A7QZTz2O9KFNkNxxFN9tA==} + '@oxfmt/binding-linux-arm64-gnu@0.57.0': + resolution: {integrity: sha512-ajtjaxSaj9xl4BW7REt+Cef/ttzbAq00Bq4z7JUDZEfgFXdwSjH8K9bF+IcIJzZB9lKqMfQ4eHuSFOvvlvtqOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-gnu@0.55.0': - resolution: {integrity: sha512-VD0y36aENezl/3tsclA/4G53Cc7iV+7Uoh7gz4yvcOTaEYBtJpQsE6PKDGTtUtOvGS4kv51ybfXY/nWZejO5IA==} + '@oxfmt/binding-linux-arm64-gnu@0.59.0': + resolution: {integrity: sha512-tXVdJ/JINsNWdponPHN0OuKHtC+HdpyoS9sd6IDPNiiEYsRki8b7tefRZ1iMnRkdbyT4SEbguWsr6o+5awvbPQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.41.0': - resolution: {integrity: sha512-VfVZxL0+6RU86T8F8vKiDBa+iHsr8PAjQmKGBzSCAX70b6x+UOMFl+2dNihmKmUwqkCazCPfYjt6SuAPOeQJ3g==} + '@oxfmt/binding-linux-arm64-musl@0.57.0': + resolution: {integrity: sha512-p4Y/+RYk9Bk5WO+zHSUXAClRmZ2fbJCejMuCAsU2HhyME4jqf6Ftt/mJYEwIah1wGCBDYOB7wEGV1x5bCEZ6hA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-arm64-musl@0.55.0': - resolution: {integrity: sha512-r8xlKJFcsRmn0H5jZrdORae6RX9jDBrZVvOoxF+bCQtampQJClv80aZEHsv+NsLsp2KCE5ql79O7DpPVzYWpXA==} + '@oxfmt/binding-linux-arm64-musl@0.59.0': + resolution: {integrity: sha512-RRTq38i2zT5fnw6XGHjvT6w2mh6x/G3m6AZcAZ56OTDTT/lsOeYnG3SVjwmH40z5kPqF+lf+o35e6m6PpKy9Dw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.41.0': - resolution: {integrity: sha512-bwzokz2eGvdfJbc0i+zXMJ4BBjQPqg13jyWpEEZDOrBCQ91r8KeY2Mi2kUeuMTZNFXju+jcAbAbpyJxRGla0eg==} + '@oxfmt/binding-linux-ppc64-gnu@0.57.0': + resolution: {integrity: sha512-By6tRALAZsno0F4zedmtG+wdMvJiJmJoXM4d3+A9zHE4HRXLqXITwRH8mgrlcXc5yJM2g2W3riRPwTYdgemZLQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-ppc64-gnu@0.55.0': - resolution: {integrity: sha512-GRKv/HXHcwIVld/WU61rF0g0R16hl5EJ+ScKdpjevT57lnLnagj/U2YUbXf2mT+2Pg1uCzWC+mvGicPV3CDdLQ==} + '@oxfmt/binding-linux-ppc64-gnu@0.59.0': + resolution: {integrity: sha512-lD3k7glAJSaXW0D6xzu8VOZbYbosvy+0ktOVkfLEoQF5HJlMSxTQ2KNW0JO+08ccP/1ElOKktVEMI0fqRbVB4w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.41.0': - resolution: {integrity: sha512-POLM//PCH9uqDeNDwWL3b3DkMmI3oI2cU6hwc2lnztD1o7dzrQs3R9nq555BZ6wI7t2lyhT9CS+CRaz5X0XqLA==} + '@oxfmt/binding-linux-riscv64-gnu@0.57.0': + resolution: {integrity: sha512-skYeG+RgvyzspqVEBsEprL90OYYZfoVNqB3HcCNR6QDJyXKOzfDRT3zncnHmUaFluIlBHuY23mU1b5WGgR98hA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.55.0': - resolution: {integrity: sha512-rdv57enTiPtpSYRMKfAiEbQb0Puw5t9N7isVinDoo5qeLDScro2gznmZqSgSWbVZRzLisTeCTW8Qwgw0bOHv3A==} + '@oxfmt/binding-linux-riscv64-gnu@0.59.0': + resolution: {integrity: sha512-WH5ZP1RbuHKBO/yfPRQKpNO/ijHcEDNbnmC4VPf/Bcd3+mbMAZpRiJWRa1PL5bREdIZZHo343mk3sqlc9x7Usw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.41.0': - resolution: {integrity: sha512-NNK7PzhFqLUwx/G12Xtm6scGv7UITvyGdAR5Y+TlqsG+essnuRWR4jRNODWRjzLZod0T3SayRbnkSIWMBov33w==} + '@oxfmt/binding-linux-riscv64-musl@0.57.0': + resolution: {integrity: sha512-FFgACrZOXAXUh5KQh2mt1CDOVOZmn+QzHP71wM9QobNwyQvoFfyAeefVUltW83g3sm7LTiH3yfFqLLVUpA5ZFQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-riscv64-musl@0.55.0': - resolution: {integrity: sha512-7v1nNrlD43VY6+sYQ6efYyb3lE6QY182304PD/768ZxTjOmFd/3dQa3u/nGBUAXYdGSWOQc5N3PnS0QzUXyEIA==} + '@oxfmt/binding-linux-riscv64-musl@0.59.0': + resolution: {integrity: sha512-743wOiaI9RZY4QVGkWkfGRavD5ZJUJ6gscFjVrVu1dP8AZh9jM+a6v3NhlR+OIzHdS6DhLM96w+gcVskskz7rw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.41.0': - resolution: {integrity: sha512-qVf/zDC5cN9eKe4qI/O/m445er1IRl6swsSl7jHkqmOSVfknwCe5JXitYjZca+V/cNJSU/xPlC5EFMabMMFDpw==} + '@oxfmt/binding-linux-s390x-gnu@0.57.0': + resolution: {integrity: sha512-Nm/BAOfQeFiiKd502mZn/GAVKJwtd0RdCg17G3Wz/WSOIQmDi3+7/SZH4BHn1Ye5KvTVH3ua8WvfwLLycNIuvA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-s390x-gnu@0.55.0': - resolution: {integrity: sha512-f4lJLUSPOgScjFl9LiflKCTocyNRwE25JmTMbN4XQdDjoZzEHjqf3wA3VESF1/csg7i8m7+EQLbrZyYDqe10UQ==} + '@oxfmt/binding-linux-s390x-gnu@0.59.0': + resolution: {integrity: sha512-xjRXQsRnrRZCcCkIEnbd2lmsQNobtwwkJxdy2bWXhZ1lIN0ouZwsBXRsoovW3yATuziAYwr9HMiQuR/Cc75NIw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.41.0': - resolution: {integrity: sha512-ojxYWu7vUb6ysYqVCPHuAPVZHAI40gfZ0PDtZAMwVmh2f0V8ExpPIKoAKr7/8sNbAXJBBpZhs2coypIo2jJX4w==} + '@oxfmt/binding-linux-x64-gnu@0.57.0': + resolution: {integrity: sha512-BiSy5Ku3mQqyxS6YIqAJgd403wEUWvI7kerfzPxc2l/txZVmZM0pSj7oDM+4bGBExowxOi7o73jEam1W0EDTZg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.55.0': - resolution: {integrity: sha512-MihqiPziJNoWy4MqNSV+jVA1g+07iQDjZiR0vaCaDoPgFEiJpCMsxamktzLV07cEeQsSJ04vQaU4CzCQwIvtDA==} + '@oxfmt/binding-linux-x64-gnu@0.59.0': + resolution: {integrity: sha512-4hNjqq/Rbr9B+StY9zMMAfm72+mtM4v80xYL5Qkb59Qd72g2vJMI0iFlPj3kf6miMsie/yJ7rt4urJT292HBgA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.41.0': - resolution: {integrity: sha512-O2exZLBxoCMIv2vlvcbkdedazJPTdG0VSup+0QUCfYQtx751zCZNboX2ZUOiQ/gDTdhtXvSiot0h6GEGkOyalA==} + '@oxfmt/binding-linux-x64-musl@0.57.0': + resolution: {integrity: sha512-BCRkJiotz5s9afLYD2LuMvzAoDYx9H17E/YbDyu4xK7l4zHDPeny9ErSXL//i/nJyaOwRk08x4b8cgJC00+JDg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-x64-musl@0.55.0': - resolution: {integrity: sha512-Yqghym7KYAVjP9MmSrNZiDeerMuoejNjo0r3ox5H3GDKk8eAfl8VyJm9i+pWCLDCTnAbcTUMMN2ZKjUYXH1v3g==} + '@oxfmt/binding-linux-x64-musl@0.59.0': + resolution: {integrity: sha512-NH579iN8EVQYsWowUB8B5vFchcylJtwPVJ7NmUAqEQHNLfhPbDT3K56KrECNAkUN4QpF4qiMgN2vsfZwVvjm7g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.41.0': - resolution: {integrity: sha512-N+31/VoL+z+NNBt8viy3I4NaIdPbiYeOnB884LKqvXldaE2dRztdPv3q5ipfZYv0RwFp7JfqS4I27K/DSHCakg==} + '@oxfmt/binding-openharmony-arm64@0.57.0': + resolution: {integrity: sha512-4Oaxe1qrGgXfpCJ1C/ERJ2iCtV2rN1R79ga9fsfyVHfSQRu/hVW780u2KDqZWFZ/iGTHODJji0JemxqFZ63eIQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-openharmony-arm64@0.55.0': - resolution: {integrity: sha512-s5SDvVVSbyQl1V5UU3Yl12M+XLUQ3rl5SglNqgAA2K4PXUtQhyNSS00wivONPEnNo5W01rCou8WkDNyvI/RGHg==} + '@oxfmt/binding-openharmony-arm64@0.59.0': + resolution: {integrity: sha512-mzZy3Z5Aj1D75Aq9FVlmoRQH5ei8Ga4o/NZmlXkKyeZ5EmPrUXRR7c6BMBteV1ZuZ/356UYDuLRLjAMxTDTiBA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.41.0': - resolution: {integrity: sha512-Z7NAtu/RN8kjCQ1y5oDD0nTAeRswh3GJ93qwcW51srmidP7XPBmZbLlwERu1W5veCevQJtPS9xmkpcDTYsGIwQ==} + '@oxfmt/binding-win32-arm64-msvc@0.57.0': + resolution: {integrity: sha512-MYLAsDnhdNsSGheLYhWgbk0vfIrlS84iQYun/y21fX6u0jj8iBtYtbpZMdiqYeuf8U12eVPUjVY2xE2NrCfJ0g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-arm64-msvc@0.55.0': - resolution: {integrity: sha512-7p9FB5R32tw2KyyNX3wpQrR2WHwEHvMEiBlGXxeTCaRMCVNx3UtFMAUbaQ/pRNWIrEUZmYhJ6tcUH52uPTRYjQ==} + '@oxfmt/binding-win32-arm64-msvc@0.59.0': + resolution: {integrity: sha512-0CpDJ1gE3jN1Gk6xms1Ie6LPfPcOtY4FAtoOmVLHQoAf8DvO2wd0DW2dIX2f7YTp5dxrr0ND8JeUEjm3DP3k5g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.41.0': - resolution: {integrity: sha512-uNxxP3l4bJ6VyzIeRqCmBU2Q0SkCFgIhvx9/9dJ9V8t/v+jP1IBsuaLwCXGR8JPHtkj4tFp+RHtUmU2ZYAUpMA==} + '@oxfmt/binding-win32-ia32-msvc@0.57.0': + resolution: {integrity: sha512-PBwdzZALJY/jcCx2E6is0yu+cuVXeySTDmwuseD+9j0mHqlRNxwlKgsyRTBed/woPeqfVfuXfWjoq4Cx2Zt3Eg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.55.0': - resolution: {integrity: sha512-ZYqj3fDnOT1IaVGMP5kpmkQl4F3tQIm2ZyAxvqkJYmI0xgWWak4ss4XYwv3VDfM+TWXeC9K4uQ/wW5jm/5XABA==} + '@oxfmt/binding-win32-ia32-msvc@0.59.0': + resolution: {integrity: sha512-zwdKBu3pt87uW0bRcywZb0oGMS7C6n87qogwRYFUgmk44T90ZzYlPjtlFYXs/DnBFrgNCvlHwCuWKfVWLeE7kw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.41.0': - resolution: {integrity: sha512-49ZSpbZ1noozyPapE8SUOSm3IN0Ze4b5nkO+4+7fq6oEYQQJFhE0saj5k/Gg4oewVPdjn0L3ZFeWk2Vehjcw7A==} + '@oxfmt/binding-win32-x64-msvc@0.57.0': + resolution: {integrity: sha512-bQJdH9i4RRfw55jm7+8/xS7GzHLLTbHx4huhrrDxQJaJtbSDbsyOnODvP1ftT7EG0KFKAYO2S+q6AcioXODx8w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.55.0': - resolution: {integrity: sha512-eEYT5tivGnGbPHuOHuQpi6CGLObhh0re/5jcNQHihD2GRYkTM85dyi5a19zjP8Q00t1uqAx+/QGLUGdHeqzWyg==} + '@oxfmt/binding-win32-x64-msvc@0.59.0': + resolution: {integrity: sha512-dUUbZkKgWrmAeI/puzv4bxN8lzcYaFnQVwFTFtwO2Gp8M7lZGSE2qJjC58g518+1bltJ8mizjYwD0BGHym0l/w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint-tsgolint/darwin-arm64@0.23.0': - resolution: {integrity: sha512-gOs9PVr2wEg4ox9z0aJo+RKhhImW86YL5N6yav8BK/rgPsIrwN/igSZ+pbRr723NFvUNKde9fgMhRA6JrXAOZw==} + '@oxlint-tsgolint/darwin-arm64@0.24.0': + resolution: {integrity: sha512-C2uMmwK5Bc4ri4ysZ6sA8Rcu+A5zBQTp6ml2u0CLLbRZp4kMFPV3yWk8B5DK9Aw7y9bbjogIm75tUwGLFzlsYQ==} cpu: [arm64] os: [darwin] - '@oxlint-tsgolint/darwin-x64@0.23.0': - resolution: {integrity: sha512-kjJ8B+7n4tB9VJdxS5A9GdJt6/bYpzbu4lXp2uO1S3sRmCB5gDEABlGoiePNApRWaW+xqL4b4xgiE727jSLhuA==} + '@oxlint-tsgolint/darwin-x64@0.24.0': + resolution: {integrity: sha512-Wgvt/1lRbDxmoNqWQKKcL+UIiqLmdJ+EWLpQa1qzoNVAfNB0PJpa82/8dH1twT/3rSs4zrP5TXPWl4juB71WuQ==} cpu: [x64] os: [darwin] - '@oxlint-tsgolint/linux-arm64@0.23.0': - resolution: {integrity: sha512-6dCZuKNu135seMXilkRk9SpCx6i1XgmiipYGalLij5WVRX6ZYS8c4xI7preN/zv9fCXhsQclTIMDu2Y/cytTjw==} + '@oxlint-tsgolint/linux-arm64@0.24.0': + resolution: {integrity: sha512-PB1rxII7KV83+ASY4sSkXtqvpij6ME66+QCRL49uksi/ofs2Rf/UVboYr095n0Rkbl2wgvlsHGl6DHC361jQUQ==} cpu: [arm64] os: [linux] - '@oxlint-tsgolint/linux-x64@0.23.0': - resolution: {integrity: sha512-3bdilnyA7kmSTjK27rvjIjSxL5SIg3wt7vwNiRkouWB83ytssyKnuGvxSYJxgMEmFpSutzaBzcCUM2jDtPGcgA==} + '@oxlint-tsgolint/linux-x64@0.24.0': + resolution: {integrity: sha512-xcz3CxKmjTQLREtE/UShh+ruWmm9nAb7UM9zKcD65BStiuYgOakAKkPHl4YS5DztpVcDrE0+HqbOolTlRKYWmw==} cpu: [x64] os: [linux] - '@oxlint-tsgolint/win32-arm64@0.23.0': - resolution: {integrity: sha512-j+OEp44SVYiQ+ZD+uttsX7u6L9SvmbbQ77SO1pSFCcJlsVMeCk8qZsjhKfGKuT/jIA+ipOJMVs/+pqUfObBWNw==} + '@oxlint-tsgolint/win32-arm64@0.24.0': + resolution: {integrity: sha512-A2i6ZGBec3i20S7RaxkgHc6r3HYtD5Mn7j/mb22NkTz14u0JuudvTu6JggAnbGMcv8+dBKQI//EasxSPJLD8pw==} cpu: [arm64] os: [win32] - '@oxlint-tsgolint/win32-x64@0.23.0': - resolution: {integrity: sha512-5MyjFuqf+g8OUPJBSGWHJtmoWnzFJYyOg4To9WMQshZYEWig/vtu7JtJ03VWnzHv9LJkAUeApY0gVCOywFR/iQ==} + '@oxlint-tsgolint/win32-x64@0.24.0': + resolution: {integrity: sha512-0ZbGd9qRB6zs82moekaKdEvncRANq49EAwfNX62JpTS46feXUhKAuoyVDvZMj6Rywejylrmmu79Wo6faYCo4Ew==} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.66.0': - resolution: {integrity: sha512-f7kq8N51T4phpzqfBpA2qaVTI/KrkCmNwaj3t/97I/WLTDI+UhlP5GL9eER+zVxBhtlx5rKXWByJU1/zDAvyaw==} + '@oxlint/binding-android-arm-eabi@1.72.0': + resolution: {integrity: sha512-zhCmvn+1Mj3UchAc/90i99S0t7jJUsHmFVSPg4UWrjO8b8eaSGwscgO6QAUtvHBstkjQwBttQNswEnAF1mIQdA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm-eabi@1.70.0': - resolution: {integrity: sha512-zFh0P4cswmRvw6nkyb89dr18rRanuaCPAsEXsFDoQY8WdaquI8Pt4NWFjaMJg6L23cy5NeN8J9cBnREbWzZhaw==} + '@oxlint/binding-android-arm-eabi@1.74.0': + resolution: {integrity: sha512-+gHd12muVI9ZLBaWLPkHt3Fj7jihFjgQ1MGtBaRL8vWrWrI0P7dLUty/cHrHS0oqPYIRgQUJsPu2CExQuMcwNw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.66.0': - resolution: {integrity: sha512-xu6QO71tdDS9mjmLZ3AqhtaVHBvdmsOKkYnReNNDgh+XiwnsipeQOIxbiYOOO0iAXycJ+GK0wdMSZP/2j/AmSg==} + '@oxlint/binding-android-arm64@1.72.0': + resolution: {integrity: sha512-mtH+aY/ozv1eZoCUC2owjFAtyNBKHpJHygKeEu9zXXnQGW1Q2/qOpvx+I+Lf23+TvTz66F4iiXUbl2cGvoLPCQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-android-arm64@1.70.0': - resolution: {integrity: sha512-qI8o4HZjeGiBrWv+pJv4lH0Yi2Gl/JSp/EumBUApezJprIKa5PS4nU0lQsQngtky8k+SplQIOjv6hwu0SSxeyg==} + '@oxlint/binding-android-arm64@1.74.0': + resolution: {integrity: sha512-xjKdoMB+H+RCOByv/7l7nfIGW9mlOisqYdcyC75UqYuQecLpReAeEYUf2CNeDEI3KtmUgxpRw/+c63y4AeF/Bw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.66.0': - resolution: {integrity: sha512-HZ24VimSOC7mxuEA99e0H2FS0C1yO3+iW13jPRAk+e2njsUs3QeAXsafCDyaIrV/MirdOVez+etQNQsJE43zNQ==} + '@oxlint/binding-darwin-arm64@1.72.0': + resolution: {integrity: sha512-EvnajNPDtfknB3ZieeOOyDTwJn9QXDiwfnF4ZDQqART6RG6hjY4WigQcZdGoK2dkB3e1vrmEzN9aYbQCUkh/gQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-arm64@1.70.0': - resolution: {integrity: sha512-8KjgVVHI5F9nVwHCRwwA78Ty7zNKP4Wd9OeN5PSv3iu/F/u1RVXoOCgLhWqust6HmwQG6xc8c+RCyaWENy24+w==} + '@oxlint/binding-darwin-arm64@1.74.0': + resolution: {integrity: sha512-iUK7wvc6sejMKsC+Pt67mntoF5weFcyEunhZfLJceU6gL419mexz5wBkSx/EnkFBExMLNtOi9fnDSc5xfK0IzQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.66.0': - resolution: {integrity: sha512-awhj8ZvJrrRSnXj7V++rpZvTmnl99L6mi0B7gg7Cp7BN6cKpzuI481bHNLvXGA9GB1/oEgA3ponuyoAc6Md12A==} + '@oxlint/binding-darwin-x64@1.72.0': + resolution: {integrity: sha512-ZkCdEa/G80A7vEHfeCDz/+L3m33DE73v32mDKhgOIgz8Uwf0DFcK7+uu6qC+7LEhmz5fpOe1osWKyjSNMydFIQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-darwin-x64@1.70.0': - resolution: {integrity: sha512-WVydssv5PSUBXFJTdNBWlmGkbNmvPGaFt/2SUT/EZRB6bq6bEOHmMlbnupZD5jmlEvi9+mZJHi8TCw15lyfSfQ==} + '@oxlint/binding-darwin-x64@1.74.0': + resolution: {integrity: sha512-ggKc/tn5SJ1u2yG2izC6VKODfYKV8MQ2AicJlNzOjuyrC29udvOef6/JzK2r32xqCnBDLFouR1VCkjzEI0/N9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.66.0': - resolution: {integrity: sha512-KQF0oVV21/FjIqkRuL8Q1vh8ECsE5+ocdH5tcqTQ4ZnYuDVoYibQUNfqBjQaUsP6UIIda5Y75Wpm5p4RgQWiWw==} + '@oxlint/binding-freebsd-x64@1.72.0': + resolution: {integrity: sha512-NroXv2vh+sxVY1uya/rM5pjhx1hm8BzlYpx9q67QP0Xhw5MH2bf5GJylpvLEC+781p1Xli/317EoV9AlGwViag==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-freebsd-x64@1.70.0': - resolution: {integrity: sha512-hJucmUf8OlinHNb1R7fI4Fw6WsAstOz7i8nmkWQfiHoZXtbufNm+MxiDTIMk1ggh2Ro4vLzgQ+bKvRY54MZoRA==} + '@oxlint/binding-freebsd-x64@1.74.0': + resolution: {integrity: sha512-u++dH/43jy9hTLbneaWlS0gla/Bp1JdwJ2zgevCl8nDFUh6qRCGMxcL0f0lb7By3A9p/LfFr+7cG4HU1hG856g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.66.0': - resolution: {integrity: sha512-9u1rgwZSEXWb30vbFZzQ78HVXBo0WCKNwJ3a2InRUTNMRng+PUDIoSFmA+m4HdUfBaIqftShq8J8qHc+eE/Vig==} + '@oxlint/binding-linux-arm-gnueabihf@1.72.0': + resolution: {integrity: sha512-0NDywYgfj279Ou/BcQuCYSj7NJwBfmWn5qc5uGO/Ny7fUWmXyIpvawqX/8acQlWG6IXelJsJhj+JAy6sjsKj0A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-gnueabihf@1.70.0': - resolution: {integrity: sha512-1BnS7wbCYDSXwWzJJ+mc3NURoha6m6m6RT5c6vgAY3oz7C3OVXP+S0awo2mRq97arrJkVvO3qRQfyAHL+76xtQ==} + '@oxlint/binding-linux-arm-gnueabihf@1.74.0': + resolution: {integrity: sha512-Sj1zmtFDVTPeIbIz4ZfcXAbFHqCmKCXdCUlAJzvTF7I20NTH1RDpoF2PhkqNODutJzVhJYmm3oz0GwgY+tvE2g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.66.0': - resolution: {integrity: sha512-Ynot2HR1bHxUaNWoC280MVTDfZuaWuP3XfSMRDhyuZrVjhzoaBCVFlw8h8qeZjWKVUBhPWFIxB7AQTlK8Z2WWg==} + '@oxlint/binding-linux-arm-musleabihf@1.72.0': + resolution: {integrity: sha512-4vpXB06h65Ezsy4hRyrGjGrfa1SkVPii09yaajiYhmVpgsFiLD+KNxIx/BNAY+XiO+i1yqp9HHdwqM8VTqa5XQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.70.0': - resolution: {integrity: sha512-yKy/UdbR55+M2yEcuiV5DCNC/gdQAjr/GioUy50QwBzSrKm8ueWADqyRLS9Xk+qjNeCYGg6A8FvUBds56ttfqg==} + '@oxlint/binding-linux-arm-musleabihf@1.74.0': + resolution: {integrity: sha512-//PKyQb/tQXcHArx2f7z+oVI/eMS2Jpv+edNuAtOrgIhWdGcpHxogveAxzmF2rpH1AIHp4Hq04RF/rgJdiICnQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.66.0': - resolution: {integrity: sha512-xCbgzciGgo+A4aQZEknsNrNiIwY7sU5SfRuMmRjPIvZAgdF34cIHiKvwOsS5XRLjlTVSFwitmq6YclTtHTfU+g==} + '@oxlint/binding-linux-arm64-gnu@1.72.0': + resolution: {integrity: sha512-immaN4g2ZGFiOkKrvRX9LvzZdd2GkQM5wR+UyzYyUuyhUTXGQ4HKUJH18xp4G8OfhCVaVAJfKZxwE1r8+4hhaQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-gnu@1.70.0': - resolution: {integrity: sha512-0A5XJ4alvmqFUFP/4oYSyaO+qLto/HrKEWTSaegiVl+HOufFngK2BjYw9x4RbwBt/du5QG6l5q1zeWiJYYG5yg==} + '@oxlint/binding-linux-arm64-gnu@1.74.0': + resolution: {integrity: sha512-/k1Me+aX2tjuH10K62mLS0y8cLkJBHX6Ce0xPK+eWeel4bSdEGZ8dv4+hYMzg0GrSmjwy4yAYsDPeEeKBft/2w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.66.0': - resolution: {integrity: sha512-hmo+ZB/lHkR1HdDmnziNpzSLmulnUSu10VEqX2Yex7OwvoBAbjJQLvy4gIBRV3AAwWnCvAxKp5Nv1GE6LU1QMg==} + '@oxlint/binding-linux-arm64-musl@1.72.0': + resolution: {integrity: sha512-JGHS9Mnr7iWyyLDxgCv1MhzVpAckgptg00F2gnxt/GD7lQ2SW1BRcxHqhSTaSdDpjWRrBkBxMMh4+Hn3aVtExg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-arm64-musl@1.70.0': - resolution: {integrity: sha512-JiylyurlB0CLSedNtx1gzv3FvfWPF1h/2Y3BJszPLNt5XQFlBsH5ke0Jle3iJb3uqu5m2e7A/DwzpuCAHdiU+A==} + '@oxlint/binding-linux-arm64-musl@1.74.0': + resolution: {integrity: sha512-3tFSjBxc5D8/zvjEuLvOqcA8ZXKD0+6NuaVO/edeamNc49MoAsbfaC9s1UiwODwgF6slGaF8yJA2TPkukd77tg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.66.0': - resolution: {integrity: sha512-2Invd4Uyy81mVooQC5FBtfxSNrvcX1OxbMlVQ6M2erRrNI2awFYF26YNW2yFxdVFZ4ffNOWKghtMjhnUPsXsVA==} + '@oxlint/binding-linux-ppc64-gnu@1.72.0': + resolution: {integrity: sha512-AOYgBZqxNshrg83P9v0RYv+m8s10Cqkj4/PxXFDhcS3k7FqsIG5+CxErshZCIN7G8iy4Y+VGfAsuEdar8AcbBg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-ppc64-gnu@1.70.0': - resolution: {integrity: sha512-J8VPG7I3/HmgaU4u8pNU2kFx2+0U+vPLS1dXFxXOaR/2TQ0f8AC7DRz0SRGRI1bfphnX2hVYTTtLuhL4nYKL+Q==} + '@oxlint/binding-linux-ppc64-gnu@1.74.0': + resolution: {integrity: sha512-9QggtPkSPXOCTu8Szis7auOK/sC7KdQaN+/TujP7YVVhzCAOhgdRfgv8uEz0r2tk5xdgus5rLYUrCDoZNtiRUw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.66.0': - resolution: {integrity: sha512-s0iXPDQVdgayE3RGa/N2DZF7tjgg0TwEtD1sGoDxqPDGrIXgo45H0yHknT0f9A0yteASsweYZtDyTuVlM4aSag==} + '@oxlint/binding-linux-riscv64-gnu@1.72.0': + resolution: {integrity: sha512-QMybPS5ij3/vrKG67mqzHwW++91sYxK/PPUVi6SBtNCEzW4niS52fVBdXbQ6nou0wWbUPEpx8Sl/ZjtgE3clXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.70.0': - resolution: {integrity: sha512-N2+4lV2KLN+oXTIIIwmWDhwkrnvqf5oX7Hw0zPjk+RuIVgiBQSOlJWF7uQoFx2siEYX0ZQ5cfSbEAHm+J3t7Wg==} + '@oxlint/binding-linux-riscv64-gnu@1.74.0': + resolution: {integrity: sha512-VM5VPUJ4DJIWiK+AZn8FScUqMr6OFrCAYybMYjEEi7W13ParI64MByiXTkKMqZpBmvQ9zxl9Ebq2VUOiZRJYUg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.66.0': - resolution: {integrity: sha512-OekL4XFiu7RPK0JIZi8VeHgtIXPREf42t8Cy/rKEsC+P3gcqDgNAAGiyuUOpdbG4wwbfue1q4CHcCO7spSve6w==} + '@oxlint/binding-linux-riscv64-musl@1.72.0': + resolution: {integrity: sha512-gOc3W7JV0PXRpIL7stUlLe3Wa9Gp0Kdlup87IT3gHDvPKck2xNgMIl/Gs2lldYY2lyXZDC4rWi3hmoLUobkgbQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-riscv64-musl@1.70.0': - resolution: {integrity: sha512-1e2L7cFCvx9QDzq6NPP+0tABKb5z6nWHyddWTNKprEsjO9xNrAtPowuCGpjNXxkTdsMiZ4jc8YQ5SstZd4XK6g==} + '@oxlint/binding-linux-riscv64-musl@1.74.0': + resolution: {integrity: sha512-SaDY1gh9rOA592J54g+gu5hkOFFQBZsMmIYHs+NRHG+Uq0OxtuuCXMWQ3vu1830Eugv5uMXyjG+bv2Z9y4IXjw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.66.0': - resolution: {integrity: sha512-Ga1D0kj1SFslm34ThA/BdkUlyAYEnTsXyRC4pF0C5agZSwtGdHYWMTQWemUfBGp4RCG4QWXgdO+HmmmKqOtlBg==} + '@oxlint/binding-linux-s390x-gnu@1.72.0': + resolution: {integrity: sha512-rpGxph+FjjHcYI5q6uxB3Az+tnfmEnDbSA8+PK9ZE/VzyUAkvBOMeuY7ZQMhu5mpZH7YQDsTdW6Cx4kV/msc6w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-s390x-gnu@1.70.0': - resolution: {integrity: sha512-Kwu/l/8GcYibCWA9m9N5pRXMIKVSsL/YbgpLzYkqDhWTiqdRfnNJ/+nqIKRKQiFbHWsdlHEhzMwruJK+qcEruA==} + '@oxlint/binding-linux-s390x-gnu@1.74.0': + resolution: {integrity: sha512-ZATQeHZCyr6MbDveg0obD5sxLHFOghtOdC5jwVwYlvFWqtFOxctgFEG6Ef/64hYvZrWyhyCckB10AelqLopeDA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.66.0': - resolution: {integrity: sha512-p5jfP1wUZe/IC3qpQO84n9DRnf9g3lKRtLBlQq23ykyrDglHcVx7sWmVTlPuU6SBw8mNnPzyOn022G3XZHnlww==} + '@oxlint/binding-linux-x64-gnu@1.72.0': + resolution: {integrity: sha512-WND+uhf/Ko13SLqQMWQUgsZuLvYYEvL0ZKgg0tgGYfLqxG7l8Ju123fHDMJyYSDl5E3bUbpFUuii/OvMreFQzw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.70.0': - resolution: {integrity: sha512-tap04CsHYOl0nSAQJfPNIuBxqEPB2HnhQqwaOXLg1jnp2XfRo8Fa814dA4QC4zpvTWXCjAAaCY1W5LOORkEQuQ==} + '@oxlint/binding-linux-x64-gnu@1.74.0': + resolution: {integrity: sha512-+aIvJyrdeD7LwCQ2WYLMUWNmnbeDRSPb40aBYtPjD9+PTqUwgJnk+HK5yLfSMeqXrMrDhE9uTmtt2y50tvjhHw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.66.0': - resolution: {integrity: sha512-vUB/sYlYZorDL1ZD+o9mRv7zbsykrrFRtmgS6R8musZqLtrPRQn1gc1eGpuX+sfdccz42STl/AqldY6XRb2upQ==} + '@oxlint/binding-linux-x64-musl@1.72.0': + resolution: {integrity: sha512-SrpbrUL70nG9vh6zP4/oKHWgLuHquwsr7MW9XOn0olBVgh10Uqr8qscKhQoBGEn6olK/IUpn5GSKcdQ5AjUhGA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-linux-x64-musl@1.70.0': - resolution: {integrity: sha512-hzJa/WgvtJpbBD9rgfy0qe+MjbxOXNUT0bfR1S6EQQzfTtBFA9xg5q8KSwRrQ2QfSS+TaP4j+4mVPQrfNc6UNg==} + '@oxlint/binding-linux-x64-musl@1.74.0': + resolution: {integrity: sha512-XyktaR8lhK2qWiCK0Tk8oYD+/cgn+oHA6ddRnxSSXUKkkojkV78CmShZUxQF+yrBFs0SuW+JBOPG6hecyc/iZg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.66.0': - resolution: {integrity: sha512-yde+6p/F59xRkGR9H1HfngWRif1QRJjynZK349l+UI0H6w9hL3G8/AVaTHFyTtLVQ56qtNbX2/5Dc77n1ovnOg==} + '@oxlint/binding-openharmony-arm64@1.72.0': + resolution: {integrity: sha512-qkrsEn6NmgFKr7U/QnezQMb+q/vzAy0Dd9Y95gQGQTyjzDLN+HRZMuM5u70iyH4nBLCfKBzhjMsYCehKay2jyg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-openharmony-arm64@1.70.0': - resolution: {integrity: sha512-xbsaNSNzVSnaJACCUYr1HQMyY/Q/Q1LkePmHG3UvZPvGCYGNxrsZp9OmtA6ick8xH47ltRRbRrPCM1YXYcyC+A==} + '@oxlint/binding-openharmony-arm64@1.74.0': + resolution: {integrity: sha512-mzbjrPl4neaVUiJ1fUiEUxTGaSZBoiKtaoB6jmIpz9S+VOA2vDYmJpihQ82w6178V5jxziclTg8Cgj5yF6tTDg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.66.0': - resolution: {integrity: sha512-O9GLucgoTdmOrbBX+EjzNe7o/Ze5TFOvXcib6bzUOtBOmj6cV+zw18NgB+cGKAkDw1Pdqs8vGkfHbbsLuDtXWg==} + '@oxlint/binding-win32-arm64-msvc@1.72.0': + resolution: {integrity: sha512-LWR6ZlFZph+KPjXv8opgZsXRDCdrdQe8VL8Cg9zxCoBS73h6znzZpydVgmdnwj8mB9AuSM5jxEgDJDpQkjboeg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-arm64-msvc@1.70.0': - resolution: {integrity: sha512-icAEsUI7JbW1TMRdEXV83mVAInhRVQYuuAlPpxdGwJ95chNdnCzjloRW8GglT0WvzOEZSio6fnYSk2DJ2Hv7LQ==} + '@oxlint/binding-win32-arm64-msvc@1.74.0': + resolution: {integrity: sha512-vUAe9okpS2Oa5+lX67lqHMuNUvfkleRKwrUDJ/WJBsgmddvZ1mrsh2HVmuFDRzqFELhaJhFaCNOuR6a7L3rtIA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.66.0': - resolution: {integrity: sha512-m3Pjwc2MfTcom4E4gOv7DyuGyt7OfGNCbmqDHd+N7EzXmP+ppHuudm2NjcA3AjV5TSeGxaguVF4SbTKHe1USYA==} + '@oxlint/binding-win32-ia32-msvc@1.72.0': + resolution: {integrity: sha512-yt6HEh7IsHvtjRWtmeZRX134eaXKHq5Gnqlf1xBJdJl1JtdoRUEJw3nAxpZoUDS860cX/foKbztO441anVBtVQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.70.0': - resolution: {integrity: sha512-FHMSWbVsPVs/f+Jcl04ws4JJ2wUnauyTzlpxWRG/lSO/8GpX08Fo2gQZqdA6CrRFI+zvkxl+N/KwJGWfUwYVZA==} + '@oxlint/binding-win32-ia32-msvc@1.74.0': + resolution: {integrity: sha512-yyXXJyYYSXL4I8K8jAWjJs+J3fa9gH2JmEbo4f5adm+1tNC9itseicBNuwK7BDHvqQ5J534s+yDULu89vYL2ZQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.66.0': - resolution: {integrity: sha512-/DbBvw8UFBhja6PqudUjV4UtfsJr0Oa7jUjWVKB0g86lj/VwnPrkngn0sFql3c9RDA0O16dh7ozsXb6GjNAzBQ==} + '@oxlint/binding-win32-x64-msvc@1.72.0': + resolution: {integrity: sha512-b2eKFD2hX7tIwmo/cyH6TDq8vzWRZ2qNHrzoGntUTmq0h3zQh/uX3eTSHCwI8OB/ADQfJCRelLItK8BsxuucDA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.70.0': - resolution: {integrity: sha512-ptOlKwCz7n4AKs5VweMqG6DAg677FmKOK+vBkkL9DMNgFATIQ+upqUYBTOEwRQyRAx1ncGlPlXleV2hIcm3z4g==} + '@oxlint/binding-win32-x64-msvc@1.74.0': + resolution: {integrity: sha512-VTC9IYTIMrVUk/i6Ms1ohzzDKZFkWn0KU2OBbPBzgmVZ2V30165T/zK4LztTr0Xgp9fZ1qQZ1rsZAu/rEmySlA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1006,8 +1015,8 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' - '@tybys/wasm-util@0.10.2': - resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -1021,24 +1030,144 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@types/node@25.9.1': - resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} + '@types/node@26.1.1': + resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} + + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] - '@vitest/browser-preview@4.1.9': - resolution: {integrity: sha512-a4/OrkMDb/WUnE4OOB/4FJbK3rYVO7YykqtUgcTKG4p2a0R3XcjPVu7SLRHFBs2+NIYhv5yxp1Lz3dbdGBjIow==} + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@vitest/browser-preview@4.1.10': + resolution: {integrity: sha512-14MJrL59ZFkqXLjwfSk6RzTDy5Czf9UG4+8q8L6Gxjs2aPjEce/cVNYV14bXAc2BvMjUNu904+ZEZA1Xc1wtvQ==} peerDependencies: - vitest: 4.1.9 + vitest: 4.1.10 - '@vitest/browser@4.1.9': - resolution: {integrity: sha512-j1BKtWmPcqpMhmx/L9EPLgAJpCb0zKfwoWLmqBbxaogCXHjOwHFSEoHCBfnGtx93xKQwilZ26m+UOsHqHMkRNg==} + '@vitest/browser@4.1.10': + resolution: {integrity: sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng==} peerDependencies: - vitest: 4.1.9 + vitest: 4.1.10 - '@vitest/expect@4.1.9': - resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==} + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} - '@vitest/mocker@4.1.9': - resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==} + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1048,30 +1177,28 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.9': - resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==} + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} - '@vitest/runner@4.1.9': - resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==} + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} - '@vitest/snapshot@4.1.9': - resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==} + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} - '@vitest/spy@4.1.9': - resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==} + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} - '@vitest/utils@4.1.9': - resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==} + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} - '@voidzero-dev/vite-plus-core@0.2.1': - resolution: {integrity: sha512-iWdtOlLezgYcDqIzxZx1yOUhY93vUB+ob+mRYBNr7/3Hf80uRyTQbqVD1WtsYaANbzeUi81SQ1ZoUraXHO+u8A==} + '@voidzero-dev/vite-plus-core@0.2.4': + resolution: {integrity: sha512-AoAYGPwNO56o9TuCR+KaQGA5XpSnTpn2QYHK0DQ0f8j3wvaMmToeWOJF6STo+XMntMDfiaB7sOsSFNE+hPYyjg==} engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} peerDependencies: '@arethetypeswrong/core': ^0.18.1 - '@tsdown/css': 0.22.3 - '@tsdown/exe': 0.22.3 '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.18 + '@vitejs/devtools': ^0.3.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -1089,10 +1216,6 @@ packages: peerDependenciesMeta: '@arethetypeswrong/core': optional: true - '@tsdown/css': - optional: true - '@tsdown/exe': - optional: true '@types/node': optional: true '@vitejs/devtools': @@ -1126,55 +1249,55 @@ packages: yaml: optional: true - '@voidzero-dev/vite-plus-darwin-arm64@0.2.1': - resolution: {integrity: sha512-9AfN/5LKRks8gbTaHPiQHT0L4yboy2xB6x6vvCRWxQMWxPS6/ZJLf5kUIZeE7I1z33AEyLKKkDscsZZVMgMLgg==} - engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + '@voidzero-dev/vite-plus-darwin-arm64@0.2.4': + resolution: {integrity: sha512-UQwoLpnBW3qLqj5H3airPQeMCX3kfffVDM2EYGe889Fn5dOS9VhikuWloJlcjwtKwqLbFqkrsGjfb6XRvuLozg==} + engines: {node: '>=20.0.0'} cpu: [arm64] os: [darwin] - '@voidzero-dev/vite-plus-darwin-x64@0.2.1': - resolution: {integrity: sha512-Q1vyimRbf4M82qIQSWRyr7NJaH9ag5G7vVEfGVVJlQHNprI+Q8zj2Phcs/PGf6QcyjcL8UclLznQTHU9NgnKZw==} - engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + '@voidzero-dev/vite-plus-darwin-x64@0.2.4': + resolution: {integrity: sha512-f4XtiA4cBc/Z260QvvXIlBqTb/dZMAioohQDoR5jLG9o9vIOaWE2Ujm/zcWDyNpyZ88RLRrcO8ZwiMrEsdzbJw==} + engines: {node: '>=20.0.0'} cpu: [x64] os: [darwin] - '@voidzero-dev/vite-plus-linux-arm64-gnu@0.2.1': - resolution: {integrity: sha512-WHW3DziqedRfhJ2upq6kC4y/pmdQWYt322DVB7+4Xb4oOa/CT9GtnSrWIiXVJ4PSO42v54+YsSTKPH2HC5RbtA==} - engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.2.4': + resolution: {integrity: sha512-TUamG9wEehZI4SFG7M6nUKb6z/7x3nNOBbnPdWIpv4C8uWKHwNtsnaaVLeygHIUIJEhbByR3oEFDylYLLr4KRw==} + engines: {node: '>=20.0.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-linux-arm64-musl@0.2.1': - resolution: {integrity: sha512-vUY7hYycZW0qEevpl7ImzZJFnOEKRYCaCOX4TBW0vk6MJZ+zj/xW7e0LOggzJcz2wbYAgLDqp5h+b8wV9dguDA==} - engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + '@voidzero-dev/vite-plus-linux-arm64-musl@0.2.4': + resolution: {integrity: sha512-CS9uQ22QFr+lZZp95Huccg3cip3QYVU9GWrhvATqMBXWXb8IRHOulzuXrFxzvhMBITyPaRS9m/eIHmnM4L4h4Q==} + engines: {node: '>=20.0.0'} cpu: [arm64] os: [linux] libc: [musl] - '@voidzero-dev/vite-plus-linux-x64-gnu@0.2.1': - resolution: {integrity: sha512-tFxpToEaykBGxMQHp8M/qmr1yruRRED+c9gA1h9kmplqot04OxuqzRCWu/IiIvMJ0v3JFdOP3gqkyjXLLJhxIA==} - engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + '@voidzero-dev/vite-plus-linux-x64-gnu@0.2.4': + resolution: {integrity: sha512-X5XfvftFERjer78SG+QKaJCa9LgIKygx4w13sD1/RS/kYOtaf1+yifrJpOFel+t9TRe/YqGTZa5C8uFrDz3OHw==} + engines: {node: '>=20.0.0'} cpu: [x64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-linux-x64-musl@0.2.1': - resolution: {integrity: sha512-2scSS7wEbLO2758fqr1/bAULg7nLCFa5V8LO2b5w3g1CrTYdMTDt2WX1ghPesIi+70pYGydRbXo6iaaN43zfMg==} - engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + '@voidzero-dev/vite-plus-linux-x64-musl@0.2.4': + resolution: {integrity: sha512-2iIWW6JR0UOK4QIFKgUQHjaF/7hZxELePny8R1OIn2jzShRfQhS1cmxksSg8ce/5nhYrfQ9dkg5ZmdLbxhpS/A==} + engines: {node: '>=20.0.0'} cpu: [x64] os: [linux] libc: [musl] - '@voidzero-dev/vite-plus-win32-arm64-msvc@0.2.1': - resolution: {integrity: sha512-3+5FJYhi9SqBszjngI2LBmvoiqEwxJWyQ5UsOUtNz6/d+yDrDw+tOgHLl4OKIh5aVNZeIGXzxvP6h24kcEqIyg==} - engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.2.4': + resolution: {integrity: sha512-3yY4FnpvKBxjmHtEcao6Wcs/VBstzC0GhXAPWetbiFEl/sgL66V4Uhws02esfOSWw8abqLrXyPE9vK+newtsuw==} + engines: {node: '>=20.0.0'} cpu: [arm64] os: [win32] - '@voidzero-dev/vite-plus-win32-x64-msvc@0.2.1': - resolution: {integrity: sha512-5sOEwEoU5PW7ObmJ5VCakU09Oh14rYCoLQJkFqvOph6PK30lN5iqWGk0KigEyfcd7Zv+fZg9EmcERDol/3Xl9w==} - engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} + '@voidzero-dev/vite-plus-win32-x64-msvc@0.2.4': + resolution: {integrity: sha512-ERnFrh1O0XZhmGSqND04jtHM7tyKaGAOeT1w/HpVFmL/cQK2vuLl2GKjEwOqkBLd2csNGCcnk3e7C2qDmrNR/Q==} + engines: {node: '>=20.0.0'} cpu: [x64] os: [win32] @@ -1337,17 +1460,25 @@ packages: resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} engines: {node: '>=12.20.0'} - oxc-parser@0.132.0: - resolution: {integrity: sha512-+0LAPHaqtfQlvWdpaAa09SmOaZZgP8C552xosEkGJ4+ruEwP1Vgx+sqBgcBCNfR6KDCmagGOZTde8wmAvcI/Hg==} + oxc-parser@0.140.0: + resolution: {integrity: sha512-h6QFWd6lBMfjESqgQ27GjzrSDb0qbznp7VDQqp2zvgsrWut4vcchyMIzOVXvGQ2GMZgKw9RWrFNWv9WqGL0p7Q==} engines: {node: ^20.19.0 || >=22.12.0} - oxfmt@0.41.0: - resolution: {integrity: sha512-sKLdJZdQ3bw6x9qKiT7+eID4MNEXlDHf5ZacfIircrq6Qwjk0L6t2/JQlZZrVHTXJawK3KaMuBoJnEJPcqCEdg==} + oxfmt@0.57.0: + resolution: {integrity: sha512-ZB7Bi+rGDSqmVIo9jwcLyFgjxXvQhDdU+jx+ZrVy6VRiVXK2+CHc4hO3J4dUQjHe7V0ymHB+MDuv5z+NhK07HA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true - oxfmt@0.55.0: - resolution: {integrity: sha512-jSj2wCTakwgPMxkfiVZX0jf+nX+Nz6xlyAZjqNE0qXTFdCBPYlP6JAN+ODjmealw7DXBjOzYbdsqwBMAZnPZ6A==} + oxfmt@0.59.0: + resolution: {integrity: sha512-Xqk6cPZS1yMvVa7OAuenaDZUsgMDutvvbZ9/L5gSvAfW64+WN4HVhgipLj5rVERbYQt8fLs9TopyZ1rU1XEG/w==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -1359,26 +1490,29 @@ packages: vite-plus: optional: true - oxlint-tsgolint@0.23.0: - resolution: {integrity: sha512-3mBv3CoPbh8dFbzfDGIWa2ytZjn2v+3EX4aKRXjIhsoGFzG8GCjfRirz3rwZf1wYbZzsNLTSgpw8VjQuWdp/jA==} + oxlint-tsgolint@0.24.0: + resolution: {integrity: sha512-giCk5sEvG02d5tzPmFMX3hem8ndzEEu1xvGYS5OwNfO2WGl6ZVxt5LjE0yiMDoz94INI7XkXwgFAQiydPvVHDw==} hasBin: true - oxlint@1.66.0: - resolution: {integrity: sha512-N4LLxYLd94KEBqXDMDM5f+2PUpItTjDLreXe2Gn5KhjhCK4Qp2YUXaBi8Yu325ryOgKwt22m45fpD7nPOn69Yw==} + oxlint@1.72.0: + resolution: {integrity: sha512-1rhdZIP/EvoI91ABIwNU5Q8+bWf8mjrS5UzIOZld4d4bXxJvtlUhlQvaoTogIGin/qdErMOrwaIJvCSIAKTLhA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: oxlint-tsgolint: '>=0.22.1' + vite-plus: '*' peerDependenciesMeta: oxlint-tsgolint: optional: true + vite-plus: + optional: true - oxlint@1.70.0: - resolution: {integrity: sha512-D6JgHtzkhRwvEC+A0Nw5AEc5bk8x5i1pHzvZIEf/a0C4hOzmAACNGtkDGPyFaxxX3ZVGxCPeig3P3rMM8XU3/g==} + oxlint@1.74.0: + resolution: {integrity: sha512-odGl2s2x5IOJoj3A0v1k0PGBXVFBZeZ2+AK/+K2MJur7Ghi3bkyX5NuLUWHKqa4js1wjep3hJeuTQJOlr+4+dA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.22.1' + oxlint-tsgolint: '>=0.24.0' vite-plus: '*' peerDependenciesMeta: oxlint-tsgolint: @@ -1459,8 +1593,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.22.3: - resolution: {integrity: sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==} + tsx@4.23.1: + resolution: {integrity: sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==} engines: {node: '>=18.0.0'} hasBin: true @@ -1469,16 +1603,21 @@ packages: engines: {node: '>=14.17'} hasBin: true - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true + + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - vite-plus@0.2.1: - resolution: {integrity: sha512-q5q/Y38UkWFsNg1JO+RyRdPUqoewaSqIlMyK2p83GKNUvf4D38Ntb3PToRTDZbTRh7mWt+B+d0DQBv4nCDpMcQ==} + vite-plus@0.2.4: + resolution: {integrity: sha512-gaBBjOXIq9lLRU44oAYdIr99p+JBLX1kxs+l/6LqGgSXwcVKAdDa1boSrOTELqYCkQQ0fpppXUGWi9o6JDT5zw==} engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} hasBin: true peerDependencies: - '@vitest/browser-playwright': 4.1.9 - '@vitest/browser-webdriverio': 4.1.9 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 peerDependenciesMeta: '@vitest/browser-playwright': optional: true @@ -1528,20 +1667,20 @@ packages: yaml: optional: true - vitest@4.1.9: - resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==} + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.9 - '@vitest/browser-preview': 4.1.9 - '@vitest/browser-webdriverio': 4.1.9 - '@vitest/coverage-istanbul': 4.1.9 - '@vitest/coverage-v8': 4.1.9 - '@vitest/ui': 4.1.9 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1606,16 +1745,32 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.11.2': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.28.0': optional: true @@ -1696,329 +1851,336 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 + '@tybys/wasm-util': 0.10.3 optional: true - '@oxc-parser/binding-android-arm-eabi@0.132.0': + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.3 optional: true - '@oxc-parser/binding-android-arm64@0.132.0': + '@oxc-parser/binding-android-arm-eabi@0.140.0': optional: true - '@oxc-parser/binding-darwin-arm64@0.132.0': + '@oxc-parser/binding-android-arm64@0.140.0': optional: true - '@oxc-parser/binding-darwin-x64@0.132.0': + '@oxc-parser/binding-darwin-arm64@0.140.0': optional: true - '@oxc-parser/binding-freebsd-x64@0.132.0': + '@oxc-parser/binding-darwin-x64@0.140.0': optional: true - '@oxc-parser/binding-linux-arm-gnueabihf@0.132.0': + '@oxc-parser/binding-freebsd-x64@0.140.0': optional: true - '@oxc-parser/binding-linux-arm-musleabihf@0.132.0': + '@oxc-parser/binding-linux-arm-gnueabihf@0.140.0': optional: true - '@oxc-parser/binding-linux-arm64-gnu@0.132.0': + '@oxc-parser/binding-linux-arm-musleabihf@0.140.0': optional: true - '@oxc-parser/binding-linux-arm64-musl@0.132.0': + '@oxc-parser/binding-linux-arm64-gnu@0.140.0': optional: true - '@oxc-parser/binding-linux-ppc64-gnu@0.132.0': + '@oxc-parser/binding-linux-arm64-musl@0.140.0': optional: true - '@oxc-parser/binding-linux-riscv64-gnu@0.132.0': + '@oxc-parser/binding-linux-ppc64-gnu@0.140.0': optional: true - '@oxc-parser/binding-linux-riscv64-musl@0.132.0': + '@oxc-parser/binding-linux-riscv64-gnu@0.140.0': optional: true - '@oxc-parser/binding-linux-s390x-gnu@0.132.0': + '@oxc-parser/binding-linux-riscv64-musl@0.140.0': optional: true - '@oxc-parser/binding-linux-x64-gnu@0.132.0': + '@oxc-parser/binding-linux-s390x-gnu@0.140.0': optional: true - '@oxc-parser/binding-linux-x64-musl@0.132.0': + '@oxc-parser/binding-linux-x64-gnu@0.140.0': optional: true - '@oxc-parser/binding-openharmony-arm64@0.132.0': + '@oxc-parser/binding-linux-x64-musl@0.140.0': optional: true - '@oxc-parser/binding-wasm32-wasi@0.132.0': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@oxc-parser/binding-openharmony-arm64@0.140.0': optional: true - '@oxc-parser/binding-win32-arm64-msvc@0.132.0': + '@oxc-parser/binding-wasm32-wasi@0.140.0': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) optional: true - '@oxc-parser/binding-win32-ia32-msvc@0.132.0': + '@oxc-parser/binding-win32-arm64-msvc@0.140.0': optional: true - '@oxc-parser/binding-win32-x64-msvc@0.132.0': + '@oxc-parser/binding-win32-ia32-msvc@0.140.0': optional: true - '@oxc-project/runtime@0.136.0': {} + '@oxc-parser/binding-win32-x64-msvc@0.140.0': + optional: true - '@oxc-project/types@0.132.0': {} + '@oxc-project/runtime@0.138.0': {} '@oxc-project/types@0.133.0': {} - '@oxc-project/types@0.136.0': {} + '@oxc-project/types@0.138.0': {} + + '@oxc-project/types@0.140.0': {} - '@oxfmt/binding-android-arm-eabi@0.41.0': + '@oxfmt/binding-android-arm-eabi@0.57.0': optional: true - '@oxfmt/binding-android-arm-eabi@0.55.0': + '@oxfmt/binding-android-arm-eabi@0.59.0': optional: true - '@oxfmt/binding-android-arm64@0.41.0': + '@oxfmt/binding-android-arm64@0.57.0': optional: true - '@oxfmt/binding-android-arm64@0.55.0': + '@oxfmt/binding-android-arm64@0.59.0': optional: true - '@oxfmt/binding-darwin-arm64@0.41.0': + '@oxfmt/binding-darwin-arm64@0.57.0': optional: true - '@oxfmt/binding-darwin-arm64@0.55.0': + '@oxfmt/binding-darwin-arm64@0.59.0': optional: true - '@oxfmt/binding-darwin-x64@0.41.0': + '@oxfmt/binding-darwin-x64@0.57.0': optional: true - '@oxfmt/binding-darwin-x64@0.55.0': + '@oxfmt/binding-darwin-x64@0.59.0': optional: true - '@oxfmt/binding-freebsd-x64@0.41.0': + '@oxfmt/binding-freebsd-x64@0.57.0': optional: true - '@oxfmt/binding-freebsd-x64@0.55.0': + '@oxfmt/binding-freebsd-x64@0.59.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.41.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.55.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.59.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.41.0': + '@oxfmt/binding-linux-arm-musleabihf@0.57.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.55.0': + '@oxfmt/binding-linux-arm-musleabihf@0.59.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.41.0': + '@oxfmt/binding-linux-arm64-gnu@0.57.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.55.0': + '@oxfmt/binding-linux-arm64-gnu@0.59.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.41.0': + '@oxfmt/binding-linux-arm64-musl@0.57.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.55.0': + '@oxfmt/binding-linux-arm64-musl@0.59.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.41.0': + '@oxfmt/binding-linux-ppc64-gnu@0.57.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.55.0': + '@oxfmt/binding-linux-ppc64-gnu@0.59.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.41.0': + '@oxfmt/binding-linux-riscv64-gnu@0.57.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.55.0': + '@oxfmt/binding-linux-riscv64-gnu@0.59.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.41.0': + '@oxfmt/binding-linux-riscv64-musl@0.57.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.55.0': + '@oxfmt/binding-linux-riscv64-musl@0.59.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.41.0': + '@oxfmt/binding-linux-s390x-gnu@0.57.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.55.0': + '@oxfmt/binding-linux-s390x-gnu@0.59.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.41.0': + '@oxfmt/binding-linux-x64-gnu@0.57.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.55.0': + '@oxfmt/binding-linux-x64-gnu@0.59.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.41.0': + '@oxfmt/binding-linux-x64-musl@0.57.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.55.0': + '@oxfmt/binding-linux-x64-musl@0.59.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.41.0': + '@oxfmt/binding-openharmony-arm64@0.57.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.55.0': + '@oxfmt/binding-openharmony-arm64@0.59.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.41.0': + '@oxfmt/binding-win32-arm64-msvc@0.57.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.55.0': + '@oxfmt/binding-win32-arm64-msvc@0.59.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.41.0': + '@oxfmt/binding-win32-ia32-msvc@0.57.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.55.0': + '@oxfmt/binding-win32-ia32-msvc@0.59.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.41.0': + '@oxfmt/binding-win32-x64-msvc@0.57.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.55.0': + '@oxfmt/binding-win32-x64-msvc@0.59.0': optional: true - '@oxlint-tsgolint/darwin-arm64@0.23.0': + '@oxlint-tsgolint/darwin-arm64@0.24.0': optional: true - '@oxlint-tsgolint/darwin-x64@0.23.0': + '@oxlint-tsgolint/darwin-x64@0.24.0': optional: true - '@oxlint-tsgolint/linux-arm64@0.23.0': + '@oxlint-tsgolint/linux-arm64@0.24.0': optional: true - '@oxlint-tsgolint/linux-x64@0.23.0': + '@oxlint-tsgolint/linux-x64@0.24.0': optional: true - '@oxlint-tsgolint/win32-arm64@0.23.0': + '@oxlint-tsgolint/win32-arm64@0.24.0': optional: true - '@oxlint-tsgolint/win32-x64@0.23.0': + '@oxlint-tsgolint/win32-x64@0.24.0': optional: true - '@oxlint/binding-android-arm-eabi@1.66.0': + '@oxlint/binding-android-arm-eabi@1.72.0': optional: true - '@oxlint/binding-android-arm-eabi@1.70.0': + '@oxlint/binding-android-arm-eabi@1.74.0': optional: true - '@oxlint/binding-android-arm64@1.66.0': + '@oxlint/binding-android-arm64@1.72.0': optional: true - '@oxlint/binding-android-arm64@1.70.0': + '@oxlint/binding-android-arm64@1.74.0': optional: true - '@oxlint/binding-darwin-arm64@1.66.0': + '@oxlint/binding-darwin-arm64@1.72.0': optional: true - '@oxlint/binding-darwin-arm64@1.70.0': + '@oxlint/binding-darwin-arm64@1.74.0': optional: true - '@oxlint/binding-darwin-x64@1.66.0': + '@oxlint/binding-darwin-x64@1.72.0': optional: true - '@oxlint/binding-darwin-x64@1.70.0': + '@oxlint/binding-darwin-x64@1.74.0': optional: true - '@oxlint/binding-freebsd-x64@1.66.0': + '@oxlint/binding-freebsd-x64@1.72.0': optional: true - '@oxlint/binding-freebsd-x64@1.70.0': + '@oxlint/binding-freebsd-x64@1.74.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.66.0': + '@oxlint/binding-linux-arm-gnueabihf@1.72.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.70.0': + '@oxlint/binding-linux-arm-gnueabihf@1.74.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.66.0': + '@oxlint/binding-linux-arm-musleabihf@1.72.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.70.0': + '@oxlint/binding-linux-arm-musleabihf@1.74.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.66.0': + '@oxlint/binding-linux-arm64-gnu@1.72.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.70.0': + '@oxlint/binding-linux-arm64-gnu@1.74.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.66.0': + '@oxlint/binding-linux-arm64-musl@1.72.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.70.0': + '@oxlint/binding-linux-arm64-musl@1.74.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.66.0': + '@oxlint/binding-linux-ppc64-gnu@1.72.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.70.0': + '@oxlint/binding-linux-ppc64-gnu@1.74.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.66.0': + '@oxlint/binding-linux-riscv64-gnu@1.72.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.70.0': + '@oxlint/binding-linux-riscv64-gnu@1.74.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.66.0': + '@oxlint/binding-linux-riscv64-musl@1.72.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.70.0': + '@oxlint/binding-linux-riscv64-musl@1.74.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.66.0': + '@oxlint/binding-linux-s390x-gnu@1.72.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.70.0': + '@oxlint/binding-linux-s390x-gnu@1.74.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.66.0': + '@oxlint/binding-linux-x64-gnu@1.72.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.70.0': + '@oxlint/binding-linux-x64-gnu@1.74.0': optional: true - '@oxlint/binding-linux-x64-musl@1.66.0': + '@oxlint/binding-linux-x64-musl@1.72.0': optional: true - '@oxlint/binding-linux-x64-musl@1.70.0': + '@oxlint/binding-linux-x64-musl@1.74.0': optional: true - '@oxlint/binding-openharmony-arm64@1.66.0': + '@oxlint/binding-openharmony-arm64@1.72.0': optional: true - '@oxlint/binding-openharmony-arm64@1.70.0': + '@oxlint/binding-openharmony-arm64@1.74.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.66.0': + '@oxlint/binding-win32-arm64-msvc@1.72.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.70.0': + '@oxlint/binding-win32-arm64-msvc@1.74.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.66.0': + '@oxlint/binding-win32-ia32-msvc@1.72.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.70.0': + '@oxlint/binding-win32-ia32-msvc@1.74.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.66.0': + '@oxlint/binding-win32-x64-msvc@1.72.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.70.0': + '@oxlint/binding-win32-x64-msvc@1.74.0': optional: true '@oxlint/plugins@1.68.0': {} @@ -2065,7 +2227,7 @@ snapshots: dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true '@rolldown/binding-win32-arm64-msvc@1.0.3': @@ -2093,7 +2255,7 @@ snapshots: dependencies: '@testing-library/dom': 10.4.1 - '@tybys/wasm-util@0.10.2': + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 optional: true @@ -2109,32 +2271,92 @@ snapshots: '@types/estree@1.0.9': {} - '@types/node@25.9.1': + '@types/node@26.1.1': dependencies: - undici-types: 7.24.6 + undici-types: 8.3.0 + + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true - '@vitest/browser-preview@4.1.9(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3))(vitest@4.1.9)': + '@vitest/browser-preview@4.1.10(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))(vitest@4.1.10)': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/browser': 4.1.9(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3))(vitest@4.1.9) - vitest: 4.1.9(@types/node@25.9.1)(@vitest/browser-preview@4.1.9)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)) + '@vitest/browser': 4.1.10(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))(vitest@4.1.10) + vitest: 4.1.10(@types/node@26.1.1)(@vitest/browser-preview@4.1.10)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.1.9(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3))(vitest@4.1.9)': + '@vitest/browser@4.1.10(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))(vitest@4.1.10)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.9(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)) - '@vitest/utils': 4.1.9 + '@vitest/mocker': 4.1.10(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) + '@vitest/utils': 4.1.10 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@25.9.1)(@vitest/browser-preview@4.1.9)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)) + vitest: 4.1.10(@types/node@26.1.1)(@vitest/browser-preview@4.1.10)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) ws: 8.21.0 transitivePeerDependencies: - bufferutil @@ -2142,82 +2364,96 @@ snapshots: - utf-8-validate - vite - '@vitest/expect@4.1.9': + '@vitest/expect@4.1.10': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.9 - '@vitest/utils': 4.1.9 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.9(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3))': + '@vitest/mocker@4.1.10(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))': dependencies: - '@vitest/spy': 4.1.9 + '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3) + vite: 8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1) - '@vitest/pretty-format@4.1.9': + '@vitest/pretty-format@4.1.10': dependencies: tinyrainbow: 3.1.0 - '@vitest/runner@4.1.9': + '@vitest/runner@4.1.10': dependencies: - '@vitest/utils': 4.1.9 + '@vitest/utils': 4.1.10 pathe: 2.0.3 - '@vitest/snapshot@4.1.9': + '@vitest/snapshot@4.1.10': dependencies: - '@vitest/pretty-format': 4.1.9 - '@vitest/utils': 4.1.9 + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.9': {} + '@vitest/spy@4.1.10': {} - '@vitest/utils@4.1.9': + '@vitest/utils@4.1.10': dependencies: - '@vitest/pretty-format': 4.1.9 + '@vitest/pretty-format': 4.1.10 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@voidzero-dev/vite-plus-core@0.2.1(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)(typescript@6.0.3)': + '@voidzero-dev/vite-plus-core@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@6.0.3)': dependencies: - '@oxc-project/runtime': 0.136.0 - '@oxc-project/types': 0.136.0 + '@oxc-project/runtime': 0.138.0 + '@oxc-project/types': 0.138.0 lightningcss: 1.32.0 postcss: 8.5.15 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.1.1 esbuild: 0.28.0 fsevents: 2.3.3 - tsx: 4.22.3 + tsx: 4.23.1 typescript: 6.0.3 - '@voidzero-dev/vite-plus-darwin-arm64@0.2.1': + '@voidzero-dev/vite-plus-core@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)': + dependencies: + '@oxc-project/runtime': 0.138.0 + '@oxc-project/types': 0.138.0 + lightningcss: 1.32.0 + postcss: 8.5.15 + optionalDependencies: + '@types/node': 26.1.1 + esbuild: 0.28.0 + fsevents: 2.3.3 + tsx: 4.23.1 + typescript: 7.0.2 optional: true - '@voidzero-dev/vite-plus-darwin-x64@0.2.1': + '@voidzero-dev/vite-plus-darwin-arm64@0.2.4': optional: true - '@voidzero-dev/vite-plus-linux-arm64-gnu@0.2.1': + '@voidzero-dev/vite-plus-darwin-x64@0.2.4': optional: true - '@voidzero-dev/vite-plus-linux-arm64-musl@0.2.1': + '@voidzero-dev/vite-plus-linux-arm64-gnu@0.2.4': optional: true - '@voidzero-dev/vite-plus-linux-x64-gnu@0.2.1': + '@voidzero-dev/vite-plus-linux-arm64-musl@0.2.4': optional: true - '@voidzero-dev/vite-plus-linux-x64-musl@0.2.1': + '@voidzero-dev/vite-plus-linux-x64-gnu@0.2.4': optional: true - '@voidzero-dev/vite-plus-win32-arm64-msvc@0.2.1': + '@voidzero-dev/vite-plus-linux-x64-musl@0.2.4': optional: true - '@voidzero-dev/vite-plus-win32-x64-msvc@0.2.1': + '@voidzero-dev/vite-plus-win32-arm64-msvc@0.2.4': + optional: true + + '@voidzero-dev/vite-plus-win32-x64-msvc@0.2.4': optional: true ansi-regex@5.0.1: {} @@ -2347,135 +2583,188 @@ snapshots: obug@2.1.3: {} - oxc-parser@0.132.0: + oxc-parser@0.140.0: + dependencies: + '@oxc-project/types': 0.140.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.140.0 + '@oxc-parser/binding-android-arm64': 0.140.0 + '@oxc-parser/binding-darwin-arm64': 0.140.0 + '@oxc-parser/binding-darwin-x64': 0.140.0 + '@oxc-parser/binding-freebsd-x64': 0.140.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.140.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.140.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.140.0 + '@oxc-parser/binding-linux-arm64-musl': 0.140.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.140.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.140.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.140.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.140.0 + '@oxc-parser/binding-linux-x64-gnu': 0.140.0 + '@oxc-parser/binding-linux-x64-musl': 0.140.0 + '@oxc-parser/binding-openharmony-arm64': 0.140.0 + '@oxc-parser/binding-wasm32-wasi': 0.140.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.140.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.140.0 + '@oxc-parser/binding-win32-x64-msvc': 0.140.0 + + oxfmt@0.57.0(vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@6.0.3)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))): dependencies: - '@oxc-project/types': 0.132.0 + tinypool: 2.1.0 optionalDependencies: - '@oxc-parser/binding-android-arm-eabi': 0.132.0 - '@oxc-parser/binding-android-arm64': 0.132.0 - '@oxc-parser/binding-darwin-arm64': 0.132.0 - '@oxc-parser/binding-darwin-x64': 0.132.0 - '@oxc-parser/binding-freebsd-x64': 0.132.0 - '@oxc-parser/binding-linux-arm-gnueabihf': 0.132.0 - '@oxc-parser/binding-linux-arm-musleabihf': 0.132.0 - '@oxc-parser/binding-linux-arm64-gnu': 0.132.0 - '@oxc-parser/binding-linux-arm64-musl': 0.132.0 - '@oxc-parser/binding-linux-ppc64-gnu': 0.132.0 - '@oxc-parser/binding-linux-riscv64-gnu': 0.132.0 - '@oxc-parser/binding-linux-riscv64-musl': 0.132.0 - '@oxc-parser/binding-linux-s390x-gnu': 0.132.0 - '@oxc-parser/binding-linux-x64-gnu': 0.132.0 - '@oxc-parser/binding-linux-x64-musl': 0.132.0 - '@oxc-parser/binding-openharmony-arm64': 0.132.0 - '@oxc-parser/binding-wasm32-wasi': 0.132.0 - '@oxc-parser/binding-win32-arm64-msvc': 0.132.0 - '@oxc-parser/binding-win32-ia32-msvc': 0.132.0 - '@oxc-parser/binding-win32-x64-msvc': 0.132.0 - - oxfmt@0.41.0: + '@oxfmt/binding-android-arm-eabi': 0.57.0 + '@oxfmt/binding-android-arm64': 0.57.0 + '@oxfmt/binding-darwin-arm64': 0.57.0 + '@oxfmt/binding-darwin-x64': 0.57.0 + '@oxfmt/binding-freebsd-x64': 0.57.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.57.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.57.0 + '@oxfmt/binding-linux-arm64-gnu': 0.57.0 + '@oxfmt/binding-linux-arm64-musl': 0.57.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.57.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.57.0 + '@oxfmt/binding-linux-riscv64-musl': 0.57.0 + '@oxfmt/binding-linux-s390x-gnu': 0.57.0 + '@oxfmt/binding-linux-x64-gnu': 0.57.0 + '@oxfmt/binding-linux-x64-musl': 0.57.0 + '@oxfmt/binding-openharmony-arm64': 0.57.0 + '@oxfmt/binding-win32-arm64-msvc': 0.57.0 + '@oxfmt/binding-win32-ia32-msvc': 0.57.0 + '@oxfmt/binding-win32-x64-msvc': 0.57.0 + vite-plus: 0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@6.0.3)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) + + oxfmt@0.57.0(vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))): dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.41.0 - '@oxfmt/binding-android-arm64': 0.41.0 - '@oxfmt/binding-darwin-arm64': 0.41.0 - '@oxfmt/binding-darwin-x64': 0.41.0 - '@oxfmt/binding-freebsd-x64': 0.41.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.41.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.41.0 - '@oxfmt/binding-linux-arm64-gnu': 0.41.0 - '@oxfmt/binding-linux-arm64-musl': 0.41.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.41.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.41.0 - '@oxfmt/binding-linux-riscv64-musl': 0.41.0 - '@oxfmt/binding-linux-s390x-gnu': 0.41.0 - '@oxfmt/binding-linux-x64-gnu': 0.41.0 - '@oxfmt/binding-linux-x64-musl': 0.41.0 - '@oxfmt/binding-openharmony-arm64': 0.41.0 - '@oxfmt/binding-win32-arm64-msvc': 0.41.0 - '@oxfmt/binding-win32-ia32-msvc': 0.41.0 - '@oxfmt/binding-win32-x64-msvc': 0.41.0 - - oxfmt@0.55.0(vite-plus@0.2.1(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3))): + '@oxfmt/binding-android-arm-eabi': 0.57.0 + '@oxfmt/binding-android-arm64': 0.57.0 + '@oxfmt/binding-darwin-arm64': 0.57.0 + '@oxfmt/binding-darwin-x64': 0.57.0 + '@oxfmt/binding-freebsd-x64': 0.57.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.57.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.57.0 + '@oxfmt/binding-linux-arm64-gnu': 0.57.0 + '@oxfmt/binding-linux-arm64-musl': 0.57.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.57.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.57.0 + '@oxfmt/binding-linux-riscv64-musl': 0.57.0 + '@oxfmt/binding-linux-s390x-gnu': 0.57.0 + '@oxfmt/binding-linux-x64-gnu': 0.57.0 + '@oxfmt/binding-linux-x64-musl': 0.57.0 + '@oxfmt/binding-openharmony-arm64': 0.57.0 + '@oxfmt/binding-win32-arm64-msvc': 0.57.0 + '@oxfmt/binding-win32-ia32-msvc': 0.57.0 + '@oxfmt/binding-win32-x64-msvc': 0.57.0 + vite-plus: 0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) + optional: true + + oxfmt@0.59.0(vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))): dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.55.0 - '@oxfmt/binding-android-arm64': 0.55.0 - '@oxfmt/binding-darwin-arm64': 0.55.0 - '@oxfmt/binding-darwin-x64': 0.55.0 - '@oxfmt/binding-freebsd-x64': 0.55.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.55.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.55.0 - '@oxfmt/binding-linux-arm64-gnu': 0.55.0 - '@oxfmt/binding-linux-arm64-musl': 0.55.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.55.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.55.0 - '@oxfmt/binding-linux-riscv64-musl': 0.55.0 - '@oxfmt/binding-linux-s390x-gnu': 0.55.0 - '@oxfmt/binding-linux-x64-gnu': 0.55.0 - '@oxfmt/binding-linux-x64-musl': 0.55.0 - '@oxfmt/binding-openharmony-arm64': 0.55.0 - '@oxfmt/binding-win32-arm64-msvc': 0.55.0 - '@oxfmt/binding-win32-ia32-msvc': 0.55.0 - '@oxfmt/binding-win32-x64-msvc': 0.55.0 - vite-plus: 0.2.1(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)) - - oxlint-tsgolint@0.23.0: + '@oxfmt/binding-android-arm-eabi': 0.59.0 + '@oxfmt/binding-android-arm64': 0.59.0 + '@oxfmt/binding-darwin-arm64': 0.59.0 + '@oxfmt/binding-darwin-x64': 0.59.0 + '@oxfmt/binding-freebsd-x64': 0.59.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.59.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.59.0 + '@oxfmt/binding-linux-arm64-gnu': 0.59.0 + '@oxfmt/binding-linux-arm64-musl': 0.59.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.59.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.59.0 + '@oxfmt/binding-linux-riscv64-musl': 0.59.0 + '@oxfmt/binding-linux-s390x-gnu': 0.59.0 + '@oxfmt/binding-linux-x64-gnu': 0.59.0 + '@oxfmt/binding-linux-x64-musl': 0.59.0 + '@oxfmt/binding-openharmony-arm64': 0.59.0 + '@oxfmt/binding-win32-arm64-msvc': 0.59.0 + '@oxfmt/binding-win32-ia32-msvc': 0.59.0 + '@oxfmt/binding-win32-x64-msvc': 0.59.0 + vite-plus: 0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) + + oxlint-tsgolint@0.24.0: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 0.24.0 + '@oxlint-tsgolint/darwin-x64': 0.24.0 + '@oxlint-tsgolint/linux-arm64': 0.24.0 + '@oxlint-tsgolint/linux-x64': 0.24.0 + '@oxlint-tsgolint/win32-arm64': 0.24.0 + '@oxlint-tsgolint/win32-x64': 0.24.0 + + oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@6.0.3)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))): optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 0.23.0 - '@oxlint-tsgolint/darwin-x64': 0.23.0 - '@oxlint-tsgolint/linux-arm64': 0.23.0 - '@oxlint-tsgolint/linux-x64': 0.23.0 - '@oxlint-tsgolint/win32-arm64': 0.23.0 - '@oxlint-tsgolint/win32-x64': 0.23.0 - - oxlint@1.66.0(oxlint-tsgolint@0.23.0): + '@oxlint/binding-android-arm-eabi': 1.72.0 + '@oxlint/binding-android-arm64': 1.72.0 + '@oxlint/binding-darwin-arm64': 1.72.0 + '@oxlint/binding-darwin-x64': 1.72.0 + '@oxlint/binding-freebsd-x64': 1.72.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.72.0 + '@oxlint/binding-linux-arm-musleabihf': 1.72.0 + '@oxlint/binding-linux-arm64-gnu': 1.72.0 + '@oxlint/binding-linux-arm64-musl': 1.72.0 + '@oxlint/binding-linux-ppc64-gnu': 1.72.0 + '@oxlint/binding-linux-riscv64-gnu': 1.72.0 + '@oxlint/binding-linux-riscv64-musl': 1.72.0 + '@oxlint/binding-linux-s390x-gnu': 1.72.0 + '@oxlint/binding-linux-x64-gnu': 1.72.0 + '@oxlint/binding-linux-x64-musl': 1.72.0 + '@oxlint/binding-openharmony-arm64': 1.72.0 + '@oxlint/binding-win32-arm64-msvc': 1.72.0 + '@oxlint/binding-win32-ia32-msvc': 1.72.0 + '@oxlint/binding-win32-x64-msvc': 1.72.0 + oxlint-tsgolint: 0.24.0 + vite-plus: 0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@6.0.3)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) + + oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.66.0 - '@oxlint/binding-android-arm64': 1.66.0 - '@oxlint/binding-darwin-arm64': 1.66.0 - '@oxlint/binding-darwin-x64': 1.66.0 - '@oxlint/binding-freebsd-x64': 1.66.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.66.0 - '@oxlint/binding-linux-arm-musleabihf': 1.66.0 - '@oxlint/binding-linux-arm64-gnu': 1.66.0 - '@oxlint/binding-linux-arm64-musl': 1.66.0 - '@oxlint/binding-linux-ppc64-gnu': 1.66.0 - '@oxlint/binding-linux-riscv64-gnu': 1.66.0 - '@oxlint/binding-linux-riscv64-musl': 1.66.0 - '@oxlint/binding-linux-s390x-gnu': 1.66.0 - '@oxlint/binding-linux-x64-gnu': 1.66.0 - '@oxlint/binding-linux-x64-musl': 1.66.0 - '@oxlint/binding-openharmony-arm64': 1.66.0 - '@oxlint/binding-win32-arm64-msvc': 1.66.0 - '@oxlint/binding-win32-ia32-msvc': 1.66.0 - '@oxlint/binding-win32-x64-msvc': 1.66.0 - oxlint-tsgolint: 0.23.0 - - oxlint@1.70.0(oxlint-tsgolint@0.23.0)(vite-plus@0.2.1(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3))): + '@oxlint/binding-android-arm-eabi': 1.72.0 + '@oxlint/binding-android-arm64': 1.72.0 + '@oxlint/binding-darwin-arm64': 1.72.0 + '@oxlint/binding-darwin-x64': 1.72.0 + '@oxlint/binding-freebsd-x64': 1.72.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.72.0 + '@oxlint/binding-linux-arm-musleabihf': 1.72.0 + '@oxlint/binding-linux-arm64-gnu': 1.72.0 + '@oxlint/binding-linux-arm64-musl': 1.72.0 + '@oxlint/binding-linux-ppc64-gnu': 1.72.0 + '@oxlint/binding-linux-riscv64-gnu': 1.72.0 + '@oxlint/binding-linux-riscv64-musl': 1.72.0 + '@oxlint/binding-linux-s390x-gnu': 1.72.0 + '@oxlint/binding-linux-x64-gnu': 1.72.0 + '@oxlint/binding-linux-x64-musl': 1.72.0 + '@oxlint/binding-openharmony-arm64': 1.72.0 + '@oxlint/binding-win32-arm64-msvc': 1.72.0 + '@oxlint/binding-win32-ia32-msvc': 1.72.0 + '@oxlint/binding-win32-x64-msvc': 1.72.0 + oxlint-tsgolint: 0.24.0 + vite-plus: 0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) + optional: true + + oxlint@1.74.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.70.0 - '@oxlint/binding-android-arm64': 1.70.0 - '@oxlint/binding-darwin-arm64': 1.70.0 - '@oxlint/binding-darwin-x64': 1.70.0 - '@oxlint/binding-freebsd-x64': 1.70.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.70.0 - '@oxlint/binding-linux-arm-musleabihf': 1.70.0 - '@oxlint/binding-linux-arm64-gnu': 1.70.0 - '@oxlint/binding-linux-arm64-musl': 1.70.0 - '@oxlint/binding-linux-ppc64-gnu': 1.70.0 - '@oxlint/binding-linux-riscv64-gnu': 1.70.0 - '@oxlint/binding-linux-riscv64-musl': 1.70.0 - '@oxlint/binding-linux-s390x-gnu': 1.70.0 - '@oxlint/binding-linux-x64-gnu': 1.70.0 - '@oxlint/binding-linux-x64-musl': 1.70.0 - '@oxlint/binding-openharmony-arm64': 1.70.0 - '@oxlint/binding-win32-arm64-msvc': 1.70.0 - '@oxlint/binding-win32-ia32-msvc': 1.70.0 - '@oxlint/binding-win32-x64-msvc': 1.70.0 - oxlint-tsgolint: 0.23.0 - vite-plus: 0.2.1(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)) + '@oxlint/binding-android-arm-eabi': 1.74.0 + '@oxlint/binding-android-arm64': 1.74.0 + '@oxlint/binding-darwin-arm64': 1.74.0 + '@oxlint/binding-darwin-x64': 1.74.0 + '@oxlint/binding-freebsd-x64': 1.74.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.74.0 + '@oxlint/binding-linux-arm-musleabihf': 1.74.0 + '@oxlint/binding-linux-arm64-gnu': 1.74.0 + '@oxlint/binding-linux-arm64-musl': 1.74.0 + '@oxlint/binding-linux-ppc64-gnu': 1.74.0 + '@oxlint/binding-linux-riscv64-gnu': 1.74.0 + '@oxlint/binding-linux-riscv64-musl': 1.74.0 + '@oxlint/binding-linux-s390x-gnu': 1.74.0 + '@oxlint/binding-linux-x64-gnu': 1.74.0 + '@oxlint/binding-linux-x64-musl': 1.74.0 + '@oxlint/binding-openharmony-arm64': 1.74.0 + '@oxlint/binding-win32-arm64-msvc': 1.74.0 + '@oxlint/binding-win32-ia32-msvc': 1.74.0 + '@oxlint/binding-win32-x64-msvc': 1.74.0 + oxlint-tsgolint: 0.24.0 + vite-plus: 0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) pathe@2.0.3: {} @@ -2552,49 +2841,129 @@ snapshots: tslib@2.8.1: optional: true - tsx@4.22.3: + tsx@4.23.1: dependencies: esbuild: 0.28.0 optionalDependencies: fsevents: 2.3.3 - typescript@6.0.3: {} + typescript@6.0.3: + optional: true - undici-types@7.24.6: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + + undici-types@8.3.0: {} + + vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@6.0.3)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)): + dependencies: + '@oxc-project/types': 0.138.0 + '@oxlint/plugins': 1.68.0 + '@vitest/browser': 4.1.10(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))(vitest@4.1.10) + '@vitest/browser-preview': 4.1.10(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))(vitest@4.1.10) + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + '@voidzero-dev/vite-plus-core': 0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@6.0.3) + oxfmt: 0.57.0(vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@6.0.3)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))) + oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@6.0.3)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))) + oxlint-tsgolint: 0.24.0 + vitest: 4.1.10(@types/node@26.1.1)(@vitest/browser-preview@4.1.10)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) + optionalDependencies: + '@voidzero-dev/vite-plus-darwin-arm64': 0.2.4 + '@voidzero-dev/vite-plus-darwin-x64': 0.2.4 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.2.4 + '@voidzero-dev/vite-plus-linux-arm64-musl': 0.2.4 + '@voidzero-dev/vite-plus-linux-x64-gnu': 0.2.4 + '@voidzero-dev/vite-plus-linux-x64-musl': 0.2.4 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.2.4 + '@voidzero-dev/vite-plus-win32-x64-msvc': 0.2.4 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - svelte + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vite + - yaml - vite-plus@0.2.1(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)): + vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)): dependencies: - '@oxc-project/types': 0.136.0 + '@oxc-project/types': 0.138.0 '@oxlint/plugins': 1.68.0 - '@vitest/browser': 4.1.9(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3))(vitest@4.1.9) - '@vitest/browser-preview': 4.1.9(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3))(vitest@4.1.9) - '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)) - '@vitest/pretty-format': 4.1.9 - '@vitest/runner': 4.1.9 - '@vitest/snapshot': 4.1.9 - '@vitest/spy': 4.1.9 - '@vitest/utils': 4.1.9 - '@voidzero-dev/vite-plus-core': 0.2.1(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)(typescript@6.0.3) - oxfmt: 0.55.0(vite-plus@0.2.1(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3))) - oxlint: 1.70.0(oxlint-tsgolint@0.23.0)(vite-plus@0.2.1(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3))) - oxlint-tsgolint: 0.23.0 - vitest: 4.1.9(@types/node@25.9.1)(@vitest/browser-preview@4.1.9)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)) + '@vitest/browser': 4.1.10(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))(vitest@4.1.10) + '@vitest/browser-preview': 4.1.10(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))(vitest@4.1.10) + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + '@voidzero-dev/vite-plus-core': 0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2) + oxfmt: 0.57.0(vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))) + oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.4(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)(typescript@7.0.2)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))) + oxlint-tsgolint: 0.24.0 + vitest: 4.1.10(@types/node@26.1.1)(@vitest/browser-preview@4.1.10)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) optionalDependencies: - '@voidzero-dev/vite-plus-darwin-arm64': 0.2.1 - '@voidzero-dev/vite-plus-darwin-x64': 0.2.1 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.2.1 - '@voidzero-dev/vite-plus-linux-arm64-musl': 0.2.1 - '@voidzero-dev/vite-plus-linux-x64-gnu': 0.2.1 - '@voidzero-dev/vite-plus-linux-x64-musl': 0.2.1 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.2.1 - '@voidzero-dev/vite-plus-win32-x64-msvc': 0.2.1 + '@voidzero-dev/vite-plus-darwin-arm64': 0.2.4 + '@voidzero-dev/vite-plus-darwin-x64': 0.2.4 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.2.4 + '@voidzero-dev/vite-plus-linux-arm64-musl': 0.2.4 + '@voidzero-dev/vite-plus-linux-x64-gnu': 0.2.4 + '@voidzero-dev/vite-plus-linux-x64-musl': 0.2.4 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.2.4 + '@voidzero-dev/vite-plus-win32-x64-msvc': 0.2.4 transitivePeerDependencies: - '@arethetypeswrong/core' - '@edge-runtime/vm' - '@opentelemetry/api' - - '@tsdown/css' - - '@tsdown/exe' - '@types/node' - '@vitejs/devtools' - '@vitest/coverage-istanbul' @@ -2621,8 +2990,9 @@ snapshots: - utf-8-validate - vite - yaml + optional: true - vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3): + vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -2630,20 +3000,20 @@ snapshots: rolldown: 1.0.3 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 26.1.1 esbuild: 0.28.0 fsevents: 2.3.3 - tsx: 4.22.3 + tsx: 4.23.1 - vitest@4.1.9(@types/node@25.9.1)(@vitest/browser-preview@4.1.9)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)): + vitest@4.1.10(@types/node@26.1.1)(@vitest/browser-preview@4.1.10)(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)): dependencies: - '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)) - '@vitest/pretty-format': 4.1.9 - '@vitest/runner': 4.1.9 - '@vitest/snapshot': 4.1.9 - '@vitest/spy': 4.1.9 - '@vitest/utils': 4.1.9 + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -2655,11 +3025,11 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3) + vite: 8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.9.1 - '@vitest/browser-preview': 4.1.9(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3))(vitest@4.1.9) + '@types/node': 26.1.1 + '@vitest/browser-preview': 4.1.10(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.0)(tsx@4.23.1))(vitest@4.1.10) transitivePeerDependencies: - msw From cb22e9c11975671a000c4c4024aace15e12854a0 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Thu, 16 Jul 2026 13:55:32 +0800 Subject: [PATCH 8/8] fix: keep vite-plus external when compiling the TS sidecar oxfmt 0.59 and oxlint 1.74 lazily import("vite-plus") to read a Vite+ config file. bun compiles the sidecar from a throwaway workdir that installs only oxfmt/oxlint/oxc-parser, so the bundler could not resolve the specifier and the staging step failed: error: Could not resolve: "vite-plus" at node_modules/oxfmt/dist/cli.js:133:27 Upstream ships this specifier external on purpose so the user-installed copy is used at runtime. Do the same, alongside the napi bindings and prettier plugins already listed. The sidecar bundles .oxfmtrc.json and .oxlintrc.json, so the Vite+ config path is unreachable there, and a project that keeps its config in vite.config.ts carries its own vite-plus for the import to resolve against. Verified with infra/scripts/tasks/test-binary-smoke.sh on darwin_arm64. --- infra/scripts/release/stage-ts-assets.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/infra/scripts/release/stage-ts-assets.sh b/infra/scripts/release/stage-ts-assets.sh index c551193..3c4ac38 100755 --- a/infra/scripts/release/stage-ts-assets.sh +++ b/infra/scripts/release/stage-ts-assets.sh @@ -123,6 +123,12 @@ cp "${root}/packages/devx/scripts/package.json" "${workdir}/scripts/package.json # next to the sidecar through NAPI_RS_NATIVE_LIBRARY_PATH, which keeps the JS # bundle platform-independent. oxfmt's optional prettier plugins are external # too; they are not installed by any fmtkit distribution channel. +# +# vite-plus is an optional peer of oxfmt/oxlint that both lazily import to read +# a Vite+ config file. Upstream ships the specifier external for the same +# reason. The sidecar bundles .oxfmtrc.json/.oxlintrc.json, so that path is +# unreachable here; a project that does keep its config in vite.config.ts has +# its own vite-plus for the import to resolve against. bun_externals=( --external '@oxc-parser/binding-*' --external '@oxfmt/binding-*' @@ -131,6 +137,7 @@ bun_externals=( --external 'prettier-plugin-*' --external '@shopify/prettier-plugin-liquid' --external '@zackad/prettier-plugin-twig' + --external 'vite-plus' ) fetch_binding() {