diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000..7baddd8 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "personal", + "interface": { + "displayName": "Personal" + }, + "plugins": [ + { + "name": "committy", + "source": { + "source": "local", + "path": "./plugins/committy" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" + } + ] +} diff --git a/.agents/skills b/.agents/skills new file mode 120000 index 0000000..6153a62 --- /dev/null +++ b/.agents/skills @@ -0,0 +1 @@ +../plugins/committy/skills \ No newline at end of file diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..2096b65 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-marketplace.json", + "name": "committy-tools", + "description": "Agent workflows and enforcement for Committy repositories.", + "owner": { + "name": "Arnaud Leherpeur", + "email": "arnaud.leherpeur@taultek.com" + }, + "plugins": [ + { + "name": "committy", + "source": "./plugins/committy", + "description": "Token-efficient commit, branch, release, and convention enforcement workflows.", + "category": "Development", + "tags": ["git", "conventional-commits", "agents"] + } + ] +} diff --git a/.claude/hooks/block-dangerous-git.sh b/.claude/hooks/block-dangerous-git.sh new file mode 100755 index 0000000..5ca2568 --- /dev/null +++ b/.claude/hooks/block-dangerous-git.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Block genuinely destructive git operations. +# +# Design notes: +# - Matches the *command*, not the raw tool input. The previous version +# grepped the whole string, so prose in a heredoc that merely named a +# command was blocked (including this file's own replacement), while +# `git push` with two spaces or `git -C . push` slipped straight through. +# - Plain `git push` is not destructive and is no longer blocked. The +# repository's own pre-push hook is the policy-backed control; this hook +# only refuses operations that destroy history or uncommitted work. +# - Degrades safely when jq is unavailable. + +INPUT=$(cat) + +if command -v jq >/dev/null 2>&1; then + COMMAND=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty') +else + COMMAND=$(printf '%s' "$INPUT" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/p') +fi + +[ -z "$COMMAND" ] && exit 0 + +# Drop heredoc bodies, so documentation or test data that names a command is +# not mistaken for an invocation of it. A heredoc body spans lines, so this +# keeps the line that opens it and discards everything after. +# +# Exception: if the heredoc feeds an interpreter, the body IS executed, and +# dropping it would be a bypass (`bash <&2 + exit 2 +} + +# --force rewrites remote history unconditionally; --force-with-lease is guarded. +# Checked as two independent conditions rather than one ordered regex: the flag +# may precede or follow the remote, and `push -f` leaves no space for a pattern +# that expects one before the flag. +if printf '%s' "$SCRUBBED" | grep -qE '(^|[;&|[:space:]])push([[:space:]]|$)' && + printf '%s' "$SCRUBBED" | grep -qE '(^|[[:space:]])(--force|-f)([[:space:]]|$)'; then + printf '%s' "$SCRUBBED" | grep -q -- '--force-with-lease' || deny "an unguarded force-push" +fi + +printf '%s' "$SCRUBBED" | grep -qE 'reset[[:space:]]+--hard' && deny "a hard reset (discards working-tree changes)" +printf '%s' "$SCRUBBED" | grep -qE 'clean[[:space:]]+-[a-zA-Z]*f' && deny "a forced clean (deletes untracked files)" +printf '%s' "$SCRUBBED" | grep -qE 'branch[[:space:]]+-D([[:space:]]|$)' && deny "a forced branch delete" +printf '%s' "$SCRUBBED" | grep -qE '(checkout|restore)[[:space:]]+(--[[:space:]]+)?\.([[:space:]]|$)' && deny "a bulk discard of working-tree changes" + +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..9d674a1 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous-git.sh" + } + ] + } + ] + } +} diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..3a21eec --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,6 @@ +# Copilot instructions + +See [`AGENTS.md`](../AGENTS.md) at the repository root. It is the canonical +guide for every coding agent working in this repository and applies in full: +build and verify commands, the CLI-first agent workflow, architecture, and the +commit and pull-request rules. diff --git a/.github/workflows/cargo-release.yml b/.github/workflows/cargo-release.yml index 5413135..6d48257 100644 --- a/.github/workflows/cargo-release.yml +++ b/.github/workflows/cargo-release.yml @@ -13,14 +13,34 @@ on: - "v[0-9]+.[0-9]+.[0-9]+-*" jobs: + verify-version: + name: Verify Cargo.toml matches the tag + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Compare Cargo.toml version with tag + shell: bash + run: | + VERSION=$(grep -m1 'version\s*=' Cargo.toml | cut -d'"' -f2 | cut -d"'" -f2) + TAG="$GITHUB_REF_NAME" + if [ "v$VERSION" != "$TAG" ]; then + echo "::error::Tag $TAG points at a tree whose Cargo.toml says $VERSION." + echo "The published binaries would report $VERSION to --version, and self-update would keep offering $TAG forever." + echo "Bump Cargo.toml to ${TAG#v} on the release branch, then move or recreate the tag." + exit 1 + fi + echo "Cargo.toml version $VERSION matches tag $TAG" + create-release: + needs: verify-version runs-on: ubuntu-latest outputs: is_prerelease: ${{ steps.release_type.outputs.is_prerelease }} draft: ${{ steps.release_type.outputs.draft }} changelog: ${{ steps.changelog.outputs.changelog }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 @@ -54,7 +74,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} release: - needs: create-release + needs: [verify-version, create-release] name: Release for ${{ matrix.target }} runs-on: ${{ matrix.os }} @@ -84,7 +104,7 @@ jobs: asset_suffix: macos-arm64 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Get binary name and version shell: bash diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3c35008..2aeee09 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,7 +4,11 @@ on: push: branches: [main, develop] pull_request: - branches: ["*"] + # `**` rather than `*`: in Actions branch filters `*` does not match `/`, + # so a pull request targeting any branch with a slash in its name -- every + # stacked pull request, and anything under a `feat/` or `fix/` prefix -- + # ran no checks at all. + branches: ["**"] env: CARGO_TERM_COLOR: always @@ -13,7 +17,7 @@ jobs: lint-and-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install Rust uses: actions-rs/toolchain@v1.0.7 with: diff --git a/.github/workflows/native-git-verification.yml b/.github/workflows/native-git-verification.yml index 3710d50..cb36fe4 100644 --- a/.github/workflows/native-git-verification.yml +++ b/.github/workflows/native-git-verification.yml @@ -7,6 +7,6 @@ jobs: native-git-e2e: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Run native git Docker verification run: ./scripts/verify_native_git_e2e.sh diff --git a/AGENTS.md b/AGENTS.md index 5c1572f..37410c2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,172 +1,34 @@ -# AGENTS.md +# Committy agent guide -This file provides guidance to Codex (Codex.ai/code) when working with code in this repository. +Committy is a Rust CLI for conventional commits, branch policy, grouped commits, releases, and multi-package version updates. The checkout is CLI-first; do not assume a TUI exists. -## Overview +## Work safely -Committy is a Rust CLI for composing conventional commits, linting history quality, planning grouped commits, generating tags, and orchestrating multi-package version updates. +- Discover active policy with `cargo run -- schema --output json`. +- Prefer `--non-interactive`, `--repo-path`, `--dry-run`, and `--output json`. +- Set `COMMITTY_NONINTERACTIVE=1` in the environment instead of relying on flag placement. `--non-interactive` and `-q`/`--quiet` are global and work on either side of the subcommand; every other flag (`--repo-path`, `--output`, `--dry-run`, and `--verbose`) must follow it. +- Inspect `api_version`, `ok`, `errors`, and the command-specific plan before applying it. +- Use the `committy-branch`, `committy-commit`, `committy-release`, and `committy-enforce` plugin skills when available. +- Never infer permission for a push, publish, destructive Git action, or unrelated staging. -The current repository state is **CLI-first and JSON-friendly**. This checkout does **not** contain a checked-in `src/tui/` module or a `tui` subcommand, so agent workflows should use the machine-readable CLI commands instead of assuming a terminal UI exists. +## Build and verify -## Essential Commands - -### Build and Test -```bash -# Build the project -cargo build - -# Run all tests -cargo test - -# Run a specific test -cargo test test_name - -# Run tests with verbose output -cargo test -- --nocapture -``` - -### Running the CLI -```bash -# Run from source -cargo run - -# Run with arguments -cargo run -- [options] - -# Agent-safe previews -cargo run -- branch --type feat --ticket AI42 --subject "agent flow" --dry-run --output json -cargo run -- commit --type feat --scope core --message "add agent flow" --dry-run --output json -cargo run -- group-commit --mode plan --output json -cargo run -- lint --output json -cargo run -- tag --dry-run --output json -``` - -### Development ```bash -# Check code without building cargo check - -# Format code -cargo fmt - -# Run linter -cargo clippy +cargo test +cargo fmt --all -- --check +cargo clippy --all-targets --all-features ``` -## Current CLI Surface - -The current checked-in command set is defined in `src/cli/mod.rs`: -- `commit` -- `amend` -- `tag` -- `lint` -- `lint-message` -- `branch` -- `group-commit` -- `init` -- `config` -- `packages` +Tests are in `tests/`; CLI integration tests use `assert_cmd` and temporary repositories. Add public-seam tests for machine output, exit codes, Git behavior, hook protocol, and plugin manifests. ## Architecture -### Module Structure - -**CLI Layer** (`src/cli/`) -- `commands/`: concrete command implementations for `amend`, `branch`, `commit`, `config`, `group_commit`, `init`, `lint`, `lint_message`, `packages`, and `tag` -- `src/main.rs`: top-level flag parsing, non-interactive mode detection, update checks, and command dispatch - -**Workflow + Repository Config** -- `.committy/config.toml`: repository-level multi-package config for Committy itself -- `src/workflow/orchestrator.rs`: coordinates scope detection, version bumps, and dependency updates -- `src/config/hierarchy.rs` and `src/config/repository.rs`: merged config loading and repository config support -- Current scope mapping in this repo: - - `src/**` → `core` - - `docs/**` → `docs` - -**Git Operations** (`src/git/`) -- `repository.rs`: repository discovery, staged-file queries, and git config validation -- `commit.rs`: commit creation and message formatting -- `tag.rs`: tag generation helpers -- `branch.rs`: branch operations - -**Linting** (`src/linter/`) -- Validates conventional commit messages -- Used by `commit`, `amend`, `group-commit`, `lint`, and `lint-message` -- Exit code `3` is reserved for lint issues - -**Packages + Versioning** (`src/packages/`, `src/versioning/`, `src/version/`) -- Multi-package detection and sync helpers -- Version bump calculation for independent, unified, and hybrid strategies -- Version file updates across supported package managers - -**AI Integration** (`src/ai/`) -- Supports OpenRouter and Ollama providers -- Currently used by `group-commit --ai` - -**Input/Prompts** (`src/input/`) -- Interactive prompt handling via `inquire` -- Validation and auto-correction helpers for commit and branch inputs - -**Error Handling** (`src/error.rs`) -- `CliError` is the central error type across commands - -### Key Flow: Commit Command - -1. `src/cli/commands/commit.rs` discovers the repo and checks staged changes. -2. It loads allowed commit types and, when repo config exists, auto-detects scopes. -3. It formats and lints the full commit message. -4. When `.committy/config.toml` is available, it runs `WorkflowOrchestrator` to preview or apply version/dependency side effects. -5. `--dry-run --output json` returns the resolved message plus an optional `workflow` preview without mutating git. -6. A real run validates git config, stages workflow-modified files if needed, and then creates or amends the commit. - -### Key Flow: Group Commit - -1. `src/cli/commands/group_commit.rs` classifies changed files into `docs`, `tests`, `ci`, `deps`, `build`, `chore`, and `code` groups. -2. `--mode plan` returns grouped JSON output without mutations. -3. `--mode apply` creates one validated commit per group. -4. `--push` is only valid in apply mode and must be paired with `--confirm-push`. - -## Recommended Agent Workflow - -Use the current CLI states as a **preview → inspect → apply** workflow: - -1. Preview first with `--dry-run --output json` when the command supports it. -2. Inspect `ok`, `dry_run`, `errors`, and command-specific fields such as: - - `branch_name` for `branch` - - `message`, `commit_type`, `scope`, and optional `workflow` for `commit` - - `mode`, `groups`, and `commits` for `group-commit` -3. Rerun without `--dry-run` only when the preview matches the intended change. -4. Use `--repo-path` when operating on a checkout other than the current shell directory. -5. Before any push or release, run `committy --non-interactive lint --repo-path . --output json`. -6. Require explicit confirmation for remote mutations: - - `group-commit --mode apply --push --confirm-push` - - `tag --publish --confirm-publish` - -The canonical reference for this flow is `docs/src/content/docs/reference/agent-workflows.mdx`. - -## Non-Interactive Mode - -- Enabled via `--non-interactive`, `COMMITTY_NONINTERACTIVE=1`, or `CI=1` -- In non-interactive mode, required flags must be provided and prompts are skipped -- This is the preferred mode for agentic or CI-driven usage - -## Build System - -- `build.rs` injects `SENTRY_DSN` and `POSTHOG_API_KEY` at compile time from environment variables -- Defaults to `"undefined"` if those environment variables are not set - -## Testing - -- Tests live in `tests/` -- Integration-style CLI coverage uses `assert_cmd` -- Temporary git repositories are created with `tempfile` -- Good entry points for agent workflow behavior: - - `tests/agent_cli_tests.rs` - - `tests/group_commit_tests.rs` - - `tests/lint_message_cli_tests.rs` - -## Contributing Notes +- `src/cli/commands/`: command implementations and JSON payloads +- `src/config/`, `src/convention/`: hierarchical typed policy +- `src/git/`: repository and Git operations +- `src/linter/`: message/history validation; exit code 3 means lint findings +- `src/workflow/`, `src/versioning/`, `src/packages/`: release side effects +- `plugins/committy/`: shared Codex and Claude skills -- PRs should target the `develop` branch, not `main` -- Commit messages should follow conventional commit format -- Versioning is managed through `Cargo.toml`, `.committy/config.toml`, and the tag/workflow helpers +PRs target `develop`. Commits follow Conventional Commits. Never add AI attribution trailers — no `Co-Authored-By:`, no `Claude-Session:`, no equivalent — to commit messages or PR descriptions, even when tooling suggests them. The canonical agent reference is `docs/src/content/docs/reference/agent-workflows.mdx`. diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..fa62fc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +## 1.9.1 - 2026-08-02 +_Since v1.9.0_ + +### Bug Fixes +- **hooks:** support Windows release builds (b35b003) diff --git a/CLAUDE.md b/CLAUDE.md index 8cd9c17..be0811b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,172 +1,7 @@ -# CLAUDE.md +@AGENTS.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## Claude Code specifics -## Overview +`AGENTS.md` above is the canonical guide and applies in full. Only this section is Claude-specific. -Committy is a Rust CLI for composing conventional commits, linting history quality, planning grouped commits, generating tags, and orchestrating multi-package version updates. - -The current repository state is **CLI-first and JSON-friendly**. This checkout does **not** contain a checked-in `src/tui/` module or a `tui` subcommand, so Claude workflows should use the machine-readable CLI commands instead of assuming a terminal UI exists. - -## Essential Commands - -### Build and Test -```bash -# Build the project -cargo build - -# Run all tests -cargo test - -# Run a specific test -cargo test test_name - -# Run tests with verbose output -cargo test -- --nocapture -``` - -### Running the CLI -```bash -# Run from source -cargo run - -# Run with arguments -cargo run -- [options] - -# Agent-safe previews -cargo run -- branch --type feat --ticket AI42 --subject "agent flow" --dry-run --output json -cargo run -- commit --type feat --scope core --message "add agent flow" --dry-run --output json -cargo run -- group-commit --mode plan --output json -cargo run -- lint --output json -cargo run -- tag --dry-run --output json -``` - -### Development -```bash -# Check code without building -cargo check - -# Format code -cargo fmt - -# Run linter -cargo clippy -``` - -## Current CLI Surface - -The current checked-in command set is defined in `src/cli/mod.rs`: -- `commit` -- `amend` -- `tag` -- `lint` -- `lint-message` -- `branch` -- `group-commit` -- `init` -- `config` -- `packages` - -## Architecture - -### Module Structure - -**CLI Layer** (`src/cli/`) -- `commands/`: concrete command implementations for `amend`, `branch`, `commit`, `config`, `group_commit`, `init`, `lint`, `lint_message`, `packages`, and `tag` -- `src/main.rs`: top-level flag parsing, non-interactive mode detection, update checks, and command dispatch - -**Workflow + Repository Config** -- `.committy/config.toml`: repository-level multi-package config for Committy itself -- `src/workflow/orchestrator.rs`: coordinates scope detection, version bumps, and dependency updates -- `src/config/hierarchy.rs` and `src/config/repository.rs`: merged config loading and repository config support -- Current scope mapping in this repo: - - `src/**` → `core` - - `docs/**` → `docs` - -**Git Operations** (`src/git/`) -- `repository.rs`: repository discovery, staged-file queries, and git config validation -- `commit.rs`: commit creation and message formatting -- `tag.rs`: tag generation helpers -- `branch.rs`: branch operations - -**Linting** (`src/linter/`) -- Validates conventional commit messages -- Used by `commit`, `amend`, `group-commit`, `lint`, and `lint-message` -- Exit code `3` is reserved for lint issues - -**Packages + Versioning** (`src/packages/`, `src/versioning/`, `src/version/`) -- Multi-package detection and sync helpers -- Version bump calculation for independent, unified, and hybrid strategies -- Version file updates across supported package managers - -**AI Integration** (`src/ai/`) -- Supports OpenRouter and Ollama providers -- Currently used by `group-commit --ai` - -**Input/Prompts** (`src/input/`) -- Interactive prompt handling via `inquire` -- Validation and auto-correction helpers for commit and branch inputs - -**Error Handling** (`src/error.rs`) -- `CliError` is the central error type across commands - -### Key Flow: Commit Command - -1. `src/cli/commands/commit.rs` discovers the repo and checks staged changes. -2. It loads allowed commit types and, when repo config exists, auto-detects scopes. -3. It formats and lints the full commit message. -4. When `.committy/config.toml` is available, it runs `WorkflowOrchestrator` to preview or apply version/dependency side effects. -5. `--dry-run --output json` returns the resolved message plus an optional `workflow` preview without mutating git. -6. A real run validates git config, stages workflow-modified files if needed, and then creates or amends the commit. - -### Key Flow: Group Commit - -1. `src/cli/commands/group_commit.rs` classifies changed files into `docs`, `tests`, `ci`, `deps`, `build`, `chore`, and `code` groups. -2. `--mode plan` returns grouped JSON output without mutations. -3. `--mode apply` creates one validated commit per group. -4. `--push` is only valid in apply mode and must be paired with `--confirm-push`. - -## Recommended Agent Workflow - -Use the current CLI states as a **preview → inspect → apply** workflow: - -1. Preview first with `--dry-run --output json` when the command supports it. -2. Inspect `ok`, `dry_run`, `errors`, and command-specific fields such as: - - `branch_name` for `branch` - - `message`, `commit_type`, `scope`, and optional `workflow` for `commit` - - `mode`, `groups`, and `commits` for `group-commit` -3. Rerun without `--dry-run` only when the preview matches the intended change. -4. Use `--repo-path` when operating on a checkout other than the current shell directory. -5. Before any push or release, run `committy --non-interactive lint --repo-path . --output json`. -6. Require explicit confirmation for remote mutations: - - `group-commit --mode apply --push --confirm-push` - - `tag --publish --confirm-publish` - -The canonical reference for this flow is `docs/src/content/docs/reference/agent-workflows.mdx`. - -## Non-Interactive Mode - -- Enabled via `--non-interactive`, `COMMITTY_NONINTERACTIVE=1`, or `CI=1` -- In non-interactive mode, required flags must be provided and prompts are skipped -- This is the preferred mode for agentic or CI-driven usage - -## Build System - -- `build.rs` injects `SENTRY_DSN` and `POSTHOG_API_KEY` at compile time from environment variables -- Defaults to `"undefined"` if those environment variables are not set - -## Testing - -- Tests live in `tests/` -- Integration-style CLI coverage uses `assert_cmd` -- Temporary git repositories are created with `tempfile` -- Good entry points for agent workflow behavior: - - `tests/agent_cli_tests.rs` - - `tests/group_commit_tests.rs` - - `tests/lint_message_cli_tests.rs` - -## Contributing Notes - -- PRs should target the `develop` branch, not `main` -- Commit messages should follow conventional commit format -- Versioning is managed through `Cargo.toml`, `.committy/config.toml`, and the tag/workflow helpers +- Invoke the bundled skills as `/committy:committy-branch`, `/committy:committy-commit`, `/committy:committy-release`, and `/committy:committy-enforce` when the plugin is installed. Other runtimes resolve them by bare name. diff --git a/Cargo.toml b/Cargo.toml index 5628d98..c441772 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "committy" -version = "1.7.0" +version = "1.10.0" authors = ["Arnaud (Martient) Leherpeur "] edition = "2021" description = "🚀 Generate clear, concise, and structured commit messages effortlessly" @@ -48,6 +48,4 @@ assert_cmd = "2.1.1" predicates = "3.1.3" tempfile = "3.23.0" mockall = "0.13.1" -once_cell = "1.21.3" serial_test = "3.2.0" - diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..f74c7f7 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,5 @@ +# Gemini instructions + +See [`AGENTS.md`](./AGENTS.md). It is the canonical guide for every coding agent +working in this repository and applies in full: build and verify commands, the +CLI-first agent workflow, architecture, and the commit and pull-request rules. diff --git a/docs/CLI_WORKFLOW_REFERENCE.md b/docs/CLI_WORKFLOW_REFERENCE.md index fdff19d..10821e4 100644 --- a/docs/CLI_WORKFLOW_REFERENCE.md +++ b/docs/CLI_WORKFLOW_REFERENCE.md @@ -139,12 +139,14 @@ info = "Team-specific conventional commits." [[convention.types]] name = "feat" description = "A feature" +contexts = ["commit", "branch"] bump = "minor" changelog_section = "Features" [[convention.types]] name = "sec" description = "A security fix" +contexts = ["commit", "branch"] bump = "patch" changelog_section = "Security" @@ -155,3 +157,14 @@ prompt = "Select a change type" required = true choices = ["feat", "sec", "fix", "docs", "chore"] ``` + +Branch-specific ticket policy stays separate from reusable type metadata: + +```toml +[branch_rules] +require_ticket = true +ticket_pattern = "^[A-Z]+[0-9]+$" +enforce_explicit_names = false +``` + +Types without `contexts` remain commit types for backward compatibility. Query the effective typed definitions and described CLI capabilities with `committy schema --output json`. diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index d0a73e4..64bcf2c 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -10,9 +10,9 @@ export default defineConfig({ integrations: [ starlight({ title: 'Committy Docs', - social: { - github: 'https://github.com/martient/committy', - }, + social: [ + { icon: 'github', label: 'GitHub', href: 'https://github.com/martient/committy' }, + ], sidebar: [ { label: 'Intro', @@ -23,14 +23,13 @@ export default defineConfig({ }, { label: 'Reference', - autogenerate: { directory: 'reference' }, + items: [{ autogenerate: { directory: 'reference' } }], }, { label: 'Project', - autogenerate: { directory: 'project' }, + items: [{ autogenerate: { directory: 'project' } }], }, ], - }), ], }); diff --git a/docs/audits/agent-compatibility-2026-09.md b/docs/audits/agent-compatibility-2026-09.md new file mode 100644 index 0000000..f021f6d --- /dev/null +++ b/docs/audits/agent-compatibility-2026-09.md @@ -0,0 +1,281 @@ +# Agent compatibility & AI capability audit — September 2026 + +Scope: everything in this repository that an AI coding agent touches — the agent +instruction files, the shared Codex/Claude plugin, the machine-readable CLI +contract (`--output json` / `schema`), the generated enforcement (hooks + CI), +and the in-process LLM integration under `src/ai/`. + +Method: full read of the agent-facing surfaces at `caf53e3` (v1.9.1), compared +against the state of the agent-integration ecosystem as of September 2026. + +--- + +## 1. What exists today + +| Surface | Location | State | +|---|---|---| +| Agent instructions | `AGENTS.md`, `CLAUDE.md` | Two near-identical files (diff is 2 lines) | +| Claude marketplace | `.claude-plugin/marketplace.json` | Valid, points at `./plugins/committy` | +| Codex marketplace | `.agents/plugins/marketplace.json` | Valid, local source | +| Plugin manifests | `plugins/committy/.claude-plugin/plugin.json`, `.codex-plugin/plugin.json` | Skills only; no commands/agents/hooks/mcpServers | +| Skills | 4 × `SKILL.md` (branch, commit, release, enforce) | Well-scoped, progressive disclosure, safety rules present | +| Codex interface shims | 4 × `agents/openai.yaml` | Display metadata only | +| Machine contract | `src/cli/output.rs` (`api_version = 1`) + per-command payloads | Stable envelope: `api_version`, `command`, `ok`, `dry_run`, `errors` | +| Capability discovery | `src/cli/commands/schema.rs` | 21 capabilities with consent metadata (was 10, bare) | +| Repo enforcement | `src/cli/commands/hooks.rs:14-37` | `commit-msg`, `pre-push`, GitHub Actions template | +| Local agent guardrail | `.claude/settings.json` + `.claude/hooks/block-dangerous-git.sh` | PreToolUse Bash denylist | +| LLM integration | `src/ai/mod.rs`, `src/cli/commands/group_commit.rs:98-151, 356-507` | OpenRouter + Ollama, `group-commit` only | + +The core architectural bet is **CLI-first with typed JSON**. That still holds, +but not for the reason an earlier draft gave: see §3.4, where the blanket +"never build an MCP server" recommendation is withdrawn, and §3.5, where the +CLI's own agent ergonomics turned out to be the real problem. + +--- + +## 2. What changed in the space (relevant deltas) + +1. **`AGENTS.md` is now the cross-tool standard.** Stewarded by the Agentic AI + Foundation (Linux Foundation), read by 30+ agents (Codex, Copilot, Cursor, + Gemini CLI, Jules, Aider, Zed, Windsurf, Devin), 60k+ repositories. Claude Code + remains the outlier reading `CLAUDE.md`; the accepted pattern is a one-line + `@AGENTS.md` import rather than a maintained duplicate. +2. **Agent Skills has a published spec.** Frontmatter keys are fixed to + `name`, `description`, `license`, `allowed-tools`, `metadata`, `compatibility`; + unknown keys fail validation. `allowed-tools` is the security-relevant addition. +3. **`.agents/skills/` is the cross-tool skill discovery path.** Codex CLI, the + Codex app, Claude Code and Copilot CLI all resolve it. Skills are now the most + portable plugin component; per-vendor interface files are the least portable. +4. **Codex shipped a plugin marketplace** (26 Mar 2026) bundling skills, MCP + servers and app integrations; self-serve publishing to the official directory + is still "coming soon" as of mid-2026. +5. **Claude Code plugins now carry more than skills**: `commands`, `agents`, + `hooks`, `mcpServers`, `outputStyles`, `lspServers`, auto-discovered from + standard directories. +6. **The MCP token argument has a shelf life.** The benchmarks (35× token cost, + reliability dropping to 72% on hard tasks, ~55k tokens for a naive GitHub + server) measure *eager-loading* MCP, where every tool schema is injected at + session start. Progressive disclosure recovers a reported 60–85%. +7. **The 2026-07-28 MCP spec is the largest revision since launch**: stateless + core, sessions gone, Roots/Sampling/Logging deprecated, MCP Apps and Tasks + added, progressive discovery and tool search on the roadmap. Recommendations + written against the old full-bundle design no longer apply cleanly. +8. **Ollama structured output moved to JSON-Schema-constrained decoding** via a + **top-level** `format` field; `/api/chat` still defaults to `stream: true`. + +--- + +## 3. Findings + +Severity: **P0** = broken or misleading in the field · **P1** = real compatibility +or maintenance cost · **P2** = polish. + +### 3.1 Defects to fix + +**P0-1 — The Ollama provider could not work as written.** ✅ **Fixed.** +`src/ai/mod.rs:160-205` posts to `/api/chat` without `stream: false`. Ollama +defaults to `stream: true`, so the response is newline-delimited JSON events, and +`resp.json::()` will fail to deserialize on essentially every call. +Every `--ai-provider ollama` run therefore ends in `LlmError::Parse` and silently +falls back to the default message. Fix: send `stream: false`. + +**P0-2 — Ollama JSON mode was a no-op.** ✅ **Fixed.** +`format` is nested inside `options` (`src/ai/mod.rs:174-196`). Ollama reads +`format` as a **top-level** request field, so JSON mode has never taken effect. +Fix: hoist `format` to the request body, and prefer passing the +`AiCommitSuggestion` JSON Schema rather than the string `"json"` — Ollama has +supported schema-constrained decoding since 0.5. + +**P0-3 — AI in default (safe) mode was given no signal at all.** ✅ **Fixed.** +`src/cli/commands/group_commit.rs:400-413`: unless `--ai-allow-sensitive` is set, +the user prompt contains only the group name and the default type/short, and +instructs the model to work "without revealing code or filenames". The model is +asked to improve a description it cannot see the basis for. In practice this +either reproduces the default or produces a lint failure. Fix: send redacted but +real signal by default — path *shapes* (extension histogram, directory prefixes, +add/modify/delete counts, scope mappings from `.committy/config.toml`) — none of +which is file content. Reserve `--ai-allow-sensitive` for actual diff hunks. + +**P0-4 — WITHDRAWN. This finding was wrong.** ❌ +The original text claimed `--ai` was silently ignored in `--mode apply`. It is +not: the apply arm has its own AI block (`group_commit.rs:586`) with its own +`suggest_commit` calls. The error came from reading a `grep` run through +`awk 'NR>=530'`, whose renumbered output was mistaken for line numbers in the +plan arm. The documented `--mode apply ... --ai` example always worked. + +What is real in that area is **duplication**: `plan` and `apply` carried +byte-identical copies of both the group-building loop and the ~150-line AI +enrichment block. That duplication is what made the misreading possible. Both +are now extracted into `build_groups()` and `ai_user_prompt()` and shared. ✅ **Fixed.** + +**P1-5 — `--ai-diff-lines-per-file` was a dead flag.** ✅ **Fixed** (removed). +Declared at `group_commit.rs:143-147` as `_ai_diff_lines_per_file` and never read. +It is documented in `ai-flags.mdx` and accepted on the command line. Remove it, or +implement it as part of P0-3. + +**P1-6 — `ai-flags.mdx` overstated what is sent.** ✅ **Fixed.** +It claims `--ai-allow-sensitive` "may include snippets or diff lines from code" — +no diff is ever sent, in either mode. It also recommends +`--ai-model openrouter/anthropic/claude-3.5-sonnet`, a model generation that is two +families out of date. Both should be corrected. + +### 3.2 Compatibility gaps to close + +**P1-7 — `capabilities` in `schema` was stale and undersold the CLI.** ✅ **Fixed.** +`schema.rs` advertised 10 capabilities covering branch, commit and hooks only, +while the `committy-release` and `committy-enforce` skills instructed agents to +run `bump`, `tag`, `changelog` and `config validate` — commands the capability +list denied existed. `agent-workflows.mdx` promises capabilities exist precisely +"so skills do not need to hard-code the command surface"; they had to. + +Now 21 capabilities cover the whole agent surface, and each declares `mutating` +and `requires_confirmation` so an agent can reason about consent without +pattern-matching command names (`tag.publish` is the only one gated on an +explicit confirmation flag). Covered by +`tests/agent_ergonomics_tests.rs`. + +Still open: the list is hand-maintained and can drift again. Deriving it from +the command registry would close that for good. + +**P1-8 — `AGENTS.md` and `CLAUDE.md` are maintained duplicates.** +The diff is two lines. This will drift. Make `AGENTS.md` canonical and reduce +`CLAUDE.md` to an `@AGENTS.md` import plus the Claude-specific skill-invocation +line — the pattern the ecosystem settled on. + +**P1-9 — No skills are exposed at `.agents/skills/`.** +`.agents/plugins/marketplace.json` covers Codex plugin installation, but the +cross-tool discovery path that Copilot CLI and others read is `.agents/skills/`. +Exposing the four skills there (symlink, or move the canonical copy there and +point both plugin manifests at it) makes them portable to every agent that +implements the spec, at zero maintenance cost. + +**P1-10 — Skill frontmatter omits the spec's optional security fields.** +All four `SKILL.md` files carry only `name` and `description`. The spec now +defines `allowed-tools`, `license`, `metadata` and `compatibility`. Adding +`allowed-tools` to each skill pre-authorizes exactly what the skill needs in +security-conscious runtimes, and `metadata` gives you a version handle for the +skills independent of the CLI version. + +**P1-11 — Only two agents are addressed; the instruction file is the cheap part.** +There is no `.github/copilot-instructions.md`, no `GEMINI.md`, no Cursor rules. +Given `AGENTS.md` already exists, these are thin pointer files. Worth doing for +Copilot specifically — it is the widest-deployed agent that does not read +`AGENTS.md` natively. + +**P1-12 — No tests cover the agent plugin surface.** +`CLAUDE.md` explicitly asks for "public-seam tests for machine output, exit codes, +Git behavior, hook protocol, and **plugin manifests**." `tests/agent_cli_tests.rs` +covers the first four well (21 tests); nothing validates the marketplace JSON, +plugin manifests, or SKILL.md frontmatter against their schemas. A manifest typo +currently ships silently. Add a test that parses all six JSON/YAML manifests and +asserts every `SKILL.md` frontmatter uses only spec-allowed keys. + +**P2-13 — Generated CI template is dated and slow.** +`hooks.rs:31-36` pins `actions/checkout@v4` (v5 is current) and installs via +`cargo install committy --locked` on every PR — a multi-minute Rust build per run +with no caching and no toolchain pin. Prefer downloading the release binary from +`install.sh`, or add `Swatinem/rust-cache`. The same `checkout@v4` pin appears in +all three of this repo's own workflows. + +**P2-14 — `$committy-commit` syntax leaks Codex convention into shared skills.** +`committy-release/SKILL.md` closes with "Use `$committy-commit`". Claude Code +invokes these as `/committy:committy-commit` (as `CLAUDE.md` itself documents). +Since the `SKILL.md` body is shared across both runtimes, reference sibling skills +by bare name and let each runtime resolve it. + +**P2-15 — The local `block-dangerous-git.sh` guardrail is easily evaded and +over-blocks.** `.claude/hooks/block-dangerous-git.sh` greps the raw command string +for nine substrings. It matches the *text* of a command, not its effect — writing +this very report through a heredoc was blocked because the prose names the push +command. It misses trivial variants (double-space, `env X=1` prefix, `git -C .`), +and it blocks the push-with-upstream form that this repository's own agent +workflow instructions require. It also assumes `jq` is installed with no fallback. +Treat it as a speed bump, not a control: the real enforcement is the `pre-push` +hook, which is policy-engine backed. + +### 3.3 Removals + +| Remove | Where | Why | +|---|---|---| +| `--ai-diff-lines-per-file` | `group_commit.rs:143-147`, `ai-flags.mdx` | Never read; pure surface area (or implement under P0-3) | +| `LlmError::_Timeout` | `src/ai/mod.rs:12` | Dead variant; timeouts surface as `RequestFailed` | +| Commented-out `LlmProvider` enum | `src/ai/mod.rs:30-35` | Dead code; provider is a `&str` match at the call site | +| Duplicated body of `CLAUDE.md` | `CLAUDE.md` | Replace with `@AGENTS.md` import (P1-8) | +| `agents/openai.yaml` × 4 | `plugins/committy/skills/*/agents/` | Candidate only — verify Codex still reads these; if the marketplace entry supplies display metadata, they are redundant per-vendor files on the least portable layer | +| Empty roadmap page | `docs/src/content/docs/project/roadmap.mdx` | Frontmatter only, no body — publishes an empty page | + +### 3.4 On building an MCP server + +An earlier revision of this report said flatly "do not build one". That was +argued against eager-loading MCP, which the protocol has since moved off: the +2026-07-28 spec makes the core stateless, drops sessions, and puts progressive +discovery and tool search on the roadmap. The headline benchmarks against MCP — +35× token cost, reliability falling to 72% on hard tasks — measure the old +full-bundle design; progressive disclosure reportedly recovers 60–85% of that. +So the blanket recommendation was wrong and is withdrawn. + +What remains true is the ordering. The observed agent struggle traced to three +mechanical CLI faults (§3.5), not to the CLI *being* a CLI, and all three were +fixable in an afternoon. An MCP server would have fixed them incidentally, by +deleting argv from the problem, while adding a server to build, distribute, and +keep in version lockstep with the binary — and it would not help the Git hook and +CI path at all, which must stay CLI and is where Committy's actual enforcement +lives. + +Revisit if agents still struggle now that §3.5 is fixed. If built, shape it as +roughly six stateless tools mapping to capability groups (`policy.discover`, +`commit.preview`/`apply`, `branch.preview`/`apply`, `lint`) rather than eighteen +tools mirroring subcommands, and have `policy.discover` serve the existing +`schema --output json` payload instead of re-describing the surface. + +Still not recommended: **an LLM path on the single-commit `commit` command.** The +calling agent already has the diff in context and writes better messages than a +256-token side-channel call. Committy's differentiated value is *validating* the +agent's message, not competing with it. + +### 3.5 CLI ergonomics — diagnosed, fixed, and under test + +Reproduced against v1.9.1 and now covered by `tests/agent_ergonomics_tests.rs`: + +| # | Fault | Fix | +|---|---|---| +| E-1 | `--non-interactive`, `-q` were root-only, so `committy schema --non-interactive …` — the form agents write — was rejected by clap | `global = true` on both | +| E-2 | Argument-parse rejections wrote nothing to stdout and prose to stderr, even with `--output json` | `invalid_usage` envelope on stdout; text mode unchanged | +| E-3 | `COMMITTY_NONINTERACTIVE=1` makes placement irrelevant but appeared in no agent-facing doc | documented in `AGENTS.md`, `CLAUDE.md`, `agent-workflows.mdx` | + +`--verbose` was deliberately left root-only: `-v` is `branch --validate` and +`--verbose` belongs to `config validate|show`, so promoting it would silently +steal both shorts. A test pins that trade-off. + +--- + +## 4. Suggested order of work + +0. ~~**P1-7**, **E-1**, **E-2**, **E-3**~~ — done: capabilities expanded with + consent metadata, global flag placement, `invalid_usage` envelope, env var + documented. See §3.5. +1. ~~**P0-1, P0-2, P0-3**, **P1-5**, **P1-6**~~ — done: Ollama functions, + the safe AI path carries redacted shape, dead flag and docs corrected. + **P0-4 was withdrawn as a false finding.** +2. **P1-8, P1-9, P1-10** — cross-tool portability (`AGENTS.md` canonical, + `.agents/skills/`, spec-complete frontmatter). +3. **P1-12** — manifest and frontmatter tests so the step above cannot regress. +4. **P1-11, P2-13, P2-14, P2-15** — reach, CI cost, consistency, guardrail honesty. + +## 5. Sources + +- [AGENTS.md Field Guide, 2026 edition](https://www.iuriio.com/blog/posts/2026/05/agents-md-field-guide-2026) +- [AGENTS.md Specification](https://asdlc.io/practices/agents-md-spec/) +- [Agent Skills — Specification](https://agentskills.io/specification) +- [SKILL.md YAML frontmatter reference](https://github.com/shalomb/agent-skills/blob/main/docs/reference/yaml-frontmatter.md) +- [Claude Code plugins reference](https://code.claude.com/docs/en/plugins-reference) +- [Codex CLI plugin marketplace](https://codex.danielvaughan.com/2026/04/24/codex-cli-plugin-marketplace-building-distributing-extending/) +- [OpenAI's Codex gets plugins](https://thenewstack.io/openais-codex-gets-plugins/) +- [Ollama structured outputs](https://docs.ollama.com/capabilities/structured-outputs) +- [Ollama /api/chat reference](https://docs.ollama.com/api/chat) +- [MCP roadmap: progressive discovery and agent auth](https://www.developersdigest.tech/blog/mcp-roadmap-progressive-discovery-agent-auth) +- [Progressive tool loading is the new MCP context pattern](https://usewire.io/blog/progressive-tool-loading-mcp-context-pattern/) +- [MCP context bloat fix 2026: tool search, code mode, progressive disclosure](https://mcp.directory/blog/mcp-context-bloat-fix-2026-tool-search-code-mode-progressive-disclosure) +- [MCP servers use 35x more tokens than CLI tools](https://www.mindstudio.ai/blog/mcp-servers-35x-more-tokens-cli-tools-reliability-benchmark) +- [MCP vs CLI for AI agents](https://www.firecrawl.dev/blog/mcp-vs-cli) +- [CLI vs MCP: should your agent call tools or run commands?](https://blog.mcpservers.org/posts/cli-vs-mcp) diff --git a/docs/package-lock.json b/docs/package-lock.json index b2872ad..c8c2029 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1,255 +1,384 @@ { "name": "committy-docs", - "version": "0.0.1", + "version": "0.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "committy-docs", - "version": "0.0.1", + "version": "0.2.1", "dependencies": { - "@astrojs/starlight": "0.32.1", - "astro": "5.3.1", - "sharp": "0.33.5" + "@astrojs/starlight": "^0.42.4", + "astro": "^7.3.5", + "sharp": "^0.35.5" } }, - "node_modules/@astrojs/compiler": { - "version": "2.10.3", - "license": "MIT" + "node_modules/@astrojs/compiler-binding": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding/-/compiler-binding-0.5.1.tgz", + "integrity": "sha512-oczyolLUl9mNrtuBuNjuvn5thpHrFLXAHAWGTRg0JDHQ8cGVsVeFekOu2uSoQGgainJIi+A3/9m0DyYwz3Nlnw==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@astrojs/compiler-binding-android-arm64": "0.5.1", + "@astrojs/compiler-binding-darwin-arm64": "0.5.1", + "@astrojs/compiler-binding-darwin-x64": "0.5.1", + "@astrojs/compiler-binding-linux-arm64-gnu": "0.5.1", + "@astrojs/compiler-binding-linux-arm64-musl": "0.5.1", + "@astrojs/compiler-binding-linux-x64-gnu": "0.5.1", + "@astrojs/compiler-binding-linux-x64-musl": "0.5.1", + "@astrojs/compiler-binding-wasm32-wasi": "0.5.1", + "@astrojs/compiler-binding-win32-arm64-msvc": "0.5.1", + "@astrojs/compiler-binding-win32-x64-msvc": "0.5.1" + } + }, + "node_modules/@astrojs/compiler-binding-android-arm64": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-android-arm64/-/compiler-binding-android-arm64-0.5.1.tgz", + "integrity": "sha512-nMb2nMIJtcWLNH+fV75Enmi+jp4tIOYOfsv4XtpgdOWkqc9P4uSSdK5u1xb340AzrXo5V18SxLl/nc3vcqtamQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@astrojs/internal-helpers": { + "node_modules/@astrojs/compiler-binding-darwin-arm64": { "version": "0.5.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-arm64/-/compiler-binding-darwin-arm64-0.5.1.tgz", + "integrity": "sha512-5k4+nZ/WRt+dcVow4z7rBuI7iv9A58h/uQ91YJe1PrTivbcCkq3u2Ynx/AZJwII4CsZawBCeGpojkFjutZBhBQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@astrojs/markdown-remark": { - "version": "6.1.0", + "node_modules/@astrojs/compiler-binding-darwin-x64": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-x64/-/compiler-binding-darwin-x64-0.5.1.tgz", + "integrity": "sha512-0Buw+YST9Ff2EqQyhRMQujuFq/dijLw3zXWhsvI0dJ2pGrT38iVoxTEca2eBX/PfhlwkVWSRJEKO5gMqKHyeHg==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@astrojs/prism": "3.2.0", - "github-slugger": "^2.0.0", - "hast-util-from-html": "^2.0.3", - "hast-util-to-text": "^4.0.2", - "import-meta-resolve": "^4.1.0", - "js-yaml": "^4.1.0", - "mdast-util-definitions": "^6.0.0", - "rehype-raw": "^7.0.0", - "rehype-stringify": "^10.0.1", - "remark-gfm": "^4.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.1.1", - "remark-smartypants": "^3.0.2", - "shiki": "^1.29.1", - "smol-toml": "^1.3.1", - "unified": "^11.0.5", - "unist-util-remove-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "unist-util-visit-parents": "^6.0.1", - "vfile": "^6.0.3" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@astrojs/mdx": { - "version": "4.0.6", - "license": "MIT", - "dependencies": { - "@astrojs/markdown-remark": "6.0.2", - "@mdx-js/mdx": "^3.1.0", - "acorn": "^8.14.0", - "es-module-lexer": "^1.6.0", - "estree-util-visit": "^2.0.0", - "hast-util-to-html": "^9.0.4", - "kleur": "^4.1.5", - "rehype-raw": "^7.0.0", - "remark-gfm": "^4.0.0", - "remark-smartypants": "^3.0.2", - "source-map": "^0.7.4", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.3" - }, + "node_modules/@astrojs/compiler-binding-linux-arm64-gnu": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-gnu/-/compiler-binding-linux-arm64-gnu-0.5.1.tgz", + "integrity": "sha512-x5LRpyCoMKuu3INr18cn42YhwSOaRpp9b5Ny3eJJ/sWVeejC16Zeshu1JlvlSmEZACLeWxU5JM0RX6T6nI+YyA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.17.1 || ^20.3.0 || >=22.0.0" - }, - "peerDependencies": { - "astro": "^5.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark": { - "version": "6.0.2", + "node_modules/@astrojs/compiler-binding-linux-arm64-musl": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-musl/-/compiler-binding-linux-arm64-musl-0.5.1.tgz", + "integrity": "sha512-mL7tQEuDk3rGfaKmrO63nw6WBy1TPVioQGmVyjcAr+C+a/k3SVMYBytNSMqH9mZPT/Swj30JaZ2N842Z1NnOEQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@astrojs/prism": "3.2.0", - "github-slugger": "^2.0.0", - "hast-util-from-html": "^2.0.3", - "hast-util-to-text": "^4.0.2", - "import-meta-resolve": "^4.1.0", - "js-yaml": "^4.1.0", - "mdast-util-definitions": "^6.0.0", - "rehype-raw": "^7.0.0", - "rehype-stringify": "^10.0.1", - "remark-gfm": "^4.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.1.1", - "remark-smartypants": "^3.0.2", - "shiki": "^1.26.2", - "unified": "^11.0.5", - "unist-util-remove-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "unist-util-visit-parents": "^6.0.1", - "vfile": "^6.0.3" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki": { - "version": "1.29.1", + "node_modules/@astrojs/compiler-binding-linux-x64-gnu": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-gnu/-/compiler-binding-linux-x64-gnu-0.5.1.tgz", + "integrity": "sha512-uLGc9jtxwiDe4cQyhNPQim4ECiB/YzbWlXssfLAv8r374kArfTwBHjwR2+j+XVyF71yCx5mF9YNRclRqdNBgeg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@shikijs/core": "1.29.1", - "@shikijs/engine-javascript": "1.29.1", - "@shikijs/engine-oniguruma": "1.29.1", - "@shikijs/langs": "1.29.1", - "@shikijs/themes": "1.29.1", - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki/node_modules/@shikijs/core": { - "version": "1.29.1", + "node_modules/@astrojs/compiler-binding-linux-x64-musl": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-musl/-/compiler-binding-linux-x64-musl-0.5.1.tgz", + "integrity": "sha512-G0cywinaQUc8v1MrytvYKUX7svTDMmYtOCZfxjwKPbJi59zIqM+Ll0rWgO0GQWLZH3XoPYo19ZwlP3Xt9ep9YA==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@shikijs/engine-javascript": "1.29.1", - "@shikijs/engine-oniguruma": "1.29.1", - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.4" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki/node_modules/@shikijs/engine-javascript": { - "version": "1.29.1", + "node_modules/@astrojs/compiler-binding-wasm32-wasi": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-wasm32-wasi/-/compiler-binding-wasm32-wasi-0.5.1.tgz", + "integrity": "sha512-yMhLL8asmLydoAdnEZxAw6eLfNDs3YJl6jqn0pjPt6zrb2U5qIDLnii4/twThpIFODKkQ6YDpBRTOz/vSfZCxw==", + "cpu": [ + "wasm32" + ], "license": "MIT", + "optional": true, "dependencies": { - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1", - "oniguruma-to-es": "^2.2.0" + "@emnapi/core": "1.11.3", + "@emnapi/runtime": "1.11.3", + "@napi-rs/wasm-runtime": "^1.2.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-win32-arm64-msvc": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-arm64-msvc/-/compiler-binding-win32-arm64-msvc-0.5.1.tgz", + "integrity": "sha512-aClIG6poFt3Wf/bZykrEAX2nAnKvBHW6RXiKUa69QjBtWH0/FOlOAjnFcxRNlhej2AMWNYFx0o+LGjwMcH8P7Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-win32-x64-msvc": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-x64-msvc/-/compiler-binding-win32-x64-msvc-0.5.1.tgz", + "integrity": "sha512-/E6uBCagY70YpWqssBC+/FuniNSloeQPRgj7l3LDWiqjxIyTLnIgX1sTt18FU7jrigetpSnC9cHAM6b6IxSgPw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki/node_modules/@shikijs/engine-oniguruma": { - "version": "1.29.1", + "node_modules/@astrojs/compiler-rs": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-rs/-/compiler-rs-0.5.1.tgz", + "integrity": "sha512-1k1acnX7DepIiea9U9/Y+d1hdAFfylu1ZQQkkYgRSwBOBAsXInolyZuh/2tax+83vu/A+hOYP6P3+265RvMTvw==", "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1" + "@astrojs/compiler-binding": "0.5.1" + }, + "engines": { + "node": ">=22.12.0" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki/node_modules/@shikijs/langs": { - "version": "1.29.1", + "node_modules/@astrojs/internal-helpers": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.11.0.tgz", + "integrity": "sha512-3rzxJ+xbo0+8YyqOzLziIN32wmsHdCjEVz2sGOpRxJ+Ben/KiLph4ItxBy1abEL+E8fkRzqjg0rfXmaHJGw9JA==", "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.1" + "@types/hast": "^3.0.4", + "@types/mdast": "^4.0.4", + "js-yaml": "^4.3.0", + "picomatch": "^4.0.4", + "retext-smartypants": "^6.2.0", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "unified": "^11.0.5" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki/node_modules/@shikijs/themes": { - "version": "1.29.1", + "node_modules/@astrojs/markdown-satteri": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-satteri/-/markdown-satteri-0.4.2.tgz", + "integrity": "sha512-g4QehXnB/MzxQWnSf17NciFInMqEJy9948p8cc7zGg/r7995m0Ln5PMr3Xlnj24iey1LchaoZ3SzTOa3D4P/3w==", "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.1" + "@astrojs/internal-helpers": "0.11.0", + "@astrojs/prism": "4.0.2", + "github-slugger": "^2.0.0", + "satteri": "^0.10.3" } }, - "node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark/node_modules/shiki/node_modules/@shikijs/types": { - "version": "1.29.1", + "node_modules/@astrojs/mdx": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-8.0.2.tgz", + "integrity": "sha512-WedIVP2Bu8fXSzxb3ZYZ8xDrWWjf2SRKCneh52Llhoa+O3lcpHPhoZVP3FerlivUaH/0LJz2yVaCNvMdg0u8Bw==", "license": "MIT", "dependencies": { - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" + "@astrojs/internal-helpers": "0.11.0", + "@astrojs/markdown-satteri": "0.4.2", + "es-module-lexer": "^2.0.0" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "@astrojs/markdown-remark": "^7.3.0", + "@astrojs/markdown-satteri": "^0.4.0", + "astro": "^7.2.10" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-remark": { + "optional": true + } } }, "node_modules/@astrojs/prism": { - "version": "3.2.0", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.2.tgz", + "integrity": "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==", "license": "MIT", "dependencies": { - "prismjs": "^1.29.0" + "prismjs": "^1.30.0" }, "engines": { - "node": "^18.17.1 || ^20.3.0 || >=22.0.0" + "node": ">=22.12.0" } }, "node_modules/@astrojs/sitemap": { - "version": "3.2.1", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.4.tgz", + "integrity": "sha512-LbKNC24bdUWcQf/pThB6qLlSqHojxGjZDURIzFocY8rlWnAn2t74nnhnK6S5x0NHriHoAduLEpVjRykmeGiVvA==", "license": "MIT", "dependencies": { - "sitemap": "^8.0.0", - "stream-replace-string": "^2.0.0", - "zod": "^3.23.8" + "sitemap": "^9.0.0", + "zod": "^4.3.6" } }, "node_modules/@astrojs/starlight": { - "version": "0.32.1", + "version": "0.42.4", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.42.4.tgz", + "integrity": "sha512-EsQsqGJ1uTBQcIMojvLqqg4Ku9idwi6PSNuppIOoYmvVP18xjoUkijMdKoDkhmmwKdhbB4ctLhGjPRJzxaq+Jw==", "license": "MIT", "dependencies": { - "@astrojs/mdx": "^4.0.5", - "@astrojs/sitemap": "^3.2.1", + "@astrojs/markdown-satteri": "^0.4.0", + "@astrojs/mdx": "^8.0.0", + "@astrojs/sitemap": "^3.7.3", "@pagefind/default-ui": "^1.3.0", - "@types/hast": "^3.0.4", + "@types/hast": "^3.0.5", "@types/js-yaml": "^4.0.9", "@types/mdast": "^4.0.4", - "astro-expressive-code": "^0.40.0", + "astro-expressive-code": "^0.44.0", "bcp-47": "^2.1.0", - "hast-util-from-html": "^2.0.1", - "hast-util-select": "^6.0.2", - "hast-util-to-string": "^3.0.0", - "hastscript": "^9.0.0", - "i18next": "^23.11.5", - "js-yaml": "^4.1.0", + "hast-util-format": "^1.1.0", + "hast-util-select": "^6.0.4", + "hast-util-to-html": "^9.0.5", + "hast-util-to-string": "^3.0.1", + "hastscript": "^9.0.1", + "i18next": "^26.0.7", + "js-yaml": "^4.1.1", "klona": "^2.0.6", - "mdast-util-directive": "^3.0.0", - "mdast-util-to-markdown": "^2.1.0", + "magic-string": "^1.2.3", + "mdast-util-directive": "^3.1.0", + "mdast-util-to-markdown": "^2.1.2", "mdast-util-to-string": "^4.0.0", - "pagefind": "^1.3.0", - "rehype": "^13.0.1", - "rehype-format": "^5.0.0", - "remark-directive": "^3.0.0", + "pagefind": "^1.5.2", + "remark-directive": "^4.0.0", + "satteri": "^0.10.3", + "ultrahtml": "^1.6.0", "unified": "^11.0.5", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.2" + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.3" }, "peerDependencies": { - "astro": "^5.1.5" + "@astrojs/markdown-remark": "^7.3.0", + "astro": "^7.2.10" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-remark": { + "optional": true + } } }, "node_modules/@astrojs/telemetry": { - "version": "3.2.0", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.3.tgz", + "integrity": "sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ==", "license": "MIT", "dependencies": { - "ci-info": "^4.1.0", - "debug": "^4.3.7", - "dlv": "^1.1.3", + "ci-info": "^4.4.0", "dset": "^3.1.4", - "is-docker": "^3.0.0", - "is-wsl": "^3.1.0", - "which-pm-runs": "^1.1.0" + "is-docker": "^4.0.0", + "package-manager-detector": "^1.6.0" }, "engines": { - "node": "^18.17.1 || ^20.3.0 || >=22.0.0" + "node": "18.20.8 || ^20.3.0 || >=22.0.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.5", + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.5" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -258,36 +387,23 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.26.0", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { - "version": "7.26.5", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@ctrl/tinycolor": { - "version": "4.1.0", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", + "node_modules/@bruits/satteri-darwin-arm64": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-arm64/-/satteri-darwin-arm64-0.10.5.tgz", + "integrity": "sha512-27KTVl4TJkVahMy/ohyA7qd4938G5UNneFUz/PsScYfpIhj0IVAS23mpcJXdPF44sa6nva198lmV/cKIb2YPyA==", "cpu": [ "arm64" ], @@ -295,217 +411,308 @@ "optional": true, "os": [ "darwin" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@expressive-code/core": { - "version": "0.40.1", + "node_modules/@bruits/satteri-darwin-x64": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-x64/-/satteri-darwin-x64-0.10.5.tgz", + "integrity": "sha512-IjnLe3nKspq6qaeqGgjT7MT8VrTV74yWRlaag7ZdNsI8TDAYZ0iPxMCo+9KQZHUk5EyVB+reBI/PFWL5KuFw9Q==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@ctrl/tinycolor": "^4.0.4", - "hast-util-select": "^6.0.2", - "hast-util-to-html": "^9.0.1", - "hast-util-to-text": "^4.0.1", - "hastscript": "^9.0.0", - "postcss": "^8.4.38", - "postcss-nested": "^6.0.1", - "unist-util-visit": "^5.0.0", - "unist-util-visit-parents": "^6.0.1" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@expressive-code/core/node_modules/postcss": { - "version": "8.5.1", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "node_modules/@bruits/satteri-linux-arm64-gnu": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-gnu/-/satteri-linux-arm64-gnu-0.10.5.tgz", + "integrity": "sha512-glkYXZCJywjP13v67eAyAMSJdF+ncvEbYvgi/wOtffL9tQ27lr/zsyzUfgs+ovjJ9d8JNQKiXeiArJcX8PJL9w==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" ], "license": "MIT", - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@expressive-code/plugin-frames": { - "version": "0.40.1", + "node_modules/@bruits/satteri-linux-arm64-musl": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-musl/-/satteri-linux-arm64-musl-0.10.5.tgz", + "integrity": "sha512-yWdgG1g17Nh2QyGVlFUxGRa3FEFwiMcpZEyMNWkbM3deC94cmVc+/i9OuyFpdKuWo3GkgoCtYVOoxk1uCnCZIA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@expressive-code/core": "^0.40.1" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@expressive-code/plugin-shiki": { - "version": "0.40.1", + "node_modules/@bruits/satteri-linux-x64-gnu": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-gnu/-/satteri-linux-x64-gnu-0.10.5.tgz", + "integrity": "sha512-FVaLoPT1fBgGl0J+AYebyyXJYBachGl8Oyyrf1lye4RTqCB4S0Gwkj1uM9RJyThUOvx5VUmAT1CnNh1SFHA+kw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@expressive-code/core": "^0.40.1", - "shiki": "^1.26.1" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki": { - "version": "1.29.1", + "node_modules/@bruits/satteri-linux-x64-musl": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-musl/-/satteri-linux-x64-musl-0.10.5.tgz", + "integrity": "sha512-EHpVAx2bqW3GINHTKkljtxVfQmVDGWIuwOYOP5YghTj+0PkBa2o8oKPRtQ9Kbsr1Fye8jtUcDjhwj2jMNugZKg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@shikijs/core": "1.29.1", - "@shikijs/engine-javascript": "1.29.1", - "@shikijs/engine-oniguruma": "1.29.1", - "@shikijs/langs": "1.29.1", - "@shikijs/themes": "1.29.1", - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki/node_modules/@shikijs/core": { - "version": "1.29.1", + "node_modules/@bruits/satteri-wasm32-wasi": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-wasm32-wasi/-/satteri-wasm32-wasi-0.10.5.tgz", + "integrity": "sha512-ypz8c/Zmipxp4IoeDa228Gstv6TLzVmNs3yC6wKCoNSOjx1iwpgzu87Y3hTkXFdwChVGU85qeUDuOIarGUZQLw==", + "cpu": [ + "wasm32" + ], "license": "MIT", + "optional": true, "dependencies": { - "@shikijs/engine-javascript": "1.29.1", - "@shikijs/engine-oniguruma": "1.29.1", - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.4" + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.2.3" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki/node_modules/@shikijs/engine-javascript": { - "version": "1.29.1", + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", "license": "MIT", + "optional": true, "dependencies": { - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1", - "oniguruma-to-es": "^2.2.0" + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" } }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki/node_modules/@shikijs/engine-oniguruma": { - "version": "1.29.1", + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "license": "MIT", + "optional": true, "dependencies": { - "@shikijs/types": "1.29.1", - "@shikijs/vscode-textmate": "^10.0.1" + "tslib": "^2.4.0" } }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki/node_modules/@shikijs/langs": { - "version": "1.29.1", + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "license": "MIT", + "optional": true, "dependencies": { - "@shikijs/types": "1.29.1" + "tslib": "^2.4.0" } }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki/node_modules/@shikijs/themes": { - "version": "1.29.1", + "node_modules/@bruits/satteri-win32-arm64-msvc": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-arm64-msvc/-/satteri-win32-arm64-msvc-0.10.5.tgz", + "integrity": "sha512-siTV88nb0LRqNpkL2gXboqCwVdq95sLtzMHS1/3eONV2gLbB3NAK46wmSMvCO/yquBvI2lvaFIfd8P12ecsxBw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.1" - } + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@bruits/satteri-win32-x64-msvc": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-x64-msvc/-/satteri-win32-x64-msvc-0.10.5.tgz", + "integrity": "sha512-C3IfPvfvMXmlzBxaMPKFS1XiuV9pu2mC7YqkPk7PSvTgPZ8gbdASIpHpztDLvTTQjqZ0z1Ol8tK5X+V6XXC0wQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@expressive-code/plugin-shiki/node_modules/shiki/node_modules/@shikijs/types": { - "version": "1.29.1", + "node_modules/@capsizecss/unpack": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.1.tgz", + "integrity": "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==", "license": "MIT", "dependencies": { - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" + "fontkitten": "^1.0.3" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@expressive-code/plugin-text-markers": { - "version": "0.40.1", + "node_modules/@clack/core": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.5.1.tgz", + "integrity": "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==", + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.8.1.tgz", + "integrity": "sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==", "license": "MIT", "dependencies": { - "@expressive-code/core": "^0.40.1" + "@clack/core": "1.5.1", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" } }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", + "node_modules/@ctrl/tinycolor": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.1.tgz", + "integrity": "sha512-Vh5uy9Y1JBMId6J1f68TMXnZrTCvNdo9Fu2k0d1YnJngR+sM5kWTKl441c/3TFs+cG0NWOjRWgQ/iaWhbYx+7A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ - "arm64" + "ppc64" ], - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ - "darwin" + "aix" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ - "arm64" + "arm" ], - "license": "LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ - "darwin" + "android" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "license": "MIT" - }, - "node_modules/@mdx-js/mdx": { - "version": "3.1.0", + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdx": "^2.0.0", - "collapse-white-space": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-util-scope": "^1.0.0", - "estree-walker": "^3.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "markdown-extensions": "^2.0.0", - "recma-build-jsx": "^1.0.0", - "recma-jsx": "^1.0.0", - "recma-stringify": "^1.0.0", - "rehype-recma": "^1.0.0", - "remark-mdx": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "source-map": "^0.7.0", - "unified": "^11.0.0", - "unist-util-position-from-estree": "^2.0.0", - "unist-util-stringify-position": "^4.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@oslojs/encoding": { - "version": "1.1.0", - "license": "MIT" + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@pagefind/darwin-arm64": { - "version": "1.3.0", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], @@ -513,2326 +720,3209 @@ "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@pagefind/default-ui": { - "version": "1.3.0", - "license": "MIT" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.4", + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "license": "MIT" - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.31.0", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], "license": "MIT", "optional": true, "os": [ - "darwin" - ] + "freebsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@shikijs/core": { - "version": "1.29.2", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@shikijs/engine-javascript": "1.29.2", - "@shikijs/engine-oniguruma": "1.29.2", - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.4" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@shikijs/engine-javascript": { - "version": "1.29.2", + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", - "oniguruma-to-es": "^2.2.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "1.29.2", + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@shikijs/langs": { - "version": "1.29.2", + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@shikijs/themes": { - "version": "1.29.2", + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@shikijs/types": { - "version": "1.29.2", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], "license": "MIT", - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.1", - "license": "MIT" - }, - "node_modules/@types/acorn": { - "version": "4.0.6", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], "license": "MIT", - "dependencies": { - "@types/estree": "*" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "license": "MIT" - }, - "node_modules/@types/debug": { - "version": "4.1.12", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], "license": "MIT", - "dependencies": { - "@types/ms": "*" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/estree": { - "version": "1.0.6", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], "license": "MIT", - "dependencies": { - "@types/estree": "*" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/hast": { - "version": "3.0.4", + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@types/unist": "*" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/js-yaml": { - "version": "4.0.9", - "license": "MIT" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@types/unist": "*" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/mdx": { - "version": "2.0.13", - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "license": "MIT" + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@types/nlcst": { - "version": "2.0.3", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@types/unist": "*" + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/node": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", - "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "undici-types": "~7.10.0" + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/sax": { - "version": "1.2.7", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@types/node": "*" + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@types/unist": { - "version": "3.0.3", - "license": "MIT" + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.1", - "license": "ISC" + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/acorn": { - "version": "8.14.0", + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.4.0" + "node": ">=18" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "license": "ISC", + "node_modules/@expressive-code/core": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.44.2.tgz", + "integrity": "sha512-Yw5KonQCD1HoOO2P89y4wifqS9qrJcGVY0LkVMUsVZjhi++bhz4WwQ37inenKFNOEVo50P7cjoVsCdaVnUbq8w==", + "license": "MIT", "dependencies": { - "string-width": "^4.1.0" + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "hast-util-to-text": "^4.0.1", + "hastscript": "^9.0.0", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" } }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", + "node_modules/@expressive-code/plugin-frames": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.44.2.tgz", + "integrity": "sha512-gVLm6TQMNIRWIaM7o+mCtxkGNjLZY5/2faSTLoXeo9wv/Pkj/0QAtZyXPVPrRsPARqiipATNFmfvQFLJudxPHQ==", "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "@expressive-code/core": "^0.44.2" } }, - "node_modules/ansi-align/node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "license": "MIT" + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.44.2.tgz", + "integrity": "sha512-ebWIvCDzXvjyD8rH6T6DfytYJw1GoArLR4nusIAE4VxXrCQf3d+owytiv010KU/dUg+SRLZskDn0N+HaFXeuSg==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.44.2", + "shiki": "^4.0.2" + } }, - "node_modules/ansi-align/node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.44.2.tgz", + "integrity": "sha512-SNI7EgUSiADqbqUzzMbj1ZeC/r2pa5eg3KyFOGAd3a7EsIA4qzjXQLp+ODWQtHVDKFCdGs4J3ovSJUqvKIK96g==", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "@expressive-code/core": "^0.44.2" } }, - "node_modules/ansi-align/node_modules/string-width/node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "license": "MIT", + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.4" } }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "license": "MIT", + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.4" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "license": "ISC", + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@img/sharp-wasm32": "0.35.5" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "license": "MIT", - "engines": { - "node": ">=8.6" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://opencollective.com/libvips" } }, - "node_modules/arg": { - "version": "5.0.2", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.2", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/array-iterate": { - "version": "2.0.1", - "license": "MIT", + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/astring": { - "version": "1.9.0", - "license": "MIT", - "bin": { - "astring": "bin/astring" + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro": { - "version": "5.3.1", - "license": "MIT", - "dependencies": { - "@astrojs/compiler": "^2.10.3", - "@astrojs/internal-helpers": "0.5.1", - "@astrojs/markdown-remark": "6.1.0", - "@astrojs/telemetry": "3.2.0", - "@oslojs/encoding": "^1.1.0", - "@rollup/pluginutils": "^5.1.4", - "@types/cookie": "^0.6.0", - "acorn": "^8.14.0", - "aria-query": "^5.3.2", - "axobject-query": "^4.1.0", - "boxen": "8.0.1", - "ci-info": "^4.1.0", - "clsx": "^2.1.1", - "common-ancestor-path": "^1.0.1", - "cookie": "^0.7.2", - "cssesc": "^3.0.0", - "debug": "^4.4.0", - "deterministic-object-hash": "^2.0.2", - "devalue": "^5.1.1", - "diff": "^5.2.0", - "dlv": "^1.1.3", - "dset": "^3.1.4", - "es-module-lexer": "^1.6.0", - "esbuild": "^0.24.2", - "estree-walker": "^3.0.3", - "flattie": "^1.1.1", - "github-slugger": "^2.0.0", - "html-escaper": "3.0.3", - "http-cache-semantics": "^4.1.1", - "js-yaml": "^4.1.0", - "kleur": "^4.1.5", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "mrmime": "^2.0.0", - "neotraverse": "^0.6.18", - "p-limit": "^6.2.0", - "p-queue": "^8.1.0", - "picomatch": "^4.0.2", - "preferred-pm": "^4.1.1", - "prompts": "^2.4.2", - "rehype": "^13.0.2", - "semver": "^7.7.1", - "shiki": "^1.29.2", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.12", - "tsconfck": "^3.1.4", - "ultrahtml": "^1.5.3", - "unist-util-visit": "^5.0.0", - "unstorage": "^1.14.4", - "vfile": "^6.0.3", - "vite": "^6.0.11", - "vitefu": "^1.0.5", - "which-pm": "^3.0.1", - "xxhash-wasm": "^1.1.0", - "yargs-parser": "^21.1.1", - "yocto-spinner": "^0.2.0", - "zod": "^3.24.1", - "zod-to-json-schema": "^3.24.1", - "zod-to-ts": "^1.2.0" - }, - "bin": { - "astro": "astro.js" - }, - "engines": { - "node": "^18.17.1 || ^20.3.0 || >=22.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0" - }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "type": "opencollective", - "url": "https://opencollective.com/astrodotbuild" - }, - "optionalDependencies": { - "sharp": "^0.33.3" + "url": "https://opencollective.com/libvips" } }, - "node_modules/astro-expressive-code": { - "version": "0.40.1", - "license": "MIT", - "dependencies": { - "rehype-expressive-code": "^0.40.1" - }, - "peerDependencies": { - "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0" + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/axobject-query": { - "version": "4.1.0", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/bail": { - "version": "2.0.2", - "license": "MIT", + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/base-64": { - "version": "1.0.0", - "license": "MIT" + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/bcp-47": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0" - }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/bcp-47-match": { - "version": "2.0.3", - "license": "MIT", + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "license": "MIT", + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.4" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "license": "ISC" - }, - "node_modules/boxen": { - "version": "8.0.1", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" - }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" + "url": "https://opencollective.com/libvips" }, - "engines": { - "node": ">=8" + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.4" } }, - "node_modules/camelcase": { - "version": "8.0.0", - "license": "MIT", + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.4" } }, - "node_modules/ccount": { - "version": "2.0.1", - "license": "MIT", + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.4" } }, - "node_modules/chalk": { - "version": "5.4.1", - "license": "MIT", + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.4" } }, - "node_modules/character-entities": { - "version": "2.0.2", - "license": "MIT", + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.4" } }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "license": "MIT", + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" } }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "license": "MIT", + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" } }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "license": "MIT", + "node_modules/@img/sharp-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "license": "MIT", + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "@img/sharp-wasm32": "0.35.5" }, "engines": { - "node": ">= 8.10.0" + "node": ">=20.9.0" }, "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "url": "https://opencollective.com/libvips" } }, - "node_modules/ci-info": { - "version": "4.1.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" ], - "license": "MIT", "engines": { - "node": ">=8" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "license": "MIT", + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10" + "node": "^20.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/libvips" } }, - "node_modules/clsx": { - "version": "2.1.1", - "license": "MIT", + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6" - } - }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "license": "MIT", + "node": ">=20.9.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://opencollective.com/libvips" } }, - "node_modules/color": { - "version": "4.2.3", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", + "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", "license": "MIT", + "optional": true, "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" + "@tybys/wasm-util": "^0.10.3" }, "engines": { - "node": ">=12.5.0" + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, - "node_modules/color-convert": { - "version": "2.0.1", + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@oxc-project/types": { + "version": "0.151.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.151.0.tgz", + "integrity": "sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==", "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "funding": { + "url": "https://github.com/sponsors/oxc-project" } }, - "node_modules/color-name": { - "version": "1.1.4", + "node_modules/@pagefind/darwin-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.5.2.tgz", + "integrity": "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.5.2.tgz", + "integrity": "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.5.2.tgz", + "integrity": "sha512-pm1LMnQg8N2B3n2TnjKlhaFihpz6zTiA4HiGQ6/slKO/+8K9CAU5kcjdSSPgpuk1PMuuN4hxLipUIifnrkl3Sg==", "license": "MIT" }, - "node_modules/color-string": { - "version": "1.9.1", + "node_modules/@pagefind/freebsd-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.5.2.tgz", + "integrity": "sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", + "node_modules/@pagefind/linux-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.5.2.tgz", + "integrity": "sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/common-ancestor-path": { - "version": "1.0.1", - "license": "ISC" + "node_modules/@pagefind/linux-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.5.2.tgz", + "integrity": "sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-arm64/-/windows-arm64-1.5.2.tgz", + "integrity": "sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/consola": { - "version": "3.4.0", + "node_modules/@pagefind/windows-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.5.2.tgz", + "integrity": "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg==", + "cpu": [ + "x64" + ], "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/cookie": { - "version": "0.7.2", + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.11.tgz", + "integrity": "sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==", + "cpu": [ + "arm" + ], "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.6" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/cookie-es": { - "version": "1.2.2", - "license": "MIT" - }, - "node_modules/crossws": { - "version": "0.3.1", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.11.tgz", + "integrity": "sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "uncrypto": "^0.1.3" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/css-selector-parser": { - "version": "3.0.5", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.11.tgz", + "integrity": "sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==", + "cpu": [ + "arm64" ], - "license": "MIT" - }, - "node_modules/cssesc": { - "version": "3.0.0", "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/debug": { - "version": "4.4.0", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.11.tgz", + "integrity": "sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/decode-named-character-reference": { - "version": "1.0.2", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.11.tgz", + "integrity": "sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/defu": { - "version": "6.1.4", - "license": "MIT" - }, - "node_modules/dequal": { - "version": "2.0.3", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.11.tgz", + "integrity": "sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==", + "cpu": [ + "arm" + ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/destr": { - "version": "2.0.3", - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "license": "Apache-2.0", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.11.tgz", + "integrity": "sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/deterministic-object-hash": { - "version": "2.0.2", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.11.tgz", + "integrity": "sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "base-64": "^1.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/devalue": { - "version": "5.1.1", - "license": "MIT" - }, - "node_modules/devlop": { - "version": "1.1.0", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.11.tgz", + "integrity": "sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/diff": { - "version": "5.2.0", - "license": "BSD-3-Clause", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.11.tgz", + "integrity": "sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.3.1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/direction": { - "version": "2.0.1", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.11.tgz", + "integrity": "sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], "license": "MIT", - "bin": { - "direction": "cli.js" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/dlv": { - "version": "1.1.3", - "license": "MIT" - }, - "node_modules/dset": { - "version": "3.1.4", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.11.tgz", + "integrity": "sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/emoji-regex": { - "version": "10.4.0", - "license": "MIT" + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.11.tgz", + "integrity": "sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/emoji-regex-xs": { - "version": "1.0.0", - "license": "MIT" + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.11.tgz", + "integrity": "sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/entities": { - "version": "4.5.0", - "license": "BSD-2-Clause", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.11.tgz", + "integrity": "sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/es-module-lexer": { - "version": "1.6.0", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "license": "MIT" }, - "node_modules/esast-util-from-estree": { - "version": "2.0.0", + "node_modules/@shikijs/core": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.4.3.tgz", + "integrity": "sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==", "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0" + "@shikijs/primitive": "4.4.3", + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5", + "hast-util-to-html": "^9.0.5" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=20" } }, - "node_modules/esast-util-from-js": { - "version": "2.0.1", + "node_modules/@shikijs/engine-javascript": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.4.3.tgz", + "integrity": "sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==", "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "acorn": "^8.0.0", - "esast-util-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.6" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=20" } }, - "node_modules/esbuild": { - "version": "0.24.2", - "hasInstallScript": true, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.4.3.tgz", + "integrity": "sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==", "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" - } - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.4.3.tgz", + "integrity": "sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==", "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@shikijs/types": "4.4.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=20" } }, - "node_modules/esprima": { - "version": "4.0.1", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "node_modules/@shikijs/primitive": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.4.3.tgz", + "integrity": "sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" }, "engines": { - "node": ">=4" + "node": ">=20" } }, - "node_modules/estree-util-attach-comments": { - "version": "3.0.0", + "node_modules/@shikijs/themes": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.4.3.tgz", + "integrity": "sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0" + "@shikijs/types": "4.4.3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=20" } }, - "node_modules/estree-util-build-jsx": { - "version": "3.0.1", + "node_modules/@shikijs/types": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.4.3.tgz", + "integrity": "sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==", "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-walker": "^3.0.0" + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=20" } }, - "node_modules/estree-util-is-identifier-name": { - "version": "3.0.0", + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/estree-util-scope": { - "version": "1.0.0", + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@types/ms": "*" } }, - "node_modules/estree-util-to-js": { - "version": "2.0.0", + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "astring": "^1.8.0", - "source-map": "^0.7.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@types/estree": "*" } }, - "node_modules/estree-util-visit": { - "version": "2.0.0", + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@types/unist": "*" } }, - "node_modules/estree-walker": { - "version": "3.0.3", + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0" + "@types/unist": "*" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, - "node_modules/expressive-code": { - "version": "0.40.1", + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", "license": "MIT", "dependencies": { - "@expressive-code/core": "^0.40.1", - "@expressive-code/plugin-frames": "^0.40.1", - "@expressive-code/plugin-shiki": "^0.40.1", - "@expressive-code/plugin-text-markers": "^0.40.1" + "@types/unist": "*" } }, - "node_modules/extend": { - "version": "3.0.2", - "license": "MIT" + "node_modules/@types/node": { + "version": "24.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.0.tgz", + "integrity": "sha512-zY+5tKxXdhGh1PYI0ac+7juvEu4OI6vWtVVoj5i2m42jxAY1U+zHGt6QCyOFwykdP62sM3MJ9stoYYUw5aCWew==", + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } }, - "node_modules/fdir": { - "version": "6.4.3", + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "dependencies": { + "@types/node": "*" } }, - "node_modules/fill-range": { - "version": "7.1.1", + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "license": "ISC" + }, + "node_modules/am-i-vibing": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/am-i-vibing/-/am-i-vibing-0.4.0.tgz", + "integrity": "sha512-MxT4XZL7pzLHpuvhDKdMaQHMGGkJDLluKBLsbstn+8wv9sWcFT6h+0ve9qkml95amVTZtZV83gQe2hY+ojgHLg==", "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" + "process-ancestry": "^0.1.0" + }, + "bin": { + "am-i-vibing": "dist/cli.mjs" } }, - "node_modules/find-up": { - "version": "4.1.0", - "license": "MIT", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/find-up-simple": { - "version": "1.0.0", + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=8.6" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/find-yarn-workspace-root2": { - "version": "1.2.16", - "license": "Apache-2.0", - "dependencies": { - "micromatch": "^4.0.2", - "pkg-dir": "^4.2.0" - } + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" }, - "node_modules/flattie": { - "version": "1.1.1", - "license": "MIT", - "engines": { - "node": ">=8" - } + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" }, - "node_modules/fsevents": { - "version": "2.3.3", - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.4" } }, - "node_modules/get-east-asian-width": { - "version": "1.3.0", + "node_modules/astro": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/astro/-/astro-7.3.5.tgz", + "integrity": "sha512-1p/ernaq/RuESbM5NMjWzbO6SiebMGrGcYlGjMfBhzzgM+n/X7AUmVyw8BqW2QYCdivpsHwJBcD1fqmlTZjZHg==", "license": "MIT", + "dependencies": { + "@astrojs/compiler-rs": "^0.5.0", + "@astrojs/internal-helpers": "0.11.0", + "@astrojs/markdown-satteri": "0.4.2", + "@astrojs/telemetry": "3.3.3", + "@capsizecss/unpack": "^4.0.0", + "@clack/prompts": "^1.1.0", + "@oslojs/encoding": "^1.1.0", + "am-i-vibing": "^0.4.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "ci-info": "^4.4.0", + "clsx": "^2.1.1", + "common-ancestor-path": "^2.0.0", + "cookie": "^2.0.1", + "devalue": "^5.8.1", + "diff": "^9.0.0", + "dset": "^3.1.4", + "es-module-lexer": "^2.0.0", + "esbuild": "^0.28.0", + "find-proc": "0.2.0", + "flattie": "^1.1.1", + "fontace": "~0.4.1", + "get-tsconfig": "5.0.0-beta.4", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "js-yaml": "^4.3.2", + "jsonc-parser": "^3.3.1", + "magic-string": "^1.0.0", + "magicast": "^0.5.2", + "mrmime": "^2.0.1", + "neotraverse": "^1.0.1", + "obug": "^3.0.0", + "p-limit": "^7.3.0", + "p-queue": "^9.1.0", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.4", + "shiki": "^4.0.2", + "smol-toml": "^1.8.0", + "svgo": "^4.1.0", + "tinyclip": "^1.0.1", + "tinyexec": "^1.0.4", + "tinyglobby": "^0.2.15", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.5", + "unstorage": "^1.17.5", + "verkit": "^0.4.0", + "vite": "^8.0.13", + "vitefu": "^1.1.2", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^22.0.0", + "zod": "^4.5.4" + }, + "bin": { + "astro": "bin/astro.mjs" + }, "engines": { - "node": ">=18" + "node": ">=22.12.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.35.4" + }, + "peerDependencies": { + "@astrojs/markdown-remark": "^7.3.0" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-remark": { + "optional": true + } } }, - "node_modules/github-slugger": { - "version": "2.0.0", - "license": "ISC" - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "license": "ISC", + "node_modules/astro-expressive-code": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.44.2.tgz", + "integrity": "sha512-avop7nZcRwC7quvIxVguhwEZ9Tp6IoTzKUCLwP30Fb7+er+pWQgcysP01dcuzRIJo81kLGg30q/YAI2Yb1Sdzg==", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "rehype-expressive-code": "^0.44.2", + "url-extras": "^0.1.0" }, - "engines": { - "node": ">= 6" + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta || ^7.0.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "license": "ISC" - }, - "node_modules/h3": { - "version": "1.13.1", - "license": "MIT", - "dependencies": { - "cookie-es": "^1.2.2", - "crossws": "^0.3.1", - "defu": "^6.1.4", - "destr": "^2.0.3", - "iron-webcrypto": "^1.2.1", - "ohash": "^1.1.4", - "radix3": "^1.1.2", - "ufo": "^1.5.4", - "uncrypto": "^0.1.3", - "unenv": "^1.10.0" + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" } }, - "node_modules/hast-util-embedded": { - "version": "3.0.0", + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-is-element": "^3.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-format": { - "version": "1.1.0", + "node_modules/bcp-47": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.1.tgz", + "integrity": "sha512-KLw+H/gd2p4zly1X7Yh/qziuyae5/w/QFnvTng9eZL5fvszL7Whl3MBoWF8yxL7ksUjBfOD+OxkytiqbBpG+Fw==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-minify-whitespace": "^1.0.0", - "hast-util-phrasing": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "html-whitespace-sensitive-tag-names": "^3.0.0", - "unist-util-visit-parents": "^6.0.0" + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-from-html": { + "node_modules/bcp-47-match": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "devlop": "^1.1.0", - "hast-util-from-parse5": "^8.0.0", - "parse5": "^7.0.0", - "vfile": "^6.0.0", - "vfile-message": "^4.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.2", + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^6.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-has-property": { - "version": "3.0.0", + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-is-body-ok-link": { - "version": "3.0.1", + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-is-element": { + "node_modules/character-entities-legacy": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-minify-whitespace": { - "version": "1.0.1", + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "unist-util-is": "^6.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/hast-util-phrasing": { - "version": "3.0.1", + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-has-property": "^3.0.0", - "hast-util-is-body-ok-link": "^3.0.0", - "hast-util-is-element": "^3.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" } }, - "node_modules/hast-util-raw": { - "version": "9.1.0", + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" + "engines": { + "node": ">=22" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/express" } }, - "node_modules/hast-util-select": { - "version": "6.0.3", + "node_modules/cookie-es": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", + "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "bcp-47-match": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "css-selector-parser": "^3.0.0", - "devlop": "^1.0.0", - "direction": "^2.0.0", - "hast-util-has-property": "^3.0.0", - "hast-util-to-string": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "nth-check": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "uncrypto": "^0.1.3" } }, - "node_modules/hast-util-to-estree": { - "version": "3.1.1", - "license": "MIT", + "node_modules/css-select": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", + "license": "BSD-2-Clause", "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-attach-comments": "^3.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-object": "^1.0.0", - "unist-util-position": "^5.0.0", - "zwitch": "^2.0.0" + "boolbase": "^1.0.0", + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/hast-util-to-html": { - "version": "9.0.4", + "node_modules/css-selector-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz", + "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.2", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-object": "^1.0.0", - "unist-util-position": "^5.0.0", - "vfile-message": "^4.0.0" + "node_modules/css-what": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.0", + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" + "bin": { + "cssesc": "bin/cssesc" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=4" } }, - "node_modules/hast-util-to-string": { - "version": "3.0.1", + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" + "css-tree": "~2.2.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, - "node_modules/hast-util-to-text": { - "version": "4.0.2", + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "unist-util-find-after": "^5.0.0" + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0" + "ms": "^2.1.3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/hastscript": { - "version": "9.0.0", + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0" + "character-entities": "^2.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/html-escaper": { - "version": "3.0.3", + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "license": "MIT" }, - "node_modules/html-void-elements": { - "version": "3.0.0", + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">=6" } }, - "node_modules/html-whitespace-sensitive-tag-names": { - "version": "3.0.1", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" } }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "license": "BSD-2-Clause" + "node_modules/devalue": { + "version": "5.9.4", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.4.tgz", + "integrity": "sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==", + "license": "MIT" }, - "node_modules/i18next": { - "version": "23.16.8", - "funding": [ - { - "type": "individual", - "url": "https://locize.com" - }, - { - "type": "individual", - "url": "https://locize.com/i18next.html" - }, - { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" - } - ], + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.2" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.1.0", - "license": "MIT", + "dequal": "^2.0.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/inline-style-parser": { - "version": "0.2.4", - "license": "MIT" - }, - "node_modules/iron-webcrypto": { - "version": "1.2.1", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/brc-dd" + "node_modules/diff": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, - "node_modules/is-alphabetical": { + "node_modules/direction": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", "license": "MIT", + "bin": { + "direction": "cli.js" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "license": "MIT", "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "license": "MIT" + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" }, - "node_modules/is-binary-path": { - "version": "2.1.0", + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/is-decimal": { - "version": "2.0.1", - "license": "MIT", + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/is-docker": { - "version": "3.0.0", + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "hasInstallScript": true, "license": "MIT", "bin": { - "is-docker": "cli.js" + "esbuild": "bin/esbuild" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", + "node_modules/expressive-code": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.44.2.tgz", + "integrity": "sha512-MLNghkvhyFgVW4oCD5DxBuYnnTXIf0PygzGmfT6OZeOOYWh/D4sH1j931biElkA5RG/J9wY/iuSMGAPF5vzHOg==", "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@expressive-code/core": "^0.44.2", + "@expressive-code/plugin-frames": "^0.44.2", + "@expressive-code/plugin-shiki": "^0.44.2", + "@expressive-code/plugin-text-markers": "^0.44.2" } }, - "node_modules/is-glob": { - "version": "4.0.3", + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" + "fast-string-truncated-width": "^3.0.2" } }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "dependencies": { + "fast-string-width": "^3.0.2" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, "engines": { - "node": ">=14.16" + "node": ">=12.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/is-number": { - "version": "7.0.0", + "node_modules/find-proc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/find-proc/-/find-proc-0.2.0.tgz", + "integrity": "sha512-a6h2nTrgB3g5Wrn4au9Ux4kMk7IJtyF6oA8uGn+mk3weLZssUtc1cp6meHIzzuW2o1srrBAxzkmXLbqjRkgB6w==", "license": "MIT", "engines": { - "node": ">=0.12.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/is-plain-obj": { - "version": "4.1.0", + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/is-wsl": { - "version": "3.1.0", + "node_modules/fontace": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", "license": "MIT", "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "fontkitten": "^1.0.2" } }, - "node_modules/js-yaml": { - "version": "4.1.0", + "node_modules/fontkitten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", + "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "tiny-inflate": "^1.0.3" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=20" } }, - "node_modules/kleur": { - "version": "4.1.5", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/klona": { - "version": "2.0.6", + "node_modules/get-tsconfig": { + "version": "5.0.0-beta.4", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz", + "integrity": "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==", "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, "engines": { - "node": ">= 8" + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/load-yaml-file": { - "version": "0.2.0", + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/h3": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", + "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.5", - "js-yaml": "^3.13.0", - "pify": "^4.0.1", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" + "cookie-es": "^1.2.3", + "crossws": "^0.3.5", + "defu": "^6.1.6", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" } }, - "node_modules/load-yaml-file/node_modules/js-yaml": { - "version": "3.14.1", + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/load-yaml-file/node_modules/js-yaml/node_modules/argparse": { - "version": "1.0.10", + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/locate-path": { - "version": "5.0.0", + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "@types/hast": "^3.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/longest-streak": { - "version": "3.1.0", + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/lru-cache": { - "version": "10.4.3", - "license": "ISC" - }, - "node_modules/magic-string": { - "version": "0.30.17", + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/magicast": { - "version": "0.3.5", + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/markdown-extensions": { - "version": "2.0.0", + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", "license": "MIT", - "engines": { - "node": ">=16" + "dependencies": { + "@types/hast": "^3.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/markdown-table": { - "version": "3.0.4", + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-definitions": { - "version": "6.0.0", + "node_modules/hast-util-select": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", + "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", + "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-directive": { - "version": "3.0.0", + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", + "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", - "unist-util-visit-parents": "^6.0.0" + "zwitch": "^2.0.4" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" + "@types/hast": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", + "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-gfm": { + "node_modules/hast-util-whitespace": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", "license": "MIT", "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "@types/hast": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.0.0", + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "26.4.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.4.2.tgz", + "integrity": "sha512-RX+R0VLg13IbvRuJSxnqykUFS9vQZTl8wYpWPCIUDWVrSGjsQywB5Y+pjzrkboxGAuYfJZVH1InFTdgBdxq6ug==", + "funding": [ + { + "type": "individual", + "url": "https://www.locize.com/i18next" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://www.locize.com" + } + ], "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "peerDependencies": { + "typescript": "^5 || ^6 || ^7" }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/brc-dd" } }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/mdast-util-mdx": { - "version": "3.0.0", + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz", + "integrity": "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=20" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-mdx-expression": { + "node_modules/is-hexadecimal": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-mdx-jsx": { - "version": "3.2.0", + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" + "argparse": "^2.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "2.0.1", + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "license": "MPL-2.0", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark": { - "version": "4.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-directive": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "parse-entities": "^4.0.0" + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/parcel" } }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", + "node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.2.tgz", + "integrity": "sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==", "license": "MIT", "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@jridgewell/sourcemap-codec": "^1.6.0" } }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", + "node_modules/magicast": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.5.tgz", + "integrity": "sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==", "license": "MIT", "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" } }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", "license": "MIT", "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdx-expression": { - "version": "3.0.0", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.1", + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "license": "MIT", "dependencies": { - "@types/acorn": "^4.0.0", - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdx-md": { - "version": "2.0.0", + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { - "micromark-util-types": "^2.0.0" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdxjs": { - "version": "3.0.0", + "node_modules/mdast-util-to-markdown": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.3.tgz", + "integrity": "sha512-wgyJtgUkUcdU7zci7uuwc/tzoAhm0TswWhaXuvnWolqug+8jY7bgNkBBL0dYBnHgc/tHL7lCzRdPsdUxUKKBqw==", "license": "MIT", "dependencies": { - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", - "micromark-extension-mdx-expression": "^3.0.0", - "micromark-extension-mdx-jsx": "^3.0.0", - "micromark-extension-mdx-md": "^2.0.0", - "micromark-extension-mdxjs-esm": "^3.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-extension-mdxjs-esm": { - "version": "3.0.0", + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" + "@types/mdast": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.3.tgz", + "integrity": "sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==", "funding": [ { "type": "GitHub Sponsors", @@ -2845,13 +3935,30 @@ ], "license": "MIT", "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-edit-map": "^1.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", + "node_modules/micromark-core-commonmark": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.4.tgz", + "integrity": "sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==", "funding": [ { "type": "GitHub Sponsors", @@ -2864,57 +3971,48 @@ ], "license": "MIT", "dependencies": { + "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.1.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-edit-map": "^1.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "micromark-util-types": "^2.0.3" } }, - "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromark-factory-title": { + "node_modules/micromark-factory-destination": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", "funding": [ { "type": "GitHub Sponsors", @@ -2927,14 +4025,15 @@ ], "license": "MIT", "dependencies": { - "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-whitespace": { + "node_modules/micromark-factory-label": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", "funding": [ { "type": "GitHub Sponsors", @@ -2947,14 +4046,16 @@ ], "license": "MIT", "dependencies": { - "micromark-factory-space": "^2.0.0", + "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-character": { - "version": "2.1.1", + "node_modules/micromark-factory-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.1.0.tgz", + "integrity": "sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==", "funding": [ { "type": "GitHub Sponsors", @@ -2967,29 +4068,14 @@ ], "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0", + "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { + "node_modules/micromark-factory-title": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", "funding": [ { "type": "GitHub Sponsors", @@ -3002,48 +4088,16 @@ ], "license": "MIT", "dependencies": { + "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { + "node_modules/micromark-factory-whitespace": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", "funding": [ { "type": "GitHub Sponsors", @@ -3056,83 +4110,16 @@ ], "license": "MIT", "dependencies": { - "decode-named-character-reference": "^1.0.0", + "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-events-to-acorn": { - "version": "2.0.2", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/acorn": "^4.0.0", - "@types/estree": "^1.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" + "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -3145,11 +4132,14 @@ ], "license": "MIT", "dependencies": { + "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-sanitize-uri": { + "node_modules/micromark-util-chunked": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", "funding": [ { "type": "GitHub Sponsors", @@ -3162,13 +4152,13 @@ ], "license": "MIT", "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-util-subtokenize": { - "version": "2.0.3", + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", "funding": [ { "type": "GitHub Sponsors", @@ -3181,14 +4171,15 @@ ], "license": "MIT", "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", + "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-symbol": { + "node_modules/micromark-util-combine-extensions": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", "funding": [ { "type": "GitHub Sponsors", @@ -3199,10 +4190,16 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } }, - "node_modules/micromark-util-types": { - "version": "2.0.1", + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", "funding": [ { "type": "GitHub Sponsors", @@ -3213,707 +4210,603 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/mrmime": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">=10" + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.8", + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/neotraverse": { - "version": "0.6.18", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/nlcst-to-string": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/node-fetch-native": { - "version": "1.6.6", - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/ofetch": { - "version": "1.4.1", - "license": "MIT", - "dependencies": { - "destr": "^2.0.3", - "node-fetch-native": "^1.6.4", - "ufo": "^1.5.4" - } - }, - "node_modules/ohash": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/oniguruma-to-es": { - "version": "2.2.0", - "license": "MIT", - "dependencies": { - "emoji-regex-xs": "^1.0.0", - "regex": "^5.1.1", - "regex-recursion": "^5.1.1" - } - }, - "node_modules/p-limit": { - "version": "6.2.0", - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue": { - "version": "8.1.0", - "license": "MIT", - "dependencies": { - "eventemitter3": "^5.0.1", - "p-timeout": "^6.1.2" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "6.1.4", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pagefind": { - "version": "1.3.0", - "license": "MIT", - "bin": { - "pagefind": "lib/runner/bin.cjs" - }, - "optionalDependencies": { - "@pagefind/darwin-arm64": "1.3.0", - "@pagefind/darwin-x64": "1.3.0", - "@pagefind/linux-arm64": "1.3.0", - "@pagefind/linux-x64": "1.3.0", - "@pagefind/windows-x64": "1.3.0" - } - }, - "node_modules/parse-entities": { - "version": "4.0.2", - "license": "MIT", "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.11", - "license": "MIT" - }, - "node_modules/parse-latin": { - "version": "7.0.0", - "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "@types/unist": "^3.0.0", - "nlcst-to-string": "^4.0.0", - "unist-util-modify-children": "^4.0.0", - "unist-util-visit-children": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse5": { - "version": "7.2.1", - "license": "MIT", - "dependencies": { - "entities": "^4.5.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "1.1.2", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.2", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "license": "MIT", - "engines": { - "node": ">=6" + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", + "node_modules/micromark-util-edit-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-edit-map/-/micromark-util-edit-map-1.0.0.tgz", + "integrity": "sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" + "micromark-util-types": "^2.0.0" } }, - "node_modules/postcss": { - "version": "8.5.3", + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/postcss-nested": { - "version": "6.2.0", + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" + "micromark-util-types": "^2.0.0" } }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/preferred-pm": { - "version": "4.1.1", + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "find-up-simple": "^1.0.0", - "find-yarn-workspace-root2": "1.2.16", - "which-pm": "^3.0.1" - }, - "engines": { - "node": ">=18.12" + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/prismjs": { - "version": "1.29.0", - "license": "MIT", - "engines": { - "node": ">=6" - } + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/prompts": { - "version": "2.4.2", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } + "node_modules/micromark-util-types": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.3.tgz", + "integrity": "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/prompts/node_modules/kleur": { - "version": "3.0.3", + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "license": "MIT", "engines": { - "node": ">=6" - } - }, - "node_modules/property-information": { - "version": "6.5.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "node": ">=10" } }, - "node_modules/radix3": { - "version": "1.1.2", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/readdirp": { - "version": "3.6.0", + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">=8.10.0" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", + "node_modules/neotraverse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-1.0.1.tgz", + "integrity": "sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w==", "license": "MIT", "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">= 10" } }, - "node_modules/recma-build-jsx": { - "version": "1.0.0", + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-build-jsx": "^3.0.0", - "vfile": "^6.0.0" + "@types/nlcst": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/recma-jsx": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "acorn-jsx": "^5.0.0", - "estree-util-to-js": "^2.0.0", - "recma-parse": "^1.0.0", - "recma-stringify": "^1.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" }, - "node_modules/recma-parse": { - "version": "1.0.0", + "node_modules/node-mock-http": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.5.tgz", + "integrity": "sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "esast-util-from-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/recma-stringify": { - "version": "1.0.0", - "license": "MIT", + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-to-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" + "boolbase": "^1.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "license": "MIT" - }, - "node_modules/regex": { - "version": "5.1.1", - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/regex-recursion": { - "version": "5.1.1", + "node_modules/obug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], "license": "MIT", - "dependencies": { - "regex": "^5.1.1", - "regex-utilities": "^2.3.0" + "engines": { + "node": ">=12.20.0" } }, - "node_modules/regex-utilities": { - "version": "2.3.0", - "license": "MIT" - }, - "node_modules/rehype": { - "version": "13.0.2", + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "rehype-parse": "^9.0.0", - "rehype-stringify": "^10.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" } }, - "node_modules/rehype-expressive-code": { - "version": "0.40.1", + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "license": "MIT" + }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", "license": "MIT", "dependencies": { - "expressive-code": "^0.40.1" + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" } }, - "node_modules/rehype-format": { - "version": "5.0.1", + "node_modules/p-limit": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.3.tgz", + "integrity": "sha512-SKgVvDU5TNIxQ4r30U75BNLC6a+6GOEjngrk/ysM5+Zu3oJuk1SC5ApzIzY/7PjCEjoVPMWJ0zho/4QUiEB7TQ==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-format": "^1.0.0" + "yocto-queue": "^1.2.1" + }, + "engines": { + "node": ">=20" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rehype-parse": { - "version": "9.0.1", + "node_modules/p-queue": { + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz", + "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-from-html": "^2.0.0", - "unified": "^11.0.0" + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rehype-raw": { - "version": "7.0.0", + "node_modules/p-timeout": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.2.tgz", + "integrity": "sha512-prbX4Z3YszrFNgH+MW5Zoeq3baXrMtP/MQnFeET90UB/GtGcGDQ5Usg9OCy6ETjTTntOw1SL2z9fMPUppN3Guw==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-raw": "^9.0.0", - "vfile": "^6.0.0" + "engines": { + "node": ">=20" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rehype-recma": { - "version": "1.0.0", + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.5.2.tgz", + "integrity": "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==", "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "hast-util-to-estree": "^3.0.0" + "bin": { + "pagefind": "lib/runner/bin.cjs" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.5.2", + "@pagefind/darwin-x64": "1.5.2", + "@pagefind/freebsd-x64": "1.5.2", + "@pagefind/linux-arm64": "1.5.2", + "@pagefind/linux-x64": "1.5.2", + "@pagefind/windows-arm64": "1.5.2", + "@pagefind/windows-x64": "1.5.2" } }, - "node_modules/rehype-stringify": { - "version": "10.0.1", + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-to-html": "^9.0.0", - "unified": "^11.0.0" + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/remark-directive": { - "version": "3.0.0", + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-directive": "^3.0.0", - "micromark-extension-directive": "^3.0.0", - "unified": "^11.0.0" + "engines": { + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/remark-gfm": { - "version": "4.0.0", + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": "^10 || ^12 || >=14" } }, - "node_modules/remark-mdx": { - "version": "3.1.0", + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "mdast-util-mdx": "^3.0.0", - "micromark-extension-mdxjs": "^3.0.0" + "postcss-selector-parser": "^6.1.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" } }, - "node_modules/remark-parse": { - "version": "11.0.0", + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-ancestry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/process-ancestry/-/process-ancestry-0.1.0.tgz", + "integrity": "sha512-tGqJW/UnclpYASFcM6Xh8D8l/BMtaQ9+CSG0vlJSJTcdMM4lDRv4c6H0Pdcsfted+bVczdYSfk2fdukg2gQkZg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/remark-rehype": { - "version": "11.1.1", + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" + "engines": { + "node": ">= 20.19.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/remark-smartypants": { - "version": "3.0.2", + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", "license": "MIT", "dependencies": { - "retext": "^9.0.0", - "retext-smartypants": "^6.0.0", - "unified": "^11.0.4", - "unist-util-visit": "^5.0.0" - }, - "engines": { - "node": ">=16.0.0" + "regex-utilities": "^2.3.0" } }, - "node_modules/remark-stringify": { - "version": "11.0.0", + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "regex-utilities": "^2.3.0" } }, - "node_modules/retext": { - "version": "9.0.0", + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype-expressive-code": { + "version": "0.44.2", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.44.2.tgz", + "integrity": "sha512-jvjQpsfWKBquDrEl5YQA3CJybRRIuDOYsui1BJTs5oKDUK8pPkJSSxY8FjxNlIFIdtgrTjC1zuahuY4Cc1j9SQ==", "license": "MIT", "dependencies": { - "@types/nlcst": "^2.0.0", - "retext-latin": "^4.0.0", - "retext-stringify": "^4.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "expressive-code": "^0.44.2" } }, - "node_modules/retext-latin": { + "node_modules/remark-directive": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-4.0.0.tgz", + "integrity": "sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA==", "license": "MIT", "dependencies": { - "@types/nlcst": "^2.0.0", - "parse-latin": "^7.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^4.0.0", "unified": "^11.0.0" }, "funding": { @@ -3921,74 +4814,99 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/retext-smartypants": { - "version": "6.2.0", + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "license": "MIT", - "dependencies": { - "@types/nlcst": "^2.0.0", - "nlcst-to-string": "^4.0.0", - "unist-util-visit": "^5.0.0" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/retext-stringify": { - "version": "4.0.0", + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", "license": "MIT", "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", - "unified": "^11.0.0" + "unist-util-visit": "^5.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/rollup": { - "version": "4.31.0", + "node_modules/rolldown": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.11.tgz", + "integrity": "sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==", "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@oxc-project/types": "=0.151.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.31.0", - "@rollup/rollup-android-arm64": "4.31.0", - "@rollup/rollup-darwin-arm64": "4.31.0", - "@rollup/rollup-darwin-x64": "4.31.0", - "@rollup/rollup-freebsd-arm64": "4.31.0", - "@rollup/rollup-freebsd-x64": "4.31.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.31.0", - "@rollup/rollup-linux-arm-musleabihf": "4.31.0", - "@rollup/rollup-linux-arm64-gnu": "4.31.0", - "@rollup/rollup-linux-arm64-musl": "4.31.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.31.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.31.0", - "@rollup/rollup-linux-riscv64-gnu": "4.31.0", - "@rollup/rollup-linux-s390x-gnu": "4.31.0", - "@rollup/rollup-linux-x64-gnu": "4.31.0", - "@rollup/rollup-linux-x64-musl": "4.31.0", - "@rollup/rollup-win32-arm64-msvc": "4.31.0", - "@rollup/rollup-win32-ia32-msvc": "4.31.0", - "@rollup/rollup-win32-x64-msvc": "4.31.0", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm-eabi": "1.2.11", + "@rolldown/binding-android-arm64": "1.2.11", + "@rolldown/binding-darwin-arm64": "1.2.11", + "@rolldown/binding-darwin-x64": "1.2.11", + "@rolldown/binding-freebsd-x64": "1.2.11", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.11", + "@rolldown/binding-linux-arm64-gnu": "1.2.11", + "@rolldown/binding-linux-arm64-musl": "1.2.11", + "@rolldown/binding-linux-ppc64-gnu": "1.2.11", + "@rolldown/binding-linux-s390x-gnu": "1.2.11", + "@rolldown/binding-linux-x64-gnu": "1.2.11", + "@rolldown/binding-linux-x64-musl": "1.2.11", + "@rolldown/binding-openharmony-arm64": "1.2.11", + "@rolldown/binding-win32-arm64-msvc": "1.2.11", + "@rolldown/binding-win32-x64-msvc": "1.2.11" + } + }, + "node_modules/satteri": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/satteri/-/satteri-0.10.5.tgz", + "integrity": "sha512-Ao1LKpAEa9Wdg0otgbVKViZHEq9ebdXe4DMrp3s9vQAU0HNIuHnFEuMuOcm0ZIXyV0Yzxj91NvhLpvXZJO/5ZQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.5", + "@types/hast": "^3.0.5", + "@types/mdast": "^4.0.4", + "@types/unist": "^3.0.3" + }, + "optionalDependencies": { + "@bruits/satteri-darwin-arm64": "0.10.5", + "@bruits/satteri-darwin-x64": "0.10.5", + "@bruits/satteri-linux-arm64-gnu": "0.10.5", + "@bruits/satteri-linux-arm64-musl": "0.10.5", + "@bruits/satteri-linux-x64-gnu": "0.10.5", + "@bruits/satteri-linux-x64-musl": "0.10.5", + "@bruits/satteri-wasm32-wasi": "0.10.5", + "@bruits/satteri-win32-arm64-msvc": "0.10.5", + "@bruits/satteri-win32-x64-msvc": "0.10.5" } }, "node_modules/sax": { - "version": "1.4.1", - "license": "ISC" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } }, "node_modules/semver": { - "version": "7.7.1", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3998,102 +4916,102 @@ } }, "node_modules/sharp": { - "version": "0.33.5", - "hasInstallScript": true, + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", "license": "Apache-2.0", "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" - } - }, - "node_modules/sharp/node_modules/semver": { - "version": "7.6.3", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" }, - "engines": { - "node": ">=10" + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/shiki": { - "version": "1.29.2", - "license": "MIT", - "dependencies": { - "@shikijs/core": "1.29.2", - "@shikijs/engine-javascript": "1.29.2", - "@shikijs/engine-oniguruma": "1.29.2", - "@shikijs/langs": "1.29.2", - "@shikijs/themes": "1.29.2", - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.4.3.tgz", + "integrity": "sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==", "license": "MIT", "dependencies": { - "is-arrayish": "^0.3.1" + "@shikijs/core": "4.4.3", + "@shikijs/engine-javascript": "4.4.3", + "@shikijs/engine-oniguruma": "4.4.3", + "@shikijs/langs": "4.4.3", + "@shikijs/themes": "4.4.3", + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" } }, "node_modules/sisteransi": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, "node_modules/sitemap": { - "version": "8.0.0", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz", + "integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==", "license": "MIT", "dependencies": { - "@types/node": "^17.0.5", + "@types/node": "^24.9.2", "@types/sax": "^1.2.1", "arg": "^5.0.0", - "sax": "^1.2.4" + "sax": "^1.4.1" }, "bin": { - "sitemap": "dist/cli.js" + "sitemap": "dist/esm/cli.js" }, "engines": { - "node": ">=14.0.0", - "npm": ">=6.0.0" + "node": ">=20.19.5", + "npm": ">=10.8.2" } }, - "node_modules/sitemap/node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "license": "MIT" - }, "node_modules/smol-toml": { - "version": "1.3.1", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.0.tgz", + "integrity": "sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==", "license": "BSD-3-Clause", "engines": { "node": ">= 18" @@ -4102,15 +5020,10 @@ "url": "https://github.com/sponsors/cyyynthia" } }, - "node_modules/source-map": { - "version": "0.7.4", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 8" - } - }, "node_modules/source-map-js": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -4118,37 +5031,18 @@ }, "node_modules/space-separated-tokens": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "license": "BSD-3-Clause" - }, - "node_modules/stream-replace-string": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/string-width": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/stringify-entities": { "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "license": "MIT", "dependencies": { "character-entities-html4": "^2.0.0", @@ -4159,43 +5053,63 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/svgo": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.1.0.tgz", + "integrity": "sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "commander": "^11.1.0", + "css-select": "^6.0.0", + "css-tree": "^3.0.1", + "css-what": "^7.0.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "1.6.1" + }, + "bin": { + "svgo": "bin/svgo.js" }, "engines": { - "node": ">=12" + "node": ">=16" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/svgo" } }, - "node_modules/strip-bom": { - "version": "3.0.0", + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyclip": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-1.0.3.tgz", + "integrity": "sha512-nTv2raKFMzGG6JJp0lpNDsRPdfFE6UHlu2rVEjXo8vkx1ZS3LW2CVXqIaLu8xZlu+6tbFs9V58Qc/Jmk/Ii7wA==", "license": "MIT", "engines": { - "node": ">=4" + "node": "^16.14.0 || >= 17.3.0" } }, - "node_modules/style-to-object": { - "version": "1.0.8", + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "license": "MIT", - "dependencies": { - "inline-style-parser": "0.2.4" + "engines": { + "node": ">=18" } }, - "node_modules/tinyexec": { - "version": "0.3.2", - "license": "MIT" - }, "node_modules/tinyglobby": { - "version": "0.2.12", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { - "fdir": "^6.4.3", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -4204,18 +5118,10 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/trim-lines": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "license": "MIT", "funding": { "type": "github", @@ -4224,83 +5130,58 @@ }, "node_modules/trough": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/tsconfck": { - "version": "3.1.4", - "license": "MIT", - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^18 || >=20" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/type-fest": { - "version": "4.32.0", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.7.3", - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true }, "node_modules/ufo": { - "version": "1.5.4", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", "license": "MIT" }, "node_modules/ultrahtml": { - "version": "1.5.3", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.7.0.tgz", + "integrity": "sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==", "license": "MIT" }, "node_modules/uncrypto": { "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", "license": "MIT" }, - "node_modules/undici-types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", - "license": "MIT" - }, - "node_modules/unenv": { - "version": "1.10.0", + "node_modules/undici": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz", + "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==", "license": "MIT", - "dependencies": { - "consola": "^3.2.3", - "defu": "^6.1.4", - "mime": "^3.0.0", - "node-fetch-native": "^1.6.4", - "pathe": "^1.1.2" + "engines": { + "node": ">=22.19.0" } }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "license": "MIT" + }, "node_modules/unified": { "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -4316,8 +5197,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unifont": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.5.tgz", + "integrity": "sha512-ULe/Cs+ZIsq+dcFofNkhqielCrUJnb5mr+Yc4EBM2VlL+6OZR6+cjtI2mT1bJvRBrVncqHAbLURxmPLcCXzWMg==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.1.0", + "ohash": "^2.0.11", + "undici": "^8.0.0" + } + }, "node_modules/unist-util-find-after": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -4329,7 +5223,9 @@ } }, "node_modules/unist-util-is": { - "version": "6.0.0", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -4339,20 +5235,10 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-modify-children": { - "version": "4.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "array-iterate": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-position": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -4362,31 +5248,10 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-position-from-estree": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-remove-position": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -4397,7 +5262,9 @@ } }, "node_modules/unist-util-visit": { - "version": "5.0.0", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -4409,19 +5276,10 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-visit-children": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-visit-parents": { - "version": "6.0.1", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -4433,37 +5291,40 @@ } }, "node_modules/unstorage": { - "version": "1.14.4", + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", + "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", "license": "MIT", "dependencies": { "anymatch": "^3.1.3", - "chokidar": "^3.6.0", - "destr": "^2.0.3", - "h3": "^1.13.0", - "lru-cache": "^10.4.3", - "node-fetch-native": "^1.6.4", - "ofetch": "^1.4.1", - "ufo": "^1.5.4" + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.10", + "lru-cache": "^11.2.7", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", - "@azure/identity": "^4.5.0", + "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", - "@capacitor/preferences": "^6.0.3", - "@deno/kv": ">=0.8.4", - "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", - "@vercel/blob": ">=0.27.0", - "@vercel/kv": "^1.0.1", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", - "uploadthing": "^7.4.1" + "uploadthing": "^7.4.4" }, "peerDependenciesMeta": { "@azure/app-configuration": { @@ -4502,6 +5363,9 @@ "@vercel/blob": { "optional": true }, + "@vercel/functions": { + "optional": true + }, "@vercel/kv": { "optional": true }, @@ -4522,28 +5386,44 @@ } } }, + "node_modules/url-extras": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/url-extras/-/url-extras-0.1.0.tgz", + "integrity": "sha512-8tzwTeXFPuX/5PHuCDQE5Dd9Ts4rwoq2t9aIT+HS4iAVpmj5l4Ao7Q+BuuFjvWRqrLswBhQDk8O96ZicgCqQqw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/vfile": { - "version": "6.0.3", + "node_modules/verkit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.4.1.tgz", + "integrity": "sha512-mMVzj0TXExtVdlDEq+Mzp0eyOuyznNpFobNM3uAqe3HVm/Ps2c+u17BligMkZjZCA6EiXpoon8iRTer3iu40SQ==", "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" + "engines": { + "node": ">=18.12.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/vfile-location": { - "version": "5.0.3", + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", - "vfile": "^6.0.0" + "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", @@ -4551,7 +5431,9 @@ } }, "node_modules/vfile-message": { - "version": "4.0.2", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -4563,18 +5445,22 @@ } }, "node_modules/vite": { - "version": "6.2.0", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.1.tgz", + "integrity": "sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==", "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "postcss": "^8.5.3", - "rollup": "^4.30.1" + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.9", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -4583,14 +5469,15 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -4599,13 +5486,16 @@ "@types/node": { "optional": true }, - "jiti": { + "@vitejs/devtools": { "optional": true }, - "less": { + "esbuild": { + "optional": true + }, + "jiti": { "optional": true }, - "lightningcss": { + "less": { "optional": true }, "sass": { @@ -4631,67 +5521,18 @@ } } }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.25.0", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" - } - }, - "node_modules/vite/node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/vitefu": { - "version": "1.0.5", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", "license": "MIT", "workspaces": [ "tests/deps/*", - "tests/projects/*" + "tests/projects/*", + "tests/projects/workspace/packages/*" ], "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "vite": { @@ -4699,72 +5540,25 @@ } } }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/which-pm": { - "version": "3.0.1", - "license": "MIT", - "dependencies": { - "load-yaml-file": "^0.2.0" - }, - "engines": { - "node": ">=18.12" - } - }, - "node_modules/which-pm-runs": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/widest-line": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wrap-ansi": { - "version": "9.0.0", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/xxhash-wasm": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", "license": "MIT" }, "node_modules/yargs-parser": { - "version": "21.1.1", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "license": "ISC", "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yocto-queue": { - "version": "1.1.1", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "license": "MIT", "engines": { "node": ">=12.20" @@ -4773,52 +5567,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yocto-spinner": { - "version": "0.2.1", - "license": "MIT", - "dependencies": { - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18.19" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/zod": { - "version": "3.24.1", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/zod-to-json-schema": { - "version": "3.24.1", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, - "node_modules/zod-to-ts": { - "version": "1.2.0", - "peerDependencies": { - "typescript": "^4.9.4 || ^5.0.2", - "zod": "^3" - } - }, "node_modules/zwitch": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", "license": "MIT", "funding": { "type": "github", diff --git a/docs/package.json b/docs/package.json index 3cbbeb1..6037f07 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,17 +1,17 @@ { + "dependencies": { + "@astrojs/starlight": "^0.42.4", + "astro": "^7.3.5", + "sharp": "^0.35.5" + }, "name": "committy-docs", - "type": "module", - "version": "0.0.1", "scripts": { - "dev": "astro dev", - "start": "astro dev", + "astro": "astro", "build": "astro build", + "dev": "astro dev", "preview": "astro preview", - "astro": "astro" + "start": "astro dev" }, - "dependencies": { - "@astrojs/starlight": "0.35.2", - "astro": "5.13.3", - "sharp": "0.34.3" - } -} \ No newline at end of file + "type": "module", + "version": "0.2.1" +} diff --git a/docs/src/content/docs/project/roadmap.mdx b/docs/src/content/docs/project/roadmap.mdx deleted file mode 100644 index 1dafba3..0000000 --- a/docs/src/content/docs/project/roadmap.mdx +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Roadmap -description: Get an overview of the future of Committy ---- \ No newline at end of file diff --git a/docs/src/content/docs/reference/agent-workflows.mdx b/docs/src/content/docs/reference/agent-workflows.mdx index f645e07..fbe1bde 100644 --- a/docs/src/content/docs/reference/agent-workflows.mdx +++ b/docs/src/content/docs/reference/agent-workflows.mdx @@ -12,11 +12,50 @@ Committy can be embedded directly in system prompts or skills when you want an a Preferred sequence: -1. Run a `--dry-run --output json` preview. -2. Inspect the JSON payload. -3. Rerun without `--dry-run` only if the plan looks correct. -4. Require explicit confirmation for any remote mutation. -5. Use `--repo-path` when the agent should operate on a checkout other than its current shell directory. +1. Query `committy schema --output json` once for active types and capabilities. +2. Run a `--dry-run --output json` preview. +3. Inspect the JSON payload. +4. Rerun without `--dry-run` only if the plan looks correct. +5. Require explicit confirmation for any remote mutation. +6. Use `--repo-path` when the agent should operate on another checkout. + +## Flag placement + +Committy has two flag namespaces, and mixing them up is the most common way an +agent invocation fails: + +- **Global flags** — `--non-interactive` and `-q`/`--quiet` — are accepted on + either side of the subcommand. +- **Everything else** — `--repo-path`, `--output`, `--dry-run`, and every + command-specific flag — must follow the subcommand. +- **`-v`/`--verbose` must precede the subcommand.** It is deliberately not + global: `-v` already means `--validate` on `branch`, and `--verbose` is a real + flag on `config validate` and `config show`. + +The most reliable option is to stop passing `--non-interactive` altogether and +set the environment variable instead, which makes placement irrelevant: + +```bash +export COMMITTY_NONINTERACTIVE=1 +committy schema --repo-path . --output json +``` + +`CI=1` has the same effect, so Committy is already non-interactive on most CI +runners. + +When an invocation is rejected outright — an unknown flag, a misplaced argument +— Committy still writes one JSON document to stdout if `--output json` was +requested, with `"code": "invalid_usage"` and clap's explanation as the message. +Agents can parse the rejection and self-correct rather than falling back to +scraping stderr. + +## Policy discovery + +```bash +committy schema --repo-path . --output json +``` + +The backward-compatible `types` array lists commit types. `type_definitions` adds descriptions, SemVer behavior, aliases, and `commit` or `branch` contexts. `capabilities` describes supported agent operations so skills do not need to hard-code the command surface. ## Branch recipe @@ -111,6 +150,7 @@ For grouped commits or tags, require explicit confirmation flags before any remo ## JSON fields to rely on +- `api_version`: machine contract version; currently `1`. - `command`: the command that produced the payload. - `ok`: whether the command-level operation succeeded. - `dry_run`: whether the payload is only a preview. @@ -118,3 +158,23 @@ For grouped commits or tags, require explicit confirmation flags before any remo - `mode`: present on multi-mode commands such as `group-commit`. Command-specific fields remain stable on top of that envelope. + +Command errors exit `1`; convention lint findings exit `3`. JSON mode writes one document to stdout on both success and failure. + +## Repository enforcement + +Preview native Git hooks plus GitHub Actions enforcement: + +```bash +committy --non-interactive hooks install \ + --repo-path . \ + --with-ci \ + --dry-run \ + --output json +``` + +Remove `--dry-run` to install `commit-msg`, `pre-push`, and `.github/workflows/committy.yml`. Existing files are never replaced unless `--force` is explicit. The hooks are thin delegates to `committy hooks run`, keeping local and CI validation on the same policy engine. + +## Codex and Claude skills + +The shared plugin lives in `plugins/committy/`. Codex discovers it through `.agents/plugins/marketplace.json`; Claude Code uses `.claude-plugin/marketplace.json`. Both load the same four progressive-disclosure skills for branches, commits, releases, and enforcement, so their small metadata is always available while full instructions load only for matching work. diff --git a/docs/src/content/docs/reference/ai-flags.mdx b/docs/src/content/docs/reference/ai-flags.mdx index f290cf4..8a4fcea 100644 --- a/docs/src/content/docs/reference/ai-flags.mdx +++ b/docs/src/content/docs/reference/ai-flags.mdx @@ -11,7 +11,7 @@ This page enumerates flags, best practices, and security considerations. ## Enabling AI -Add `--ai` to `group-commit`: +Add `--ai` to `group-commit`. It applies in both `--mode plan` and `--mode apply`: ```bash committy --non-interactive group-commit --mode plan --output json \ @@ -29,48 +29,80 @@ committy --non-interactive group-commit --mode plan --output json \ - `--ai-max-tokens ` — model tokens cap for responses - `--ai-temperature ` — creativity setting - `--ai-timeout-ms ` — request timeout -- `--no-ai-json-mode` — do not force JSON-mode (advanced; prefer default) +- `--no-ai-json-mode` — do not request structured output (advanced; prefer default) - `--ai-system-prompt ` — inline system prompt override - `--ai-system-prompt-file ` — load system prompt from file -- `--ai-file-limit ` — limit how many files are described to AI -- `--ai-allow-sensitive` — allow sending sensitive file content (off by default) +- `--ai-file-limit ` — cap how many paths are listed under `--ai-allow-sensitive` +- `--ai-allow-sensitive` — send real file paths instead of a redacted summary (off by default) + +## What is actually sent + +__No file content or diff text is ever sent__, in either mode. The difference +between the default and `--ai-allow-sensitive` is paths, not content. + +By default Committy sends a redacted *shape* of each group — how many files, +an extension histogram, and the top-level directories involved: + +```text +Group: code +Default type: feat +Default short: update source +Change shape (no filenames or content): 7 file(s); types: .rs x6, .toml x1; areas: src, tests +``` + +Filenames, paths below the first segment, and file contents are all withheld. +This is enough signal for a model to write a sensible subject line without the +provider learning your module, product, or customer names. + +With `--ai-allow-sensitive`, the group's file paths are listed instead, capped +by `--ai-file-limit` and elided with `...` beyond it. Use it only when you trust +the provider with your repository layout. ## Security & privacy -- __Sensitive content is not sent__ by default. The tool shares filenames and summarized context. -- Only pass `--ai-allow-sensitive` if required and you trust the provider. This may include snippets or diff lines from code. -- Prefer using an API key via env var name with `--ai-api-key-env` to avoid leaking keys in process lists or logs. -- Use `--ai-file-limit` to reduce context size and exposure. -- Consider on-premise providers (`ollama`) for maximum privacy. +- Prefer the default. The redacted summary is usually sufficient. +- Pass the API key by env var *name* with `--ai-api-key-env` so the secret never + appears in a process list, shell history, or CI log. +- Use `--ai-file-limit` to bound exposure when `--ai-allow-sensitive` is on. +- Consider an on-premise provider (`ollama`) when no data should leave the host. ## Failure handling -- AI suggestions are validated by Committy's linter. If invalid or timed out, Committy falls back to safe default messages per group. -- JSON parsing is strict; malformed outputs are rejected and replaced with defaults. +- AI suggestions are validated by Committy's linter. If a suggestion is invalid + or the request times out, Committy keeps the safe default message for that group + and records the reason in `errors`. +- Structured output is requested by default, and malformed responses are rejected + rather than committed. ## Provider examples ### OpenRouter ```bash -# export your key and reference by env var name +# export your key and reference it by env var name export OPENROUTER_API_KEY=sk-or-... committy --non-interactive group-commit --mode plan --output json \ --ai --ai-provider openrouter \ - --ai-model openrouter/anthropic/claude-3.5-sonnet \ + --ai-model openrouter/anthropic/claude-sonnet-4.5 \ --ai-api-key-env OPENROUTER_API_KEY \ --ai-max-tokens 600 --ai-temperature 0.2 ``` +JSON mode sends `response_format: {"type": "json_object"}`. + ### Ollama (local) ```bash -# ensure Ollama is running locally; default base URL is http://127.0.0.1:11434 +# ensure Ollama is running locally; default base URL is http://localhost:11434 # no API key needed for local Ollama committy --non-interactive group-commit --mode apply --output json \ --ai --ai-provider ollama \ --ai-model llama3.1:8b \ - --ai-timeout-ms 30000 --ai-file-limit 64 + --ai-timeout-ms 30000 ``` + +Requests to Ollama set `stream: false` and pass a JSON Schema in the top-level +`format` field, so the model's output is constrained to the expected object +rather than merely asked for as JSON. diff --git a/docs/src/content/docs/reference/tag.mdx b/docs/src/content/docs/reference/tag.mdx index ef8e0e7..cfdf36e 100644 --- a/docs/src/content/docs/reference/tag.mdx +++ b/docs/src/content/docs/reference/tag.mdx @@ -27,8 +27,8 @@ committy --non-interactive tag --publish --confirm-publish # Pre-release flow committy --non-interactive tag --prerelease --prerelease-suffix beta -# Bump versions in config files when tagging -committy --non-interactive tag --bump-files +# Tag without touching version files +committy --non-interactive tag --no-bump-files ``` ## Flags (TagCommand) @@ -40,7 +40,13 @@ committy --non-interactive tag --bump-files Confirm creation without prompting (interactive path). If omitted, you'll be prompted. - __--bump-files__ (alias: `-b`) - Auto-bump registered version files to the new version and commit them. + Bump registered version files to the new version and commit them. This is the default, + so the flag is only needed for compatibility with existing scripts. + +- __--no-bump-files__ + Leave version files untouched. Note that the tag then points at a commit whose + `Cargo.toml`/`package.json` still carries the previous version, so binaries built from + it report the old version. - __--output__ `` Output format. Defaults to `text`. @@ -77,7 +83,8 @@ committy --non-interactive tag --bump-files - When `--name` is provided: creates that tag locally. - Non-interactive without `--name`: calculates the new tag from commit log using regex rules in config and options above, then creates it locally. - `--publish --confirm-publish` is required before the tag or any version-bump commit is pushed to `origin`. -- `--bump-files`: updates common version files and commits them (message: `chore: bump version to `). Remote push still requires `--publish --confirm-publish`. +- Version files are bumped by default: common version files are updated and committed (message: `chore: bump version to `) before the tag is created, so the tag contains the new version. Pass `--no-bump-files` to opt out. Remote push still requires `--publish --confirm-publish`. +- In multi-package repositories the same applies per package; the bump commit message lists each package as `@`. - Fetch behavior: default is fetch unless `--no-fetch`; `--fetch` forces fetch. ## JSON output diff --git a/plugins/committy/.claude-plugin/plugin.json b/plugins/committy/.claude-plugin/plugin.json new file mode 100644 index 0000000..16f27d2 --- /dev/null +++ b/plugins/committy/.claude-plugin/plugin.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", + "name": "committy", + "version": "0.1.0", + "description": "Token-efficient commit, branch, release, and repository convention workflows backed by the Committy CLI.", + "author": { + "name": "Arnaud Leherpeur", + "email": "arnaud.leherpeur@taultek.com" + }, + "homepage": "https://github.com/martient/committy", + "repository": "https://github.com/martient/committy", + "license": "Apache-2.0", + "keywords": ["git", "conventional-commits", "branches", "releases", "agents"] +} diff --git a/plugins/committy/.codex-plugin/plugin.json b/plugins/committy/.codex-plugin/plugin.json new file mode 100644 index 0000000..0c84df7 --- /dev/null +++ b/plugins/committy/.codex-plugin/plugin.json @@ -0,0 +1,28 @@ +{ + "name": "committy", + "version": "0.1.0", + "description": "Token-efficient commit, branch, release, and repository convention workflows backed by the Committy CLI.", + "author": { + "name": "Arnaud Leherpeur", + "email": "arnaud.leherpeur@taultek.com", + "url": "https://github.com/martient" + }, + "homepage": "https://github.com/martient/committy", + "repository": "https://github.com/martient/committy", + "license": "Apache-2.0", + "keywords": ["git", "conventional-commits", "branches", "releases", "agents"], + "skills": "./skills/", + "interface": { + "displayName": "Committy", + "shortDescription": "Enforce Git conventions with low-token workflows.", + "longDescription": "Use Committy's typed JSON schema, dry-run commands, Git hooks, and CI checks for consistent agent-driven commits, branches, and releases.", + "developerName": "Arnaud Leherpeur", + "category": "Productivity", + "capabilities": ["Commit preview", "Branch policy", "Git hooks", "Release planning"], + "defaultPrompt": [ + "Commit this change with Committy.", + "Create a compliant branch for this ticket.", + "Add Committy enforcement to this repository." + ] + } +} diff --git a/plugins/committy/skills/committy-branch/SKILL.md b/plugins/committy/skills/committy-branch/SKILL.md new file mode 100644 index 0000000..fbc3254 --- /dev/null +++ b/plugins/committy/skills/committy-branch/SKILL.md @@ -0,0 +1,19 @@ +--- +name: committy-branch +description: Preview, validate, and create repository-compliant Git branches with Committy. Use when an agent is asked to name a branch, start work for a ticket, validate a proposed branch name, or switch to a new branch. +license: Apache-2.0 +allowed-tools: Bash Read Grep Glob +metadata: + version: "0.1.0" + requires-committy: ">=1.9.0" +--- + +# Branch with Committy + +1. Query `committy -q schema --repo-path --output json` and select a type whose `contexts` includes `branch`. +2. Prefer structured input: `committy --non-interactive -q branch --repo-path --type --ticket --subject --dry-run --output json`. Omit `--ticket` only when repository rules allow it. +3. Inspect `api_version`, `ok`, `branch_name`, and `errors`. If validation fails, repair the inputs; never fall back to raw `git branch` to evade policy. +4. Create the branch by repeating the command without `--dry-run` when the requested operation authorizes it. +5. Use `--name` only when the user supplied a full branch name or exact preservation matters. Repository policy may enforce explicit names. + +Do not force-recreate an existing branch unless the user explicitly requests it. diff --git a/plugins/committy/skills/committy-branch/agents/openai.yaml b/plugins/committy/skills/committy-branch/agents/openai.yaml new file mode 100644 index 0000000..dc4a88d --- /dev/null +++ b/plugins/committy/skills/committy-branch/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Branch with Committy" + short_description: "Preview and create compliant branches" + default_prompt: "Use $committy-branch to create a compliant branch for this work." diff --git a/plugins/committy/skills/committy-commit/SKILL.md b/plugins/committy/skills/committy-commit/SKILL.md new file mode 100644 index 0000000..6fbcbd1 --- /dev/null +++ b/plugins/committy/skills/committy-commit/SKILL.md @@ -0,0 +1,21 @@ +--- +name: committy-commit +description: Draft, lint, preview, and create repository-compliant commits with Committy. Use when an agent is asked to stage a coherent change, write or repair a commit message, amend a commit, or make a conventional commit. +license: Apache-2.0 +allowed-tools: Bash Read Grep Glob +metadata: + version: "0.1.0" + requires-committy: ">=1.9.0" +--- + +# Commit with Committy + +1. Inspect `git status --short` and the relevant diff. Stage only files belonging to the requested change. +2. Query policy once with `committy -q schema --repo-path --output json`. Choose a commit type from `types`; use `type_definitions` descriptions when intent is ambiguous. +3. Lint a supplied full message with `committy -q lint-message --repo-path --message --output json`. +4. Preview with `committy --non-interactive -q commit --repo-path --type --message --dry-run --output json`. Add `--scope`, `--long-message`, or `--breaking-change` only when supported by the change. +5. Inspect `api_version`, `ok`, `message`, `errors`, and optional `workflow`. Correct errors; do not bypass repository policy. +6. Create the commit with the same arguments after the user has authorized committing. Use `amend` only when explicitly requested. +7. Report the SHA and subject. Stop after lint or preview when that is all the user requested. + +Never stage unrelated work, publish, tag, or push as part of this skill. diff --git a/plugins/committy/skills/committy-commit/agents/openai.yaml b/plugins/committy/skills/committy-commit/agents/openai.yaml new file mode 100644 index 0000000..93c418a --- /dev/null +++ b/plugins/committy/skills/committy-commit/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Commit with Committy" + short_description: "Preview and create compliant commits" + default_prompt: "Use $committy-commit to commit my current change safely." diff --git a/plugins/committy/skills/committy-enforce/SKILL.md b/plugins/committy/skills/committy-enforce/SKILL.md new file mode 100644 index 0000000..496e1c8 --- /dev/null +++ b/plugins/committy/skills/committy-enforce/SKILL.md @@ -0,0 +1,20 @@ +--- +name: committy-enforce +description: Configure and enforce repository commit and branch conventions with Committy Git hooks and CI. Use when an agent is asked to adopt Committy, define typed convention descriptions or branch ticket rules, install hooks, or add convention checks to GitHub Actions. +license: Apache-2.0 +allowed-tools: Bash Read Grep Glob Edit Write +metadata: + version: "0.1.0" + requires-committy: ">=1.9.0" +--- + +# Enforce with Committy + +1. Inspect existing `.committy/config.toml`, hooks, and CI before changing anything. +2. Query `committy -q schema --repo-path --output json` to understand current typed definitions and supported capabilities. +3. Keep convention types in `[[convention.types]]` with `name`, `description`, `contexts`, bump, changelog, aliases, and visibility fields. Put ticket requirements in `[branch_rules]`. +4. Preview local enforcement with `committy --non-interactive -q hooks install --repo-path --with-ci --dry-run --output json`. +5. Apply without `--dry-run` after the user authorizes repository writes. Do not use `--force` unless replacement of every reported file is intentional. +6. Verify with `committy --non-interactive -q lint --repo-path --output json` and a representative branch dry run. + +Git hooks and CI are enforcement. Agent instructions are workflow guidance and should stay short. diff --git a/plugins/committy/skills/committy-enforce/agents/openai.yaml b/plugins/committy/skills/committy-enforce/agents/openai.yaml new file mode 100644 index 0000000..f9edb57 --- /dev/null +++ b/plugins/committy/skills/committy-enforce/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Enforce with Committy" + short_description: "Install Git and CI convention checks" + default_prompt: "Use $committy-enforce to preview convention enforcement for this repository." diff --git a/plugins/committy/skills/committy-release/SKILL.md b/plugins/committy/skills/committy-release/SKILL.md new file mode 100644 index 0000000..8177a54 --- /dev/null +++ b/plugins/committy/skills/committy-release/SKILL.md @@ -0,0 +1,20 @@ +--- +name: committy-release +description: Plan and apply version bumps, changelogs, tags, and prereleases with Committy. Use when an agent is asked to prepare, preview, tag, or publish a release, or inspect the release impact of commit history. +license: Apache-2.0 +allowed-tools: Bash Read Grep Glob +metadata: + version: "0.1.0" + requires-committy: ">=1.9.0" +--- + +# Release with Committy + +1. Validate repository setup with `committy -q config validate --repo-path --output json`. +2. Preview the release using `committy --non-interactive -q bump --repo-path --dry-run --output json`. Use an explicit increment or prerelease only when requested. +3. For narrower work, preview `changelog --dry-run` or `tag --dry-run --no-fetch` directly. +4. Inspect versions, changed files, tags, changelog, publish state, and errors. Preserve the previewed arguments. +5. Apply only after mutation is authorized. Publishing additionally requires the CLI confirmation flag; never infer remote-mutation consent. +6. Report resulting versions, tags, and publish state. + +Use the `committy-commit` skill for ordinary commits and `committy-enforce` for hooks or CI setup. diff --git a/plugins/committy/skills/committy-release/agents/openai.yaml b/plugins/committy/skills/committy-release/agents/openai.yaml new file mode 100644 index 0000000..67029b4 --- /dev/null +++ b/plugins/committy/skills/committy-release/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Release with Committy" + short_description: "Plan and apply compliant releases" + default_prompt: "Use $committy-release to preview the next release." diff --git a/src/ai/mod.rs b/src/ai/mod.rs index 4dfdf83..d245779 100644 --- a/src/ai/mod.rs +++ b/src/ai/mod.rs @@ -1,4 +1,4 @@ -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use std::time::Duration; use thiserror::Error; @@ -8,8 +8,6 @@ pub enum LlmError { NotConfigured, #[error("AI provider request failed: {0}")] RequestFailed(String), - #[error("AI provider timeout")] - _Timeout, #[error("AI response parse error: {0}")] Parse(String), } @@ -27,12 +25,79 @@ pub trait LlmClient: Send + Sync { ) -> Result; } -// #[derive(Debug, Clone, Copy, Serialize, Deserialize)] -// #[serde(rename_all = "kebab-case")] -// pub enum LlmProvider { -// OpenRouter, -// Ollama, -// } +/// JSON Schema describing [`AiCommitSuggestion`]. +/// +/// Ollama constrains decoding to a supplied schema, which is stricter than +/// asking for "some JSON" and removes most parse failures. +fn suggestion_schema() -> serde_json::Value { + serde_json::json!({ + "type": "object", + "properties": { + "commit_type": {"type": "string"}, + "short": {"type": "string"}, + "scope": {"type": "string"}, + "long": {"type": "string"}, + "message": {"type": "string"}, + }, + }) +} + +fn chat_messages(system_prompt: &str, user_prompt: &str) -> serde_json::Value { + serde_json::json!([ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ]) +} + +/// Build the OpenRouter `/chat/completions` request body. +fn openrouter_request_body( + model: &str, + system_prompt: &str, + user_prompt: &str, + json_mode: bool, + max_tokens: u32, + temperature: f32, +) -> serde_json::Value { + let mut body = serde_json::json!({ + "model": model, + "messages": chat_messages(system_prompt, user_prompt), + "max_tokens": max_tokens, + "temperature": temperature, + }); + if json_mode { + body["response_format"] = serde_json::json!({"type": "json_object"}); + } + body +} + +/// Build the Ollama `/api/chat` request body. +/// +/// Two details are load-bearing and were wrong before: `stream` must be false +/// (the endpoint defaults to true and would return newline-delimited events +/// that cannot deserialize as one object), and `format` is a **top-level** +/// field, not an entry under `options`. +fn ollama_request_body( + model: &str, + system_prompt: &str, + user_prompt: &str, + json_mode: bool, + max_tokens: u32, + temperature: f32, +) -> serde_json::Value { + let mut body = serde_json::json!({ + "model": model, + "messages": chat_messages(system_prompt, user_prompt), + "stream": false, + "options": { + "temperature": temperature, + "num_predict": max_tokens as i32, + }, + }); + if json_mode { + body["format"] = suggestion_schema(); + } + body +} pub struct OpenRouterClient { pub base_url: String, @@ -63,42 +128,14 @@ impl LlmClient for OpenRouterClient { let url = format!("{}/chat/completions", self.base_url.trim_end_matches('/')); - #[derive(Serialize)] - struct Message<'a> { - role: &'a str, - content: &'a str, - } - #[derive(Serialize)] - struct RequestBody<'a> { - model: &'a str, - messages: Vec>, - max_tokens: u32, - temperature: f32, - #[serde(skip_serializing_if = "Option::is_none")] - response_format: Option, - } - let response_format = if json_mode { - Some(serde_json::json!({"type": "json_object"})) - } else { - None - }; - - let body = RequestBody { - model: &self.model, - messages: vec![ - Message { - role: "system", - content: system_prompt, - }, - Message { - role: "user", - content: user_prompt, - }, - ], + let body = openrouter_request_body( + &self.model, + system_prompt, + user_prompt, + json_mode, max_tokens, temperature, - response_format, - }; + ); let client = reqwest::Client::builder() .timeout(Duration::from_millis(timeout_ms)) @@ -159,43 +196,14 @@ impl LlmClient for OllamaClient { ) -> Result { let url = format!("{}/api/chat", self.base_url.trim_end_matches('/')); - #[derive(Serialize)] - struct Message<'a> { - role: &'a str, - content: &'a str, - } - #[derive(Serialize)] - struct RequestBody<'a> { - model: &'a str, - messages: Vec>, - options: OllamaOptions, - } - #[derive(Serialize)] - struct OllamaOptions { - temperature: f32, - num_predict: i32, - #[serde(skip_serializing_if = "Option::is_none")] - format: Option<&'static str>, - } - - let body = RequestBody { - model: &self.model, - messages: vec![ - Message { - role: "system", - content: system_prompt, - }, - Message { - role: "user", - content: user_prompt, - }, - ], - options: OllamaOptions { - temperature, - num_predict: max_tokens as i32, - format: if json_mode { Some("json") } else { None }, - }, - }; + let body = ollama_request_body( + &self.model, + system_prompt, + user_prompt, + json_mode, + max_tokens, + temperature, + ); let client = reqwest::Client::builder() .timeout(Duration::from_millis(timeout_ms)) @@ -239,3 +247,73 @@ pub struct AiCommitSuggestion { pub long: Option, pub message: Option, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ollama_body_disables_streaming() { + let body = ollama_request_body("llama3.2", "sys", "user", true, 256, 0.2); + assert_eq!( + body["stream"], + serde_json::Value::Bool(false), + "Ollama defaults /api/chat to stream:true; a streaming response is \ + newline-delimited and will not deserialize as one object" + ); + } + + #[test] + fn ollama_body_puts_format_at_the_top_level() { + let body = ollama_request_body("llama3.2", "sys", "user", true, 256, 0.2); + assert!( + body["format"].is_object(), + "Ollama reads `format` as a top-level field; nesting it under `options` \ + silently disables constrained decoding. Got: {body}" + ); + assert!( + body["options"]["format"].is_null(), + "`format` must not also be left inside `options`" + ); + } + + #[test] + fn ollama_body_constrains_to_the_suggestion_schema() { + let body = ollama_request_body("llama3.2", "sys", "user", true, 256, 0.2); + let props = &body["format"]["properties"]; + for field in ["commit_type", "short", "scope", "long", "message"] { + assert_eq!( + props[field]["type"], + serde_json::Value::String("string".into()), + "schema must describe AiCommitSuggestion::{field}" + ); + } + } + + #[test] + fn ollama_body_omits_format_when_json_mode_is_off() { + let body = ollama_request_body("llama3.2", "sys", "user", false, 256, 0.2); + assert!(body["format"].is_null()); + assert_eq!(body["stream"], serde_json::Value::Bool(false)); + } + + #[test] + fn ollama_body_carries_sampling_options() { + let body = ollama_request_body("llama3.2", "sys", "user", true, 512, 0.7); + assert_eq!(body["options"]["num_predict"], serde_json::json!(512)); + assert_eq!(body["model"], serde_json::json!("llama3.2")); + assert_eq!(body["messages"][0]["role"], serde_json::json!("system")); + assert_eq!(body["messages"][1]["content"], serde_json::json!("user")); + } + + #[test] + fn openrouter_body_requests_json_object_when_asked() { + let body = openrouter_request_body("m", "sys", "user", true, 256, 0.2); + assert_eq!( + body["response_format"]["type"], + serde_json::json!("json_object") + ); + let plain = openrouter_request_body("m", "sys", "user", false, 256, 0.2); + assert!(plain["response_format"].is_null()); + } +} diff --git a/src/cli/commands/amend.rs b/src/cli/commands/amend.rs index 19f6b5d..568042a 100644 --- a/src/cli/commands/amend.rs +++ b/src/cli/commands/amend.rs @@ -1,4 +1,5 @@ use crate::cli::commands::commit::CommitCommand; +use crate::cli::output::MachineContext; use crate::cli::Command; use crate::error::CliError; use std::path::PathBuf; @@ -62,6 +63,13 @@ impl Command for AmendCommand { commit.execute_with_command_name(non_interactive, "amend") } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "amend", + dry_run: self.dry_run, + }) + } } impl Default for AmendCommand { diff --git a/src/cli/commands/branch.rs b/src/cli/commands/branch.rs index 4e587d7..88d794b 100644 --- a/src/cli/commands/branch.rs +++ b/src/cli/commands/branch.rs @@ -1,7 +1,10 @@ use std::collections::HashMap; +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; -use crate::config::BRANCH_TYPES; +use crate::config::hierarchy::MergedConfig; +use crate::config::repository::BranchRulesConfig; +use crate::convention::Convention; use crate::error::CliError; use crate::git; use crate::input; @@ -46,6 +49,7 @@ pub struct BranchCommand { #[derive(Debug, Serialize)] struct BranchCommandOutput { + api_version: u8, command: String, ok: bool, dry_run: bool, @@ -84,7 +88,22 @@ struct BranchPlan { impl Command for BranchCommand { fn execute(&self, non_interactive: bool) -> Result<(), CliError> { - let Some(plan) = self.build_plan(non_interactive)? else { + let repo = git::discover_repository_from(&self.repo_path)?; + let repo_path = repo + .workdir() + .ok_or_else(|| CliError::GitError(git2::Error::from_str("No working directory")))?; + let convention = Convention::load(repo_path)?; + let mut allowed_types = convention.allowed_branch_types(); + if allowed_types.is_empty() { + allowed_types = Convention::from_config(Default::default())?.allowed_branch_types(); + } + let merged = MergedConfig::load(repo_path).map_err(|e| CliError::Generic(e.to_string()))?; + let rules = merged + .repository_config() + .map(|config| config.branch_rules.clone()) + .unwrap_or_default(); + + let Some(plan) = self.build_plan(non_interactive, &allowed_types, &rules)? else { return Ok(()); }; git::validate_branch_name(&plan.branch_name)?; @@ -98,6 +117,7 @@ impl Command for BranchCommand { } let output = BranchCommandOutput { + api_version: API_VERSION, command: "branch".into(), ok: errors.is_empty(), dry_run: self.dry_run, @@ -157,10 +177,22 @@ impl Command for BranchCommand { Ok(()) } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "branch", + dry_run: self.dry_run, + }) + } } impl BranchCommand { - fn build_plan(&self, non_interactive: bool) -> Result, CliError> { + fn build_plan( + &self, + non_interactive: bool, + allowed_types: &[String], + rules: &BranchRulesConfig, + ) -> Result, CliError> { if self.name.is_some() && (self.branch_type.is_some() || self.ticket.is_some() || self.subject.is_some()) { @@ -171,7 +203,16 @@ impl BranchCommand { } if let Some(name) = &self.name { - let (branch_type, ticket, subject) = parse_branch_name(name); + let (branch_type, ticket, subject) = parse_branch_name(name, rules)?; + if rules.enforce_explicit_names { + validate_branch_type(&branch_type, allowed_types)?; + validate_ticket(&ticket, rules)?; + if subject.is_empty() { + return Err(CliError::InputError( + "Explicit branch name must include a subject".to_string(), + )); + } + } return Ok(Some(BranchPlan { branch_name: name.clone(), branch_type, @@ -182,7 +223,7 @@ impl BranchCommand { } if self.branch_type.is_some() || self.ticket.is_some() || self.subject.is_some() { - return self.build_structured_plan(non_interactive); + return self.build_structured_plan(non_interactive, allowed_types, rules); } if non_interactive { @@ -192,8 +233,9 @@ impl BranchCommand { )); } - let branch_type = input::select_branch_type()?; + let branch_type = input::select_branch_type_from(allowed_types)?; let ticket = input::input_ticket()?; + validate_ticket(&ticket, rules)?; let subject = input::input_subject()?; let branch_name = if ticket.is_empty() { @@ -221,10 +263,15 @@ impl BranchCommand { })) } - fn build_structured_plan(&self, non_interactive: bool) -> Result, CliError> { + fn build_structured_plan( + &self, + non_interactive: bool, + allowed_types: &[String], + rules: &BranchRulesConfig, + ) -> Result, CliError> { let branch_type = match &self.branch_type { - Some(branch_type) => validate_branch_type(branch_type)?, - None if !non_interactive => input::select_branch_type()?, + Some(branch_type) => validate_branch_type(branch_type, allowed_types)?, + None if !non_interactive => input::select_branch_type_from(allowed_types)?, None => { return Err(CliError::InputError( "Branch type is required when using structured branch flags in non-interactive mode" @@ -238,6 +285,7 @@ impl BranchCommand { None if !non_interactive => input::input_ticket()?, None => String::new(), }; + validate_ticket(&ticket, rules)?; let subject = match &self.subject { Some(subject) => validate_structured_section(subject, "subject")?, @@ -298,14 +346,37 @@ fn build_branch_name(branch_type: &str, ticket: &str, subject: &str) -> String { } } -fn validate_branch_type(branch_type: &str) -> Result { - if BRANCH_TYPES.iter().any(|known| known == &branch_type) { +fn validate_branch_type(branch_type: &str, allowed_types: &[String]) -> Result { + if allowed_types.iter().any(|known| known == branch_type) { Ok(branch_type.to_string()) } else { Err(CliError::InputError(format!( "Invalid branch type '{}'. Valid branch types are: {}", branch_type, - BRANCH_TYPES.join(", ") + allowed_types.join(", ") + ))) + } +} + +fn validate_ticket(ticket: &str, rules: &BranchRulesConfig) -> Result<(), CliError> { + if ticket.is_empty() { + return if rules.require_ticket { + Err(CliError::InputError( + "A ticket is required by branch_rules".to_string(), + )) + } else { + Ok(()) + }; + } + + let pattern = regex::Regex::new(&rules.ticket_pattern) + .map_err(|error| CliError::RegexError(error.to_string()))?; + if pattern.is_match(ticket) { + Ok(()) + } else { + Err(CliError::InputError(format!( + "Ticket '{}' does not match branch_rules.ticket_pattern '{}'", + ticket, rules.ticket_pattern ))) } } @@ -316,10 +387,24 @@ fn validate_structured_section(value: &str, field_name: &str) -> Result (String, String, String) { - let mut branch_parts = name.splitn(2, '-'); +fn parse_branch_name( + name: &str, + rules: &BranchRulesConfig, +) -> Result<(String, String, String), CliError> { + let mut branch_parts = name.splitn(3, '-'); let branch_type = branch_parts.next().unwrap_or("unknown").to_string(); - let ticket = String::new(); - let subject = branch_parts.next().unwrap_or("").to_string(); - (branch_type, ticket, subject) + let second = branch_parts.next().unwrap_or(""); + let third = branch_parts.next(); + let ticket_pattern = regex::Regex::new(&rules.ticket_pattern) + .map_err(|error| CliError::RegexError(error.to_string()))?; + let (ticket, subject) = if let Some(subject) = third { + if rules.require_ticket || ticket_pattern.is_match(second) { + (second.to_string(), subject.to_string()) + } else { + (String::new(), format!("{second}-{subject}")) + } + } else { + (String::new(), second.to_string()) + }; + Ok((branch_type, ticket, subject)) } diff --git a/src/cli/commands/commit.rs b/src/cli/commands/commit.rs index e192f58..fb87feb 100644 --- a/src/cli/commands/commit.rs +++ b/src/cli/commands/commit.rs @@ -2,6 +2,7 @@ use serde_json::Value; use std::collections::HashMap; use std::path::{Path, PathBuf}; +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; use crate::config::hierarchy::MergedConfig; use crate::config::repository::RepositoryConfig; @@ -63,6 +64,7 @@ pub struct CommitCommand { #[derive(Debug, Serialize)] struct CommitCommandOutput { + api_version: u8, command: String, ok: bool, dry_run: bool, @@ -79,6 +81,13 @@ impl Command for CommitCommand { fn execute(&self, non_interactive: bool) -> Result<(), CliError> { self.execute_with_command_name(non_interactive, "commit") } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "commit", + dry_run: self.dry_run, + }) + } } impl CommitCommand { @@ -280,6 +289,7 @@ impl CommitCommand { .map_err(|e| CliError::Generic(e.to_string()))?; if !validation_issues.is_empty() { let output = CommitCommandOutput { + api_version: API_VERSION, command: command_name.into(), ok: false, dry_run: self.dry_run, @@ -303,6 +313,7 @@ impl CommitCommand { self.execute_workflow_if_configured(repo_path, &full_message, !self.dry_run)?; let output = CommitCommandOutput { + api_version: API_VERSION, command: command_name.into(), ok: true, dry_run: self.dry_run, diff --git a/src/cli/commands/config.rs b/src/cli/commands/config.rs index 696020b..9b932bb 100644 --- a/src/cli/commands/config.rs +++ b/src/cli/commands/config.rs @@ -455,6 +455,7 @@ pub fn scaffold(repo_path: &Path, dry_run: bool, output: &str) -> Result<()> { dependencies: vec![], scopes: Default::default(), commit_rules: CommitRulesConfig::default(), + branch_rules: Default::default(), git: Default::default(), convention: Some(Default::default()), release: Some(Default::default()), @@ -547,6 +548,7 @@ mod tests { dependencies: vec![], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/cli/commands/group_commit.rs b/src/cli/commands/group_commit.rs index 7284284..20d540e 100644 --- a/src/cli/commands/group_commit.rs +++ b/src/cli/commands/group_commit.rs @@ -1,4 +1,5 @@ use crate::ai::{AiCommitSuggestion, LlmClient, LlmError, OllamaClient, OpenRouterClient}; +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; use crate::error::CliError; use crate::git::format_commit_message; @@ -35,6 +36,7 @@ pub struct PlanGroup { #[derive(Debug, Serialize)] pub struct GroupCommitPlanResult { + pub api_version: u8, pub command: String, pub mode: String, pub ok: bool, @@ -54,6 +56,7 @@ pub struct CommitRecord { #[derive(Debug, Serialize)] pub struct GroupCommitApplyResult { + pub api_version: u8, pub command: String, pub mode: String, pub ok: bool, @@ -139,10 +142,6 @@ pub struct GroupCommitCommand { #[structopt(long = "ai-file-limit", default_value = "20")] ai_file_limit: usize, - /// Diff lines per file sent to AI - #[structopt(long = "ai-diff-lines-per-file", default_value = "80")] - _ai_diff_lines_per_file: usize, - /// Allow sending sensitive content to external AI providers #[structopt(long = "ai-allow-sensitive")] ai_allow_sensitive: bool, @@ -178,7 +177,6 @@ impl Default for GroupCommitCommand { ai_system_prompt: None, ai_system_prompt_file: None, ai_file_limit: 20, - _ai_diff_lines_per_file: 80, ai_allow_sensitive: false, git_config: vec![], repo_path: PathBuf::from("."), @@ -315,41 +313,7 @@ impl Command for GroupCommitCommand { })?; let _git_command_config = crate::git::resolve_git_command_config(repo_path, &self.git_config)?; - let mut by_group: std::collections::BTreeMap> = [ - (GroupName::Docs, vec![]), - (GroupName::Tests, vec![]), - (GroupName::Ci, vec![]), - (GroupName::Deps, vec![]), - (GroupName::Build, vec![]), - (GroupName::Chore, vec![]), - (GroupName::Code, vec![]), - ] - .into_iter() - .collect(); - - for f in files { - let g = classify_file(&f); - if let Some(v) = by_group.get_mut(&g) { - v.push(f); - } - } - - let mut groups: Vec = Vec::new(); - for (name, files) in by_group.into_iter() { - if files.is_empty() { - continue; - } - let commit_type = default_type_for(&name).to_string(); - let short = default_short_for(&name).to_string(); - let message = format_commit_message(&commit_type, false, "", &short, ""); - groups.push(PlanGroup { - name, - commit_type, - files, - suggested_message: message, - issues: None, - }); - } + let mut groups = build_groups(files); let mut errors: Vec = Vec::new(); @@ -380,38 +344,14 @@ impl Command for GroupCommitCommand { .map_err(|e| CliError::Generic(e.to_string()))?; for g in groups.iter_mut() { - let files_preview: Vec = if self.ai_allow_sensitive { - let mut v = g - .files - .iter() - .take(self.ai_file_limit) - .cloned() - .collect::>(); - if g.files.len() > self.ai_file_limit { - v.push("...".to_string()); - } - v - } else { - vec![] - }; - let user_prompt = if self.ai_allow_sensitive { - format!( - "Group: {}\nDefault type: {}\nDefault short: {}\nFiles (truncated):\n- {}\nReturn a JSON object with fields: commit_type, short, scope, long, message.", - group_name_str(g.name), - g.commit_type, - default_short_for(&g.name), - files_preview.join("\n- ") - ) - } else { - format!( - "Group: {}\nDefault type: {}\nDefault short: {}\nWithout revealing code or filenames, suggest a better short description if needed. Return JSON.", - group_name_str(g.name), - g.commit_type, - default_short_for(&g.name) - ) - }; + let user_prompt = ai_user_prompt( + &g.name, + &g.commit_type, + &g.files, + self.ai_allow_sensitive, + self.ai_file_limit, + ); - // Prepare client per provider let result: Result = match provider { "openrouter" => { let base = self @@ -508,6 +448,7 @@ impl Command for GroupCommitCommand { .map_err(|e| CliError::Generic(e.to_string()))?; let res = GroupCommitPlanResult { + api_version: API_VERSION, command: "group-commit".into(), mode: "plan".into(), ok: errors.is_empty(), @@ -540,41 +481,7 @@ impl Command for GroupCommitCommand { crate::git::resolve_git_command_config(repo_path, &self.git_config)?; // Build groups as in plan let files = list_changed_files_from(&self.repo_path, self.include_unstaged)?; - let mut by_group: std::collections::BTreeMap> = [ - (GroupName::Docs, vec![]), - (GroupName::Tests, vec![]), - (GroupName::Ci, vec![]), - (GroupName::Deps, vec![]), - (GroupName::Build, vec![]), - (GroupName::Chore, vec![]), - (GroupName::Code, vec![]), - ] - .into_iter() - .collect(); - - for f in files { - let g = classify_file(&f); - if let Some(v) = by_group.get_mut(&g) { - v.push(f); - } - } - - let mut groups: Vec = Vec::new(); - for (name, files) in by_group.into_iter() { - if files.is_empty() { - continue; - } - let commit_type = default_type_for(&name).to_string(); - let short = default_short_for(&name).to_string(); - let message = format_commit_message(&commit_type, false, "", &short, ""); - groups.push(PlanGroup { - name, - commit_type, - files, - suggested_message: message, - issues: None, - }); - } + let mut groups = build_groups(files); let mut errors: Vec = Vec::new(); @@ -604,32 +511,13 @@ impl Command for GroupCommitCommand { .map_err(|e| CliError::Generic(e.to_string()))?; for g in groups.iter_mut() { - let files_preview: Vec = if self.ai_allow_sensitive { - let mut v = g - .files - .iter() - .take(self.ai_file_limit) - .cloned() - .collect::>(); - if g.files.len() > self.ai_file_limit { - v.push("...".to_string()); - } - v - } else { - vec![] - }; - - let user_prompt = if self.ai_allow_sensitive { - format!( - "Group: {}\nDefault type: {}\nDefault short: {}\nFiles (truncated):\n- {}\nReturn a JSON object with fields: commit_type, short, scope, long, message.", - group_name_str(g.name), g.commit_type, default_short_for(&g.name), files_preview.join("\n- ") - ) - } else { - format!( - "Group: {}\nDefault type: {}\nDefault short: {}\nWithout revealing code or filenames, suggest a better short description if needed. Return JSON.", - group_name_str(g.name), g.commit_type, default_short_for(&g.name) - ) - }; + let user_prompt = ai_user_prompt( + &g.name, + &g.commit_type, + &g.files, + self.ai_allow_sensitive, + self.ai_file_limit, + ); let result: Result = match provider { "openrouter" => { @@ -852,6 +740,7 @@ impl Command for GroupCommitCommand { let ok = commits.iter().all(|c| c.ok) && errors.is_empty(); let res = GroupCommitApplyResult { + api_version: API_VERSION, command: "group-commit".into(), mode: "apply".into(), ok, @@ -875,6 +764,136 @@ impl Command for GroupCommitCommand { _ => Err(CliError::Generic("invalid mode".into())), } } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "group-commit", + dry_run: self.mode == "plan", + }) + } +} + +/// Summarise a change set without naming any file. +/// +/// The default (non-sensitive) AI path used to send only the group name, which +/// gave the model nothing to improve on. This sends *shape* — how many files, +/// which extensions, which top-level areas — and never a filename, a path +/// below the first segment, or any file content. +fn redacted_shape_summary(files: &[String]) -> String { + use std::collections::BTreeMap; + + let mut extensions: BTreeMap = BTreeMap::new(); + let mut areas: std::collections::BTreeSet = std::collections::BTreeSet::new(); + + for file in files { + let trimmed = file.trim_start_matches("./"); + let extension = std::path::Path::new(trimmed) + .extension() + .and_then(|e| e.to_str()) + .map(|e| format!(".{e}")) + .unwrap_or_else(|| "(no extension)".to_string()); + *extensions.entry(extension).or_insert(0) += 1; + + // First path segment only: "src", "docs", "tests". Anything deeper can + // carry product or customer names. + let area = trimmed + .split('/') + .next() + .filter(|segment| !segment.is_empty() && trimmed.contains('/')) + .unwrap_or("(repository root)"); + areas.insert(area.to_string()); + } + + let extension_list = extensions + .iter() + .map(|(extension, count)| format!("{extension} x{count}")) + .collect::>() + .join(", "); + + format!( + "{} file(s); types: {}; areas: {}", + files.len(), + extension_list, + areas.into_iter().collect::>().join(", ") + ) +} + +/// The per-group user prompt sent to the model. +/// +/// `allow_sensitive` is the only switch that lets real paths leave the machine. +fn ai_user_prompt( + name: &GroupName, + commit_type: &str, + files: &[String], + allow_sensitive: bool, + file_limit: usize, +) -> String { + let header = format!( + "Group: {}\nDefault type: {}\nDefault short: {}", + group_name_str(*name), + commit_type, + default_short_for(name) + ); + + if allow_sensitive { + let mut listed: Vec = files.iter().take(file_limit).cloned().collect(); + if files.len() > file_limit { + listed.push("...".to_string()); + } + format!( + "{header}\nFiles (truncated):\n- {}\nReturn a JSON object with fields: \ + commit_type, short, scope, long, message.", + listed.join("\n- ") + ) + } else { + format!( + "{header}\nChange shape (no filenames or content): {}\nSuggest a better short \ + description for this group. Return a JSON object with fields: commit_type, \ + short, scope, long, message.", + redacted_shape_summary(files) + ) + } +} + +/// Classify a change set into commit groups. +/// +/// Shared by `plan` and `apply`; they previously carried byte-identical copies. +fn build_groups(files: Vec) -> Vec { + let mut by_group: std::collections::BTreeMap> = [ + (GroupName::Docs, vec![]), + (GroupName::Tests, vec![]), + (GroupName::Ci, vec![]), + (GroupName::Deps, vec![]), + (GroupName::Build, vec![]), + (GroupName::Chore, vec![]), + (GroupName::Code, vec![]), + ] + .into_iter() + .collect(); + + for file in files { + let group = classify_file(&file); + if let Some(bucket) = by_group.get_mut(&group) { + bucket.push(file); + } + } + + by_group + .into_iter() + .filter(|(_, files)| !files.is_empty()) + .map(|(name, files)| { + let commit_type = default_type_for(&name).to_string(); + let short = default_short_for(&name).to_string(); + let suggested_message = format_commit_message(&commit_type, false, "", &short, ""); + PlanGroup { + name, + commit_type, + files, + suggested_message, + issues: None, + } + }) + .collect() } fn validate_group_messages( @@ -900,3 +919,85 @@ fn validate_group_messages( Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + fn files() -> Vec { + vec![ + "src/cli/commands/secret_feature.rs".to_string(), + "src/cli/commands/other.rs".to_string(), + "docs/internal/roadmap.md".to_string(), + ] + } + + #[test] + fn shape_summary_describes_without_naming() { + let summary = redacted_shape_summary(&files()); + + assert!( + summary.contains('3'), + "must report the file count: {summary}" + ); + assert!(summary.contains(".rs"), "must report extensions: {summary}"); + assert!(summary.contains(".md"), "must report extensions: {summary}"); + assert!( + summary.contains("src"), + "must report top-level areas: {summary}" + ); + assert!( + summary.contains("docs"), + "must report top-level areas: {summary}" + ); + + for leaked in ["secret_feature", "other.rs", "roadmap.md", "internal"] { + assert!( + !summary.contains(leaked), + "shape summary leaked `{leaked}`: {summary}" + ); + } + } + + #[test] + fn default_prompt_carries_shape_but_no_paths() { + let prompt = ai_user_prompt(&GroupName::Code, "feat", &files(), false, 20); + + assert!( + prompt.contains(&redacted_shape_summary(&files())), + "the safe prompt must include the redacted shape: {prompt}" + ); + for leaked in ["secret_feature", "roadmap.md", "src/cli/commands"] { + assert!(!prompt.contains(leaked), "safe prompt leaked `{leaked}`"); + } + } + + #[test] + fn sensitive_prompt_lists_paths_and_respects_the_limit() { + let prompt = ai_user_prompt(&GroupName::Code, "feat", &files(), true, 2); + + assert!(prompt.contains("src/cli/commands/secret_feature.rs")); + assert!( + prompt.contains("..."), + "over-limit file lists must be elided: {prompt}" + ); + assert!( + !prompt.contains("docs/internal/roadmap.md"), + "third path exceeds the limit of 2" + ); + } + + #[test] + fn build_groups_is_shared_between_plan_and_apply() { + let groups = build_groups(files()); + + let names: Vec = groups.iter().map(|g| g.name).collect(); + assert!(names.contains(&GroupName::Code)); + assert!(names.contains(&GroupName::Docs)); + assert!( + groups.iter().all(|g| !g.files.is_empty()), + "empty groups must be dropped" + ); + assert!(groups.iter().all(|g| !g.suggested_message.is_empty())); + } +} diff --git a/src/cli/commands/hooks.rs b/src/cli/commands/hooks.rs new file mode 100644 index 0000000..d906371 --- /dev/null +++ b/src/cli/commands/hooks.rs @@ -0,0 +1,332 @@ +use crate::cli::output::{MachineContext, API_VERSION}; +use crate::cli::Command; +use crate::error::CliError; +use crate::git; +use crate::linter::{check_message_format_for_repo, CommitIssue, CommitLinter}; +use serde::Serialize; +use std::fs; +use std::io::{self, Read}; +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; +use std::path::{Path, PathBuf}; +use structopt::StructOpt; + +const COMMIT_MSG_HOOK: &str = r#"#!/bin/sh +exec committy --non-interactive -q hooks run commit-msg --message-file "$1" --repo-path . +"#; + +const PRE_PUSH_HOOK: &str = r#"#!/bin/sh +exec committy --non-interactive -q hooks run pre-push --repo-path . +"#; + +const GITHUB_WORKFLOW: &str = r#"name: Commit conventions + +on: + pull_request: + +jobs: + lint-commits: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + # Full history: linting a PR needs both ends of the commit range. + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@stable + - name: Cache the Committy build + uses: Swatinem/rust-cache@v2 + with: + key: committy-cli + - name: Install Committy + run: cargo install committy --locked + - name: Lint pull request commits + env: + COMMITTY_NONINTERACTIVE: "1" + run: committy lint --from-ref "${{ github.event.pull_request.base.sha }}" --to-ref "${{ github.sha }}" +"#; + +#[derive(Debug, StructOpt)] +pub struct HooksCommand { + #[structopt(subcommand)] + action: HooksAction, +} + +#[derive(Debug, StructOpt)] +enum HooksAction { + #[structopt(name = "install")] + Install(HooksInstallCommand), + #[structopt(name = "run")] + Run(HooksRunCommand), +} + +#[derive(Debug, StructOpt)] +struct HooksInstallCommand { + #[structopt(long, help = "Also install a GitHub Actions commit lint workflow")] + with_ci: bool, + #[structopt(long, help = "Replace existing hook and workflow files")] + force: bool, + #[structopt(long, help = "Preview files without writing them")] + dry_run: bool, + #[structopt(long, default_value = "json", possible_values = &["text", "json"])] + output: String, + #[structopt(long, default_value = ".", parse(from_os_str))] + repo_path: PathBuf, +} + +#[derive(Debug, StructOpt)] +struct HooksRunCommand { + #[structopt(subcommand)] + hook: HookRunner, +} + +#[derive(Debug, StructOpt)] +enum HookRunner { + #[structopt(name = "commit-msg")] + CommitMsg(CommitMsgHookCommand), + #[structopt(name = "pre-push")] + PrePush(PrePushHookCommand), +} + +#[derive(Debug, StructOpt)] +struct CommitMsgHookCommand { + #[structopt(long = "message-file", parse(from_os_str))] + message_file: PathBuf, + #[structopt(long, default_value = "text", possible_values = &["text", "json"])] + output: String, + #[structopt(long, default_value = ".", parse(from_os_str))] + repo_path: PathBuf, +} + +#[derive(Debug, StructOpt)] +struct PrePushHookCommand { + #[structopt(long, default_value = "text", possible_values = &["text", "json"])] + output: String, + #[structopt(long, default_value = ".", parse(from_os_str))] + repo_path: PathBuf, +} + +#[derive(Debug, Serialize)] +struct HooksInstallOutput { + api_version: u8, + command: &'static str, + ok: bool, + dry_run: bool, + installed: bool, + files: Vec, + errors: Option>, +} + +#[derive(Debug, Serialize)] +struct HookRunOutput<'a, T: Serialize> { + api_version: u8, + command: &'static str, + hook: &'static str, + ok: bool, + dry_run: bool, + count: usize, + issues: &'a [T], + errors: Option>, +} + +impl Command for HooksCommand { + fn execute(&self, _non_interactive: bool) -> Result<(), CliError> { + match &self.action { + HooksAction::Install(command) => command.execute(), + HooksAction::Run(command) => command.execute(), + } + } + + fn machine_context(&self) -> Option { + let (json, dry_run) = match &self.action { + HooksAction::Install(command) => (command.output == "json", command.dry_run), + HooksAction::Run(HooksRunCommand { + hook: HookRunner::CommitMsg(command), + }) => (command.output == "json", false), + HooksAction::Run(HooksRunCommand { + hook: HookRunner::PrePush(command), + }) => (command.output == "json", false), + }; + json.then_some(MachineContext { + command: "hooks", + dry_run, + }) + } +} + +impl HooksInstallCommand { + fn execute(&self) -> Result<(), CliError> { + let repo = git::discover_repository_from(&self.repo_path)?; + let workdir = repo + .workdir() + .ok_or_else(|| CliError::GitError(git2::Error::from_str("No working directory")))?; + let hooks_dir = repo.commondir().join("hooks"); + let mut files = vec![ + (hooks_dir.join("commit-msg"), COMMIT_MSG_HOOK), + (hooks_dir.join("pre-push"), PRE_PUSH_HOOK), + ]; + if self.with_ci { + files.push(( + workdir.join(".github/workflows/committy.yml"), + GITHUB_WORKFLOW, + )); + } + + if !self.force { + if let Some((path, _)) = files.iter().find(|(path, _)| path.exists()) { + return Err(CliError::InputError(format!( + "Refusing to replace existing file {}. Use --force to replace it.", + path.display() + ))); + } + } + + let display_files = files + .iter() + .map(|(path, _)| display_path(workdir, path)) + .collect::>(); + if !self.dry_run { + for (path, contents) in &files { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + fs::write(path, contents)?; + if path.starts_with(&hooks_dir) { + set_hook_executable(path)?; + } + } + } + + let payload = HooksInstallOutput { + api_version: API_VERSION, + command: "hooks", + ok: true, + dry_run: self.dry_run, + installed: !self.dry_run, + files: display_files, + errors: None, + }; + if self.output == "json" { + println!("{}", serde_json::to_string(&payload).unwrap()); + } else if self.dry_run { + println!("Would install {} enforcement files", payload.files.len()); + } else { + println!("Installed {} enforcement files", payload.files.len()); + } + Ok(()) + } +} + +#[cfg(unix)] +fn set_hook_executable(path: &Path) -> Result<(), CliError> { + let mut permissions = fs::metadata(path)?.permissions(); + permissions.set_mode(permissions.mode() | 0o755); + fs::set_permissions(path, permissions)?; + Ok(()) +} + +#[cfg(not(unix))] +fn set_hook_executable(_path: &Path) -> Result<(), CliError> { + Ok(()) +} + +impl HooksRunCommand { + fn execute(&self) -> Result<(), CliError> { + match &self.hook { + HookRunner::CommitMsg(command) => command.execute(), + HookRunner::PrePush(command) => command.execute(), + } + } +} + +impl CommitMsgHookCommand { + fn execute(&self) -> Result<(), CliError> { + let message = fs::read_to_string(&self.message_file)?; + let issues = check_message_format_for_repo(&self.repo_path, &message) + .map_err(|error| CliError::Generic(error.to_string()))?; + print_hook_result("commit-msg", &self.output, &issues)?; + if issues.is_empty() { + Ok(()) + } else { + Err(CliError::LintIssues(issues.len())) + } + } +} + +impl PrePushHookCommand { + fn execute(&self) -> Result<(), CliError> { + let mut input = String::new(); + io::stdin().read_to_string(&mut input)?; + let repo_path = self + .repo_path + .to_str() + .ok_or_else(|| CliError::InputError("Invalid repo path".to_string()))?; + let linter = + CommitLinter::new(repo_path).map_err(|error| CliError::Generic(error.to_string()))?; + let mut issues = Vec::::new(); + for line in input.lines() { + let fields = line.split_whitespace().collect::>(); + if fields.len() != 4 { + return Err(CliError::InputError( + "Expected pre-push input: " + .to_string(), + )); + } + let local_sha = fields[1]; + let remote_sha = fields[3]; + if is_zero_oid(local_sha) { + continue; + } + let from_ref = (!is_zero_oid(remote_sha)).then_some(remote_sha); + issues.extend( + linter + .check_rev_range(from_ref, Some(local_sha)) + .map_err(|error| CliError::Generic(error.to_string()))?, + ); + } + print_hook_result("pre-push", &self.output, &issues)?; + if issues.is_empty() { + Ok(()) + } else { + Err(CliError::LintIssues(issues.len())) + } + } +} + +fn print_hook_result( + hook: &'static str, + output: &str, + issues: &[T], +) -> Result<(), CliError> { + if output == "json" { + println!( + "{}", + serde_json::to_string(&HookRunOutput { + api_version: API_VERSION, + command: "hooks", + hook, + ok: issues.is_empty(), + dry_run: false, + count: issues.len(), + issues, + errors: None, + }) + .map_err(|error| CliError::Generic(error.to_string()))? + ); + } else if issues.is_empty() { + println!("{hook} policy passed"); + } else { + eprintln!("{hook} policy found {} issue(s)", issues.len()); + } + Ok(()) +} + +fn display_path(workdir: &Path, path: &Path) -> String { + path.strip_prefix(workdir) + .unwrap_or(path) + .display() + .to_string() +} + +fn is_zero_oid(value: &str) -> bool { + !value.is_empty() && value.bytes().all(|byte| byte == b'0') +} diff --git a/src/cli/commands/init.rs b/src/cli/commands/init.rs index b166a4e..5290978 100644 --- a/src/cli/commands/init.rs +++ b/src/cli/commands/init.rs @@ -177,6 +177,7 @@ impl Command for InitCommand { mappings: vec![], }, commit_rules: CommitRulesConfig::default(), + branch_rules: Default::default(), git: Default::default(), convention: Some(Default::default()), release: Some(Default::default()), diff --git a/src/cli/commands/lint.rs b/src/cli/commands/lint.rs index 8240257..8ffa48e 100644 --- a/src/cli/commands/lint.rs +++ b/src/cli/commands/lint.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; use serde::Serialize; use structopt::StructOpt; +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; use crate::error::CliError; use crate::linter::{check_message_format_for_repo, CommitIssue, CommitLinter}; @@ -82,6 +83,13 @@ impl Command for LintCommand { Err(CliError::LintIssues(issues.len())) } } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "lint", + dry_run: false, + }) + } } impl LintCommand { @@ -129,6 +137,7 @@ fn print_history_output(output: &str, issues: &[CommitIssue]) -> Result<(), CliE if output == "json" { #[derive(Serialize)] struct LintOutput<'a> { + api_version: u8, command: &'static str, ok: bool, dry_run: bool, @@ -137,6 +146,7 @@ fn print_history_output(output: &str, issues: &[CommitIssue]) -> Result<(), CliE errors: Option>, } let payload = LintOutput { + api_version: API_VERSION, command: "lint", ok: issues.is_empty(), dry_run: false, @@ -165,6 +175,7 @@ fn print_message_output(output: &str, issues: &[String]) -> Result<(), CliError> if output == "json" { #[derive(Serialize)] struct LintMessageOutput<'a> { + api_version: u8, command: &'static str, ok: bool, dry_run: bool, @@ -173,6 +184,7 @@ fn print_message_output(output: &str, issues: &[String]) -> Result<(), CliError> errors: Option>, } let payload = LintMessageOutput { + api_version: API_VERSION, command: "lint", ok: issues.is_empty(), dry_run: false, diff --git a/src/cli/commands/lint_message.rs b/src/cli/commands/lint_message.rs index 880036b..57d7eee 100644 --- a/src/cli/commands/lint_message.rs +++ b/src/cli/commands/lint_message.rs @@ -1,3 +1,4 @@ +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; use crate::error::CliError; use crate::linter::check_message_format_for_repo; @@ -51,6 +52,7 @@ impl Command for LintMessageCommand { if self.output == "json" { #[derive(Serialize)] struct LintMessageOutput<'a> { + api_version: u8, command: &'static str, ok: bool, dry_run: bool, @@ -59,6 +61,7 @@ impl Command for LintMessageCommand { errors: Option>, } let payload = LintMessageOutput { + api_version: API_VERSION, command: "lint-message", ok: issues.is_empty(), dry_run: false, @@ -82,4 +85,11 @@ impl Command for LintMessageCommand { Err(CliError::LintIssues(issues.len())) } } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "lint-message", + dry_run: false, + }) + } } diff --git a/src/cli/commands/mod.rs b/src/cli/commands/mod.rs index 326f6be..6a3b1b4 100644 --- a/src/cli/commands/mod.rs +++ b/src/cli/commands/mod.rs @@ -6,6 +6,7 @@ pub mod commit; pub mod config; pub mod example; pub mod group_commit; +pub mod hooks; pub mod info; pub mod init; pub mod lint; diff --git a/src/cli/commands/schema.rs b/src/cli/commands/schema.rs index 9e8c475..f8754c2 100644 --- a/src/cli/commands/schema.rs +++ b/src/cli/commands/schema.rs @@ -3,7 +3,9 @@ use std::path::PathBuf; use serde::Serialize; use structopt::StructOpt; +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; +use crate::config::convention::ConventionType; use crate::error::CliError; use crate::release::ReleaseEngine; @@ -18,6 +20,7 @@ pub struct SchemaCommand { #[derive(Debug, Serialize)] struct SchemaOutput { + api_version: u8, command: &'static str, ok: bool, dry_run: bool, @@ -25,19 +28,146 @@ struct SchemaOutput { schema: String, parser: String, types: Vec, + type_definitions: Vec, + capabilities: Vec, errors: Option>, } +#[derive(Debug, Serialize)] +struct CapabilityDefinition { + name: &'static str, + description: &'static str, + /// Whether applying this capability changes the repository or a remote. + mutating: bool, + /// Whether the CLI refuses to proceed without an explicit confirmation + /// flag. An agent must never infer this consent from the task alone. + requires_confirmation: bool, +} + +/// Read-only capability: previews and validation. +const fn read_only(name: &'static str, description: &'static str) -> CapabilityDefinition { + CapabilityDefinition { + name, + description, + mutating: false, + requires_confirmation: false, + } +} + +/// Local mutation: changes this checkout, needs no confirmation flag. +const fn local_write(name: &'static str, description: &'static str) -> CapabilityDefinition { + CapabilityDefinition { + name, + description, + mutating: true, + requires_confirmation: false, + } +} + +/// Remote mutation: gated behind an explicit confirmation flag. +const fn remote_write(name: &'static str, description: &'static str) -> CapabilityDefinition { + CapabilityDefinition { + name, + description, + mutating: true, + requires_confirmation: true, + } +} + +fn capabilities() -> Vec { + vec![ + read_only( + "schema.discover", + "Report active convention, types, and this capability list", + ), + read_only( + "branch.preview", + "Resolve and validate a branch without changing git", + ), + local_write("branch.apply", "Create a validated branch"), + read_only( + "branch.lint", + "Validate structured or policy-enforced branch names", + ), + read_only( + "commit.preview", + "Resolve and lint a commit without changing git", + ), + local_write("commit.apply", "Create a validated conventional commit"), + local_write("commit.amend", "Preview or apply a validated amend"), + read_only("commit.lint", "Lint one message or repository history"), + read_only( + "group-commit.plan", + "Group a dirty worktree into coherent commits without changing git", + ), + local_write( + "group-commit.apply", + "Create the planned grouped commits; pushing additionally requires confirmation", + ), + read_only( + "bump.preview", + "Compute the next version from commit history without writing files", + ), + local_write( + "bump.apply", + "Write the computed version to the configured version files", + ), + read_only( + "changelog.preview", + "Render a changelog from git history without writing files", + ), + read_only( + "tag.preview", + "Compute the next tag and release plan without changing git", + ), + local_write( + "tag.apply", + "Create the computed tag in the local repository", + ), + remote_write( + "tag.publish", + "Push tags and publish the release; requires an explicit confirmation flag", + ), + read_only( + "config.validate", + "Validate repository configuration and report findings", + ), + read_only( + "packages.list", + "List configured packages and their resolved versions", + ), + local_write( + "hooks.install", + "Preview or install native Git hooks and CI enforcement", + ), + read_only( + "hooks.commit-msg", + "Enforce commit messages through a native git hook", + ), + read_only( + "hooks.pre-push", + "Enforce outgoing commit history through a native git hook", + ), + ] +} + impl Command for SchemaCommand { fn execute(&self, _non_interactive: bool) -> Result<(), CliError> { let engine = ReleaseEngine::load(&self.repo_path, &[])?; let convention = engine.convention().config(); let types = engine.convention().allowed_types(); + let type_definitions = engine + .convention() + .visible_types() + .into_iter() + .cloned() + .collect(); if self.output == "json" { println!( "{}", serde_json::to_string(&SchemaOutput { + api_version: API_VERSION, command: "schema", ok: true, dry_run: false, @@ -45,6 +175,8 @@ impl Command for SchemaCommand { schema: engine.convention().schema().to_string(), parser: engine.convention().parser_pattern().to_string(), types, + type_definitions, + capabilities: capabilities(), errors: None, }) .map_err(|e| CliError::Generic(e.to_string()))? @@ -54,4 +186,11 @@ impl Command for SchemaCommand { } Ok(()) } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "schema", + dry_run: false, + }) + } } diff --git a/src/cli/commands/tag.rs b/src/cli/commands/tag.rs index 19ad048..05f2c5f 100644 --- a/src/cli/commands/tag.rs +++ b/src/cli/commands/tag.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use crate::cli::output::{MachineContext, API_VERSION}; use crate::cli::Command; use crate::config::hierarchy::MergedConfig; use crate::config::repository::{RepositoryConfig, VersioningStrategy}; @@ -7,6 +8,7 @@ use crate::dependency::updater::DependencyUpdater; use crate::error::CliError; use crate::git; use crate::input; +use crate::scope::detector::ScopeDetector; use crate::telemetry; use crate::versioning::hybrid::HybridVersioning; use crate::versioning::independent::IndependentVersioning; @@ -32,10 +34,16 @@ pub struct TagCommand { #[structopt( short = "b", long = "bump-files", - help = "Want to auto bump the config to the new version (y/N)" + help = "Bump version files to the new version (enabled by default)" )] bump_config_files: bool, + #[structopt( + long = "no-bump-files", + help = "Do not touch version files when tagging" + )] + no_bump_config_files: bool, + #[structopt(flatten)] tag_options: git::TagGeneratorOptions, @@ -59,6 +67,7 @@ pub struct TagCommand { #[derive(Debug, Serialize)] struct TagCommandOutput { + api_version: u8, command: String, ok: bool, dry_run: bool, @@ -77,6 +86,12 @@ impl Command for TagCommand { )); } + if self.bump_config_files && self.no_bump_config_files { + return Err(CliError::InputError( + "Use either --bump-files or --no-bump-files, not both".to_string(), + )); + } + let default_repo_path = PathBuf::from("."); let effective_repo_path = if self.repo_path != default_repo_path { self.repo_path.clone() @@ -117,11 +132,12 @@ impl Command for TagCommand { // Explicit tag name provided - use legacy flow let version_manager = git::TagGenerator::new( tag_options.clone(), - self.bump_config_files, + self.should_bump_files(), git_command_config.clone(), ); version_manager.create_and_push_tag(&version_manager.open_repository()?, name)?; let payload = TagCommandOutput { + api_version: API_VERSION, command: "tag".into(), ok: true, dry_run: self.tag_options.dry_run(), @@ -148,13 +164,14 @@ impl Command for TagCommand { // In non-interactive mode, auto-calculate and act based on options let mut version_manager = git::TagGenerator::new( tag_options.clone(), - self.bump_config_files, + self.should_bump_files(), git_command_config.clone(), ); version_manager.run()?; // Print the calculated tag so callers/tests can consume it let payload = TagCommandOutput { + api_version: API_VERSION, command: "tag".into(), ok: true, dry_run: self.tag_options.dry_run(), @@ -181,11 +198,12 @@ impl Command for TagCommand { } let mut version_manager = git::TagGenerator::new( tag_options, - self.bump_config_files, + self.should_bump_files(), git_command_config.clone(), ); version_manager.run()?; let payload = TagCommandOutput { + api_version: API_VERSION, command: "tag".into(), ok: true, dry_run: self.tag_options.dry_run(), @@ -212,7 +230,7 @@ impl Command for TagCommand { "is_pre_release", Value::from(version_manager.is_pre_release), ), - ("allow_bump_files", Value::from(self.bump_config_files)), + ("allow_bump_files", Value::from(self.should_bump_files())), ]), )) { @@ -222,9 +240,22 @@ impl Command for TagCommand { Ok(()) } + + fn machine_context(&self) -> Option { + (self.output == "json").then_some(MachineContext { + command: "tag", + dry_run: self.tag_options.dry_run(), + }) + } } impl TagCommand { + /// Version files are bumped unless the caller opts out with `--no-bump-files`. + /// `--bump-files` is still accepted so existing scripts keep working. + fn should_bump_files(&self) -> bool { + !self.no_bump_config_files + } + /// Execute multi-package tag operation fn execute_multi_package( &self, @@ -245,11 +276,17 @@ impl TagCommand { // Step 1: Get commit log since last tag let repo = git::discover_repository_from(repo_path)?; - let commit_log = self.get_commit_log_since_last_tag(&repo, git_command_config)?; + let latest_tag = self.find_latest_tag(&repo)?; + let latest_tag = (!latest_tag.is_empty()).then_some(latest_tag); + let commit_log = + self.get_commit_log_since_tag(&repo, latest_tag.as_deref(), git_command_config)?; debug!("Commit log since last tag:\n{}", commit_log); - // Step 2: Detect scopes (affected packages) from commit log - let affected_packages = self.detect_affected_packages(&commit_log, repo_config)?; + // Step 2: Detect affected packages from commit scopes and changed files + let changed_files = + self.get_changed_files_since_tag(&repo, latest_tag.as_deref(), git_command_config)?; + let affected_packages = + self.detect_affected_packages(&commit_log, &changed_files, repo_config)?; if affected_packages.is_empty() { info!("ℹ️ No packages affected by commits. Skipping tag creation."); return Ok(()); @@ -269,13 +306,33 @@ impl TagCommand { return Ok(()); } - // Step 5: Update version files per package - if self.bump_config_files { - self.apply_version_updates(repo_path, &version_updates)?; - info!( - "✅ Updated version files for {} package(s)", - version_updates.len() - ); + if self.tag_options.dry_run() { + for tag_name in planned_multi_package_tags(repo_config, &version_updates) { + info!("🧪 Dry run: Tag would be {}", tag_name); + if self.output != "json" { + println!("{}", tag_name); + } + } + return Ok(()); + } + + // Step 5: Update version files per package, and commit them so the tag we + // create below actually points at the bumped versions. + if self.should_bump_files() { + let updated_files = self.apply_version_updates(repo_path, &version_updates)?; + if !updated_files.is_empty() { + info!( + "✅ Updated version files for {} package(s)", + version_updates.len() + ); + self.commit_version_updates( + repo_path, + &updated_files, + &version_updates, + git_command_config, + )?; + info!("✅ Committed version changes"); + } } // Step 6: Update dependencies if requested @@ -292,18 +349,15 @@ impl TagCommand { } /// Get commit log since last tag - fn get_commit_log_since_last_tag( + fn get_commit_log_since_tag( &self, repo: &git2::Repository, + latest_tag: Option<&str>, git_command_config: &git::GitCommandConfig, ) -> Result { - // Try to find the latest tag - let latest_tag = self.find_latest_tag(repo)?; - let range = if latest_tag.is_empty() { - "HEAD".to_string() - } else { - format!("{}..HEAD", latest_tag) - }; + let range = latest_tag + .map(|tag| format!("{tag}..HEAD")) + .unwrap_or_else(|| "HEAD".to_string()); // Use git log to get commit messages let repo_path = repo @@ -320,6 +374,41 @@ impl TagCommand { .map_err(|e| CliError::Generic(format!("Invalid UTF-8 in commit log: {}", e))) } + fn get_changed_files_since_tag( + &self, + repo: &git2::Repository, + latest_tag: Option<&str>, + git_command_config: &git::GitCommandConfig, + ) -> Result, CliError> { + let repo_path = repo + .workdir() + .ok_or_else(|| CliError::GitError(git2::Error::from_str("No working directory")))?; + let output = if let Some(tag) = latest_tag { + let range = format!("{tag}..HEAD"); + git::run_git_capture( + repo_path, + &["diff", "--name-only", &range], + "get changed files", + git_command_config, + )? + } else { + git::run_git_capture( + repo_path, + &["log", "--format=", "--name-only", "HEAD"], + "get changed files", + git_command_config, + )? + }; + let files = String::from_utf8(output.stdout) + .map_err(|e| CliError::Generic(format!("Invalid UTF-8 in changed file list: {}", e)))? + .lines() + .map(str::trim) + .filter(|line| !line.is_empty()) + .map(PathBuf::from) + .collect::>(); + Ok(files) + } + /// Find the latest tag in the repository fn find_latest_tag(&self, repo: &git2::Repository) -> Result { let tags = repo.tag_names(None).map_err(CliError::from)?; @@ -330,25 +419,39 @@ impl TagCommand { fn detect_affected_packages( &self, commit_log: &str, + changed_files: &[PathBuf], config: &RepositoryConfig, ) -> Result, CliError> { let mut packages = std::collections::HashSet::new(); - // Extract scopes from commit messages (pattern: type(scope): message) - let scope_regex = Regex::new(r"^[a-z]+\(([^)]+)\):") + // Extract scopes from commit headers (pattern: type(scope)!: message) + let scope_regex = Regex::new(r"^[a-z0-9-]+\(([^)]+)\)(?:!)?:") .map_err(|e| CliError::Generic(format!("Regex error: {}", e)))?; for line in commit_log.lines() { if let Some(caps) = scope_regex.captures(line) { let scope = caps.get(1).map(|m| m.as_str()).unwrap_or(""); - // Check if scope matches a package - if config.packages.iter().any(|p| p.name == scope) { - packages.insert(scope.to_string()); + for package in resolve_scope_packages(scope, config) { + packages.insert(package); } } } - Ok(packages.into_iter().collect()) + if !changed_files.is_empty() { + let detector = ScopeDetector::new(config.clone(), &self.repo_path); + let changed_file_scopes = detector.detect_from_files(changed_files).map_err(|e| { + CliError::Generic(format!("Failed to detect scopes from files: {e}")) + })?; + for scope in changed_file_scopes { + for package in resolve_scope_packages(&scope, config) { + packages.insert(package); + } + } + } + + let mut packages = packages.into_iter().collect::>(); + packages.sort(); + Ok(packages) } /// Determine version bump type from commit messages @@ -413,7 +516,8 @@ impl TagCommand { &self, repo_path: &Path, updates: &[crate::versioning::manager::VersionUpdate], - ) -> Result<(), CliError> { + ) -> Result, CliError> { + let mut updated_files = Vec::new(); for update in updates { // Find package config to get version file let config = RepositoryConfig::try_load(repo_path) @@ -441,16 +545,54 @@ impl TagCommand { let updated = content.replace(&update.old_version, &update.new_version); + // Nothing to stage or commit when the file already carries the new version. + if updated == content { + continue; + } + fs::write(&version_file, updated) .map_err(|e| CliError::Generic(format!("Failed to write version file: {}", e)))?; - // Stage the updated file - if let Err(e) = git::stage_file(&version_file) { - debug!("Failed to stage {}: {}", version_file.display(), e); - } + updated_files.push(version_file); } - Ok(()) + Ok(updated_files) + } + + /// Stage and commit the bumped version files so the tags created afterwards + /// point at a commit that actually contains them. + fn commit_version_updates( + &self, + repo_path: &Path, + updated_files: &[PathBuf], + updates: &[crate::versioning::manager::VersionUpdate], + git_command_config: &git::GitCommandConfig, + ) -> Result<(), CliError> { + let relative_files: Vec = updated_files + .iter() + .map(|file| { + file.strip_prefix(repo_path) + .unwrap_or(file) + .display() + .to_string() + }) + .collect(); + let mut add_args = vec!["add", "--"]; + add_args.extend(relative_files.iter().map(String::as_str)); + git::run_git( + repo_path, + &add_args, + "stage version updates", + git_command_config, + )?; + + let summary = updates + .iter() + .map(|update| format!("{}@{}", update.package_name, update.new_version)) + .collect::>() + .join(", "); + let message = format!("chore: bump version to {summary}"); + git::commit_changes_in_with_config(repo_path, &message, false, git_command_config) } /// Apply dependency updates to other packages @@ -507,7 +649,7 @@ impl TagCommand { info!("📌 Created unified tag: {}", tag_name); } } - VersioningStrategy::Independent | VersioningStrategy::Hybrid => { + VersioningStrategy::Independent => { // Per-package tags for update in updates { let tag_name = format!("{}-v{}", update.package_name, update.new_version); @@ -515,6 +657,14 @@ impl TagCommand { info!("📌 Created tag: {}", tag_name); } } + VersioningStrategy::Hybrid => { + if let Some(tag_name) = hybrid_tag_name(config, updates) { + self.create_and_push_tag(repo, &tag_name, git_command_config)?; + info!("📌 Created hybrid tag: {}", tag_name); + } else { + info!("ℹ️ No primary package version change. Skipping tag creation."); + } + } } Ok(()) @@ -570,3 +720,237 @@ impl TagCommand { ) } } + +fn resolve_scope_packages(scope_value: &str, config: &RepositoryConfig) -> Vec { + let mut packages = std::collections::HashSet::new(); + let separator = config.scopes.scope_separator.as_str(); + + let scopes = if separator.is_empty() { + vec![scope_value] + } else { + scope_value.split(separator).collect::>() + }; + + for scope in scopes { + let scope = scope.trim(); + if scope.is_empty() { + continue; + } + + if config.packages.iter().any(|package| package.name == scope) { + packages.insert(scope.to_string()); + } + + for mapping in config + .scopes + .mappings + .iter() + .filter(|mapping| mapping.scope == scope) + { + if config + .packages + .iter() + .any(|package| package.name == mapping.package) + { + packages.insert(mapping.package.clone()); + } + } + } + + let mut packages = packages.into_iter().collect::>(); + packages.sort(); + packages +} + +fn hybrid_tag_name( + config: &RepositoryConfig, + updates: &[crate::versioning::manager::VersionUpdate], +) -> Option { + let primary = config.packages.iter().find(|package| package.primary)?; + let update = updates + .iter() + .find(|update| update.package_name == primary.name)?; + Some(format!("v{}", update.new_version)) +} + +fn planned_multi_package_tags( + config: &RepositoryConfig, + updates: &[crate::versioning::manager::VersionUpdate], +) -> Vec { + match config.versioning.strategy { + VersioningStrategy::Unified => updates + .first() + .map(|update| vec![format!("v{}", update.new_version)]) + .unwrap_or_default(), + VersioningStrategy::Independent => updates + .iter() + .map(|update| format!("{}-v{}", update.package_name, update.new_version)) + .collect(), + VersioningStrategy::Hybrid => hybrid_tag_name(config, updates).into_iter().collect(), + } +} + +#[cfg(test)] +mod tests { + use super::{hybrid_tag_name, planned_multi_package_tags, resolve_scope_packages, TagCommand}; + use crate::config::repository::{ + PackageConfig, RepositoryConfig, RepositoryMetadata, RepositoryType, ScopeConfig, + ScopeMapping, VersioningConfig, VersioningStrategy, + }; + use crate::versioning::manager::VersionUpdate; + use std::path::PathBuf; + use structopt::StructOpt; + + fn create_multi_package_config() -> RepositoryConfig { + RepositoryConfig { + repository: RepositoryMetadata { + name: "workspace".to_string(), + repo_type: RepositoryType::MultiPackage, + description: None, + }, + versioning: VersioningConfig { + strategy: VersioningStrategy::Hybrid, + unified_version: None, + rules: None, + }, + packages: vec![ + PackageConfig { + name: "committy-cli".to_string(), + package_type: "rust-cargo".to_string(), + path: ".".to_string(), + version_file: "Cargo.toml".to_string(), + version_field: "package.version".to_string(), + primary: true, + sync_with: None, + independent: false, + workspace_member: false, + description: None, + }, + PackageConfig { + name: "docs".to_string(), + package_type: "node-npm".to_string(), + path: "docs".to_string(), + version_file: "package.json".to_string(), + version_field: "version".to_string(), + primary: false, + sync_with: None, + independent: true, + workspace_member: false, + description: None, + }, + ], + dependencies: vec![], + scopes: ScopeConfig { + auto_detect: true, + require_scope_for_multi_package: true, + allow_multiple_scopes: true, + scope_separator: ",".to_string(), + mappings: vec![ + ScopeMapping { + pattern: "src/**".to_string(), + scope: "core".to_string(), + package: "committy-cli".to_string(), + description: None, + }, + ScopeMapping { + pattern: "docs/**".to_string(), + scope: "docs".to_string(), + package: "docs".to_string(), + description: None, + }, + ], + }, + commit_rules: Default::default(), + branch_rules: Default::default(), + git: Default::default(), + convention: None, + release: None, + changelog: None, + workspace: None, + } + } + + #[test] + fn resolve_scope_packages_uses_scope_mappings() { + let config = create_multi_package_config(); + + assert_eq!( + resolve_scope_packages("core", &config), + vec!["committy-cli"] + ); + } + + #[test] + fn detect_affected_packages_accepts_breaking_multi_scope_headers() { + let command = TagCommand::from_iter(["tag"]); + let config = create_multi_package_config(); + let packages = command + .detect_affected_packages("feat(core, docs)!: release both\n", &[], &config) + .unwrap(); + + assert_eq!(packages, vec!["committy-cli", "docs"]); + } + + #[test] + fn detect_affected_packages_falls_back_to_changed_files() { + let command = TagCommand::from_iter(["tag"]); + let config = create_multi_package_config(); + let packages = command + .detect_affected_packages( + "fix: tag mapping\n", + &[PathBuf::from("src/cli/commands/tag.rs")], + &config, + ) + .unwrap(); + + assert_eq!(packages, vec!["committy-cli"]); + } + + #[test] + fn hybrid_tag_name_uses_primary_package_version() { + let config = create_multi_package_config(); + let tag = hybrid_tag_name( + &config, + &[ + VersionUpdate::new( + "committy-cli".to_string(), + "1.0.0".to_string(), + "1.0.1".to_string(), + ), + VersionUpdate::new("docs".to_string(), "2.0.0".to_string(), "2.0.1".to_string()), + ], + ); + + assert_eq!(tag.as_deref(), Some("v1.0.1")); + } + + #[test] + fn hybrid_tag_name_skips_when_only_independent_package_changes() { + let config = create_multi_package_config(); + let tag = hybrid_tag_name( + &config, + &[VersionUpdate::new( + "docs".to_string(), + "2.0.0".to_string(), + "2.0.1".to_string(), + )], + ); + + assert!(tag.is_none()); + } + + #[test] + fn planned_multi_package_tags_uses_hybrid_repo_tag() { + let config = create_multi_package_config(); + let tags = planned_multi_package_tags( + &config, + &[VersionUpdate::new( + "committy-cli".to_string(), + "1.0.0".to_string(), + "1.0.1".to_string(), + )], + ); + + assert_eq!(tags, vec!["v1.0.1"]); + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 2244c58..0385edb 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,7 +1,8 @@ pub mod commands; +pub mod output; use self::commands::{ - amend, branch, bump, changelog, commit, config, example, group_commit, info, init, lint, + amend, branch, bump, changelog, commit, config, example, group_commit, hooks, info, init, lint, lint_message, ls, packages, schema, tag, version, }; use crate::error::CliError; @@ -9,6 +10,10 @@ use structopt::StructOpt; pub trait Command { fn execute(&self, non_interactive: bool) -> Result<(), CliError>; + + fn machine_context(&self) -> Option { + None + } } #[derive(StructOpt)] @@ -41,6 +46,8 @@ pub enum CliCommand { Branch(branch::BranchCommand), #[structopt(about = "Group changes and optionally commit/apply them (with optional AI)")] GroupCommit(group_commit::GroupCommitCommand), + #[structopt(about = "Install and run Committy git enforcement hooks")] + Hooks(hooks::HooksCommand), #[structopt(about = "Initialize multi-package support")] Init(init::InitCommand), #[structopt(about = "Manage repository configuration")] @@ -66,9 +73,25 @@ impl CliCommand { CliCommand::Version(cmd) => cmd.execute(non_interactive), CliCommand::Branch(cmd) => cmd.execute(non_interactive), CliCommand::GroupCommit(cmd) => cmd.execute(non_interactive), + CliCommand::Hooks(cmd) => cmd.execute(non_interactive), CliCommand::Init(cmd) => cmd.execute(non_interactive), CliCommand::Config(cmd) => cmd.execute(non_interactive), CliCommand::Packages(cmd) => cmd.execute(non_interactive), } } + + pub fn machine_context(&self) -> Option { + match self { + CliCommand::Commit(cmd) => cmd.machine_context(), + CliCommand::Amend(cmd) => cmd.machine_context(), + CliCommand::Tag(cmd) => cmd.machine_context(), + CliCommand::Lint(cmd) => cmd.machine_context(), + CliCommand::LintMessage(cmd) => cmd.machine_context(), + CliCommand::Branch(cmd) => cmd.machine_context(), + CliCommand::GroupCommit(cmd) => cmd.machine_context(), + CliCommand::Hooks(cmd) => cmd.machine_context(), + CliCommand::Schema(cmd) => cmd.machine_context(), + _ => None, + } + } } diff --git a/src/cli/output.rs b/src/cli/output.rs new file mode 100644 index 0000000..b70710d --- /dev/null +++ b/src/cli/output.rs @@ -0,0 +1,69 @@ +use crate::error::CliError; +use serde::Serialize; + +pub const API_VERSION: u8 = 1; + +#[derive(Clone, Copy, Debug)] +pub struct MachineContext { + pub command: &'static str, + pub dry_run: bool, +} + +#[derive(Debug, Serialize)] +struct MachineError<'a> { + code: &'static str, + message: &'a str, +} + +#[derive(Debug, Serialize)] +struct ErrorEnvelope<'a> { + api_version: u8, + command: &'a str, + ok: bool, + dry_run: bool, + errors: [MachineError<'a>; 1], +} + +/// Strip ANSI styling so the envelope carries plain text. +/// +/// Clap colours its rejections, and those escape sequences would otherwise end +/// up inside a JSON string value. +fn strip_ansi(text: &str) -> String { + static ANSI: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| regex::Regex::new(r"\x1b\[[0-9;]*[a-zA-Z]").unwrap()); + ANSI.replace_all(text, "").into_owned() +} + +/// Emit the machine envelope for an argument-parse rejection. +/// +/// `command` is attributed from raw argv and falls back to `"unknown"`, because +/// clap rejects the invocation before any value is parsed. +pub fn print_usage_error(command: &str, message: &str) { + let message = strip_ansi(message); + let payload = ErrorEnvelope { + api_version: API_VERSION, + command, + ok: false, + dry_run: false, + errors: [MachineError { + code: "invalid_usage", + message: &message, + }], + }; + println!("{}", serde_json::to_string(&payload).unwrap()); +} + +pub fn print_error(context: MachineContext, error: &CliError) { + let message = error.to_string(); + let payload = ErrorEnvelope { + api_version: API_VERSION, + command: context.command, + ok: false, + dry_run: context.dry_run, + errors: [MachineError { + code: error.code(), + message: &message, + }], + }; + println!("{}", serde_json::to_string(&payload).unwrap()); +} diff --git a/src/config.rs b/src/config.rs index 8526eb2..87fd259 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,11 +11,6 @@ pub const COMMIT_TYPES: &[&str] = &[ "test", "security", "config", ]; -pub const BRANCH_TYPES: &[&str] = &[ - "feat", "fix", "refactor", "test", "docs", "perf", "security", "hotfix", "release", "spike", - "tooling", -]; - pub const MAX_SHORT_DESCRIPTION_LENGTH: usize = 150; pub const MAX_TICKET_NAME_LENGTH: usize = 10; pub const MAX_SCOPE_NAME_LENGTH: usize = 15; diff --git a/src/config/convention.rs b/src/config/convention.rs index 61955d4..7ccc0f3 100644 --- a/src/config/convention.rs +++ b/src/config/convention.rs @@ -24,12 +24,21 @@ pub struct ConventionConfig { pub struct ConventionType { pub name: String, pub description: String, + #[serde(default = "default_type_contexts")] + pub contexts: Vec, pub bump: BumpType, pub changelog_section: String, pub aliases: Vec, pub hidden: bool, } +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum ConventionContext { + Commit, + Branch, +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(default)] pub struct ConventionQuestion { @@ -84,6 +93,7 @@ impl Default for ConventionType { Self { name: "chore".to_string(), description: "Other changes that do not modify src or test files".to_string(), + contexts: default_type_contexts(), bump: BumpType::None, changelog_section: "Maintenance".to_string(), aliases: vec![], @@ -92,6 +102,18 @@ impl Default for ConventionType { } } +fn default_type_contexts() -> Vec { + vec![ConventionContext::Commit] +} + +fn commit_and_branch_contexts() -> Vec { + vec![ConventionContext::Commit, ConventionContext::Branch] +} + +fn branch_contexts() -> Vec { + vec![ConventionContext::Branch] +} + impl Default for ConventionQuestion { fn default() -> Self { Self { @@ -173,6 +195,7 @@ pub fn default_types() -> Vec { ConventionType { name: "feat".to_string(), description: "A new feature".to_string(), + contexts: commit_and_branch_contexts(), bump: BumpType::Minor, changelog_section: "Features".to_string(), aliases: vec!["feature".to_string()], @@ -181,6 +204,7 @@ pub fn default_types() -> Vec { ConventionType { name: "fix".to_string(), description: "A bug fix".to_string(), + contexts: commit_and_branch_contexts(), bump: BumpType::Patch, changelog_section: "Bug Fixes".to_string(), aliases: vec![], @@ -189,6 +213,7 @@ pub fn default_types() -> Vec { ConventionType { name: "docs".to_string(), description: "Documentation only changes".to_string(), + contexts: commit_and_branch_contexts(), bump: BumpType::Patch, changelog_section: "Documentation".to_string(), aliases: vec![], @@ -197,6 +222,7 @@ pub fn default_types() -> Vec { ConventionType { name: "perf".to_string(), description: "A code change that improves performance".to_string(), + contexts: commit_and_branch_contexts(), bump: BumpType::Patch, changelog_section: "Performance".to_string(), aliases: vec![], @@ -205,6 +231,7 @@ pub fn default_types() -> Vec { ConventionType { name: "refactor".to_string(), description: "A code change that neither fixes a bug nor adds a feature".to_string(), + contexts: commit_and_branch_contexts(), bump: BumpType::Patch, changelog_section: "Refactoring".to_string(), aliases: vec![], @@ -213,6 +240,7 @@ pub fn default_types() -> Vec { ConventionType { name: "build".to_string(), description: "Changes that affect the build system".to_string(), + contexts: default_type_contexts(), bump: BumpType::Patch, changelog_section: "Build".to_string(), aliases: vec![], @@ -221,6 +249,7 @@ pub fn default_types() -> Vec { ConventionType { name: "ci".to_string(), description: "CI configuration changes".to_string(), + contexts: default_type_contexts(), bump: BumpType::Patch, changelog_section: "CI".to_string(), aliases: vec![], @@ -229,6 +258,7 @@ pub fn default_types() -> Vec { ConventionType { name: "chore".to_string(), description: "Other changes that do not modify src or test files".to_string(), + contexts: default_type_contexts(), bump: BumpType::None, changelog_section: "Maintenance".to_string(), aliases: vec![], @@ -237,6 +267,7 @@ pub fn default_types() -> Vec { ConventionType { name: "test".to_string(), description: "Adding or fixing tests".to_string(), + contexts: commit_and_branch_contexts(), bump: BumpType::Patch, changelog_section: "Tests".to_string(), aliases: vec![], @@ -245,10 +276,28 @@ pub fn default_types() -> Vec { ConventionType { name: "style".to_string(), description: "Code style changes".to_string(), + contexts: default_type_contexts(), bump: BumpType::Patch, changelog_section: "Style".to_string(), aliases: vec![], hidden: false, }, + branch_type("security", "Security-focused work"), + branch_type("hotfix", "Urgent production fix"), + branch_type("release", "Release preparation"), + branch_type("spike", "Time-boxed investigation"), + branch_type("tooling", "Developer tooling work"), ] } + +fn branch_type(name: &str, description: &str) -> ConventionType { + ConventionType { + name: name.to_string(), + description: description.to_string(), + contexts: branch_contexts(), + bump: BumpType::None, + changelog_section: "Other".to_string(), + aliases: vec![], + hidden: false, + } +} diff --git a/src/config/hierarchy.rs b/src/config/hierarchy.rs index b3dff66..5b6e975 100644 --- a/src/config/hierarchy.rs +++ b/src/config/hierarchy.rs @@ -82,11 +82,7 @@ impl MergedConfig { } pub fn effective_convention(&self) -> ConventionConfig { - let mut config = self - .user - .convention - .clone() - .unwrap_or_else(ConventionConfig::default); + let mut config = self.user.convention.clone().unwrap_or_default(); if let Some(repository) = &self.repository { if let Some(repository_convention) = &repository.convention { @@ -159,6 +155,7 @@ fn custom_commit_type_to_convention( super::convention::ConventionType { name: custom.name.clone(), description: custom.description.clone(), + contexts: vec![super::convention::ConventionContext::Commit], bump: custom.bump.clone(), changelog_section: "Custom".to_string(), aliases: vec![], @@ -172,9 +169,11 @@ mod tests { use crate::config::repository::{ RepositoryMetadata, RepositoryType, VersioningConfig, VersioningRules, VersioningStrategy, }; + use serial_test::serial; use tempfile::TempDir; #[test] + #[serial] fn test_merged_config_without_repository() { let temp_dir = TempDir::new().unwrap(); let merged = MergedConfig::load(temp_dir.path()).unwrap(); @@ -184,6 +183,7 @@ mod tests { } #[test] + #[serial] fn test_regex_fallback_to_user_config() { let temp_dir = TempDir::new().unwrap(); let merged = MergedConfig::load(temp_dir.path()).unwrap(); @@ -195,6 +195,7 @@ mod tests { } #[test] + #[serial] fn test_repository_config_overrides_user() { let temp_dir = TempDir::new().unwrap(); @@ -218,6 +219,7 @@ mod tests { dependencies: vec![], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/config/repository.rs b/src/config/repository.rs index 52a50da..e8bab2b 100644 --- a/src/config/repository.rs +++ b/src/config/repository.rs @@ -26,6 +26,8 @@ pub struct RepositoryConfig { #[serde(default)] pub commit_rules: CommitRulesConfig, #[serde(default)] + pub branch_rules: BranchRulesConfig, + #[serde(default)] pub git: GitConfig, #[serde(default)] pub convention: Option, @@ -187,6 +189,27 @@ pub struct CommitRulesConfig { pub custom_types: Vec, } +/// Branch naming rules used by structured branch creation and optional explicit-name enforcement. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BranchRulesConfig { + #[serde(default)] + pub require_ticket: bool, + #[serde(default = "default_ticket_pattern")] + pub ticket_pattern: String, + #[serde(default)] + pub enforce_explicit_names: bool, +} + +impl Default for BranchRulesConfig { + fn default() -> Self { + Self { + require_ticket: false, + ticket_pattern: default_ticket_pattern(), + enforce_explicit_names: false, + } + } +} + impl Default for CommitRulesConfig { fn default() -> Self { Self { @@ -256,6 +279,10 @@ fn default_100() -> usize { 100 } +fn default_ticket_pattern() -> String { + "^[A-Z]+[0-9]+$".to_string() +} + impl RepositoryConfig { /// Load repository configuration from .committy/config.toml pub fn load(repo_path: &Path) -> Result { @@ -300,6 +327,8 @@ impl RepositoryConfig { /// Validate the configuration pub fn validate(&self, repo_path: &Path) -> Result<()> { validate_git_config_overrides(&self.git.config_overrides)?; + regex::Regex::new(&self.branch_rules.ticket_pattern) + .with_context(|| "Invalid branch_rules.ticket_pattern")?; // Validate package names are unique let mut names = HashSet::new(); @@ -547,6 +576,7 @@ mod tests { dependencies: vec![], scopes: ScopeConfig::default(), commit_rules: CommitRulesConfig::default(), + branch_rules: BranchRulesConfig::default(), git: Default::default(), convention: None, release: None, @@ -613,6 +643,7 @@ mod tests { dependencies: vec![], scopes: ScopeConfig::default(), commit_rules: CommitRulesConfig::default(), + branch_rules: BranchRulesConfig::default(), git: Default::default(), convention: None, release: None, @@ -667,6 +698,7 @@ mod tests { dependencies: vec![], scopes: ScopeConfig::default(), commit_rules: CommitRulesConfig::default(), + branch_rules: BranchRulesConfig::default(), git: Default::default(), convention: None, release: None, @@ -727,6 +759,7 @@ mod tests { dependencies: vec![], scopes: ScopeConfig::default(), commit_rules: CommitRulesConfig::default(), + branch_rules: BranchRulesConfig::default(), git: Default::default(), convention: None, release: None, diff --git a/src/convention/mod.rs b/src/convention/mod.rs index f06db39..9bb05cc 100644 --- a/src/convention/mod.rs +++ b/src/convention/mod.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::path::Path; -use crate::config::convention::{ConventionConfig, ConventionType}; +use crate::config::convention::{ConventionConfig, ConventionContext, ConventionType}; use crate::config::hierarchy::MergedConfig; use crate::config::repository::BumpType; use crate::error::CliError; @@ -75,8 +75,22 @@ impl Convention { .collect() } - pub fn allowed_types(&self) -> Vec { + pub fn visible_types_for(&self, context: ConventionContext) -> Vec<&ConventionType> { self.visible_types() + .into_iter() + .filter(|item| item.contexts.contains(&context)) + .collect() + } + + pub fn allowed_types(&self) -> Vec { + self.visible_types_for(ConventionContext::Commit) + .into_iter() + .map(|item| item.name.clone()) + .collect() + } + + pub fn allowed_branch_types(&self) -> Vec { + self.visible_types_for(ConventionContext::Branch) .into_iter() .map(|item| item.name.clone()) .collect() @@ -240,7 +254,9 @@ impl Convention { fn type_by_name(&self, commit_type: &str) -> Option<&ConventionType> { let canonical = self.canonical_type(commit_type)?; - self.config.types.iter().find(|item| item.name == canonical) + self.config.types.iter().find(|item| { + item.name == canonical && item.contexts.contains(&ConventionContext::Commit) + }) } } @@ -279,4 +295,17 @@ mod tests { let issues = convention.validate_message("invalid commit"); assert!(!issues.is_empty()); } + + #[test] + fn test_branch_only_type_is_not_a_valid_commit_type() { + let convention = Convention::from_config(ConventionConfig::default()).unwrap(); + let issues = convention.validate_message("spike: investigate agent protocol"); + assert!(issues + .iter() + .any(|issue| issue.starts_with("Commit type must be one of:"))); + assert!(convention + .allowed_branch_types() + .contains(&"spike".to_string())); + assert!(!convention.allowed_types().contains(&"spike".to_string())); + } } diff --git a/src/dependency/updater.rs b/src/dependency/updater.rs index 7882eab..adcce20 100644 --- a/src/dependency/updater.rs +++ b/src/dependency/updater.rs @@ -266,6 +266,7 @@ mod tests { }], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/error.rs b/src/error.rs index a4e63ca..60691a1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -38,6 +38,31 @@ pub enum CliError { LintIssues(usize), } +impl CliError { + pub fn code(&self) -> &'static str { + match self { + Self::GitError(_) => "git_error", + Self::IoError(_) => "io_error", + Self::InputError(_) => "invalid_input", + Self::NoStagedChanges => "no_staged_changes", + Self::StagedChanges => "staged_changes_present", + Self::GitConfigError(_) => "git_config_error", + Self::Generic(_) => "command_error", + Self::SemVerError(_) => "semver_error", + Self::RegexError(_) => "regex_error", + Self::LintIssues(_) => "lint_issues", + } + } + + pub fn exit_code(&self) -> i32 { + if matches!(self, Self::LintIssues(_)) { + 3 + } else { + 1 + } + } +} + impl From for CliError { fn from(error: clap::Error) -> Self { CliError::Generic(error.to_string()) diff --git a/src/git/process.rs b/src/git/process.rs index d3bf744..86c4645 100644 --- a/src/git/process.rs +++ b/src/git/process.rs @@ -173,6 +173,7 @@ mod tests { dependencies: vec![], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: GitConfig { config_overrides: vec!["repo.value=1".to_string(), "shared.value=repo".to_string()], }, diff --git a/src/git/repository.rs b/src/git/repository.rs index 682f274..ef1670f 100644 --- a/src/git/repository.rs +++ b/src/git/repository.rs @@ -12,27 +12,7 @@ pub fn discover_repository_from(path: &Path) -> Result { log::debug!("Starting repository discovery from: {path:?}"); match Repository::discover(path) { - Ok(repo) => { - // Get the absolute path to the repository root - let repo_path = repo - .path() - .parent() - .and_then(|p| p.canonicalize().ok()) - .ok_or_else(|| { - CliError::GitError(git2::Error::from_str( - "Could not determine repository root directory", - )) - })?; - - // Open a new repository instance using the absolute path - match Repository::open(&repo_path) { - Ok(new_repo) => Ok(new_repo), - Err(e) => { - log::error!("Failed to open repository at {repo_path:?}: {e}"); - Err(CliError::GitError(e)) - } - } - } + Ok(repo) => Ok(repo), Err(e) => { log::error!("Failed to discover repository from {path:?}: {e}"); Err(CliError::GitError(git2::Error::from_str( diff --git a/src/input/prompts.rs b/src/input/prompts.rs index b35be68..f161881 100644 --- a/src/input/prompts.rs +++ b/src/input/prompts.rs @@ -2,8 +2,7 @@ use super::validation::{ auto_correct_scope, validate_scope, validate_section, validate_short_message, }; use crate::config::{ - BRANCH_TYPES, COMMIT_TYPES, MAX_SCOPE_NAME_LENGTH, MAX_SHORT_DESCRIPTION_LENGTH, - MAX_TICKET_NAME_LENGTH, + COMMIT_TYPES, MAX_SCOPE_NAME_LENGTH, MAX_SHORT_DESCRIPTION_LENGTH, MAX_TICKET_NAME_LENGTH, }; use crate::error::CliError; use inquire::{Confirm, Select, Text}; @@ -42,13 +41,13 @@ pub fn select_commit_type_from(types: &[String]) -> Result { Ok(commit_type.to_string()) } -pub fn select_branch_type() -> Result { +pub fn select_branch_type_from(types: &[String]) -> Result { if non_interactive_env() { return Err(CliError::InputError( "Non-interactive environment: cannot prompt for branch type".to_string(), )); } - let branch_type = Select::new("Select the type of branch:", BRANCH_TYPES.to_vec()) + let branch_type = Select::new("Select the type of branch:", types.to_vec()) .with_help_message("Use arrow keys to navigate, Enter to select") .prompt() .map_err(|e| CliError::InputError(e.to_string()))?; diff --git a/src/main.rs b/src/main.rs index 4e9f046..8f97af2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,25 +56,91 @@ struct Opt { )] pre_release: bool, - #[structopt(long = "non-interactive", help = "Run in non-interactive mode")] + #[structopt( + long = "non-interactive", + global = true, + help = "Run in non-interactive mode" + )] non_interactive: bool, #[structopt(long = "metrics-toggle", help = "Toggle metrics collection on/off")] metrics_toggle: bool, + // Not global: `-v` is already taken by `branch --validate` and `--verbose` + // by `config validate|show`. Promoting it would silently steal those + // shorts. Agents use `-q` and `--non-interactive`, which are global below. #[structopt( short = "v", long = "verbose", parse(from_occurrences), - help = "Increase verbosity (-v, -vv)" + help = "Increase verbosity (-v, -vv); must precede the subcommand" )] verbose: u8, - #[structopt(short = "q", long = "quiet", help = "Reduce verbosity (errors only)")] + #[structopt( + short = "q", + long = "quiet", + global = true, + help = "Reduce verbosity (errors only)" + )] quiet: bool, } +/// Subcommand names as they appear on the command line. +/// +/// Used only to attribute an argv rejection to a command, since clap gives us +/// no parsed value in that case. +const SUBCOMMAND_NAMES: &[&str] = &[ + "commit", + "amend", + "tag", + "bump", + "changelog", + "lint", + "lint-message", + "example", + "info", + "ls", + "schema", + "version", + "branch", + "group-commit", + "hooks", + "init", + "config", + "packages", +]; + +/// Whether the caller asked for machine output, determined from raw argv. +/// +/// An argv rejection happens before any flag is parsed, so this is the only way +/// to know whether the caller is an agent expecting one JSON document. +fn requested_json_output(args: &[String]) -> bool { + args.iter().enumerate().any(|(index, arg)| { + arg == "--output=json" + || (arg == "--output" && args.get(index + 1).map(String::as_str) == Some("json")) + }) +} + +/// Best-effort command attribution for an argv rejection. +fn command_from_args(args: &[String]) -> &str { + args.iter() + .skip(1) + .find(|arg| !arg.starts_with('-')) + .map(String::as_str) + .filter(|candidate| SUBCOMMAND_NAMES.contains(candidate)) + .unwrap_or("unknown") +} + fn main() { + // Parse argv before touching configuration so a malformed invocation is + // rejected without side effects. + let args: Vec = std::env::args().collect(); + let opt = match Opt::from_iter_safe(&args) { + Ok(opt) => opt, + Err(error) => exit_on_parse_error(error, &args), + }; + // Load configuration let mut config = Config::load().unwrap_or_else(|_| { let default_config = Config::default(); @@ -84,21 +150,42 @@ fn main() { default_config }); - if let Err(e) = run(&mut config) { - // Map specific errors to exit codes - if let Some(CliError::LintIssues(_)) = e.downcast_ref::() { - eprintln!("{e}"); - std::process::exit(3); - } else { - eprintln!("{e}"); - std::process::exit(1); - } + if let Err(e) = run(&mut config, opt) { + eprintln!("{e}"); + let exit_code = e + .downcast_ref::() + .map(CliError::exit_code) + .unwrap_or(1); + std::process::exit(exit_code); } } -fn run(config: &mut Config) -> Result<()> { - let opt = Opt::from_args(); +/// Report a clap rejection and terminate. +/// +/// `--help` and `--version` arrive here as errors too; they are successful +/// output and keep their plain-text form. A genuine rejection honours +/// `--output json` so that an agent's parser sees the same envelope it sees for +/// every other failure, instead of an empty stdout and prose on stderr. +fn exit_on_parse_error(error: structopt::clap::Error, args: &[String]) -> ! { + use structopt::clap::ErrorKind; + + if matches!( + error.kind, + ErrorKind::HelpDisplayed | ErrorKind::VersionDisplayed + ) { + println!("{}", error.message); + std::process::exit(0); + } + + if requested_json_output(args) { + cli::output::print_usage_error(command_from_args(args), &error.message); + } else { + eprintln!("{}", error.message); + } + std::process::exit(1); +} +fn run(config: &mut Config, opt: Opt) -> Result<()> { // Initialize logger based on verbosity flags let mut builder = Builder::from_env(Env::default().default_filter_or("info")); let level = if opt.quiet { @@ -224,8 +311,18 @@ fn run(config: &mut Config) -> Result<()> { } } - let result = match opt.cmd { - Some(cmd) => cmd.execute(non_interactive), + let result = match &opt.cmd { + Some(cmd) => { + let result = cmd.execute(non_interactive); + if let Err(error) = &result { + if !matches!(error, CliError::LintIssues(_)) { + if let Some(context) = cmd.machine_context() { + cli::output::print_error(context, error); + } + } + } + result + } None => { let cmd = CommitCommand::default_interactive(); cmd.execute(non_interactive) diff --git a/src/release/changelog.rs b/src/release/changelog.rs index ea0f558..0e56a0c 100644 --- a/src/release/changelog.rs +++ b/src/release/changelog.rs @@ -143,7 +143,7 @@ pub fn render_changelog( sections.push((name.clone(), entries)); } } - sections.extend(grouped.into_iter()); + sections.extend(grouped); let template = if let Some(path) = &config.template_path { fs::read_to_string(path).map_err(CliError::IoError)? diff --git a/src/release/engine.rs b/src/release/engine.rs index e216680..6f97b5a 100644 --- a/src/release/engine.rs +++ b/src/release/engine.rs @@ -299,26 +299,25 @@ impl ReleaseEngine { } } - if plan.publish { - if Repository::open(&self.repo_path) + if plan.publish + && Repository::open(&self.repo_path) .map_err(CliError::from)? .find_remote("origin") .is_ok() - { + { + run_git( + &self.repo_path, + &["push", "origin", "HEAD"], + "push release commit", + &self.git_command_config, + )?; + for tag_name in &plan.tag_names { run_git( &self.repo_path, - &["push", "origin", "HEAD"], - "push release commit", + &["push", "origin", &format!("refs/tags/{tag_name}")], + "push release tag", &self.git_command_config, )?; - for tag_name in &plan.tag_names { - run_git( - &self.repo_path, - &["push", "origin", &format!("refs/tags/{tag_name}")], - "push release tag", - &self.git_command_config, - )?; - } } } diff --git a/src/scope/detector.rs b/src/scope/detector.rs index 952c9df..10e3965 100644 --- a/src/scope/detector.rs +++ b/src/scope/detector.rs @@ -229,6 +229,7 @@ mod tests { dependencies: vec![], scopes, commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/version/mod.rs b/src/version/mod.rs index 15e6e39..df25eac 100644 --- a/src/version/mod.rs +++ b/src/version/mod.rs @@ -18,9 +18,12 @@ impl VersionFile { }) } - pub fn update_version(&self, new_version: &str) -> Result<(), CliError> { + /// Rewrites the version in this file, returning `true` when the contents + /// actually changed. A file that already carries `new_version` is left alone + /// so callers do not try to commit an empty diff. + pub fn update_version(&self, new_version: &str) -> Result { if !self.path.exists() { - return Ok(()); // Skip if file doesn't exist + return Ok(false); // Skip if file doesn't exist } let content = std::fs::read_to_string(&self.path).map_err(CliError::IoError)?; @@ -30,9 +33,13 @@ impl VersionFile { .replace_all(&content, &self.format.replace("{}", version_without_v)) .to_string(); + if new_content == content { + return Ok(false); + } + std::fs::write(&self.path, new_content).map_err(CliError::IoError)?; - Ok(()) + Ok(true) } } @@ -114,8 +121,7 @@ impl VersionManager { let mut updated_files = Vec::new(); for file in &self.version_files { - if file.path.exists() { - file.update_version(new_version)?; + if file.path.exists() && file.update_version(new_version)? { updated_files.push(file.path.clone()); } } diff --git a/src/versioning/hybrid.rs b/src/versioning/hybrid.rs index 3cb6200..0f5d0b4 100644 --- a/src/versioning/hybrid.rs +++ b/src/versioning/hybrid.rs @@ -196,6 +196,7 @@ mod tests { dependencies: vec![], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/versioning/independent.rs b/src/versioning/independent.rs index f135875..dcdb6f7 100644 --- a/src/versioning/independent.rs +++ b/src/versioning/independent.rs @@ -128,6 +128,7 @@ mod tests { dependencies: vec![], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/versioning/unified.rs b/src/versioning/unified.rs index cf63fd8..b420d78 100644 --- a/src/versioning/unified.rs +++ b/src/versioning/unified.rs @@ -123,6 +123,7 @@ mod tests { dependencies: vec![], scopes: Default::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/src/workflow/orchestrator.rs b/src/workflow/orchestrator.rs index 6ef6201..43997ed 100644 --- a/src/workflow/orchestrator.rs +++ b/src/workflow/orchestrator.rs @@ -316,6 +316,7 @@ mod tests { dependencies: vec![], scopes: ScopeConfig::default(), commit_rules: Default::default(), + branch_rules: Default::default(), git: Default::default(), convention: None, release: None, diff --git a/tests/agent_cli_tests.rs b/tests/agent_cli_tests.rs index 0560a1c..d411707 100644 --- a/tests/agent_cli_tests.rs +++ b/tests/agent_cli_tests.rs @@ -71,6 +71,53 @@ allow_multiple_scopes = false .expect("Failed to write commit rules config"); } +fn write_branch_rules_config(dir: &std::path::Path, body: &str) { + fs::create_dir_all(dir.join(".committy")).expect("Failed to create .committy"); + fs::write( + dir.join(".committy/config.toml"), + format!( + r#"packages = [] + +[repository] +name = "agent-repo" +type = "single-package" + +[versioning] +strategy = "independent" + +[branch_rules] +{body} +"# + ), + ) + .expect("Failed to write branch rules config"); +} + +fn write_legacy_convention_config(dir: &std::path::Path) { + fs::create_dir_all(dir.join(".committy")).expect("Failed to create .committy"); + fs::write( + dir.join(".committy/config.toml"), + r#"packages = [] + +[repository] +name = "agent-repo" +type = "single-package" + +[versioning] +strategy = "independent" + +[convention] + +[[convention.types]] +name = "wip" +description = "Legacy custom commit type" +bump = "none" +changelog_section = "Custom" +"#, + ) + .expect("Failed to write legacy convention config"); +} + #[test] fn test_branch_dry_run_json_outputs_plan() { let temp_dir = setup_repo(); @@ -147,6 +194,141 @@ fn test_branch_structured_dry_run_json_outputs_plan() { assert_eq!(v["would_checkout"], Value::Bool(false)); } +#[test] +fn test_branch_invalid_type_json_outputs_one_versioned_error_document() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("branch") + .arg("--type") + .arg("unknown") + .arg("--subject") + .arg("agent-flow") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .code(1); + + let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let mut documents = serde_json::Deserializer::from_str(&stdout).into_iter::(); + let payload = documents.next().unwrap().unwrap(); + assert!( + documents.next().is_none(), + "stdout must contain one JSON document" + ); + + assert_eq!(payload["api_version"], Value::from(1)); + assert_eq!(payload["command"], Value::String("branch".into())); + assert_eq!(payload["ok"], Value::Bool(false)); + assert_eq!(payload["dry_run"], Value::Bool(true)); + assert_eq!( + payload["errors"][0]["code"], + Value::String("invalid_input".into()) + ); + assert!(payload["errors"][0]["message"] + .as_str() + .unwrap() + .contains("Invalid branch type")); +} + +#[test] +fn test_branch_rules_require_matching_ticket_for_structured_branches() { + let temp_dir = setup_repo(); + write_branch_rules_config( + temp_dir.path(), + r#"require_ticket = true +ticket_pattern = "^ENG[0-9]+$""#, + ); + + let missing = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("branch") + .arg("--type") + .arg("feat") + .arg("--subject") + .arg("agent-flow") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .code(1); + let payload: Value = serde_json::from_slice(&missing.get_output().stdout).unwrap(); + assert_eq!(payload["errors"][0]["code"], "invalid_input"); + assert!(payload["errors"][0]["message"] + .as_str() + .unwrap() + .contains("ticket is required")); + + let valid = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("branch") + .arg("--type") + .arg("feat") + .arg("--ticket") + .arg("ENG42") + .arg("--subject") + .arg("agent-flow") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .success(); + let payload: Value = serde_json::from_slice(&valid.get_output().stdout).unwrap(); + assert_eq!(payload["branch_name"], "feat-ENG42-agent_flow"); +} + +#[test] +fn test_branch_rules_can_enforce_explicit_names() { + let temp_dir = setup_repo(); + write_branch_rules_config( + temp_dir.path(), + r#"require_ticket = true +ticket_pattern = "^ENG[0-9]+$" +enforce_explicit_names = true"#, + ); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("branch") + .arg("--name") + .arg("wildly-nonstandard") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .code(1); + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["errors"][0]["code"], "invalid_input"); +} + +#[test] +fn test_structured_branch_uses_builtins_for_legacy_commit_only_convention() { + let temp_dir = setup_repo(); + write_legacy_convention_config(temp_dir.path()); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("branch") + .arg("--type") + .arg("feat") + .arg("--subject") + .arg("compatible") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .success(); + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["branch_name"], "feat-compatible"); +} + #[test] fn test_branch_structured_flags_conflict_with_name() { let temp_dir = setup_repo(); @@ -193,6 +375,47 @@ fn test_branch_dry_run_json_supports_repo_path_without_chdir() { assert_eq!(v["branch_name"], Value::String("feat-agent-plan".into())); } +#[test] +fn test_branch_dry_run_json_supports_linked_worktree() { + let temp_dir = setup_repo(); + let worktree_parent = tempdir().expect("Failed to create worktree parent"); + let worktree_path = worktree_parent.path().join("linked-worktree"); + + let output = StdCommand::new("git") + .args(["worktree", "add", "--detach"]) + .arg(&worktree_path) + .current_dir(&temp_dir) + .output() + .expect("Failed to create linked worktree"); + assert!( + output.status.success(), + "git worktree add failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let assert = common::committy_cmd() + .current_dir(&worktree_path) + .arg("--non-interactive") + .arg("branch") + .arg("--repo-path") + .arg(".") + .arg("--name") + .arg("feat-linked-worktree") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .success(); + + let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let payload: Value = serde_json::from_str(stdout.trim()).unwrap(); + assert_eq!(payload["ok"], Value::Bool(true)); + assert_eq!( + payload["branch_name"], + Value::String("feat-linked-worktree".into()) + ); +} + #[test] fn test_commit_dry_run_json_does_not_create_commit() { let temp_dir = setup_repo(); @@ -228,6 +451,7 @@ fn test_commit_dry_run_json_does_not_create_commit() { let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); let v: Value = serde_json::from_str(output.trim()).unwrap(); + assert_eq!(v["api_version"], Value::from(1)); assert_eq!(v["command"], Value::String("commit".into())); assert_eq!(v["ok"], Value::Bool(true)); assert_eq!(v["dry_run"], Value::Bool(true)); @@ -248,6 +472,79 @@ fn test_commit_dry_run_json_does_not_create_commit() { ); } +#[test] +fn test_commit_json_reports_no_staged_changes() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("commit") + .arg("--type") + .arg("feat") + .arg("--message") + .arg("agent contract") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .code(1); + + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["api_version"], Value::from(1)); + assert_eq!(payload["command"], "commit"); + assert_eq!(payload["errors"][0]["code"], "no_staged_changes"); +} + +#[test] +fn test_commit_json_reports_missing_non_interactive_fields() { + let temp_dir = setup_repo(); + fs::write(temp_dir.path().join("tracked.txt"), "changed\n").unwrap(); + StdCommand::new("git") + .args(["add", "tracked.txt"]) + .current_dir(&temp_dir) + .output() + .unwrap(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("commit") + .arg("--dry-run") + .arg("--output") + .arg("json") + .assert() + .code(1); + + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["errors"][0]["code"], "invalid_input"); + assert!(payload["errors"][0]["message"] + .as_str() + .unwrap() + .contains("--type and --message are required")); +} + +#[test] +fn test_lint_json_reports_invalid_rev_range() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .arg("--non-interactive") + .arg("lint") + .arg("--rev-range") + .arg("HEAD") + .arg("--output") + .arg("json") + .assert() + .code(1); + + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["api_version"], Value::from(1)); + assert_eq!(payload["command"], "lint"); + assert_eq!(payload["errors"][0]["code"], "invalid_input"); +} + #[test] fn test_commit_dry_run_json_supports_repo_path_without_chdir() { let temp_dir = setup_repo(); @@ -369,19 +666,26 @@ bump = "none""#, fn test_tag_publish_requires_confirmation() { let temp_dir = setup_repo(); - common::committy_cmd() + let assert = common::committy_cmd() .current_dir(&temp_dir) .arg("--non-interactive") .arg("tag") .arg("--name") .arg("v1.2.3") .arg("--publish") + .arg("--output") + .arg("json") .assert() - .failure() + .code(1) .stderr(predicates::str::contains( "Publishing a tag requires --confirm-publish", )); + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["api_version"], Value::from(1)); + assert_eq!(payload["command"], "tag"); + assert_eq!(payload["errors"][0]["code"], "invalid_input"); + let tags = StdCommand::new("git") .args(["tag", "--list", "v1.2.3"]) .current_dir(&temp_dir) diff --git a/tests/agent_ergonomics_tests.rs b/tests/agent_ergonomics_tests.rs new file mode 100644 index 0000000..d6f0f46 --- /dev/null +++ b/tests/agent_ergonomics_tests.rs @@ -0,0 +1,472 @@ +//! Agent ergonomics: the seams where AI coding agents actually trip. +//! +//! Three classes of failure are covered here: +//! 1. Global flags must parse on either side of the subcommand. +//! 2. Argument-parse failures must honour `--output json` like every other error. +//! 3. `schema` must advertise the whole command surface, with consent metadata. + +mod common; + +use serde_json::Value; +use std::fs; +use std::process::Command as StdCommand; +use tempfile::tempdir; + +fn setup_repo() -> tempfile::TempDir { + common::setup_test_env(); + + let dir = tempdir().expect("Failed to create temp directory"); + + for args in [ + vec!["init"], + vec!["config", "user.name", "Test User"], + vec!["config", "user.email", "test@example.com"], + ] { + StdCommand::new("git") + .args(&args) + .current_dir(&dir) + .output() + .unwrap_or_else(|e| panic!("git {args:?} failed: {e}")); + } + + fs::write(dir.path().join("tracked.txt"), "initial\n").expect("Failed to write tracked file"); + StdCommand::new("git") + .args(["add", "tracked.txt"]) + .current_dir(&dir) + .output() + .expect("Failed to stage tracked file"); + StdCommand::new("git") + .args(["commit", "-m", "feat: initial"]) + .current_dir(&dir) + .output() + .expect("Failed to create initial commit"); + + dir +} + +fn stdout_of(assert: &assert_cmd::assert::Assert) -> String { + String::from_utf8(assert.get_output().stdout.clone()).expect("stdout was not valid UTF-8") +} + +// --------------------------------------------------------------------------- +// 1. Global flag placement +// --------------------------------------------------------------------------- + +/// The documented form. Guards against a regression while fixing the rest. +#[test] +fn test_global_flags_before_subcommand_still_parse() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args([ + "--non-interactive", + "-q", + "schema", + "--repo-path", + ".", + "--output", + "json", + ]) + .assert() + .success(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()).unwrap(); + assert_eq!(v["command"], Value::String("schema".into())); +} + +/// The form agents actually write: global flags after the subcommand. +#[test] +fn test_non_interactive_parses_after_subcommand() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args([ + "schema", + "--non-interactive", + "--repo-path", + ".", + "--output", + "json", + ]) + .assert() + .success(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()).unwrap(); + assert_eq!(v["command"], Value::String("schema".into())); +} + +#[test] +fn test_quiet_parses_after_subcommand() { + let temp_dir = setup_repo(); + + for flag in ["-q", "--quiet"] { + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args(["schema", flag, "--repo-path", ".", "--output", "json"]) + .assert() + .success(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()) + .unwrap_or_else(|e| panic!("`schema {flag}` did not emit JSON: {e}")); + assert_eq!(v["command"], Value::String("schema".into())); + } +} + +/// `--verbose` is deliberately *not* global. +/// +/// `-v` is `branch --validate` and `--verbose` is a real flag on +/// `config validate|show`. Promoting the root flag would silently steal both. +/// This test pins that trade-off so a later change has to confront it. +#[test] +fn test_verbose_stays_root_only_because_subcommands_own_it() { + let temp_dir = setup_repo(); + + common::committy_cmd() + .current_dir(&temp_dir) + .args([ + "--verbose", + "schema", + "--repo-path", + ".", + "--output", + "json", + ]) + .assert() + .success(); + + // `branch -v` must still mean `--validate`, not root verbosity. + common::committy_cmd() + .current_dir(&temp_dir) + .args([ + "branch", + "-v", + "--name", + "feat-keeps-validate", + "--output", + "json", + ]) + .assert() + .success(); +} + +/// Mutating commands take the same treatment, not just read-only ones. +#[test] +fn test_global_flags_after_subcommand_on_branch_and_commit() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args([ + "branch", + "--non-interactive", + "--name", + "feat-agent-ergonomics", + "--dry-run", + "--output", + "json", + ]) + .assert() + .success(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()).unwrap(); + assert_eq!(v["command"], Value::String("branch".into())); + assert_eq!(v["dry_run"], Value::Bool(true)); + + // `commit` reports a command-level error here (nothing staged), but it must + // be a *parsed* command-level error, not an argv rejection. + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args([ + "commit", + "--non-interactive", + "--type", + "feat", + "--message", + "agent ergonomics", + "--dry-run", + "--output", + "json", + ]) + .assert(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()) + .expect("commit must emit a JSON envelope, not an argv rejection"); + assert_eq!(v["command"], Value::String("commit".into())); +} + +// --------------------------------------------------------------------------- +// 2. Argument-parse errors must honour --output json +// --------------------------------------------------------------------------- + +#[test] +fn test_unknown_flag_with_json_output_emits_error_envelope() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args(["schema", "--output", "json", "--definitely-not-a-flag"]) + .assert() + .failure(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()) + .expect("an argv rejection must still produce one JSON document on stdout"); + + assert_eq!(v["api_version"], Value::from(1)); + assert_eq!(v["command"], Value::String("schema".into())); + assert_eq!(v["ok"], Value::Bool(false)); + assert_eq!( + v["errors"][0]["code"], + Value::String("invalid_usage".into()) + ); + assert!( + v["errors"][0]["message"] + .as_str() + .expect("message must be a string") + .contains("--definitely-not-a-flag"), + "the envelope must name the offending argument so an agent can self-correct" + ); +} + +#[test] +fn test_unknown_flag_with_equals_form_json_output_emits_envelope() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args(["schema", "--output=json", "--definitely-not-a-flag"]) + .assert() + .failure(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()) + .expect("--output=json must be recognised as well as --output json"); + assert_eq!(v["ok"], Value::Bool(false)); + assert_eq!( + v["errors"][0]["code"], + Value::String("invalid_usage".into()) + ); +} + +/// An unknown subcommand cannot be attributed to a command; the envelope still +/// has to be valid so the agent's parser does not blow up. +#[test] +fn test_unknown_subcommand_with_json_output_emits_envelope() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args(["nonexistent-command", "--output", "json"]) + .assert() + .failure(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()) + .expect("an unknown subcommand must still produce one JSON document"); + assert_eq!(v["ok"], Value::Bool(false)); + assert_eq!(v["command"], Value::String("unknown".into())); +} + +/// Humans keep the terminal-friendly behaviour: plain text on stderr, no JSON. +#[test] +fn test_unknown_flag_without_json_output_stays_human_readable() { + let temp_dir = setup_repo(); + + let assert = common::committy_cmd() + .current_dir(&temp_dir) + .args(["schema", "--definitely-not-a-flag"]) + .assert() + .failure(); + + assert!( + stdout_of(&assert).trim().is_empty(), + "text mode must not print a JSON document to stdout" + ); + let stderr = String::from_utf8(assert.get_output().stderr.clone()).unwrap(); + assert!( + stderr.contains("--definitely-not-a-flag"), + "text mode must still explain the problem on stderr" + ); +} + +// --------------------------------------------------------------------------- +// 3. Capability discovery +// --------------------------------------------------------------------------- + +fn capabilities_of(temp_dir: &tempfile::TempDir) -> Vec { + let assert = common::committy_cmd() + .current_dir(temp_dir) + .args(["--non-interactive", "schema", "--output", "json"]) + .assert() + .success(); + + let v: Value = serde_json::from_str(stdout_of(&assert).trim()).unwrap(); + v["capabilities"] + .as_array() + .expect("schema must expose a capabilities array") + .clone() +} + +#[test] +fn test_capabilities_cover_the_whole_agent_command_surface() { + let temp_dir = setup_repo(); + let capabilities = capabilities_of(&temp_dir); + + let names: Vec<&str> = capabilities + .iter() + .map(|c| c["name"].as_str().expect("capability needs a name")) + .collect(); + + // Every command the shipped skills instruct an agent to run. + for expected in [ + "branch.preview", + "branch.apply", + "branch.lint", + "commit.preview", + "commit.apply", + "commit.amend", + "commit.lint", + "group-commit.plan", + "group-commit.apply", + "bump.preview", + "bump.apply", + "changelog.preview", + "tag.preview", + "tag.apply", + "tag.publish", + "config.validate", + "packages.list", + "hooks.install", + "hooks.commit-msg", + "hooks.pre-push", + "schema.discover", + ] { + assert!( + names.contains(&expected), + "capability `{expected}` is missing; skills hard-code it today. Present: {names:?}" + ); + } +} + +#[test] +fn test_capabilities_declare_consent_metadata() { + let temp_dir = setup_repo(); + let capabilities = capabilities_of(&temp_dir); + + for capability in &capabilities { + let name = capability["name"].as_str().unwrap(); + assert!( + capability["mutating"].is_boolean(), + "capability `{name}` must declare `mutating`" + ); + assert!( + capability["requires_confirmation"].is_boolean(), + "capability `{name}` must declare `requires_confirmation`" + ); + assert!( + capability["description"].is_string(), + "capability `{name}` must keep its description" + ); + } + + let by_name = |needle: &str| -> Value { + capabilities + .iter() + .find(|c| c["name"] == Value::String(needle.into())) + .unwrap_or_else(|| panic!("missing capability {needle}")) + .clone() + }; + + // Previews never touch the repository. + for read_only in ["branch.preview", "commit.preview", "schema.discover"] { + assert_eq!( + by_name(read_only)["mutating"], + Value::Bool(false), + "{read_only} must not be marked mutating" + ); + assert_eq!( + by_name(read_only)["requires_confirmation"], + Value::Bool(false) + ); + } + + // Local writes mutate but do not need the remote-mutation confirmation flag. + assert_eq!(by_name("commit.apply")["mutating"], Value::Bool(true)); + assert_eq!( + by_name("commit.apply")["requires_confirmation"], + Value::Bool(false) + ); + + // Remote mutation is the case an agent must never infer consent for. + assert_eq!(by_name("tag.publish")["mutating"], Value::Bool(true)); + assert_eq!( + by_name("tag.publish")["requires_confirmation"], + Value::Bool(true), + "publishing must advertise that it needs explicit confirmation" + ); +} + +// --------------------------------------------------------------------------- +// 4. The escape hatch must be discoverable where agents read +// --------------------------------------------------------------------------- + +#[test] +fn test_agent_docs_mention_the_non_interactive_env_var() { + let repo_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); + + // `AGENTS.md` is canonical. Per-runtime files may either restate the rule + // or defer to it; what must never happen is a runtime being told nothing. + let agent_facing = [ + "AGENTS.md", + "CLAUDE.md", + "GEMINI.md", + ".github/copilot-instructions.md", + "docs/src/content/docs/reference/agent-workflows.mdx", + ]; + + for relative in agent_facing { + let body = fs::read_to_string(repo_root.join(relative)) + .unwrap_or_else(|e| panic!("cannot read {relative}: {e}")); + let defers_to_canonical = body.contains("AGENTS.md"); + assert!( + body.contains("COMMITTY_NONINTERACTIVE") || defers_to_canonical, + "{relative} must document COMMITTY_NONINTERACTIVE or point at AGENTS.md; \ + it is the escape hatch that makes flag placement irrelevant for agents" + ); + + // Documenting `-v` as global would be worse than not documenting + // placement at all: it reads as permission to write `schema -v`. + assert!( + !body.contains("`--non-interactive`, `-q`, `-v`"), + "{relative} lists -v among the global flags, but --verbose is root-only" + ); + } +} + +/// The cross-tool skill discovery path must resolve to the real skills. +/// +/// `.agents/skills` is a symlink. If a checkout materialises it as a plain +/// file (Windows without developer mode, or `core.symlinks=false`), discovery +/// silently breaks for every runtime that reads that path. +#[test] +fn test_skills_are_discoverable_at_the_cross_tool_path() { + let repo_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); + let cross_tool = repo_root.join(".agents/skills"); + + assert!( + cross_tool.is_dir(), + ".agents/skills must resolve to a directory; if this is a plain file, \ + the symlink did not survive checkout" + ); + + for skill in [ + "committy-branch", + "committy-commit", + "committy-enforce", + "committy-release", + ] { + assert!( + cross_tool.join(skill).join("SKILL.md").is_file(), + "{skill}/SKILL.md must be reachable via .agents/skills" + ); + } +} diff --git a/tests/cli_surface_tests.rs b/tests/cli_surface_tests.rs index 0ac8d47..e22cad4 100644 --- a/tests/cli_surface_tests.rs +++ b/tests/cli_surface_tests.rs @@ -203,10 +203,32 @@ fn test_schema_example_info_ls_and_version_json() { .success(), ); assert_eq!(schema["command"], Value::String("schema".into())); + assert_eq!(schema["api_version"], Value::from(1)); assert_eq!( schema["convention"], Value::String("conventional-commits".into()) ); + assert!(schema["types"] + .as_array() + .unwrap() + .contains(&Value::from("feat"))); + let feat = schema["type_definitions"] + .as_array() + .unwrap() + .iter() + .find(|item| item["name"] == "feat") + .unwrap(); + assert_eq!(feat["description"], "A new feature"); + assert!(feat["contexts"] + .as_array() + .unwrap() + .contains(&Value::from("branch"))); + assert!(schema["capabilities"] + .as_array() + .unwrap() + .iter() + .any(|item| item["name"] == "branch.preview" + && item["description"].as_str().unwrap().contains("without"))); let example = parse_stdout( common::committy_cmd() diff --git a/tests/guardrail_hook_tests.rs b/tests/guardrail_hook_tests.rs new file mode 100644 index 0000000..bea25c9 --- /dev/null +++ b/tests/guardrail_hook_tests.rs @@ -0,0 +1,180 @@ +//! Behaviour matrix for the local agent guardrail. +//! +//! `.claude/hooks/block-dangerous-git.sh` is a PreToolUse hook: it reads the +//! tool input as JSON on stdin and exits 2 to refuse the command. Nothing +//! executed it before, so its deny rules were unverified — which matters more +//! than for ordinary code, because a guardrail that silently stops refusing +//! looks exactly like one that works. +//! +//! Exit codes: 0 = allowed, 2 = blocked. + +#![cfg(unix)] + +use std::io::Write; +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +fn hook_path() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".claude/hooks/block-dangerous-git.sh") +} + +/// Feed a command to the hook exactly as the harness would, and report whether +/// it was refused. +fn is_blocked(command: &str) -> bool { + let payload = serde_json::json!({ "tool_input": { "command": command } }).to_string(); + + let mut child = Command::new("bash") + .arg(hook_path()) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to run the guardrail hook"); + + child + .stdin + .take() + .expect("hook stdin") + .write_all(payload.as_bytes()) + .expect("failed to write hook input"); + + let status = child.wait().expect("hook did not exit"); + match status.code() { + Some(0) => false, + Some(2) => true, + other => panic!("hook returned unexpected exit code {other:?} for: {command}"), + } +} + +// --------------------------------------------------------------------------- +// Deny side — the reason the hook exists +// --------------------------------------------------------------------------- + +#[test] +fn refuses_unguarded_force_push() { + for command in [ + "git push --force origin main", + "git push -f origin main", + "git push origin main --force", + ] { + assert!(is_blocked(command), "must refuse: {command}"); + } +} + +#[test] +fn refuses_hard_reset() { + assert!(is_blocked("git reset --hard HEAD~1")); + assert!(is_blocked("git reset --hard origin/main")); +} + +#[test] +fn refuses_forced_clean() { + for command in ["git clean -f", "git clean -fd", "git clean -xfd"] { + assert!(is_blocked(command), "must refuse: {command}"); + } +} + +#[test] +fn refuses_forced_branch_delete() { + assert!(is_blocked("git branch -D some-branch")); +} + +#[test] +fn refuses_bulk_working_tree_discard() { + for command in ["git checkout .", "git restore .", "git checkout -- ."] { + assert!(is_blocked(command), "must refuse: {command}"); + } +} + +/// The old hook matched raw substrings, so these evaded it entirely. +#[test] +fn refuses_destructive_commands_that_evaded_substring_matching() { + for command in [ + "git reset --hard HEAD~1", // two spaces + "env FOO=1 git reset --hard HEAD~1", // leading assignment + "git -C . push --force origin main", // -C before the subcommand + "cd /tmp && git clean -fd", // not at the start of the command + ] { + assert!(is_blocked(command), "must refuse: {command}"); + } +} + +// --------------------------------------------------------------------------- +// Allow side — over-blocking is what deadlocked this repo's own workflow +// --------------------------------------------------------------------------- + +#[test] +fn allows_ordinary_pushes() { + for command in [ + "git push", + "git push -u origin my-branch", + "git push origin --delete stale-branch", + "git push --force-with-lease origin my-branch", + ] { + assert!(!is_blocked(command), "must allow: {command}"); + } +} + +#[test] +fn allows_non_destructive_git() { + for command in [ + "git status --short", + "git commit -m 'feat: something'", + "git checkout -b new-branch", + "git restore --staged src/main.rs", + "git branch -d merged-branch", + "git reset HEAD~1", + ] { + assert!(!is_blocked(command), "must allow: {command}"); + } +} + +#[test] +fn allows_commands_that_are_not_git() { + for command in ["ls -la", "cargo test", "rm -rf target"] { + assert!(!is_blocked(command), "must allow: {command}"); + } +} + +/// The failure that motivated the rewrite: the old hook matched the *text* of +/// a command, so writing documentation that merely named one was refused — +/// including, circularly, the heredoc that would have replaced the hook. +#[test] +fn allows_a_heredoc_that_writes_prose_naming_a_dangerous_command() { + let command = "cat > notes.md <<'EOF'\n\ + Run git push --force to overwrite.\n\ + git reset --hard undoes work.\n\ + EOF"; + assert!( + !is_blocked(command), + "a heredoc written to a file is data, not an invocation" + ); +} + +/// A heredoc fed to an interpreter *is* executed, so its body must still be +/// scanned. Dropping every heredoc body would make this a trivial bypass. +#[test] +fn still_refuses_a_destructive_command_inside_an_interpreter_heredoc() { + for command in [ + "bash <<'EOF'\ngit reset --hard HEAD~1\nEOF", + "sh < /tmp/note.txt"), + "quoted text is not distinguishable from `bash -c \"...\"`, so it stays blocked" + ); +} diff --git a/tests/hooks_cli_tests.rs b/tests/hooks_cli_tests.rs new file mode 100644 index 0000000..8bf981b --- /dev/null +++ b/tests/hooks_cli_tests.rs @@ -0,0 +1,170 @@ +mod common; + +use serde_json::Value; +use std::fs; +use std::os::unix::fs::PermissionsExt; +use std::process::Command as StdCommand; +use tempfile::tempdir; + +fn setup_repo() -> tempfile::TempDir { + common::setup_test_env(); + let dir = tempdir().unwrap(); + StdCommand::new("git") + .arg("init") + .current_dir(&dir) + .output() + .unwrap(); + dir +} + +#[test] +fn hooks_install_dry_run_lists_files_without_writing() { + let repo = setup_repo(); + let assert = common::committy_cmd() + .current_dir(&repo) + .args([ + "--non-interactive", + "hooks", + "install", + "--with-ci", + "--dry-run", + "--output", + "json", + ]) + .assert() + .success(); + + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["api_version"], 1); + assert_eq!(payload["command"], "hooks"); + assert_eq!(payload["dry_run"], true); + assert_eq!(payload["installed"], false); + assert_eq!(payload["files"].as_array().unwrap().len(), 3); + assert!(!repo.path().join(".git/hooks/commit-msg").exists()); + assert!(!repo.path().join(".github/workflows/committy.yml").exists()); +} + +#[test] +fn hooks_install_writes_executable_hooks_and_ci_workflow() { + let repo = setup_repo(); + common::committy_cmd() + .current_dir(&repo) + .args([ + "--non-interactive", + "hooks", + "install", + "--with-ci", + "--output", + "json", + ]) + .assert() + .success(); + + let commit_msg = repo.path().join(".git/hooks/commit-msg"); + let pre_push = repo.path().join(".git/hooks/pre-push"); + let workflow = repo.path().join(".github/workflows/committy.yml"); + assert!(fs::read_to_string(&commit_msg) + .unwrap() + .contains("hooks run commit-msg")); + assert!(fs::read_to_string(&pre_push) + .unwrap() + .contains("hooks run pre-push")); + // The workflow must lint the PR range, and must do so without prompting. + // Non-interactive mode comes from COMMITTY_NONINTERACTIVE rather than the + // flag, which keeps the command immune to flag-placement mistakes. + let workflow_body = fs::read_to_string(workflow).unwrap(); + assert!( + workflow_body.contains("committy lint --from-ref"), + "workflow must lint the pull request commit range" + ); + assert!( + workflow_body.contains("COMMITTY_NONINTERACTIVE") + || workflow_body.contains("--non-interactive"), + "workflow must run non-interactively" + ); + assert!( + workflow_body.contains("fetch-depth: 0"), + "linting a range needs full history" + ); + assert_ne!( + fs::metadata(commit_msg).unwrap().permissions().mode() & 0o111, + 0 + ); +} + +#[test] +fn hooks_run_commit_msg_preserves_lint_exit_code() { + let repo = setup_repo(); + let message = repo.path().join("COMMIT_EDITMSG"); + fs::write(&message, "not conventional\n").unwrap(); + + common::committy_cmd() + .current_dir(&repo) + .arg("--non-interactive") + .arg("hooks") + .arg("run") + .arg("commit-msg") + .arg("--message-file") + .arg(&message) + .arg("--output") + .arg("json") + .assert() + .code(3); +} + +#[test] +fn hooks_run_pre_push_lints_outgoing_commits() { + let repo = setup_repo(); + for (key, value) in [ + ("user.name", "Test User"), + ("user.email", "test@example.com"), + ] { + StdCommand::new("git") + .args(["config", key, value]) + .current_dir(&repo) + .output() + .unwrap(); + } + fs::write(repo.path().join("tracked.txt"), "content\n").unwrap(); + StdCommand::new("git") + .args(["add", "tracked.txt"]) + .current_dir(&repo) + .output() + .unwrap(); + StdCommand::new("git") + .args(["commit", "-m", "not conventional", "--no-verify"]) + .current_dir(&repo) + .output() + .unwrap(); + let sha = String::from_utf8( + StdCommand::new("git") + .args(["rev-parse", "HEAD"]) + .current_dir(&repo) + .output() + .unwrap() + .stdout, + ) + .unwrap(); + let input = format!( + "refs/heads/test {} refs/heads/test {}\n", + sha.trim(), + "0".repeat(40) + ); + + let assert = common::committy_cmd() + .current_dir(&repo) + .args([ + "--non-interactive", + "hooks", + "run", + "pre-push", + "--output", + "json", + ]) + .write_stdin(input) + .assert() + .code(3); + let payload: Value = serde_json::from_slice(&assert.get_output().stdout).unwrap(); + assert_eq!(payload["hook"], "pre-push"); + assert!(payload["count"].as_u64().unwrap() > 0); +} diff --git a/tests/plugin_manifest_tests.rs b/tests/plugin_manifest_tests.rs new file mode 100644 index 0000000..d18fb4a --- /dev/null +++ b/tests/plugin_manifest_tests.rs @@ -0,0 +1,257 @@ +//! Public-seam tests for the agent plugin surface. +//! +//! `AGENTS.md` asks for tests over "machine output, exit codes, Git behavior, +//! hook protocol, and plugin manifests". The first four were covered; these +//! close the last. A typo in any of these files currently ships silently, +//! because nothing compiles or executes them. + +use serde_json::Value; +use std::collections::BTreeSet; +use std::fs; +use std::path::{Path, PathBuf}; + +fn repo_root() -> &'static Path { + Path::new(env!("CARGO_MANIFEST_DIR")) +} + +fn read_json(relative: &str) -> Value { + let path = repo_root().join(relative); + let body = fs::read_to_string(&path).unwrap_or_else(|e| panic!("cannot read {relative}: {e}")); + serde_json::from_str(&body).unwrap_or_else(|e| panic!("{relative} is not valid JSON: {e}")) +} + +fn skill_dirs() -> Vec { + let skills = repo_root().join("plugins/committy/skills"); + let mut dirs: Vec = fs::read_dir(&skills) + .expect("plugins/committy/skills must exist") + .filter_map(|entry| entry.ok()) + .map(|entry| entry.path()) + .filter(|path| path.is_dir()) + .collect(); + dirs.sort(); + assert!(!dirs.is_empty(), "no skills found"); + dirs +} + +/// Split `SKILL.md` into its YAML frontmatter and body. +fn frontmatter_of(skill_md: &Path) -> String { + let body = fs::read_to_string(skill_md) + .unwrap_or_else(|e| panic!("cannot read {}: {e}", skill_md.display())); + let rest = body + .strip_prefix("---\n") + .unwrap_or_else(|| panic!("{} must open with YAML frontmatter", skill_md.display())); + let end = rest + .find("\n---\n") + .unwrap_or_else(|| panic!("{} frontmatter is not terminated", skill_md.display())); + rest[..end].to_string() +} + +// --------------------------------------------------------------------------- +// Marketplace and plugin manifests +// --------------------------------------------------------------------------- + +#[test] +fn claude_marketplace_manifest_is_well_formed() { + let manifest = read_json(".claude-plugin/marketplace.json"); + + assert!(manifest["name"].is_string(), "marketplace needs a name"); + let plugins = manifest["plugins"] + .as_array() + .expect("marketplace needs a plugins array"); + assert!(!plugins.is_empty()); + + for plugin in plugins { + let source = plugin["source"] + .as_str() + .expect("each plugin entry needs a string source"); + let resolved = repo_root().join(source.trim_start_matches("./")); + assert!( + resolved.is_dir(), + "marketplace points at `{source}`, which does not exist" + ); + assert!( + resolved.join(".claude-plugin/plugin.json").is_file(), + "`{source}` has no .claude-plugin/plugin.json" + ); + } +} + +#[test] +fn codex_marketplace_manifest_is_well_formed() { + let manifest = read_json(".agents/plugins/marketplace.json"); + + let plugins = manifest["plugins"] + .as_array() + .expect("marketplace needs a plugins array"); + assert!(!plugins.is_empty()); + + for plugin in plugins { + let path = plugin["source"]["path"] + .as_str() + .expect("each plugin entry needs source.path"); + let resolved = repo_root().join(path.trim_start_matches("./")); + assert!( + resolved.is_dir(), + "marketplace points at `{path}`, which does not exist" + ); + assert!( + resolved.join(".codex-plugin/plugin.json").is_file(), + "`{path}` has no .codex-plugin/plugin.json" + ); + } +} + +#[test] +fn plugin_manifests_agree_on_identity() { + let claude = read_json("plugins/committy/.claude-plugin/plugin.json"); + let codex = read_json("plugins/committy/.codex-plugin/plugin.json"); + + for field in ["name", "version", "license"] { + assert_eq!( + claude[field], codex[field], + "plugin manifests disagree on `{field}`; they describe the same plugin" + ); + } + assert!(claude["name"].is_string()); + assert!( + claude["version"].is_string(), + "plugin needs a version string" + ); +} + +#[test] +fn codex_manifest_skills_path_resolves() { + let codex = read_json("plugins/committy/.codex-plugin/plugin.json"); + let skills = codex["skills"] + .as_str() + .expect("codex manifest declares a skills path"); + let resolved = repo_root() + .join("plugins/committy") + .join(skills.trim_start_matches("./")); + assert!( + resolved.is_dir(), + "codex manifest points skills at `{skills}`, which does not resolve" + ); +} + +// --------------------------------------------------------------------------- +// SKILL.md frontmatter, against the Agent Skills spec +// --------------------------------------------------------------------------- + +/// The spec fixes the allowed key set; anything else fails validation in a +/// spec-compliant runtime, so an unrecognised key here is a shipping bug. +const SPEC_KEYS: &[&str] = &[ + "name", + "description", + "license", + "allowed-tools", + "metadata", + "compatibility", +]; + +#[test] +fn skill_frontmatter_uses_only_spec_keys() { + for dir in skill_dirs() { + let skill_md = dir.join("SKILL.md"); + let frontmatter = frontmatter_of(&skill_md); + + let keys: BTreeSet = frontmatter + .lines() + .filter(|line| !line.starts_with(char::is_whitespace) && !line.trim().is_empty()) + .filter_map(|line| line.split_once(':').map(|(key, _)| key.trim().to_string())) + .collect(); + + for key in &keys { + assert!( + SPEC_KEYS.contains(&key.as_str()), + "{}: `{key}` is not an Agent Skills frontmatter key (allowed: {SPEC_KEYS:?})", + skill_md.display() + ); + } + for required in ["name", "description"] { + assert!( + keys.contains(required), + "{}: `{required}` is required", + skill_md.display() + ); + } + } +} + +#[test] +fn skill_name_matches_its_directory() { + for dir in skill_dirs() { + let expected = dir.file_name().unwrap().to_str().unwrap(); + let frontmatter = frontmatter_of(&dir.join("SKILL.md")); + let name = frontmatter + .lines() + .find_map(|line| line.strip_prefix("name:")) + .map(str::trim) + .expect("skill needs a name"); + assert_eq!( + name, expected, + "skill directory and frontmatter name must match, or discovery breaks" + ); + } +} + +#[test] +fn skill_descriptions_say_when_to_use_the_skill() { + for dir in skill_dirs() { + let frontmatter = frontmatter_of(&dir.join("SKILL.md")); + let description = frontmatter + .lines() + .find_map(|line| line.strip_prefix("description:")) + .map(str::trim) + .expect("skill needs a description"); + + // Description is the only thing a runtime sees before loading the body, + // so it has to carry the trigger, not just the capability. + assert!( + description.len() > 40, + "{}: description is too thin to route on: {description}", + dir.display() + ); + assert!( + description.contains("Use when"), + "{}: description must say when to use the skill: {description}", + dir.display() + ); + } +} + +#[test] +fn skills_do_not_use_runtime_specific_invocation_syntax() { + for dir in skill_dirs() { + let skill_md = dir.join("SKILL.md"); + let body = fs::read_to_string(&skill_md).unwrap(); + + // `$skill` is Codex syntax; Claude Code uses `/plugin:skill`. The body + // is shared by both runtimes, so neither form belongs in it. + assert!( + !body.contains("$committy-"), + "{}: uses Codex `$skill` syntax in a body shared with Claude Code", + skill_md.display() + ); + assert!( + !body.contains("/committy:committy-"), + "{}: uses Claude `/plugin:skill` syntax in a body shared with Codex", + skill_md.display() + ); + } +} + +#[test] +fn every_skill_is_reachable_from_both_discovery_paths() { + for dir in skill_dirs() { + let name = dir.file_name().unwrap(); + assert!( + repo_root() + .join(".agents/skills") + .join(name) + .join("SKILL.md") + .is_file(), + "{name:?} is not reachable via the cross-tool .agents/skills path" + ); + } +} diff --git a/tests/release_workflow_tests.rs b/tests/release_workflow_tests.rs new file mode 100644 index 0000000..d9a395e --- /dev/null +++ b/tests/release_workflow_tests.rs @@ -0,0 +1,38 @@ +//! The release workflow is the last place a version mistake can be caught before +//! binaries reach users, so the guard itself is worth pinning down. + +use std::fs; +use std::path::Path; + +fn release_workflow() -> String { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(".github/workflows/cargo-release.yml"); + fs::read_to_string(&path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display())) +} + +#[test] +fn release_workflow_verifies_cargo_version_against_the_tag() { + let workflow = release_workflow(); + + assert!( + workflow.contains("verify-version:"), + "the release workflow must carry a job that compares Cargo.toml with the tag" + ); + assert!( + workflow.contains(r#"if [ "v$VERSION" != "$TAG" ]; then"#), + "the guard must compare the Cargo.toml version with the tag name" + ); +} + +#[test] +fn every_release_job_waits_for_the_version_guard() { + let workflow = release_workflow(); + + assert!( + workflow.contains("needs: verify-version"), + "create-release must not publish a release before the version guard passes" + ); + assert!( + workflow.contains("needs: [verify-version, create-release]"), + "the build matrix must not upload assets before the version guard passes" + ); +} diff --git a/tests/tag_bump_files_tests.rs b/tests/tag_bump_files_tests.rs new file mode 100644 index 0000000..9516202 --- /dev/null +++ b/tests/tag_bump_files_tests.rs @@ -0,0 +1,255 @@ +mod common; + +use git2::{Repository, Signature}; +use predicates::prelude::*; +use std::fs; +use std::path::Path; +use tempfile::tempdir; + +/// A repository on `master` with a Cargo.toml and one feature commit, so the tag +/// command has something to calculate a bump from. +fn setup_cargo_repo() -> tempfile::TempDir { + let dir = tempdir().unwrap(); + let repo = Repository::init(dir.path()).unwrap(); + + let mut config = repo.config().unwrap(); + config.set_str("user.name", "Test User").unwrap(); + config.set_str("user.email", "test@example.com").unwrap(); + + fs::write( + dir.path().join("Cargo.toml"), + "[package]\nname = \"demo\"\nversion = \"0.0.1\"\nedition = \"2021\"\n", + ) + .unwrap(); + + let signature = Signature::now("Test User", "test@example.com").unwrap(); + let mut index = repo.index().unwrap(); + index.add_path(Path::new("Cargo.toml")).unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("HEAD"), + &signature, + &signature, + "feat: add the demo package", + &tree, + &[], + ) + .unwrap(); + + drop(tree); + dir +} + +fn cargo_version(dir: &Path) -> String { + let content = fs::read_to_string(dir.join("Cargo.toml")).unwrap(); + content + .lines() + .find_map(|line| line.strip_prefix("version = ")) + .unwrap() + .trim_matches('"') + .to_string() +} + +fn head_message(repo: &Repository) -> String { + repo.head() + .unwrap() + .peel_to_commit() + .unwrap() + .message() + .unwrap() + .trim() + .to_string() +} + +#[test] +fn tagging_bumps_version_files_by_default() { + let dir = setup_cargo_repo(); + + let mut cmd = common::committy_cmd(); + cmd.current_dir(dir.path()) + .arg("--non-interactive") + .arg("tag"); + cmd.assert().success(); + + let repo = Repository::open(dir.path()).unwrap(); + let mut tags = Vec::new(); + repo.tag_foreach(|_, name| { + tags.push(String::from_utf8_lossy(name).replace("refs/tags/", "")); + true + }) + .unwrap(); + assert_eq!(tags.len(), 1, "expected exactly one tag, got {tags:?}"); + let tag = &tags[0]; + + // The version file carries the tagged version, not the stale one. + assert_ne!(cargo_version(dir.path()), "0.0.1"); + assert_eq!(cargo_version(dir.path()), tag.trim_start_matches('v')); + + // And the bump is committed, so a build from the tag reports the new version. + assert!( + head_message(&repo).starts_with("chore: bump version to"), + "expected a bump commit at HEAD, got {:?}", + head_message(&repo) + ); + let tagged_commit = repo + .revparse_single(&format!("refs/tags/{tag}")) + .unwrap() + .peel_to_commit() + .unwrap(); + assert_eq!( + tagged_commit.id(), + repo.head().unwrap().peel_to_commit().unwrap().id(), + "the tag must point at the bump commit" + ); +} + +#[test] +fn no_bump_files_leaves_version_files_untouched() { + let dir = setup_cargo_repo(); + + let mut cmd = common::committy_cmd(); + cmd.current_dir(dir.path()) + .arg("--non-interactive") + .arg("tag") + .arg("--no-bump-files"); + cmd.assert().success(); + + assert_eq!(cargo_version(dir.path()), "0.0.1"); + + let repo = Repository::open(dir.path()).unwrap(); + assert_eq!(head_message(&repo), "feat: add the demo package"); +} + +#[test] +fn bump_files_flag_is_still_accepted() { + let dir = setup_cargo_repo(); + + let mut cmd = common::committy_cmd(); + cmd.current_dir(dir.path()) + .arg("--non-interactive") + .arg("tag") + .arg("--bump-files"); + cmd.assert().success(); + + assert_ne!(cargo_version(dir.path()), "0.0.1"); +} + +#[test] +fn conflicting_bump_flags_are_rejected() { + let dir = setup_cargo_repo(); + + let mut cmd = common::committy_cmd(); + cmd.current_dir(dir.path()) + .arg("--non-interactive") + .arg("tag") + .arg("--bump-files") + .arg("--no-bump-files"); + cmd.assert().failure().stderr(predicate::str::contains( + "Use either --bump-files or --no-bump-files, not both", + )); + + assert_eq!(cargo_version(dir.path()), "0.0.1"); +} + +/// Multi-package repositories used to stage the bumped version files without +/// committing them, so the tags landed on a commit that still carried the old +/// versions — the same defect that shipped a v1.10.0 tarball reporting 1.9.1. +#[test] +fn multi_package_bump_is_committed_before_tagging() { + let dir = tempdir().unwrap(); + let repo = Repository::init(dir.path()).unwrap(); + + let mut config = repo.config().unwrap(); + config.set_str("user.name", "Test User").unwrap(); + config.set_str("user.email", "test@example.com").unwrap(); + + fs::create_dir_all(dir.path().join("alpha")).unwrap(); + fs::write( + dir.path().join("alpha/Cargo.toml"), + "[package]\nname = \"alpha\"\nversion = \"0.0.1\"\n", + ) + .unwrap(); + fs::create_dir_all(dir.path().join(".committy")).unwrap(); + fs::write( + dir.path().join(".committy/config.toml"), + r#"[repository] +name = "workspace" +type = "multi-package" + +[versioning] +strategy = "independent" + +[[packages]] +name = "alpha" +type = "rust-cargo" +path = "alpha" +version_file = "Cargo.toml" +version_field = "package.version" +"#, + ) + .unwrap(); + + let signature = Signature::now("Test User", "test@example.com").unwrap(); + let mut index = repo.index().unwrap(); + index + .add_all(["."], git2::IndexAddOption::DEFAULT, None) + .unwrap(); + index.write().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); + repo.commit( + Some("HEAD"), + &signature, + &signature, + "feat(alpha): add the alpha package", + &tree, + &[], + ) + .unwrap(); + drop(tree); + + let mut cmd = common::committy_cmd(); + cmd.current_dir(dir.path()) + .arg("--non-interactive") + .arg("tag"); + cmd.assert().success(); + + let repo = Repository::open(dir.path()).unwrap(); + let head = repo.head().unwrap().peel_to_commit().unwrap(); + assert!( + head_message(&repo).starts_with("chore: bump version to alpha@"), + "expected a bump commit at HEAD, got {:?}", + head_message(&repo) + ); + + // The bumped file is in the tagged tree, not just in the working directory. + let mut tags = Vec::new(); + repo.tag_foreach(|_, name| { + tags.push(String::from_utf8_lossy(name).replace("refs/tags/", "")); + true + }) + .unwrap(); + assert_eq!(tags.len(), 1, "expected exactly one tag, got {tags:?}"); + let tagged_commit = repo + .revparse_single(&format!("refs/tags/{}", tags[0])) + .unwrap() + .peel_to_commit() + .unwrap(); + assert_eq!(tagged_commit.id(), head.id()); + + let version = tags[0].trim_start_matches("alpha-v").to_string(); + let blob = tagged_commit + .tree() + .unwrap() + .get_path(Path::new("alpha/Cargo.toml")) + .unwrap() + .to_object(&repo) + .unwrap(); + let content = String::from_utf8(blob.as_blob().unwrap().content().to_vec()).unwrap(); + assert!( + content.contains(&format!("version = \"{version}\"")), + "tagged tree still carries the old version: {content}" + ); +} diff --git a/tests/tag_tests.rs b/tests/tag_tests.rs index f347f5a..f676b19 100644 --- a/tests/tag_tests.rs +++ b/tests/tag_tests.rs @@ -266,6 +266,11 @@ fn test_tag_respects_repo_git_overrides_and_cli_can_override_them() { dir.path(), &[ "tag.gpgsign=true".to_string(), + // Pin the signing format. `gpg.program` is only consulted for + // openpgp; a machine with `gpg.format=ssh` in its global config + // routes signing to `gpg.ssh.program` instead, the stub below + // never runs, and this test silently asserts nothing. + "gpg.format=openpgp".to_string(), format!("gpg.program={}", failing_gpg.display()), ], );